Kubernetes 1.36 深度拆解:当容器学会「隐身」——从 User Namespaces GA、DRA 可消费容量到 PodGroup Gang 调度的工程全貌(2026)
标签:Kubernetes | K8s 1.36 | User Namespaces | DRA | 动态资源分配 | Gang 调度 | 云原生 | AI 工作负载 | 容器安全
关键词:Kubernetes 1.36, User Namespaces, Dynamic Resource Allocation, DRA, PodGroup, 容器安全, 云原生, AI 工作负载, GPU 调度, kubeadm 升级
栏目:编程(cid=1)
如果你过去两年一直在用 Kubernetes 跑业务,大概率会有这样一种体感:K8s 的版本号蹭蹭往上涨,但每次升级好像「没什么感知」——直到 1.36 这一版。
2026 年 5 月,Kubernetes 社区发布了代号 Haru 的 1.36 版本。这是 2026 年的首个重要版本,包含 70 项增强功能:18 项进入 Stable、25 项进入 Beta、25 项仍为 Alpha。表面看又是一次例行发布,但如果你把这一版的增强按「主题」聚类,会发现一条清晰的主线:K8s 正在从「能跑容器的编排器」加速蜕变成「AI 原生时代的底层操作系统」。
这条主线体现在三个维度:
- 安全默认配置强化:用户命名空间(User Namespaces)正式 GA,容器内核级别的隔离能力被默认打开。
- AI / ML 工作负载支持日趋成熟:动态资源分配(DRA)迎来一波「上强度」式的增强,GPU、NPU、FPGA 这些加速卡的调度终于有了像样的原语。
- 大规模 API 可扩展性:Workload / PodGroup API 进入 alpha,原生 Gang 调度开始从第三方控制器(Volcano、KubeBatch)向核心靠拢。
这篇文章不堆版本说明,我们直接下钻到工程实现层:User Namespaces 在内核里到底怎么映射 uid、DRA 的三件套对象怎么配合、为什么分布式训练必须 Gang 调度、以及升级 1.36 时那些「无声但致命」的破坏性变更。每个点都配可运行的 YAML / 代码片段。
注:本文涉及的部分 API(尤其是 scheduling.k8s.io/v1alpha2 的 Workload/PodGroup)仍处于 alpha,字段可能随版本演进。文中示例以 1.36 的设计意图为准,生产落地前请以官方 API Reference 为准。
一、背景:为什么 1.36 是云原生的一个分水岭
先看一张增强分布的速览表:
| 维度 | 数量 | 代表特性 |
|---|---|---|
| Stable (GA) | 18 | User Namespaces、DRA 可消费容量、DRA 优先列表、DRA 管理员访问 |
| Beta | 25 | DRA 设备绑定条件、DRA 设备污点与容忍、DRA 分区设备 |
| Alpha | 25 | DRA 原生资源映射、DRA 列表类型属性、Workload/PodGroup API |
单看数字没什么冲击,但把时间轴拉长来看,K8s 的重心迁移非常明显。20232024 年的版本(1.271.31)主题还是「稳定性、可维护性、传统工作负载打磨」;而从 1.32 开始,DRA 结构化参数 GA、DeviceClass 成型,到 1.36 这波 DRA 增强集中落地,加速卡调度已经从「补丁式 workaround」变成「一等公民」.
先说发布节奏这个容易被忽略的事实。Kubernetes 自 1.19 起固定为「每 4 个月一个大版本、每个大版本维护约 14 个月」的火车制发布(release train),一年三个大版本。这意味着 1.36 并非孤立的一次性改动,而是沿着 1.32(DRA 结构化参数 GA)、1.34、1.35 逐步累积后的一个「量变到质变」节点。社区把最激进的破坏性变更尽量放在 alpha/beta 里灰度,GA 的东西往往已经在前面两三个版本里被验证过。理解这一点很重要:你升级 1.36 时踩到的坑,大概率不是 1.36 新引入的,而是前几个版本埋下、到 1.36 才默认翻转的开关。
这背后是真实的需求压力:当一家公司 80% 的新 GPU 预算都拿去跑大模型训练和推理,K8s 如果还只能用 nvidia.com/gpu: 1 这种「整数个整卡」的粗粒度资源来调度,那 GPU 利用率会惨不忍睹——一张 80GB 显存的 H100 被一个只需要 20GB 的推理服务独占,剩下的 60GB 就白白浪费。1.36 的 DRA 增强正是冲着这个问题来的。
而 User Namespaces GA,则是对另一个老大难问题的回应:容器逃逸。过去十几年,安全团队最怕的就是「容器内 root 逃逸到宿主机 root」,1.36 用一行 securityContext 配置,把这个问题的爆炸半径大幅压缩。
下面我们逐个拆解。
二、User Namespaces GA:让容器「隐身」的安全革命
2.1 Linux user namespace 到底是什么
要理解 K8s 的 User Namespaces,得先回到 Linux 内核。
在 Linux 里,每个进程都有一个 uid(用户 ID)和 gid(组 ID)。传统容器(比如用 docker run 起的容器)做了文件系统、网络、进程的隔离(mount/network/pid namespace),但默认不做用户隔离:容器内跑的 root 用户,uid 就是 0,它在宿主机内核眼里,和宿主机的 root(uid 0)是同一个人。
这就是为什么容器逃逸这么可怕——一旦攻击者通过内核漏洞从容器内跳出,拿到的是宿主机 uid 0 的完整权限。
User namespace 是内核提供的另一种隔离维度。它的核心能力是 uid/gid 映射:在一个 user namespace 内部,进程可以认为自己 uid 是 0(容器内的 root),但内核在真正做权限检查时,会把它翻译成宿主机上的另一个 uid(比如 100000)。映射关系写在 /proc/<pid>/uid_map 里,形如:
# 格式:<容器内uid> <宿主机起始uid> <映射数量>
0 100000 65536
意思是:容器内的 uid 065535,依次映射到宿主机的 uid 100000165535。容器内看着自己是 root,实际上在宿主机只是个普通高 uid 用户。这就是「隐身」的由来。
用一个具体数字感受一下。假设宿主机为 user namespace 分配的映射区间是 0 100000 65536,那么容器内的 uid 0、1、2…65535 会依次平移到宿主机的 100000、100001、100002…165535。你可以在宿主机上用 cat /proc/<pid>/uid_map 直接看到这张三段式映射表。关键在于:宿主机的「真实 root」是 uid 0,而容器内那个 root 在宿主机眼里是 uid 100000——它既不是 uid 0,也不在任何 sudoer 列表里,更没有 CAP_SYS_ADMIN 这类特权能力。换句话说,user namespace 没有改变「容器内进程觉得自己是 root」的观感(很多软件启动时会检查 getuid()==0),但彻底改变了「宿主机内核如何看待它」的权限。(注:映射区间通常由 CRI 运行时从 /etc/subuid、/etc/subgid 读取,kubelet 侧会为每个 Pod 切分一段不重叠的范围,避免不同 Pod 的 uid 互相踩踏。)
2.2 一个逃逸场景,看清楚收益
假设宿主机上一个进程通过内核漏洞逃逸出容器。在「没有 user namespace」的世界里:
容器内 uid 0 ──逃逸──▶ 宿主机 uid 0 ──▶ 完整控制节点
在「开了 user namespace」的世界里:
容器内 uid 0 ──逃逸──▶ 宿主机 uid 100000 ──▶ 只是个普通用户,没有特权
即便逃逸成功,攻击者也只是宿主机上一个「高 uid、无特权」的普通账户,拿不到节点控制权。这就是 User Namespaces 的核心价值:把「逃逸」的爆炸半径从「整机 root」降到「一个普通用户」。
2.3 K8s 1.36 怎么用:UserNamespacesPodSecurityStandards
在 1.36 中,User Namespaces 通过 UserNamespacesPodSecurityStandards 这个 Feature Gate 与 Pod Security Standards(PSS)集成,并达到 GA。开启后,你可以在 Pod 级别声明使用 user namespace。
一个最小可用示例:
apiVersion: v1
kind: Pod
metadata:
name: userns-demo
spec:
securityContext:
# 让 K8s 为这个 Pod 自动分配一个 user namespace
namespaceOptions:
user: Auto
containers:
- name: app
image: nginx:stable
command: ["sleep", "infinity"]
securityContext:
# 容器内仍以 root 运行,但被映射到宿主机非特权 uid
runAsNonRoot: false
关键点解释:
spec.securityContext.namespaceOptions.user: Auto:告诉 K8s「给我这个 Pod 分配一个独立的 user namespace,自己挑一段宿主机 uid 范围做映射」。- 容器里
id命令会显示uid=0(root),但cat /proc/self/uid_map能看到真实的映射(比如0 100000 65536)。 - 宿主机上
ps看到的这个进程,owner 是100000级别的普通用户,不是 root。
你可以用这条命令验证映射:
# 进容器看自己「以为」的 uid
kubectl exec userns-demo -- id
# uid=0(root) gid=0(root) groups=0(root)
# 在宿主机看这个进程真实的 uid(假设容器 PID 在宿主机是 12345)
cat /proc/12345/uid_map
# 0 100000 65536
2.4 收益与边界
收益:
- 容器逃逸后攻击者只是宿主机普通用户,无法触碰节点上其他 Pod 的存储、无法操作节点网络栈。
- 和传统「以非 root 用户运行容器」(
runAsNonRoot: true) 互补:很多遗留镜像硬编码要 root,你没法改镜像,但可以用 user namespace 把「伪 root」关进笼子。
边界(务必了解,否则会踩坑):
- 运行时支持:需要 containerd / CRI 运行时支持 user namespace 映射,老版本 Docker(非 containerd 模式)或某些自定义运行时可能不支持。
- 与特权容器互斥:
privileged: true的容器无法使用 user namespace,因为特权容器需要宿主机的真实 root。 - hostPath / 主机资源:如果 Pod 挂载了
hostPath或使用了主机 PID/IPC namespace,user namespace 的隔离会被削弱甚至失效。 - PSS 集成:
UserNamespacesPodSecurityStandards让 user namespace 自动满足 baseline/restricted 的部分约束,但要注意它会改变 Pod 的 securityContext 评估逻辑。
2.5 实战:从「特权降级」到 user namespace
一个典型迁移场景:某遗留服务需要以 root 启动初始化脚本,但又不想给宿主机真实 root。
改造前(高危):
spec:
containers:
- name: legacy
image: legacy-service:1.0
securityContext:
privileged: true # 危险:容器内 root == 宿主机 root
改造后(安全):
spec:
securityContext:
namespaceOptions:
user: Auto
containers:
- name: legacy
image: legacy-service:1.0
# 不再需要 privileged,容器内 root 被映射到宿主机非特权 uid
迁移检查清单:
- 确认运行时版本支持 user namespace(containerd ≥ 对应版本)。
- 确认没有
privileged: true与 user namespace 混用。 - 确认没有强依赖宿主机的真实 root 能力(如修改宿主机 iptables、加载内核模块)。
- 用
kubectl exec进容器跑id和cat /proc/self/uid_map验证映射生效。 - 在 staging 集群灰度一周,观察是否有依赖真实 uid 0 的逻辑(如写
/proc/sys)。
三、DRA 深度拆解:GPU 与加速卡的「新调度范式」
如果说 User Namespaces 是安全线,那 DRA(Dynamic Resource Allocation,动态资源分配)就是 1.36 在「算力调度」这条线上的主菜。
3.1 旧世界的痛:Device Plugin 与 Extended Resources
在 DRA 之前,K8s 调度 GPU 的方式是 Device Plugin + Extended Resources。大致流程:
- 节点上的
nvidia-device-plugin向 kubelet 注册,声明本机有nvidia.com/gpu: 8。 - kubelet 把这些「扩展资源」上报给 API Server。
- 用户写
resources.limits: { nvidia.com/gpu: 1 },调度器按整数分配。
这套机制能用,但有三个根本性的缺陷:
- 粒度太粗:只能按「整卡」分配。要 20GB 显存,对不起,请拿走一整张 80GB 的 H100。
- 无法表达拓扑:两张卡是否在同一个 NVLink 域、是否跨 NUMA 节点,调度器一无所知,只能靠节点标签(node label)手工标注,极易出错。
- 无法表达亲和/反亲和与部分分配:想要「这 4 张卡必须在同一个 NVSwitch 下」「这个任务只要半张卡」,原生不支持。
结果就是:GPU 集群名义利用率很高,真实利用率(MFU,模型算力利用率)经常只有 30%~50%,剩下全浪费在碎片化上。
举个真实的排障场景。某推理平台有 8 张 H100,单卡 80GB 显存。线上有两类任务:一类是「大模型推理」,单实例稳定吃掉整张卡(因为框架默认按整卡申请);另一类是「小模型批处理」,其实只需要 20GB。由于扩展资源只能整数分配,小模型任务也不得不占满整张卡,于是 8 张卡被 8 个小模型占满,大模型推理全部 Pending。运维同学一看监控:「GPU 占用率 100%,但业务 QPS 上不去」——典型的「占而不用」。DRA 的可消费容量正是为这种场景设计的:把一张卡的算力/显存声明成可切分的量,让多个小模型共享一张卡,把真实利用率从 30% 拉到 70%+。
3.2 DRA 架构三件套
DRA 的核心思想是:把「设备」当作一等资源对象,由设备驱动(driver)自己描述设备的能力和拓扑,由用户用声明式 API 申请,由调度器做结构化匹配。它引入了三个核心对象(API 组 resource.k8s.io,1.32 起 GA):
1) ResourceSlice —— 节点上报「我有什么设备」
apiVersion: resource.k8s.io/v1beta1
kind: ResourceSlice
metadata:
name: gpu-slice-worker-01
labels:
node: worker-gpu-01
spec:
nodeName: worker-gpu-01
driver: gpu.example.com
pool:
name: gpu-pool
resourceSliceCount: 1
devices:
- name: gpu-0
attributes:
- name: "gpu.example.com/model"
value: { string: "H100" }
- name: "gpu.example.com/memoryGB"
value: { int: 80 }
capacity:
- name: "gpu.example.com/cores"
value: 80
- name: gpu-1
attributes:
- name: "gpu.example.com/model"
value: { string: "H100" }
- name: "gpu.example.com/memoryGB"
value: { int: 80 }
capacity:
- name: "gpu.example.com/cores"
value: 80
注意 attributes 和 capacity 的区别:attributes 是设备的「静态描述」(型号、显存大小),capacity 是「可被消费的量」(比如可切分的算力核心数)。
2) DeviceClass —— 定义「我要什么类型的设备」
用 CEL 表达式做选择器,类似 nodeSelector 但针对设备属性:
apiVersion: resource.k8s.io/v1beta1
kind: DeviceClass
metadata:
name: gpu-h100
spec:
selectors:
- cel:
expression: device.attributes["gpu.example.com/model"] == "H100"
3) ResourceClaim / ResourceClaimTemplate —— 「消费方」的申请
apiVersion: resource.k8s.io/v1beta1
kind: ResourceClaimTemplate
metadata:
name: h100-claim
spec:
spec:
devices:
requests:
- name: gpu
deviceClassName: gpu-h100
---
apiVersion: v1
kind: Pod
metadata:
name: train-job
spec:
resourceClaims:
- name: gpu
resourceClaimTemplateName: h100-claim
containers:
- name: trainer
image: pytorch:2.6-cuda12.4
resources:
claims:
- name: gpu
command: ["python", "train.py"]
Pod 通过 resourceClaims 引用 ResourceClaimTemplate,调度器在调度时会先匹配 DeviceClass,再在 ResourceSlice 里找满足属性的设备,分配成功后把设备信息注入到容器里(通过 /dev 设备节点 + 环境变量)。
3.3 配合 PyTorch 训练任务的完整链路
DRA 注入的 GPU 以 /dev/nvidia* 形式出现,PyTorch 通过 CUDA_VISIBLE_DEVICES 自动感知;多卡通信交给 NCCL:
# train.py —— DRA 注入的 GPU 由 CUDA_VISIBLE_DEVICES 暴露
import os, torch, torch.distributed as dist
local_rank = int(os.environ.get("LOCAL_RANK", 0))
torch.cuda.set_device(local_rank)
dist.init_process_group(backend="nccl")
model = torch.nn.Linear(1024, 1024).cuda()
for step in range(100):
loss = model(torch.randn(1024, 1024).cuda()).sum()
loss.backward()
关键洞察:DRA 负责「把正确的 GPU 分给正确的 Pod」,NCCL/PyTorch 负责「在分配到的 GPU 上通信」,两者解耦,设备驱动不再硬编码进调度逻辑。
3.4 1.36 给 DRA 上了强度:六项增强
1.36 对 DRA 的增强,几乎每一项目标都是「让 GPU 调度更细、更稳、更像云原生资源」。逐一拆解:
(1) 可消费容量 GA(Consumable Capacity)—— 把一张卡切成多份
这是最能直接提升利用率的特性。此前 DRA 设备基本是「整设备分配」,1.36 让设备的 capacity 可以被「按量消费」。
想象一张 80GB 显存的卡,现在可以声明 80 个单位的 cores 容量,然后两个推理 Pod 各申请 40:
# ResourceSlice 里声明可消费容量
spec:
devices:
- name: gpu-0
capacity:
- name: "gpu.example.com/cores"
value: 80 # 总共 80 份
# 推理 Pod A 只申请 40 份
spec:
devices:
requests:
- name: gpu
deviceClassName: gpu-h100
selectors:
- cel:
expression: >
device.capacity["gpu.example.com/cores"] >= 40
两个 Pod 各自拿到 40 份,跑在同一张物理卡上,显存碎片问题迎刃而解。对推理场景(显存需求小、并发高)这是直接的生产力提升。
(2) 分区设备 Beta(Partitionable Devices)
与可消费容量互补:允许把一个物理设备「逻辑分区」后分别分配给不同 Pod,驱动层负责隔离(比如 MIG 式的 GPU 切片)。
(3) 设备绑定条件 Beta(默认启用)
让设备在「真正绑定到 Pod」时才做最终的可用性判断,避免调度器在预选阶段就因为瞬时状态误判。默认启用,对 GPU 这类「绑定后才初始化」的设备尤其重要。
(4) 设备污点与容忍 Beta(Device Taints & Tolerations)
类比节点污点(node taints):某些设备可以被「打污点」,只有声明了对应容忍的 Pod 才能用。典型场景:
# 把一张「故障频发但还能用」的卡打上污点
spec:
devices:
- name: gpu-flaky
taints:
- key: "gpu.example.com/unstable"
value: "true"
effect: NoSchedule
# 只有声明了容忍的「容错训练」Pod 才会被调度上去
spec:
devices:
requests:
- name: gpu
tolerations:
- key: "gpu.example.com/unstable"
operator: Equal
value: "true"
effect: NoSchedule
(5) 优先列表 GA(Priority List)
当一个设备类有多个可用设备时,允许用「优先列表」告诉调度器「优先挑拓扑最优的那个」。比如优先挑和 CPU 同 NUMA 域的 GPU,降低跨 NUMA 访存延迟。
(6) 管理员访问 GA(Admin Access)
允许集群管理员在设备被 Pod 占用时,仍保有对该设备的管理访问权限(比如在线诊断、热重置),而不必驱逐 Pod。对大规模 GPU 集群的运维是刚需。
(7) 原生资源映射 Alpha(Native Resource Mapping)
前瞻特性:把设备的算力「映射」成 K8s 原生的 CPU / 内存资源,让标准调度器也能基于这些映射做通用的资源账本。
(8) 列表类型属性 Alpha(List-typed Attributes)
允许设备属性是「列表」而非「单值」,比如一张卡支持的多个 CUDA 架构 [sm_80, sm_90],让选择器能表达「支持其中任意一个」。
把上面这些特性串起来,1.36 的 DRA 已基本能支撑细粒度 GPU 虚拟化调度:细粒度共享(可消费容量+分区设备)、拓扑感知(优先列表+attributes)、故障隔离(设备污点)、运维友好(管理员访问)。这本质上是把过去 Volcano、KubeBatch 等项目「自己造的轮子」往 K8s 核心收敛,意味着更少的自建控制器、更一致的 API。
四、Workload 与 PodGroup:为分布式训练而生的 Gang 调度
4.1 为什么要 all-or-nothing
GPU 集群最经典的浪费场景之一:分布式训练的部分调度。
假设你要跑一个 8 卡的数据并行训练,提交 8 个 Pod。调度器挨个调度,结果集群里只剩 7 张空闲卡——7 个 Pod 起来了,第 8 个卡在 Pending。但这 7 个 Pod 因为凑不齐 8 卡,NCCL 初始化永远等不到第 8 个 rank,全部卡死,白白占着 7 张卡不放,还阻塞了其他任务。
把这个场景拆解成时间线会更刺眼:T0 提交 8 卡任务;T1T7 七个 Pod 陆续调度成功并拉起,开始初始化 NCCL;T8 第 8 个 Pod 因资源不足一直 Pending;T9T∞ 前 7 个 Pod 在 dist.barrier() 上无限等待第 8 个 rank,GPU 显存被占满却零计算产出。更糟的是,这 7 张卡被标记为「已分配」,其他更小的任务也调度不进来,整段 GPU 拓扑被死锁。Gang 调度要解决的,就是这种「凑不齐就别占」的原子性。
这就是 Gang Scheduling( gang 调度 / 全有或全无调度) 要解决的问题:要么 8 张卡一次性凑齐、8 个 Pod 同时启动;要么一个都别起,把资源让给别人。
过去 K8s 原生没有这个能力,大家靠 Volcano、KubeBatch 这样的 CRD 控制器实现。1.36 把相关能力以 scheduling.k8s.io/v1alpha2 的 Workload / PodGroup API 形式引入核心(alpha)。
4.2 K8s 原生解法:scheduling.k8s.io/v1alpha2
设计意图是引入一个 Workload 对象来聚合一组 Pod,并声明 minAvailable(最少可用副本数)和 scheduleTimeoutSeconds(调度超时):
# 注意:以下为 1.36 alpha 设计意图示意,字段可能演进
apiVersion: scheduling.k8s.io/v1alpha2
kind: Workload
metadata:
name: dist-training
spec:
# 超过这个时间还没凑齐 minAvailable,则整体放弃,释放已占用资源
scheduleTimeoutSeconds: 3600
podGroups:
- name: workers
minAvailable: 8
template:
spec:
containers:
- name: trainer
image: pytorch:2.6-cuda12.4
resources:
claims:
- name: gpu
它的价值在于:调度器在决策时以 Workload 为整体单位,8 个 Pod 要么一起被调度、要么一起 Pending,杜绝「半吊子占用」。
与 DRA 协同的图景:
Workload (minAvailable=8)
├── Pod 1 ──resourceClaim──▶ DRA ──▶ GPU 拓扑最优分配
├── Pod 2 ──resourceClaim──▶ DRA
└── ... (8 个 Pod 一次性绑定)
Gang 调度保证「凑齐才跑」,DRA 保证「跑在最优硬件上」,两者一结合,分布式训练的资源利用率和稳定性同时提升。
再次提醒:Workload/PodGroup 在 1.36 仍是 alpha,生产环境建议继续用 Volcano 等成熟方案,同时关注核心 API 的演进,做好未来迁移的准备。
五、可观测性与运维变更:无声但致命
前面都是「新能力」,这一节讲「升级时会咬人的地方」。很多团队升级 K8s 后监控大面积飘红,不是因为业务挂了,而是因为指标名被改了。
5.1 指标重命名(破坏性)
1.36 重命名了若干监控指标,Prometheus / Grafana / 告警规则里写死旧名字会直接「无数据」:
| 旧指标名 | 新指标名 |
|---|---|
volume_operation_total_errors | volume_operation_errors_total |
etcd_bookmark_counts | etcd_bookmark_total |
迁移动作:升级前 grep 所有监控配置,升级后立刻验证新指标有数据:
grep -rn "volume_operation_total_errors\|etcd_bookmark_counts" prometheus/rules/ grafana/dashboards/
kubectl get --raw /metrics | grep "volume_operation_errors_total"
kubectl get --raw /metrics | grep "etcd_bookmark_total"
5.2 kubeadm 移除 FlexVolume 内置支持
1.36 中 kubeadm 移除了对 FlexVolume 的内置集成支持。FlexVolume 是 CSI(Container Storage Interface)之前的老存储插件机制,早已被官方标记为废弃。
影响:如果你的集群还在用基于 FlexVolume 的存储驱动(某些老旧的私有云存储插件),升级后 kubelet 可能无法加载这些插件。
迁移动作:
# 检查集群里是否还有 FlexVolume 的使用痕迹
kubectl get storageclass -o json | grep -i flexvolume
# 检查 kubelet 配置
grep -ri flexVolume /etc/kubernetes/ 2>/dev/null
如果确实有依赖,需要在升级前完成向 CSI 驱动的迁移,否则相关 PV/PVC 会在升级后处于不可用状态。
六、生产级升级指南
讲了这么多特性,落地才是硬道理。下面是一份面向 kubeadm 集群的 1.36 升级实操清单。
6.1 升级前检查清单
- 备份 etcd:
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%s).db - 扫描旧指标名(见 5.1),更新监控配置。
- 扫描 FlexVolume 依赖(见 5.2),完成 CSI 迁移。
- 确认运行时支持 User Namespaces(containerd 版本)。
- 检查自定义控制器是否依赖将被废弃的 alpha API。
- 在 staging 集群先行演练,跑一轮核心业务回归。
6.2 破坏性变更汇总
| 变更 | 类型 | 应对 |
|---|---|---|
volume_operation_total_errors → volume_operation_errors_total | 指标重命名 | 改监控配置 |
etcd_bookmark_counts → etcd_bookmark_total | 指标重命名 | 改监控配置 |
| kubeadm 移除 FlexVolume 内置支持 | 功能移除 | 迁移到 CSI |
| User Namespaces 默认行为与 PSS 集成变化 | 行为变化 | 验证安全策略 |
6.3 实操:kubeadm 升级脚本
控制平面节点逐个升级(生产环境务必逐节点、可灰度):
sudo apt-get update && sudo apt-get install -y kubeadm=1.36.0-00
sudo kubeadm upgrade plan # 预检,确认无阻断项
sudo kubeadm upgrade apply v1.36.0 # 控制平面升级(首节点)
sudo apt-get install -y kubelet=1.36.0-00 kubectl=1.36.0-00
sudo systemctl daemon-reload && sudo systemctl restart kubelet
# 逐个工作节点 drain + upgrade + uncordon
kubectl drain worker-01 --ignore-daemonsets --delete-emptydir-data
sudo apt-get install -y kubelet=1.36.0-00 && sudo systemctl restart kubelet
kubectl uncordon worker-01
6.4 回滚与灰度
- etcd 快照回滚:若升级后出现不可恢复的数据问题,用
etcdctl snapshot restore从 6.1 的备份恢复。 - 灰度:先升 1 个控制平面 + 1 个 worker,观察 24 小时核心指标与业务 SLA,再全量推进。
- 保留旧版本二进制:升级前把旧 kubeadm/kubelet 包缓存好,必要时
apt-get install -y kubelet=旧版本回退。
七、总结与展望:K8s 进入 AI-native 时代
把 1.36 的增强放回大图景,你会发现 K8s 的演进方向已经非常明确:
安全线——User Namespaces GA 把容器的内核级隔离从「可选补丁」变成「默认能力」。容器逃逸的代价从「丢整台节点」降到「丢一个普通用户」,这是云原生多租户隔离的关键一步。
调度线——DRA 的一波增强(可消费容量、分区设备、设备污点、优先列表)让 GPU/NPU 这些昂贵加速卡第一次有了「云原生级别的细粒度调度原语」。配合 Workload/PodGroup 的 Gang 调度,分布式训练的资源浪费将被系统性压缩。
运维线——指标重命名、FlexVolume 移除这类「无声变更」提醒我们:K8s 在进化的同时,对「版本纪律」的要求也越来越高。监控即代码、升级即演练,不再是口号。
展望未来几个值得持续跟踪的点:
- 设备虚拟化的终局:当 DRA 的「原生资源映射」「列表类型属性」从 alpha 毕业,GPU 可能像 CPU/内存一样被统一账本管理,「切卡」成为默认行为。
- 原生 AI 调度:Workload/PodGroup 若 GA,Volcano 等生存空间被大幅压缩,K8s 核心直接承载训练/推理编排。
- 多租户强隔离:User Namespaces + Kata/VM 隔离 + 设备污点,可能组合出「公有云级别」的多租户 GPU 共享方案。
一个更落地的判断:未来 12~18 个月内,主流 GPU 云大概率会把「单卡多实例 + 拓扑感知调度 + 命名空间级隔离」打包成默认产品能力,底层正是依赖 1.36 这批 DRA 增强。对中小团队而言,这意味着不用自己折腾 MIG 切片和自研调度器,也能以「按显存用量计费」的方式用上稀缺的 H 卡。
Kubernetes 1.36 不是一次炫技式的发布,它是 K8s 拥抱 AI 原生时代的一次「地基加固」。当你下次为 GPU 利用率上不去发愁、为容器逃逸担惊受怕时,回头看看这一版——答案可能已经在里面了。
参考来源
- Kubernetes v1.36 官方发布说明与 KEP 列表(User Namespaces、DRA、Workload API)
- Kubernetes 官方文档:Dynamic Resource Allocation、Pod Security Standards、User Namespaces
- 社区技术解析:Kubernetes 1.36 版本重大更新详解、K8s 云原生架构新特性详解(2026)
- Linux 内核文档:User Namespaces (
user_namespaces(7)) - Container Storage Interface (CSI) 迁移指南
(本文示例 YAML 字段以 1.36 设计意图为准,alpha 阶段 API 可能演进,落地前请以官方 API Reference 校验。)