Talos Linux 集群身份配置详解:DiscoveryIdentityConfig 配置文档指南
2026/9/23 14:10:06 网站建设 项目流程

Talos Linux 集群身份配置详解:DiscoveryIdentityConfig 配置文档指南

【免费下载链接】talosTalos Linux is a modern Linux distribution built for Kubernetes.项目地址: https://gitcode.com/gh_mirrors/ta/talos

导读

DiscoveryIdentityConfig是 Talos Linux 中一个专门用于配置集群身份的配置文档(config document),它向 Talos 的 discovery service(集群发现服务)提供全局唯一的集群标识符(cluster ID)与共享密钥(cluster secret)。本文将基于 Talos Linux v1.15 的官方参考文档,结合仓库源码深入剖析该配置文档的结构、字段语义、校验规则、生成方式以及与旧版.cluster.id/.cluster.secret配置的迁移关系。读完本文,你将掌握如何阅读、生成、验证与迁移DiscoveryIdentityConfig,并理解其在集群发现与 KubeSpan 网络中的安全作用。

一、DiscoveryIdentityConfig 是什么

Talos Linux 使用 discovery service 让集群中的节点彼此发现、交换信息,从而完成 etcd 集群组建、KubeSpan 加密网络建立等任务。为了让集群之间的信息隔离,discovery service 需要一个"集群身份"(cluster identity),由两部分组成:

  • cluster ID:全局唯一标识符,用于识别集群;
  • cluster secret:集群共享密钥,用于加密与认证集群成员之间的通信。

在 v1.15 版本中,该身份以独立的多文档(multi-doc)配置形式存在,即本文主角DiscoveryIdentityConfig。其官方描述为:"DiscoveryIdentityConfig is a config document to configure the cluster identity used by the discovery service"(见 官网参考文档)。

从源码接口定义看,DiscoveryIdentityConfig提供的正是这两个核心访问器(见 pkg/machinery/config/config/cluster.go):

// DiscoveryIdentityConfig provides the cluster identity (ID and shared secret) used by the // discovery service and KubeSpan. type DiscoveryIdentityConfig interface { ClusterID() string ClusterSecret() string }

注意注释中同时提到了KubeSpan:集群身份不仅服务于 discovery service,还被 KubeSpan 用于集群成员之间的加密网络认证。

二、配置文档结构

DiscoveryIdentityConfig是一个标准的 YAML 配置文档,与其他 Talos 配置文档一样,通过apiVersionkind标识自身。官方参考文档给出的完整示例如下:

apiVersion: v1alpha1 kind: DiscoveryIdentityConfig clusterID: cluster-id-base64-encoded-32-bytes # Globally unique identifier for this cluster (base64 encoded random 32 bytes). clusterSecret: cluster-secret-base64-encoded-32-bytes # Shared secret of cluster (base64 encoded random 32 bytes).

其字段定义如下表(来自 官方参考文档):

FieldTypeDescriptionValue(s)
clusterIDstringGlobally unique identifier for this cluster (base64 encoded random 32 bytes).
clusterSecretstringShared secret of cluster (base64 encoded random 32 bytes).
This secret is shared among cluster members but should never be sent over the network.

两个字段均为必填项(在 JSON Schema 中标记为schemaRequired: true),这在源码结构体中也有体现(见 pkg/machinery/config/types/cluster/discovery_identity.go):

type DiscoveryIdentityConfigV1Alpha1 struct { meta.Meta `yaml:",inline"` // description: | // Globally unique identifier for this cluster (base64 encoded random 32 bytes). // schemaRequired: true MetaClusterID string `yaml:"clusterID"` // description: | // Shared secret of cluster (base64 encoded random 32 bytes). // This secret is shared among cluster members but should never be sent over the network. // schemaRequired: true MetaClusterSecret string `yaml:"clusterSecret"` }

meta.Meta内嵌结构可知,该文档通过MetaKind: "DiscoveryIdentityConfig"MetaAPIVersion: "v1alpha1"完成自身标识(见 pkg/machinery/config/types/cluster/discovery_identity.go 中NewDiscoveryIdentityConfigV1Alpha1构造函数)。

三、字段语义与安全要点

3.1 clusterID:全局唯一标识符

clusterID是集群的全局唯一标识符,本质是32 字节随机数经过 base64 编码后的字符串。Talos 官方文档与源码注释均强调其作用:

  • 仅用于唯一标识集群,不会在网络上传输(discovery service 端只用它做身份关联);
  • 因为是唯一标识而非密钥,代码中从不解码它,因此校验时只检查非空,不校验 base64 编码与 32 字节长度。这一点在Validate()的注释中写得非常明确(见 pkg/machinery/config/types/cluster/discovery_identity.go):
// We don't need to validate the clusterID is base64 encoded nor that it's 32 bytes long, // because we only use it as a unique identifier. We never need to decode it.

3.2 clusterSecret:集群共享密钥

clusterSecret是集群成员之间共享的密钥,官方文档特别强调:"This secret is shared among cluster members but should never be sent over the network."(该密钥在集群成员间共享,但绝不应通过网络发送)。

clusterID不同,clusterSecret的约束严格得多。源码注释给出了根本原因(见 pkg/machinery/config/types/cluster/discovery_identity.go):

The cluster secret is used as an AES encryption key, so it must:

  • be base64 encoded (via StdEncoding)
  • decode to 32 bytes for AES-256

clusterSecret 被用作 AES-256 加密密钥,因此必须:

  1. 是合法的 base64 编码字符串(使用 StdEncoding);
  2. 解码后恰好为 32 字节(对应 AES-256 密钥长度)。

3.3 编码标准的变化(1.14 起统一为 StdEncoding)

仓库源码还记录了编码标准的一段历史(见 pkg/machinery/config/types/cluster/discovery_identity.go 与 Validate 注释):

var ( ClusterIDEncoding = base64.StdEncoding ClusterSecretEncoding = base64.StdEncoding )

在 Talos 1.14 之前,talosctl gen secrets生成的 cluster ID 使用URLEncoding,而代码库其余部分使用StdEncoding;从 Talos 1.14 开始两者已对齐,统一采用StdEncoding生成 cluster ID。这意味着如果手工构造配置,务必使用标准 base64 编码(带+/而非-_)。

四、校验规则:源码级验证逻辑

DiscoveryIdentityConfigV1Alpha1实现了config.Validator接口,其Validate()方法完整逻辑如下(见 pkg/machinery/config/types/cluster/discovery_identity.go):

func (s *DiscoveryIdentityConfigV1Alpha1) Validate(validation.RuntimeMode, ...validation.Option) ([]string, error) { if s.MetaClusterID == "" { return nil, errors.New("clusterID is required") } if s.MetaClusterSecret == "" { return nil, errors.New("clusterSecret is required") } if err := ValidateBase64WithLen(s.MetaClusterSecret, ClusterSecretEncoding, constants.DefaultClusterSecretSize); err != nil { return nil, fmt.Errorf("invalid clusterSecret: %w", err) } return nil, nil }

其中ValidateBase64WithLen是一个通用校验函数(见 pkg/machinery/config/types/cluster/discovery_identity.go):

func ValidateBase64WithLen(base64Str string, encoding *base64.Encoding, wantLenBytes int) error { decoded, err := encoding.DecodeString(base64Str) if err != nil { return fmt.Errorf("failed to decode from base64: %s; %w", base64Str, err) } if len(decoded) != wantLenBytes { return fmt.Errorf("expected %d bytes, got %d: %s", wantLenBytes, len(decoded), base64Str) } return nil }

对应的常量定义(见 pkg/machinery/constants/constants.go):

// DefaultClusterIDSize is the default size in bytes for the cluster ID token. DefaultClusterIDSize = 32 // DefaultClusterSecretSize is the default size in bytes for the cluster secret. DefaultClusterSecretSize = 32

校验规则汇总

规则说明
clusterID非空必填;不校验 base64 与长度(仅作唯一标识,从不解码)
clusterSecret非空必填
clusterSecret合法 base64使用 StdEncoding 解码,失败则报invalid clusterSecret: failed to decode from base64: ...
clusterSecret解码 32 字节对应 AES-256 密钥;长度不符则报invalid clusterSecret: expected 32 bytes, got N: ...

以上规则均有对应测试用例覆盖(见 pkg/machinery/config/types/cluster/discovery_identity_test.go),包括"合法配置"、"缺失 clusterID"、"非法 base64 的 clusterSecret"、"clusterSecret 长度错误(16 字节/66 字节/0 字节)"等场景,并断言了精确的错误前缀。

五、如何生成:talosctl gen secrets 与自动注入

5.1 手工生成随机值

由于clusterSecret必须是"32 字节随机数的 base64 编码",最稳妥的方式是使用talosctl gen secrets生成完整 secrets bundle,而不是手工拼凑。该命令的实现位于 cmd/talosctl/cmd/mgmt/gen/secrets.go:

talosctl gen secrets -o secrets.yaml

其核心逻辑调用secrets.NewBundle(),在填充 bundle 时生成随机的 cluster ID 与 cluster secret(见 pkg/machinery/config/generate/secrets/bundle.go):

if bundle.Cluster.ID == "" { clusterID, err := randBytes(constants.DefaultClusterIDSize) ... bundle.Cluster.ID = cluster.ClusterIDEncoding.EncodeToString(clusterID) } if bundle.Cluster.Secret == "" { clusterSecret, err := randBytes(constants.DefaultClusterSecretSize) ... bundle.Cluster.Secret = cluster.ClusterSecretEncoding.EncodeToString(clusterSecret) }

可以看到,生成逻辑严格遵循"32 字节随机数 + StdEncoding base64 编码"的规范。

5.2 配置生成时的自动注入

在通过talosctl gen config生成机器配置时,只要目标 Talos 版本满足条件,控制平面与 worker 配置都会自动携带DiscoveryIdentityConfig文档。生成器在版本契约判断通过后调用构造函数注入(见 pkg/machinery/config/generate/init.go 与 pkg/machinery/config/generate/worker.go):

if in.Options.VersionContract.DiscoveryIdentityMultidocConfig() { documents = append(documents, clustertypes.NewDiscoveryIdentityConfigV1Alpha1( in.Options.SecretsBundle.Cluster.ID, in.Options.SecretsBundle.Cluster.Secret, )) }

版本契约判断定义如下(见 pkg/machinery/config/contract.go):

// DiscoveryIdentityMultidocConfig returns true if version of Talos should use the multi-doc DiscoveryIdentityConfig. func (contract *VersionContract) DiscoveryIdentityMultidocConfig() bool { return contract.Greater(TalosVersion1_13) }

Talos 1.14 及以上版本采用独立的多文档DiscoveryIdentityConfig,而旧版本继续使用 legacy 的.cluster.id/.cluster.secret字段。这一行为有专门的生成测试验证(见 pkg/machinery/config/generate/generate_test.go):

  • 1.14 版本契约:生成 1 个DiscoveryIdentityConfig文档,同时 legacy 的.cluster.id/.cluster.secret为空;
  • 1.13 版本契约:不生成该文档,身份写入 legacy 字段;
  • 无论哪种形式,最终都能通过统一的cfg.DiscoveryIdentityConfig()访问器读到身份信息。

5.3 一个真实的合法示例

仓库测试数据中保存了一份完整可用的DiscoveryIdentityConfig配置(见 pkg/machinery/config/types/cluster/testdata/discoveryidentityconfig.yaml):

apiVersion: v1alpha1 kind: DiscoveryIdentityConfig clusterID: MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDE= clusterSecret: vlf2HU1NEZL3Ezi9Tk+RZBLJUbjnsHnTzs3wK9JNk6Q=

注意这里clusterID恰好是一个 StdEncoding 编码的 32 字节 base64 字符串,而clusterSecret是标准 32 字节 base64 编码。该文件被用于 marshal/unmarshal 稳定性测试(见 pkg/machinery/config/types/cluster/discovery_identity_test.go):序列化后与源文件逐字节一致,反序列化后能通过provider.DiscoveryIdentityConfig()访问器正确读取ClusterID()ClusterSecret()

六、与旧版配置的关系与迁移

6.1 互斥约束

DiscoveryIdentityConfig文档与旧版 v1alpha1 配置中的.cluster.id/.cluster.secret互斥。该约束通过container.V1Alpha1ConflictValidator接口实现(见 pkg/machinery/config/types/cluster/discovery_identity.go):

// The multi-doc DiscoveryIdentityConfig is mutually exclusive with the v1alpha1 cluster identity config. func (s *DiscoveryIdentityConfigV1Alpha1) V1Alpha1ConflictValidate(v1alpha1Cfg *v1alpha1.Config) error { if v1alpha1Cfg.ClusterConfig != nil && (v1alpha1Cfg.ClusterConfig.ClusterID != "" || v1alpha1Cfg.ClusterConfig.ClusterSecret != "") { return errors.New("cluster identity is already configured in .cluster.id/.cluster.secret of the v1alpha1 config") } return nil }

对应的冲突测试覆盖了三种情况:v1alpha1 配置为空、只有 ClusterConfig 但无身份字段、legacy 字段存在(见 pkg/machinery/config/types/cluster/discovery_identity_test.go)。

6.2 访问器优先级

在多文档容器中,DiscoveryIdentityConfig()访问器的取用逻辑是legacy 优先、文档兜底(见 pkg/machinery/config/container/container.go):

// The dedicated document and the deprecated v1alpha1 cluster identity (.cluster.id/.cluster.secret) are // mutually exclusive (enforced by DiscoveryIdentityConfigV1Alpha1.V1Alpha1ConflictValidate); the v1alpha1 // config takes priority. func (container *Container) DiscoveryIdentityConfig() config.DiscoveryIdentityConfig { // v1alpha1 cluster identity takes priority when it yields a config if container.v1alpha1Config != nil { if legacy := container.v1alpha1Config.DiscoveryIdentityConfig(); legacy != nil { return legacy } } // fallback to dedicated multi-doc. Take first, since this doc is not named. if docs := findMatchingDocsconfig.DiscoveryIdentityConfig; len(docs) > 0 { return docs[0] } return nil }

旧版字段通过一个适配器类型转换为统一接口(见 pkg/machinery/config/types/v1alpha1/v1alpha1_discoveryidentity.go),只有.cluster.id.cluster.secret至少存在一个时才返回身份对象。这意味着 Talos 内部(如 secrets bundle 重建、discovery 校验)可以透明地处理新旧两种形式。

七、依赖关系与运行时校验

7.1 discovery service 强依赖集群身份

容器级校验确保:只要启用了集群发现(DiscoveryServiceConfig),就必须存在集群身份,无论它以DiscoveryIdentityConfig文档还是 legacy 字段形式提供(见 pkg/machinery/config/container/validate.go):

// Discovery requires a cluster identity if discoveryConfigs := container.DiscoveryServiceConfigs(); len(discoveryConfigs) > 0 { identity := container.DiscoveryIdentityConfig() if identity == nil || identity.ClusterID() == "" { errs = multierror.Append(errs, fmt.Errorf("cluster ID (.cluster.id or DiscoveryIdentityConfig) should be set when cluster discovery (DiscoveryServiceConfig) is enabled")) } if identity == nil || identity.ClusterSecret() == "" { errs = multierror.Append(errs, fmt.Errorf("cluster secret (.cluster.secret or DiscoveryIdentityConfig) should be set when cluster discovery (DiscoveryServiceConfig) is enabled")) } }

7.2 KubeSpan 的联动要求

同一段校验代码还确认:启用 KubeSpan 必须同时启用集群发现(见 pkg/machinery/config/container/validate.go),而集群发现又依赖集群身份,因此DiscoveryIdentityConfig是 KubeSpan 加密网络得以成立的前置条件之一。

7.3 secrets bundle 的逆向读取

在从已有控制平面配置重建 secrets bundle 的场景中,NewBundleFromConfig会通过统一访问器读取集群身份(见 pkg/machinery/config/generate/secrets/bundle.go):

cluster := &Cluster{} if identity := c.DiscoveryIdentityConfig(); identity != nil { cluster.ID = identity.ClusterID() cluster.Secret = identity.ClusterSecret() }

这保证了无论配置采用哪种形式存储身份,后续工具链都能正确取回。

八、敏感信息处理与 Redact

由于clusterSecret属于机密信息,DiscoveryIdentityConfig实现了config.SecretDocument接口,在导出/脱敏配置时会将 secret 替换为占位符,而保留非机密的clusterID(见 pkg/machinery/config/types/cluster/discovery_identity.go):

// Redact implements config.SecretDocument interface. func (s *DiscoveryIdentityConfigV1Alpha1) Redact(replacement string) { if s.MetaClusterSecret != "" { s.MetaClusterSecret = replacement } }

对应的 Redact 测试验证了两点(见 pkg/machinery/config/types/cluster/discovery_identity_test.go):

  • 设置 secret 后,Redact("**.***")会把ClusterSecret()替换为占位符;
  • ClusterID()保持不变(它本身不是密钥)。

容器级测试同样验证了脱敏后的配置可通过访问器读到占位符(见 pkg/machinery/config/container/container_test.go)。因此在分享或归档机器配置时,可以放心依赖 Talos 的脱敏机制,避免泄露 cluster secret。

九、实践要点速查

  1. 不要手工编造clusterSecret:它必须是合法 StdEncoding base64 且解码后恰好 32 字节(AES-256 密钥)。优先使用talosctl gen secrets -o secrets.yaml生成。
  2. 1.14 及以上版本自动生成talosctl gen config会根据版本契约自动在控制平面与 worker 配置中注入DiscoveryIdentityConfig文档,无需手工添加。
  3. 与旧版互斥:若配置中仍存在.cluster.id/.cluster.secret,再添加DiscoveryIdentityConfig文档会触发校验错误,需二选一。
  4. clusterID仅作标识:它不会被解码,因此校验宽松;但为了保证全局唯一性,仍应按规范使用 32 字节随机数的 base64 编码。
  5. 保密意识clusterSecret在集群成员间共享但绝不应出现在网络上;导出配置时使用脱敏后的版本。
  6. JSON Schema:完整的字段约束也体现在 website/content/v1.15/schemas/config.schema.json 的cluster.DiscoveryIdentityConfigV1Alpha1定义中,可用于编辑器提示与静态校验。

十、深入阅读

  • 官方参考文档:website/content/v1.15/reference/configuration/cluster/discoveryidentityconfig.md
  • 核心实现:pkg/machinery/config/types/cluster/discovery_identity.go
  • 单元测试:pkg/machinery/config/types/cluster/discovery_identity_test.go
  • 测试样例:pkg/machinery/config/types/cluster/testdata/discoveryidentityconfig.yaml
  • 生成链路:pkg/machinery/config/generate/init.go、pkg/machinery/config/generate/worker.go、pkg/machinery/config/generate/secrets/bundle.go
  • 版本契约:pkg/machinery/config/contract.go
  • 容器访问与校验:pkg/machinery/config/container/container.go、pkg/machinery/config/container/validate.go
  • 命令入口:cmd/talosctl/cmd/mgmt/gen/secrets.go

【免费下载链接】talosTalos Linux is a modern Linux distribution built for Kubernetes.项目地址: https://gitcode.com/gh_mirrors/ta/talos

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询