展开主题导航
工程验证记录

Kubernetes Secret 轮换失败?别信“更新成功”,先查应用真没读到新值

阅读说明与证据边界

正文中的规范规定、官方实现行为、工程建议与待验证假设应分别判断;请结合版本与适用范围阅读。

Kubernetes Secret 轮换失败?别信“更新成功”,先查应用真没读到新值

Kubernetes Secret 轮换失败排查需从控制器更新状态延伸至应用实际生效,通过验证分发路径与消费闭环来确认最终结果。

为什么你的 Kubernetes Secret 轮换失败了?先破除“控制器更新即生效”的误区

Secret 轮换失败常因误判控制器资源版本变更即代表生效,实则未加密存储与权限边界模糊导致旧凭据仍可被间接读取。

你以为 resourceVersion 变了就是轮换成功?这只是控制面的一厢情愿。Kubernetes Secret 默认以未加密形式存于 etcd,能访问 API 或存储层的主体随时可能直接读取内容[1]。更隐蔽的风险在于权限边界:同一命名空间内获准创建 Pod 的主体,通常能令 Pod 引用该空间的机密,形成间接读取路径[1]。因此,“允许创建工作负载”与“允许读取机密”并非独立授权域,审查访问权时必须联动检查 Deployment 等配置[1]。

新手最常栽跟头的地方往往在“权限最小化”这一步。很多团队为了图省事,给 ServiceAccount 赋予了 get, list, watch secrets 的宽泛权限,却忘了同步检查其是否拥有 create pod 的权限。一旦这两个权限同时存在,攻击者(或误操作脚本)就能利用“创建 Pod”的权限,让新启动的 Pod 去引用该命名空间内的任意 Secret,从而绕过直接读取 Secret 的 RBAC 限制。正确的做法是,在审查权限时,必须将“谁能创建 Pod”和“谁有权读 Secret”作为一组关联条件进行交叉验证,而不是单独审核某一个角色。

控制器更新与应用生效的断层

CSI 驱动同步了文件,或 cert-manager 更新了证书,不代表应用进程已感知新值。环境变量方式强制要求重启 Pod,文件挂载方式则依赖应用自身的热加载能力[2]。仅凭资源版本变化无法判定 Secret 自动轮换机制是否真正生效,必须建立从控制面到数据面的完整证据链[3][2]。

本章核查清单

  • [ ] 确认 Secret 存储是否启用静态加密(非默认状态)
  • [ ] 审查创建 Pod 的 ServiceAccount 是否具备引用同命名空间机密的权限
  • [ ] 区分应用消费模式:环境变量需重启,文件挂载需热加载支持
  • [ ] 验证新版本是否已被目标工作负载实际消费,而非仅停留在控制器

Kubernetes Secret 轮换失败排查步骤:检查分发路径与权威来源

排查分发路径问题需优先核查权威来源定义是否清晰及链路是否打通,而非盲目重启 Pod,以解决控制面成功但应用仍读旧值的现象。

你的控制器日志显示“更新成功”,但应用进程读取的仍是旧密码。问题通常出在分发路径未打通,或权威来源定义模糊导致覆盖冲突。别急着重启 Pod,先按顺序核查这两条核心链路。

CSI Driver 轮换前提条件核查清单

Secrets Store CSI Driver 负责将外部机密挂载到容器 tmpfs 卷中,但这不代表它会自动感知变化。自 v1.6.0 版本起,周期性更新依赖 RequiresRepublish 机制,且必须显式开启旋转功能。[2]

执行以下三步确认驱动状态:

  • 核对 CSIDriver 对象:检查集群中该驱动的 CRD 资源,确认 spec.podInfoOnMount.requiresRepublish 字段已设为 true。
  • 验证启动参数:查看 DaemonSet 或 Deployment 的容器命令,确保包含 --enable-secret-rotation=true 标志。仅设置 API 字段而不加此参数,驱动不会主动拉取新内容。[2]
  • 审查轮询日志:在驱动 Pod 日志中搜索 refreshing secret 关键字,确认其正按设定周期从 Provider 获取数据并写入挂载点。

若上述任一环节缺失,即便控制器完成了更新,应用侧也只会看到静态快照,导致 CSI Driver 挂载更新失效。

cert-manager 与外部 Provider 的关联验证

另一条常见路径是 cert-manager 签发证书并写入 Secret。这条链路与 CSI Driver 逻辑不同:前者生成私钥和证书存入 K8s Secret,后者直接从外部源拉取文件。[4][5]

两者混用极易引发循环依赖。请执行以下验证:

  • 追踪签发状态:查看 CertificateRequest 资源,确保其状态为 Approved 且无阻塞错误。
  • 定位写入目标:确认签发结果确实写入了 secretName 指定的 Secret,而非被其他组件意外拦截。
  • 锁定唯一权威:明确每类机密的唯一来源。若同时配置了 cert-manager 和 CSI Driver 同步到同一 Secret,必有一方会被另一方覆盖。[4][5]
对比项 cert-manager 模式 Secrets Store CSI Driver 模式
数据来源 CA/Issuer (如 Vault, ACM) 外部 Provider (如 AWS Secrets Manager)
落点位置 直接写入 Kubernetes Secret 挂载至 Pod tmpfs 卷 (可选同步回 Secret)
更新触发 到期前自动续期 依赖轮询周期 + RequiresRepublish 标志
消费方式 环境变量 / Volume Mount 文件读取 / Volume Mount
权威冲突 高 (若与 CSI 同步目标重叠) 高 (若与 cert-manager 同步目标重叠)

设计原则只有一条:为每类机密指定唯一的权威来源。防止控制器职责重叠导致的循环依赖或覆盖写入,是排查轮换失效的第一道防线。[4][5]

实战中常见的一个反例是混合架构下的“双写”陷阱。某金融客户曾同时使用 cert-manager 管理内部 CA 签发的证书,又通过 AWS Secrets Manager 同步外部密钥。他们配置了 cert-manager 将证书写入 K8s Secret,同时又配置了 CSI Driver 将 AWS 中的密钥同步到同一个 Secret 对象。结果是,cert-manager 的周期性续期动作会不断覆盖 CSI Driver 刚刚写入的密钥,导致应用端频繁出现认证失败,因为密钥在“被写入”和“被覆盖”之间反复横跳。这种冲突往往难以通过简单的日志排查发现,必须从架构层面梳理每个 Secret 对象的“单一事实来源”。

本章执行检查清单

  • [ ] 确认 CSIDriver 对象的 requiresRepublish 字段已启用
  • [ ] 检查驱动 Pod 启动参数包含 --enable-secret-rotation=true
  • [ ] 验证驱动日志中存在正常的轮询刷新记录
  • [ ] 确认 cert-manager 签发的 Secret 名称未被 CSI 覆盖
  • [ ] 明确区分两类链路的权威来源,消除双向同步冲突

如何验证新版本已被消费?将“已轮换”转化为可审计的闭环条件

验证新版本已被消费需将轮换完成定义为外部生成、控制器同步、工作负载消费及旧版失效四个连续状态的完整闭环。

别把控制器日志里的“同步成功”当成终点。真正的轮换完成,必须拆成四个连续状态:外部 Provider 生成新版本 -> 控制器完成同步 -> 工作负载实际消费 -> 旧版本失效删除[2][5]。缺了最后一步,所谓的自动轮换只是数据在存储层变了,业务端还在用旧凭据。

构建可审计的轮换验收框架

要证明新版本被消费,你得保留完整的证据链,而不是只盯着 Secret 的时间戳。

1. 锁定权威来源与变更记录 先确认源头。如果是证书链路,检查 Certificate、CertificateRequest 和 Issuer 的状态,记录目标 Secret 中的证书序列号变化。如果是外部机密,核对 SecretProviderClass 配置、CSI 驱动参数以及挂载记录的变更历史[4][5]。这些是证明“机制发生过动作”的基础。

2. 区分消费者类型,执行针对性验证 不同加载方式决定了你的验证手段完全不同:

消费者类型 错误验证方式 正确验证标准 关键证据
文件挂载 (CSI) 仅检查文件修改时间戳 进程已重新加载文件并完成协议重载 应用日志显示配置热更新或重启信号
环境变量 仅检查 Pod 资源版本更新 滚动策略覆盖重启窗口 + Readiness 探针通过 实例冗余数据 + 业务监控无抖动
不可变对象 尝试原地修改数据 发布新对象 + 切换引用 + 清理旧对象 新旧对象共存窗口期的连接审计

对于文件消费者,文件时间戳变了不代表进程读了它。你必须看到应用日志里出现了“重读配置”或“协议重载”的记录[2]。对于环境变量消费者,由于容器启动时读取一次就固化,必须依赖滚动更新策略、Readiness 探针和足够的实例冗余来覆盖重启窗口,确保没有流量在旧值生效期间丢失[2]。

3. 强制旧版本退出 如果开启了 immutable 字段,Secret 无法原地更新。此时轮换流程必须转为:发布新对象 -> 切换消费者引用 -> 在明确定义的重叠窗口保留旧对象 -> 满足清理条件后移除旧对象[3]。只有当连接审计和加载证据确认旧凭据不再被客户端使用时,才算完成旧版本退出[2]。

零中断轮换的实战判断标准

最终结论很简单:只有当新版本被目标工作负载消费,且旧版本按风险策略退出,才算真正的轮换完成[2][5]。

在此之前,你还需要做两件事来兜底。第一,确认业务监控覆盖滚动更新期间,服务可用性指标(如错误率、延迟)保持正常。第二,在缺乏热加载能力的场景下,必须演练回退机制,验证一旦新版本有问题能否快速切回旧版。

不要相信“控制器成功”就是胜利。把验收单元从控制面提升到应用面,才是可审计的闭环。


常见问题解答 (FAQ)

Q: 为什么我的 Secret 资源版本更新了,但应用还是报错认证失败? A: 这通常是因为应用通过环境变量注入密钥,而环境变量在容器启动时一次性读取,不会随 Secret 更新而动态变化。你需要检查应用是否支持热加载,或者是否配置了正确的滚动更新策略来重启 Pod。

Q: Secrets Store CSI Driver 的轮询频率可以调整吗? A: 可以。在驱动的配置中通常可以通过参数调整轮询间隔,但建议不要设置过短,以免给后端 Provider 造成不必要的压力。同时务必确认 RequiresRepublish 标志已正确开启。

Q: 如何避免 cert-manager 和 CSI Driver 同时管理同一个 Secret 造成的冲突? A: 最佳实践是遵循“单一事实来源”原则。要么让 cert-manager 直接写入 K8s Secret 供应用读取,要么让 CSI Driver 直接挂载文件供应用读取,尽量避免两个组件同时向同一个 Secret 对象写入数据。


参考来源

  1. Secrets | Kubernetes · https://kubernetes.io/docs/concepts/configuration/secret/(A级)
  2. Secret Auto Rotation - Secrets Store CSI Driver · https://secrets-store-csi-driver.sigs.k8s.io/topics/secret-auto-rotation(A级)
  3. Secret | Kubernetes · https://kubernetes.io/docs/reference/kubernetes-api/core/secret-v1/(A级)
  4. Usage - Secrets Store CSI Driver · https://secrets-store-csi-driver.sigs.k8s.io/getting-started/usage(A级)
  5. Certificate resource - cert-manager Documentation · https://cert-manager.io/docs/usage/certificate/(A级)