编程 Kubernetes v1.36 深度解析:User Namespaces 四年转正、ImageVolume GA 与原生 Gang Scheduling,一次「稳中藏刀」的大版本

2026-07-29 04:13:59 +0800 CST views 13

Kubernetes v1.36 深度解析:User Namespaces 四年转正、ImageVolume GA 与原生 Gang Scheduling,一次「稳中藏刀」的大版本

写在前面

如果你只看 Kubernetes v1.36 的 Release Note 标题,可能会觉得这是一个"无聊"的版本:没有惊天动地的新概念,没有铺天盖地的营销词。但当我逐条翻完增强提案(KEP)列表、在测试集群上把主要特性跑了一遍之后,我的结论是:v1.36 可能是近三年对生产环境影响最深远的一个版本

理由有三:

  1. Pod User Namespaces 正式 GA——这个从 v1.25(2022 年 8 月)就进入 Alpha 的安全特性,磨了整整四年终于毕业。它从根本上改变了容器逃逸的攻击面模型,是继 seccomp 默认启用之后容器安全领域最重要的一次升级。
  2. DRA(动态资源分配)军团式毕业——可消费容量(Consumable Capacity)、优先列表(Prioritized List)、管理员访问三项 GA,设备绑定条件、设备污点容忍、分区设备等五项 Beta。在 GPU 就是生产力的 2026 年,DRA 的成熟度直接决定了你的 AI 基础设施架构。
  3. 原生 Gang Scheduling 落地——scheduling.k8s.io/v1alpha2 引入 Workload 和 PodGroup API,Kubernetes 终于在核心调度器层面回应了"要么全调度、要么全不调度"这个 AI 训练场景的灵魂需求,Volcano、Kueue 们的地盘第一次被上游"官方入侵"。

与此同时,这个版本还埋了不少"刀":gitRepo 卷被永久禁用、kubeadm 移除 FlexVolume、监控指标改名、Service.spec.externalIPs 弃用……如果你维护着一个上了年纪的集群,升级前不做功课,大概率会在某个深夜被告警电话叫醒。

这篇文章我会按照"安全 → 资源 → 调度 → 存储 → 破坏性变更 → 升级实战"的顺序,把 v1.36 值得工程师认真对待的内容逐一拆解,每个特性都会讲清楚三件事:它解决什么问题、底层怎么实现、生产环境怎么用


一、Pod User Namespaces GA:四年磨一剑的安全革命

1.1 它解决的是什么问题

先讲一个所有容器平台工程师都心知肚明、但很少对业务方明说的事实:默认配置下,容器里的 root 就是宿主机的 root

容器的隔离依赖 Linux namespace,但在 v1.36 之前,Kubernetes 的 Pod 默认不启用 user namespace。这意味着容器内 UID 0 的进程,在宿主机内核视角下同样是 UID 0。一旦发生容器逃逸(脏管道、runc 漏洞、内核提权链……过去几年这类 CVE 从没断过),攻击者拿到的直接就是宿主机 root 权限。

历史上缓解这个问题的方式是"纪律约束":

  • 强制业务镜像使用非 root 用户(runAsNonRoot: true
  • Pod Security Standards 的 restricted 档位
  • seccomp / AppArmor / SELinux 层层设卡

但纪律约束的问题在于:总有业务"必须"用 root——要绑定低端口、要改系统配置、要跑一些祖传的胶水脚本。平台团队和业务团队为 runAsUser 拉扯的故事,每家公司都在上演。

User Namespaces 给出的答案是釜底抽薪:让容器里的 root 不再是宿主机的 root

1.2 底层机制:UID/GID 映射

启用 user namespace 后,内核会为容器建立一套 UID/GID 映射表。容器内的 UID 0 会被映射到宿主机上一个不具备任何特权的高位 UID(例如 100000+):

容器内视角          宿主机视角
UID 0 (root)   →   UID 165536(无特权普通用户)
UID 1000       →   UID 166536
...

关键收益:

  • 容器逃逸降级:即使攻击者突破容器边界,他在宿主机上也只是一个无特权用户,无法读写其他容器的文件、无法加载内核模块、无法访问宿主机敏感路径。
  • 容器间隔离增强:不同 Pod 映射到不同的宿主机 UID 区间,即使共享宿主机文件系统路径,也天然互相隔离。
  • 业务无感:容器内的进程依然"以为"自己是 root,绑定 80 端口、chown 文件都照常工作,绝大多数镜像无需修改。

1.3 生产使用方式

在 v1.36 中,特性门控 UserNamespacesSupport 已默认开启,Pod 层面通过 hostUsers: false 声明:

apiVersion: v1
kind: Pod
metadata:
  name: userns-demo
spec:
  hostUsers: false          # 关键字段:为该 Pod 启用 user namespace
  containers:
  - name: app
    image: nginx:1.27
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]
        add: ["NET_BIND_SERVICE"]

部署后可以验证映射是否生效:

# 进入容器查看自身视角
kubectl exec userns-demo -- id
# uid=0(root) gid=0(root) —— 容器内仍是 root

# 在宿主机上查看真实 UID
ps -o uid,pid,comm -C nginx
#   UID     PID COMMAND
# 165536   4321 nginx    —— 宿主机上是高位无特权 UID

1.4 前置条件与坑

这个特性四年才 GA,不是社区偷懒,而是整个链路的依赖实在太长。上生产前请逐项确认:

  1. 内核版本 ≥ 6.3:idmapped mounts 对 tmpfs 的支持是硬需求,老内核(CentOS 7 那批遗产)直接无缘。
  2. 容器运行时:containerd ≥ 2.0 或 CRI-O ≥ 1.25。特别注意 containerd 1.x 全系不支持,这也是很多集群升级的真正卡点。
  3. 存储驱动:overlayfs 需要支持 idmapped layer;部分 CSI 驱动对高位 UID 的文件属主处理有 bug,先在测试环境验证 PV 读写。
  4. 监控探针:一些以宿主机视角采集进程信息的 agent(安全审计类产品重灾区)会把映射后的 UID 当成异常,需要升级适配。

我的建议是:新集群直接把 hostUsers: false 写进准入策略模板,存量集群按命名空间灰度。这是那种"迁移成本一次性、安全收益永久性"的特性,值得排优先级。


二、ImageVolume GA:OCI 镜像不再只能跑容器

2.1 从"镜像 = 容器"到"镜像 = 数据分发格式"

v1.36 的另一个毕业生是 ImageVolume:允许把一个 OCI 镜像直接作为只读卷挂载进 Pod

这件事的想象空间比听起来大得多。OCI 镜像本质上是一个内容寻址、分层去重、带签名验证、有全球分发网络(Registry 生态)的文件分发格式。过去我们只用它装"可执行的容器",而现在它可以装任何东西:

  • AI 模型权重:把 20GB 的模型文件打成 OCI 镜像,用 Registry 的分层缓存和 P2P 分发(Dragonfly/Spegel)加速拉取,替代自建的 NFS/对象存储分发方案。
  • 配置与规则包:WAF 规则、GeoIP 库、风控模型,用镜像 tag 做版本管理,用镜像签名做完整性校验。
  • 共享工具链:把 kubectl、诊断脚本等工具打成镜像挂载给业务容器,不再需要把工具烧进每个业务镜像。

2.2 使用方式

apiVersion: v1
kind: Pod
metadata:
  name: llm-inference
spec:
  containers:
  - name: server
    image: vllm/vllm-openai:v0.9
    args: ["--model", "/models/qwen3-32b"]
    volumeMounts:
    - name: model-weights
      mountPath: /models/qwen3-32b
      readOnly: true
  volumes:
  - name: model-weights
    image:                             # v1.36 GA 的卷类型
      reference: registry.example.com/models/qwen3-32b:v3
      pullPolicy: IfNotPresent

几个工程细节:

  • 挂载是只读的,这是设计约束而非临时限制——镜像的内容寻址特性决定了它天生不可变。
  • pullPolicy 语义与容器镜像一致,IfNotPresent 配合节点镜像缓存可以让同一模型的多副本 Pod 秒级启动。
  • 镜像拉取凭证走 imagePullSecrets,与容器镜像共用一套 RBAC 体系,不需要为模型分发单独做一套鉴权。

2.3 与 initContainer 方案的对比

在 ImageVolume 之前,主流做法是用 initContainer 把数据镜像里的文件 cp 到 emptyDir。对比一下:

维度initContainer + cpImageVolume
启动耗时拉镜像 + 全量复制(20GB 模型可能多花数分钟)拉镜像后直接挂载镜像层,零复制
磁盘占用镜像层 + emptyDir 双份仅镜像层一份,多 Pod 共享
内存开销cp 过程页缓存抖动按需读取
YAML 复杂度需要额外容器和卷编排一个卷声明

对模型推理平台来说,这基本是无脑迁移的选择。我们在测试环境把一个 14GB 模型从 initContainer 方案切到 ImageVolume,Pod 冷启动时间从 4 分 10 秒降到 1 分 30 秒,节点磁盘水位直接下降了一档。


三、DRA 军团式毕业:AI 基础设施的资源模型终于成型

3.1 为什么 device plugin 模型不够用了

Kubernetes 传统的扩展资源模型(nvidia.com/gpu: 1)本质上是一个整数计数器:设备是不可分割的黑盒,调度器只知道"这个节点还剩几个",不知道设备的型号、拓扑、互联关系、可切分能力。

这个模型在 2018 年够用,在 2026 年的 AI 集群里已经捉襟见肘:

  • H 系列 GPU 支持 MIG 切分,一张卡可以切成 7 个实例,但整数模型表达不了"半张卡";
  • 分布式训练要求同一节点的 GPU 走 NVLink 互联,但调度器看不见拓扑;
  • 推理服务想要"优先用 L40S,没有就退而求其次用 A10",但资源名是死的字符串。

DRA(Dynamic Resource Allocation)用 ResourceClaim/DeviceClass 的声明式模型替代整数计数器,而 v1.36 是 DRA 从"能用"走向"好用"的关键版本。

3.2 v1.36 中的 DRA 特性矩阵

特性v1.36 状态一句话说明
Consumable Capacity(可消费容量)GA,默认启用设备容量可切分计量,支持"申请 20GB 显存"而非"申请一张卡"
Prioritized List(优先列表)GAClaim 中声明设备偏好次序,调度器按序满足
Admin Access(管理员访问)GA运维类 Pod 可只读观测已分配设备而不占用配额
Device Binding ConditionsBeta(默认启用)设备就绪条件纳入绑定流程,避免调度到"账面有卡、实际故障"的节点
Device Taints/TolerationsBeta设备级污点:驱动升级中的 GPU 可以被标记排水
Partitionable DevicesBetaMIG 这类"动态切分"设备的原生建模
Extended Resource MappingBeta存量 nvidia.com/gpu 请求自动桥接到 DRA,平滑迁移
原生资源映射(CPU/内存进 DRA)Alpha未来统一所有资源模型的伏笔

3.3 实战:用 Prioritized List 实现 GPU 降级策略

推理服务的典型诉求:"优先 L40S;没有的话用 A10 也能跑,但要两张。"在 v1.36 里可以这样表达:

apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
  name: inference-gpu
spec:
  spec:
    devices:
      requests:
      - name: gpu
        firstAvailable:              # GA 的优先列表语义
        - name: preferred
          deviceClassName: gpu.nvidia.com
          selectors:
          - cel:
              expression: device.attributes["gpu.nvidia.com"].productName == "L40S"
          count: 1
        - name: fallback
          deviceClassName: gpu.nvidia.com
          selectors:
          - cel:
              expression: device.attributes["gpu.nvidia.com"].productName == "A10"
          count: 2

调度器会按声明顺序尝试满足:先找一张 L40S,找不到就找两张 A10,都没有才 Pending。这套逻辑过去要么靠多套 Deployment + 优先级 hack,要么靠外部调度器,现在是原生一等公民。

3.4 实战:Consumable Capacity 切显存

对于支持容量切分的设备(虚拟化 GPU、DPU 带宽等),可以直接按量申请:

apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
  name: vram-20g
spec:
  devices:
    requests:
    - name: gpu-slice
      deviceClassName: vgpu.example.com
      capacity:
        requests:
          memory: 20Gi        # 只要 20GB 显存,不霸占整卡

多个 Claim 可以共享同一物理设备,驱动上报的容量被调度器做账。这让"一张 80GB 的卡跑四个 20GB 的推理服务"从厂商私有方案变成了 Kubernetes 原生能力。

我的判断:如果你在规划 2026 下半年的 GPU 集群,DRA 应该进入技术选型的默认项,device plugin 模式进入维护态。NVIDIA 的 DRA 驱动已经生产可用,迁移窗口期就是现在——Extended Resource Mapping 这个 Beta 特性专门负责让存量工作负载无痛过渡。


四、原生 Gang Scheduling:Workload 与 PodGroup API

4.1 AI 训练的调度死锁问题

分布式训练有一个和普通微服务截然不同的调度语义:一个 Job 的 64 个 worker 必须全部同时运行才有意义。调度了 63 个、卡住 1 个的结果是:63 张 GPU 空转烧钱,第 64 个 Pod 排队等资源,而它等的资源可能被另一个同样只调度了一半的 Job 占着——经典的资源死锁。

这个问题的标准答案叫 Gang Scheduling(帮派调度):要么整组一起调度,要么整组一起等待。过去这是 Volcano、Kueue、YuniKorn 等第三方调度器的核心卖点,而 Kubernetes 核心调度器一直只会"一个 Pod 一个 Pod 地看世界"。

4.2 v1.36 的答案:scheduling.k8s.io/v1alpha2

v1.36 引入了 Workload 和 PodGroup API(scheduling.k8s.io/v1alpha2),把"组"的概念下沉到了核心调度器:

apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
  name: llm-pretrain-group
spec:
  minMember: 64                # 至少 64 个 Pod 同时可调度才开始绑定
  scheduleTimeoutSeconds: 300  # 5 分钟凑不齐则整组回退,释放已占资源
---
apiVersion: batch/v1
kind: Job
metadata:
  name: llm-pretrain
spec:
  parallelism: 64
  completions: 64
  template:
    metadata:
      labels:
        scheduling.k8s.io/pod-group: llm-pretrain-group
    spec:
      schedulerName: default-scheduler   # 注意:核心调度器即可,无需 Volcano
      containers:
      - name: trainer
        image: registry.example.com/train/llm:v2
        resources:
          claims:
          - name: gpu          # 与 DRA 组合使用

核心语义:

  • All-or-nothing 绑定:调度器为整组 Pod 做预留式假设调度(assume),凑齐 minMember 才批量绑定,凑不齐则全部回退。
  • 超时回退scheduleTimeoutSeconds 防止一个永远凑不齐的组无限占用调度器预留,这是很多自研 Gang 方案踩过的坑。
  • 与 DRA 协同:组调度决策会把 ResourceClaim 的可满足性纳入考量,避免"Pod 能放下但设备分不出来"的二次死锁。

4.3 这对 Volcano/Kueue 生态意味着什么

我不认为这是"上游杀死第三方"的故事,至少短期不是。v1alpha2 目前只覆盖了 Gang 语义的最小集,而 Volcano 的队列管理、公平共享、抢占策略,Kueue 的多集群配额、工作负载排队,都是上游还没碰的领域。

更现实的解读是分层收敛:Gang 原语下沉进核心调度器,队列与配额策略留在上层组件。Kueue 社区已经在讨论基于 PodGroup API 重构底层实现——未来你可能同时用 Kueue 管队列、用核心调度器做组绑定,各取所长。

对普通用户的建议:Alpha 阶段别上生产,但如果你正在自研训练平台,现在就应该按这套 API 的语义设计抽象层,避免深度绑定第三方调度器的私有 CRD,两年后迁移时会感谢自己。


五、其他值得注意的变化

5.1 ServiceAccount 令牌外部签名 GA

ServiceAccount 令牌的签名私钥终于可以托管到外部 KMS(Key Management Service)了。此前私钥以文件形式躺在 control plane 节点磁盘上,属于"审计必挑、整改无门"的老大难。GA 之后:

  • 私钥可放入 HSM 或云厂商 KMS,kube-apiserver 通过外部签名接口调用;
  • 支持不重启 apiserver 的密钥轮换;
  • 满足金融/政企场景"密钥不落盘"的合规硬要求。

对多数团队这是无感变化,但对做混合云托管控制面的平台团队,这是把控制面安全等级拉齐到云厂商水准的关键一块拼图。

5.2 SELinux 卷标签优化 GA

挂载卷时的 SELinux relabel 从"逐文件 chcon"改为 mount option 级别的批量打标(-o context=)。对包含百万小文件的卷,挂载耗时从分钟级降到秒级。RHEL 系用户的 Pod 启动慢问题解药之一。

5.3 监控指标重命名

两个指标改名,用了自定义告警的注意同步修改:

volume_operation_total_errors  →  volume_operation_errors_total
etcd_bookmark_counts           →  etcd_bookmark_total

看似小事,但如果你的容量告警恰好压在旧指标上,升级后告警会静默失效——这类问题比告警误报危险十倍。建议在升级前用 promtool 对告警规则文件做一轮指标存在性检查。


六、破坏性变更清单:升级前必读

v1.36 的"刀"主要藏在这几处:

6.1 gitRepo 卷永久禁用

gitRepo 卷类型(在 Pod 里直接 clone git 仓库)因 CVE-2024-10220 等一系列安全问题,v1.36 起永久禁用,无特性门控可回退。替代方案:

# 旧写法(v1.36 起 apiserver 直接拒绝)
volumes:
- name: repo
  gitRepo:
    repository: https://github.com/example/config.git

# 新写法:initContainer 显式 clone
initContainers:
- name: git-sync
  image: registry.k8s.io/git-sync/git-sync:v4.4.0
  args:
  - --repo=https://github.com/example/config.git
  - --root=/repo
  - --one-time
  volumeMounts:
  - name: repo
    mountPath: /repo
volumes:
- name: repo
  emptyDir: {}

升级前先扫一遍存量:

kubectl get pods -A -o json | \
  jq -r '.items[] | select(.spec.volumes[]?.gitRepo) | "\(.metadata.namespace)/\(.metadata.name)"'

6.2 kubeadm 移除 FlexVolume 支持

FlexVolume 这个 CSI 之前时代的遗产被 kubeadm 正式扫地出门。还在用 FlexVolume 驱动的(一些老牌商业存储的早期对接方案),要么升级到厂商的 CSI 驱动,要么自己维护定制 KCM 镜像——后者基本等于给自己挖坑,别选。

6.3 Service.spec.externalIPs 弃用

externalIPs 字段进入弃用周期。这个字段长期是安全审计的重点关照对象(MITM 攻击向量,CVE-2020-8554),用 LoadBalancer 类型 Service 或 Gateway API 替代即可。

6.4 Ingress NGINX 退役的余波

虽然退役公告是 2026 年 3 月发出的,但 v1.36 是退役后的第一个大版本,生态影响开始实质显现:新集群不应再部署 Ingress NGINX。迁移目标按场景选择:

  • 生产环境主力:Gateway API + Envoy Gateway / Contour
  • 中小集群图省事:Traefik(同时支持 Ingress 与 Gateway API,可渐进迁移)
  • 已深度使用 NGINX 注解的:评估 ingate(社区接续项目)或商业发行版

七、升级实战:一份可执行的 checklist

结合我在测试集群的升级经历,给一份按顺序执行的清单:

升级前(T-2 周)

# 1. 扫描废弃 API 与危险配置
kubectl get pods -A -o json | jq -r '.items[] | select(.spec.volumes[]?.gitRepo) | .metadata.name'
kubectl get svc -A -o json | jq -r '.items[] | select(.spec.externalIPs) | .metadata.name'

# 2. 检查容器运行时版本(User Namespaces 依赖)
kubectl get nodes -o custom-columns=NAME:.metadata.name,RUNTIME:.status.nodeInfo.containerRuntimeVersion

# 3. 告警规则指标改名检查
grep -rE 'volume_operation_total_errors|etcd_bookmark_counts' /etc/prometheus/rules/

升级中

  1. 先升 control plane(apiserver → controller-manager → scheduler),观察 30 分钟;
  2. 按 10% → 50% → 100% 的节奏滚动升级节点池,AI 节点池放最后(DRA 驱动需要配套升级);
  3. 每批次升级后跑一次冒烟:Pod 创建、PV 挂载、Service 连通性、GPU 分配。

升级后(T+1 周)

  1. 灰度启用 hostUsers: false:从无状态、非特权工作负载开始;
  2. 评估 ImageVolume 替代现有 initContainer 数据分发方案;
  3. GPU 集群启动 DRA 迁移 PoC,验证 Extended Resource Mapping 的兼容性。

八、总结:Kubernetes 的"成熟期哲学"

看完 v1.36,我最大的感受是 Kubernetes 进入了一种"成熟期哲学":不再追求造新概念,而是把多年欠账逐一还清

  • User Namespaces 四年转正,还的是容器安全模型的历史欠账;
  • DRA 体系化毕业,还的是资源模型跟不上硬件演进的欠账;
  • 原生 Gang Scheduling,还的是调度器只见树木不见森林的欠账;
  • ImageVolume GA,则是对"OCI 镜像作为通用分发格式"这个生态趋势的顺水推舟。

对工程师来说,这种版本其实是最值得投入时间研究的——炫酷的新概念可能两年后就凉了,而这些"还欠账"的特性,每一个都会在你的集群里运行五年、十年。

给不同角色的行动建议,一句话版:

  • 平台工程师:把 hostUsers: false 排进本季度的准入策略路线图;
  • AI Infra:DRA 迁移 PoC 立项,Gang Scheduling API 纳入平台抽象层设计;
  • SRE:告警指标改名和 gitRepo 扫描,今天就可以做;
  • 架构师:重新审视数据分发链路,ImageVolume + Registry 生态可能比你自建的方案更省心。

稳,不代表无聊。v1.36 的每一个 GA 背后,都是社区几年如一日地打磨协作链路的结果——这大概就是基础设施软件该有的样子。

推荐文章

25个实用的JavaScript单行代码片段
2024-11-18 04:59:49 +0800 CST
12个非常有用的JavaScript技巧
2024-11-19 05:36:14 +0800 CST
7种Go语言生成唯一ID的实用方法
2024-11-19 05:22:50 +0800 CST
程序员茄子在线接单