3. Secrets生命周期管理

Secret 不是“创建完就结束”的静态对象。在真实集群里,它从被创建那一刻起就进入一条生命周期:创建 → 挂载/注入 → 更新 → 生效 → 轮换 → 删除 → 回收。任何一个环节处理不当,都会导致两类典型事故:一是旧凭据未及时失效,二是新凭据未及时生效,业务在轮换窗口内出现 401/403 或连接失败。本节围绕这条生命周期链,讲清楚每个阶段的机制、可观察现象与判断依据。

创建:命令式与声明式两条路径

创建 Secret 有两种方式。命令式适合临时调试,声明式适合纳入 GitOps 与审计。命令式最常用的是从字面量或文件生成:

# 从字面量创建(值会被 base64 编码存储)
kubectl create secret generic db-cred \
  --from-literal=username=app \
  --from-literal=password='S3cr3t-2024!'

# 从文件创建,键名默认取文件名
kubectl create secret generic tls-cert \
  --from-file=./tls.crt --from-file=./tls.key

# 查看(注意 data 是 base64,不是加密)
kubectl get secret db-cred -o jsonpath='{.data.password}' | base64 -d

声明式则用 YAML,配合 stringData 字段可直接写明文,API Server 在写入时自动转成 data 的 base64。这里必须强调一个常见误解:base64 是编码而非加密,任何能读取 etcd 或拥有 get secret 权限的人都能还原明文。因此创建阶段就要决定是否启用静态加密(EncryptionConfiguration)与最小权限 RBAC。

创建后,Secret 通过三种方式被消费:作为环境变量(envFrom/valueFrom)、作为卷挂载(volume mount)、或由 kubelet 用于拉取镜像(imagePullSecrets)。这三种方式的“更新可见性”完全不同,是理解后续更新的关键。

更新:三种消费方式的生效差异

更新 Secret 后,Pod 是否立即看到新值,取决于消费方式:

  • 环境变量:进程启动时读取一次,之后永不更新。要生效必须重启 Pod(滚动重启或删除重建)。
  • 卷挂载:kubelet 会周期性同步(默认约 60 秒,受 --sync-frequency 与 configMapAndSecretChangeDetectionStrategy 影响),挂载文件内容会被替换。但应用是否重新读取文件,取决于应用自身逻辑。
  • subPath 挂载:不会随 Secret 更新而更新,这是最容易被忽略的坑。

因此“更新 Secret”本身不等于“凭据已轮换”。一个可判断的验证方法是:进入容器执行 cat /etc/secret/password 对比新旧值,并观察应用日志中是否出现重新加载或鉴权成功记录。若使用环境变量,则必须触发一次重启,例如:

kubectl rollout restart deployment/app-api
kubectl rollout status deployment/app-api

删除与垃圾回收:谁负责清理

删除 Secret 用 kubectl delete secret db-cred。但真正需要设计的是“谁在什么时候删”。Kubernetes 的垃圾回收基于 ownerReferences:当父对象被删除,且子对象的 ownerReference 指向它、blockOwnerDeletion 未阻止时,子对象会被级联回收。典型场景是 Helm 管理的 Secret 带有 helm.sh/hook 或 owner 注解,卸载 Release 时被一并清理。

反过来,如果 Secret 被 Pod 以卷方式引用,删除 Secret 不会立即删除 Pod,但挂载点会变成空或报错,新调度到该节点的 Pod 可能因找不到 Secret 而卡在 ContainerCreating。因此删除前应先确认引用关系:

# 查找哪些 Pod 引用了该 Secret(需结合 describe 或审计)
kubectl get pods -A -o json | \
  jq '.items[] | select(.spec.volumes[]?.secret.secretName=="db-cred") | .metadata.name'

对于动态生成的凭据(如 Vault Agent 注入、External Secrets Operator 同步),删除策略应由控制器决定:是保留、重建还是标记为过期。建议为每个 Secret 打上 owner、rotation-policy、expires-at 等标签,让回收有据可依。

不可变 Secret:把“变更”变成“替换”

从 Kubernetes v1.21 起,Secret 支持 immutable: true。一旦设为不可变,data 字段不能再修改,只能删除后重建。这带来两个直接收益:一是防止误改导致线上凭据漂移;二是 kubelet 不再需要 watch 该 Secret,显著降低大规模集群中 API Server 与 kubelet 的负载。

apiVersion: v1
kind: Secret
metadata:
  name: db-cred-v2
type: Opaque
immutable: true
stringData:
  password: "S3cr3t-2024-07!"

不可变 Secret 的代价是:更新必须走“新建 + 切换引用 + 删除旧对象”的路径。这恰好与版本控制策略天然契合——把每次轮换视为一个新版本对象,而不是原地修改。

版本控制策略:命名、切换与回滚

推荐做法是给 Secret 名称带版本后缀,例如 db-cred-v1、db-cred-v2,并在 Deployment 中通过引用切换。切换时先创建新 Secret,再更新工作负载引用,最后观察就绪,确认无误后删除旧版本。这样任何一步失败都能快速回滚到旧名称。

下面这张图把整条生命周期与三种消费方式的生效差异放在一起,便于对照判断“更新后到底有没有生效”。

Secret 生命周期与消费方式生效差异 创建 literal / file / YAML 挂载 / 注入 env / volume / pull 更新 原地改 / 新版本 生效验证 文件 / 日志 / 鉴权 删除 / 回收 ownerRef / 级联 三种消费方式对“更新”的响应 环境变量 envFrom / valueFrom • 进程启动时读取一次 • 更新后容器内值不变 • 必须重启 Pod 才生效 卷挂载 volumeMounts • kubelet 周期同步(约 60s) • 挂载文件内容被替换 • 应用需自行重读文件 subPath 挂载 • 不随 Secret 更新而更新 • 常见于证书单文件挂载 • 需重建 Pod 或改挂载方式 不可变 Secret 与版本控制策略 immutable: true data 不可原地修改 降低 kubelet watch 压力 版本命名 db-cred-v1 / v2 每次轮换新对象 引用切换 Deployment 改引用 滚动发布 + 就绪检查 回滚 / 清理 切回旧版本 删除过期对象 验证通过后进入下一轮

把这张图落到操作上,一个可复用的轮换闭环是:新建带版本后缀的 Secret → 更新工作负载引用 → 滚动发布并等待就绪 → 验证鉴权与日志 → 删除旧版本 Secret。其中“验证”这一步不能省,因为卷挂载的同步有延迟、环境变量需要重启、subPath 根本不更新,三者表现不同,只有实际观察才能确认轮换成功。

实践建议:对生产凭据统一使用 immutable: true + 版本后缀命名,把“更新”转化为“替换”;对必须原地更新的场景,明确记录消费方式,并在变更单中写清“是否需要重启 Pod”。
常见误区:把 Secret 更新后直接认为已生效。若使用环境变量或 subPath,更新不会自动进入容器;若使用卷挂载,也要等 kubelet 同步周期,且应用必须重新读取文件。删除 Secret 前务必确认引用它的工作负载,避免新 Pod 卡在 ContainerCreating。

最后给出一个可直接套用的轮换检查清单,字段按本节语境设计:

检查项判断依据不通过时的动作
新 Secret 已创建且键名一致kubectl get secret db-cred-v2 -o jsonpath='{.data}'核对键名与旧版本一致
工作负载已切换引用kubectl get deploy app-api -o yaml 中 secretName 为新版本修正 YAML 并重新 apply
Pod 已滚动完成kubectl rollout status 返回 successfully rolled out查看事件与容器日志定位失败原因
容器内实际值已更新卷挂载:cat /etc/secret/password;env:需重启后核对卷挂载等待同步;env 触发重启
业务鉴权正常应用日志无 401/403,健康检查通过回滚到旧版本 Secret 引用
旧版本已清理kubectl get secret db-cred-v1 返回 NotFound确认无引用后删除

至此,Secret 的生命周期不再是零散命令的集合,而是一条可设计、可验证、可回滚的工程链路:创建时决定消费方式与是否不可变,更新时区分三种生效路径,删除时依赖 ownerReferences 与引用检查,版本控制则把“改”变成“换”,让每一次轮换都有明确的起点、验证点和回退点。