跳转至

配置与密钥

把配置从镜像里拆出来,是十二要素应用的第一条。但「拆出来」不等于「管好了」:环境变量不会自动更新,Secret 默认只是 base64,挂载卷还有 subPath 的坑。

难度 · 基础 预计 40 分钟 前置:Pod 与容器

你将学到

  • ConfigMap 与 Secret 的创建方式与消费方式
  • 环境变量、envFrom、卷挂载、subPath 的行为差异
  • 配置热更新的真实边界,以及应用如何感知变更
  • Downward API 与 Projected Volume 的组合用法
  • Secret 的安全边界:base64 不是加密

ConfigMap 与 Secret 的区别

维度 ConfigMap Secret
用途 非敏感配置 密码、Token、证书、密钥
存储 etcd 明文 etcd 中 base64(可配置静态加密)
大小限制 1 MiB 1 MiB
挂载 文件 / 环境变量 文件 / 环境变量
RBAC 可读 应严格限制 get/list/watch
版本化 手工 可用 SealedSecret / External Secrets

base64 不是加密

data 字段只是 base64 编码,任何有权限读 Secret 的人都能解码。生产环境应开启 etcd 静态加密、限制 RBAC,并考虑 External Secrets Operator / Sealed Secrets。

创建 ConfigMap

从字面量

kubectl create configmap app-config \
  --from-literal=APP_MODE=prod \
  --from-literal=LOG_LEVEL=info \
  -n demo

从文件

kubectl create configmap nginx-conf \
  --from-file=myserver.conf=./nginx-conf.d/myserver.conf \
  --from-file=myserver-status.cfg=./nginx-conf.d/myserver-status.cfg \
  -n demo

从 YAML

configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: demo
data:
  APP_MODE: "prod"
  LOG_LEVEL: "info"
  application.yaml: |
    server:
      port: 8080
    logging:
      level: info
kubectl apply -f configmap.yaml
kubectl get configmap app-config -n demo -o yaml

消费 ConfigMap

1. 单个环境变量

env:
  - name: APP_MODE
    valueFrom:
      configMapKeyRef:
        name: app-config
        key: APP_MODE
        optional: false

2. 批量注入

envFrom:
  - configMapRef:
      name: app-config
  - secretRef:
      name: mysql-secret
  - prefix: APP_
    configMapRef:
      name: app-config

envFrom 会把所有 key 变成环境变量。key 必须是合法的环境变量名;非法 key 会被跳过,并在事件中记录。

3. 挂载为文件

volumes:
  - name: config
    configMap:
      name: nginx-conf
      items:
        - key: myserver.conf
          path: conf.d/myserver.conf
containers:
  - name: nginx
    image: nginx:1.27
    volumeMounts:
      - name: config
        mountPath: /etc/nginx/conf.d
        readOnly: true

挂载后目录结构:

/etc/nginx/conf.d/
└── myserver.conf

4. subPath 的单文件挂载

volumeMounts:
  - name: config
    mountPath: /etc/nginx/nginx.conf
    subPath: nginx.conf

subPath 不会热更新

使用 subPath 挂载的文件,在 ConfigMap 更新后不会同步到容器。这是最常见的热更新坑。需要热更新时,挂载目录而不是单文件,或者接受重启 Pod。

消费 Secret

创建

kubectl create secret generic mysql-secret \
  --from-literal=MYSQL_ROOT_PASSWORD='S3cure!Pass' \
  --from-literal=MYSQL_PASSWORD='appPass' \
  -n demo

kubectl create secret tls demo-tls \
  --cert=./tls/tls.crt \
  --key=./tls/tls.key \
  -n demo

YAML 形式

secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
  namespace: demo
type: Opaque
data:
  MYSQL_ROOT_PASSWORD: UzNjdXJlIVBhc3M=
  MYSQL_PASSWORD: YXBwUGFzcw==

生成 base64:

echo -n 'S3cure!Pass' | base64

解码验证:

kubectl get secret mysql-secret -n demo -o jsonpath='{.data.MYSQL_ROOT_PASSWORD}' | base64 -d

常见 Secret 类型

类型 用途 必填 key
Opaque 通用 自定义
kubernetes.io/tls TLS 证书 tls.crt、tls.key
kubernetes.io/dockerconfigjson 镜像仓库凭据 .dockerconfigjson
kubernetes.io/basic-auth 用户名密码 username、password
kubernetes.io/ssh-auth SSH 私钥 ssh-privatekey

镜像拉取凭据

kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=robot \
  --docker-password='***' \
  --docker-email=dev@example.com \
  -n demo
spec:
  imagePullSecrets:
    - name: regcred

热更新:到底谁会变

消费方式 更新后是否生效 说明
环境变量(env / envFrom) 否 进程启动时读取,必须重启 Pod
卷挂载(目录) 是(有延迟) kubelet 周期同步,通常几十秒
卷挂载(subPath) 否 单文件绑定,不更新
immutable: true 否 禁止修改,只能删了重建

即使文件更新了,应用也未必会重新加载。常见做法:

  1. 应用支持 SIGHUP / 文件监听:最理想;
  2. 滚动重启:kubectl rollout restart deploy/app;
  3. Checksum 注解:Helm 中把 ConfigMap 的 hash 写进 Pod template,配置变则 Pod 重建;
  4. 配置热加载 Sidecar:如 reloader,监听 ConfigMap 变化并触发重启。
checksum-annotation.yaml
spec:
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}

Downward API

Downward API 把 Pod / 容器的元数据注入容器,不需要额外对象。

downward-api.yaml
apiVersion: v1
kind: Pod
metadata:
  name: downward-demo
  labels:
    app: demo
spec:
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "env; sleep 3600"]
      env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        - name: CPU_LIMIT
          valueFrom:
            resourceFieldRef:
              containerName: app
              resource: limits.cpu
      volumeMounts:
        - name: podinfo
          mountPath: /etc/podinfo
  volumes:
    - name: podinfo
      downwardAPI:
        items:
          - path: labels
            fieldRef:
              fieldPath: metadata.labels
          - path: annotations
            fieldRef:
              fieldPath: metadata.annotations

Projected Volume

Projected Volume 把多个来源(Secret、ConfigMap、Downward API、ServiceAccount Token)投影到同一个目录,适合需要把证书、配置、Token 放在一起的场景。

projected.yaml
apiVersion: v1
kind: Pod
metadata:
  name: projected-demo
spec:
  containers:
    - name: app
      image: nginx:1.27
      volumeMounts:
        - name: all-in-one
          mountPath: /etc/app
          readOnly: true
  volumes:
    - name: all-in-one
      projected:
        sources:
          - configMap:
              name: app-config
          - secret:
              name: demo-tls
          - downwardAPI:
              items:
                - path: pod-name
                  fieldRef:
                    fieldPath: metadata.name
          - serviceAccountToken:
              path: token
              expirationSeconds: 3600
              audience: api

实验:配置热更新验证

1. 创建 ConfigMap 并挂载

kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap web-config \
  --from-literal=index.html='version 1' \
  -n demo
web.yaml
apiVersion: v1
kind: Pod
metadata:
  name: web
  namespace: demo
spec:
  containers:
    - name: nginx
      image: nginx:1.27
      volumeMounts:
        - name: html
          mountPath: /usr/share/nginx/html
  volumes:
    - name: html
      configMap:
        name: web-config
kubectl apply -f web.yaml
kubectl exec -n demo web -- cat /usr/share/nginx/html/index.html

2. 更新 ConfigMap

kubectl create configmap web-config \
  --from-literal=index.html='version 2' \
  -n demo --dry-run=client -o yaml | kubectl apply -f -

等待 30–60 秒,再查看:

kubectl exec -n demo web -- cat /usr/share/nginx/html/index.html

预期变成 version 2。如果用的是 subPath,这里仍然是 version 1。

3. 环境变量对比

给 Pod 加一个 env 引用 web-config,更新 ConfigMap 后检查环境变量,确认不会变化。

4. 不可变 ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: immutable-config
data:
  key: value
immutable: true

修改会被拒绝:

The ConfigMap "immutable-config" is invalid: data: Forbidden: field is immutable

只能删除重建。

排错表

现象 原因 处理
CreateContainerConfigError 引用的 ConfigMap/Secret 不存在或 key 缺失 describe pod 看具体 key
环境变量为空 key 名不匹配 / envFrom 非法 key kubectl get cm -o yaml 对照
挂载目录为空 items 路径写错 检查 path 与 mountPath
更新不生效 用了 subPath / 环境变量 改为目录挂载或重启
Secret 解码乱码 编码时带了换行 用 echo -n 或 printf
TLS Secret 不被 Ingress 识别 缺少 tls.crt / tls.key 检查类型与 key
镜像拉取 401 没配 imagePullSecrets 创建 docker-registry Secret

进阶挑战

  1. 用 immutable: true 保护一个生产配置,设计一次安全的配置变更流程。
  2. 用 Projected Volume 同时挂载 ConfigMap、TLS Secret 和 ServiceAccount Token。
  3. 写一个脚本,比较 ConfigMap 更新前后 Pod 内文件的内容与 mtime。
  4. 给 Deployment 加 checksum 注解,验证配置变更会触发滚动更新。
  5. 用 kubectl auth can-i get secrets 检查当前用户是否有权读取 Secret。

小结

  • ConfigMap 管普通配置,Secret 管敏感数据;Secret 的 base64 不是加密。
  • 环境变量不会热更新,目录挂载会,subPath 不会。
  • Downward API 注入元数据,Projected Volume 把多个来源合并到一个目录。
  • 生产环境要限制 Secret 的 RBAC,并考虑外部密钥管理。

延伸阅读