Kubernetes 1.35 已将 Pod 原地调整资源能力推进到 GA:运行中的容器可以修改 CPU 和内存请求,而不必通过重建 Pod 才能生效。但这只解决了“如何调整”,没有完全解决“节点空间不足时怎么办”。
Kubernetes 1.37 引入 Alpha 特性 InPlacePodVerticalScalingSchedulerPreemption。当高优先级 Pod 的原地扩容因节点容量不足而进入 Deferred 状态时,调度器可以抢占同一节点上的低优先级 Pod,为扩容释放资源。
Deferred 不是失败,而是可能无限期等待
当用户或 Vertical Pod Autoscaler 修改运行中容器的资源请求时,Kubelet 会检查当前节点能否满足新的分配。结果大致可以分成两类:
Infeasible:请求本身无法满足,例如超过物理节点容量、LimitRange 或准入配额。Deferred:请求合法,但节点暂时没有足够的空闲资源。
问题在于,“暂时”可能持续很久。如果节点长期处于高利用率,关键服务即使急需更多内存来避免 OOM,也只能等待其他负载自然退出。过去,管理员通常只能手动驱逐 Pod、等待集群扩容,或者依赖定制控制器处理容量。这些做法要么需要人工介入,要么可能破坏原地扩容希望避免重启和迁移的初衷。
新机制让调度器开始跟踪已经绑定到节点、但存在 Deferred 调整请求的 Pod。虽然这类 Pod 已经设置了 spec.nodeName,调度器仍会将其纳入专门的调度评估,以判断是否需要抢占低优先级工作负载。
抢占只发生在当前节点
这种抢占和普通的首次调度抢占有一个关键区别:它不会在整个集群中寻找新节点。
原地调整的目标是保留正在运行的 Pod,因此调度器只检查该 Pod 当前所在的节点,并从该节点选择优先级更低的受害 Pod。低优先级 Pod 退出并释放资源后,Kubelet 才能落实新的 CPU 或内存配置。
这带来几个明确边界:
- 即使其他节点有空间,也不会为了这次原地调整迁移目标 Pod。
- 如果驱逐当前节点上所有符合条件的低优先级 Pod 后仍然不够,调整仍保持
Deferred。 - 调度器会将正在申请的新增资源视为已占用,避免释放出来的容量又被其他调度决策分配出去。
- 调整期间若同一节点出现优先级更高的新请求,Kubelet 和调度器会重新观察状态,并可能发起新一轮抢占。
职责也变得更清晰:Kubelet 负责判断资源是否足够并执行容器资源调整,调度器集中负责调整相关的抢占决策。启用该特性后,Kubelet 的 critical Pod admission handler 不会替原地调整执行本地抢占。
在 kind 中复现一次 CPU 抢占
以下实验假设本机已经安装 kind 和 kubectl,并且可用的 kind 节点镜像支持 Kubernetes 1.37。该功能仍是 Alpha,必须在控制面组件和 Kubelet 上启用对应 feature gate。
创建 kind-config.yaml:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
InPlacePodVerticalScalingSchedulerPreemption: true
创建集群并检查节点可分配 CPU:
kind create cluster \
--config kind-config.yaml \
--image kindest/node:v1.37.0
kubectl get nodes \
-o custom-columns=NAME:.metadata.name,ALLOCATABLE_CPU:.status.allocatable.cpu
下面的示例按节点拥有约 8 核可分配 CPU 设计。若你的输出不同,请相应修改 CPU 请求。创建 preemption-demo.yaml:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: High priority workload
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: low-priority
value: 1000
globalDefault: false
description: Low priority workload
---
apiVersion: v1
kind: Pod
metadata:
name: low-priority-pod
spec:
priorityClassName: low-priority
containers:
- name: worker
image: nginx
resources:
requests:
cpu: "3"
memory: 500Mi
limits:
cpu: "3"
memory: 500Mi
---
apiVersion: v1
kind: Pod
metadata:
name: high-priority-pod
spec:
priorityClassName: high-priority
containers:
- name: app
image: nginx
resources:
requests:
cpu: "4"
memory: 1Gi
limits:
cpu: "4"
memory: 1Gi
部署并等待两个 Pod 运行:
kubectl apply -f preemption-demo.yaml
kubectl wait --for=condition=Ready pod/low-priority-pod --timeout=120s
kubectl wait --for=condition=Ready pod/high-priority-pod --timeout=120s
kubectl get pods -o wide
两个 Pod 总计请求 7 核。将高优先级 Pod 从 4 核调整到 6 核,需要额外增加 2 核,超过剩余的约 1 核空间:
kubectl patch pod high-priority-pod \
--subresource=resize \
--type=merge \
--patch '{"spec":{"containers":[{"name":"app","resources":{"requests":{"cpu":"6","memory":"1Gi"},"limits":{"cpu":"6","memory":"1Gi"}}}]}}'
观察两个 Pod 的事件:
kubectl get events \
--field-selector=involvedObject.name=low-priority-pod \
--sort-by=.lastTimestamp
kubectl get events \
--field-selector=involvedObject.name=high-priority-pod \
--sort-by=.lastTimestamp
预期低优先级 Pod 出现 Preempted 和 Killing 事件,高优先级 Pod 则依次出现 ResizeDeferred、ResizeStarted 和 ResizeCompleted。最后检查容器实际获得的 CPU:
kubectl get pod high-priority-pod \
-o jsonpath='{.status.containerStatuses[0].allocatedResources.cpu}{"\n"}'
成功时结果应为 6,并且高优先级 Pod 没有因为本次资源调整而重建。
节点可以单独禁用调整抢占
某些节点由专门的容量控制器管理。控制器可能希望优先缩减其他 Pod 的资源,或者先动态扩大节点容量,而不是立即驱逐工作负载。Kubernetes 1.37 为此提供了节点级 spec.podPreemptionPolicy:
apiVersion: v1
kind: Node
metadata:
name: batch-workload-node
spec:
podPreemptionPolicy:
disableResizePreemption:
- cluster-autoscaler.kubernetes.io/disable-preemption
- operator.example.com/policy-override
这里的字符串可以用来记录禁用该行为的控制器或策略。具体使用前应在测试集群验证 API、控制器协作方式以及节点策略的生命周期,因为该能力仍处于 Alpha 阶段。
上线前需要确认的取舍
这项能力让平台可以更积极地使用低优先级批处理任务填充节点空闲资源,同时保留关键服务临时扩容的通道。不过,更高的利用率并不等于没有代价:扩容成功可能意味着其他 Pod 被终止,优雅退出也会带来释放资源的延迟。
试用时应重点检查:
- 控制面与所有工作节点是否都运行 Kubernetes 1.37 或更高版本,并一致启用 feature gate。
- PriorityClass 是否真实反映业务重要性,避免普通服务意外驱逐关键后台任务。
- 低优先级工作负载能否容忍终止、重试和重新调度。
- PDB、终止宽限期和应用退出逻辑是否符合预期。
- 监控是否覆盖
ResizeDeferred持续时间、抢占次数、调整完成率和 OOM。 - 对不能接受抢占的节点,是否明确设置节点级策略。
由于这是 Alpha 功能,更适合先在压测集群或非关键节点验证。重点不只是确认扩容最终成功,还要测量从 ResizeDeferred 到 ResizeCompleted 的时延,以及被抢占工作负载对队列积压和整体服务容量的影响。