摘要:很多人以为把数据库密码塞进 Kubernetes Secret 就"安全"了,其实 Secret 默认只是 base64 编码,在 etcd 里近乎明文躺着。本文从 K8S 原生凭据方案的真实痛点出发,拆解如何用外部凭据管理系统(SMS)实现"密码不进 YAML、不落 etcd、动态注入、自动轮转",并给出 Init Container、Sidecar、CSI Driver、SDK 直连四种集成方式的完整落地示例。
一、先泼一盆冷水:K8S Secret 并不"安全"
几乎每个上云团队都踩过这个坑——把密码写进 Deployment 的环境变量太丑,于是改用 Secret,然后就心安理得了。但 Secret 有三个致命误区:
1.1 Secret 不是加密,是 base64 编码
# 你以为的"加密"$echo-n'Admin@123'|base64 QWRtaW5AMTIz# 任何人拿到就能还原$echo'QWRtaW5AMTIz'|base64-dAdmin@123base64 是编码,不是加密,任何拿到 Secret YAML 或 etcd 快照的人都能一秒还原。
1.2 etcd 默认不加密存储
Secret 最终存在 etcd 里。如果没有开启EncryptionConfiguration,etcd 备份文件、快照、甚至误配置的备份 OSS 桶,都等于把生产库密码明文送人。
1.3 GitOps 让 Secret 进了 Git 仓库
ArgoCD / Flux 流行后,大家习惯把 K8S 清单全放进 Git。Secret 也跟着进了仓库——哪怕用了 SealedSecrets,密钥管理和轮转依然是老大难。
一句话总结痛点:K8S 原生方案解决的是"密码怎么传给 Pod",没解决"密码本身怎么被安全地存储、轮转、审计"。
二、容器环境凭据治理的六个真实痛点
结合大量落地案例,K8S 场景下凭据管理的痛点可以归纳为下面这张表:
| # | 痛点 | 后果 | 等保/合规影响 |
|---|---|---|---|
| 1 | Secret base64 明文可还原 | 拿到 YAML/etcd 即泄露 | 高危项 |
| 2 | 密码硬编码进镜像/ConfigMap | 镜像分发即泄露 | 一票否决 |
| 3 | 密码无法自动轮转 | 改密码要重启全部 Pod | 长期不轮转高危 |
| 4 | 多集群多命名空间凭据分散 | 无统一管理入口 | 审计困难 |
| 5 | 谁读了 Secret 无记录 | 无法追溯泄露源 | 审计缺失 |
| 6 | 离职/外包人员仍能读 Secret | 权限回收滞后 | 访问控制不达标 |
这些痛点的共性是:凭据的生命周期(存储、分发、轮转、销毁、审计)散落在 K8S 之外,没有统一治理面。
三、思路:把凭据管理下沉到专用系统
解法不是给 K8S Secret 打补丁,而是引入一个独立的凭据管理系统(Secret Management System,下文简称 SMS),让 K8S 只负责调度,凭据的存储与治理全部交给 SMS:
┌─────────────────────────────────────────────┐ │ Kubernetes 集群 │ │ ┌────────────┐ ┌────────────┐ │ │ │ 业务 Pod │ │ 业务 Pod │ │ │ │ (无密码) │ │ (无密码) │ │ │ └─────┬──────┘ └─────┬──────┘ │ │ │ 动态获取临时凭据 │ │ │ └────────┬────────┘ │ └─────────────────┼─────────────────────────────┘ │ ① ServiceAccount 身份鉴权 ▼ ┌───────────────────────┐ │ SMS 凭据管理系统 │ │ • 统一加密存储 │ │ • 动态临时凭据 (TTL) │ │ • 自动轮转 │ │ • 全程访问审计 │ └──────────┬────────────┘ │ ② 返回带 TTL 的临时凭据 ▼ ┌───────────────────────┐ │ 数据库 / 中间件 / API │ └───────────────────────┘这里用到两个关键能力:
- 动态临时凭据:Pod 每次拿到的都是带有效期(TTL,如 1 小时)的临时密码,用完即毁,密码不进 YAML、不落 etcd。
- 自动轮转:数据库真实密码由 SMS 按周期(如 ≤90 天)自动轮转,业务 Pod 无感知,不用重启。
四、四种集成方法(含完整示例)
在 K8S 里接入 SMS,主流有四条路径,按"侵入性从低到高"排列。
4.1 方法一:Init Container 预拉取
启动前用 Init Container 向 SMS 申请凭据,写入共享的emptyDir内存卷,业务容器直接读文件。业务代码零改造。
apiVersion:apps/v1kind:Deploymentmetadata:name:order-servicespec:template:spec:serviceAccountName:order-sa# 用 SA 向 SMS 鉴权volumes:-name:credsemptyDir:medium:Memory# 内存卷,不落磁盘initContainers:-name:fetch-credsimage:registry/sms-agent:latestargs:["fetch","--id=order-db","--out=/creds/db.json"]volumeMounts:-{name:creds,mountPath:/creds}containers:-name:appimage:registry/order-service:1.0volumeMounts:-{name:creds,mountPath:/creds,readOnly:true}适用:凭据在 Pod 生命周期内基本不变的场景。缺点是长时间运行的 Pod 拿不到轮转后的新凭据(需配合 Sidecar)。
4.2 方法二:Sidecar 常驻刷新
注入一个 sidecar 容器常驻 Pod,定时向 SMS 续期/刷新凭据,写入共享内存卷。轮转后 sidecar 自动拉新,业务无感知。
containers:-name:appimage:registry/order-service:1.0volumeMounts:-{name:creds,mountPath:/creds,readOnly:true}-name:sms-sidecarimage:registry/sms-agent:latestargs:["watch","--id=order-db","--out=/creds/db.json","--interval=300"]volumeMounts:-{name:creds,mountPath:/creds}适用:长时间运行、需要感知凭据轮转的服务。这是与"自动轮转"配合最好的模式。
4.3 方法三:Secrets Store CSI Driver
通过标准的 CSI Driver 把 SMS 里的凭据以卷的形式挂载进 Pod,云原生程度最高,运维统一。
volumes:-name:credscsi:driver:secrets-store.csi.k8s.ioreadOnly:truevolumeAttributes:secretProviderClass:"sms-order-db"配套的SecretProviderClass声明去哪个 SMS 实例、取哪些凭据,权限与集群解耦。
适用:已有平台化运维、希望用统一 CSI 规范管理所有外部密钥的团队。
4.4 方法四:SDK 直连
业务代码里直接调 SMS SDK 动态获取凭据,控制力最强,改动一行配置读取逻辑即可。
# ❌ 改造前:密码硬编码 / 从 Secret 环境变量读,明文可见# password = os.environ["DB_PASSWORD"]# ✅ 改造后:运行时动态获取临时凭据,不落盘fromsms_sdkimportCredentialManager cm=CredentialManager(auth="k8s-sa")# 用 Pod 的 SA Token 鉴权cred=cm.get_credential("order-db")# 返回带 TTL 的临时凭据conn=mysql.connect(host=cred["host"],user=cred["user"],password=cred["password"],# 1 小时后自动失效)适用:新项目或愿意做少量改造、追求最小密码暴露窗口的场景。
四种方法对比:
| 方法 | 业务改造 | 支持轮转刷新 | 云原生度 | 推荐场景 |
|---|---|---|---|---|
| Init Container | 零 | 否 | 中 | 凭据基本不变 |
| Sidecar | 零 | 是 | 中 | 长运行+需轮转 |
| CSI Driver | 零 | 是 | 高 | 平台化统一运维 |
| SDK 直连 | 少量 | 是 | 中 | 新项目/最小暴露 |
五、身份鉴权:Pod 凭什么能取凭据?
关键在于用 K8S ServiceAccount 做 Pod 到 SMS 的身份鉴权,而不是再发一个静态 token(否则又回到了"密码换密码"的死循环)。
流程如下:
- Pod 挂载自己的 SA Token(K8S 自动注入
/var/run/secrets/...)。 - sms-agent / SDK 拿 SA Token 向 SMS 换取短时会话。
- SMS 校验 SA 身份(命名空间 + SA 名 + 集群),命中授权策略才签发临时凭据。
- 每次签发写入审计日志:哪个 Pod、哪个 SA、取了哪个库、什么时间。
这样即使 Pod 被攻破,攻击者拿到的也只是带 TTL 的临时凭据,且行为全程留痕,可快速定位与止血。
六、落地 checklist
- 梳理集群内全量凭据清单(数据库、中间件、API、云账号)。
- 部署 SMS,把凭据迁入加密存储,删除 YAML/ConfigMap 里的明文。
- 按服务特征选集成方式:短命 Job 用 Init Container,常驻服务用 Sidecar/CSI。
- 配置 SA 授权策略,做到"一个命名空间只能取自己的凭据"。
- 开启自动轮转(≤90 天)+ 新旧双写过渡,验证 Pod 无感知刷新。
- 打开全程审计,日志归档留存,接入 SIEM 告警。
七、写在最后
K8S 把应用调度做到了极致,但凭据治理从来不是它的强项。与其在 Secret 上层层打补丁,不如把凭据的存储、轮转、审计交给专门的凭据管理系统,让 Pod 只在运行时拿到"用完即毁"的临时凭据。密码不进 YAML、不落 etcd、可轮转、可审计——这才是容器环境凭据治理应有的样子。
作者注:本文所述动态临时凭据、自动轮转能力可结合国密算法与硬件密钥模块落地,具体部署方案建议结合集群规模与合规要求评估。
免责声明:本文仅供技术交流,具体合规要求以官方标准文件为准。