前言

“改个数据库地址要重新打镜像?"——配置与代码分离是十二要素应用的核心原则。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 篇)

  1. base64 ≠ 加密:拿到 YAML 就能解码。真正防护靠:
    • etcd 加密配置(--encryption-provider-config)
    • RBAC 限制 get secrets 权限
  2. 生产进阶:外部密钥管理——External Secrets Operator(对接 Vault/云 KMS)或 Sealed Secrets(加密后可进 Git)
  3. 不用的 Secret 及时删:kubectl delete secret old-cred
  4. 审计谁读了 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 篇。