适用场景
应用把 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=info。jsonpath 中的 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.conf 与 mountPath: /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 更新成功当成文件已立即更新。更关键的是,文件变化不会自动触发所有应用重新加载;若程序不支持热重载,仍应滚动重启。
预防措施
- 在部署规范中明确配置生效方式:目录挂载加应用重载,或单文件挂载加滚动发布。不要把
subPath当作热更新机制。 - 每次配置发布同时验证 API 值、至少一个新 Pod 内的文件值和应用实际行为。多副本服务应逐副本或通过版本指标核对。
- 需要精确追溯与回滚时,可使用带版本名的 ConfigMap,并更新 Pod 模板中的引用;模板变化会触发受控滚动发布。不要修改已标记
immutable: true的 ConfigMap 数据。 - 配置中若包含密码或令牌,应改用 Secret 和相应的权限管理,不要放在 ConfigMap。
总结
ConfigMap 更新成功只说明 API 对象已变化。subPath 单文件挂载的运行中容器不会得到新文件;目录挂载可以接收后续文件更新,但还要考虑传播延迟与应用重载。排查时按“API 值 → Pod 文件 → 应用行为”逐层核对,再选择滚动重启或调整挂载方式。
参考资料:
Discussion
评论