External Secrets · 密钥同步
External Secrets Operator(ESO)是这套集群里唯一的密钥下发通道:业务密码、token、连接串都存在 Vault 里,ESO 负责把它们拉下来、渲染成命名空间内的原生 Kubernetes Secret,再由各服务挂载使用。git 仓库里从头到尾没有任何明文密钥。
external-secrets 命名空间,镜像 oci.external-secrets.io/external-secrets/external-secrets:v0.20.4,钉在 workload: infra 节点上。它消费的 Vault 部署见 Vault。为什么 secret 不进 git
整个 k8s-config 仓库是 ArgoCD 的同步源,main 分支上的任何东西几分钟后就会被自动应用进集群——把明文密钥写进 git 等于把它公开给所有能读仓库的人,且会随提交历史永久留存。所以约定是:密钥永远不进 git。仓库里只存 ExternalSecret 这一份「从哪里取、取哪些字段、渲染成什么 Secret」的声明,真正的密文留在 Vault。这样仓库可以公开评审,而敏感值始终只存在于 Vault 和它在集群内同步出的 Secret 对象里。
组件构成
applications/infrastructure/external-secrets/ 是一个 kustomize root,通过 Helm chart external-secrets-0.20.4 的渲染产物纳管。三个 Deployment 协同工作,均为单副本、runAsNonRoot、readOnlyRootFilesystem:
- Name
- external-secrets
- Description
- 核心 controller,watch 集群里所有
ExternalSecret资源并执行同步。启动参数--concurrent=1、--metrics-addr=:8080,指标端口 8080。资源请求 50m/128Mi、上限 200m/256Mi,并由 VerticalPodAutoscaler(updateMode: Recreate,上限 500m/512Mi)调整。
- Name
- external-secrets-webhook
- Description
- 校验 webhook,监听端口 10250,对外 Service 端口 8081。配套
ValidatingWebhookConfiguration在ExternalSecret/SecretStore等资源写入时做准入校验。
- Name
- external-secrets-cert-controller
- Description
- 为 webhook 自动签发并轮换 TLS 证书的控制器。
命名空间内还配了 ResourceQuota 与 LimitRange 兜底。一组访问 Vault / kube-api / DNS 的 NetworkPolicy 已写好但当前在 kustomization.yaml 里被注释停用(allow-vault-access 放行 egress 到 vault 命名空间的 8200 端口)。
ClusterSecretStore vault-backend
clustersecretstores-vault.yaml 定义了一个集群级的 ClusterSecretStore,名为 vault-backend,全集群所有命名空间的 ExternalSecret 都引用它。它指向集群内 Vault 的 KV v2 引擎:
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: 'http://vault.vault.svc.cluster.local:8200'
path: 'secret'
version: 'v2'
auth:
tokenSecretRef:
name: 'vault-token'
namespace: 'external-secrets'
key: 'VAULT_TOKEN'
几个关键点:path: "secret" 是 Vault 里 KV v2 引擎的挂载点;version: "v2" 启用 KV v2 语义;认证用一枚存在 external-secrets/vault-token Secret 里的 token(key 为 VAULT_TOKEN)。因为是 ClusterSecretStore 而非命名空间级的 SecretStore,所有业务命名空间共用这一个 store,无需各自重复配置。
ExternalSecret 到 Secret 的同步流程
每个需要密钥的服务在自己的命名空间里放一份 ExternalSecret(按惯例文件名 external-secret.yaml),声明要从 Vault 哪个路径取哪些字段、渲染成什么 Secret。以 applications/data/postgres/external-secret.yaml 为例:
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: postgres-secret
namespace: postgres
spec:
secretStoreRef:
kind: ClusterSecretStore
name: vault-backend
target:
name: postgres-secret
creationPolicy: Owner
refreshInterval: 1h
data:
- secretKey: POSTGRES_PASSWORD
remoteRef:
key: database/prod/postgres
property: postgres_password
ESO controller 据此执行:① secretStoreRef 指向 vault-backend,确定从哪个 Vault 取;② 按 data[].remoteRef.key + property 从 Vault 读出对应字段;③ 把每个字段映射到 secretKey,渲染成 target.name 指定的原生 Secret(creationPolicy: Owner 表示这个 Secret 由 ExternalSecret 拥有、生命周期跟随它);④ 按 refreshInterval: 1h 周期性重新拉取,Vault 里改了值后最长 1 小时内同步到集群。各服务只管挂载这个普通 Secret,对 Vault 无感知。
remoteRef.key 前缀约定
remoteRef.key 是 Vault 里的路径。业务服务统一约定为 yldm/production/<app>,另有共享路径如 yldm/database;数据库类按 database/prod/<db> 组织(如上面 postgres 用的 database/prod/postgres)。一个 app 的 ExternalSecret 可以同时引用自己的私有路径和共享路径——applications/app/magicbox/external-secret.yaml 就既取 yldm/production/magicbox(自己的 DB/Redis/JWT 配置)又取 yldm/database(共享的 DB admin 凭证)。
secret/ 挂载前缀在 ESO 里要省略,在 vault CLI 里不能省。 ESO 的 remoteRef.key 写的是相对于 store path 的路径(store 已经配了 path: "secret",会自动加上前缀),所以写 yldm/production/magicbox;而你用 vault CLI 手动写入同一份密钥时必须带上挂载点:vault kv put secret/yldm/production/magicbox ...。两边指的是同一个位置,只是 ESO 帮你补了 secret/。相关页面:Vault、集群架构、GitOps 工作流。