bigywater.tech
Kubernetes · 63

subPath 挂载:为什么更新不生效

用 subPath 挂的 ConfigMap 文件,为什么怎么改都不更新?这篇把同一个 ConfigMap 分别整体挂载和 subPath 挂载,对比文件类型、mountinfo 的来源路径和重启后的行为,直接看到原因。

62 秒 vs 永远
整体挂载更新用了 62 秒;subPath 挂载等到 122 秒仍是旧值
//deleted
subPath 挂载在 mountinfo 里绑定的是已被删除的旧目录里的文件
1 次容器重启
重启容器(不重建 Pod)后 subPath 文件才变成新内容

同一个 ConfigMap,两种结果

实测 · 本机执行输出
  初始:/whole/app.conf='v1';/etc/app.conf='v1'
  改成 v2:整体挂载的 /whole/app.conf 62 秒后变成 v2
  再等 60 秒(总共 122 秒):/whole/app.conf='v2';/etc/app.conf='v1'(subPath 的文件一直是 v1)

把同一个 ConfigMap 整体挂到 /whole,又用 subPath: app.conf 把单个文件挂到 /etc/app.conf。把 app.conf 从 v1 改成 v2:整体挂载的 /whole/app.conf 在 62 秒后变成 v2(同步延迟在不同次运行里有 1~86 秒的差别,见上一篇);/etc/app.conf 即使等到 122 秒仍是 v1。

为什么:绑定的是旧文件

实测 · 本机执行输出
  ls -la /whole:
    lrwxrwxrwx    1 root     root            31 Oct 10 14:57 ..data -> ..2026_10_10_14_57_45.338504384
    lrwxrwxrwx    1 root     root            15 Oct 10 14:56 app.conf -> ..data/app.conf
    lrwxrwxrwx    1 root     root            17 Oct 10 14:56 other.conf -> ..data/other.conf
  ls -la /etc/app.conf:
    -rw-r--r--    0 root     root             2 Oct 10 14:56 /etc/app.conf
  mountinfo:挂载点 /whole;来源路径 …/volumes/kubernetes.io~configmap/cfg
  mountinfo:挂载点 /etc/app.conf;来源路径 …/volumes/kubernetes.io~configmap/cfg/..2026_10_10_14_56_43.971564555/app.conf//deleted
  节点上卷目录里的 ..data 现在指向:..2026_10_10_14_57_45.338504384;目录里 app.conf 的内容 'v2'

/whole/app.conf 是指向 ..data/app.conf 的符号链接,更新靠切换 ..data;而 /etc/app.conf 是一个普通文件,它是 bind mount。mountinfo 里这个挂载的来源是 …/cfg/..2026_10_10_14_56_43.…/app.conf//deleted——启动时那个时间戳目录里的文件,更新后这个目录被 kubelet 删掉了,所以带 //deleted。bind mount 绑定的是当时的文件本身,不是路径,新内容写在新的目录里,它永远看不到。节点上的 ..data 此时已指向新目录,里面的 app.conf 是 v2。

怎么让它变

实测 · 本机执行输出
  容器重启后(restartCount=1,Pod 没重建):/etc/app.conf='v2'(新容器启动时从卷里重新取了 v2)

重启容器(Pod 不用重建)就够了:用 crictl 停掉容器,kubelet 重启后 restartCount=1,/etc/app.conf 变成 v2——新容器启动时重新做了 subPath 的绑定。实际操作里常用 kubectl rollout restart,或者干脆不用 subPath(把配置挂到独立目录再用 --config 指过去),或者借助 reloader 之类的外部控制器。

subPath 的价值

实测 · 本机执行输出
  没挂卷时 /var 里有:['run', 'spool', 'www']
  把整个卷挂到 /var 之后 /var 里只剩:['app.conf', 'other.conf', 'run'](原来的内容被遮住)
  用 subPath 挂单个文件到 /var/app.conf 后 /var 里有:['app.conf', 'run', 'spool', 'www'](原来的文件都在,多了 app.conf)

把整个卷挂到 /var,原来 /var 下的 spool、www 就被遮住了(只剩 app.conf、other.conf 和 run;run 应该是为 /var/run/secrets 的令牌挂载留下的);用 subPath 把单个文件挂到 /var/app.conf,原有的 run、spool、www 都还在——这是 subPath 存在的理由,代价就是不会更新。

subPathExpr

实测 · 本机执行输出
  两个 Pod 共用节点上的 hostPath /tmp/k57-shared,各自 subPathExpr=$(POD_NAME):节点上 ['w1/out.log=w1', 'w2/out.log=w2']

subPathExpr 可以用环境变量展开路径:两个 Pod 共用节点上的 hostPath /tmp/k57-shared,各自 subPathExpr: $(POD_NAME)(POD_NAME 由 downward API 注入),节点上得到 w1/out.log 和 w2/out.log,互不覆盖。

分步演示

subPath
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0);每 0.5 秒 kubectl exec 读文件得到更新延迟;mountinfo 取自容器内的 /proc/self/mountinfo;subPath 重启实验用 crictl stop 触发 kubelet 重启容器;每种情形各一次(同步延迟本身在多次运行中为 1~86 秒)
    • 没有验证:subPath 用在 Secret / projected / emptyDir 上的差别(机制相同,本文只测了 ConfigMap);subPath 指向目录而不是文件时的更新行为;subPath 与符号链接的安全限制(CVE 相关的 subPath 逃逸检查);hostPath 上 subPathExpr 的权限;应用用 inotify 监听配置文件的行为
    京ICP备2026067502号-1京公网安备11010502063487号