External Secrets Operator 实战:把云厂商密钥同步进 Kubernetes

在 Kubernetes 里手写 Secret 是运维最大的隐患之一:密钥散落在 YAML、Git、镜像里,轮转靠人工,泄露难追溯。External Secrets Operator(ESO)把云厂商密钥托管平台(AWS Secrets Manager、阿里云 Secrets Manager、HashiCorp Vault 等)的密钥自动同步为 K8s Secret,让应用只读、平台托管,彻底把”密钥从代码里赶出去”。我们曾遇到一次真实事故:某服务把 RDS 密码写进 ConfigMap 提交到内部 Git,仓库被无意分享后数据库被拖库——如果当时用 ESO,密钥根本不会出现在仓库里。

一、为什么需要 External Secrets Operator

原生 Secret 用 base64 编码,那不是加密。一旦仓库被拉取、镜像被导出,密钥就裸奔。更糟的是轮转:改一个数据库密码要手动 kubectl create secret 再滚动重启,出错是常态。ESO 的核心价值是”单一事实源”——密钥只存在于托管平台,K8s 里只是它的只读副本,上游一改,集群自动跟着变。

维度手写 SecretExternal Secrets Operator
密钥存储明文 base64 进 Git只在云厂商密钥库
轮转人工重建 + 滚动重启周期自动同步
权限谁有 YAML 谁可见按 IAM/RBAC 细粒度
审计几乎无平台侧完整日志

二、核心概念:SecretStore 与 ExternalSecret

ESO 用两个 CRD 把”去哪取”和”取什么”拆开:

  • SecretStore:定义”从哪里取”——provider 类型(aws、alibaba、vault…)加鉴权方式。集群级用 ClusterSecretStore,全集群共享一份后端配置。
  • ExternalSecret:定义”取哪个密钥、同步成什么名字、多久刷新一次”。它引用某个 SecretStore,把远端值映射成本地 K8s Secret。

三、快速上手:对接 AWS Secrets Manager

用 Helm 安装控制器(推荐固定版本,避免自动升级引入破坏性变更):

helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm install external-secrets external-secrets/external-secrets \
  -n external-secrets --create-namespace \
  --set installCRDs=true \
  --version 0.9.20

定义一个 ClusterSecretStore,用 IRSA(IAM Roles for Service Accounts)而非长期 AK,把权限收敛到最小:

apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
  name: aws-secretsmanager
spec:
  provider:
    aws:
      service: SecretsManager
      region: ap-northeast-1
      auth:
        jwt:
          serviceAccountRef:
            name: external-secrets-sa
            namespace: external-secrets

再写一个 ExternalSecret,把 prod/db/password 同步成名为 db-credentials 的 Secret:

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: app
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secretsmanager
    kind: ClusterSecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
  data:
    - secretKey: password
      remoteRef:
        key: prod/db/password

应用后查看状态,Synced 即成功:

kubectl apply -f externalsecret.yaml
kubectl -n app get externalsecret db-credentials
# NAME            STORE                   REFRESH   STATUS   AGE
# db-credentials  aws-secretsmanager      1h        Synced   12s
kubectl -n app get secret db-credentials -o jsonpath='{.data.password}' | base64 -d

四、对接阿里云与其他 Provider

阿里云 Secrets Manager 同样走 IRSA 思路(用 RRSA + 临时 STS 凭据),避免把 AccessKey 写进集群。SecretStore 配置如下:

apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
  name: alibaba-secretsmanager
spec:
  provider:
    alibaba:
      region: cn-hangzhou
      auth:
        rrsa:
          oidcProviderArnRef:
            name: cluster-oidc-arn
          oidcProviderARN: acs:ram::123456:oidc-provider/ack-rrsa
          ramRoleArnRef:
            name: eso-role-arn
          ramRoleARN: acs:ram::123456:role/eso-reader

provider 还支持 Vault、GCP Secret Manager、Azure Key Vault、1Password、HashiCorp Vault 动态密钥等几十种后端,配置结构基本一致,迁移成本低。

五、刷新策略与密钥轮转

默认 refreshInterval: 1h 周期拉取。上游改了值,最多 1 小时后集群生效。需要立即生效时,给 ExternalSecret 打注解强制触发:

kubectl -n app annotate externalsecret db-credentials \
  external-secrets.io/trigger=reconcile
参数含义建议
refreshInterval同步周期高频变更 15m,稳定值 1h
creationPolicySecret 归属Owner(ESO 托管删除)
target.template自定义 Secret 结构多 key 合并时用
dataFrom整库导入一次性搬多个 key

注意:ESO 只负责”把值放进 Secret”,应用是否热加载取决于自己。像 Prometheus 这类支持信号重载的组件能无缝生效;不支持热加载的需配合滚动重启或 Reloader 类工具。

六、生产实践:多租户与权限边界

  • ClusterSecretStore 共享、ExternalSecret 分散:一份后端配置,各命名空间按需取,避免重复定义凭据。
  • 网络隔离:用 Cilium NetworkPolicy 限制 ESO 控制器只够访问密钥 API,缩小爆炸半径。
  • RBAC 收敛:禁止业务命名空间编辑 ClusterSecretStore,防止越权指向别的后端。
  • 防误删:creationPolicy: Owner 时删 ExternalSecret 会连带删 Secret;跨团队共享的用 Orphan 更安全。

七、用 dataFrom 批量导入整库密钥

当一组密钥都在同一个前缀下,逐个写 data 太啰嗦。用 dataFrom 可以一次把整段(find by tag 或 path)全部同步进来,Secret 里自动生成对应 key:

spec:
  target:
    name: app-all-secrets
  dataFrom:
    - find:
        name:
          regexp: "^prod/app/.*"
        tags:
          - env: production
        conversionStrategy: Default

批量导入适合”先把密钥搬进来跑通、再逐步精细化”的迁移期;上线稳定后建议收口到显式 data 列表,避免无意中把不该暴露的 key 也同步进集群。

八、和 GitOps 协同(Argo CD)

ESO 与 GitOps 天然互补:Git 里只放 ExternalSecret 清单(不含任何真实值),GitHub Actions 或 Argo CD 把它们同步到集群,真实密钥由控制器在运行时拉取。这样 Git 仓库可以放心公开评审,密钥永不进版本库。注意 Argo CD 默认会比对 Secret 内容,建议把 ExternalSecret 设为 managed-by ESO、在 Argo CD 里忽略生成的 Secret,避免被误判为漂移。

# argocd-cm: 让 Argo CD 忽略 ESO 生成的 Secret
resource.customizations.ignoreResourceUpdates.secret: |
  - jsonPointers:
    - /data
    - /metadata/annotations

九、与 Vault / Sealed Secrets 怎么选

方案密钥来源适用场景
External Secrets Operator云厂商/外部密钥库已用 AWS/Aliyun/Vault 托管密钥
Sealed Secrets加密进 Git想 GitOps 但无外部密钥库
CSI Secrets Store挂载为卷需文件形态、不进环境变量
Vault InjectorVault Agent 注入已在用 Vault 动态密钥

没有银弹:已经在用外部密钥平台的,ESO 是最小改动;纯 GitOps 且不想引云的,Sealed Secrets 更轻。两者也可并存。

十、常见误区与排障

现象根因处理
SecretNotFound远端 key 不存在核对 key 拼写与区域
Forbidden/AccessDeniedIRSA 角色权限不足补 IAM 读密钥策略
SecretStore 引用失败kind 写错ClusterSecretStore 必须写 kind
值一直不更新refreshInterval 过长缩短周期或手动触发
同步超时网络/限频查 K8s 网络策略与配额

十一、密钥轮转后自动重启:Reloader 实战

很多老旧应用不支持热加载:ESO 把 Secret 更新了,Pod 里的环境变量还是旧值,要等人去滚动重启。stakater/Reloader 监听 Secret 变动,给引用它的工作负载自动打注解触发滚动重启,闭环密钥轮转。如果只同步 Secret 而不重启进程,应用读到的永远是挂载时的旧副本,轮转就失去了意义——Reloader 正是补齐这最后一环的关键组件。

helm install reloader stakater/reloader \
  -n reloader --create-namespace \
  --set reloader.watchGlobally=false

# 在需要自动重启的工作负载上加注解
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    reloader.stakater.com/auto: "true"
spec:
  template:
    metadata:
      annotations:
        reloader.stakater.com/search: "true"

注意轮转节奏:refreshInterval 过短又赶上上游频繁改值,会造成 Pod 反复重启抖动。建议把高危密钥的刷新周期设为 1h,并配合 revisionHistoryLimit 控制历史版本数,出问题时能快速回滚。

十二、生产落地检查清单

  • 用 IRSA/临时凭据,绝不写长期 AK 进集群;
  • 固定 ESO 版本,开启 installCRDs;
  • ClusterSecretStore 收敛 RBAC,禁止业务命名空间改;
  • refreshInterval 按变更频率设定,关键密钥加手动触发预案;
  • 应用侧确认是否支持热加载,否则配 Reloader;
  • 用网络策略限制控制器出网,缩小泄露面;
  • 迁移前先用 dataFrom 灰度验证单 key,再全量;
  • GitOps 场景在 Argo CD 忽略生成的 Secret,避免误判漂移。

把密钥托管交给专业平台、把同步交给 ESO,K8s 里的 Secret 从此只是”影子”。配合 Docker 与 GitHub Actions 的流水线,密钥从开发到生产的流转链路可以做到全程不落盘、可审计。这才是云原生时代该有的密钥治理姿势。

上一篇 MySQL 主从延迟排查实战:监控到根治
下一篇 Dagger 实战:用流水线即代码统一本地与 CI 的构建
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]