前言
“改个数据库地址要重新打镜像?"——配置与代码分离是十二要素应用的核心原则。K8s 用 ConfigMap(普通配置)+ Secret(敏感配置) 承载这一切。
一、创建
# ---- ConfigMap 三种创建姿势 ----
# 1. 字面量
kubectl create configmap app-conf \
--from-literal=APP_ENV=production \
--from-literal=LOG_LEVEL=info
# 2. 从文件
kubectl create configmap nginx-conf --from-file=nginx.conf
# 3. 从目录(每个文件一个 key)
kubectl create configmap site --from-file=./conf/
声明式 YAML(推荐):
apiVersion: v1
kind: ConfigMap
metadata:
name: app-conf
data:
# 简单键值
APP_ENV: "production"
LOG_LEVEL: "info"
# 整文件内容(多行用 |)
redis.conf: |
bind 0.0.0.0
maxmemory 512mb
Secret 同理(base64 编码,注意是编码不是加密):
kubectl create secret generic db-cred \
--from-literal=password='S3cret!' \
--from-file=tls.crt=server.crt
# stringData 写明文(创建时自动编码, 更友好)
apiVersion: v1
kind: Secret
metadata:
name: db-cred
stringData:
username: app
password: "S3cret!"
二、注入方式一:环境变量
spec:
containers:
- name: app
image: myapp:v1.0
env:
- name: APP_ENV # 单个取值
valueFrom:
configMapKeyRef: {name: app-conf, key: APP_ENV}
- name: DB_PASSWORD # 从 Secret 取
valueFrom:
secretKeyRef: {name: db-cred, key: password}
envFrom: # 一把全导(key 变量名)
- configMapRef: {name: app-conf}
- secretRef: {name: db-cred}
特点:简单直接,但 Pod 启动后不更新(需要重启 Pod)。
三、注入方式二:挂载文件(推荐)
spec:
containers:
- name: app
image: myapp:v1.0
volumeMounts:
- name: conf
mountPath: /etc/app # 目录下每个 key 一个文件
volumes:
- name: conf
configMap:
name: app-conf
挂出来长这样:
kubectl exec -it <pod> -- ls /etc/app
# APP_ENV LOG_LEVEL redis.conf ← 每个key都是文件
kubectl exec -it <pod> -- cat /etc/app/APP_ENV
# production
只想挂单个文件(别盖掉目录里其他文件):
volumeMounts:
- name: conf
mountPath: /etc/nginx/nginx.conf # 精确到文件名
subPath: nginx.conf # 对应 configMap 里的 key
⚠️ SubPath 的坑:用了 subPath 的挂载不会热更新(详见下节)。需要热更新的文件别用 subPath。
四、热更新机制
ConfigMap 变更后,volume 挂载方式会自动同步(约 1 分钟内,实际是 kubelet 的同步周期):
# 修改
kubectl edit configmap app-conf # 把 LOG_LEVEL 改成 debug
# 等 ~60s, 容器里文件已变(但应用是否重读是应用自己的事!)
kubectl exec -it <pod> -- cat /etc/app/LOG_LEVEL
三种应用感知策略:
| 策略 | 做法 |
|---|---|
| 应用自己 watch 文件 | 优雅但要求开发配合(如 nginx -s reload) |
| sidecar 检测变化发信号 | Reloader / 注入一个 watcher 容器 |
| 滚动重启 | kubectl rollout restart deploy/app(简单粗暴, 大多数场景够用) |
环境变量方式永远不热更新;Secret 挂载同理自动同步。
五、不可变 ConfigMap/Secret(性能优化)
高频创建的场景可以锁死:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-conf
immutable: true
data: {...}
好处:kubelet 不再 watch,大幅降低 API Server 压力(集群级优化);代价:改配置必须重建对象。
六、实战:一个完整的"配置分离"应用
apiVersion: v1
kind: ConfigMap
metadata:
name: web-conf
data:
default.conf: |
server {
listen 80;
server_name _;
location / { root /usr/share/nginx/html; }
location /healthz { return 200 "ok"; }
}
---
apiVersion: v1
kind: Secret
metadata:
name: web-basic-auth
stringData:
auth: "admin:$apr1$xyz..."
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 2
selector: {matchLabels: {app: web}}
template:
metadata: {labels: {app: web}}
spec:
containers:
- name: nginx
image: nginx:1.25
volumeMounts:
- name: conf
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
volumes:
- name: conf
configMap: {name: web-conf}
kubectl apply -f web.yaml
kubectl port-forward deploy/web 8080:80
curl http://localhost:8080/healthz
七、安全要点(Secret 篇)
- base64 ≠ 加密:拿到 YAML 就能解码。真正防护靠:
- etcd 加密配置(
--encryption-provider-config) - RBAC 限制
get secrets权限
- etcd 加密配置(
- 生产进阶:外部密钥管理——External Secrets Operator(对接 Vault/云 KMS)或 Sealed Secrets(加密后可进 Git)
- 不用的 Secret 及时删:
kubectl delete secret old-cred - 审计谁读了 Secret:开 API audit log
八、常见坑速查
| 现象 | 原因 |
|---|---|
| Pod 起不来:CreateContainerConfigError | 引用的 key 不存在(describe 看 Events) |
| 挂载文件是目录 | mountPath 写到目录但想挂单文件——加 subPath |
| 改了配置没生效 | env 方式/SubPath 挂载不热更新 |
| 挂载后原目录文件消失 | 目录被 volume 整个盖掉,逐文件用 subPath |
| Secret 中文乱码 | base64 编码前先确认 UTF-8 |
小结
| 需求 | 方案 |
|---|---|
| 少量简单配置 | envFrom 环境变量 |
| 配置文件 | volume 挂载(免 subPath 可热更) |
| 敏感信息 | Secret + RBAC(进阶: 外部密钥系统) |
| 高频变更 | immutable 锁定 + 重建 |
| 应用不重读配置 | rollout restart 或 Reloader |
本文是「云原生」系列第 10 篇。