把 Secret、ConfigMap、令牌、Pod 元数据放在同一个目录里,到底是怎么实现的?路径冲突会怎样?来源不存在会怎样?这篇在真集群里把一个 projected 卷拆开,看文件、权限、符号链接和各种失败。
容器里 /etc/all 下的所有文件(find):./creds/db-password ./level ./meta/cpu-request ./meta/labels ./mode ./token/jwt
ls -la /etc/all:
total 4
drwxrwxrwt 3 root root 180 Oct 10 14:37 .
drwxr-xr-x 1 root root 4096 Oct 10 14:37 ..
drwxr-xr-x 5 root root 140 Oct 10 14:37 ..2026_10_10_14_37_24.422283576
lrwxrwxrwx 1 root root 31 Oct 10 14:37 ..data -> ..2026_10_10_14_37_24.422283576
lrwxrwxrwx 1 root root 12 Oct 10 14:37 creds -> ..data/creds
lrwxrwxrwx 1 root root 12 Oct 10 14:37 level -> ..data/level
lrwxrwxrwx 1 root root 11 Oct 10 14:37 meta -> ..data/meta
lrwxrwxrwx 1 root root 11 Oct 10 14:37 mode -> ..data/mode
lrwxrwxrwx 1 root root 12 Oct 10 14:37 token -> ..data/token
各文件内容:db-password='s3cret';mode='prod';labels='app="demo"';cpu-request='250'
权限:db-password 400(items 里写的 mode 0400);mode(没写 mode)644(volume 的 defaultMode 0644)
token/jwt 的声明:aud=['demo-audience'];exp-iat=3600 秒;sub=system:serviceaccount:k55:default
关键:Secret 的 user 键没出现在目录里(因为用了 items,只挂了 password):True同一个 projected 卷里放了:Secret db(只用 items 挂 password,路径 creds/db-password,权限 0400)、整个 ConfigMap app、Downward API 的 labels 与 requests.cpu(divisor: 1m,值是 250)、audience: demo-audience 的 ServiceAccountToken。容器里 /etc/all 下有 creds/db-password、level、meta/cpu-request、meta/labels、mode、token/jwt。
每个文件其实是 ..data 指向的带时间戳目录里的符号链接。用了 items 的 Secret 只挂列出的键,user 键没有出现;没写 mode 的文件权限是 volume 的 defaultMode 644,写了 0400 的是 400。令牌的 aud 是 demo-audience,有效期 3600 秒。
创建:退出码 1;The Pod "p2" is invalid: * spec.volumes[0].projected: Invalid value: "app": conflicting duplicate paths
整个 Secret + 整个 ConfigMap(键名不同)合并:['level', 'mode', 'password', 'user']
整个 Secret 和整个 ConfigMap 里恰好有同名键 password:退出码 0;pod/p4 created
运行起来之后 /etc/all/password 的内容:'from-configmap'(ConfigMap 在列表里排在 Secret 之后)
把两个来源的顺序对调(ConfigMap 在前、Secret 在后):/etc/all/password='s3cret'两个来源用 items 写到同一个路径,创建 Pod 就被拒绝:conflicting duplicate paths。但整个 Secret 与整个 ConfigMap 碰巧有同名键 password 时 API Server 不检查,Pod 能创建:ConfigMap 排在 Secret 后面时文件内容是 from-configmap,把顺序对调就是 s3cret——列表里靠后的来源赢。这类静默覆盖要靠自己小心。
Secret later 不存在:phase=Pending;容器 waiting.reason=ContainerCreating
事件:['FailedMount: MountVolume.SetUp failed for volume "all" : secret "later" not found']
创建 Secret later 之后 8.8 秒容器启动(kubelet 重试挂载有间隔)
optional: true 的 Secret nobody 不存在:0.9 秒后 Running;/etc/all 里有:['level', 'mode'](只有 ConfigMap 的键)
之后创建 Secret nobody:87 秒后 /etc/all/hi 出现(optional 的来源后来出现也会补进来)
items 里引用不存在的键 nokey:容器 ContainerCreating;事件:['MountVolume.SetUp failed for volume "all" : references non-existent secret key: nokey']引用不存在的 Secret,Pod 卡在 ContainerCreating,事件 FailedMount: … secret "later" not found;补建 Secret 后 8.8 秒容器才启动,kubelet 的重试有间隔。optional: true 的来源缺了不影响启动(0.9 秒 Running,目录里只有 ConfigMap 的键);之后创建这个 Secret,hi 文件在 87 秒后才出现。items 引用不存在的键同样卡在 ContainerCreating,事件是 references non-existent secret key。
expirationSeconds=100:退出码 1;The Pod "e100" is invalid: * spec.volumes[0].projected.sources[0].serviceAccountToken.expirationSeconds: Invalid value: 100: may not specify a duration less than 10 minutes expirationSeconds=600:退出码 0;pod/e600 created expirationSeconds=3600:退出码 0;pod/e3600 created 不写 audience 和 expirationSeconds:aud=['https://kubernetes.default.svc.cluster.local'];exp-iat=3600 秒(默认值 3600)
projected 的 serviceAccountToken 来源要求 expirationSeconds 不小于 10 分钟:写 100 被拒(may not specify a duration less than 10 minutes),600 与 3600 可以。不写 audience 和有效期时,aud 是 https://kubernetes.default.svc.cluster.local,有效期 3600 秒——这也是每个 Pod 默认挂的 kube-api-access-xxxxx 卷的样子。