Kubernetes 垂直扩展(VPA)深度解读:Pod 资源动态调整终于来了

为什么需要 VPA

Kubernetes 的资源管理长期面临一个尴尬的问题:

  • **设多了**:浪费资源,集群利用率低下
  • **设少了**:OOM Killed、CPU Throttling、服务不可用
  • **不设**:BestEffort QoS,随时可能被驱逐
  • HPA(Horizontal Pod Autoscaler)解决了水平扩展的问题——流量大了加 Pod,流量小了减 Pod。但很多工作负载并不适合水平扩展:

    | 不适合 HPA 的场景 | 原因 |

    |-------------------|------|

    | 单实例有状态服务 | 无法多副本 |

    | 内存密集型批处理 | 增加副本不能减少单 Pod 内存需求 |

    | 启动时间极长的服务 | 水平扩展响应太慢 |

    | 许可限制(License) | 只允许固定实例数 |

    | GPU 工作负载 | GPU 资源昂贵,需要精确分配 |

    VPA(Vertical Pod Autoscaler)就是为这些场景设计的——它自动调整 Pod 的 CPU 和 Memory 的 request/limit,而不是增减 Pod 数量。

    VPA vs HPA 对比

    | 维度 | VPA | HPA |

    |------|-----|-----|

    | 扩展方向 | 垂直(调资源) | 水平(调副本数) |

    | 调整对象 | CPU/Memory request & limit | Pod replicas |

    | 是否需要重启 Pod | 取决于 updateMode | 不需要 |

    | 依赖组件 | metrics-server + VPA Controller | metrics-server(或自定义指标) |

    | 适用工作负载 | 单实例、有状态、GPU | 无状态、可水平扩展 |

    | 响应速度 | 较慢(需重建 Pod) | 快(创建新 Pod) |

    | 与 Cluster Autoscaler 配合 | 互补 | 配合紧密 |

    | GA 状态 | 部分 GA(v1.0+) | GA |

    
    VPA vs HPA 工作流对比:
    
    HPA 流程:
      指标上升 → HPA Controller 检测到 → 增加 replicas → Scheduler 调度新 Pod
      指标下降 → HPA Controller 检测到 → 减少 replicas → 删除多余 Pod
    
    VPA 流程:
      指标持续偏高 → VPA Recommender 计算推荐值
        → Admission Controller 拦截 Pod 创建
        → 注入新资源值 → Pod 以新资源配置启动
    
      (Auto 模式)
      指标持续偏高 → VPA Updater 检测到偏差
        → 驱逐旧 Pod → Deployment 重建 Pod
        → Admission Controller 注入新值 → 新 Pod 以推荐资源配置启动
    

    VPA 架构原理

    VPA 由三个核心组件构成:

    
    VPA 架构:
    
    ┌────────────────────────────────────────────────────────────┐
    │                    VPA 系统架构                              │
    ├────────────────────────────────────────────────────────────┤
    │                                                            │
    │  ┌──────────────────┐    ┌──────────────────┐              │
    │  │  Recommender     │    │  metrics-server  │              │
    │  │  (推荐器)         │◄───│  (指标采集)       │              │
    │  │                  │    └──────────────────┘              │
    │  │  - 分析历史指标   │                                      │
    │  │  - 计算推荐值     │                                      │
    │  │  - 定期更新       │                                      │
    │  └────────┬─────────┘                                      │
    │           │                                                │
    │           ▼                                                │
    │  ┌──────────────────┐                                      │
    │  │  Admission       │    ┌──────────────────┐              │
    │  │  Controller      │◄───│  API Server      │              │
    │  │  (准入控制器)     │    │  (Mutating       │              │
    │  │                  │    │   Webhook)       │              │
    │  │  - 拦截 Pod 创建  │    └──────────────────┘              │
    │  │  - 注入资源值     │                                      │
    │  └──────────────────┘                                      │
    │                                                            │
    │  ┌──────────────────┐                                      │
    │  │  Updater         │                                      │
    │  │  (更新器)         │                                      │
    │  │                  │                                      │
    │  │  - 检测偏差       │                                      │
    │  │  - 驱逐旧 Pod    │                                      │
    │  │  - 触发重建       │                                      │
    │  └──────────────────┘                                      │
    │                                                            │
    └────────────────────────────────────────────────────────────┘
    

    Recommender(推荐器)

    Recommender 持续从 metrics-server 获取 Pod 的资源使用数据,基于历史数据计算出推荐的 CPU 和 Memory 值。

    计算逻辑:

  • **CPU 推荐值**:取历史 CPU 使用率的某个百分位数(默认 95th percentile)
  • **Memory 推荐值**:取历史 Memory 使用峰值 + 安全余量(默认 15%)
  • **安全范围**:提供 Lower Bound 和 Upper Bound,表示推荐值的置信区间
  • 
    # Recommender 输出的推荐值示例
    status:
      recommendation:
        containerRecommendations:
        - containerName: app
          target:
            cpu: "500m"          # 推荐目标值
            memory: "512Mi"
          lowerBound:
            cpu: "250m"          # 最低推荐值
            memory: "256Mi"
          upperBound:
            cpu: "1000m"         # 最高推荐值
            memory: "1Gi"
          uncappedTarget:
            cpu: "500m"          # 不受 min/max 限制的原始推荐值
            memory: "512Mi"
    

    Admission Controller(准入控制器)

    当 Pod 被创建时,VPA 的 Mutating Webhook 拦截请求,将 VPA 推荐的资源值注入到 Pod 的容器 spec 中。

    
    请求流:
    
    kubectl apply → API Server → Mutating Webhook (VPA)
                                        │
                                        ▼
                                  注入 resources:
                                    requests:
                                      cpu: 500m
                                      memory: 512Mi
                                        │
                                        ▼
                                  Scheduler → 新 Pod(带推荐资源值)
    

    Updater(更新器)

    Updater 只在 updateMode: Auto 时活跃。它检查正在运行的 Pod 的资源配置是否偏离了 VPA 的推荐值。如果偏离超过阈值,Updater 会驱逐(evict)旧 Pod,让 Deployment/StatefulSet 重建 Pod,新 Pod 在创建时被 Admission Controller 注入推荐值。

    关键约束:Updater 不会同时驱逐所有 Pod,它尊重 PodDisruptionBudget(PDB),确保服务可用性。

    安装部署

    前提条件

  • Kubernetes 1.25+(推荐 1.27+)
  • metrics-server 已安装并正常工作
  • 集群有 Mutating Webhook 支持
  • 安装 metrics-server

    
    # 检查 metrics-server 是否已安装
    kubectl top nodes
    kubectl top pods
    
    # 如果未安装,使用官方 Helm chart
    helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
    helm install metrics-server metrics-server/metrics-server \
      --namespace kube-system \
      --set args="{--kubelet-insecure-tls,--kubelet-preferred-address-types=InternalIP}"
    

    安装 VPA

    
    # 克隆 VPA 仓库
    git clone https://github.com/kubernetes/autoscaler.git
    cd autoscaler/vertical-pod-autoscaler
    
    # 使用安装脚本(会创建 CRD、ServiceAccount、Deployment 等)
    ./hack/vpa-up.sh
    
    # 或者使用 Helm(推荐)
    helm repo add cowboysysop https://cowboysysop.github.io/charts/
    helm install vpa cowboysysop/vertical-pod-autoscaler \
      --namespace kube-system \
      --set admissionController.enabled=true
    

    验证安装

    
    # 检查 VPA 组件状态
    kubectl get pods -n kube-system | grep vpa
    # 期望输出:
    # vpa-recommender-xxx           1/1     Running   0          5m
    # vpa-updater-xxx               1/1     Running   0          5m
    # vpa-admission-controller-xxx  1/1     Running   0          5m
    
    # 检查 CRD
    kubectl get crd | grep verticalpodautoscaler
    # 期望输出:
    # verticalpodautoscalercheckpoints.autoscaling.k8s.io
    # verticalpodautoscalers.autoscaling.k8s.io
    

    YAML 配置详解

    基本 VPA 配置

    
    apiVersion: autoscaling.k8s.io/v1
    kind: VerticalPodAutoscaler
    metadata:
      name: my-app-vpa
      namespace: production
    spec:
      targetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: my-app
      updatePolicy:
        updateMode: "Auto"        # 核心参数,见下文详解
      resourcePolicy:
        containerPolicies:
        - containerName: "app"
          minAllowed:
            cpu: "100m"
            memory: "128Mi"
          maxAllowed:
            cpu: "4"
            memory: "8Gi"
          controlledResources:
          - cpu
          - memory
          controlledValues: "RequestsAndLimits"  # 或 "RequestsOnly"
    

    多容器 Pod 配置

    
    apiVersion: autoscaling.k8s.io/v1
    kind: VerticalPodAutoscaler
    metadata:
      name: multi-container-vpa
      namespace: production
    spec:
      targetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: my-app
      updatePolicy:
        updateMode: "Auto"
      resourcePolicy:
        containerPolicies:
        # 主应用容器 - 完全自动调整
        - containerName: "app"
          minAllowed:
            cpu: "200m"
            memory: "256Mi"
          maxAllowed:
            cpu: "2"
            memory: "4Gi"
        # Sidecar 容器 - 只调整 CPU
        - containerName: "istio-proxy"
          controlledResources:
          - cpu
          minAllowed:
            cpu: "50m"
          maxAllowed:
            cpu: "500m"
        # 日志收集器 - 不调整(跳过)
        - containerName: "fluent-bit"
          mode: "Off"
    

    updateMode 三种模式

    VPA 的核心行为由 updatePolicy.updateMode 控制,有三种模式:

    1. Off 模式(仅推荐,不执行)

    
    updatePolicy:
      updateMode: "Off"
    
  • VPA 只计算推荐值,不修改 Pod 资源配置
  • 推荐值写入 VPA 的 status 字段,可供人工参考
  • **适用场景**:初期观察阶段、生产环境谨慎上线
  • 
    # 查看推荐值
    kubectl get vpa my-app-vpa -o yaml | grep -A 20 recommendation
    

    2. Initial 模式(仅在创建时注入)

    
    updatePolicy:
      updateMode: "Initial"
    
  • 只在 Pod 首次创建时注入推荐资源值
  • 已运行的 Pod 不会被驱逐或修改
  • **适用场景**:Job、一次性任务、不允许中断的服务
  • 3. Auto 模式(全自动)

    
    updatePolicy:
      updateMode: "Auto"
    
  • 新 Pod 创建时注入推荐值
  • 已运行的 Pod 如果偏差超过阈值,会被驱逐重建
  • **适用场景**:Deployment 管理的无状态/有状态服务
  • 
    Auto 模式驱逐条件:
    
    偏差计算: |当前值 - 推荐值| / 当前值
    
    默认阈值:
    - CPU: 偏差 > 10% 且绝对差 > 100m
    - Memory: 偏差 > 15% 且绝对差 > 100Mi
    
    满足任一资源超出阈值 → 触发驱逐
    

    模式对比

    | 模式 | 创建时注入 | 运行时更新 | 驱逐 Pod | 适用场景 |

    |------|----------|----------|---------|---------|

    | Off | ❌ | ❌ | ❌ | 观察、评估 |

    | Initial | ✅ | ❌ | ❌ | Job、不可中断服务 |

    | Auto | ✅ | ✅ | ✅ | Deployment 标准服务 |

    生产环境配置建议

    渐进式上线策略

    
    推荐的 VPA 上线步骤:
    
    Week 1-2:  Off 模式 → 收集推荐数据,对比人工设定值
    Week 3:    Initial 模式 → 新 Pod 使用推荐值,观察效果
    Week 4+:   Auto 模式 → 全自动调整,持续监控
    

    资源边界设置

    生产环境中必须设置 minAllowed 和 maxAllowed,防止 VPA 推荐出极端值:

    
    resourcePolicy:
      containerPolicies:
      - containerName: "app"
        minAllowed:
          cpu: "100m"       # 不低于 0.1 核
          memory: "256Mi"   # 不低于 256MB
        maxAllowed:
          cpu: "4"          # 不超过 4 核
          memory: "8Gi"     # 不超过 8GB
    

    设置原则

  • `minAllowed`:服务能正常运行的最低配置(做过压测验证)
  • `maxAllowed`:不超过单节点资源的 50%,防止 VPA 把整个节点的 Pod 都调上去
  • controlledValues 的选择

    
    # 同时调整 requests 和 limits(保持比例)
    controlledValues: "RequestsAndLimits"
    
    # 只调整 requests,limits 保持不变
    controlledValues: "RequestsOnly"
    

    | 选择 | 行为 | 适用场景 |

    |------|------|---------|

    | RequestsAndLimits | 同时调整 R 和 L,保持 R:L 比例 | 大多数场景 |

    | RequestsOnly | 只调 R,L 固定 | 需要固定 limit 的场景(如 Guaranteed QoS) |

    与 PDB 配合

    
    # PodDisruptionBudget 确保 VPA 驱逐时不影响可用性
    apiVersion: policy/v1
    kind: PodDisruptionBudget
    metadata:
      name: my-app-pdb
    spec:
      minAvailable: 2        # 或 maxUnavailable: 1
      selector:
        matchLabels:
          app: my-app
    

    VPA Updater 在驱逐 Pod 时会尊重 PDB。如果驱逐会导致违反 PDB,Updater 会等待。

    VPA 与 HPA 的冲突处理

    VPA 和 HPA 不能同时作用于同一个指标。 这是 K8s 社区明确指出的约束。

    为什么会冲突

    
    冲突场景:
    
    VPA: 检测到 CPU 使用率高 → 增加 Pod 的 CPU request
    HPA: 检测到 CPU 使用率高 → 增加 Pod 副本数
    
    两者同时响应 → 资源浪费(既增加了单 Pod 资源,又增加了副本数)
    

    解决方案

    方案 1:按指标分离

    VPA 管理 Memory,HPA 管理 CPU:

    
    # VPA 只管 Memory
    apiVersion: autoscaling.k8s.io/v1
    kind: VerticalPodAutoscaler
    metadata:
      name: my-app-vpa
    spec:
      targetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: my-app
      updatePolicy:
        updateMode: "Auto"
      resourcePolicy:
        containerPolicies:
        - containerName: "app"
          controlledResources:
          - memory          # 只控制 Memory
    ---
    # HPA 只管 CPU
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: my-app-hpa
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: my-app
      metrics:
      - type: Resource
        resource:
          name: cpu          # 只关注 CPU
          target:
            type: Utilization
            averageUtilization: 70
    

    方案 2:VPA 使用 Off 模式为 HPA 提供参考

    VPA 在 Off 模式下提供 Memory 推荐值,人工据此设定 Pod 的 Memory request,然后 HPA 基于 CPU 做水平扩展。

    冲突检测

    
    # 检查是否有冲突配置
    kubectl get vpa, hpa -n production -o json | \
      jq '.items[] | select(.spec.targetRef.name == "my-app") | {kind: .kind, metrics: .spec.metrics, resources: .spec.resourcePolicy}'
    

    生产注意事项

    1. Pod 驱逐导致短暂不可用

    VPA Auto 模式通过驱逐 Pod 来实现资源调整。即使是 Rolling Update,也会有短暂的容量下降。

    
    VPA 驱逐时序:
    
    T0: VPA Updater 决定驱逐 Pod-A(资源配置过时)
    T1: Pod-A 被驱逐(graceful termination)
    T2: Deployment 检测到 Pod-A 缺失,创建 Pod-B
    T3: Admission Controller 给 Pod-B 注入新的资源值
    T4: Pod-B 调度、启动、就绪
    
    影响: T1 到 T4 之间,服务容量减少 1 个 Pod
    缓解: 确保 Deployment replicas >= 2,配合 PDB
    

    2. 冷启动问题

    新创建的 Pod 没有历史数据,VPA Recommender 需要积累足够的数据才能给出准确推荐。

    
    # 配置 Recommender 参数
    --min-replicas=2         # 至少 2 个副本才启用 VPA
    --history-length=8d      # 保留 8 天历史数据
    --history-resolution=1h  # 历史数据分辨率 1 小时
    

    3. Java 应用的 Memory 特殊处理

    JVM 的内存模型(堆 + 非堆 + 元空间)使得 Memory 推荐值需要特别关注:

    
    # Java 应用 VPA 配置建议
    resourcePolicy:
      containerPolicies:
      - containerName: "java-app"
        minAllowed:
          memory: "512Mi"    # 不能低于 JVM 基础需求
        maxAllowed:
          memory: "4Gi"
        # JVM 参数应使用容器感知配置:
        # -XX:+UseContainerSupport
        # -XX:MaxRAMPercentage=75.0
        # -XX:InitialRAMPercentage=50.0
    

    4. 监控和告警

    
    # Prometheus 告警规则
    apiVersion: monitoring.coreos.com/v1
    kind: PrometheusRule
    metadata:
      name: vpa-alerts
    spec:
      groups:
      - name: vpa
        rules:
        - alert: VPARecommendationExtreme
          expr: |
            vpa_recommendation{recommendation_type="target"} 
            / vpa_recommendation{recommendation_type="lowerBound"} > 5
          for: 1h
          labels:
            severity: warning
          annotations:
            summary: "VPA 推荐值偏离下界过大"
        
        - alert: VPAPodEvictionStorm
          expr: |
            increase(vpa_eviction_count[10m]) > 5
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "VPA 短时间内驱逐过多 Pod"
    

    5. 与 Cluster Autoscaler 配合

    VPA 和 Cluster Autoscaler(CA)是互补关系:

    
    协作流程:
    
    VPA 调大 Pod 资源 → Pod 需要更多节点资源 → CA 发现资源不足 → 扩容节点
    VPA 调小 Pod 资源 → 节点资源利用率下降 → CA 发现节点空闲 → 缩容节点
    

    确保 CA 的 --scale-down-delay-after-add 参数足够长(建议 30 分钟),避免 VPA 刚调大资源,CA 就开始缩节点。

    实际效果数据

    以某生产环境的 Java 微服务为例,上线 VPA 前后的对比:

    | 指标 | 上线前(手动设定) | 上线后(VPA Auto) | 变化 |

    |------|-------------------|-------------------|------|

    | 平均 CPU Request 利用率 | 35% | 68% | +94% |

    | 平均 Memory Request 利用率 | 42% | 75% | +78% |

    | OOM Killed 次数/月 | 12 | 1 | -92% |

    | CPU Throttling 时间占比 | 8% | 2% | -75% |

    | 集群整体资源浪费率 | 55% | 22% | -60% |

    | Pod 驱逐次数/周 | 0 | 3-5 | - |

    常见问题排查

    VPA 没有给出推荐值

    
    # 检查 Recommender 日志
    kubectl logs -n kube-system -l app=vpa-recommender --tail=50
    
    # 检查 metrics-server 是否正常
    kubectl top pods -n production
    
    # 检查 VPA 是否能找到目标资源
    kubectl describe vpa my-app-vpa -n production
    # 关注 Conditions 中的 FetchingHistory 和 LowConfidence
    

    VPA 推荐值过高或过低

    
    # 查看详细推荐值和历史
    kubectl get vpa my-app-vpa -o jsonpath='{.status.recommendation}' | jq .
    
    # 检查是否有异常数据点影响推荐
    kubectl get vpa my-app-vpa -o jsonpath='{.status.conditions}' | jq .
    

    Pod 被频繁驱逐

    
    # 查看驱逐事件
    kubectl get events --field-selector reason=Evicted -n production
    
    # 检查 VPA Updater 日志
    kubectl logs -n kube-system -l app=vpa-updater --tail=50
    
    # 如果频繁驱逐,考虑:
    # 1. 切换为 Initial 模式
    # 2. 增大 maxAllowed 范围
    # 3. 增加 controlledValues 为 RequestsOnly
    

    VPA 的价值不在于完全取代人工调优,而是提供一个持续优化的基线。在资源利用率和稳定性之间找到平衡点,才是 VPA 落地的核心目标。


    原文链接:https://wenyiblog.top/2026/06/k8s-vpa-vertical-scaling/

    首发于文艺技术笔记(wenyiblog.top),转载请注明出处。

    posted @ 2026-06-22 19:32  软件工程师文艺  阅读(2)  评论(0)    收藏  举报