Skip to content

Kubernetes ConfigMap、Secret与配置轮换全过程

ConfigMap和Secret解决“应用制品”与“环境配置”分离:同一个不可变镜像可以部署到测试、预发和生产,通过不同配置连接不同依赖。它们不是配置中心的完整替代,也不是创建后应用自动热更新的魔法对象。

本页从配置提交到API Server开始,追踪etcd持久化、PodSpec引用、kubelet获取、环境变量快照、投射卷原子切换和应用重载。Secret部分进一步解释Base64、静态加密、RBAC、节点/Pod暴露面、外部密钥系统和零停机轮换。

学习目标

学完后应能:

  1. 区分ConfigMap、Secret、环境变量、配置中心和外部密钥系统。
  2. 解释配置对象从API请求到etcd、kubelet、容器的完整路径。
  3. 区分databinaryDatastringData和Secret Type。
  4. 解释envenvFrom、Command参数和Volume投射的生效时机。
  5. 解释为什么环境变量不会热更新。
  6. 解释投射卷的周期同步、Atomic Writer和..data符号链接切换。
  7. 解释为什么subPath不会接收ConfigMap/Secret更新。
  8. 设计应用安全的配置热加载和失败回退。
  9. 解释Base64、TLS传输、etcd静态加密和KMS各保护哪一段。
  10. 识别get/list/watch secret、创建Pod、exec和节点权限的间接泄密路径。
  11. 比较原生Secret、External Secrets、CSI挂载和应用直连Vault/KMS。
  12. 设计数据库密码、API Key和TLS证书的零停机轮换。
  13. 使用版本化对象/Checksum触发可审计滚动发布。
  14. 排查CreateContainerConfigError、旧配置、权限和轮换失败。

一、为什么配置不能写死在镜像

如果把生产配置写进Docker镜像:

  • 每个环境需要重建不同镜像,无法证明多环境运行的是同一制品。
  • 密码可能进入镜像层、构建缓存、Registry和SBOM扫描系统。
  • 修改开关也要重新Build,配置和代码发布无法独立审计。
  • 回滚镜像时可能同时回滚不该回滚的环境配置。
  • 镜像被下载到开发机后可能携带生产Secret。

正确边界:

text
镜像:代码、Runtime、静态默认值
ConfigMap:非敏感环境配置
Secret/外部密钥系统:敏感值
Deployment:引用哪个配置版本以及如何发布

默认值仍可随代码存在,但必须是安全默认值,例如生产危险功能默认关闭;环境覆盖值由部署系统注入。

二、ConfigMap与Secret准确区别

对象设计用途典型内容默认安全能力
ConfigMap非敏感配置日志级别、服务地址、功能开关、模板普通API对象,不应放密码
Secret敏感小数据密码、Token、TLS私钥、Registry凭据专用类型与挂载处理,但默认Base64不是加密

把密码放进Secret比ConfigMap更能表达安全意图,并支持Secret专用Volume和类型校验,但如果RBAC、etcd加密、审计和节点安全都没有配置,Secret仍可泄露。

三、配置从提交到容器的全链路

mermaid
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 --> K

API对象创建成功只证明配置被集群接受,不证明:

  • Pod引用的名称和Key正确。
  • Node上的kubelet能读取/投射。
  • 应用能解析格式。
  • 应用已经重新加载。
  • 所有副本同时使用同一版本。

四、对象大小和适用边界

ConfigMap和Secret适合小型配置,单个对象通常限制在约1MiB级别,确切校验以目标Kubernetes版本为准。不要用于:

  • 大型模型文件。
  • JAR/二进制制品。
  • 大字典和视频。
  • 高频变化的海量动态配置。
  • 业务记录。

大文件使用对象存储、制品仓库或Volume;高频动态配置使用专业配置中心,并设计缓存、推送、版本和回退。

五、ConfigMap数据结构

yaml
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: true

5.1 data

data值是UTF-8字符串,适合键值和文本文件。YAML中的布尔/数字最好加引号,确保最终类型是字符串:

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

yaml
immutable: true

不可变对象不能原地修改内容,优点:

  • 防止生产配置被同名覆盖。
  • 减少kubelet/API的变更Watch压力。
  • 发布和回滚引用明确版本。

更新时创建新名称,例如 v43,修改Deployment引用。不可变字段启用后不能改回可变,通常需要删除重建对象;删除前必须确认所有Pod引用。

六、Secret数据结构

yaml
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: true

6.1 data

data中的值必须是Base64编码:

yaml
data:
  username: b3JkZXJfYXBw

任何能读Secret的人都可以解码:

bash
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/tlsTLS证书和私钥tls.crttls.key
kubernetes.io/dockerconfigjson私有Registry认证.dockerconfigjson
kubernetes.io/basic-auth用户名密码语义usernamepassword
kubernetes.io/ssh-authSSH私钥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清单:

bash
kubectl create configmap order-api-config-v42 -n commerce \
  --from-file=application.yml=./application-prod.yml \
  --dry-run=client -o yaml

Secret可以从受控文件/输入创建,但命令参数可能进入Shell历史和进程列表,不推荐:

bash
# 不推荐把真实密码直接写在命令行参数中
kubectl create secret generic order-db \
  --from-literal=password='real-password'

更安全的交付方式取决于平台:加密Git清单、External Secrets、Secrets Store CSI Driver、Vault/云密钥服务或受保护CI管道。无论哪种方式,都要审计明文在哪个时刻、哪个进程、哪个磁盘上出现。

十、方式一:单个环境变量valueFrom

yaml
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批量导入

yaml
envFrom:
  - configMapRef:
      name: order-api-config-v42
    prefix: APP_
  - secretRef:
      name: order-db-credential-v7
    prefix: DB_

如果Secret有:

text
username
password

应用可能得到:

text
DB_username
DB_password

环境变量名称大小写取决于Key和前缀,不会自动转成应用想要的格式。

风险:

  • 对象新增一个Key会在下次启动时自动进入容器。
  • 多个来源同名时存在覆盖顺序和显式env优先规则,必须按API语义验证。
  • 不符合环境变量名称规则的Key可能无法注入并产生Event。
  • 批量Secret容易把应用不需要的敏感值全部暴露。

生产敏感值优先逐项引用和最小暴露。

十二、为什么环境变量不会热更新

创建容器时,kubelet/Runtime根据PodSpec和ConfigMap/Secret解析出环境变量,传给容器初始进程。进程环境是启动时快照:

mermaid
flowchart TD
    A["Pod启动前读取ConfigMap/Secret"] --> B["解析env和envFrom"]
    B --> C["Runtime创建容器进程"]
    C --> D["进程获得环境变量快照"]
    D --> E["之后ConfigMap/Secret被修改"]
    E --> F["正在运行的进程环境保持旧值"]

Linux没有由Kubernetes远程修改任意进程环境的安全通用机制。要生效必须:

  • 重建/重启Pod;或
  • 不使用环境变量,改用文件/动态配置客户端并实现重载。

执行:

bash
kubectl rollout restart deployment/order-api -n commerce

会修改Pod Template Annotation触发新ReplicaSet,但它没有把配置版本写入发布审计。更推荐版本化引用或Checksum Annotation。

十三、Command和Args引用环境变量

Pod command/args中可以按Kubernetes规则展开 $(VAR_NAME)

yaml
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

yaml
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

容器看到:

text
/app/config/application.yml

注意挂载目录会遮住镜像原路径内容。如果镜像中 /app/config/default.yml 存在,整个目录被Volume覆盖后可能不可见。需要把默认配置放另一目录,或明确列出Volume内容。

十五、方式四:Secret Volume

yaml
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

应用读取:

text
/run/secrets/order-db/username
/run/secrets/order-db/password

Linux节点上的Secret Volume通常由kubelet以tmpfs等内存文件系统方式提供,避免按普通持久卷写入磁盘;具体实现和Windows节点边界应按环境确认。Secret仍会存在于Node内存、容器内存和应用连接对象中。

十六、文件权限defaultMode

YAML支持八进制写法:

yaml
defaultMode: 0400

JSON不支持八进制字面量,等价值需要写十进制。最终权限还受:

  • 容器User/Group。
  • fsGroup
  • 文件系统和平台。
  • 应用是否需要写入。

投射文件通常应只读。应用若需要生成派生配置,应复制到可写 emptyDir,不要尝试覆盖只读Secret投射。

十七、Projected Volume

Projected Volume可把多个来源合并到同一目录:

yaml
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变化后重新生成投射内容。

mermaid
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。容器内可能看到:

text
..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为什么不更新

示例:

yaml
volumeMounts:
  - name: app-config
    mountPath: /app/application.yml
    subPath: application.yml

subPath通常在容器创建时绑定到当时具体文件/inode,不跟随Volume内部后续 ..data 链接切换。ConfigMap/Secret更新后,该文件仍可能保持旧内容。

如果需要热更新:

  • 挂载整个目录,不使用subPath;或
  • 使用版本化对象并滚动重建Pod。

不要因为API对象内容是新的,就断言subPath容器文件已经更新。

二十二、多个文件是否原子一致

同一个投射Volume内,Atomic Writer通常先准备完整新目录再切换 ..data,使该Volume内文件集合一起切换。但:

  • 两个不同Volume没有跨Volume原子事务。
  • ConfigMap和Secret是两个独立API更新时,中间可能出现版本组合。
  • 应用读取多个文件期间仍可能跨越切换时刻。

需要强一致配置集合时:

  • 合并为单一版本化配置包;或
  • 加入manifest/version并读取后校验所有文件版本;或
  • 通过配置中心事务/发布机制;或
  • 滚动新Pod,启动时只读取固定版本对象。

二十三、应用热加载必须怎样设计

安全流程:

mermaid
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:

yaml
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 实现,但具体模板路径和渲染结果要测试。

二十六、版本化对象名称

更明确的方式:

yaml
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数据文件/备份访问权的人可能读取内容。

静态加密链路:

mermaid
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可能仍以旧格式保存,通常需要受控读取并重写对象完成迁移。

密钥轮换流程应包括:

  1. 新Key加入Provider列表首位。
  2. 保留旧Key用于解密。
  3. 重写Secret使其使用新Key。
  4. 验证所有对象可读、etcd备份可恢复。
  5. 确认不再依赖旧Key后再移除。

直接删除旧Key可能让历史Secret和备份无法解密。必须在隔离环境演练恢复。

三十、静态加密不能防什么

etcd静态加密保护磁盘和备份中的数据,不防:

  • 有权限通过API get/list/watch secrets 的用户。
  • 被授权挂载Secret的Pod。
  • 可创建/修改Pod并引用Secret的人。
  • 可exec进入业务Pod的人。
  • 被攻陷的Node/kubelet。
  • 应用把Secret写入日志或Trace。
  • Secret已经加载进进程内存后的泄露。

Secret安全是身份、权限、加密、节点、应用和审计的组合。

三十一、RBAC中的间接Secret读取

最明显的权限:

text
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数据可能在哪里暴露

text
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瞬间失效。更安全的是双凭据重叠:

mermaid
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:

  1. 创建Key B,Key A保持有效。
  2. Secret/配置加入B。
  3. 金丝雀Pod使用B。
  4. 全量滚动并观察B调用成功率。
  5. 确认A无使用后撤销A。

若只支持单Key,必须与提供方协商切换窗口、双读/代理或短暂停机方案。不能假设Kubernetes Secret更新能让所有Pod同一时刻切换。

三十九、TLS证书轮换

证书和私钥写入TLS Secret后,Ingress Controller或应用需要Watch并热加载。验证:

  • Secret ResourceVersion。
  • 新证书SAN、链和有效期。
  • Controller/应用加载日志。
  • 多副本是否全部更新。
  • 真实入口握手返回的新证书。

保留旧证书/私钥多久取决于终止点和回滚策略。私钥轮换后应撤销旧访问和安全清理,不只更新Secret。

四十、完整商业Deployment Demo

前提:order-api-config-v42order-db-credential-v7由受控流程创建。

yaml
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: 0440

DB_PASSWORD_FILE只是应用约定,Spring Boot不会自动理解所有 _FILE变量;应用或启动脚本必须实现按文件读取,并确保不把内容拼进命令行或日志。示例通过 fsGroup 和组只读 0440 让非root应用读取投射文件;实际文件所有权行为需在目标存储/运行时验证,不能为了快速修复权限改成 0777

四十一、发布配置全过程

mermaid
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状态:

text
CreateContainerConfigError

常见原因:

  • ConfigMap/Secret不存在。
  • 对象在不同Namespace。
  • Key不存在。
  • env/envFrom引用错误。
  • Volume items引用不存在Key。
  • ServiceAccount或ImagePullSecret配置错误。

证据:

bash
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 yaml

describe Event通常明确指出哪个对象/Key缺失。不要因为Secret敏感就完全不检查元数据;可以核对名称、Key列表和ResourceVersion,不输出值。

四十三、修改环境变量配置不生效

确认:

  1. ConfigMap/Secret API对象ResourceVersion是否更新。
  2. Pod创建时间是否早于更新。
  3. 注入方式是否env/envFrom。
  4. Deployment是否产生新ReplicaSet。
  5. 新Pod是否引用新对象/Hash。

环境变量不会热更新。执行受控滚动发布,而不是进入容器尝试 export,后者只影响当前Shell及其子进程,不会修改Java主进程环境。

四十四、Volume文件仍是旧值

按顺序:

  • 是否使用subPath
  • ConfigMap/Secret是否可变并已更新。
  • Pod是否引用正确对象。
  • kubelet/Node是否健康。
  • 等待是否超过合理同步窗口。
  • 容器路径是否被另一个Volume覆盖。
  • 应用是否长期持有旧File Descriptor。
  • 应用是否缓存解析结果。

只执行 cat 新文件能证明路径读取到新内容,不能证明应用内存中的配置对象已经更新。

四十五、配置文件权限错误

表现:

text
Permission denied

检查:

bash
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

bash
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写进低保护日志。

五十、常见误区

  1. Secret Base64就是加密:任何读取者都能解码。
  2. 开启etcd加密就绝对安全:API读取者、Pod和Node仍可获得明文。
  3. 修改ConfigMap后环境变量会变:进程环境是启动快照。
  4. Volume文件更新等于应用生效:应用可能缓存或持有旧FD。
  5. subPath会跟随更新:通常绑定旧inode。
  6. 两个Volume会一起原子更新:跨Volume没有统一事务。
  7. rollout restart记录了配置版本:它只触发新Pod,版本仍需显式记录。
  8. 不能get Secret就无法窃取:创建Pod/修改Workload/exec也可能间接读取。
  9. 把Secret放环境变量绝对安全:日志、诊断和进程环境可能泄露。
  10. 外部Secret不会进入集群:Operator同步模式仍创建原生Secret。
  11. 轮换就是覆盖旧密码:无重叠窗口会中断旧Pod。
  12. 删除旧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和回滚窗口演练,记录每一步指标。

验收清单

  1. 能画出API到etcd、kubelet、容器的配置链。
  2. 能区分data、binaryData、stringData和Secret Type。
  3. 能解释env/envFrom的覆盖和生命周期。
  4. 能解释Atomic Writer、symlink和inode。
  5. 能证明subPath不更新。
  6. 能设计应用配置解析、验证、原子切换和失败回退。
  7. 能区分Base64、TLS、静态加密和KMS保护范围。
  8. 能审计直接和间接Secret读取权限。
  9. 能比较三类外部Secret集成。
  10. 能设计数据库/API Key/TLS轮换。
  11. 能用版本名或Checksum触发可审计Rollout。
  12. 能完成CreateContainerConfigError、旧文件和轮换故障Runbook。

关联知识点