Skip to content

配置与密钥

Kubernetes 里普通配置放 ConfigMap,敏感数据放 Secret。它们通过环境变量、文件挂载或镜像拉取凭据的方式进入容器。配置变更后应用是否真的加载了新值,不只看 kubectl apply 有没有成功——环境变量只在启动时注入一次,文件挂载和 subPath 的热更新行为也不一样。

ConfigMap

ConfigMap 保存明文配置,可以是键值对,也可以是完整的配置文件:

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: demo
data:
  LOG_LEVEL: info
  APP_MODE: prod
  app.yaml: |            # 多行配置可以直接放在 ConfigMap 里
    server:
      port: 8080

把整段 ConfigMap 注入环境变量:

yaml
envFrom:
  - configMapRef:
      name: app-config   # ConfigMap 里每个 key 变成一个环境变量,key 名即变量名

envFrom 注入的环境变量只在容器进程启动时读取一次。后续 kubectl edit cm 修改 ConfigMap,不会改变已经在运行的容器的环境变量。要让新配置生效,需要重建 Pod(滚动重启 Deployment、StatefulSet 等控制器触发新建)。

只取单个 key:

yaml
env:
  - name: LOG_LEVEL
    valueFrom:
      configMapKeyRef:
        name: app-config
        key: LOG_LEVEL    # 只取 ConfigMap 里这个 key 的值

文件挂载

ConfigMap 作为文件挂载到容器目录,kubelet 会周期性(默认约 1 分钟)把 ConfigMap 的变更同步到挂载目录的文件里:

yaml
volumes:
  - name: config
    configMap:
      name: app-config
containers:
  - name: app
    image: harbor.example.com/demo/app:v1.0.0
    volumeMounts:
      - name: config
        mountPath: /etc/app   # ConfigMap 里每个 key 成为 /etc/app/ 下的一个文件

ConfigMap 更新后,kubelet 会修改挂载目录里的文件,但应用进程什么时候读到新文件是另一回事。Nginx 需要 nginx -s reload,很多 Java 应用需要重启,Go 应用如果做文件 watch 可以热加载。ConfigMap 文件变化和进程加载之间没有自动关联。

subPath 不会热更新

subPath 只挂载 ConfigMap 里的某一个文件:

yaml
volumeMounts:
  - name: config
    mountPath: /etc/app/app.yaml
    subPath: app.yaml     # 只挂这一个文件,不挂整个目录

subPath 挂载的文件是挂载时的一个快照,ConfigMap 更新后这个文件不会跟着变。这是 Linux bind mount 的固有行为,不是 K8s 的 bug。要用 subPath 的场景里,配置变更后通常靠滚动重启让新 Pod 拿到新值:

bash
kubectl -n demo rollout restart deploy/app

Secret

Secret 和 ConfigMap 结构类似,但设计上用于存储敏感数据。它默认以 base64 编码存储(不是加密),能读到 Secret 对象的账号或能访问 etcd 的人都能拿到里面的明文内容。

用命令行创建:

bash
kubectl -n demo create secret generic db-auth \
  --from-literal=username=app \
  --from-literal=password='change-me'

环境变量引用:

yaml
env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: db-auth
        key: password

文件挂载并限制权限:

yaml
volumes:
  - name: db-auth
    secret:
      secretName: db-auth
      defaultMode: 0400    # 只有文件 owner 可读,其他用户不可读写

Secret 真正的风险不只在"有没有加密"。即使 etcd 加密了、RBAC 收紧了,应用日志里把 Secret 打印出来、CI 输出里带了密码、YAML 被原样提交到 Git 仓库——这些才是更容易泄露的路径。生产环境更完整的方案通常结合 External Secrets Operator(从外部 Secret Manager 同步)、Sealed Secrets(加密后可安全存入 Git)或云厂商的 Secret Manager。

镜像拉取 Secret

私有镜像仓库需要配置拉取凭据:

bash
kubectl -n demo create secret docker-registry harbor-auth \
  --docker-server=harbor.example.com \
  --docker-username=robot-demo \
  --docker-password='<password>' \
  --docker-email=ops@example.com

Pod 引用:

yaml
spec:
  imagePullSecrets:
    - name: harbor-auth
  containers:
    - name: app
      image: harbor.example.com/demo/app:v1.0.0

imagePullSecrets 必须和 Pod 在同一个 namespace。两种常见的 ImagePullBackOff:一种是 Secret 建错了 namespace,Pod 找不到;另一种是 --docker-server 写成了带路径的完整 URL(正确的写法是只写 registry 地址 harbor.example.com,不包含路径)。

给 ServiceAccount 打上 imagePullSecrets 可以让同一 namespace 下用这个 SA 的 Pod 自动带上,不用每个 Pod 单独写。

配置变更排查

bash
kubectl -n demo get cm app-config -o yaml
kubectl -n demo describe pod <pod-name>
# 进入容器确认实际挂载的文件内容和权限
kubectl -n demo exec <pod-name> -- ls -l /etc/app
kubectl -n demo exec <pod-name> -- cat /etc/app/app.yaml
现象原因处理方向
ConfigMap 已改,环境变量没变环境变量只在容器启动时注入重启 Pod
文件已更新,应用没生效应用没有 re-read 配置文件的机制reload 或重启
subPath 文件不变subPath 不支持热更新滚动重启
Secret 报找不到namespace 不匹配、Secret 名写错、key 名写错核对三个名称
私有镜像拉取失败imagePullSecrets 跨 namespace、docker-server 值写错、仓库证书看 Pod Events、节点上 crictl pull
启动后读到的配置是旧的容器内缓存了文件内容、用了环境变量启动参数确认应用读取配置的方式

配置发布后从三个角度确认:K8s 对象是否更新、容器内文件是否变化、应用进程是否加载了新配置。