Chapter 06

配置管理:ConfigMap 与 Secret

将配置与代码分离,掌握 ConfigMap 的多种挂载方式、Secret 类型与安全存储,以及外部 Secret 管理方案。

为什么要将配置与代码分离

"同一个镜像,跑在 dev/staging/prod 不同环境"是容器化的核心优势。但不同环境的数据库地址、日志级别、第三方 API Key 各不相同。这些配置不能硬编码在镜像里——否则就丧失了可移植性。

K8s 通过两种资源分离配置:

ConfigMap

存储非敏感配置,如应用参数、配置文件、日志级别。数据以明文存储在 etcd 中。

Secret

存储敏感信息,如密码、Token、TLS 证书。数据以 Base64 编码存储(注意:不是加密!)。

ConfigMap

创建 ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: default
data:                              # 键值对数据
  LOG_LEVEL: info                  # 简单键值
  APP_PORT: "8080"
  DATABASE_HOST: mysql-service
  nginx.conf: |                    # 多行文件内容(| 保留换行)
    server {
        listen 80;
        server_name example.com;
        location / {
            proxy_pass http://backend;
        }
    }
  app.properties: |
    spring.datasource.url=jdbc:mysql://mysql-service:3306/mydb
    spring.datasource.max-pool-size=10
# 从文件创建 ConfigMap
kubectl create configmap nginx-conf --from-file=nginx.conf

# 从目录创建(目录下所有文件)
kubectl create configmap app-config --from-file=config/

# 从字面值创建
kubectl create configmap env-config \
  --from-literal=LOG_LEVEL=info \
  --from-literal=APP_PORT=8080

挂载方式一:作为环境变量

spec:
  containers:
  - name: app
    image: my-app:v1
    env:                            # 注入单个键
    - name: LOG_LEVEL
      valueFrom:
        configMapKeyRef:
          name: app-config        # ConfigMap 名称
          key: LOG_LEVEL          # ConfigMap 中的键
    envFrom:                       # 注入全部键(作为环境变量)
    - configMapRef:
        name: app-config

挂载方式二:作为 Volume 文件

spec:
  containers:
  - name: nginx
    image: nginx:1.25
    volumeMounts:
    - name: config-vol
      mountPath: /etc/nginx/conf.d  # ConfigMap 所有 key 成为该目录下的文件
    - name: config-vol
      mountPath: /etc/nginx/nginx.conf
      subPath: nginx.conf           # subPath:只挂载指定键为单个文件
  volumes:
  - name: config-vol
    configMap:
      name: app-config
      defaultMode: 0644             # 文件权限(八进制)
      items:                        # 只挂载指定 key
      - key: nginx.conf
        path: default.conf          # 重命名文件
配置热更新

以 Volume 方式挂载的 ConfigMap,K8s 会在 ConfigMap 更新后(约 1 分钟内)自动更新挂载的文件,无需重启 Pod。但环境变量方式注入的配置不会热更新,需要重启 Pod 才生效。应用还需要自行监听文件变更并重新加载配置(如 nginx 的 -s reload)。

Secret

Secret 类型

Opaque
默认类型,存储任意 base64 编码的键值对。常用于存储数据库密码、API Token 等。
kubernetes.io/tls
存储 TLS 证书和私钥。必须包含 tls.crt(证书)和 tls.key(私钥)字段。Ingress 的 TLS 配置使用此类型。
kubernetes.io/dockerconfigjson
存储 Docker 镜像仓库认证信息(imagePullSecret)。用于拉取私有镜像仓库的镜像。
kubernetes.io/service-account-token
ServiceAccount Token,由 K8s 自动创建,注入到 Pod 中用于访问 API Server。

创建 Opaque Secret

apiVersion: v1
kind: Secret
metadata:
  name: db-secret
type: Opaque
data:                              # base64 编码的值
  username: YWRtaW4=              # echo -n 'admin' | base64
  password: cGFzc3dvcmQxMjM=     # echo -n 'password123' | base64
# 也可以用 stringData(明文,K8s 自动编码,不会写回 YAML)
stringData:
  dsn: "mysql://admin:password123@mysql:3306/mydb"
# 命令行创建(推荐,避免 base64 手动编码出错)
kubectl create secret generic db-secret \
  --from-literal=username=admin \
  --from-literal=password=password123

# 创建 TLS Secret
kubectl create secret tls api-tls \
  --cert=tls.crt \
  --key=tls.key

# 创建 Docker 镜像拉取 Secret
kubectl create secret docker-registry registry-secret \
  --docker-server=registry.example.com \
  --docker-username=user \
  --docker-password=passwd

在 Pod 中使用 Secret

spec:
  imagePullSecrets:               # 拉取私有镜像
  - name: registry-secret
  containers:
  - name: app
    image: my-app:v1
    env:
    - name: DB_USERNAME
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: username
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: db-secret
          key: password
    volumeMounts:
    - name: tls-certs
      mountPath: /etc/ssl/certs
      readOnly: true
  volumes:
  - name: tls-certs
    secret:
      secretName: api-tls
      defaultMode: 0400            # 私钥文件权限要严格
Secret 不等于加密!

K8s Secret 的 data 字段只是 Base64 编码,不是加密!任何能读取 Secret 的人都可以解码。必须做到:
1. 开启 etcd 静态加密(EncryptionConfiguration
2. 严格控制 RBAC,最小化 Secret 读取权限
3. 生产环境推荐使用 Vault 或 External Secrets Operator 管理敏感配置
4. 永远不要把 Secret YAML 提交到 Git 仓库!

外部 Secret 管理

External Secrets Operator

External Secrets Operator(ESO)将外部 Secret 管理系统(Vault、AWS Secrets Manager、GCP Secret Manager 等)的 Secret 同步为 K8s Secret。

# ExternalSecret:定义从哪个外部系统同步什么数据
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-external-secret
spec:
  refreshInterval: 1h             # 每小时同步一次
  secretStoreRef:                 # 引用 SecretStore(外部系统连接配置)
    name: vault-backend
    kind: SecretStore
  target:
    name: db-secret               # 生成的 K8s Secret 名称
  data:
  - secretKey: password          # K8s Secret 中的键名
    remoteRef:
      key: production/db          # Vault 中的路径
      property: password         # Vault 中的字段
Sealed Secrets

如果你无法使用 Vault 等外部系统,可以考虑 Sealed Secrets(Bitnami)。它允许将 Secret 加密后安全地提交到 Git(只有集群内的控制器能解密),实现 GitOps 安全管理 Secret。

ConfigMap 热更新与版本化策略

ConfigMap 更新后,挂载为文件的配置会在 约 2 分钟内自动同步到 Pod(通过 kubelet 的 sync 机制)。但注入为环境变量的配置不会热更新,需要重启 Pod 才能生效。

热更新延迟
kubelet 默认每 60 秒同步一次 ConfigMap 内容到 Volume,加上 API Server 缓存,实际生效时间约为 60~120 秒。可通过 --sync-frequency 调整,但不建议设太短以免 API Server 压力过大。
ConfigMap 版本化(推荐实践)
在 ConfigMap 名称中加入哈希值(如 app-config-a3f2c1d),每次配置变更生成新 ConfigMap。Deployment 引用新名称触发滚动更新,实现配置变更可审计、可回滚。Helm 的 sha256sum 模板函数可自动计算。
immutable ConfigMap(K8s 1.21+)
设置 immutable: true 后,ConfigMap 内容不可修改(只能删除重建)。好处:防止意外修改导致的配置漂移;kubelet 不再 watch 该 ConfigMap,减少 API Server 压力。
# 不可变 ConfigMap(生产推荐)
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config-v20241201          # 名称包含版本/日期
immutable: true                        # 设为不可变
data:
  LOG_LEVEL: info
大型配置不适合放 ConfigMap

etcd 单个对象大小上限为 1.5 MiB(含 etcd 元数据,实际 data 约 1 MiB)。超大配置文件(如 ML 模型权重、大型 SQL 文件)应存储在外部存储(S3、NFS),然后通过 InitContainer 下载到 emptyDir,再挂载到主容器使用。

ServiceAccount Token 与 Secret 的演变

在 K8s 1.24 之前,创建 ServiceAccount 时会自动创建一个永不过期的 Secret Token。K8s 1.24+ 改为按需生成有时效的 Token(通过 TokenRequest API),安全性大幅提升。

# K8s 1.24+ 获取 ServiceAccount Token(有时效,默认 1 小时)
kubectl create token my-serviceaccount \
  --duration 1h

# 查看 ServiceAccount 是否已挂载默认 Token
kubectl get pod my-pod -o yaml | grep serviceAccountToken

# 在 Pod 内部直接读取已挂载的 Token
cat /var/run/secrets/kubernetes.io/serviceaccount/token
本章小结

配置与镜像分离是容器化的基本原则。ConfigMap 管理非敏感配置,支持环境变量注入和 Volume 挂载两种方式;挂载为文件的配置支持热更新(约 2 分钟),环境变量注入需重启 Pod。Secret 仅做 Base64 编码,不等于加密——生产环境必须结合 etcd 加密和严格 RBAC,推荐使用 External Secrets Operator 与 Vault 集成。immutable: true 是生产 ConfigMap 和 Secret 的推荐实践,防止意外变更并减少 API Server 压力。