ConfigMap 与 Secret
配置注入、敏感信息管理。
ConfigMap 与 Secret
这一课你将能做到
- 用清单方式创建 ConfigMap 与 Secret,并以环境变量注入到 Pod/Deployment
- 说清「配置烤进镜像」与「配置外置」的利弊,对照 Compose 的 environment/env_file 建立映射
- 知道 Secret 默认只是 Base64 编码不是加密,说出生产环境的三层加固手段
为什么要学
镜像应该「一次构建、处处运行」:本站 backend 的 SPRING_PROFILES_ACTIVE、DB_HOST、REDIS_HOST、数据库密码,如果写死在 Dockerfile 或 application.yml 里,每个环境就得打一个镜像——构建产物爆炸,泄密风险也随镜像层扩散。
K8s 的解法与 Compose 的 environment/env_file 同构:ConfigMap 放非敏感配置,Secret 放敏感数据,两者都注入到 Pod。Spring Boot 恰好天然读环境变量(SPRING_DATASOURCE_URL 这类宽松绑定),所以「本地 .env / Compose environment / K8s ConfigMap+Secret」可以共用同一套键名约定——这是本课最实际的工程价值。
先建立图像
镜像(只读配方,不含环境差异)
+
ConfigMap(普通配置) + Secret(敏感信息)
↓ 注入
Pod 里的进程读到环境变量 / 配置文件
注入两种方式:
env / envFrom → 环境变量(Spring Boot 最友好)
volumeMount → 文件(适合整份 application.yml、证书)
| ConfigMap | Secret |
|---|
| 用途 | 普通配置、开关、地址 | 口令、令牌、证书材料 |
| etcd 中的形态 | 明文 | Base64 编码(默认可解码,非加密) |
| 大小限制 | 约 1MiB | 约 1MiB |
| 谁能读 | RBAC 控制 | RBAC 控制(应更严) |
人话:Secret 是「别随手 cat 到聊天群里的类型」,不是保险箱本身。
跟着做
步骤 1:创建本站 backend 的 ConfigMap
cm-backend.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: backend-config
data:
SPRING_PROFILES_ACTIVE: "prod"
DB_HOST: "mysql" # 集群内 Service 名或集群外地址
REDIS_HOST: "redis"
kubectl apply -f cm-backend.yaml
kubectl get configmap backend-config -o yaml
命令式快捷方式(冒烟用,正式环境仍推荐清单进 Git):
kubectl create configmap quick --from-literal=KEY1=v1 --from-literal=KEY2=v2
步骤 2:创建 Secret
secret-db.yaml(用 stringData 免手算 Base64):
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
stringData:
DB_PASSWORD: "change-me-demo"
kubectl apply -f secret-db.yaml
kubectl get secret db-secret -o yaml
注意观察:apply 后 stringData 变成了 data 里的 Base64 字符串(如 Y2hhbmdlLW1lLWRlbW8=)。命令式等价:
kubectl create secret generic db-secret --from-literal=DB_PASSWORD=change-me-demo
铁律:含真实口令的 YAML 绝不进 Git;仓库只放模板或用 CI 注入。
步骤 3:注入到 Deployment
在上一课 deploy-backend.yaml 的容器段追加(本例仍用 nginx 占位演示注入):
spec:
containers:
- name: api
image: nginx:1.27-alpine
envFrom: # 整表注入:ConfigMap 所有键
- configMapRef:
name: backend-config
env: # 单键注入:更精确、可改名
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: DB_PASSWORD
kubectl apply -f deploy-backend.yaml
kubectl exec deploy/backend -- printenv SPRING_PROFILES_ACTIVE
kubectl exec deploy/backend -- printenv DB_HOST
(deploy/backend 是「该 Deployment 第一个 Pod」的缩写,比复制 Pod 名方便。)应打印出注入的值。习惯:日志与 printenv 输出不要贴到公开场合,环境变量里的口令会随截图泄露。
步骤 4:文件挂载方式(适合整份配置文件)
把整份 application.yml 挂进容器:
spec:
containers:
- name: api
image: your-registry/backend:1.0.0
volumeMounts:
- name: app-config
mountPath: /app/config # Spring Boot 默认会读 ./config/
readOnly: true
volumes:
- name: app-config
configMap:
name: backend-file-config # 该 CM 的 data 键是文件名
apiVersion: v1
kind: ConfigMap
metadata:
name: backend-file-config
data:
application-prod.yml: |
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://mysql:3306/coderoadmap
步骤 5:体验「改配置 ≠ 生效」
改 cm-backend.yaml 里的 SPRING_PROFILES_ACTIVE 为 prod-v2 并 apply,然后:
kubectl exec deploy/backend -- printenv SPRING_PROFILES_ACTIVE
多数情况仍显示旧值——以环境变量注入的配置不会热更新,需要滚动重启加载新值:
kubectl rollout restart deployment/backend
kubectl rollout status deployment/backend
再 printenv 应看到新值。(volume 挂载的文件会由 kubelet 异步刷新,但应用是否重读取决于程序本身;入门统一按「改配置 = 发版动作」处理最安全。)
步骤 6:亲手验证「Base64 不是加密」
kubectl get secret db-secret -o jsonpath='{.data.DB_PASSWORD}'
[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('Y2hhbmdlLW1lLWRlbW8='))
瞬间还原明文——这就是「编码 ≠ 加密」的直观证据。生产加固三层:① RBAC 收紧 get secrets 权限(第 11 课);② etcd 加密落盘(集群级配置);③ 用 Vault/External Secrets/Sealed Secrets 管理真实密钥。
讲清楚要点
- 镜像不可变交付,环境差异必须外置:同一个 backend 镜像从 dev 跑到 prod,变的只有 ConfigMap/Secret——这是十二要素应用(12-Factor)的配置原则。
- envFrom 图省事,env 图精确:envFrom 整表注入会把 CM 里所有键灌进进程环境,键名脏了会污染环境;Secret 建议逐键 env + secretKeyRef,看得清来源。
- 改配置对象 ≠ 改了正在跑的进程:环境变量在进程启动时定格,变更生效需要滚动重启(
rollout restart),要纳入发布流程。
- Secret 的默认保护是「权限与习惯」:Base64 谁都能解;真正的防线是 RBAC 最小权限、审计日志、以及密钥不进 Git 的纪律。
- Spring Boot 与 K8s 配置天然对齐:环境变量覆盖 application.yml(如
SPRING_DATASOURCE_URL),所以本站键名表(DB_HOST/REDIS_HOST/密码)可以三环境通用。
- 引用必须先于使用存在:Deployment 引用了不存在的 ConfigMap/Secret 时,Pod 会卡在
CreateContainerConfigError——apply 顺序应为:配置 → 密钥 → 工作负载。
你可能会懵的地方
-
Secret 到底加密吗?
默认只是 Base64 编码(防止「手滑看到明文」,不是密码学保护)。加密要靠集群启用 etcd encryption at rest,或直接引入 Vault 类系统。
-
整个 application.yml 放 ConfigMap 合适吗?
放文件挂载可以,注意 1MiB 限制与变更流程;实践中更推荐「敏感/环境相关走环境变量,结构化大配置走挂载文件」的混合模式。
-
和 Docker 的 --env-file / secrets 有什么区别?
问题域相同(配置与镜像分离),K8s 把配置升级为集群级对象:有版本化、有 RBAC、能挂载成文件、能被多个 Deployment 共享引用。
-
kubectl create secret 和 YAML 选哪个?
演示用命令式;生产用「脱敏模板 YAML + CI 注入真实值」,保证流程可审计且密钥不落仓库。
-
配置变了但行为没变,怎么证明?
进容器 printenv 对账(步骤 5 的方法);再不行 kubectl get cm <名> -o yaml 看对象当前值。三个层面(对象值、进程环境、应用行为)逐层对齐。
错法对照
| 错法 | 问题 | 正解 |
|---|
| Dockerfile ENV 写死数据库密码 | 镜像即泄密,且换环境要重建 | Secret 注入,镜像保持中性 |
| 含真口令的 Secret YAML 推进 Git | 历史记录永久泄漏 | 仓库只放模板,值由 CI/密管系统注入 |
| 改完 ConfigMap 等它自动生效 | env 注入不会热更新 | rollout restart 或重新发布 |
| 所有配置一律塞 Secret | 管理噪音、审查困难 | 非敏感进 ConfigMap,敏感才 Secret |
| 日志/printenv 全量输出 | 口令随日志外泄 | 脱敏日志,口令只进密管 |
卡点急救
- Pod 卡在
CreateContainerConfigError:引用的 ConfigMap/Secret 名或 key 不存在。kubectl describe pod 会点名缺谁;先建配置对象再重建工作负载。
- 改了配置「没生效」:先确认对象值已更新(get cm -o yaml),再执行
kubectl rollout restart deployment/<名>;volume 挂载场景还要确认应用自身会重读文件。
- Base64 解码结果带乱码/换行:可能复制丢了字符或混入了换行符;用
-o jsonpath='{.data.KEY}' 精确取值,解码后比对长度。
面试常问
-
ConfigMap 和 Secret 怎么选?Secret 加密吗?
非敏感、可公开的配置进 ConfigMap(地址、开关、profile);口令、令牌、证书私钥进 Secret。Secret 默认只是 Base64 编码,不是加密——任何人拿到读权限即可解码。生产要叠加:RBAC 收敛 get secrets、etcd 加密落盘、优先接入 Vault / External Secrets / Sealed Secrets 等密管方案。
-
环境变量注入和 volume 挂载注入有什么区别?
环境变量:进程启动时定格,ConfigMap 更新后不会热生效,必须滚动重启;适合开关类键值,Spring Boot 直接识别。volume 挂载:kubelet 会把文件更新同步到容器(默认有约一分钟的同步周期),但应用要不要重读取决于程序;适合整份配置文件与证书。工程约定:改配置一律走发布流程,不依赖热更新。
-
如何防止密钥进 Git 泄漏?
仓库只放脱敏模板(值用占位符);真实值通过 CI 变量、密管系统在部署时注入;本地调试用不进版本库的 .env;再配一层 pre-commit 扫描(gitleaks 等)兜底。泄漏后的标准动作是立即轮换密钥,而不是删除提交历史。
自测三问
- 本站 backend 的哪些键进 ConfigMap、哪些进 Secret?边界怎么画?
- 为什么说「Base64 不是加密」?请给出生产环境的三层加固。
DB_HOST=mysql 在 Compose 和 K8s 里分别指向什么?
小结一句话
配置与密钥和镜像分离:ConfigMap/Secret 注入环境变量或文件,变更通过滚动发布生效——下一课处理「磁盘上的数据」如何跨 Pod 重建而存活。
延伸
- ConfigMaps
- Secrets
- 下一课预告:存储卷——emptyDir 的临时性、PV/PVC/StorageClass 的分工,以及 MySQL 数据目录的持久化实验。