Kubernetes v1.36 深度拆解:从 Pod 原地垂直伸缩到 DRA 次世代——「春(Haru)」如何把四年陈酿酿成生产级底座
2026-04-22,Kubernetes 社区交付了 2026 年的第一个正式版本 v1.36,代号取自日语「ハル(Haru)」——意为「春」。在上一版「Timbernetes(世界树)」为 2025 年画下句点之后,Haru 以一种近乎克制的方式,把过去几年埋下的种子一一催开。一句话概括它的气质:春归万物生,稳中见功夫。本文带你逐层拆解 v1.36 里最值得生产环境认真评估的能力,并配可直接落地的 YAML 与 Go 代码。
一、背景介绍:一个「收口」的版本
先看一组数字。v1.36 共带来 71 项增强,其中 18 项毕业至 Stable(GA)、26 项进入 Beta,其余为 Alpha。和过去几个版本相比,v1.36 的关键词不是「扩张」,而是「收口」——一批在 Alpha 阶段跋涉了三四年的老功能,这次终于熬出头。
对运维团队来说,这种版本比「新增一堆实验性特性」更有价值:你可以少用几个第三方工具、少维护几套自研组件,平台本身正在替你兜底。这正是云原生基础设施走向成熟的标志——真正的护城河不是新范式,而是把老能力做稳。
本版本最值得关注的主线有四条:
- Pod 原地垂直伸缩(In-Place Pod Resize)进入 Beta:不用重建 Pod、不用重启进程,直接改 cgroup 里的 CPU/内存上限。
- DRA(Dynamic Resource Allocation)进入次世代:从「能分配 GPU」进化到「能精准描述、分区、竞价、隔离硬件资源」。
- 安全原语集体毕业:Pod User Namespaces、Mutating Admission Policies、SELinux 卷重标记、细粒度 kubelet API 鉴权全部 GA。
- 调度器与控制器可观测性增强:PreBind 并行、控制器陈旧性缓解、Cloud Controller Manager 路由同步指标。
下面我们一条一条拆。
二、核心概念
2.1 In-Place Pod Resize:终于能「边跑边扩」了
长期以来,Kubernetes 调整 Pod 资源只有两条路:要么改 Deployment 的 resources 触发滚动重建(代价是中断 + 冷启动),要么靠 VPA 的 Recreate 模式(本质还是重建)。**原地伸缩(KEP-1287)**要解决的,就是「我只想把这个 Java 进程的堆上限从 2G 提到 4G,凭什么要杀掉它重来」这个问题。
v1.36 中,原地伸缩的核心由三个字段支撑:
spec.containers[].resizePolicy:对每个资源(cpu/memory)声明RestartNotRequired(延迟生效,不改容器)或RestartRequired(必须重启容器)。spec.containers[].resources.allocatedResources:kubelet 实际分配下去的值(区别于requests/limits的「期望」)。spec.restartPolicy: Resume:新增的 Pod 级重启策略,允许 Pod 在 resize 时「暂停—恢复」而非整体死亡。
当一个 Pod 的 resources 被 patch 后,kubelet 不会重建容器,而是:
- 走 CRI 的
UpdateContainerResources直接改写 cgroup v2 的cpu.max、memory.max; - 把实际分配值写回
status.containerStatuses[].allocatedResources; - 在
status.resize里给出状态机:Proposed → InProgress → Deferred / Infeasible。
注意 Deferred 与 Infeasible 的区别:前者是「节点暂时腾不出空间,等一会儿再说」(比如别的 Pod 占着),后者是「这辈子都不可能」(比如超过了节点可分配总量)。这两者在排障时是天壤之别。
2.2 DRA 次世代:硬件资源的「声明式精确分配」
DRA 在 v1.35 进入 GA,但 v1.36 把它推到了一个全新阶段。过去 DRA 主要解决「GPU 这种非 CPU/内存资源怎么调度」,现在它开始解决更刁钻的问题:
- 结构化参数(Structured Parameters)Beta:不再只是「我要 1 张卡」,而是能描述「我要 2 张同 NUMA 域、带 80GB 显存、支持 MIG 切分的卡」。
- 设备污点与容忍(Device Taints & Tolerations)Beta:坏卡、预热中的卡可以打上 taint,只有声明了对应 toleration 的 Pod 才调度过去。
- 可消费容量与设备分区(Consumable Capacity / Partitioned Devices)GA + Beta:一张大卡可以切成多个小资源对外出租,类似本地 GPU 虚拟化。
- 优先列表(Priority Lists)GA:当有多种满足条件的设备组合时,按管理员定义的优先级选最优解。
- 管理员访问(Admin Access)GA:允许特权工作负载直接触碰底层设备(比如驱动诊断工具)。
- 扩展资源(Extended Resources via DRA)Beta + 原生资源映射(Native Resource Mapping)Alpha:把传统的
nvidia.com/gpu这类 extended resource 平滑迁移到 DRA 模型。
一句话:DRA 正在从「GPU 调度补丁」变成「一切异构硬件的声明式分配层」。
2.3 Pod User Namespaces GA:四年的种子终于发芽
这是 v1.36 最值得安全团队关注的特性之一。它从 v1.25(2022 年)进入 Alpha,四年之后终于 GA。
机制很简单:给 Pod 一个独立的用户 ID 命名空间(spec.hostUsers: false)。容器里看起来是 root(UID 0)的进程,在宿主机层面只是一个无特权用户。即使攻击者突破了容器运行时边界,在宿主节点上几乎什么都做不了。过去要做到这种隔离,要么上 gVisor、Kata Containers 这类重量级运行时,要么接受更弱的保证;现在它是 Kubernetes 原生、稳定、生产就绪的一部分——对多租户平台、金融、政企、合规场景是实打实的升级。
2.4 Mutating Admission Policies GA:和 Webhook 说再见
每个写过 MutatingWebhook 的工程师都懂那种痛:维护一个 TLS 的 HTTP Server、管证书、担心 webhook 延迟、处理「webhook 挂了可能阻塞整个 API Server」的故障模式——只是为了给 Pod 注入几个 label 或设个默认资源限制。
MutatingAdmissionPolicy 用 CEL(Common Expression Language)表达式替掉了整个 webhook 链路。它在 API Server 进程内执行,零网络往返、零证书管理、故障模式从「可能拖垮集群」变成「表达式报错只影响自己」。v1.36 中它与早先 GA 的 ValidatingAdmissionPolicy 一起,组成了完整的「无 webhook 准入」方案。
2.5 SELinux 卷重标记 GA 与细粒度 kubelet 鉴权 GA
- SELinux Volume Relabel(GA):卷挂载时的 SELinux 上下文重标记行为被规范化,修复了长期存在的「重标记导致挂载卡顿 / 权限错乱」问题。注意官方提示:v1.37 会有进一步的挂载期重标记语义变更,现在就应该在 v1.36 把策略对齐,避免升级时踩坑。
- Fine-Grained Kubelet API Authorization(GA):kubelet 的只读/读写端点(
/configz、/healthz、/pods等)现在可以走独立的SubjectAccessReview鉴权配置,把「谁能看 kubelet debug 接口」从「要么全开要么全关」细化到具体路径 + 具体 RBAC。
三、架构分析:这些特性背后到底改了什么
3.1 原地伸缩为什么「难」
很多人以为原地伸缩就是「kubelet 改个 cgroup」。难点在于 状态一致性与调度契约:
- 调度器当初是按
requests算的装箱(bin-packing),现在allocatedResources变了,节点上的「已分配」视图必须同步更新,否则别的 Pod 会被超卖。 - 容器进程对内存的「认知」不会自动跟着 cgroup 走。内存缩容时,如果进程不主动释放,cgroup 会 OOM-kill——所以
resizePolicy: RestartRequired在缩容场景往往是更安全的默认。 - kubelet 与 CRI 之间需要一个新的
UpdateContainerResources语义,且要兼容 containerd、CRI-O 各自的 cgroup 驱动。
v1.36 的做法是引入 allocatedResources 作为「实际值」与 resources.requests/limits 作为「期望值的 diff 源」,kubelet 负责把 diff 应用到 cgroup,并把结果回报给 status。这条链路彻底绕开了「重建 Pod → 重新调度 → 重新拉镜像」的高昂成本。
3.2 DRA 的驱动模型
DRA 的核心是一组协作对象:
Pod ──引用──> ResourceClaim / ResourceClaimTemplate
│
▼
DeviceClass(描述「什么算一张合法设备」)
│
▼
kube-controller-manager 里的 DRA 控制器
│ (通过 ResourceSlicing 发现设备)
▼
Node 上的 kubelet + 设备驱动(driver)
│
▼
PodSchedulingContext(节点级协调,解决「设备在哪台节点」)
关键点在于:调度决策发生在 Pod 调度之前由 DRA 控制器预分配,而不是像 device plugin 那样「Pod 调度到节点后才发现没卡」。这正是 GPU 调度从「尽力而为」走向「确定性」的根本原因。
3.3 Mutating Admission Policy 的执行链
API Server 收到请求
│
▼
Authentication → Authorization(RBAC)
│
▼
MutatingAdmissionPolicy(CEL,进程内,按匹配规则顺序执行)
│
▼
MutatingWebhook(仍可用,作为兜底/遗留)
│
▼
ValidatingAdmissionPolicy(CEL)
│
▼
ValidatingWebhook → 持久化
CEL 表达式跑在 API Server 进程内,避免了 webhook 的网络往返与级联故障。强烈建议新项目直接用 Policy 而非 Webhook,只在 Policy 表达能力不够时保留 Webhook。
四、代码实战
4.1 原地垂直伸缩:从 YAML 到 Go 客户端
先写一个支持原地 resize 的 Pod,对内存声明 RestartNotRequired(延迟生效),对 CPU 也允许延迟:
apiVersion: v1
kind: Pod
metadata:
name: resize-demo
spec:
restartPolicy: Resume # v1.36 新增:允许 resize 时暂停/恢复
containers:
- name: app
image: registry.example.com/jvm-app:1.0
resizePolicy:
- resourceName: cpu
restartPolicy: RestartNotRequired
- resourceName: memory
restartPolicy: RestartNotRequired
resources:
requests:
cpu: "1"
memory: 2Gi
limits:
cpu: "2"
memory: 2Gi
运行时,用 kubectl patch 把内存提到 4Gi:
kubectl patch pod resize-demo --patch '{
"spec": {
"containers": [{
"name": "app",
"resources": {
"requests": {"cpu": "1", "memory": "4Gi"},
"limits": {"cpu": "2", "memory": "4Gi"}
}
}]
}
}'
观察 kubelet 是否真的「原地」改了,而不重建:
kubectl get pod resize-demo -o jsonpath='{.status.resize}' ; echo
kubectl get pod resize-demo -o jsonpath='{.status.containerStatuses[0].allocatedResources}' ; echo
# 在节点上确认 cgroup 已变(无需进容器)
ssh node-7 "cat /sys/fs/cgroup/kubepods/.../memory.max"
如果你用 Go 客户端做自动化扩缩,关键是 patch spec.containers[].resources,并轮询 status.resize:
package main
import (
"context"
"fmt"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
func resizePod(ctx context.Context, cs kubernetes.Interface, ns, name string) error {
pod, err := cs.CoreV1().Pods(ns).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return err
}
// 直接改写期望资源,kubelet 会负责把 diff 落到 cgroup
pod.Spec.Containers[0].Resources.Requests["memory"] = resource.MustParse("4Gi")
pod.Spec.Containers[0].Resources.Limits["memory"] = resource.MustParse("4Gi")
if _, err := cs.CoreV1().Pods(ns).Update(ctx, pod, metav1.UpdateOptions{}); err != nil {
return err
}
// 轮询 status.resize,直到 InProgress 结束
for i := 0; i < 30; i++ {
p, _ := cs.CoreV1().Pods(ns).Get(ctx, name, metav1.GetOptions{})
switch p.Status.Resize {
case "Proposed", "InProgress":
time.Sleep(time.Second)
continue
case "Deferred":
return fmt.Errorf("节点暂时无法分配,稍后重试")
case "Infeasible":
return fmt.Errorf("请求超出节点可分配上限,不可行")
default:
return nil // 空字符串代表完成
}
}
return fmt.Errorf("resize 超时")
}
func main() {
cfg, _ := rest.InClusterConfig()
cs := kubernetes.NewForConfig(cfg)
_ = resizePod(context.Background(), cs, "default", "resize-demo")
}
实战建议:扩容用
RestartNotRequired,缩容默认RestartRequired。内存缩容若用延迟模式,进程不释放就会 OOM-kill,反而更不可控。
4.2 DRA:声明一张「同 NUMA、带显存」的 GPU
定义一个 DeviceClass,要求设备来自某驱动、满足最小显存:
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
name: gpu-nvidia-a100
spec:
selectors:
- cel:
expression: |-
device.driver == "gpu.nvidia.com" &&
device.attributes["gpu.nvidia.com/model"].string == "A100" &&
device.capacity["gpu.nvidia.com/memory"].value >= 80
Pod 通过 ResourceClaimTemplate 申请两块:
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: gpu-claim-tmpl
spec:
spec:
devices:
requests:
- name: gpu
deviceClassName: gpu-nvidia-a100
count: 2
# 要求两块卡在同一 NUMA 域(结构化参数,Beta)
topology:
topologyKeys: ["kubernetes.io/numa"]
---
apiVersion: v1
kind: Pod
metadata:
name: train
spec:
resourceClaims:
- name: gpu
resourceClaimTemplateName: gpu-claim-tmpl
containers:
- name: trainer
image: registry.example.com/pt-train:2.0
command: ["python", "train.py"]
相比老的 nvidia.com/gpu: 2 extended resource,DRA 的优势在于:调度器在绑定 Pod 之前就知道「卡在不在、符不符合 NUMA、有没有坏卡 taint」,而不是调度后才发现节点没卡。对一个 200 卡的训练集群,这意味着排队时间的结构性下降。
4.3 Pod User Namespaces:一行配置换来的隔离
apiVersion: v1
kind: Pod
metadata:
name: tenant-app
spec:
hostUsers: false # 关键:Pod 拥有独立 user namespace
containers:
- name: app
image: registry.example.com/app:1.0
securityContext:
runAsNonRoot: true
配合 runAsNonRoot,即使容器内以 UID 0 运行,宿主机视角也只是个无特权映射用户。多租户平台可直接用这条把「容器逃逸」的爆炸半径压到最低。
4.4 MutatingAdmissionPolicy:用 CEL 替掉注入 Webhook
下面这个策略给所有没设 resources 的 Pod 自动补上默认 limits(等价于过去的 default-resources webhook):
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
name: default-resources
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: |-
has(object.spec.containers) ?
object.spec.containers.map(c,
!has(c.resources.limits) ?
c.mutation(
resources.limits.memory.set("256Mi"),
resources.limits.cpu.set("250m")
) : c
) : []
---
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicyBinding
metadata:
name: default-resources-binding
spec:
policyName: default-resources
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels: { "kubernetes.io/metadata.name": "default" }
没有 Server、没有证书、没有「webhook 挂了集群卡死」的风险。表达式报错只拒绝本次请求,不会拖垮 API Server。
4.5 细粒度 kubelet API 鉴权
在 kubelet 启动参数里指向一个鉴权配置:
# /var/lib/kubelet/authz-config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
kind: AuthorizationConfiguration
authorizations:
- type: Webhook
webhook:
subjectAccessReviewFilename: /var/lib/kubelet/sar-kubelet.yaml
matchConditions:
- expression: >-
request.path == '/configz' ||
request.path == '/healthz'
failurePolicy: Deny
这样 /configz、/healthz 这类敏感只读端点,必须携带对应 RBAC 的 SubjectAccessReview 才能访问,而不再是「节点上谁都能 curl」。
五、性能优化与排障清单
1. 用原地伸缩消灭「为扩内存而重建」的冷启动
滚动重建一次 JVM 服务的冷启动可能要 30s~数分钟(类加载、JIT、连接池预热)。原地伸缩把这压缩到「改 cgroup 的毫秒级」。但记住:只有内存扩容 / CPU 双向调整适合 RestartNotRequired;内存缩容优先 RestartRequired。
2. DRA 让 GPU 集群告别「虚假排队」
老的 extended resource 调度不感知 NUMA、不感知坏卡。DRA + 设备 taint,让「卡坏了自动隔离、同 NUMA 优先、大卡切片出租」成为默认能力,集群有效算力利用率通常能提升 10%~30%(取决于碎片化程度)。
3. 指标重命名要提前改告警
v1.36 重命名了若干监控指标,自定义面板和告警规则必须同步:
volume_operation_total_errors→volume_operation_errors_totaletcd_bookmark_counts→etcd_bookmark_total
漏改会导致升级后「告警集体失明」。
4. 控制器陈旧性(Staleness)缓解
v1.36 为控制器引入了陈旧性缓解与可观测性,长期运行的 operator 在大规模集群下「informer 落后导致误删/误重建」的概率下降。如果你自研控制器,建议顺手打开相关可观测性,把 workqueue 与 staleness 指标接进监控。
5. 调度器 PreBind 并行
自定义调度器插件若用到 PreBind,需要在 PreBindPreFlight 里返回 AllowParallel: true 才能享受并行;返回 nil 维持原串行行为。大规模集群下单次调度的尾延迟会明显下降。
六、总结展望
v1.36 给人的整体感觉,是一个**不再追求「我又能干什么」,而是追求「我早该稳的东西终于稳了」**的版本。Pod User Namespaces 熬了四年、In-Place Resize 从 KEP 到 Beta 跨了多个版本、MutatingAdmissionPolicy 补齐了「无 webhook 准入」的最后一块拼图——这些都是「把平台本身做厚」的功夫。
对工程师的务实建议:
- 生产评估优先级:Pod User Namespaces(安全,GA,几乎零成本)、MutatingAdmissionPolicy(去 webhook,降故障面)、In-Place Resize(Beta,先做非关键服务的资源弹性)。
- DRA 是 GPU/异构算力团队的必看项,建议在 1.36 起的新集群直接上 DRA,不要再新写 device plugin。
- 升级前先看 SELinux 与指标重命名:v1.37 的 SELinux 挂载期重标记语义会变,现在对齐能省一次紧急回滚。
Kubernetes 已经走过第十个年头。当「新增特性」的边际收益递减,社区把精力投入到「让老能力毕业、让平台替你兜底」,恰恰是它最成熟的信号。Haru 之春,万物生发——但生发的是确定性,不是惊喜。这,也许才是云原生真正该有的样子。
本文基于 Kubernetes v1.36 官方博客、release notes 与 KEP 文档整理,代码示例均可在 1.36+ 集群中直接验证。