External Secrets Operator v1beta1 CRD 设计解析:dataFrom 批量拉取与模板引擎演进
【免费下载链接】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
本文基于仓库中的设计文档 design/001-design-crd-v1beta1.md 展开。该文档记录了 External Secrets Operator(ESO)在迈向 GA 过程中将核心 CRD 从v1alpha1提升为v1beta1的设计提案,核心围绕三件事:定义一份可正式发布的 beta CRD、为"一次拉取多个 Provider 密钥"设计统一的dataFrom结构、以及引入新的模板引擎。读完本文,你将掌握 v1beta1ExternalSecret的完整字段语义、dataFrom.extract/dataFrom.find的用法与底层执行路径,以及 SecretStore 在 v1beta1 中的定位与升级注意事项。
设计背景与目标
该设计文档(标题为External Secrets CRD promotion,创建于 2022-02-08,状态implemented)指出:项目在用户数量与成熟度上已经达到一个节点,因此开始推动其走向 GA(General Availability),将 ExternalSecrets CRD 提升到 beta 就是其中一项工作。这份设计文档旨在捕获 ExternalSecrets CRD 在 beta 阶段的最终变更。
Goals(目标)
- 定义一份 beta 版本的 CRD;
- 定义"获取 Provider 全部密钥"(get all provider secrets)的结构;
- 定义新模板引擎(templating engine)的结构。
Non-Goals(非目标)
该 KEP(Kubernetes Enhancement Proposal)只提出 CRD Spec 并记录用例,不涉及实现技术的选型或向 CRD 落地的迁移路径——技术选型与迁移属于实现阶段的工作。
核心术语与用户模型
文档定义了整套领域术语,后续所有 YAML 字段都建立在这些概念之上:
- External Secrets Operator(ESO):运行控制循环(control loop)并同步密钥的应用程序;
- ESO instance:运行单个控制循环的独立实体;
- Provider:密钥的来源(source),位于 ESO 外部,例如 AWS Secrets Manager、AWS Systems Manager、Azure Key Vault 等托管服务;
- SecretStore(ST):用于在 ESO 实例与 Provider 之间完成认证并配置连接的自定义资源;
- ExternalSecret(ES):声明"应该同步哪些密钥"的自定义资源;
- Frontend:已同步密钥的汇(sink),通常是 Kubernetes 的
Secret资源; - Secret:作为敏感信息访问凭据的数据。
由此衍生出两类角色与两条用户故事:
operator :=我管理一个或多个 ESO 实例;user :=我只创建ExternalSecret,ESO 由他人管理。
对应需求(User Stories):
- 作为 ESO operator,我希望从给定 Provider 的给定路径下获取全部密钥(在 Provider 支持的前提下);
- 作为 ESO operator,我希望模板处理像自然语言一样自然,而不需要关心底层实现细节。
ExternalSecret:v1beta1 核心提案
设计文档给出了一份带完整注释的 v1beta1ExternalSecret示例(以下为原文完整继承,并补充了当前仓库源码中的字段语义):
#only changed fields are commented out. apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: "hello-world" labels: acme.org/owned-by: "q-team" annotations: acme.org/sha: 1234 spec: secretStoreRef: name: secret-store-name kind: SecretStore refreshInterval: "1h" target: name: my-secret creationPolicy: 'Merge' deletionPolicy: 'None' #Possible values are None, Merge, Delete - TBC during implementation. template: engineVersion: v2 #Defaults to v2 in v1beta1 type: kubernetes.io/dockerconfigjson metadata: annotations: {} labels: {} data: config.yml: | endpoints: - https://{{ .data.user }}:{{ .data.password }}@api.example.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: - extract: #extract all the keys from one given secret key: provider-key version: provider-key-version property: provider-key-property - find: name: #find secrets that match a particular pattern regexp: .*pattern.* tags: #find secrets that match the following labels/tags provider-label: provider-value status: refreshTime: "2019-08-12T12:33:02Z" conditions: - type: Ready status: "True" reason: "SecretSynced" message: "Secret was synced" lastTransitionTime: "2019-08-12T12:33:02Z"dataFrom 的新行为(Behavior)
设计文档明确指出:v1beta1 的ExternalSecret对dataFrom采用了全新的结构,使得只用一份 ExternalSecret 定义即可拉取多个 Provider 密钥,并且支持基于正则表达式或标签/标签选择器来查找密钥。若用户需要重命名密钥(例如 Provider 中的键名/foo/bar不是合法的 Secret 键名),应使用template函数生成映射。
在 apis/externalsecrets/v1beta1/externalsecret_types.go 中,ExternalSecretDataFromRemoteRef结构体与设计文档完全对应,并在此基础上演进出更多能力:
Extract *ExternalSecretDataRemoteRef:从一个 Provider 密钥中取出多个 key/value 对(等价于 v1alpha1 中dataFrom[].key的行为);注意注释明确说明Extract 不支持sourceRef.Generator/GeneratorRef;Find *ExternalSecretFind:基于标签或正则查找密钥;同样不支持 Generator 类 sourceRef;Rewrite []ExternalSecretRewrite:对 Provider 返回的密钥键名进行重写,多个 Rewrite 操作按"从第一个到最后一个"分层依次应用;SourceRef *StoreGeneratorSourceRef:指向某个 SecretStore 或 Generator 作为取值来源;当指向 Generator 时 Extract/Find 不可用(Generator 返回的是静态键值映射)。
ExternalSecretFind(同文件 L366-L389)在设计的name.regexp与tags基础上增加了path(查找操作的根路径起点)以及conversionStrategy(默认Default)、decodingStrategy(默认None)。
单键同步与批量同步的对照
设计文档同时展示了data与dataFrom两种模式,二者在 ExternalSecretSpec 中并存:
spec.data[]:显式地把单个 Provider 键映射到单个 Kubernetes Secret 键。每个条目由secretKey(Kubernetes 侧键名,需匹配^[-._a-zA-Z0-9]+$)与remoteRef(key必填,version、property可选,另有metadataPolicy默认None、conversionStrategy默认Default、decodingStrategy默认None)组成;spec.dataFrom[]:批量地从 Provider 拉取。多个条目按书写顺序合并进同一个 Secret,后续条目会覆盖同名键(源码注释明确"Secret keys are merged in the specified order")。
从控制器实现看,pkg/controllers/externalsecret/externalsecret_controller_secret.go 的 reconcile 流程正是按dataFrom优先、data其次的顺序执行:先遍历Spec.DataFrom,对每个条目依次判定Find(handleFindAllSecrets)、Extract(handleExtractSecrets)、GeneratorRef(handleGenerateSecrets)三种分支,最后用esutils.MergeByteMap合并结果;再遍历Spec.Data逐键调用handleSecretData。handleExtractSecrets(L213 起)内部依次执行client.GetSecretMap、键名 Rewrite/Convert、ValidateKeys、decoding.DecodeMap与空字节校验,一条链路上即可验证"批量拉取 → 键名规范化 → 解码"的完整语义。
target 与模板引擎 v2
设计文档在spec.target.template中引入了engineVersion: v2,并声明v1beta1 默认即 v2。这在 ExternalSecretTemplate 中得到落实:EngineVersion通过+kubebuilder:default="v2"默认取v2(且当前枚举仅允许v2),模板数据既可以写在data内联字段,也可以通过templateFrom引用ConfigMap或Secret中的模板片段,并支持mergePolicy(默认Replace)控制模板合并行为。模板中通过{{ .data.user }}、{{ .data.password }}这类自然语言式的引用访问已拉取的密钥值——这正是设计文档第二条用户故事("像自然语言一样处理模板")的直接体现。相关模板引擎实现位于 runtime/template 目录(v1/v2 两套引擎及配套测试)。
creationPolicy 与 deletionPolicy 的最终枚举
设计文档示例中注明creationPolicy: 'Merge'、deletionPolicy: 'None'且"Possible values are None, Merge, Delete - TBC during implementation"(实现期间待定)。在最终落地的源码中,枚举被明确为:
ExternalSecretCreationPolicy(L39-L56):Owner(创建 Secret 并设置 ownerReference,默认值)、Orphan(不设置 ownerReference,ExternalSecret 删除后 Secret 成为孤儿)、Merge(不创建 Secret,仅合并 data 字段)、None(不创建 Secret,预留给未来的注入器);ExternalSecretDeletionPolicy(L58-L79):Delete(Provider 侧密钥全部删除后删除 Secret)、Merge(仅移除对应键,不删除 Secret 本身)、Retain(保留 Secret,默认值,Provider 密钥不存在时进入SecretSyncedError状态)。
设计文档示例中的deletionPolicy: 'None'在正式实现中并未采用——这正是文档标注 "TBC during implementation" 的用意,最终以源码枚举为准。
SecretStore:v1beta1 保持兼容
设计文档明确写道:SecretStore 与 ClusterSecretStore 相比 v1alpha1 没有任何变更,并给出了一份多 Provider 的 SecretStore 示例(以下完整继承原文):
apiVersion: external-secrets.io/v1beta1 kind: SecretStore metadata: name: example namespace: example-ns spec: controller: dev retrySettings: maxRetries: 5 retryInterval: "10s" provider: aws: service: SecretsManager role: iam-role region: eu-central-1 auth: secretRef: accessKeyID: name: awssm-secret key: access-key secretAccessKey: name: awssm-secret key: secret-access-key vault: server: "https://vault.acme.org" path: "secret" version: "v2" namespace: "a-team" caBundle: "..." caProvider: type: "Secret" name: "my-cert-secret" key: "cert-key" auth: tokenSecretRef: name: "my-secret" namespace: "secret-admin" key: "vault-token" appRole: path: "approle" roleId: "db02de05-fa39-4855-059b-67221c5c2f63" secretRef: name: "my-secret" namespace: "secret-admin" key: "vault-token" kubernetes: mountPath: "kubernetes" role: "demo" serviceAccountRef: name: "my-sa" namespace: "secret-admin" secretRef: name: "my-secret" namespace: "secret-admin" key: "vault" gcpsm: auth: secretRef: secretAccessKeySecretRef: name: gcpsm-secret key: secret-access-credentials projectID: myproject status: conditions: - type: Ready status: "False" reason: "ConfigError" message: "SecretStore validation failed" lastTransitionTime: "2019-08-12T12:33:02Z"对照 apis/externalsecrets/v1beta1/secretstore_types.go 的SecretStoreSpec,可以确认:controller字段用于选择正确的 ESO 控制器实例(类比ingress.ingressClassName),provider一次只能配置一个(MinProperties=1/MaxProperties=1),retrySettings配置失败时的 HTTP 重试。Provider 类型在SecretStoreProvider中持续扩充(当前仓库已支持 AWS、AzureKV、Vault、GCPSM、Webhook、Kubernetes、Fake 等数十种,见 secretstore_types.go),但 SecretStore 的框架结构自 v1alpha1 起保持稳定。spec.controller的机制细节可参考 docs/guides/controller-class.md。
v1beta1 的落地、升级与验证
与 v1alpha1 的差异
设计文档提出的结构差异在 docs/guides/v1beta1.md 中有明确说明:v1alpha1 与 v1beta1 对 SecretStore、ClusterSecretStore完全兼容,唯一差异在ExternalSecret的dataFrom。v1alpha1 中dataFrom直接写key:
spec: dataFrom: - key: my-key - key: my-other-keyv1beta1 中拆分为两种方法:Extract与 v1alpha1 行为完全一致;Find则支持按正则或标签查找并合并进单个 Kubernetes Secret:
spec: dataFrom: - find: name: #matches any secret name ending in foo-bar regexp: .*foo-bar$ - find: tags: #matches any secrets with the following metadata. env: dev app: web升级路径
官方升级指引(docs/guides/v1beta1.md)建议:未使用dataFrom或通过官方 Helm Chart 部署 CRD 的用户,可无风险升级;手工安装 CRD 时需部署deploy/crds/bundle.yaml(注意文档中deploys/crds/bundle.yaml的路径在仓库中实际为 deploy/crds/bundle.yaml),该 bundle 同时包含 v1beta1 定义与转换 Webhook 配置,确保升级未完成前新的 CRD 请求不会生效,从而避免数据丢失。升级完成后,每次 reconcile 时存储为 v1alpha1 的 ExternalSecret、SecretStore、ClusterSecretStore 会被自动转换为 v1beta1。
实测样例与端到端验证
- 仓库自带的测试清单 tests/externalsecrets_test.yaml 覆盖了 v1beta1 ExternalSecret 的完整字段组合,可直接作为合法样例参考;
- 批量拉取的实战配置见 docs/snippets/getallsecrets-find-by-name.yaml 与 docs/snippets/getallsecrets-find-by-tags.yaml,其行为说明记录在 docs/guides/getallsecrets.md:
find会把不合法的 Secret 键名字符自动替换为_(如/path/key1变为_path_key1),若出现命名冲突可通过rewrite块以正则重写,或用find.conversionStrategy: Unicode将非法字符编码为_UXXXX_形式降低冲突概率; - CRD 定义本身位于 config/crds/bases(26 个 YAML),设计文档中的
status.conditions(Ready/SecretSynced/ConfigError等)可在 CRD 的 status 子结构中验证。
结语
design/001-design-crd-v1beta1.md是 ESO 走向 GA 的关键里程碑设计:它锁定了 v1beta1 CRD 的形态,将"批量获取 Provider 密钥"固化为dataFrom.extract与dataFrom.find两种语义,并把模板引擎统一到 v2。该设计已在当前仓库完整落地——从 apis/externalsecrets/v1beta1/externalsecret_types.go 的类型定义,到 pkg/controllers/externalsecret/externalsecret_controller_secret.go 的控制器实现,再到 docs/guides/getallsecrets.md、docs/guides/v1beta1.md 等使用与升级文档,均与设计初衷一一对应。对使用者而言,理解本文的data/dataFrom双模式与模板引擎语义,即可安全地在 v1beta1 上构建"一份 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-secrets
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考