
External Secrets Operator 的 ClusterExternalSecret 详解多命名空间 ExternalSecret 分发、直连轮询优化与强制同步【免费下载链接】external-secretsExternal Secrets Operator reads information from a third-party service like AWS Secrets Manager and automatically injects the values as Kubernetes Secrets.项目地址: https://gitcode.com/GitHub_Trending/ex/external-secretsClusterExternalSecret是 External Secrets OperatorESO提供的一个集群作用域cluster-scoped资源用于在指定命名空间中批量创建和管理ExternalSecret。本文以官方 API 文档为核心结合仓库源码CRD 类型定义、控制器实现与运行时工具深入讲解其配置字段、命名空间选择机制、大规模场景下的 provider 调用优化fan-out 模式、强制同步与弃用项帮助你用它把一份密钥安全地复制到任意多个命名空间同时避免对上游 provider 的重复轮询。什么是 ClusterExternalSecretClusterExternalSecret是一个集群级资源它的作用是把一份ExternalSecret规格批量分发到符合条件的一个或多个命名空间。你只需要定义一次密钥来源与目标 Secret 的形态ESO 控制器就会在每一个匹配的命名空间里创建对应的ExternalSecret再由这些ExternalSecret各自把远程密钥同步为命名空间内的 KubernetesSecret。它特别适合以下场景多租户 / 多环境集群中需要在多个命名空间里部署同一份配置或凭证应用发布在多个命名空间但密钥源如 AWS Secrets Manager、Vault只有一个希望用标签驱动的方式声明这些命名空间都需要这份密钥命名空间一打标签即自动生效。一个需要注意的约束是如果目标命名空间中已存在同名资源控制器会报错对应状态里的failedNamespaces原因通常是external secret already exists in namespace。从资源定义看ClusterExternalSecret的类型声明位于 apis/externalsecrets/v1/clusterexternalsecret_types.go其控制器实现在 pkg/controllers/clusterexternalsecret/clusterexternalsecret_controller.go。API 组为external-secrets.io/v1短名shortName为ces见 apis/externalsecrets/v1/clusterexternalsecret_types.go#L119因此文档中常用kubectl annotate ces ...来操作它。工作方式与核心配置示例下面是官方文档中的完整ClusterExternalSecret示例原始文件为 docs/snippets/full-cluster-external-secret.yamlapiVersion: external-secrets.io/v1 kind: ClusterExternalSecret metadata: name: hello-world spec: # 生成的 ExternalSecrets 使用的名称。 # 省略时默认使用 ClusterExternalSecret 自身的名称。 externalSecretName: hello-world-es # 可选为每个被创建的 ExternalSecret 设置 labels 和 annotations。 externalSecretMetadata: labels: {} annotations: {} # 基本的 label selector用于选择要部署 ExternalSecrets 的命名空间。 # 已弃用Deprecated请改用 namespaceSelectors。 # namespaceSelector: # matchLabels: # cool: label # 一组基本 label selector用于选择要部署 ExternalSecrets 的命名空间。 # 多个 selector 之间是“或”OR关系只要任意一个匹配就会在该命名空间部署。 namespaceSelectors: - matchLabels: cool: label # 按名称选择命名空间与 namespaceSelectors 匹配的结果取“或”。 # 已弃用Deprecated请改用 namespaceSelectors。 # namespaces: # - my-namespace # ClusterExternalSecret 自身的调和reconcile频率。 # 决定控制器多久检查一次匹配的命名空间中 ExternalSecrets 是否存在。 # 省略时使用控制器的默认 requeue 间隔。 refreshTime: 1m # 待创建 ExternalSecrets 的 spec 模板内容与 ExternalSecret 示例一致。 externalSecretSpec: secretStoreRef: name: secret-store-name kind: SecretStore # RefreshPolicy 决定 ExternalSecret 如何刷新 # - CreatedOnce: 仅当 Secret 不存在时创建之后不再更新 # - Periodic:默认按 refreshInterval 指定的间隔同步 # - OnChange: 仅当 ExternalSecret 的 metadata 或 spec 变化时同步 refreshPolicy: Periodic refreshInterval: 1h0m0s target: name: my-secret creationPolicy: Merge template: type: kubernetes.io/dockerconfigjson metadata: annotations: {} labels: {} data: config.yml: | endpoints: - https://{{ .data.user }}:{{ .data.password }}api.exmaple.com templateFrom: - configMap: name: alertmanager items: - key: alertmanager.yaml data: - secretKey: secret-key-to-be-managed remoteRef: key: provider-key version: provider-key-version property: provider-key-property dataFrom: - key: provider-key version: provider-key-version property: provider-key-property status: # 列出创建 ExternalSecret 失败的命名空间。 # 注意这里不会列出 ExternalSecret 自身的问题需要单独查看那些 ExternalSecret。 failedNamespaces: - namespace: matching-ns-1 # 下面是最常见的失败原因之一 reason: external secret already exists in namespace # 所有匹配且成功部署的命名空间都列在这里 provisionedNamespaces: - matching-ns-3 - matching-ns-2 # 唯一的 condition 类型是 Ready。 # 全部匹配命名空间同步成功时 status 为 True # 有任意一个命名空间失败时 status 为 False失败的列在上面的 failedNamespaces。 conditions: - type: Ready status: False message: one or more namespaces failed lastTransitionTime: 2022-01-12T12:33:02Z各字段的行为说明对照 apis/externalsecrets/v1/clusterexternalsecret_types.go 中的ClusterExternalSecretSpec可以把上述字段归纳为几类字段类型说明源码要点externalSecretSpecExternalSecretSpec必填所有被创建 ExternalSecret 的规格模板控制器直接把它复制到每个 ExternalSecret 的specexternalSecretNamestring可选生成的 ExternalSecret 名称省略时默认使用 CES 自身名称有MinLength1、MaxLength253与 DNS 子域名正则校验改名时会先删除旧名称的 ExternalSecret见控制器reconcileexternalSecretMetadata结构体给每个生成的 ExternalSecret 附加的 labels / annotations见ExternalSecretMetadata类型namespaceSelector*LabelSelector已弃用单个标签选择器控制器会把旧字段与新字段合并后一起使用namespaceSelectors[]*LabelSelector推荐多个标签选择器OR关系与namespaces的结果再取 ORnamespaces[]string已弃用按名称精确选择命名空间在GetTargetNamespaces中被转换为kubernetes.io/metadata.name的标签选择器refreshTime*metav1.DurationCES 自身的重新调和间隔省略则用控制器默认 requeue 间隔控制器Reconcile返回RequeueAfter: refreshInt关于命名空间的最终匹配逻辑可以看 runtime/esutils/utils.go#L687 的GetTargetNamespaces它先把namespaces列表转换为kubernetes.io/metadata.name的标签选择器再与namespaceSelectors合并逐个执行List并去重。也就是说多个选择器之间全部是或的关系命中任何一个即被选中。而命名空间标签一旦变化控制器会通过NamespacePredicate()runtime/esutils/utils.go#L726触发对应 CES 的重新调和——因此给命名空间打标签 → 自动生成 ExternalSecret是即时生效的。状态字段failedNamespaces创建/更新失败的命名空间及原因。常见原因external secret already exists in namespace来自控制器对同名 ExternalSecret 的属主检查clusterexternalsecret_controller.go#L226如果目标命名空间里已存在同名 ExternalSecret 且不是该 CES 创建的就会报错并跳过provisionedNamespaces匹配且成功部署的命名空间列表conditions唯一的 condition 类型是Ready。全部成功时为True任一失败为False错误信息统一为one or more namespaces failed构造逻辑见 pkg/controllers/clusterexternalsecret/util.go。另外控制器还会维护两条 finalizerCES 自身的externalsecrets.external-secrets.io/clusterexternalsecret-cleanup以及按 CES 名称命名的命名空间 finalizerexternalsecrets.external-secrets.io/ces-cesName见 clusterexternalsecret_controller.go#L69 与buildCESFinalizer。这意味着删除 CES 时它创建的所有 ExternalSecret 都会被清理避免孤儿资源也防止命名空间删除被阻塞。大规模命名空间集合如何减少 provider 调用这是ClusterExternalSecret最重要的设计考量之一。一个ClusterExternalSecret会为每个匹配的命名空间创建一个ExternalSecret而每个ExternalSecret都会按照自己的refreshInterval独立轮询上游 provider。这意味着provider API 调用次数与匹配的命名空间数量成正比。如果选择器匹配了几十个甚至上百个命名空间每个命名空间的 ExternalSecret 都在自己的刷新周期内独立访问 AWS Secrets Manager / Vault 等后端成本会线性增长也很容易触及 API 速率限制。这是该设计的已知特性known characteristic官方文档明确指出了这一点。如果你的选择器匹配的不只是寥寥几个命名空间官方推荐的做法是从上游 provider 只拉取一次到集群内的单个 Secret再用 Kubernetes provider 通过ClusterExternalSecret扇出fan-out到所有目标命名空间而不是让每个命名空间都直连云厂商。Fan-out 模式三步走官方文档给出了完整的推荐流程示例见 docs/snippets/cluster-external-secret-fanout.yaml第 1 步一个命名空间级ExternalSecret从上游 provider 拉取写入一个位于专用源命名空间的 Secret。apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: shared-credentials namespace: eso-fanout-source spec: refreshInterval: 1h secretStoreRef: name: my-upstream-store kind: ClusterSecretStore target: name: shared-credentials dataFrom: - extract: key: path/to/shared-credentials第 2 步一个使用 Kubernetes provider 的ClusterSecretStore指向源命名空间中的那个 Secret。apiVersion: external-secrets.io/v1 kind: ClusterSecretStore metadata: name: shared-credentials-store spec: provider: kubernetes: remoteNamespace: eso-fanout-source server: caProvider: type: ConfigMap name: kube-root-ca.crt namespace: eso-fanout-source key: ca.crt auth: serviceAccount: name: eso-fanout-reader namespace: eso-fanout-source第 3 步ClusterExternalSecret引用这个ClusterSecretStore把 Secret 复制到每个匹配的命名空间。apiVersion: external-secrets.io/v1 kind: ClusterExternalSecret metadata: name: shared-credentials spec: externalSecretName: shared-credentials namespaceSelectors: - matchLabels: shared-credentials: true externalSecretSpec: refreshInterval: 1h secretStoreRef: name: shared-credentials-store kind: ClusterSecretStore target: name: shared-credentials dataFrom: - extract: key: shared-credentials这个模式下无论匹配多少个命名空间上游 provider 都只被第 1 步的那个源 ExternalSecret 调用一次其余命名空间全部通过集群内的 Kubernetes provider 复制数据provider 负载大幅下降。Kubernetes provider store 所需的 ServiceAccount 与 RBAC 配置与 Kubernetes provider 文档 中描述的一致。什么时候仍然可以直接直连官方文档强调直接使用ClusterExternalSecret对接云厂商 store 仍然适用以下场景命名空间集合很小只有少量命名空间时线性增长的调用量可以接受刻意需要按命名空间隔离刷新例如不同命名空间期望不同的刷新节奏或者个别命名空间需要独立拉取。这是一个如何减少 provider 负载的推荐而不是对前面直连模式的弃用deprecation——两种用法都会长期支持。同步对应的 ExternalSecrets定时刷新与强制同步ClusterExternalSecret对已生成 ExternalSecret 的刷新控制分为两层定期刷新通过refreshPolicy与refreshInterval控制。注意这两个字段位于externalSecretSpec中随模板复制给每个 ExternalSecret决定每个 ExternalSecret 自身的同步节奏而refreshTime只控制 CES 控制器检查匹配命名空间、补齐缺失 ExternalSecret的频率。临时/手动同步可以随时通过设置、更新或删除external-secrets.io/force-sync注解来触发一次 ad-hoc 同步kubectl annotate ces my-ces external-secrets.io/force-sync$(date %s) --overwrite该注解的常量定义在 apis/externalsecrets/v1/externalsecret_types.go#L773AnnotationForceSync external-secrets.io/force-sync。对 CES 注解的任何改动都会被同步到它所拥有的全部 ExternalSecret 上——控制器在createOrUpdateExternalSecret中会读取 CES 上的该注解值并覆写到生成的 ExternalSecret 上CES 上注解被删除时也会从 ExternalSecret 上同步删除见 clusterexternalsecret_controller.go#L397。$(date %s)每次生成不同的时间戳确保注解值发生变化、从而可靠触发一次新的同步。弃用项说明namespaceSelector单数字段namespaceSelector单数形式已被namespaceSelectors复数形式取代并将在未来的版本中移除。迁移方式很简单把单个namespaceSelector.matchLabels改写成namespaceSelectors列表中的一项同样地namespaces按名称选择字段也已弃用建议改用namespaceSelectors来表达同样的选择逻辑例如用kubernetes.io/metadata.name标签。迁移期间旧字段仍会被控制器识别——从 clusterexternalsecret_controller.go#L154-L158 可以看到控制器会把已弃用的namespaceSelector与新的namespaceSelectors合并后再统一计算目标命名空间因此新老字段共存不会导致行为差异。但请尽早迁移避免未来版本升级时资源失效。与相关资源的关系与建议阅读想了解 CES 生成的单个资源如何工作可阅读 ExternalSecret API 文档fan-out 模式依赖的 Kubernetes provider 详见 Kubernetes provider 文档多租户场景下的命名空间隔离实践可参考 multi-tenancy 指南端到端测试与完整清单可查看 tests/clusterexternalsecret_test.yaml控制器的单元测试见 pkg/controllers/clusterexternalsecret/clusterexternalsecret_controller_test.go。实践要点小结命名空间选择用namespaceSelectorsOR 语义密钥分发用 fan-out 模式避免 provider 调用随命名空间数线性膨胀按需同步用external-secrets.io/force-sync注解namespaceSelector/namespaces两个旧字段尽快迁移到namespaceSelectors。【免费下载链接】external-secretsExternal Secrets Operator reads information from a third-party service like AWS Secrets Manager and automatically injects the values as Kubernetes Secrets.项目地址: https://gitcode.com/GitHub_Trending/ex/external-secrets创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考