先说一个常见的认知误区:Kubernetes里的Secret并不等于加密。它本质上只是把明文做了一次base64编码,任何人只要拿到etcd的访问权限或者能执行kubectl get secret,就能瞬间还原出密码原文。真正安全的做法,是把敏感数据托管在专业的密钥管理系统里,集群只负责在运行时拉取。本文就围绕这个思路,把外部密钥管理服务对接到Kubernetes的完整过程讲清楚。

为什么原生Secret撑不起生产环境的安全要求
很多团队起步阶段直接把数据库密码写进Secret的YAML清单,然后提交到Git仓库。哪怕仓库是私有的,这种做法也埋下了三个隐患。第一,base64不是加密,echo cGFzc3dvcmQ= | base64 -d一行命令就能还原,任何能读到清单的人都能拿到明文。第二,Secret一旦提交进Git就进入了版本历史,即使后续删除文件,历史提交里依然留存着密文,清理成本极高。第三,Secret的分发粒度太粗,同一个命名空间内有权限列出Secret的用户,可以拿到所有凭据,违背了最小权限原则。
再往深处看,Secret在etcd中默认是明文存储的。虽然Kubernetes支持开启encryption-provider-config对etcd做静态加密,但加密密钥本身又需要一个安全的存放位置,通常配置文件里直接写一个本地key文件,这个文件泄露等于加密形同虚设。专业的密钥管理系统(比如HashiCorp Vault、AWS KMS、阿里云KMS)在密钥生命周期管理、审计日志、动态密钥、自动轮转这些方面,是原生Secret完全不具备的能力。
所以生产级的思路是:密钥的真源放在外部密钥管理系统,Kubernetes集群通过受控的身份认证去按需拉取,本地不留持久化的明文,或者只保留生命周期受严格管理的临时副本。下面介绍两种主流对接方案。
方案一:Secrets Store CSI Driver对接Vault或云厂商KMS
Secrets Store CSI Driver的核心思想是把密钥当作一种存储卷挂载给Pod。集群里先部署CSI Driver,再为具体的密钥后端安装Provider插件,然后在Pod的YAML里声明一个csi类型的volume,Driver会在Pod调度时调用Provider向后端认证并拉取密钥,以文件形式挂载到容器内。
以对接HashiCorp Vault为例,先部署Driver和Vault Provider:
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts helm install csi-driver secrets-store-csi-driver/secrets-store-csi-driver \ --set syncSecret.enabled=true # 部署Vault Provider helm install vault hashicorp/vault \ --set "server.dev.enabled=true"
然后创建一个SecretProviderClass资源,描述从Vault哪个路径读取什么密钥:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: app-db-credentials
namespace: production
spec:
provider: vault
parameters:
vaultAddress: http://vault.vault.svc.cluster.local:8200
roleName: app-role
objects: |
- objectName: db-password
secretPath: secret/data/myapp
secretKey: passwordPod里这样挂载:
apiVersion: v1
kind: Pod
metadata:
name: myapp
namespace: production
spec:
serviceAccountName: myapp-sa
containers:
- name: myapp
image: myapp:1.0
volumeMounts:
- name: secrets-volume
mountPath: /mnt/secrets
readOnly: true
volumes:
- name: secrets-volume
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: app-db-credentials容器启动后,/mnt/secrets/db-password文件里就是密码内容,应用直接读文件即可,完全不需要在环境变量或配置文件里硬编码。如果开启了syncSecret,Driver还能把拉取到的密钥同步为一个原生Secret对象,方便那些只支持读环境变量的旧应用平滑迁移。这个方案的好处是密钥不落etcd(不开同步的前提下),并且Pod删除时挂载的密钥文件也随之销毁。
方案二:External Secrets Operator实现声明式同步
External Secrets Operator(简称ESO)走了另一条路:它不改变Pod的挂载方式,而是用一个控制器持续把外部密钥同步成集群内的原生Secret。应用侧完全无感知,还是照常用环境变量或volume方式读Secret,密钥的来源则交给了ESO。
使用ESO需要两个核心资源。第一个是SecretStore,描述后端连接信息和认证方式;第二个是ExternalSecret,描述要同步哪些密钥。以对接阿里云KMS为例:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aliyun-store
namespace: production
spec:
provider:
alibaba:
regionId: cn-hangzhou
auth:
secretRef:
accessKeyID:
name: ak-secret
key: id
accessKeySecret:
name: ak-secret
key: secret
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aliyun-store
kind: SecretStore
target:
name: db-credentials-synced
data:
- secretKey: password
remoteRef:
key: prod/db/passwordESO会按照refreshInterval的设置周期性地从KMS拉取最新值,更新到db-credentials-synced这个原生Secret里。当运维在KMS侧轮转了密码,集群内副本最迟一小时后自动跟上,配合应用的配置热加载或滚动重启,就能实现准实时的密钥轮转闭环。相比CSI方案,ESO的密钥会落etcd,但换来的是极低的接入成本,特别适合存量应用多、不方便改造挂载方式的场景。
身份认证与权限收敛:对接中最容易出问题的环节
不管选哪个方案,都有一个绕不开的问题:集群里的工作负载凭什么身份去访问密钥管理系统?新手常见的做法是把云厂商的AccessKey明文塞进一个Secret,让Provider用这个AK去认证。这等于把最敏感的钥匙又存回了集群,安全隐患并没有消除,只是换了个位置。
更推荐的做法是利用云厂商的工作负载身份联邦机制。以AWS的IRSA为例,给ServiceAccount绑定一个IAM角色,Pod通过投影的ServiceAccount令牌向STS换取临时凭证,整个过程没有任何长期密钥出现在集群里:
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-sa
namespace: production
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/vault-readerVault侧再配置Kubernetes认证后端,校验这个ServiceAccount令牌的签名后签发限定TTL的Vault令牌,并按role映射到具体的密钥路径策略。这样即使某个Pod被攻破,攻击者拿到的也是短时效、范围受限的临时凭证,且所有访问都会留存在Vault和云端的审计日志里,可以追溯到具体的ServiceAccount。
权限策略的编写同样要克制。不要为了省事给应用授权整个secret/路径的读写权限,而是按应用维度拆分策略,只允许读取自己的密钥前缀,且原则上只开read。上线前建议用vault policy read或云厂商的策略模拟器做一次核对,确认越权路径会被拒绝。
密钥轮转与故障排查的实战经验
密钥管理不是配完就结束的,轮转和排障占了日常运维的大部分精力。关于轮转,有几个实践点值得注意。数据库类密钥轮转后,已建立的旧连接通常不会立刻断开,要确认应用的连接池行为,必要时触发滚动重启。如果用的是Vault的动态数据库密钥,可以在数据库侧设置与租约匹配的账户过期时间,让旧凭证自动失效,实现强制轮转。轮转演练建议每个季度做一次,验证整套链路真的能自动收敛,而不是停留在文档里。
排障方面,CSI方案最常见的故障是Pod一直卡在ContainerCreating状态,密钥文件挂不出来。排查顺序建议是:先看CSI Driver和Provider的Pod是否正常运行,再看节点上的csi-secrets-store日志,最后检查SecretProviderClass里的路径和roleName是否拼写正确、对应的Vault策略是否授权。用kubectl describe pod里的事件信息,通常会直接给出Provider返回的错误码。ESO方案则可以用kubectl describe externalsecret查看同步状态和最近错误,Status里的Conditions字段会明确指出是认证失败还是远端密钥不存在。
最后总结一下选型建议:新建应用优先考虑Secrets Store CSI Driver配合工作负载身份,密钥不落盘、审计完整;存量应用改造优先用External Secrets Operator,改动小、见效快;无论哪种方案,都要把Git仓库里的明文密钥清理干净,用git-secrets或gitleaks做一次全量历史扫描,把密钥管理的根扎在正确的位置。
Kubernetes密钥管理Secret修改时间:2026-09-16 05:09:55