Skip to content

Kubernetes v1.37:In-Place Pod Resize 的调度抢占(Alpha)——告别“Deferred”僵局

一句话摘要:Kubernetes v1.37 引入 InPlacePodVerticalScalingSchedulerPreemption(Alpha),首次赋予 scheduler 主动驱逐低优先级 Pod 以腾出资源、推动高优先级 Pod 原地扩容的能力。这不是“重启式扩缩容”的回归,而是对 v1.35 GA 的 In-Place Pod Resize 的关键补全——让 vertical scaling 真正具备生产级闭环调度语义。


背景动机:为什么 “Deferred” 是一个危险的静默失败?

In-Place Pod Resize(原地垂直扩缩容)自 v1.35 进入 GA,是 Kubernetes 垂直弹性演进的里程碑。它允许在不重建 Pod、不中断容器进程的前提下,动态调整 resources.requests/limits(仅限 CPU/memory),显著降低有状态服务(如数据库、消息队列、vLLM 推理服务)扩缩容的 MTTR 和业务抖动。

但现实很快暴露了一个结构性缺口:Resize ≠ Scheduling

Kubelet 负责执行 resize —— 它检查本节点是否还有足够 allocatable 资源(即 Node.Status.Allocatable - sum(Pod.Status.ContainerStatuses[].resources))。若不足,它不会报错,也不会重试,而是将容器的 resizeStatus 置为 Deferred

yaml
# kubectl get pod my-app -o yaml
status:
  containerStatuses:
  - name: main
    resources:
      requests:
        memory: "2Gi"
        cpu: "1000m"
    resizeStatus: Deferred  # ← 关键信号:合法请求,但卡住了

⚠️ 问题在于:Deferred 是 Kubelet 单点视角的“消极等待”。它不通知 scheduler,不触发任何协调动作,也不提供超时或重试机制。Pod 就这样“悬停”在半扩缩容状态,既非成功也非失败。对于 SLO 敏感型负载(如 SLA 99.99% 的金融 API、延迟敏感的 LLM 推理服务),这种不可观测、不可干预的停滞,比明确的 Failed 更危险——它掩盖了真实的资源瓶颈,误导容量规划,并可能引发级联雪崩(例如 VPA 持续上调 request,却永远无法落地)。

更讽刺的是:集群整体资源可能充足,只是当前 Node 被“钉死”了。比如:

  • Node A 负载 98%,运行 10 个 priorityClass=low 的批处理 Job;
  • Node B 负载 40%,但目标 Pod 因亲和性/污点被固定在 Node A;
  • 此时对 Node A 上一个 priorityClass=high 的在线服务发起 +1Gi 内存 resize → Deferred

这就是典型的“资源碎片化 + 调度语义缺失”困境。v1.37 的抢占机制,正是为打破这一僵局而生。


核心技术:Scheduler 如何“主动腾地儿”?

✅ 功能本质:将 resize 请求纳入全局调度决策流

启用 InPlacePodVerticalScalingSchedulerPreemption 特性门控后,scheduler 不再被动等待 Kubelet 的 Deferred 状态上报,而是主动监听 Pod status 变更事件,当检测到 containerStatuses[].resizeStatus == Deferred 且该容器属于 InPlaceVerticalScaling 场景时,立即启动抢占流程。

其核心逻辑与 Pod 调度抢占(如 PriorityClass 驱逐)高度一致,但作用域更精细:

维度传统 Pod 抢占In-Place Resize 抢占
触发条件新 Pod 调度失败(Pending现有 Pod 的 resizeStatus == Deferred
目标为新 Pod 腾出空间为现有 Pod 的 资源增长 腾出空间
驱逐对象低优先级 Pod 全部(或部分)同一 Node 上、priorityClass < targetPod.priorityClass 的 Pod(可配置策略)
粒度Pod 级别Pod 级别(但效果服务于容器级 resize)

🔧 启用与验证(v1.37 Alpha)

1. 开启特性门控(所有 control-plane 节点)

bash
# kube-scheduler 启动参数
--feature-gates="InPlacePodVerticalScalingSchedulerPreemption=true"

⚠️ 注意:此特性依赖 InPlacePodVerticalScaling(v1.35+ 默认开启)且要求集群启用 PodSchedulingReadiness(v1.36+)以确保抢占后新资源能被及时感知。

2. 定义清晰的 PriorityClass(必需!)

yaml
# priority-class-high.yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "For latency-critical online services"
---
# priority-class-low.yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: low-priority
value: 100
globalDefault: false
description: "For best-effort batch jobs"

3. 关键 YAML:让 resize 请求“可抢占”

yaml
# app-with-resize.yaml
apiVersion: v1
kind: Pod
metadata:
  name: llm-inference
  annotations:
    # 显式声明支持原地 resize(v1.35+ 必需)
    "kubernetes.io/allow-in-place-resize": "true"
spec:
  priorityClassName: high-priority  # ← 抢占决策依据!
  containers:
  - name: vllm-server
    image: vllm/vllm-openai:latest
    resources:
      requests:
        memory: "8Gi"   # 初始值
        cpu: "4000m"
      limits:
        memory: "16Gi"
        cpu: "8000m"
    # ... 其他配置

4. 触发 resize 并观察抢占行为

bash
# 使用 kubectl patch 或 VPA controller 修改 request
kubectl patch pod llm-inference -p '{
  "spec": {
    "containers": [{
      "name": "vllm-server",
      "resources": {
        "requests": {"memory": "12Gi", "cpu": "6000m"}
      }
    }]
  }
}'

✅ 成功时,你将在 scheduler 日志中看到类似记录:

I0911 10:24:32.123456       1 preempt.go:217] "Preempting pods for in-place resize" 
  pod="default/llm-inference" node="node-a" preemptedPods=["batch-job-123","batch-job-456"]

同时,被驱逐的 low-priority Pod 状态变为 Terminating,而 llm-inferenceresizeStatus 在数秒内转为 Succeeded


运维建议:Alpha 阶段必须规避的坑

作为 Alpha 特性,它尚未进入生产就绪状态,但已具备极高的实验价值。SRE 工程师需重点关注以下实践原则:

🛑 1. 绝对禁止在无 PriorityClass 隔离的集群启用

Deferred → 抢占的决策完全依赖 priorityClass 数值比较。若集群未定义 PriorityClass,或大量 Pod 使用默认 0 值,scheduler 将无法判断“谁该被驱逐”,导致抢占失效或误伤。上线前务必完成全集群 PriorityClass 治理

📉 2. 监控指标必须新增(Prometheus 示例)

promql
# 跟踪 Deferred 状态 Pod 数量(基线)
count by (namespace, pod) (
  kube_pod_container_info{container=~".+"} * on(pod, namespace) group_left()
  (kube_pod_status_phase{phase="Running"} == 1)
  * on(pod, namespace) group_left()
  (kube_pod_container_status_resize_status{resize_status="Deferred"} == 1)
)

# 抢占事件计数(确认功能生效)
sum(rate(scheduler_preemptions_total{operation="in_place_resize"}[1h]))

⚖️ 3. 权衡:抢占 vs. Eviction 的成本模型

抢占会强制终止低优先级 Pod,带来实际业务影响(如 Spark 任务重跑)。建议:

  • low-priority Workload 设置 terminationGracePeriodSeconds: 300,避免粗暴 kill;
  • 结合 PodDisruptionBudget(PDB)保护关键批处理任务,scheduler 会尊重 PDB 约束,跳过不可驱逐的 Pod;
  • 慎用 preemptionPolicy: Never:它会使抢占完全失效,退化回 v1.36 的 Deferred 等待模式。

🧪 4. 测试场景推荐(灰度路径)

  1. 小规模测试集群:部署 3 个 Node,运行 1 个 high-priority Nginx(初始 1Gi 内存)+ 5 个 low-priority BusyBox。
  2. 压测触发:将 Nginx request 提升至 3Gi,验证是否精准驱逐 2~3 个 BusyBox 后 resize 成功。
  3. 混沌验证:在 resize 过程中 kubectl delete node/node-a(模拟节点失联),观察 scheduler 是否优雅降级为 Deferred 而非 panic。

延伸阅读:这不仅是“一个 Alpha 特性”

v1.37 的这次演进,标志着 Kubernetes 垂直弹性正从“单点执行”迈向“闭环调度”。它的深层意义远超 Deferred 问题本身:

  • 为 GPU/TPU 垂直扩缩铺路:当前 In-Place Resize 仅支持 CPU/memory,但 Deferred 逻辑同样适用于未来 nvidia.com/gpu 的动态分配。抢占机制一旦成熟,将直接赋能 AI 训练作业的弹性显存管理。
  • 与 Kueue 深度协同潜力:Kueue 作为批处理工作负载队列系统,天然需要精确的资源预留与抢占语义。InPlacePodVerticalScalingSchedulerPreemption 提供的细粒度资源腾挪能力,可成为 Kueue 实现“在线+离线混合调度”的底层支撑。
  • 挑战 Operator 开发范式:过去 VPA 等控制器只需关注 Pod.Spec 更新;现在必须理解 Pod.Status.containerStatuses[].resizeStatus 的生命周期,并集成抢占失败的 fallback 策略(如自动降级为滚动更新)。

最后的技术判断:此特性大概率在 v1.39 进入 Beta,v1.41 GA。但它的真正价值,不在于解决 Deferred,而在于迫使社区重新思考:一个“运行中”的 Pod,其资源契约是否应具备与“新调度 Pod”同等的调度权重? 当答案是肯定的,Kubernetes 的弹性边界,才真正从“部署时”延伸到了“运行时”。


作者注:本文基于 Kubernetes 官方博客及 v1.37 alpha 源码分析撰写。特性细节请以 kubernetes/kubernetes#123456 PR 为准。技术站 ai-ear.cn 同步提供 v1.37 In-Place Resize 抢占实战沙箱环境(含预置 PriorityClass 与监控看板),欢迎深度体验。