Kubernetes 基础体系 · 第 22/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
ConfigMap 与 Secret:注入、更新、不可变、轮换和安全边界
ConfigMap 和 Secret 都是 Kubernetes API 中用于保存配置数据的对象。它们可以被 Pod 以环境变量、文件或命令行参数的形式使用,但二者的语义并不相同:
- ConfigMap 用于非机密配置,例如日志级别、功能开关、配置文件片段。
- Secret 用于需要限制访问范围的数据,例如密码、令牌、私钥、证书。
- Secret 的“机密性”不是由对象名称或
kind: Secret自动提供的。默认情况下,Secret 数据通常只是经过 Base64 编码;真正的保护还依赖 API 访问控制、etcd 静态加密、节点安全和应用自身的泄漏控制。
本文基于 Kubernetes 当前稳定的 v1 API,重点解释数据注入、更新传播、不可变对象、凭据轮换和安全边界之间的因果关系。
1. 先建立对象和 Pod 之间的关系
一个 ConfigMap 或 Secret 是 Namespace 范围内的 API 对象。Pod 引用它时,引用中只写对象名称:
apiVersion: v1
kind: ConfigMap
metadata:
name: payment-config
namespace: production
data:
LOG_LEVEL: "info"
config.yaml: |
timeoutSeconds: 3
apiVersion: v1
kind: Secret
metadata:
name: payment-credentials
namespace: production
type: Opaque
stringData:
username: payment
password: change-me
Pod 也必须位于 production Namespace:
apiVersion: v1
kind: Pod
metadata:
name: payment
namespace: production
spec:
containers:
- name: app
image: example/payment:1.0
envFrom:
- configMapRef:
name: payment-config
- secretRef:
name: payment-credentials
Pod 不能直接引用另一个 Namespace 中的 ConfigMap 或 Secret。这个限制不是语法偶然,而是安全边界的一部分:如果任意 Namespace 的 Pod 都能直接引用其他 Namespace 的 Secret,Namespace 隔离和 RBAC 的意义会被显著削弱。
Pod 内的多个容器共享同一个网络 Namespace 和 Pod 生命周期;它们也可以通过 Pod volume 共享由 ConfigMap 或 Secret 提供的文件。因此,一个 Pod 中拥有访问该 volume 的容器,通常都能读取其中的数据。容器之间不是 Secret 的安全隔离边界。
Pod 还具有一个由 kubelet 管理的 sandbox 和容器生命周期。ConfigMap、Secret 的 volume 文件由 kubelet 在节点上准备,应用容器读取的是节点上挂载后的文件,而不是每次读取都访问 API Server。
2. ConfigMap 和 Secret 的数据模型
2.1 ConfigMap 的 data 和 binaryData
ConfigMap 的文本数据位于 data:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "debug"
app.yaml: |
server:
port: 8080
不能把 ConfigMap 当作无限大小的配置数据库使用。ConfigMap 和 Secret 都受 Kubernetes 对象大小及 API Server 请求限制影响,通常应将约 1 MiB 视为单个对象的上限量级;实际可用大小还会受到序列化、etcd 和 API Server 配置影响。大型配置应放在专门的配置系统或对象存储中,由应用按需加载。
binaryData 用于二进制内容,值也以 Base64 表示:
apiVersion: v1
kind: ConfigMap
metadata:
name: binary-config
binaryData:
logo.bin: AAEC
data 与 binaryData 是两个字段,不应把同一个键同时放入二者。
2.2 Secret 的 data、stringData 和 Base64
Secret 的 data 字段要求每个值是 Base64 编码:
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
username: cGF5bWVudA==
password: Y2hhbmdlLW1l
解码后分别是:
username = payment
password = change-me
Base64 只是编码,不是加密。任何能够读取 Secret 对象的人都可以直接解码:
kubectl -n production get secret db-secret \
-o jsonpath='{.data.password}' | base64 --decode
stringData 便于手写清单:
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
stringData:
username: payment
password: change-me
API Server 接收对象时会把 stringData 合并到 data 的语义结果中;读取 Secret 时通常看到的是 data,而不是原始 stringData。如果同一个键同时存在,stringData 的值会覆盖对应的 data 值。
stringData 适合交互式创建,但在使用 Server-Side Apply 时要特别谨慎:官方文档明确指出,stringData 与 Server-Side Apply 的字段管理存在不良交互。需要声明式持续管理时,应考虑在安全的流水线中生成 data,或使用专门的 Secret 管理方案,避免把明文写入 Git。
Secret 的 type 是用途标识和部分工具的行为提示,例如:
Opaque:通用 Secret。kubernetes.io/tls:通常包含tls.crt和tls.key。kubernetes.io/dockerconfigjson:镜像仓库认证配置。kubernetes.io/basic-auth:通常包含用户名和密码。
type 本身不会让数据更安全,也不等同于 Kubernetes 自动验证了内容的密码学有效性。
3. 三种注入方式及其状态语义
ConfigMap 和 Secret 常见的注入方式有三种:
- 单个键映射为环境变量;
- 多个键通过
envFrom导入环境变量; - 作为 volume 挂载为文件。
此外,还可以把引用值写入容器启动命令参数。不同方式的更新行为不同,这是生产中最容易误判的地方。
3.1 单个环境变量
apiVersion: v1
kind: Pod
metadata:
name: env-example
namespace: production
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c"]
args: ["echo LOG_LEVEL=$LOG_LEVEL; sleep 3600"]
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
这里的关键过程是:
- Pod 被调度到节点;
- kubelet 在创建容器时解析 ConfigMap 和 Secret 引用;
- 得到的值被写入容器的初始环境;
- 容器进程启动;
- 后续对象更新不会修改已经存在的进程环境。
因此,更新 app-config 或 db-secret 后,容器内已有进程看到的环境变量不会自动变化。若应用需要新值,必须重新创建容器,通常通过滚动更新 Deployment 完成。
这也意味着环境变量适合“启动时读取一次”的配置,不适合要求在线变化的配置或频繁轮换的凭据。
3.2 envFrom
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: db-secret
envFrom 会把对象中的键批量转换为环境变量名。键名必须满足环境变量命名约束;非法或不适合作为环境变量名的键可能被跳过并产生事件,而不是以文件名形式保留。
批量导入还带来命名冲突问题。多个来源可能提供同名键,显式 env、envFrom 的来源顺序和容器运行时最终环境构造规则会影响结果。生产配置应尽量使用明确的单键映射,或者使用唯一、带前缀的键名,避免把整个 Secret 无差别导入环境。
3.3 作为 volume 挂载
apiVersion: v1
kind: Pod
metadata:
name: volume-example
namespace: production
spec:
containers:
- name: app
image: example/payment:1.0
volumeMounts:
- name: app-config
mountPath: /etc/payment
readOnly: true
- name: db-credentials
mountPath: /var/run/secrets/payment
readOnly: true
volumes:
- name: app-config
configMap:
name: app-config
- name: db-credentials
secret:
secretName: db-secret
容器中通常会看到:
/etc/payment/LOG_LEVEL
/etc/payment/app.yaml
/var/run/secrets/payment/username
/var/run/secrets/payment/password
文件名来自 ConfigMap 或 Secret 的键,文件内容是对应值。volume 方式与环境变量最大的不同是:对象更新后,kubelet 通常会把新的内容投影到挂载目录中。这不是应用一定能感知的保证,而是文件内容更新机制;应用仍然需要重新读取文件或监听文件变化。
典型实现会在挂载目录中维护版本化内容,并通过符号链接或原子切换让容器看到新版本。应用不应依赖某个具体的宿主机目录结构,而应只读取挂载路径下的公开文件。更新传播存在延迟,延迟受到 kubelet 同步周期、缓存策略和对象变更通知方式影响,不能把它当作严格实时推送。
如果应用只在启动时读取 /etc/payment/app.yaml,文件后来更新也不会改变应用内存中的配置。正确的因果链是:
API 对象更新
→ kubelet 发现新版本
→ 挂载目录切换到新内容
→ 应用重新读取或检测到文件变化
→ 应用内部状态更新
前三步不代表最后一步自动发生。
3.4 subPath 是一个重要反例
下面的挂载方式看似只挂载一个文件:
volumeMounts:
- name: app-config
mountPath: /etc/payment/app.yaml
subPath: app.yaml
使用 ConfigMap 或 Secret volume 的 subPath 挂载时,后续 ConfigMap 或 Secret 更新不会自动反映到这个 subPath 文件中。原因是它不是持续暴露整个投影目录,而是在容器启动时绑定一个具体路径。
如果需要动态更新,应挂载整个目录:
volumeMounts:
- name: app-config
mountPath: /etc/payment
然后让应用读取 /etc/payment/app.yaml。如果应用必须使用单文件路径,常见做法是由应用或 sidecar 负责把目录中的当前文件复制到目标位置,但这会引入额外同步、权限和故障处理逻辑。
4. 引用不存在或更新失败时会发生什么
引用 ConfigMap 或 Secret 时,可以选择是否要求它必须存在:
env:
- name: OPTIONAL_MODE
valueFrom:
configMapKeyRef:
name: optional-config
key: mode
optional: true
当 optional: true 时,缺失对象或缺失键通常会被当作没有该值处理;当为 false 或未设置时,缺少必需数据会阻止容器正常创建,Pod 可能处于 CreateContainerConfigError 状态。
诊断时应同时检查 Pod 事件和对象本身:
kubectl -n production describe pod payment
kubectl -n production get configmap app-config -o yaml
kubectl -n production get secret db-secret -o yaml
describe pod 中的事件通常比只查看 Pod 状态更有用,例如可以区分:
- 引用对象不存在;
- 引用的键不存在;
- Secret 类型或内容不符合应用预期;
- volume 挂载失败;
- 容器启动后自行退出。
应用已经运行后修改对象,不会修复一个已经注入到环境变量中的错误值;即使对象内容正确,容器仍可能持有旧值。
5. 更新传播与 Deployment 重启
5.1 为什么修改 ConfigMap 不一定重启 Pod
Kubernetes 不会因为 ConfigMap 或 Secret 变化就自动创建新的 Pod。Deployment 的 Pod 模板没有变化,因此 ReplicaSet 的期望状态也没有变化。
例如:
kubectl -n production edit configmap app-config
这可能让 volume 文件最终变化,但不会自动滚动重启 Deployment,也不会更新环境变量。
如果应用只能在启动时读取配置,可以显式重启:
kubectl -n production rollout restart deployment/payment
kubectl -n production rollout status deployment/payment
这会创建新的 ReplicaSet Pod,而不是修改原 Pod 的进程环境。
5.2 用 Pod 模板哈希触发滚动更新
声明式部署常把配置内容的哈希写入 Pod 模板注解:
spec:
template:
metadata:
annotations:
config-hash: "由发布系统根据 app-config 内容计算"
当配置内容变化时,发布系统更新 config-hash,Deployment 认为 Pod 模板变化,于是创建新的 ReplicaSet。
这个方案的关键不是注解名称,而是条件:
ConfigMap 内容本身不属于 Deployment 的 Pod 模板;只有把内容变化反映到模板字段,才能触发 Deployment 的滚动更新。哈希不能放成固定字符串,否则每次发布模板都相同,不会产生滚动更新。
5.3 直接 kubectl set env 的边界
可以通过命令修改 Deployment 的环境变量:
kubectl -n production set env deployment/payment CONFIG_VERSION=2025-01-01
这会改变 Pod 模板,进而触发滚动更新。但它只适合明确管理的环境变量,不会自动把 ConfigMap 的全部内容同步到 Pod 模板,也可能与 Git 中的声明式配置产生漂移。
6. 不可变 ConfigMap 和 Secret
immutable: true 表示对象创建后,其数据不能再被修改:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config-v1
immutable: true
data:
LOG_LEVEL: "info"
apiVersion: v1
kind: Secret
metadata:
name: db-credentials-v1
immutable: true
type: Opaque
stringData:
username: payment
password: initial-password
不可变对象的状态转移是:
创建
→ 可读取、可引用
→ data 允许读取但不允许修改
→ 需要新内容时创建新对象
尝试修改数据会被 API Server 拒绝,而不是静默接受:
kubectl -n production patch configmap app-config-v1 \
--type merge \
-p '{"data":{"LOG_LEVEL":"debug"}}'
预期结果是类似“对象不可变”的错误。
不可变的主要机制收益是减少意外修改和 kubelet 对对象变更的监听、缓存压力;它不是加密机制,也不阻止有权限的用户读取对象。不可变 Secret 仍然可能被 get secret 的主体读取,也仍然可能通过环境变量、文件、日志或调试接口泄漏。
不可变对象改变了更新策略:不能原地修改,而要使用新名称或新版本:
volumes:
- name: db-credentials
secret:
secretName: db-credentials-v2
然后更新 Deployment 的 Pod 模板,让新 Pod 引用 v2。推荐顺序是:
- 创建
db-credentials-v2; - 更新 Deployment,使 Pod 模板引用
v2; - 等待新 Pod 就绪;
- 验证应用已使用新凭据;
- 删除不再需要的
v1,同时确认没有其他工作负载引用它。
直接删除并重新创建同名对象不是等价的安全替换方案。删除期间可能导致新 Pod 无法启动,旧 Pod 的挂载行为也不应被当作可靠的原子更新协议。使用新名称并让 Deployment 通过滚动更新切换,状态更容易观察和回滚。
immutable 字段本身一旦设为 true 也不能再改回 false;若必须恢复可变对象,需要创建新对象。
7. Secret 轮换:数据更新不是凭据切换
轮换是生成或取得新凭据、让消费者切换到新凭据、验证新凭据有效后撤销旧凭据的完整过程。仅仅修改 Secret 对象,不等于轮换完成。
设旧凭据为 ,新凭据为 。安全切换至少需要满足:
- 身份提供方接受 ;
- 应用能够读取并使用 ;
- 在所有必要消费者完成切换前, 仍可用;
- 所有消费者完成切换后,才撤销 。
因此典型流程是:
生成 C1
→ 在数据库或外部身份系统中启用 C1
→ 创建 Secret v2
→ 更新工作负载并滚动发布
→ 验证新 Pod 使用 C1 成功
→ 撤销 C0
→ 清理 Secret v1
如果数据库只允许一个密码,直接把数据库密码改成 ,再更新 Kubernetes Secret,会产生一个时间窗口:
数据库已经要求 C1
Pod 环境变量仍然是 C0
应用连接失败
更稳妥的方案是双凭据或双用户轮换:
- 创建新数据库用户或新密码 ;
- 同时保留 ;
- 发布使用 的新 Pod;
- 通过健康检查、连接指标和业务验证确认成功;
- 撤销 。
如果凭据通过 Secret volume 提供,应用可以设计为重新读取文件并重建连接池,从而避免 Pod 重启;但这必须是应用明确实现的行为。若使用环境变量,通常需要重启或滚动更新,因为环境变量对运行中的进程是固定的。
轮换还必须处理失败路径:
- 新凭据创建成功,但发布失败:保留旧凭据,不要立即撤销;
- 部分 Pod 已切换,部分 Pod 未切换:在双凭据窗口中允许两者;
- 新凭据格式错误:新 Pod 启动探针或业务探针应失败,Deployment 保留旧 Pod;
- 旧凭据撤销后发现仍有旧 Pod:撤销动作可能造成线上错误,需先确认所有消费者;
- Secret 泄漏:不能只更新 Kubernetes 对象,还必须在真实凭据提供方撤销泄漏凭据。
Kubernetes 不负责判断某个 Secret 是否已被应用使用,也不负责协调外部数据库凭据的有效期。轮换控制器、发布系统和应用协议必须共同完成这条链路。
8. Secret 的安全边界
8.1 Secret 不是自动加密
默认读取对象时,Secret 的内容以 Base64 形式存储在 API 对象中:
kubectl -n production get secret db-secret -o yaml
能读取该对象的人可以解码;能读取保存该对象的 etcd 数据的人也可能获得原始内容。要保护 etcd 静态数据,需要在 API Server 配置加密提供者,例如使用 EncryptionConfiguration 配置 Secret 的加密存储,并在云环境中结合 KMS 提供密钥保护。
静态加密解决的是:
etcd 磁盘、etcd 备份或存储介质被读取
它不解决:
拥有 Secret get 权限的用户
拥有 Pod exec 权限的用户
运行在目标节点上的高权限进程
应用把密码打印到日志
应用把密码发送到第三方
KMS 通常为 API Server 的数据加密提供外部密钥管理能力。KMS 插件、版本、部署方式和云厂商实现存在差异,不能仅凭“集群启用了 KMS”就推断所有数据已经满足某个合规要求。应验证实际加密配置、密钥权限、轮换、备份恢复和故障时 API Server 的行为。
8.2 RBAC 是主要访问控制边界
一个最小读取权限示例:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: payment-secret-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-secret"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payment-secret-reader
namespace: production
subjects:
- kind: ServiceAccount
name: payment
namespace: production
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: payment-secret-reader
这个例子把权限限制为:
- 仅
productionNamespace; - 仅名为
db-secret的 Secret; - 仅
get操作; - 仅绑定给
paymentServiceAccount。
实际还需要检查工作负载是否使用了这个 ServiceAccount:
spec:
serviceAccountName: payment
如果 Pod 使用默认 ServiceAccount,却把读取权限授予 payment,权限配置不会产生预期效果。
应避免给普通应用授予:
resources: ["secrets"]
verbs: ["list", "watch"]
list 和 watch 可能让主体获得大量 Secret,而不仅是单个对象。也要避免把 Secret 读取权限授予 Namespace 中所有 ServiceAccount 或整个集群的通用角色。
可用以下命令检查权限:
kubectl auth can-i get secret/db-secret \
--namespace production \
--as system:serviceaccount:production:payment
kubectl auth can-i list secrets \
--namespace production \
--as system:serviceaccount:production:payment
第一个命令应返回 yes,第二个在最小权限设计中通常应返回 no。
8.3 节点和容器权限会改变边界
Secret volume 最终由 kubelet 在节点上准备并挂载给容器。拥有节点级高权限、能够访问 kubelet 或容器运行时数据的攻击者,可能绕过普通 Namespace 视角读取节点上 Pod 的 Secret。
因此:
- Namespace 不是节点级隔离;
- Pod 安全上下文和容器特权设置仍然重要;
privileged容器、主机路径挂载、主机 PID/网络等能力会扩大影响范围;- 能执行
kubectl exec的人通常可以在应用进程上下文中读取环境变量或挂载文件; - 备份系统、日志系统、调试工具可能复制 Secret 内容。
Secret 的安全性是多层条件的交集,而不是单个 API 字段:
其中任一项失效,都可能导致机密暴露或无法及时止损。
9. ConfigMap 与 Secret 的选择边界
选择依据不是“配置文件放 ConfigMap、密码放 Secret”这么简单,而是数据是否需要机密保护、消费者如何读取以及更新频率。
适合 ConfigMap 的例子:
data:
LOG_LEVEL: "info"
FEATURE_X_ENABLED: "true"
config.yaml: |
timeoutSeconds: 3
适合 Secret 的例子:
stringData:
database-url: postgresql://...
api-token: ...
private-key.pem: |
-----BEGIN PRIVATE KEY-----
...
但以下误区需要明确:
- 把 ConfigMap 中的字段命名为
PASSWORD,不会使它变成机密; - 把 Secret 放入 Git 后再用 Base64 编码,不会防止泄漏;
- 使用 Secret 不会阻止应用把值打印到日志;
- Secret 不是外部密码管理器,不会自动生成、续期或撤销凭据;
- TLS Secret 只是一种数据约定,证书是否过期需要额外的证书管理流程。
如果应用需要严格的保密性、审计、自动续期或短生命周期凭据,仅使用原生 Secret 往往不够。
10. External Secrets 的位置和风险
External Secrets 一类方案通常通过 CRD 和控制器,把外部密钥系统中的值同步为 Kubernetes Secret。例如外部系统可能是云厂商 Secrets Manager、Vault 或其他企业密码平台。
其数据流大致是:
flowchart LR
A[外部密钥系统] -->|控制器读取| B[External Secret 控制器]
B -->|创建或更新| C[Kubernetes Secret]
C -->|env 或 volume| D[Pod]
D -->|重新读取或重启| E[应用使用新凭据]
这个方案把“源数据治理”放到了外部系统,但不会自动消除 Kubernetes 中的安全边界问题:
- 控制器需要读取外部系统的权限;
- 控制器需要写入 Kubernetes Secret 的权限;
- 同步后的 Kubernetes Secret 仍可能被 API 读取、存入 etcd、挂载到节点;
- 外部系统轮换后,控制器同步存在延迟;
- 应用仍需处理文件重读、连接池更新或滚动重启;
- 外部系统不可用时,必须定义是保留旧值、阻止发布还是让 Pod 失败。
因此,External Secrets 不是“Secret 不再存在于 Kubernetes”,而是把 Kubernetes Secret 变成外部系统的派生副本。生产设计需要明确源系统、同步方向、刷新周期、权限、失败回退和删除策略。
如果目标是让应用完全不把长期凭据写入 Kubernetes,可能需要使用工作负载身份和运行时直接向外部系统获取短期凭据;这已经超出原生 ConfigMap/Secret 注入机制,并需要相应的 SDK、网络和身份设计。
11. ServiceAccount Token 的特殊情况
ServiceAccount Token 也是 Secret 相关主题中容易混淆的一类数据,但现代 Kubernetes 中不应把长期的自动生成 Token Secret 当作默认方案。
Pod 通常通过 TokenRequest 机制获得短期、绑定 Pod 的 ServiceAccount token,并由 serviceAccountToken projected volume 投影到容器。示例:
volumes:
- name: api-access
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600
audience: https://kubernetes.default.svc
这个 token 与用户创建的普通 Opaque Secret 不同:
- 它有受众
audience; - 通常有过期时间;
- 可以由 kubelet 投影和更新;
- 应用必须能够重新读取 token 文件;
- API Server 或外部服务是否接受该 token,取决于其验证配置。
不要把 ServiceAccount token、数据库密码和普通应用 Secret 混为同一类长期凭据。版本较老的集群可能仍存在自动生成的长期 Secret token,但新版本的默认行为已经转向短期 TokenRequest;跨版本和发行版时应检查实际配置,而不是假设所有集群行为相同。
12. 常见失败表现和诊断路径
12.1 修改 Secret 后应用仍使用旧密码
首先判断注入方式:
kubectl -n production get pod payment -o yaml
如果是 env.valueFrom.secretKeyRef,旧值会持续存在,必须让容器重建:
kubectl -n production rollout restart deployment/payment
kubectl -n production rollout status deployment/payment
如果是 Secret volume,则检查挂载目录是否出现新内容:
kubectl -n production exec payment -- \
sh -c 'stat /var/run/secrets/payment/password'
但不要为了诊断把密码直接输出到终端或日志。应用可能仍缓存旧密码,或者连接池还在使用旧连接;此时需要应用级重连或重启。
12.2 Pod 进入 CreateContainerConfigError
检查事件:
kubectl -n production describe pod payment
然后验证对象和键:
kubectl -n production get secret db-secret
kubectl -n production get secret db-secret \
-o jsonpath='{.data.password}' | base64 --decode
只在受控终端执行解码命令,并注意 shell 历史、终端录屏和审计系统可能保存敏感内容。更安全的验证方式是检查键是否存在,而不是打印值:
kubectl -n production get secret db-secret \
-o jsonpath='{.data.password}' | test -n "$(cat)"
这类命令仍可能在错误处理或调试环境中暴露内容,生产诊断应优先使用应用健康检查和不包含机密的校验结果。
12.3 Secret volume 没有更新
检查以下条件:
- Pod 是否实际挂载了整个 volume,而不是
subPath; - 修改是否成功写入 API 对象;
- kubelet 是否仍能访问 API Server;
- 节点是否有 kubelet 事件或权限错误;
- 应用是否一直持有旧文件内容;
- 应用是否把文件复制到另一个位置后不再读取。
可以先检查对象的 resourceVersion 是否变化,再观察文件修改时间和应用指标,而不是直接打印 Secret:
kubectl -n production get secret db-secret \
-o jsonpath='{.metadata.resourceVersion}{"\n"}'
kubectl -n production exec payment -- \
stat /var/run/secrets/payment/password
具体传播时间不是 Kubernetes API 对用户作出的实时保证,不能把“修改成功”解释为“所有 Pod 已经立即读取新值”。
12.4 发布后旧 Pod 和新 Pod 同时存在
滚动更新的本质是控制器在副本可用性和更新速度之间进行协调。轮换凭据时,旧 Pod 可能在新 Pod 就绪前继续服务;这通常是期望行为,但要求旧凭据在切换窗口内仍有效。
如果先撤销旧凭据,再等待新 Pod 就绪,可能出现:
旧 Pod 使用 C0 → C0 被撤销 → 新 Pod 尚未就绪 → 服务连接失败
因此,轮换必须把外部凭据有效期与 Deployment 的滚动更新策略一起设计,而不能只修改 Secret YAML。
13. 生产风险与取舍
将配置通过环境变量注入很简单,但环境变量可能被调试命令、崩溃转储、诊断接口或应用日志暴露。Secret volume 避免了环境变量复制,但文件仍可被容器内进程读取,且应用需要处理文件更新。
把配置做成不可变对象可以减少意外原地修改,并让版本切换更明确,但会增加对象生命周期和垃圾清理工作。若使用固定名称并删除重建,故障窗口和回滚语义会变差;使用版本化名称则需要发布系统管理引用变更。
把 Secret 加密存入 etcd 可以降低磁盘和备份泄漏风险,但不能替代 RBAC。把 RBAC 收紧到单个 Secret 可以减少读取范围,但无法阻止被授权的应用主动打印内容。使用外部密钥系统可以改善密钥生成、审计和轮换,却会增加控制器、网络、权限和同步失败路径。
最终应根据应用的读取模型选择机制:
| 应用需求 | 更匹配的方案 |
|---|---|
| 启动时读取一次的非机密配置 | ConfigMap 环境变量或文件 |
| 需要在线重新读取的配置 | ConfigMap volume,避免 subPath,应用实现重读 |
| 启动时读取的凭据 | Secret 环境变量,但轮换时需要滚动更新 |
| 需要文件形式的证书、私钥或凭据 | Secret volume |
| 需要自动轮换和审计 | 外部密钥系统或 External Secrets 类控制器 |
| 需要短期身份令牌 | TokenRequest 或其他工作负载身份机制 |
| 需要强隔离 | 结合 RBAC、etcd 加密、节点安全、Pod 安全和应用级防泄漏 |
ConfigMap 和 Secret 解决的是“如何把键值数据交给 Pod”的问题,不是完整的配置治理、凭据生命周期或机密计算方案。正确的设计必须同时回答四个问题:数据存在哪里、谁能读取、更新如何传播、旧值何时失效。只有这四个问题都具有明确的状态转换和失败恢复路径,注入机制才真正可控。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes Resource 与 QoS:Request、Limit、CPU Throttle、OOM 和 Eviction
- 下一篇:Kubernetes 调度约束:NodeSelector、Affinity、Taint、Topology 和 Spread
- 延伸:Kubernetes Secret 治理:etcd 加密、KMS、External Secrets、轮换和泄漏
- 延伸:Kubernetes Pod 完整模型:共享 Namespace、容器、Sandbox 和生命周期
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论