第 10 篇:Kubernetes ConfigMap 与 Secret 配置治理
0. 本篇定位
这是容器化后端工程实践的第 10 篇。前一篇我们讨论了 Service 与 Ingress 如何把流量送到 Pod,本篇把视角转到后端服务启动前必须准备好的另一类输入:配置。
Kubernetes 里的配置治理不是把 .env 文件换成 YAML,也不是把所有变量塞进 Secret。真正要解决的是四件事:普通配置和敏感配置分离,镜像和环境差异解耦,配置变更语义可预测,敏感信息的访问、审计和轮换有边界。只有这些问题被说清楚,同一个后端镜像才可能在 dev、test、staging、prod 中复用,而不是每个环境重新构建一份。
读完本篇,你应该能回答:哪些配置应该进 ConfigMap,哪些必须进 Secret;什么时候用 env、envFrom,什么时候用 volume 挂载;为什么改了 ConfigMap 后运行中的应用未必立刻变化;Secret 为什么不是完整密钥平台;以及排障时应该按什么证据链定位问题。
1. 问题背景
后端应用在本地开发时,配置通常来自 .env、application.yml、启动参数或 IDE 环境变量。进入 Kubernetes 后,这些配置会穿过更多边界:Git 仓库、CI/CD、镜像、Deployment、Pod、kubelet、应用进程、日志系统和权限系统。任何一个边界混乱,都会让配置问题变成发布问题、安全问题或排障问题。
典型事故往往不是 Kubernetes 不会用,而是配置治理边界没建立好。例如,同一个服务因为数据库地址不同被构建成三个镜像;数据库密码被提交到 Git 或打进镜像,泄漏后无法确认影响范围;修改 ConfigMap 后 Pod 内环境变量不变,团队误以为集群没有同步;把所有配置都放入 Secret,导致普通配置不可读、不可评审,也掩盖了真正敏感字段的轮换需求。
本篇不追求堆砌命令,而是把 ConfigMap、Secret、注入方式、更新语义和生产治理串成一条后端工程链路。配置不是一次性写对就结束的文本,它应该能被评审、被发布、被观测、被回滚,并且在出错时能留下足够证据。
2. 核心概念
2.1 普通配置与敏感配置先分层
配置治理的第一步不是选对象,而是分类。普通配置是可以被团队成员正常评审、记录和讨论的运行参数,例如功能开关、日志级别、上游服务地址、超时时间、线程池大小、非敏感的业务枚举。它们适合放入 ConfigMap,因为重点是可读、可审查、可被模板化复用。
敏感配置是泄漏后可能造成越权、数据泄露或资产损失的信息,例如数据库密码、API token、私钥、证书私钥、OAuth client secret、第三方服务访问凭据。它们应进入 Secret 或外部密钥系统,并配套 RBAC、审计、轮换和最小权限。敏感性的判断标准不是字段名是否叫 password,而是泄漏后的后果。
不要用 Secret 掩盖所有配置,也不要用 ConfigMap 承载任何凭据。前者会让配置评审变困难,后者会让泄漏面扩大。一个健康的后端服务通常同时引用 ConfigMap 和 Secret:ConfigMap 描述普通运行行为,Secret 提供少量受控凭据。
2.2 ConfigMap 的职责边界
ConfigMap 的职责是保存非敏感配置,并以环境变量或文件形式注入到 Pod。它解决的是镜像与环境差异解耦:镜像描述“应用是什么”,ConfigMap 描述“这个环境下如何运行”。只要这个边界清晰,同一份镜像就可以在不同环境中通过不同配置运行。
ConfigMap 不负责保护秘密,不负责触发应用重新加载,也不天然提供配置版本管理。它只是 Kubernetes API 中的一类对象,是否可追溯取决于你是否把声明文件、变更记录、发布单和审计流程接起来。生产中常见的误区是只修改集群里的 ConfigMap,却没有把变更回写到 Git,最后环境状态无法复现。
后端工程里,适合放入 ConfigMap 的字段包括 LOG_LEVEL、FEATURE_FLAG_xxx、HTTP_TIMEOUT_MS、UPSTREAM_BASE_URL、SPRING_PROFILES_ACTIVE 等。它们可以影响行为,但不应拥有访问权限本身。
2.3 Secret 的职责边界
Secret 的职责是保存敏感配置,并让 Pod 以受控方式读取。它的工程价值是避免密码、token、私钥直接进入镜像、普通配置文件或应用仓库。使用 Secret 后,敏感字段至少有了独立对象、独立权限和独立轮换入口。
但 Secret 不是完整密钥平台。Kubernetes Secret 的 data 字段是 base64 编码,不等同于加密;是否在 etcd 中加密存储取决于集群是否启用了 encryption at rest;谁能读取 Secret 取决于 RBAC;谁读过 Secret 取决于审计日志;如何自动轮换取决于额外流程或外部系统。把 Secret 当成“保险箱”是危险的。
生产系统通常会把 Kubernetes Secret 作为最后一公里注入机制,而不是唯一密钥来源。更成熟的做法是让密钥来自云厂商 KMS、Vault、External Secrets Operator、Sealed Secrets 或 SOPS 等体系,再同步或注入到集群。无论采用哪种方案,都要明确所有权、审批、轮换周期和泄漏处置流程。
3. 运行机制
3.1 env:精确映射单个键
env 适合把少量关键字段显式映射成环境变量。优点是可读性强,Deployment 中能直接看到应用需要哪些配置键;缺点是字段多时 YAML 会变长,新增字段需要逐个声明。
env: - name: DB_HOST valueFrom: configMapKeyRef: name: order-service-config key: db_host - name: DB_PASSWORD valueFrom: secretKeyRef: name: order-service-secret key: db_password这种写法适合数据库地址、端口、用户名、密码这类应用启动时必须明确存在的字段。排障时也更直接:检查 Pod spec 中是否引用了正确对象和 key,再检查容器环境变量是否符合预期。
3.2 envFrom:批量导入一组键
envFrom 可以把 ConfigMap 或 Secret 中的所有 key 批量导入为环境变量。它适合配置数量较多、命名约定稳定、字段冲突风险较低的场景。
envFrom: - configMapRef: name: order-service-config - secretRef: name: order-service-secret批量导入的风险在于边界不够显式。ConfigMap 和 Secret 中的 key 会直接成为环境变量,命名冲突、大小写不一致、无效变量名、误放字段都可能在运行时才暴露。因此生产服务即使用 envFrom,也应该配套配置契约文档和 CI 校验,至少保证 key 命名、必填项和敏感字段分类不会漂移。
3.3 volume:把配置当文件挂载
volume 挂载适合复杂配置文件、证书文件、Nginx 配置、日志采集配置、JVM truststore 路径等场景。应用读取的是文件,而不是环境变量。
volumes: - name: app-config configMap: name: order-service-config - name: tls-secret secret: secretName: order-service-tlscontainers: - name: app volumeMounts: - name: app-config mountPath: /etc/order-service readOnly: true - name: tls-secret mountPath: /etc/tls readOnly: true文件挂载的优势是结构化能力强,也便于承载多行内容。它的关键风险是“文件变化”和“应用重新读取”是两件事:Kubernetes 可能更新挂载内容,但应用是否自动重新加载取决于应用自身逻辑。
4. 最小可运行示例
4.1 声明 ConfigMap 与 Secret
下面示例把普通配置和敏感配置拆开。ConfigMap 保存可评审的运行参数,Secret 保存数据库密码。
apiVersion: v1kind: ConfigMapmetadata: name: order-service-configdata: SPRING_PROFILES_ACTIVE: "prod" LOG_LEVEL: "INFO" DB_HOST: "mysql.default.svc.cluster.local" DB_PORT: "3306" HTTP_TIMEOUT_MS: "3000"---apiVersion: v1kind: Secretmetadata: name: order-service-secrettype: OpaquestringData: DB_USERNAME: "order_user" DB_PASSWORD: "change-me-in-real-platform"示例里使用 stringData 是为了便于编写声明文件,API Server 会把它转换到 data。真实生产不要把明文 Secret 直接提交到普通 Git 仓库,应使用加密提交、外部密钥系统或受控同步机制。
4.2 在 Deployment 中注入配置
Deployment 可以同时引用 ConfigMap 和 Secret。这里用 envFrom 演示批量导入,用 annotation 表达配置版本变化时需要触发 Pod 模板变化。
apiVersion: apps/v1kind: Deploymentmetadata: name: order-servicespec: replicas: 2 selector: matchLabels: app: order-service template: metadata: labels: app: order-service annotations: checksum/config: "replace-with-ci-generated-hash" spec: containers: - name: app image: registry.example.com/order-service:1.0.0 envFrom: - configMapRef: name: order-service-config - secretRef: name: order-service-secret ports: - containerPort: 8080checksum/config 不是 Kubernetes 自动生成的字段,而是常见工程约定。CI 或 Helm/Kustomize 根据配置内容生成 hash,hash 变化会改变 Pod template,从而触发 Deployment 滚动更新。
4.3 观察注入结果
排查注入是否成功时,不要只看应用日志。应先确认对象存在,再确认 Pod spec 引用正确,最后确认容器内应用读取到预期值。
kubectl get configmap order-service-config -o yamlkubectl get secret order-service-secret -o yamlkubectl describe pod <pod-name>kubectl exec <pod-name> -- printenv | grep DB_HOSTkubectl logs <pod-name>注意,kubectl get secret -o yaml 会显示 base64 后的值,不代表它安全可公开。生产排障时应限制谁能执行这类命令,并避免把 Secret 内容复制到工单、聊天记录或日志平台。
5. 配置逐行拆解
5.1 环境变量是 Pod 创建时快照
通过 env 或 envFrom 注入的环境变量,在 Pod 创建时被写入容器进程环境。之后修改 ConfigMap 或 Secret,不会自动改变已经运行进程的环境变量。
这就是“改了 ConfigMap,应用没变化”的最常见原因。不是 Kubernetes 没保存新配置,而是运行中的进程没有重新创建。修复方式通常是触发滚动重启:
kubectl rollout restart deployment/order-servicekubectl rollout status deployment/order-service更工程化的做法是在发布流程里把配置内容 hash 写入 Pod template annotation,让配置变化自动触发新 ReplicaSet,而不是依赖人工记得重启。
5.2 文件挂载会更新,但应用未必重载
通过 volume 挂载 ConfigMap 或 Secret 时,Kubernetes 可以把更新后的内容反映到挂载目录中,但这个过程不是“应用配置立即生效”的保证。应用是否重新读取文件,取决于它是否监听文件变化、定时加载、收到信号后 reload,或只在启动时读取一次。
另外,如果使用 subPath 挂载单个文件,很多情况下不会接收 ConfigMap/Secret 的后续更新。这类细节在证书、Nginx 配置、JVM truststore 中尤其容易踩坑。
因此文件挂载型配置要把生效语义写清楚:是必须重启 Pod,还是应用支持 reload;reload 由谁触发;reload 失败如何回滚;如何确认新文件已经被应用进程消费,而不仅是存在于容器文件系统。
5.3 生产发布要显式触发生效
生产环境不应该依赖“改完配置等它自己变”。配置变化应进入发布系统:变更评审、配置 diff、模板渲染、dry-run、滚动更新、健康检查、失败回滚和事后审计。
常见触发方式有三种。第一,人工执行 kubectl rollout restart,适合小团队或临时操作,但审计和一致性较弱。第二,CI 根据配置内容生成 checksum annotation,配置变化自动触发滚动更新。第三,使用专门的配置 reload 组件或应用内 reload 机制,适合确实需要热更新的场景。
后端服务默认建议采用“配置变更等同一次发布”的思路。这样更慢一点,但证据链清晰,回滚路径明确,也更符合生产稳定性。
6. 后端工程场景
6.1 Spring Boot / JVM 服务的配置层次
Spring Boot 服务通常同时支持环境变量、启动参数、application.yml、Config Data、Profile 和外部文件。进入 Kubernetes 后,要避免多个来源互相覆盖却没人说得清优先级。
推荐做法是:镜像内只保留通用默认值;环境差异通过 ConfigMap/Secret 注入;关键覆盖项在发布模板中显式声明;启动日志打印非敏感配置摘要,例如 profile、端口、上游地址、超时时间,但绝不打印密码、token、私钥。
JVM 服务还要注意证书和信任库。如果数据库、消息队列或内部网关需要 TLS,证书文件适合用 Secret volume 挂载,但应用是否读取新证书、是否需要重启连接池、是否要重建 SSLContext,都要在服务侧明确。
6.2 CI/CD 中的配置版本治理
配置应该和代码一样有版本。最基础的做法是把非敏感 ConfigMap 声明放在 Git 中,通过 PR 评审;敏感配置使用加密文件或外部密钥系统,只在 CI/CD 或集群内解密/同步。
发布时至少要保留三类记录:这次部署使用了哪个镜像 digest,使用了哪个配置版本,Secret 是否发生轮换。只有这三者同时可追踪,事故中才能判断“是代码变了、配置变了、凭据变了,还是三者组合导致问题”。
不要把 kubectl edit configmap 当成常规发布手段。它适合临时止血,但止血后必须把变更回写到声明源,否则下次部署可能覆盖现场修复,或者让集群状态变成不可复现的手工状态。
6.3 多环境复用与差异控制
多环境不是复制三份 YAML 后各改各的。更稳妥的方式是把共同结构抽成基础模板,把环境差异限制在小范围覆盖中,例如 Kustomize overlay、Helm values 或平台自研模板。
差异应该可解释。开发环境可以使用较低副本数、短 TTL、弱依赖;测试环境强调可复现;预发环境应尽量贴近生产;生产环境必须有最小权限、审计和回滚。ConfigMap 和 Secret 的字段名应尽量一致,差异应体现在值,而不是每个环境 invent 一套命名。
如果一个字段只在某个环境存在,要问清楚它是临时调试开关、灰度能力,还是生产漏配。长期存在的环境差异必须文档化,否则排障时会把“环境特例”误判成“应用缺陷”。
7. 常见错误与排障
7.1 Pod 创建失败:先看对象和事件
如果 Pod 一直处于 CreateContainerConfigError、Pending 或无法启动,先检查它引用的 ConfigMap/Secret 是否存在,以及 key 是否存在。
kubectl describe pod <pod-name>kubectl get configmap order-service-configkubectl get secret order-service-secretdescribe pod 的 Events 通常会直接提示找不到对象或 key。不要一开始就怀疑业务代码,因为容器进程可能根本还没启动。排障顺序应该是对象存在性、引用名称、key 名称、namespace、权限,再到应用日志。
7.2 应用读取旧值:区分注入方式
如果集群里的 ConfigMap 已经更新,但应用仍使用旧值,先判断配置是环境变量注入还是文件挂载。环境变量注入需要重建 Pod;文件挂载需要确认文件是否更新,以及应用是否重新读取。
kubectl get configmap order-service-config -o yamlkubectl describe pod <pod-name>kubectl exec <pod-name> -- printenv | grep LOG_LEVELkubectl exec <pod-name> -- ls -l /etc/order-service如果 Pod 环境变量仍是旧值,执行滚动重启并观察新 Pod。如果文件已更新但应用行为不变,问题往往在应用 reload 逻辑,而不是 Kubernetes 对象。
7.3 排障证据链:从声明到进程
配置问题要按证据链排查,而不是凭经验猜。建议顺序是:声明源文件、CI 渲染结果、集群对象、Pod spec、容器内可见值、应用启动日志、健康检查、业务错误。
每一层都回答一个问题。声明源文件说明“我们期望什么”;CI 渲染结果说明“实际发布了什么”;集群对象说明“API Server 保存了什么”;Pod spec 说明“kubelet 准备注入什么”;容器内可见值说明“进程环境或文件系统看到了什么”;应用日志说明“应用如何解释这些值”。
这条链路能把责任边界拆清楚:是配置没有发布、对象没有更新、Pod 没有重建、文件没有 reload,还是应用读取逻辑有问题。没有证据链,团队很容易在平台、应用、网络之间来回甩锅。
8. 生产化边界
8.1 Secret 不是完整密钥平台
Secret 的安全性取决于一整套周边条件,而不是对象名字。至少要确认:etcd 是否启用静态加密;API Server 到 etcd 的链路是否安全;哪些 ServiceAccount、用户和控制器可以 get/list/watch Secret;审计日志是否记录读取行为;备份中是否包含明文 Secret。
base64 只是编码,不是加密。任何拥有读取权限的人都可以还原 Secret 值。因此,Secret 的核心治理动作不是“把值放进 Secret 就安全了”,而是减少读取者、减少暴露面、缩短有效期、记录访问行为。
如果组织已经有 KMS、Vault 或云厂商 Secret Manager,不要轻易绕过它们。Kubernetes Secret 更适合作为 Pod 启动时的注入接口,而不是企业密钥生命周期的唯一来源。
8.2 RBAC 与审计要按最小权限设计
最小权限的原则是:应用 Pod 只获得运行所需的 Secret,部署系统只获得目标 namespace 的变更权限,普通开发者不应默认拥有生产 Secret 的读取权限。
RBAC 中要特别小心 list 和 watch。一个人如果可以 list 某 namespace 下所有 Secret,影响范围通常比 get 单个 Secret 大得多。控制器、CI 机器人和调试账号也应分开,不要共用高权限 ServiceAccount。
审计日志要能回答四个问题:谁读取过 Secret,什么时候读取,读取了哪个 namespace 下哪个对象,是通过什么身份读取。没有审计,Secret 泄漏后只能靠猜。
8.3 轮换、泄漏与失效窗口
密钥治理必须预设泄漏会发生。Secret 轮换方案至少要包括:如何生成新凭据,如何让应用同时兼容新旧凭据,如何滚动更新 Pod,如何确认旧凭据不再被使用,如何废弃旧凭据。
数据库密码这类凭据常见做法是双账号或双密码过渡:先创建新凭据并注入应用,确认新 Pod 全部健康后,再撤销旧凭据。token 和证书也要提前设计重叠窗口,避免一次性切换导致全量不可用。
泄漏处置不只是“改密码”。还要查 Git 历史、镜像层、CI 日志、聊天记录、工单附件、监控标签和应用日志中是否出现过敏感值。Secret 一旦进入这些系统,轮换才是起点,清理和影响面确认同样重要。
9. 什么时候用,什么时候不用
9.1 选择 ConfigMap、Secret、外部密钥系统
如果字段是非敏感、需要团队评审、会影响运行行为的配置,优先使用 ConfigMap。如果字段泄漏后会产生安全后果,使用 Secret 或外部密钥系统。如果字段需要完整生命周期管理、自动轮换、集中审批和跨集群同步,应优先考虑外部密钥平台,再把结果注入 Kubernetes。
如果配置非常大、变化频繁、需要复杂查询或灰度规则,ConfigMap 可能不是最佳选择。它适合承载启动配置和少量文件,不适合替代配置中心、规则引擎或数据库。
判断标准可以很简单:这个值是否可以被公开评审;泄漏后是否需要紧急处置;变化后是否必须立即生效;是否需要跨服务共享;是否需要审计和轮换。答案会直接决定对象选择。
9.2 常见反模式
第一类反模式是把环境写进镜像。这样会破坏镜像复用,让回滚变得混乱,也让配置变更变成重新构建。镜像应该稳定表达制品,环境差异应由配置层表达。
第二类反模式是把 Secret 当万能配置容器。所有字段都进 Secret 后,普通配置失去可读性,评审变困难,真正敏感字段也不会得到更严格治理。
第三类反模式是只修改现场对象,不回写声明源。短期看修得快,长期看会制造不可复现环境。生产止血可以手工做,但止血后必须回到 GitOps 或发布流程。
9.3 团队成熟度判断
初级阶段的目标是把配置从镜像中拿出来,做到 ConfigMap/Secret 分离。中级阶段的目标是让配置变更进入 CI/CD,有 dry-run、diff、滚动更新和回滚。高级阶段的目标是接入密钥平台、审计、自动轮换和跨环境一致性治理。
不要一步到位追求复杂平台。小团队可以先用 ConfigMap、Secret、RBAC、checksum annotation 和受控发布流程建立基本边界;当密钥数量、集群数量、审计要求上升后,再引入 External Secrets、Vault、KMS 或 GitOps 工具。
最终判断标准不是 YAML 写得多漂亮,而是事故中能否快速回答:当前服务实际使用了哪些配置,来自哪个版本,谁改过,什么时候生效,如何回滚,敏感字段是否泄漏,旧密钥是否已经失效。
10. 下一篇衔接
本篇解决的是配置如何进入 Pod,以及配置变更和敏感信息如何被治理。下一篇会进入 Kubernetes Volume、PV、PVC 与 StatefulSet,讨论后端服务在容器化后如何处理持久化状态。
这两篇之间的连接点很自然:ConfigMap/Secret 的 volume 挂载解决的是“把配置文件放进容器”,而 PV/PVC 解决的是“把业务数据和容器生命周期解耦”。前者偏配置输入,后者偏状态存储。理解这个区别,才能避免把配置文件、证书、缓存、数据库数据都混成同一种挂载问题。