在 K8s 生产环境中,ConfigMap 几乎是每个应用都会用到的配置管理方式。但很多人在第一次用它挂载配置文件时,都会遇到同一个"坑":为什么挂载进去的文件不能改?
本文以实际部署文件 deploy-beta.yml 为参考,梳理 ConfigMap 挂载文件的核心用法,并重点解决那个让人头疼的 read-only file system 问题。
一、为什么用 ConfigMap 挂载配置文件
ConfigMap 是 Kubernetes 中用于存储非敏感配置数据的 API 对象。它的核心价值在于将配置与镜像解耦,让你不用重新构建镜像就能调整应用行为。
当应用需要的是磁盘上的配置文件(如 redis.conf、nginx.conf、application.properties),而不是环境变量时,就应该选择挂载为文件的方式。ConfigMap 中的每个键会成为挂载目录下的一个文件,值就是文件内容。
二、挂载配置文件的基本方式
先看一个标准的 Redis 配置挂载示例。假设 ConfigMap 长这样:
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
redis.conf: |
maxmemory 2mb
maxmemory-policy allkeys-lru
Pod 中这样引用:
yaml
apiVersion: v1
kind: Pod
metadata:
name: redis
spec:
containers:
- name: redis
image: redis:8.0.2
command: ["redis-server", "/redis-master/redis.conf"]
volumeMounts:
- name: config
mountPath: /redis-master
volumes:
- name: config
configMap:
name: app-config
items:
- key: redis.conf
path: redis.conf
关键点在 volumes.configMap.items 的 key 和 path:key 对应 ConfigMap 中的键名,path 是最终生成的文件名。挂载后,/redis-master/redis.conf 就是配置文件。
三、实际部署中的问题:不能修改 ConfigMap 挂载的文件
在实际项目(如你提到的 deploy-beta.yml)中,常见的一个模式是:应用启动时需要读取配置,但启动后可能还需要写入或修改这个配置文件。
如果你尝试在容器内编辑挂载进来的配置文件,就会遇到:
Error: [Errno 30] Read-only file system: '/config/xxx.conf'
为什么是只读的
从 Kubernetes 1.9 版本开始 ,secret、configMap、downwardAPI 和 projected 卷的默认行为都改为只读挂载 。即使你设置了 defaultMode 或 mode 为可写权限,也不会生效------写操作会被系统拒绝。
设计意图很明确:ConfigMap 是配置的来源,不是运行时可写的状态存储。应用对配置的修改应该发生在别的地方。
四、解决方案:用 emptyDir + initContainer 中转
既然挂载的 ConfigMap 不能写,那就在启动时把它复制到一块可写的空间里。
核心思路:用一个 initContainer 把 ConfigMap 挂载到临时位置,再把内容拷贝到一个 emptyDir 卷中;应用容器挂载这个 emptyDir,就可以自由读写文件了。
yaml
spec:
initContainers:
- name: copy-config
image: busybox:1.36
command: ['sh', '-c', 'cp /config-source/* /config-target/']
volumeMounts:
- name: config-source
mountPath: /config-source
- name: config-writable
mountPath: /config-target
containers:
- name: app
image: your-app:latest
volumeMounts:
- name: config-writable
mountPath: /app/config
volumes:
- name: config-source
configMap:
name: app-config
- name: config-writable
emptyDir: {}
这样,应用容器看到的 /app/config/ 就是一个可读可写的普通目录,里面已经有初始化好的配置文件。后续应用在运行时修改配置,不会报错,也不会影响 ConfigMap 本身。
为什么不用 subPath
有人可能会想到用 subPath 来挂载单个文件。但要注意:subPath 挂载的 ConfigMap 文件同样不会自动更新,而且 subPath 场景下你依然无法写入该文件。更关键的是,用 subPath 挂载单个文件时,如果该文件被替换(如通过符号链接更新),subPath 挂载的内容不会刷新,这在生产环境中反而容易造成配置不一致。
emptyDir 中转方案虽然多了一个 initContainer,但行为明确、可预期,是更稳妥的做法。
五、补充要点
- ConfigMap 有大小限制:单个 ConfigMap 不超过 1 MiB。大文件应考虑用 PVC 或外部配置服务。
- 自动更新有限制 :直接以卷挂载的 ConfigMap 会被 kubelet 定期同步更新(默认延迟约 1 分钟);但用 subPath 挂载的不会更新,通过
emptyDir中转的也不会自动更新。 - 环境变量方式不会自动更新:以环境变量注入的 ConfigMap,Pod 启动后不会感知变化,需要重启 Pod。
小结
ConfigMap 挂载配置文件是 K8s 的标准做法,但"挂载即只读"是 1.9 之后的设计约束。遇到需要运行时修改配置文件的场景,用 initContainer + emptyDir 做一层拷贝中转,既能保留 ConfigMap 集中管理配置的优势,又能满足应用对可写文件系统的需求。