External Secrets · 密钥同步

External Secrets Operator(ESO)是这套集群里唯一的密钥下发通道:业务密码、token、连接串都存在 Vault 里,ESO 负责把它们拉下来、渲染成命名空间内的原生 Kubernetes Secret,再由各服务挂载使用。git 仓库里从头到尾没有任何明文密钥。

为什么 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 协同工作,均为单副本、runAsNonRootreadOnlyRootFilesystem

  • 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。配套 ValidatingWebhookConfigurationExternalSecret/SecretStore 等资源写入时做准入校验。
  • Name
    external-secrets-cert-controller
    Description
    为 webhook 自动签发并轮换 TLS 证书的控制器。

命名空间内还配了 ResourceQuotaLimitRange 兜底。一组访问 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 指定的原生 SecretcreationPolicy: 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 凭证)。

相关页面:Vault集群架构GitOps 工作流

评论