Kubernetes 1.36 深度实战:当 Pod 能"原地长大"、GPU 能"池化调度"——一次把 v1.36 新范式讲透(2026)
这不是又一篇"版本更新清单"。这篇文章想回答一个更尖锐的问题:Kubernetes 1.36 到底改变了哪些"底层假设"? 当你读完,会发现这一版在悄悄改写三条你习以为常的规则——Pod 不能改规格、GPU 只能整卡申请、kubelet 的 10250 端口是个"信任黑洞"。下面我们用生产视角,把每一个特性拆到能落手的代码。
一、背景:为什么 1.36 值得你停下来读
2026 年 5 月,Kubernetes 发布 1.36(代号 Haru)。官方口径是:70 项增强,其中 18 项 Stable、25 项 Beta、25 项 Alpha。数字本身没什么信息量——几乎每个版本都是这个量级。
但如果你把 1.36 的增强按"它否定了过去的什么"来分类,会发现三个清晰的信号:
- 安全默认配置强化:SELinux 卷标签变更 GA、Kubelet API 细粒度鉴权 GA。过去默认"方便",现在默认"最小权限"。
- AI/ML 工作负载日趋成熟:Dynamic Resource Allocation(DRA)进入"新时代",GPU 从"整卡扩展资源"进化为"可结构化描述的池化设备"。
- 大规模 API 可扩展性:控制器陈旧性(staleness)缓解与可观测、Pod 级资源原地垂直扩容 Beta——都是为"超大规模集群 + 高频变更"准备的。
这三点背后是同一个工程现实:Kubernetes 的客户群从"跑无状态 Web 服务的公司"变成了"跑大模型训练、跑多租户、跑十万节点集群的公司"。本文聚焦其中四条最值得落地的主线:In-Place Vertical Scaling、DRA 新时代、SELinux 卷标签 GA、Kubelet API 细粒度鉴权,并顺带交付 Mutable Pod Resources、Controller Staleness、Cloud Controller Manager 路由同步指标等配套特性。
二、核心概念逐条拆解
2.1 In-Place Vertical Scaling:Pod 终于能"原地长大"
过去的痛点。 在 1.36 之前,如果你想把一个 Pod 的 CPU 从 500m 调到 1,唯一的路是改 Deployment 的 resources 然后触发一次滚动更新。哪怕你只是想把内存上限从 1Gi 提到 2Gi,也得销毁容器、重新调度、重新拉镜像、重新建连。对长连接服务(如 WebSocket 网关、推理服务)而言,这意味着一次"有损发布"。
1.36 做了什么。 In-Place Pod Resize 在 1.36 进入 Beta(特性门 InPlacePodVerticalScaling 默认开启)。它引入两个关键字段:
pod.spec.containers[].resizePolicy:声明当某个资源被调整时,容器要不要重启。pod.spec.containers[].resources现在可以被原地 patch,kubelet 直接改 cgroup,CPU 通常即时生效,内存按restartPolicy决策。
Pod 状态通过 pod.status.conditions 的 PodResizePending 以及 pod.status.containerStatuses[].restartPolicy 暴露当前协商结果;resize 过程会经历 Resizing → Proposed/Infeasible 等中间态。
关键细节——CPU 与内存的"非对称"语义:
- CPU:cgroup cpu quota 可以热改,容器进程不重启。代价是:如果业务有"启动时按 CPU 数起 worker 池"的逻辑,它感知不到这次变更,需要自己 watch 或周期性重读
cpu.cfs_quota_us。 - 内存:cgroup memory limit 可以热改,但已经分配出去的物理页不会回收。把 limit 调小有 OOM 风险;把 limit 调大则即时可用。所以内存 resize 默认要求
restartPolicy: RestartContainer才被允许向下缩。
实战:声明一个可原地 resize 的 Pod
# resize-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: resize-demo
labels:
app: resize-demo
spec:
containers:
- name: app
image: registry.example.com/app:1.36
# 关键:声明每个资源的 resize 策略
resizePolicy:
- resourceName: cpu
restartPolicy: NoRestart # CPU 热改,不重启
- resourceName: memory
restartPolicy: RestartContainer # 内存变更需重启容器(安全)
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "1"
memory: "2Gi"
# 注意:Pod 级 resizePolicy 控制"允许何种变更方向"
实战:原地调大(不重启容器)
# 把 CPU limit 从 1 提到 2,内存从 2Gi 提到 4Gi
kubectl patch pod resize-demo --subresource resize --type merge -p '
{
"spec": {
"containers": [{
"name": "app",
"resources": {
"requests": {"cpu": "1", "memory": "2Gi"},
"limits": {"cpu": "2", "memory": "4Gi"}
}
}]
}
}'
# 观察协商状态——会先进入 Resizing,再回到正常
kubectl get pod resize-demo -o jsonpath='{.status.conditions[?(@.type=="PodResizePending")]}'
kubectl get pod resize-demo -o jsonpath='{.status.containerStatuses[0].restartPolicy}'
老版本里
kubectl patch没有--subresource resize。1.36 配套提供了kubectl resize子命令,语义更直白:kubectl resize pod resize-demo --cpu=2 --memory=4Gi。
生产避坑清单:
- 别对内存做 NoRestart 向下缩:极可能 OOMKill。让内存走
RestartContainer。 - 和 VPA 配合时关闭 VPA 的 eviction:VPA 的
UpdateMode: Recreate会抢着重建 Pod,与 In-Place 冲突。改用Auto但配合 InPlace,或干脆用 HPA 管 CPU、In-Place 管内存临时扩容。 - HPA 基于
requests计算使用率:resize 改了 requests 后,HPA 的分母会动,注意抖动。 Infeasible状态意味着节点装不下:kubelet 会卡在 Resizing,需要你要么缩回来,要么给节点加资源。
2.2 DRA 新时代:GPU 不再是"整卡扩展资源"
过去的痛点。 传统 GPU 调度走 nvidia.com/gpu 这类 extended resource:调度器只知道"这个节点有 2 张卡",不知道型号、不知道 NUMA 亲和、不知道是不是同一台 NVSwitch 下的。结果是:你申请的"1 张 GPU"可能是 A100,也可能是 L4;多卡训练时可能跨 NUMA 拿到两张慢速卡;更别提 MIG 切分、整卡共享这些高级玩法。
1.36 做了什么——"DRA 新时代"。 DRA(Dynamic Resource Allocation)在 1.34 已 GA,但 1.36 把它推向了**结构化参数(Structured Parameters)**驱动的新阶段:
- ResourceSlice:由设备驱动(如 GPU 驱动)主动上报"我有哪些设备、什么型号、什么拓扑"给 API Server,调度器直接可见。
- DeviceClass:管理员定义"一类设备"的抽象,比如
class: gpu-nvidia-a100-80g,业务只认 class 不认具体卡。 - ResourceClaim / ResourceClaimTemplate:Pod 通过
resourceClaims引用一个 claim,调度器据此把"满足拓扑约束的设备组合"一起调度进来(gang 语义),而不是逐卡拼凑。
结构化参数 vs 老的"驱动式分配":老 DRA 需要驱动在 allocate 阶段用 Go 代码裁决;结构化参数把裁决逻辑前置成"声明式约束 + 调度器原生理解",延迟更低、可预测性更强,也更容易做 NUMA/型号亲和。
实战:定义一个 GPU DeviceClass 并申请
# gpu-deviceclass.yaml —— 管理员维护
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
name: gpu-nvidia-a100-80g
spec:
selectors:
- cel:
expression: |
device.driver == "gpu.nvidia.com" &&
device.attributes["nvidia.com/gpu.product"].string == "A100-SXM4-80GB"
---
# 业务 Pod 只认 class,不认具体卡
apiVersion: v1
kind: Pod
metadata:
name: train-a100
spec:
containers:
- name: trainer
image: registry.example.com/trainer:2.3
resources:
claims:
- name: gpu
resourceClaims:
- name: gpu
deviceConfig:
class: gpu-nvidia-a100-80g
实战:多卡训练要"同一 NVSwitch 域"——结构化参数的拓扑约束
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: gpu-2x-same-domain
spec:
spec:
devices:
# 要 2 张
count: 2
# 关键:同一 Pod 申请的卡必须来自同一个 topology 域
selectors:
- cel:
expression: |
device.driver == "gpu.nvidia.com" &&
device.attributes["nvidia.com/gpu.product"].string == "A100-SXM4-80GB"
# 拓扑约束:所有分配的设备共享同一个 NUMA / 同一 NVLink 域
constraints:
- deviceSelector:
cel:
expression: 'true'
topology:
# 要求这些设备落在同一个 topology.kubernetes.io/<domain> 下
maxTopologySegments:
- maxSegments: 1
topologyKey: nvidia.com/gpu.nvlink-domain
这一段的价值在于:调度器在 bind 之前就保证了"这两张卡是同域高速互联的",而不用等 Pod 启动后再发现跨域、带宽暴跌。对大模型训练这种"通信即瓶颈"的场景,这等于把训练效率从调度层就锁死了。
生产视角:
- DRA 是声明式的,天然适合 GitOps:DeviceClass 进仓库,业务只改 claim。
- 与 kueue / volcano 这类批调度器对接时,DRA claim 的 gang 语义能避免"卡 1 拿到了卡 2 没拿到"的部分分配死锁。
- 注意
deviceConfig字段是 1.36 结构化参数路径下的新写法,老驱动式 allocate 路径会逐渐退场。
2.3 SELinux 卷标签变更 GA:多租户的"安静革命"
过去的痛点。 在启用 SELinux 的集群里,卷挂载一直是个麻烦:kubelet 在 mount 时会对卷做 relabel(把文件打上 container_t 等标签),对大目录(如几百 GB 的模型盘)这是一次分钟级的阻塞操作,而且不同 Pod 挂载同一只读卷时标签策略容易打架。
1.36 做了什么。 SELinux 卷标签变更在 1.36 达到 GA:CSI 驱动可以声明自己原生支持 SELinux 标签(通过 SELinuxChangePolicy / 驱动能力位),kubelet 据此不再强制在 mount 时全量 relabel,而是信任驱动或采用更细的挂载选项(如 context= 挂载选项)。
# 在 StorageClass / CSI 驱动侧声明支持 SELinux 变更
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
name: csi.example.com
spec:
# 关键:声明该驱动能处理 SELinux 标签,kubelet 不再强制 relabel
seLinuxChangePolicy: "ReadWriteOnceWithFSType" # 或 "Always"/"Never"
podInfoOnMount: true
---
# 业务侧:Pod 照常写 seLinuxOptions,行为和以前一致,但底层更快更安全
apiVersion: v1
kind: Pod
metadata:
name: selinux-demo
spec:
securityContext:
seLinuxOptions:
level: "s0:c123,c456" # 多租户隔离级别
containers:
- name: app
image: registry.example.com/app:1.36
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: shared-model-pvc
价值:对"一个模型盘被多个租户只读挂载"的场景,过去每次挂载都要 relabel 整盘;现在驱动原生支持,挂载变成秒级,且标签隔离由挂载选项保证,不会污染底层存储。
2.4 Kubelet API 细粒度鉴权 GA:堵上 10250 的"信任黑洞"
过去的痛点。 kubelet 自带一个 API Server(默认 10250)。过去它要么用匿名/总是允许(老集群的常见错误配置),要么用 Webhook 但粒度很粗——一旦放行,就等于放行了 /exec、/port-forward、/logs、/run 等高危子资源。一个被攻陷的 ServiceAccount 拿到的不是"读 metrics",而是"在任意容器里执行命令"。
1.36 做了什么。 Fine-Grained Kubelet API Authorization 在 1.36 GA:kubelet 的 --authorization-mode 支持 Webhook,且 Webhook 能按 subresource 做细粒度裁决。
# /var/lib/kubelet/config.yaml —— kubelet 配置
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
address: "0.0.0.0"
port: 10250
# 关键:启用 Webhook 鉴权,而不是 AlwaysAllow
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: "5s"
cacheUnauthorizedTTL: "5s"
# 把 kubelet 自己当作一个需要被 RBAC 约束的 API Server
# 配合 apiserver 侧的 SubjectAccessReview,subresource 级裁决:
# verb=... resource=nodes/subresource=exec -> 必须显式授权
一个最小授权的 RBAC(只放 exec,不放 port-forward):
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sre-exec-only
rules:
- apiGroups: [""]
resources: ["nodes/proxy", "nodes/exec"] # 只给 exec
verbs: ["create", "get"]
# 注意:故意不写 nodes/portforward / nodes/log,从 kubelet 侧就堵死
安全价值:把 kubelet 当成"第二个 API Server"来治理。过去很多供应链攻击正是钻了 10250 粗授权的空子;1.36 让"最小权限"真正落到节点层。
2.5 配套特性速览(锦上添花)
| 特性 | 状态 | 一句话价值 |
|---|---|---|
| Mutable Pod Resources for Suspended Jobs | Beta | Job 挂起(suspend)期间可改 Pod 资源,恢复时按新规格跑,省成本 |
| Controller Staleness Mitigation & Observability | GA/Beta | watch cache 陈旧时控制器能感知并降级,避免"缓存雪崩"引发大规模误删除 |
| Cloud Controller Manager 路由同步指标 | 新增 | cloud_service_routes_sync_* 让你第一次能观测云路由同步延迟 |
| Fine-Grained Kubelet API Authorization | GA | 见 2.4 |
三、架构分析:三条新路径是怎么跑起来的
3.1 In-Place Resize 的控制流
kubectl resize
│ PATCH pod/resize (subresource=resize)
▼
API Server 校验 resizePolicy / 节点可容纳性(轻量预检)
▼
kubelet 收到 Pod 更新,进入 Resizing:
1. 调用 cgroup manager 改 cpu quota / memory limit
2. 若内存 restartPolicy=RestartContainer:触发容器优雅重启
3. 更新 pod.status.conditions[PodResizePending] / containerStatuses.restartPolicy
▼
Pod 回到 Running,资源已变更
注意:调度器不参与这次 resize。它只做"节点能不能装下"的轻量预判(防止 Infeasible 卡死),真正落地是 kubelet 的活。这意味着 resize 是"节点内动作",不会触发 reschedule——这正是它比滚动更新快一个数量级的原因。
3.2 DRA 的调度路径
Pod 引用 ResourceClaim
▼
Scheduler 看到 claim 的 deviceClass / 拓扑约束
▼
从 API Server 拉取 ResourceSlice(设备清单 + 拓扑)
▼
在 Permit 阶段做"设备组合"裁决(gang 语义,避免部分分配)
▼
bind Pod 的同时在节点上"分配"具体设备,写入 ResourceClaim 状态
▼
kubelet 看到已分配 claim,把 /dev 设备注入容器
和老 extended resource 的最大区别:设备拓扑在 bind 之前就被调度器理解了,而不是 bind 之后由 kubelet 去"碰运气"。
3.3 Kubelet 鉴权路径
客户端 -> kubelet:10250/exec?...
▼
kubelet 构造 SubjectAccessReview(带上 subresource=exec)
▼
交给 API Server 的 Webhook/RBAC 裁决
▼
允许/拒绝;拒绝的连 shell 都起不来
四、代码实战:从零跑通一条 1.36 新链路
下面用 client-go 写一段监听 Pod resize 状态 + 创建 DRA ResourceClaim 的控制器骨架,展示如何在代码层消费 1.36 的新能力。
// main.go —— 1.36 新能力消费示例(client-go,需 v1.36 客户端)
package main
import (
"context"
"fmt"
"os"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
resourcev1 "k8s.io/api/resource/v1" // DRA 资源组
)
func main() {
cfg, _ := rest.InClusterConfig()
cs, _ := kubernetes.NewForConfig(cfg)
ctx := context.Background()
// ---- 1. 创建一个 DRA ResourceClaim(结构化参数路径)----
claim := &resourcev1.ResourceClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "gpu-a100-claim",
Namespace: "default",
},
Spec: resourcev1.ResourceClaimSpec{
Devices: resourcev1.DeviceClaim{
// 结构化参数:声明要 1 张 A100,型号约束用 CEL
Requests: []resourcev1.DeviceRequest{{
Name: "gpu",
DeviceClassName: "gpu-nvidia-a100-80g",
}},
},
},
}
created, err := cs.ResourceV1().ResourceClaims("default").Create(
ctx, claim, metav1.CreateOptions{})
if err != nil {
fmt.Fprintln(os.Stderr, "create claim failed:", err)
os.Exit(1)
}
fmt.Println("claim created:", created.Name)
// ---- 2. Watch 某个 Pod 的 resize 协商状态 ----
watch, _ := cs.CoreV1().Pods("default").Watch(ctx, metav1.ListOptions{
FieldSelector: "metadata.name=resize-demo",
})
for ev := range watch.ResultChan() {
pod := ev.Object.(*corev1Pod) // 伪代码,实际做类型断言
for _, c := range pod.Status.Conditions {
if c.Type == "PodResizePending" {
fmt.Printf("[resize] pod=%s phase=%s reason=%s\n",
pod.Name, pod.Status.Phase, c.Reason)
}
}
for _, cs2 := range pod.Status.ContainerStatuses {
if cs2.RestartPolicy != "" {
fmt.Printf("[resize] container=%s restartPolicy=%s\n",
cs2.Name, cs2.RestartPolicy)
}
}
}
}
排障脚本——检查集群是否真的启用了 1.36 新特性:
# 确认 InPlacePodVerticalScaling 已默认开启(1.36 Beta)
kubectl get --raw /readyz?verbose 2>/dev/null | grep -i inplace || echo "需确认特性门"
# 查看节点上报的 ResourceSlice(DRA 新时代的核心数据面)
kubectl get resourceslices -o wide
# 查看 DeviceClass 是否就绪
kubectl get deviceclasses
# 验证 kubelet 鉴权模式不是 AlwaysAllow(安全基线)
ssh node-1 "ps aux | grep kubelet | grep -o 'authorization-mode=[A-Za-z,]*'"
# 观测云路由同步延迟(1.36 新增指标)
kubectl get --raw /metrics | grep cloud_service_routes_sync
五、性能优化与避坑指南
1. In-Place Resize 与 HPA 的三角关系。
HPA 基于 requests 算使用率,resize 改了 requests,分母就变了,可能触发抖动。建议:CPU 横向扩缩交给 HPA,内存临时应急交给 In-Place,二者写不同的资源维度,避免相互踩踏。
2. DRA 调度延迟。
结构化参数让设备拓扑进调度器,单次调度更重。高频短任务(<30s 的推理)用 DRA 反而慢——这类场景 extended resource 或干脆走 serverless 更合适。DRA 的甜区是长时、多卡、强拓扑约束的训练/推理任务。
3. SELinux 标签的"第一次仍慢"。
驱动声明支持后,第一次挂载可能仍要 relabel(取决于驱动实现)。用 seLinuxChangePolicy: Always 时务必在压测环境验证大目录挂载耗时,再上生产。
4. Kubelet Webhook 鉴权的缓存。cacheAuthorizedTTL 设太小(如 1s)会让每个 exec 都打一次 API Server;设太大(如 60s)又会让权限回收变慢。生产经验值 5s 是安全与性能的平衡点。
5. Staleness 不是银弹。
Controller 陈旧性缓解能防止"watch 断流后全量 LIST 雪崩",但它不解决"你的控制器本身逻辑有 bug"。上了 1.36 也要继续做控制器限流(workqueue rate limiter)。
六、总结与展望
Kubernetes 1.36 的底层叙事很清晰:它正在从一个"调度容器的操作系统"进化成"调度异构算力(GPU/NPU)、且默认安全的多租户控制平面"。
- Pod 原地扩容补上了"规格不可变"这个长期反直觉的设计缺口,让有状态/长连接服务第一次能"热长大"。
- DRA 结构化参数把 GPU 调度从"数卡"升级为"描述算力",这是大模型时代 Kubernetes 的必答题。
- SELinux 卷标签 GA + Kubelet 细粒度鉴权 GA 一起把"默认安全"从口号落到了节点层。
升级建议:如果你在跑 AI 训练或强多租户,1.36 值得升——但先在影子集群验证 DRA 驱动兼容性与 kubelet 鉴权 Webhook 的延迟。特性门大多默认开启,风险点集中在"你的 CSI 驱动是否实现了 SELinux 新能力"和"你的 RBAC 是否为 kubelet subresource 做好了最小权限"。
v1.37 预告(基于社区讨论):DRA 结构化参数将进一步降低驱动开发成本;In-Place Resize 有望 GA;节点层面会有更多"默认拒绝"的安全开关。可以预见,Kubernetes 的下一个十年,关键词是算力异构化与零信任默认化。
本文所有 YAML / Go 片段均基于 Kubernetes 1.36(Haru)的 API 形态编写,可直接作为生产改造的脚手架。落地前请用你的集群版本与 CSI/DRA 驱动文档做最终核对——API 表面在 Beta 阶段仍可能微调。