ConfigMap挂载是只读的?

169次阅读
一条评论

今天上午我被K8s的ConfigMap搞疯了——明明Pod启动正常,明明目录里能看到配置文件,可程序就是读不到,报错报得我怀疑人生。

背景:一个看似毫无难度的需求

今天要在K8s上部署一个服务,Java 写的,需求很明确:

  1. 应用启动需要读取好几份配置文件,比如application.properties、logback.xml这些,为了方便管理,统一放在ConfigMap里;
  2. 程序运行的时候,还会动态生成一个授权文件,路径是 /app/config/license_token —— 也就是说,/app/config这个目录,既要放配置(读),还要允许程序写文件。

我当时心想,这还不简单?ConfigMap挂载目录,程序直接读写,一套操作行云流水,结果刚部署就翻车了。

ConfigMap挂载居然是只读的?

一开始我写的YAML配置很常规,就是把ConfigMap直接挂载到/app/config目录:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s.kuboard.cn/name: prod-app-ci
    workload.user.cattle.io/workloadselector: apps.deployment-prod-app-ci
  name: prod-app-ci
  namespace: app
spec:
  replicas: 1
  selector:
    matchLabels:
      workload.user.cattle.io/workloadselector: apps.deployment-prod-app-ci
  template:
    metadata:
      labels:
        workload.user.cattle.io/workloadselector: apps.deployment-prod-app-ci
      namespace: app
    spec:
      containers:
        - image: 'app:2026_04_09_16_47_13'
          imagePullPolicy: IfNotPresent
          name: app
          ports:
            - containerPort: 8080
              hostPort: 8080
              name: http-8080
              protocol: TCP
          volumeMounts:
            - mountPath: /app/config
              name: app-config
      volumes:
        - name: app-config
          configMap:
            name: app-config

部署Pod,启动程序,结果直接炸了,日志里全是这个报错:

panic: open /app/config/license_token: read-only file system

我当时整个人都懵了:???我就挂个配置目录而已,怎么就只读了?我也没写readOnly啊?

赶紧去查文档,才发现一个被我忽略的知识点——ConfigMap挂载的目录,天生就是只读的

这不是K8s的bug,是它的设计逻辑:ConfigMap是用来存储配置的,属于“静态资源”,为了防止误改,默认就是只读挂载。

得,第一个坑踩实了,重新想思路。

解决思路:ConfigMap + emptyDir

既然ConfigMap不能写,那我就找个能写的目录呗。最常用的就是emptyDir,临时可写目录,刚好符合需求。

我的思路很简单:

  1. 用emptyDir创建一个可写的临时目录;
  2. 用initContainer(初始化容器),把ConfigMap里的配置文件,复制到这个emptyDir目录里;
  3. 主容器再挂载这个emptyDir目录——这样既可以读到配置,又能让程序写文件。

修改后的YAML是这样的,当时觉得这就是标准解法,稳得一批:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s.kuboard.cn/name: prod-app-ci
    workload.user.cattle.io/workloadselector: apps.deployment-prod-app-ci
  name: prod-app-ci
  namespace: app
spec:
  replicas: 1
  selector:
    matchLabels:
      workload.user.cattle.io/workloadselector: apps.deployment-prod-app-ci
  template:
    metadata:
      labels:
        workload.user.cattle.io/workloadselector: apps.deployment-prod-app-ci
      namespace: app
    spec:
    initContainers:
    - command:
        - sh
        - '-c'
        - 'cp -r /config/* /app/config/ && chown -R 1001:1001 /app'
    image: 'busybox:1.32'
    imagePullPolicy: IfNotPresent
    name: init
    volumeMounts:
      - mountPath: /config
        name: app-config
      - mountPath: /app/config
        name: config-tmp
      containers:
        - image: 'app:2026_04_09_16_47_13'
          imagePullPolicy: IfNotPresent
          name: app
          ports:
            - containerPort: 8080
              hostPort: 8080
              name: http-8080
              protocol: TCP
          volumeMounts:
            - mountPath: /app/config
              name: config-tmp
      volumes:
        - name: app-config
          configMap:
            name: app-config
        - name: config-tmp
          emptyDir: {}

梳理一下链路,特别清晰:

ConfigMap → initContainer(复制) → emptyDir → 主容器

部署Pod,这次没报错,启动成功了。我当时还暗自庆幸,还好反应快,第一个坑顺利解决。

结果,第二个坑马上就来了。

第二个坑

文件明明在,却读不到?玄学了。

Pod启动正常,我赶紧exec进去查看,确认配置文件是不是复制成功了:

kubectl exec -it 你的Pod名 -- sh
cd /app/config

输出结果很正常,所有配置文件都在:

application.properties  logback.xml  xxx.conf

我心里一块石头落了地,觉得没问题了。可当我尝试查看文件内容时,直接懵了:

cat application.properties

报错如下,直接给我整破防了:

No such file or directory

???什么情况?ls明明能看到文件,cat却提示不存在?

我反复执行ls和cat,确认不是自己输错了文件名,可问题就是这么诡异——文件“看得见、摸不着”,读不到任何内容。

这时候我已经有点急躁了,生产环境下,一个看似简单的配置,居然卡了两次。

真相大白

冷静下来,我觉得问题肯定出在“复制”这一步。于是我用ls -l命令,查看一下文件的详细信息,这一看,终于发现了关键线索:

ls -l /app/config

输出结果让我恍然大悟:

lrwxrwxrwx 1 root root 23 Jun  8 14:30 application.properties -> ..data/application.properties
lrwxrwxrwx 1 root root 18 Jun 8 14:30 logback.xml -> ..data/logback.xml

原来如此!这些我以为的“配置文件”,根本不是普通文件,全是符号链接(软链接)

我再深入查看一下目录结构,终于摸清了ConfigMap的底层逻辑:

/app/config/
├── application.properties -> ..data/application.properties
├── logback.xml -> ..data/logback.xml
└── ..data/
    ├── application.properties
    └── logback.xml

简单说就是:我们看到的配置文件,只是一个“快捷方式”,真正的文件内容,其实存放在上级的..data目录里。

真正的坑点

cp命令复制的是“链接”,不是文件内容。

现在再回头看我initContainer里的复制命令:

cp -r /config/* /app/config/

这一步看似没问题,实则藏着致命错误——cp命令默认复制的是“符号链接本身”,而不是链接指向的真实文件内容。

也就是说,我复制到emptyDir里的,只是一个个“快捷方式”,而这些快捷方式指向的..data目录,在emptyDir里根本不存在!

所以才会出现:ls能看到文件(其实是链接),但cat的时候,链接指向的目标不存在,就报错“没有这个文件或目录”。

找到问题根源,我瞬间松了口气——原来不是玄学,是我忽略了ConfigMap的底层实现细节。

最终解决:一个参数,救命了

解决方法其实特别简单,就给cp命令加一个参数:-L

修改后的复制命令是这样的:

cp -rL /config/. /app/config/

重点说一下这个-L参数的作用:跟随符号链接,复制链接指向的真实文件内容,而不是复制链接本身。

还有一个小细节,把/config/*改成/config/.,这样能把隐藏文件(如果有的话)也一起复制,避免遗漏。

修复后,再验证一次

重新部署Pod,exec进去查看:

ls -l /app/config

这次的输出就正常了,再也不是符号链接了:

-rw-r--r-- 1 root root  568 Jun  8 14:35 application.properties
-rw-r--r-- 1 root root 1245 Jun 8 14:35 logback.xml

再cat文件,完美读取:

cat application.properties  # 正常输出配置内容

启动程序,程序也能正常生成license_token文件,读写都没问题——困扰我1个小时的坑,终于解决了。

总结

这次踩坑,看似是两个独立的问题,本质上是我对ConfigMap的底层实现了解不够深,总结下来就一句话:

ConfigMap里的“文件”,其实是符号链接,不是真文件

由此衍生出两个典型坑点,大家一定要记牢:

坑点一:ConfigMap挂载目录,默认只读

👉 只要你的目录需要“写”操作(比如生成文件、修改配置),就不能直接挂载ConfigMap;

👉 正确做法:用emptyDir(临时)或PVC(持久化)当可写目录,通过initContainer复制ConfigMap内容。

坑点二:cp命令默认复制“链接”,不是内容

👉 复制ConfigMap里的文件时,一定要加-L参数,跟随符号链接,复制真实内容;

👉 避免用cp -r /config/*,改用cp -rL /config/.,防止遗漏隐藏文件。

延伸一下

可能有人会问:K8s为什么要搞这种“软链接+..data”的结构?多麻烦啊?

其实这是为了支持ConfigMap热更新,而且是原子操作,特别优雅:

  1. 当你更新ConfigMap内容时,K8s会创建一个新的..data目录,存放新的配置文件;
  2. 然后瞬间切换符号链接的指向,从旧的..data指向新的..data;
  3. 整个过程是原子的,不会出现“配置一半更新、一半未更新”的情况,也不用重启Pod。

搞懂这个逻辑,你就会明白,这个“麻烦”的设计,其实是K8s的用心之处。

最后

今天这个坑,给我的最大感受就是:K8s里很多“玄学问题”,其实都不是玄学,只是我们没看透它的底层逻辑。

就像这次的ConfigMap,看似简单的挂载和复制,背后藏着符号链接的机制,一旦忽略,就会踩坑。

如果你也遇到过这种“文件在,却读不到”“K8s行为很诡异”的问题,不妨往“底层实现”上多想想,大概率能找到突破口。

另外,也提醒自己和各位同行:生产环境的坑,从来都不是大问题,而是一个个小细节的疏忽。多踩一次坑,多记一个细节,下次就能少走很多弯路。

后续优化使用pvc

cm是注入的方式去进行热更新。如果是类似于nfs挂载的话,就会有多次读写IO。

ConfigMap挂载是只读的?

正文完
 0
评论(一条评论)
2026-05-10 20:47:33 回复

wouuwvmgtqlvmzjgzeymywyphsrkkw

 Windows  Firefox  美国纽约纽约