详解K8s 1.33原地扩缩容功能:原理、实践、局限与发展

发布于:2025-06-10 ⋅ 阅读:(27) ⋅ 点赞:(0)

你是否有过这样的经历?

精心配置了 Kubernetes 的 Pod,设置了“刚刚好”的 CPU 和内存(至少你当时是这么想的),结果应用不是资源紧张喘不过气,就是像“双十一”抢购一样疯狂抢占资源。

过去,唯一的解决办法就是重启整个 Pod ——这种破坏性的做法就像用黄油刀做开胸手术,而 SRE 团队正透过手术室的窗户盯着看,紧张但无能为力。

不过,近期发布的 Kubernetes 1.33 版本带来了我们梦寐以求的功能:原地 Pod 垂直伸缩(In-place Pod Vertical Scaling)。

在 Kubernetes 1.33 中该功能已升级为 Beta 版,并且默认启用。你不再需要手动启用特性,这使得它在生产环境中更加易用

(可查阅 Kubernetes 官方文档:https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/)。

这意味着你可以调整正在运行的 Pod 的 CPU 和内存配置,而无需重启

如果你一直在琢磨垂直自动伸缩(VPA)的细节,那么这次更新会令人格外兴奋——虽然 VPA 的“重新创建”模式在理论上很美好,但实操过程中常常状况百出。

原地 Pod 垂直伸缩提供了一种更加优雅的资源调整方式,或许能让 VPA 的体验更加顺滑。

01/为什么这项更新如此重要?

对 Kubernetes 用户来说,这是一项革新式的重要更新。

假设当应用突然遇到流量激增的高峰期时,过去的做法是要么预留大量资源,但成本很高;要么触发 VPA 更新,导致 Pod 重启。而现在,通过这项更新可以直接实时增加 CPU 和内存配置,不会造成应用卡顿,用户侧几乎无感。

尤其对于那些有状态应用、数据库,或要求持续可用性的服务而言,原地扩缩容能大幅降低宕机时间,提供更加无缝的弹性伸缩体验。

为什么这项特性能实现这一效果?让我们来简单分析一下:

  1. 告别 Pod重启 : 过去每次调整资源,服务是否宕机全看运气。VPA 会像一个过于热情的保安一样把 Pod 赶下线。现在呢?扩容丝滑得像咖啡师拉花。
  2. 成本优化魔法 : 不再需要为了“以防万一”的情况,而过度配置资源。正如 Sysdig 团队指出的,这实现了真正的按需付费云经济。
  3. 拯救有状态工作负载 : 数据库不再需要在“性能”与“可用性”之间做选择了。这就像在行驶的汽车上换轮胎 ——虽然有风险,但现在已经可行!

02/实际应用场景

以下是一些具体的应用场景,从中可以窥见这一功能的实用性:

  • 数据库工作负载 当你的 PostgreSQL 实例突然需要更多 RAM 来处理营销部门的数据分析的请求时,你可以在不中断正在进行的交易或清空连接池的情况下扩展资源。用户再也不会看到“请稍后再试”的消息了!
  • Node.js API 服务 Node.js 应用可以在无需重启的情况下动态使用新增的 CPU 和内存。因此非常适合使用原地扩缩容来应对流量激增。
  • 机器学习推理服务 TensorFlow Serving 等服务在处理更大批量或更复杂模型时,可以实时扩容,无需中断正在进行的推理请求。
  • 服务网格 Sidecar 在 Istio 之类的服务网格中, Envoy 代理现在可以根据流量模式动态调整资源,而不会干扰主应用容器的运行。

关于 Java 应用的提醒:
对于基于 JVM 的应用,仅仅调整 Pod 的内存配置并不会自动改变 JVM 的堆(heap)大小,后者通常是在启动时通过-Xmx等参数设置的。虽然原地 Pod 调整可以优化非堆内存和 CPU 资源的使用,但如果想要充分利用扩容后的内存限制,Java应用通常需要调整配置并重启。因此,对于不希望重启的场景来说,Java 应用并不适合进行内存扩容。

03/技术揭秘:Kubernetes 如何灵活调整资源

接下来,让我们深入探讨技术细节,下图展示了 K8s 资源调整的工作流程:

实际发生了什么

  • 可变资源字段已支持动态更新 得益于这个 issue (https://github.com/kubernetes/enhancements/issues/1287),Pod 规范中的resources.requests 和 resources.limits 字段现在支持动态修改,无需再为 Spec 是否不可变而争论了。
  • Kubelet 的评估机制 当你提交 patch 时,kubelet 会评估:(节点可用资源总量) - (当前所有容器资源分配总和) ≥ (新请求资源)?满足条件就允许变更,否则就返回 PodResizePending
  • CRI 协议握手 kubelet 会通过容器运行时接口 (CRI) 与 containerd 或 CRI-O 通信:“给这个容器加点资源”。运行时随后会调整对应的 cgroup,无需重启,轻松搞定。这是异步、非阻塞的流程,Kubelet 可以在处理资源调整的同时继续执行其他重要任务。
  • 状态更新提示 在执行kubectl describe pod时,你会看到两个新状态:
  • PodResizePending —— “节点当前资源不足,稍后再试。”
  • PodResizeInProgress —— “正在处理,资源调整中。”

容器运行时兼容性

这一功能在不同的容器运行时中均可使用,但支持程度有差异,情况如下:

  • containerd(v1.6+):完全支持,可以平滑调整 CPU 和内存的 cgroup
  • CRI-O(v1.24+):完全支持原地扩缩容
  • Docker:支持有限,因为它正在逐步退出 Kubernetes
注意:cgroup v2 在内存回收方面比 cgroup v1 更强大,特别是对于内存限制减少的情况下。

04/上手实践:安全地搞点事情

让我们通过一个简单的演示来展示原地 Pod 调整的实际效果,你可以从 Kubernetes API 和 Pod 内部同时查看资源变更。

整个演示运行在 GKE 的 Kubernetes 1.33 上。

  1. 创建一个资源监控 Pod

首先创建一个持续监控自身资源分配的Pod:

kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: resize-demo
spec:
  containers:
  - name: resource-watcher
    image: ubuntu:22.04
    command:
    - "/bin/bash"
    - "-c"
    - |
      apt-get update && apt-get install -y procps bc
      echo "=== Pod Started: $(date) ==="

      # Functions to read container resource limits
      get_cpu_limit() {
        if [ -f /sys/fs/cgroup/cpu.max ]; then
          # cgroup v2
          local cpu_data=$(cat /sys/fs/cgroup/cpu.max)
          local quota=$(echo $cpu_data | awk '{print $1}')
          local period=$(echo $cpu_data | awk '{print $2}')

          if [ "$quota" = "max" ]; then
            echo "unlimited"
          else
            echo "$(echo "scale=3; $quota / $period" | bc) cores"
          fi
        else
          # cgroup v1
          local quota=$(cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us)
          local period=$(cat /sys/fs/cgroup/cpu/cpu.cfs_period_us)

          if [ "$quota" = "-1" ]; then
            echo "unlimited"
          else
            echo "$(echo "scale=3; $quota / $period" | bc) cores"
          fi
        fi
      }

      get_memory_limit() {
        if [ -f /sys/fs/cgroup/memory.max ]; then
          # cgroup v2
          local mem=$(cat /sys/fs/cgroup/memory.max)
          if [ "$mem" = "max" ]; then
            echo "unlimited"
          else
            echo "$((mem / 1048576)) MiB"
          fi
        else
          # cgroup v1
          local mem=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes)
          echo "$((mem / 1048576)) MiB"
        fi
      }

      # Print resource info every 5 seconds
      while true; do
        echo "---------- Resource Check: $(date) ----------"
        echo "CPU limit: $(get_cpu_limit)"
        echo "Memory limit: $(get_memory_limit)"
        echo "Available memory: $(free -h | grep Mem | awk '{print $7}')"
        sleep 5
      done
    resizePolicy:
    - resourceName: cpu
      restartPolicy: NotRequired
    - resourceName: memory
      restartPolicy: NotRequired
    resources:
      requests:
        memory: "128Mi"
        cpu: "100m"
      limits:
        memory: "128Mi"
        cpu: "100m"
EOF

2. 查看 Pod 的初始状态

从 Kubernetes API 的角度查看 Pod 的资源:

kubectl describe pod resize-demo | grep -A8 Limits:

你会看到类似以下输出:

Limits:
      cpu:     100m
      memory:  128Mi
    Requests:
      cpu:     100m
      memory:  128Mi

现在,看看 Pod 自身如何看待它的资源:

kubectl logs resize-demo --tail=8

输出中会包含容器视角的 CPU 和内存限制。

3. 无缝调整 CPU

让我们在不重启的情况下将 CPU 加倍:

kubectl patch pod resize-demo --subresource resize --patch \
  '{"spec":{"containers":[{"name":"resource-watcher", "resources":{"requests":{"cpu":"200m"}, "limits":{"cpu":"200m"}}}]}}'

检查调整状态:

kubectl get pod resize-demo -o jsonpath='{.status.conditions[?(@.type=="PodResizeInProgress")]}'
注意:在 GKE 的 Kubernetes 1.33 上,你可能看不到 PodResizeInProgress状态,即使调整操作已经成功。如果 kubectl get pod resize-demo -o jsonpath='{.status.conditions}&#x27;没有显示调整信息,请直接检查实际资源。

调整完成后,从 Kubernetes API 查看更新后的资源:

kubectl describe pod resize-demo | grep -A8 Limits:

并验证 Pod 现在看到的 CPU 限制:

kubectl logs resize-demo --tail=8

你会注意到 CPU 限制从100m翻倍到200m,而 Pod 没有重启!Pod 的日志会显示 cgroup 的 CPU 限制从大约10000/100000变为20000/100000(表示从 100m 到 200m 的 CPU)。

4. 无痛调整内存

现在,让我们将内存分配加倍:

kubectl patch pod resize-demo --subresource resize --patch \
  '{"spec":{"containers":[{"name":"resource-watcher", "resources":{"requests":{"memory":"256Mi"}, "limits":{"memory":"256Mi"}}}]}}'

稍等片刻后,从 API 验证:

kubectl describe pod resize-demo | grep -A8 Limits:

从 Pod 内部查看:

kubectl logs resize-demo --tail=8

你会看到内存限制从 128Mi 变为 256Mi,而容器没有重启!

5. 验证是否重启

确认在调整过程中容器从未重启:

kubectl get pod resize-demo -o jsonpath='{.status.containerStatuses[0].restartCount}'

输出应该是0——证明我们实现了无需服务中断就能调整资源需求。

6. 清理资源

检验完成后:

kubectl delete pod resize-demo

搞定!你已经成功地在不重启 Pod 的情况下原地调整了 CPU 和内存资源

这种模式适用于任何容器化应用,除了设置适当的resizePolicy外,不需要任何特殊配置。

05/云厂商支持情况

在进行生产环境的尝试之前,先看看各大 Kubernetes 服务商的支持情况:

  • Google Kubernetes Engine (GKE):在 GKE 的 Rapid 渠道中提供支持
  • Amazon EKS:Kubernetes 1.33 版本预计将于 2025 年 5 月发布。
  • Azure AKS:Kubernetes 1.33 版本现已提供预览
  • 自建集群:只要运行 Kubernetes 1.33+ 并使用 containerd 或 CRI-O 运行时,完全支持

06/局限性:功能虽然强大,但也有边界

尽管这个功能很酷,但请记住以下几点:

平台和运行时限制

  1. Windows用户暂不支持:目前仅限Linux
  2. 某些节点级 Pod 不支持:如果节点启用了静态 CPU 或内存管理器(如 CPU 静态管理器策略),则该节点上的 Pod 将不支持原地扩缩容
  3. 容器运行时要求:需要 containerd v1.6+ 或 CRI-O v1.24+ 才能提供完整支持

资源管理限制

1. QoS 类别不可更改

Pod的原始服务质量等级(Guaranteed/Burstable/BestEffort)保持不变。无法通过调整将BestEffort升级为Guaranteed

2. 目前只支持 CPU 和内存

想动态调整 GPU 或临时存储?暂时还做不到。目前仅支持 CPU 和内存的原地调整。

3. 内存缩容需谨慎

  • 不重启容器的情况下降低内存限制,就像炸弹——理论上可行,实际上极危险。
  • 建议你设置 restartPolicy: RestartContainer以免场面失控。这在使用 cgroup v1 的系统中尤其关键。

4. Swap 使用限制

如果 Pod 开启了 Swap 功能,那你就无法动态调整内存,除非将内存的 resizePolicy 设置为 RestartContainer

5. 资源无法移除

一旦设置了资源 request 或 limit,就不能通过原地调整将其删除,只能修改数值。

配置和集成限制

1. 并非所有容器都适用

    1. ❎初始化容器和临时容器不支持,这些一次性启动的容器无法使用此功能,因为他们本身就无法重启。
    2. ✅支持 Sidecar 容器:如果你喜欢用 Sidecar 模式,Sidecar 容器和主容器一样,支持原地进行资源调整,互不打扰。

2. resizePolicy不可更改:

一旦 Pod 创建,就无法更改其resizePolicy。所以请慎重选择,这是资源配置关系里的“从一而终”。

3. 特定应用限制

如前所述,基于 JVM 的应用在不进行配置修改的情况下,无法自动使用扩容后的内存,通常还需要重启。这类限制适用于所有自行管理内存池的应用

4. 缩容的风险

如果你尝试把内存限制调低到低于当前使用量(即使设置了restartPolicy)也可能导致内存溢出(OOM)。

性能注意事项

1. 节点预留资源很关键

节点需要有足够的可用容量才能成功调度资源。在资源紧张的集群中,Pod 可能会长时间处于 PodResizePending 状态,迟迟不能完成调整。

2.资源调整并非实时完成

kubelet 是异步处理调整请求的。复杂的 cgroup 更新可能需要几秒钟才能完全生效。

3.调度器不感知扩缩容状态

Kubernetes 调度器在进行 Pod 调度时不会感知有 Pod 正在调整资源,这可能导致资源压力误判,甚至调度冲突。

记住这些注意事项,你就能避免最糟糕的情况。

07/VPA:很努力但还差点意思

正如我最近在 LinkedIn 上感叹的,垂直 Pod 自动扩缩容(VPA)就像弹性伸缩家族聚会里那个总被忽视的尴尬亲戚。

但好消息是,它的“逆袭”正在进行中。

当前状态(截至 Kubernetes 1.33):

  • VPA 尚不支持原地调整——它在调整资源时仍然会直接删除然后再重建 Pod,这在很多场景下显得非常粗暴
  • Kubernetes文档明确指出这一限制:“截至Kubernetes 1.33,VPA 不支持原地调整 Pod,但相关集成正在开发中。”

在 kubernetes/autoscaler PR #7673 中(https://github.com/kubernetes/autoscaler/pull/7673),社区已着手将 VPA 与原地扩缩容机制集成。这意味着未来我们有望看到 VPA 真正实现资源动态调整,而无需重启应用

这种混合方案有望让 VPA 真正能具备在生产环境中支持有状态工作负载的能力。在此之前,我们只能继续手动和 Pod 玩资源叠叠乐,谨慎地调整资源。

当前结合 VPA 和手动调整的方法

虽然 VPA 尚未原生支持原地扩缩容,但你仍然可以同时受益于两者:

  1. 使用 VPA 的“Off”模式:只生成资源推荐,不自动调整
  2. 基于推荐手动执行扩缩容:根据 VPA 的建议手动进行原地调整
  3. 编写脚本自动化操作流程:通过脚本自动化手动步骤,避免 Pod 重建
# Example of a simple script to apply VPA recommendations via in-place resize
#!/bin/bash
POD_NAME="my-important-db"
CPU_REC=$(kubectl get vpa db-vpa -o jsonpath='{.status.recommendation.containerRecommendations[0].target.cpu}')
MEM_REC=$(kubectl get vpa db-vpa -o jsonpath='{.status.recommendation.containerRecommendations[0].target.memory}')

kubectl patch pod $POD_NAME --subresource resize --patch \
  "{\"spec\":{\"containers\":[{\"name\":\"database\",\"resources\":{\"requests\":{\"cpu\":\"$CPU_REC\",\"memory\":\"$MEM_REC\"}}}]}}"

08/与其他解决方案的对比

09/展望未来

Kubernetes 1.33 引入的原地 Pod 调整是让垂直扩缩容走向“无感知”的重要一步,但这只是开始。接下来还有更多令人期待的进展:

  1. VPA 深度集成(开发中) 首先尝试原地调整,仅在绝对必要时才重建 Pod,不再有突发的 Pod 驱逐。
  2. 支持更多资源类型 不止 CPU 和内存,未来可能连 GPU、临时存储都能动态调整。
  3. 调度器智能感知 目前调整操作不会通知调度器,Pod 仍可能因节点资源不足被驱逐。后续可能让其具备感知能力,优先保障资源,避免被意外重新调度。
  4. 和 Cluster Autoscaler / Karpenter 的联动 实现更智能的资源决策,仅在无法原地调整时扩展节点。
  5. 基于应用指标的自动调整资源 不再只盯着 CPU 和内存,未来可能支持基于“请求延迟”、“队列深度”等应用级指标进行资源动态调整。

这些逐步完善的功能功能共同描绘出一个未来愿景:Kubernetes 正在迈向一个真正智能、高效、不中断的垂直扩缩容时代。

对于开发者和平台团队而言,原地扩缩容(in-place pod resize)功能已经具备实验性使用的条件,非常适合在非生产环境中进行测试和评估。

需要注意的是,尽管该特性功能强大,在引入生产环境前仍需充分验证与测试,以规避潜在的不确定性和兼容性风险。期待这个功能可以帮助你持续提升集群资源管理效率。