Kubernetes ConfigMap、Secret与配置轮换全过程
ConfigMap和Secret解决“应用制品”与“环境配置”分离:同一个不可变镜像可以部署到测试、预发和生产,通过不同配置连接不同依赖。它们不是配置中心的完整替代,也不是创建后应用自动热更新的魔法对象。
本页从配置提交到API Server开始,追踪etcd持久化、PodSpec引用、kubelet获取、环境变量快照、投射卷原子切换和应用重载。Secret部分进一步解释Base64、静态加密、RBAC、节点/Pod暴露面、外部密钥系统和零停机轮换。
学习目标
学完后应能:
- 区分ConfigMap、Secret、环境变量、配置中心和外部密钥系统。
- 解释配置对象从API请求到etcd、kubelet、容器的完整路径。
- 区分
data、binaryData、stringData和Secret Type。 - 解释
env、envFrom、Command参数和Volume投射的生效时机。 - 解释为什么环境变量不会热更新。
- 解释投射卷的周期同步、Atomic Writer和
..data符号链接切换。 - 解释为什么
subPath不会接收ConfigMap/Secret更新。 - 设计应用安全的配置热加载和失败回退。
- 解释Base64、TLS传输、etcd静态加密和KMS各保护哪一段。
- 识别
get/list/watch secret、创建Pod、exec和节点权限的间接泄密路径。 - 比较原生Secret、External Secrets、CSI挂载和应用直连Vault/KMS。
- 设计数据库密码、API Key和TLS证书的零停机轮换。
- 使用版本化对象/Checksum触发可审计滚动发布。
- 排查CreateContainerConfigError、旧配置、权限和轮换失败。
一、为什么配置不能写死在镜像
如果把生产配置写进Docker镜像:
- 每个环境需要重建不同镜像,无法证明多环境运行的是同一制品。
- 密码可能进入镜像层、构建缓存、Registry和SBOM扫描系统。
- 修改开关也要重新Build,配置和代码发布无法独立审计。
- 回滚镜像时可能同时回滚不该回滚的环境配置。
- 镜像被下载到开发机后可能携带生产Secret。
正确边界:
镜像:代码、Runtime、静态默认值
ConfigMap:非敏感环境配置
Secret/外部密钥系统:敏感值
Deployment:引用哪个配置版本以及如何发布默认值仍可随代码存在,但必须是安全默认值,例如生产危险功能默认关闭;环境覆盖值由部署系统注入。
二、ConfigMap与Secret准确区别
| 对象 | 设计用途 | 典型内容 | 默认安全能力 |
|---|---|---|---|
| ConfigMap | 非敏感配置 | 日志级别、服务地址、功能开关、模板 | 普通API对象,不应放密码 |
| Secret | 敏感小数据 | 密码、Token、TLS私钥、Registry凭据 | 专用类型与挂载处理,但默认Base64不是加密 |
把密码放进Secret比ConfigMap更能表达安全意图,并支持Secret专用Volume和类型校验,但如果RBAC、etcd加密、审计和节点安全都没有配置,Secret仍可泄露。
三、配置从提交到容器的全链路
flowchart TD
A["CI/管理员提交ConfigMap或Secret"] --> B["API Server认证、授权、准入和校验"]
B --> C["对象持久化到etcd"]
C --> D["PodSpec引用对象名称和Key"]
D --> E["Scheduler绑定Pod到Node"]
E --> F["kubelet获取PodSpec和引用对象"]
F --> G{"注入方式"}
G -- "env/envFrom" --> H["创建容器时写入进程环境快照"]
G -- "Volume/Projected" --> I["kubelet在Node准备投射文件"]
I --> J["挂载到容器路径"]
H --> K["应用启动并读取"]
J --> KAPI对象创建成功只证明配置被集群接受,不证明:
- Pod引用的名称和Key正确。
- Node上的kubelet能读取/投射。
- 应用能解析格式。
- 应用已经重新加载。
- 所有副本同时使用同一版本。
四、对象大小和适用边界
ConfigMap和Secret适合小型配置,单个对象通常限制在约1MiB级别,确切校验以目标Kubernetes版本为准。不要用于:
- 大型模型文件。
- JAR/二进制制品。
- 大字典和视频。
- 高频变化的海量动态配置。
- 业务记录。
大文件使用对象存储、制品仓库或Volume;高频动态配置使用专业配置中心,并设计缓存、推送、版本和回退。
五、ConfigMap数据结构
apiVersion: v1
kind: ConfigMap
metadata:
name: order-api-config-v42
namespace: commerce
labels:
app: order-api
config-version: v42
data:
LOG_LEVEL: INFO
FEATURE_RESERVATION_V2: "false"
application.yml: |
server:
port: 8080
order:
max-items: 100
reservation-timeout: 2s
binaryData:
truststore.bin: <base64-binary-content>
immutable: true5.1 data
data值是UTF-8字符串,适合键值和文本文件。YAML中的布尔/数字最好加引号,确保最终类型是字符串:
FEATURE_X: "false"
MAX_RETRY: "3"5.2 binaryData
用于不能可靠表示为UTF-8文本的二进制值,API/YAML中以Base64表示。大型二进制仍不应放ConfigMap。
5.3 data与binaryData键不能冲突
同一个Key不能同时出现在两者中。Key会成为环境变量名或文件名时,还需满足相应目标规则。
5.4 immutable
immutable: true不可变对象不能原地修改内容,优点:
- 防止生产配置被同名覆盖。
- 减少kubelet/API的变更Watch压力。
- 发布和回滚引用明确版本。
更新时创建新名称,例如 v43,修改Deployment引用。不可变字段启用后不能改回可变,通常需要删除重建对象;删除前必须确认所有Pod引用。
六、Secret数据结构
apiVersion: v1
kind: Secret
metadata:
name: order-db-credential-v7
namespace: commerce
type: Opaque
stringData:
username: order_app_v2
password: example-placeholder-do-not-commit
immutable: true6.1 data
data中的值必须是Base64编码:
data:
username: b3JkZXJfYXBw任何能读Secret的人都可以解码:
printf '%s' 'b3JkZXJfYXBw' | base64 --decode所以Base64只解决二进制传输表示,不提供保密性。
6.2 stringData
stringData允许提交明文,由API Server合并转换到data。它方便生成对象,但:
- 明文仍会出现在本地文件、命令历史或CI输入中。
- 不应把生产值提交Git。
- 与Server-Side Apply的字段管理配合存在边界,应按版本验证。
- 读取Secret时通常只看到
data,不会原样返回stringData。
6.3 不在文档Demo中使用真实密码
页面占位符不是可用于生产的凭据。真实值应由密钥系统、受控CI Secret变量或人工安全通道提供,日志和构建产物中不得回显。
七、常见Secret Type
| Type | 用途 | 关键Key/边界 |
|---|---|---|
Opaque | 通用自定义Secret | 由应用约定Key |
kubernetes.io/tls | TLS证书和私钥 | tls.crt、tls.key |
kubernetes.io/dockerconfigjson | 私有Registry认证 | .dockerconfigjson |
kubernetes.io/basic-auth | 用户名密码语义 | username、password |
kubernetes.io/ssh-auth | SSH私钥 | ssh-privatekey |
kubernetes.io/service-account-token | 旧式长期SA Token | 现代工作负载优先短期投射Token |
Type提供结构语义和部分校验,不会自动轮换或消除泄露风险。
八、ServiceAccount Token不要默认做长期Secret
现代Kubernetes工作负载优先使用Bound ServiceAccount Token Volume:
- Token投射到Pod。
- 有目标Audience。
- 有到期时间。
- kubelet负责轮换。
- 与Pod/ServiceAccount身份绑定。
长期 kubernetes.io/service-account-token Secret泄露后有效窗口更大。应用还应:
- 使用专用ServiceAccount。
- 禁止不需要的默认Token挂载:
automountServiceAccountToken: false。 - RBAC最小权限。
- 校验Audience。
- 不把Token写日志。
九、创建配置时怎样避免泄露
从本地文件生成ConfigMap清单:
kubectl create configmap order-api-config-v42 -n commerce \
--from-file=application.yml=./application-prod.yml \
--dry-run=client -o yamlSecret可以从受控文件/输入创建,但命令参数可能进入Shell历史和进程列表,不推荐:
# 不推荐把真实密码直接写在命令行参数中
kubectl create secret generic order-db \
--from-literal=password='real-password'更安全的交付方式取决于平台:加密Git清单、External Secrets、Secrets Store CSI Driver、Vault/云密钥服务或受保护CI管道。无论哪种方式,都要审计明文在哪个时刻、哪个进程、哪个磁盘上出现。
十、方式一:单个环境变量valueFrom
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: order-api-config-v42
key: LOG_LEVEL
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: order-db-credential-v7
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: order-db-credential-v7
key: password优点:
- 环境变量名称显式。
- 缺哪个Key容易审计。
- 可把外部Key映射为应用约定名称。
缺点:
- PodSpec较长。
- 进程环境可能被调试、Crash Report或同权限进程读取。
- 更新对象后正在运行的进程环境不会变化。
十一、方式二:envFrom批量导入
envFrom:
- configMapRef:
name: order-api-config-v42
prefix: APP_
- secretRef:
name: order-db-credential-v7
prefix: DB_如果Secret有:
username
password应用可能得到:
DB_username
DB_password环境变量名称大小写取决于Key和前缀,不会自动转成应用想要的格式。
风险:
- 对象新增一个Key会在下次启动时自动进入容器。
- 多个来源同名时存在覆盖顺序和显式env优先规则,必须按API语义验证。
- 不符合环境变量名称规则的Key可能无法注入并产生Event。
- 批量Secret容易把应用不需要的敏感值全部暴露。
生产敏感值优先逐项引用和最小暴露。
十二、为什么环境变量不会热更新
创建容器时,kubelet/Runtime根据PodSpec和ConfigMap/Secret解析出环境变量,传给容器初始进程。进程环境是启动时快照:
flowchart TD
A["Pod启动前读取ConfigMap/Secret"] --> B["解析env和envFrom"]
B --> C["Runtime创建容器进程"]
C --> D["进程获得环境变量快照"]
D --> E["之后ConfigMap/Secret被修改"]
E --> F["正在运行的进程环境保持旧值"]Linux没有由Kubernetes远程修改任意进程环境的安全通用机制。要生效必须:
- 重建/重启Pod;或
- 不使用环境变量,改用文件/动态配置客户端并实现重载。
执行:
kubectl rollout restart deployment/order-api -n commerce会修改Pod Template Annotation触发新ReplicaSet,但它没有把配置版本写入发布审计。更推荐版本化引用或Checksum Annotation。
十三、Command和Args引用环境变量
Pod command/args中可以按Kubernetes规则展开 $(VAR_NAME):
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: order-api-config-v42
key: LOG_LEVEL
args:
- "--logging.level.root=$(LOG_LEVEL)"这仍是容器创建时展开,不会热更新。不要把Secret放进命令行参数:
- 某些进程列表可见。
- 应用启动日志可能打印完整参数。
- 诊断工具可能采集Command Line。
敏感值优先使用受控文件或专用Secret客户端。
十四、方式三:ConfigMap Volume
volumeMounts:
- name: app-config
mountPath: /app/config
readOnly: true
volumes:
- name: app-config
configMap:
name: order-api-config-v42
items:
- key: application.yml
path: application.yml
defaultMode: 0440容器看到:
/app/config/application.yml注意挂载目录会遮住镜像原路径内容。如果镜像中 /app/config/default.yml 存在,整个目录被Volume覆盖后可能不可见。需要把默认配置放另一目录,或明确列出Volume内容。
十五、方式四:Secret Volume
volumeMounts:
- name: db-credential
mountPath: /run/secrets/order-db
readOnly: true
volumes:
- name: db-credential
secret:
secretName: order-db-credential-v7
items:
- key: username
path: username
- key: password
path: password
defaultMode: 0400应用读取:
/run/secrets/order-db/username
/run/secrets/order-db/passwordLinux节点上的Secret Volume通常由kubelet以tmpfs等内存文件系统方式提供,避免按普通持久卷写入磁盘;具体实现和Windows节点边界应按环境确认。Secret仍会存在于Node内存、容器内存和应用连接对象中。
十六、文件权限defaultMode
YAML支持八进制写法:
defaultMode: 0400JSON不支持八进制字面量,等价值需要写十进制。最终权限还受:
- 容器User/Group。
fsGroup。- 文件系统和平台。
- 应用是否需要写入。
投射文件通常应只读。应用若需要生成派生配置,应复制到可写 emptyDir,不要尝试覆盖只读Secret投射。
十七、Projected Volume
Projected Volume可把多个来源合并到同一目录:
volumes:
- name: identity
projected:
defaultMode: 0440
sources:
- serviceAccountToken:
path: token
audience: order-api
expirationSeconds: 3600
- configMap:
name: order-api-config-v42
items:
- key: LOG_LEVEL
path: log-level
- downwardAPI:
items:
- path: namespace
fieldRef:
fieldPath: metadata.namespace它可以组合ConfigMap、Secret、Downward API和ServiceAccount Token等来源。不同来源Key映射到同一路径时应避免冲突。
十八、投射卷更新全过程
普通ConfigMap/Secret Volume不是一次永久复制。kubelet会维护Pod所需对象的缓存和同步循环,在检测到ResourceVersion变化后重新生成投射内容。
flowchart TD
A["ConfigMap/Secret更新并写入API"] --> B["kubelet通过Watch/Cache/同步检测新版本"]
B --> C["在Volume内部准备新的版本目录"]
C --> D["写入全部文件并设置权限"]
D --> E["原子切换..data符号链接"]
E --> F["文件名链接指向新..data内容"]
F --> G["应用下次按路径打开时看到新文件"]这类实现常称Atomic Writer。容器内可能看到:
..2026_07_15_10_00_00.123456789/
..data -> ..2026_07_15_10_00_00.123456789
application.yml -> ..data/application.yml内部目录名称和实现细节可能变化,应用不应依赖具体 ..2026_* 名称。
十九、文件更新为什么不是即时的
更新延迟取决于:
- kubelet同步周期。
- 对象缓存/Watch传播。
- Node和API Server负载。
- Volume投射重建。
- 应用轮询/Watcher周期。
因此 API 对象更新成功后,所有Node不会在同一纳秒切换。生产配置变更必须允许传播窗口,并按Pod验证实际配置版本。
二十、打开的文件描述符可能继续读旧内容
Atomic Writer通常切换符号链接到新inode。应用如果:
- 每次读取都重新按路径打开文件,通常可读到新版本。
- 启动时打开一次并长期持有File Descriptor,可能继续读旧inode。
- 只监听文件内容修改而不处理符号链接/rename,可能收不到预期事件。
配置Watcher需要在目标语言和文件系统上测试rename、symlink和inode变化。Java应用常见安全策略是检测版本文件变化后,重新打开、完整解析和验证。
二十一、subPath为什么不更新
示例:
volumeMounts:
- name: app-config
mountPath: /app/application.yml
subPath: application.ymlsubPath通常在容器创建时绑定到当时具体文件/inode,不跟随Volume内部后续 ..data 链接切换。ConfigMap/Secret更新后,该文件仍可能保持旧内容。
如果需要热更新:
- 挂载整个目录,不使用subPath;或
- 使用版本化对象并滚动重建Pod。
不要因为API对象内容是新的,就断言subPath容器文件已经更新。
二十二、多个文件是否原子一致
同一个投射Volume内,Atomic Writer通常先准备完整新目录再切换 ..data,使该Volume内文件集合一起切换。但:
- 两个不同Volume没有跨Volume原子事务。
- ConfigMap和Secret是两个独立API更新时,中间可能出现版本组合。
- 应用读取多个文件期间仍可能跨越切换时刻。
需要强一致配置集合时:
- 合并为单一版本化配置包;或
- 加入manifest/version并读取后校验所有文件版本;或
- 通过配置中心事务/发布机制;或
- 滚动新Pod,启动时只读取固定版本对象。
二十三、应用热加载必须怎样设计
安全流程:
flowchart TD
A["检测配置版本变化"] --> B["读取完整新配置到临时对象"]
B --> C["Schema、类型、范围和组合校验"]
C --> D["预创建新连接池/客户端并做连通性验证"]
D --> E{"新配置是否可用"}
E -- "否" --> F["保留旧配置并告警"]
E -- "是" --> G["原子替换应用内配置引用"]
G --> H["排空并关闭旧资源"]
H --> I["记录脱敏版本和生效时间"]错误做法:文件一变化就逐字段修改全局对象。解析到一半失败会让应用处于新旧混合状态。
热更新还要决定哪些字段允许动态变化。端口、线程模型、JVM参数等通常需要重启;日志级别和部分开关可热更新。
二十四、为什么多数Java配置仍采用滚动重启
Spring Boot常在启动时解析环境变量和配置文件,构造Bean、线程池、连接池和客户端。文件更新不意味着这些对象自动重建。
滚动重启的优点:
- 每个Pod启动时读取完整固定配置。
- Startup/Readiness验证新配置。
- 失败时旧Pod可保留。
- 配置版本和Pod Template可审计。
代价是需要足够Surge容量和优雅终止。高风险配置优先版本化对象+滚动发布,而不是未经验证的运行时反射刷新Bean。
二十五、Checksum Annotation触发滚动更新
Deployment只观察Pod Template。ConfigMap内容变了,但Pod Template中的对象名称没变,不会自动产生新ReplicaSet。
可将配置内容Hash写入Pod Template Annotation:
spec:
template:
metadata:
annotations:
config.example.com/order-api-checksum: "sha256:9f4c..."内容变化 → Checksum变化 → Pod Template变化 → 新ReplicaSet滚动发布。
注意:
- Hash计算必须稳定,避免键顺序导致无意义发布。
- Annotation只存Hash,不存Secret明文。
- 低熵Secret的裸Hash可能被字典猜测,最好使用受保护流水线/HMAC或版本ID。
- 回滚要恢复旧配置版本和对应Hash。
Helm模板常用 sha256sum 实现,但具体模板路径和渲染结果要测试。
二十六、版本化对象名称
更明确的方式:
configMap:
name: order-api-config-v42
secret:
secretName: order-db-credential-v7更新创建 v43/v8,再修改Pod Template引用。这天然触发Rollout,并可看到每个ReplicaSet引用哪个版本。
清理旧对象前检查:
- 旧ReplicaSet回滚是否仍需要。
- 运行Pod是否仍引用。
- Job/CronJob历史模板。
- 其他Namespace/Workload引用。
- 灾备恢复清单。
删除旧配置会让回滚创建Pod时出现CreateContainerConfigError。
二十七、自动Reloader Controller边界
有些第三方Controller观察ConfigMap/Secret变化,自动修改Deployment Annotation触发重启。优点是自动,风险:
- 一个对象变化可能同时重启大量服务。
- 未经审批的Secret轮换直接扩散。
- 重启顺序和业务指标闸门不足。
- Controller故障或权限过大。
生产应有变更范围、金丝雀、发布窗口、速率限制和审计,不能把“检测到变化就全量重启”当安全默认。
二十八、Secret在etcd中怎样保护
默认Base64 Secret写入API后,会作为Kubernetes对象持久化到etcd。如果没有静态加密,拥有etcd数据文件/备份访问权的人可能读取内容。
静态加密链路:
flowchart TD
A["客户端经TLS提交Secret"] --> B["API Server完成认证授权准入"]
B --> C["Encryption Provider加密Secret字段"]
C --> D["密文写入etcd"]
D --> E["授权读取时API Server解密"]
E --> F["经TLS返回给kubelet/客户端"]可使用AESCBC、AESGCM、KMS Provider等,具体安全性、性能和版本支持需按集群设计。KMS Envelope Encryption让API Server通过KMS插件保护数据加密密钥,降低主密钥直接驻留配置文件的风险。
二十九、开启加密不等于历史数据自动全部重写
配置Encryption Provider后,新写入/更新对象使用新规则;已有Secret可能仍以旧格式保存,通常需要受控读取并重写对象完成迁移。
密钥轮换流程应包括:
- 新Key加入Provider列表首位。
- 保留旧Key用于解密。
- 重写Secret使其使用新Key。
- 验证所有对象可读、etcd备份可恢复。
- 确认不再依赖旧Key后再移除。
直接删除旧Key可能让历史Secret和备份无法解密。必须在隔离环境演练恢复。
三十、静态加密不能防什么
etcd静态加密保护磁盘和备份中的数据,不防:
- 有权限通过API
get/list/watch secrets的用户。 - 被授权挂载Secret的Pod。
- 可创建/修改Pod并引用Secret的人。
- 可exec进入业务Pod的人。
- 被攻陷的Node/kubelet。
- 应用把Secret写入日志或Trace。
- Secret已经加载进进程内存后的泄露。
Secret安全是身份、权限、加密、节点、应用和审计的组合。
三十一、RBAC中的间接Secret读取
最明显的权限:
get/list/watch secrets但以下权限也可能间接获得Secret:
- 在Namespace创建Pod,并把目标Secret挂载进去。
- 修改Deployment,使其引用Secret并把内容输出。
- exec进入已经挂载Secret的Pod。
- 创建调试容器进入目标Pod命名空间。
- 读取包含Secret的应用日志/Heap Dump。
所以“用户不能kubectl get secret”不等于不能获得Secret。Namespace内工作负载写权限接近高权限,应做角色拆分和Admission限制。
三十二、list/watch比想象中更危险
拥有 list secrets 通常可以读取列表中每个Secret内容,而不只是名称;watch可以持续获得后续更新。控制器若只需某个Secret,应尽量缩小Namespace、ResourceName和操作范围,但RBAC对list/watch与resourceNames组合有语义限制,需要按API验证。
平台Controller确需Watch大量Secret时,应独立ServiceAccount、最小Namespace范围、网络隔离、审计和供应链保护。
三十三、Secret数据可能在哪里暴露
CI变量和流水线内存
kubectl输入/终端历史
API Server请求和审计策略
etcd及其备份
kubelet/Node内存
容器Volume或环境变量
应用进程内存、Heap Dump、Core Dump
日志、Trace、错误页
监控标签或诊断包安全评审要追踪完整生命周期,而不是只检查Git里有没有Secret YAML。
三十四、环境变量注入Secret的风险
环境变量方便,但可能被:
- 应用错误地打印全部Environment。
- Spring Actuator/env等端点暴露,虽然现代版本会脱敏但不能盲目信任。
- 诊断脚本、Crash Report采集。
- 同容器内进程读取。
kubectl exec env被高权限用户查看。
文件挂载可让应用只读取特定路径并配合权限,但应用仍能读到。外部Secret SDK还可避免值进入Kubernetes API,但增加运行时依赖。
三十五、外部Secret方案对比
35.1 Operator同步为Kubernetes Secret
External Secrets类Controller从Vault/云Secret Manager读取,创建普通Kubernetes Secret。
优点:应用兼容原生Secret;缺点:敏感值最终仍进入Kubernetes API/etcd和Node,Controller拥有广泛读取/写入权限。
35.2 Secrets Store CSI Driver
Node插件在Pod启动/运行时从外部Provider把Secret挂载为文件,可选择是否同步为Kubernetes Secret。
优点:可避免持久化为原生Secret;缺点:依赖Node插件、Provider、身份和外部系统可用性,轮换后应用仍要重新读取。
35.3 应用直接调用Vault/Secret Manager
应用通过Workload Identity/短期Token读取Secret。
优点:可获得动态凭据、Lease和精细审计;缺点:应用需要SDK、缓存、续租、故障回退和启动依赖设计。
35.4 Sidecar/Agent
Agent负责认证、续租和写文件,应用读取本地文件。减少应用SDK耦合,但增加Sidecar生命周期、资源和文件重载复杂度。
没有一种方案自动解决全部问题,按威胁模型、可用性、轮换频率和运维能力选择。
三十六、Workload Identity优于静态云Key
访问云Secret Manager时,优先通过Pod ServiceAccount映射云身份,获得短期凭据,而不是把长期Access Key作为Kubernetes Secret。
收益:
- 减少长期密钥分发。
- 按ServiceAccount授权。
- 短期Token自动轮换。
- 云审计可关联工作负载身份。
仍需防止被攻陷Pod使用其合法身份读取允许的Secret,并限制元数据/Token窃取。
三十七、数据库密码零停机轮换
若数据库只允许一个用户一个密码,直接修改密码会造成旧Pod瞬间失效。更安全的是双凭据重叠:
flowchart TD
A["当前应用使用credential-v7"] --> B["数据库创建新用户/新Key v8并保留v7"]
B --> C["创建Secret v8"]
C --> D["少量新Pod引用v8并验证连接和业务"]
D --> E["滚动全部Pod到v8"]
E --> F["确认旧连接排空且无v7使用"]
F --> G["撤销数据库v7凭据"]
G --> H["按保留策略清理Secret v7"]关键点:
- 新旧凭据有受控重叠窗口。
- 新凭据先验证,旧凭据最后撤销。
- 监控按用户名/Key ID区分使用量。
- 回滚窗口内旧凭据仍可用,但窗口不能无限延长。
- 数据库连接池可能长期持有旧连接,需要排空/重建。
三十八、API Key轮换
外部系统若支持两个活跃Key:
- 创建Key B,Key A保持有效。
- Secret/配置加入B。
- 金丝雀Pod使用B。
- 全量滚动并观察B调用成功率。
- 确认A无使用后撤销A。
若只支持单Key,必须与提供方协商切换窗口、双读/代理或短暂停机方案。不能假设Kubernetes Secret更新能让所有Pod同一时刻切换。
三十九、TLS证书轮换
证书和私钥写入TLS Secret后,Ingress Controller或应用需要Watch并热加载。验证:
- Secret ResourceVersion。
- 新证书SAN、链和有效期。
- Controller/应用加载日志。
- 多副本是否全部更新。
- 真实入口握手返回的新证书。
保留旧证书/私钥多久取决于终止点和回滚策略。私钥轮换后应撤销旧访问和安全清理,不只更新Secret。
四十、完整商业Deployment Demo
前提:order-api-config-v42和order-db-credential-v7由受控流程创建。
apiVersion: v1
kind: ServiceAccount
metadata:
name: order-api
namespace: commerce
automountServiceAccountToken: false
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-api
namespace: commerce
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: order-api
template:
metadata:
labels:
app: order-api
config-version: v42
credential-version: v7
annotations:
config.example.com/config-version: v42
config.example.com/credential-version: v7
spec:
serviceAccountName: order-api
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
containers:
- name: order-api
image: registry.example.com/order-api@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
env:
- name: SPRING_CONFIG_ADDITIONAL_LOCATION
value: file:/app/config/
- name: DB_USERNAME_FILE
value: /run/secrets/order-db/username
- name: DB_PASSWORD_FILE
value: /run/secrets/order-db/password
volumeMounts:
- name: app-config
mountPath: /app/config
readOnly: true
- name: db-credential
mountPath: /run/secrets/order-db
readOnly: true
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
periodSeconds: 5
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
memory: 1Gi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumes:
- name: app-config
configMap:
name: order-api-config-v42
items:
- key: application.yml
path: application.yml
defaultMode: 0440
- name: db-credential
secret:
secretName: order-db-credential-v7
defaultMode: 0440DB_PASSWORD_FILE只是应用约定,Spring Boot不会自动理解所有 _FILE变量;应用或启动脚本必须实现按文件读取,并确保不把内容拼进命令行或日志。示例通过 fsGroup 和组只读 0440 让非root应用读取投射文件;实际文件所有权行为需在目标存储/运行时验证,不能为了快速修复权限改成 0777。
四十一、发布配置全过程
flowchart TD
A["创建不可变ConfigMap v43/Secret v8"] --> B["服务端Dry Run和策略校验"]
B --> C["修改Deployment引用和版本Label"]
C --> D["Deployment创建新ReplicaSet"]
D --> E["少量新Pod启动读取新配置"]
E --> F["Startup/Readiness和业务Smoke"]
F --> G{"错误率、延迟和业务指标正常"}
G -- "否" --> H["停止扩散并回退旧对象引用"]
G -- "是" --> I["滚动全量Pod"]
I --> J["观察旧凭据/旧配置使用归零"]
J --> K["按回滚窗口清理旧版本"]配置发布也是发布,必须有变更单、审批、金丝雀、监控和回滚。
四十二、CreateContainerConfigError排查
Pod状态:
CreateContainerConfigError常见原因:
- ConfigMap/Secret不存在。
- 对象在不同Namespace。
- Key不存在。
- env/envFrom引用错误。
- Volume items引用不存在Key。
- ServiceAccount或ImagePullSecret配置错误。
证据:
kubectl describe pod <pod> -n commerce
kubectl get configmap order-api-config-v42 -n commerce
kubectl get secret order-db-credential-v7 -n commerce
kubectl get pod <pod> -n commerce -o yamldescribe Event通常明确指出哪个对象/Key缺失。不要因为Secret敏感就完全不检查元数据;可以核对名称、Key列表和ResourceVersion,不输出值。
四十三、修改环境变量配置不生效
确认:
- ConfigMap/Secret API对象ResourceVersion是否更新。
- Pod创建时间是否早于更新。
- 注入方式是否env/envFrom。
- Deployment是否产生新ReplicaSet。
- 新Pod是否引用新对象/Hash。
环境变量不会热更新。执行受控滚动发布,而不是进入容器尝试 export,后者只影响当前Shell及其子进程,不会修改Java主进程环境。
四十四、Volume文件仍是旧值
按顺序:
- 是否使用
subPath。 - ConfigMap/Secret是否可变并已更新。
- Pod是否引用正确对象。
- kubelet/Node是否健康。
- 等待是否超过合理同步窗口。
- 容器路径是否被另一个Volume覆盖。
- 应用是否长期持有旧File Descriptor。
- 应用是否缓存解析结果。
只执行 cat 新文件能证明路径读取到新内容,不能证明应用内存中的配置对象已经更新。
四十五、配置文件权限错误
表现:
Permission denied检查:
kubectl exec <pod> -n commerce -- \
sh -c 'id && ls -l /app/config /run/secrets/order-db'精简镜像无Shell时用受控Debug Container。核对:
- defaultMode。
- runAsUser/runAsGroup。
- fsGroup。
- 应用实际路径。
- SELinux/AppArmor和平台策略。
- 文件是否只读。
不要为解决一个读取错误直接改成0777。
四十六、配置格式错误导致CrashLoopBackOff
kubectl logs <pod> -n commerce --previous --tail=300 --timestamps
kubectl describe pod <pod> -n commerce检查应用解析错误行、Schema、类型、单位和必填字段。发布前应在CI使用与应用相同解析器或配置Schema验证;YAML语法通过不等于业务配置合法。
保留旧可用ReplicaSet和 maxUnavailable: 0,避免所有Pod同时读取坏配置。
四十七、凭据轮换后一部分Pod失败
检查:
- Pod Label/Annotation中的credential-version。
- 每个ReplicaSet引用的Secret名称。
- 数据库/外部系统中新旧凭据是否同时有效。
- 连接池是否保持旧连接。
- 新Secret Key名称/格式。
- 应用是否支持文件重载,还是需要重启。
- 失败是否集中在某Node(投射/CSI问题)。
按版本指标分组,不要只看Deployment总成功率。
四十八、Secret删除后的影响
删除Secret后:
- 已运行Pod的现有投射/内存值可能短时间继续存在,不能据此认为安全。
- kubelet后续同步可能报错。
- 新Pod无法创建或启动。
- Rollout/扩容/节点恢复失败。
- 回滚到引用旧Secret的ReplicaSet失败。
删除前扫描所有Workload、Job、Ingress、ServiceAccount和外部Controller引用,并确认回滚窗口结束。
四十九、审计和可观测性
记录但不泄密:
- ConfigMap/Secret名称、UID、ResourceVersion和版本Label。
- 谁在何时创建、更新、读取、删除。
- 哪个Deployment/ReplicaSet引用哪个版本。
- 配置加载成功/失败和脱敏错误。
- 外部Secret Lease/续租状态。
- 凭据旧版本使用量。
- 证书到期时间。
不要记录:
- Secret
data。 - 完整连接串。
- Authorization/Cookie。
- 私钥。
- 原始医疗/个人敏感配置。
Kubernetes Audit Policy本身也要避免把Secret Request/Response Body写进低保护日志。
五十、常见误区
- Secret Base64就是加密:任何读取者都能解码。
- 开启etcd加密就绝对安全:API读取者、Pod和Node仍可获得明文。
- 修改ConfigMap后环境变量会变:进程环境是启动快照。
- Volume文件更新等于应用生效:应用可能缓存或持有旧FD。
subPath会跟随更新:通常绑定旧inode。- 两个Volume会一起原子更新:跨Volume没有统一事务。
rollout restart记录了配置版本:它只触发新Pod,版本仍需显式记录。- 不能get Secret就无法窃取:创建Pod/修改Workload/exec也可能间接读取。
- 把Secret放环境变量绝对安全:日志、诊断和进程环境可能泄露。
- 外部Secret不会进入集群:Operator同步模式仍创建原生Secret。
- 轮换就是覆盖旧密码:无重叠窗口会中断旧Pod。
- 删除旧Secret没有影响:扩容、重建和回滚可能失败。
五十一、面试标准回答
ConfigMap和Secret区别
ConfigMap保存非敏感配置,Secret表达密码、Token和证书等敏感小数据;Secret的Base64不是加密,生产还需TLS、etcd静态加密/KMS、RBAC、节点安全、审计和外部密钥系统。两者都可通过env或Volume注入,但更新生效机制不同。
为什么环境变量不会热更新
kubelet在创建容器时解析ConfigMap/Secret,把值传给进程初始环境;进程环境是启动快照,后续API对象变化不会远程修改正在运行进程。需要滚动重建Pod,或改用文件/配置客户端并由应用实现安全重载。
ConfigMap Volume怎样更新
kubelet检测对象新ResourceVersion后,在Volume内部准备完整新版本目录并原子切换
..data符号链接。应用重新按路径打开通常能读到新内容,但更新不是即时的;subPath绑定旧文件不会跟随,长期打开的FD和应用内缓存也可能保持旧版本。
Secret开启etcd加密后还会怎样泄露
静态加密主要保护etcd磁盘和备份,不防有API读取权限的用户、可创建Pod并挂载Secret的人、exec权限、被攻陷Node/Pod、应用日志、Heap Dump和进程内存。必须做完整生命周期和间接权限治理。
怎样零停机轮换数据库密码
先在数据库创建可并存的新用户/Key,创建新版本Secret,让金丝雀和全量新Pod使用并验证;等待旧连接排空和旧凭据使用归零后再撤销旧凭据,最后按回滚窗口清理旧Secret。直接覆盖单一密码会让旧Pod立即失效。
版本化ConfigMap和Checksum有什么作用
Deployment只观察Pod Template,同名ConfigMap内容变化不会自动Rollout。可创建不可变版本名并修改引用,或把稳定内容Hash/版本ID写入Template Annotation,使新ReplicaSet产生;这样可审计、金丝雀和回滚,Secret低熵裸Hash还要考虑猜测风险。
五十二、学习实验与验收
实验一:环境变量不热更新
创建Pod用env引用ConfigMap,修改ConfigMap后比较API对象和进程环境;滚动重建后再比较,解释快照边界。
实验二:观察Atomic Writer
挂载整个ConfigMap目录,在测试容器中查看 ls -la、..data和inode;更新对象后观察符号链接切换,并比较重新open与长期持有FD的读取结果。
实验三:验证subPath
同一ConfigMap分别以目录和subPath挂载,更新后比较两处内容,说明为什么subPath不适合热更新。
实验四:坏配置金丝雀
创建新版本ConfigMap包含Schema错误,仅让一个金丝雀Pod引用;观察启动失败、previous日志和旧ReplicaSet保留,修复后再扩散。
实验五:Secret间接权限
在隔离Namespace创建测试Role,比较不能get Secret但能创建Pod时是否能挂载读取;据此调整RBAC和Admission。不得在生产Secret上实验。
实验六:凭据轮换
用测试数据库创建v1/v2用户,完成新Secret、金丝雀、全量、旧连接排空、撤销v1和回滚窗口演练,记录每一步指标。
验收清单
- 能画出API到etcd、kubelet、容器的配置链。
- 能区分data、binaryData、stringData和Secret Type。
- 能解释env/envFrom的覆盖和生命周期。
- 能解释Atomic Writer、symlink和inode。
- 能证明subPath不更新。
- 能设计应用配置解析、验证、原子切换和失败回退。
- 能区分Base64、TLS、静态加密和KMS保护范围。
- 能审计直接和间接Secret读取权限。
- 能比较三类外部Secret集成。
- 能设计数据库/API Key/TLS轮换。
- 能用版本名或Checksum触发可审计Rollout。
- 能完成CreateContainerConfigError、旧文件和轮换故障Runbook。
