bigywater.tech
Kubernetes · 61

projected 卷:Secret、ConfigMap 与令牌合并挂载

把 Secret、ConfigMap、令牌、Pod 元数据放在同一个目录里,到底是怎么实现的?路径冲突会怎样?来源不存在会怎样?这篇在真集群里把一个 projected 卷拆开,看文件、权限、符号链接和各种失败。

4 种来源
一个 projected 卷合并出 6 个文件,还有 ..data 符号链接
后面的赢
整体挂载的 Secret 与 ConfigMap 有同名键时,列表靠后的来源覆盖前面的
10 分钟
serviceAccountToken 的 expirationSeconds 下限

一个目录,四个来源

实测 · 本机执行输出
  容器里 /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 卷的样子。

分步演示

projected 卷
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0);容器内用 busybox 的 find、ls、stat 读文件;令牌用 base64 解出 payload 的 aud、iat、exp;“87 秒”“8.8 秒”是单次轮询值,其中 optional 来源后补的延迟在不同次运行里差别很大(同一实验较早的一次是约 1 秒);每种情形各一次
    • 没有验证:kube-api-access 默认卷里 ca.crt 与 namespace 的来源;clusterTrustBundle 来源;podCertificate 来源;令牌自动轮换(kubelet 在 80% 有效期时刷新);同名键覆盖的顺序是否有文档保证;projected 卷的 defaultMode 与 fsGroup 的交互;大量来源对 kubelet 的开销
    京ICP备2026067502号-1京公网安备11010502063487号