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 配置文档一样,通过apiVersion与kind标识自身。官方参考文档给出的完整示例如下:
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).其字段定义如下表(来自 官方参考文档):
| Field | Type | Description | Value(s) |
|---|---|---|---|
clusterID | string | Globally unique identifier for this cluster (base64 encoded random 32 bytes). | |
clusterSecret | string | Shared 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 加密密钥,因此必须:
- 是合法的 base64 编码字符串(使用 StdEncoding);
- 解码后恰好为 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。
九、实践要点速查
- 不要手工编造
clusterSecret:它必须是合法 StdEncoding base64 且解码后恰好 32 字节(AES-256 密钥)。优先使用talosctl gen secrets -o secrets.yaml生成。 - 1.14 及以上版本自动生成:
talosctl gen config会根据版本契约自动在控制平面与 worker 配置中注入DiscoveryIdentityConfig文档,无需手工添加。 - 与旧版互斥:若配置中仍存在
.cluster.id/.cluster.secret,再添加DiscoveryIdentityConfig文档会触发校验错误,需二选一。 clusterID仅作标识:它不会被解码,因此校验宽松;但为了保证全局唯一性,仍应按规范使用 32 字节随机数的 base64 编码。- 保密意识:
clusterSecret在集群成员间共享但绝不应出现在网络上;导出配置时使用脱敏后的版本。 - 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),仅供参考