适用场景

应用把 ConfigMap 中的单个配置文件挂载到容器现有目录,例如 /etc/app/app.conf。运维修改了 ConfigMap,kubectl get configmap 显示新值,但运行中的应用始终使用旧配置。本文以一个单副本 Deployment 复现,并给出两种可验证的修复路径。以下命令以 Bash 和具有 demo 命名空间操作权限的 kubectl 为例。

现象与可能原因

先区分三层状态:API 中的 ConfigMap、Pod 文件系统中的文件、应用进程实际加载的配置。三者并不总是同步。

  • 使用 subPath 将 ConfigMap 的一个文件挂到指定路径时,现有容器不会收到后续 ConfigMap 更新。等待 kubelet 同步也不会解决。
  • 不使用 subPath、改为挂载整个目录时,投射到文件系统的内容会在 kubelet 后续同步后更新,但存在传播延迟。
  • 使用 ConfigMap 注入环境变量时,现有 Pod 的环境变量不会更新,需要创建新 Pod。
  • 即使文件内容更新,应用若只在启动时读一次配置,进程仍可能保持旧值;这要用应用自身的重载机制或滚动重启解决。

因此,不要一看到旧配置就修改 kubelet 缓存参数,先看挂载方式和进程是否重载。

最小复现

以下清单只用于隔离环境演示。容器每五秒读取一次文件,因此可以把“文件是否更新”和“应用是否重载”分开观察。

apiVersion: v1
kind: Namespace
metadata:
  name: demo
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-settings
  namespace: demo
data:
  app.conf: |
    log_level=info
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: config-demo
  namespace: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: config-demo
  template:
    metadata:
      labels:
        app: config-demo
    spec:
      containers:
        - name: reader
          image: busybox:1.36
          command: ["sh", "-c", "while true; do cat /etc/app/app.conf; sleep 5; done"]
          volumeMounts:
            - name: settings
              mountPath: /etc/app/app.conf
              subPath: app.conf
              readOnly: true
      volumes:
        - name: settings
          configMap:
            name: app-settings

将清单保存为 config-demo.yaml 后执行:

kubectl apply -f config-demo.yaml
kubectl -n demo rollout status deployment/config-demo --timeout=120s
kubectl -n demo exec deployment/config-demo -- cat /etc/app/app.conf
kubectl -n demo patch configmap app-settings --type=merge \
  -p '{"data":{"app.conf":"log_level=debug\n"}}'
kubectl -n demo get configmap app-settings -o jsonpath='{.data.app\.conf}'
kubectl -n demo exec deployment/config-demo -- cat /etc/app/app.conf

两次读取的关键差异是:get configmap 已输出 log_level=debug,现有 Pod 的文件仍是 log_level=infojsonpath 中的 app\.conf 用反斜杠转义键名中的点,避免把它当作字段分隔符。若 Deployment 有多个副本,应逐个 Pod 检查,不要只抽查一个。

生产环境排查

先记录当前资源与挂载定义,再决定修复方式:

kubectl -n demo get configmap app-settings -o yaml
kubectl -n demo get deployment config-demo -o yaml
kubectl -n demo get pods -l app=config-demo -o wide
kubectl -n demo exec deployment/config-demo -- cat /etc/app/app.conf
kubectl -n demo logs deployment/config-demo --tail=20

查看 Deployment 的 spec.template.spec.containers[].volumeMounts[]:若 subPath: app.confmountPath: /etc/app/app.conf 同时出现,就已经找到“文件始终不变”的直接原因。再核对 spec.template.spec.volumes[].configMap.name 是否指向刚修改的 ConfigMap,以及 Pod 是否属于当前 Deployment。若文件已变而应用行为未变,继续查应用是否只在启动时加载配置或重载失败;不要把这类问题归因于 subPath

修复路径一:保留单文件挂载,滚动创建新 Pod

当应用只能接受固定的单文件路径,或无法安全地改动现有目录布局时,可以保留 subPath,把 ConfigMap 修改和 Deployment 滚动重启作为同一次发布操作:

kubectl -n demo rollout restart deployment/config-demo
kubectl -n demo rollout status deployment/config-demo --timeout=120s
kubectl -n demo exec deployment/config-demo -- cat /etc/app/app.conf
kubectl -n demo get pods -l app=config-demo -o wide

新 Pod 创建时会读取当前 ConfigMap;上述 cat 应输出 log_level=debug。生产环境还应检查应用自己的生效指标或日志,并确认旧 Pod 已退出。滚动发布的可用性取决于副本数、就绪探针、资源余量与发布策略。本文演示的单副本配置不保证零中断。

回滚也应作为一组操作:恢复 ConfigMap 中经过验证的旧值,再触发滚动重启,等待新 Pod 就绪并核对配置。仅回滚 Deployment 镜像,不会自动恢复可变 ConfigMap 的旧内容。

修复路径二:需要文件自动更新时,挂载目录

若应用支持从目录读取配置,可以取消 subPath,把 ConfigMap 的 app.conf 投射到专用目录。替换上面 Deployment 的相关片段:

      containers:
        - name: reader
          image: busybox:1.36
          command: ["sh", "-c", "while true; do cat /etc/app-config/app.conf; sleep 5; done"]
          volumeMounts:
            - name: settings
              mountPath: /etc/app-config
              readOnly: true
      volumes:
        - name: settings
          configMap:
            name: app-settings
            items:
              - key: app.conf
                path: app.conf

items 限定只投射 app.conf 这一项;mountPath 挂载的是整个 /etc/app-config 目录。不要直接覆盖应用自带且包含其他必要文件的目录。挂载后,ConfigMap 更新会经过 kubelet 的同步与缓存传播才出现在文件中,不能把 API 更新成功当成文件已立即更新。更关键的是,文件变化不会自动触发所有应用重新加载;若程序不支持热重载,仍应滚动重启。

预防措施

  1. 在部署规范中明确配置生效方式:目录挂载加应用重载,或单文件挂载加滚动发布。不要把 subPath 当作热更新机制。
  2. 每次配置发布同时验证 API 值、至少一个新 Pod 内的文件值和应用实际行为。多副本服务应逐副本或通过版本指标核对。
  3. 需要精确追溯与回滚时,可使用带版本名的 ConfigMap,并更新 Pod 模板中的引用;模板变化会触发受控滚动发布。不要修改已标记 immutable: true 的 ConfigMap 数据。
  4. 配置中若包含密码或令牌,应改用 Secret 和相应的权限管理,不要放在 ConfigMap。

总结

ConfigMap 更新成功只说明 API 对象已变化。subPath 单文件挂载的运行中容器不会得到新文件;目录挂载可以接收后续文件更新,但还要考虑传播延迟与应用重载。排查时按“API 值 → Pod 文件 → 应用行为”逐层核对,再选择滚动重启或调整挂载方式。

参考资料: