HPA自动扩缩容

官方文档:https://kubernetes.io/zh-cn/docs/tasks/run-application/horizontal-pod-autoscale/

1.简介

HPA​(Horizontal Pod Autoscaler,Pod水平自动伸缩),根据平均 CPU 利用率、平均内存利用率或你指定的任何其他自定义指标自动调整 Deployment​ 、ReplicaSet​ 或 StatefulSet​ 或其他类似资源,实现部署的自动扩展和缩减,让部署的规模接近于实际服务的负载。HPA不适用于无法缩放的对象,例如DaemonSet。

先决条件

  1. 必须安装 metrics-server(在metrics-server中添加--kubelet-insecure-tls参数跳过证书校验)
  2. 必须定义 Request参数
  3. 开启 API Aggregator参数

配置Aggregator参数:

cat /etc/kubernetes/manifests/kube-apiserver.yaml
# 添加这行
--enable-aggregator-routing=true

HPA 到底基于什么指标扩缩容?

很多人一提到 HPA,第一反应就是“CPU 使用率”。但在 Kubernetes 1.23+ 的 autoscaling/v2 API 中,HPA 支持的指标类型远不止 CPU 这一种。

实际生产中,一般使用这四类指标:

  1. Resource metrics——CPU核 和 内存利用率指标。
  2. Pod metrics——例如网络利用率和流量。
  3. Object metrics——特定对象的指标,比如Ingress, 可以按每秒使用请求数来扩展容器。
  4. Custom metrics——自定义监控,比如通过定义服务响应时间,当响应时间达到一定指标时自动扩容。

Resource 指标:最基础,但别只依赖它
Resource 指标是 HPA 最经典的指标类型,通过 Metrics Server 从 kubelet 采集 CPU 和内存数据。

但这里有个大坑:HPA 计算 CPU 利用率是基于  Request 而不是 Limit。如果 Pod 里的某个容器没有设置 Request,HPA 就无法计算利用率,会直接报错 failed to get cpu utilization: missing request for cpu。很多新手只给业务容器设了 Request,忘了给 sidecar 设,结果 HPA 完全不动。

不过要注意,“没有 Request 就完全不工作”这个说法并不完整。准确地说:如果容器没有设置 Resource Request,HPA 无法基于 Utilization(利用率)类型进行扩缩容,但如果使用 Absolute(绝对用量)类型(如 averageValue),则仍然可以工作。也就是说,你可以绕过利用率,直接基于绝对 CPU 用量来扩缩容。

而且,未设置 Request 的容器还会“稀释”整个 Pod 的利用率计算——在混合计算中,这些容器会拉低整体平均值,可能导致基于 Resource 指标的 HPA 扩容不灵敏甚至完全不扩容。这正是 ContainerResource 指标存在的核心价值之一。

另外,CPU 和内存利用率只能反映容器层面的“健康状况”,无法反映真实的业务负载。比如你的服务是 I/O 密集型的,CPU 利用率低但 QPS 暴涨,这时候用 CPU HPA 就完全是“瞎子”。

HPA 工作原理:控制循环、算法与核心参数

控制循环:15 秒是周期,不是响应时间

Kubernetes 将 HPA 实现为一个间歇运行的控制回路,不是一个连续的过程。

控制周期由 kube-controller-manager 的 --horizontal-pod-autoscaler-sync-period 参数控制,默认 15 秒。该参数可以在 kube-controller-manager 的启动配置中调整。但请注意:15 秒是控制循环的检查间隔,不是端到端的响应时间——指标采集、计算、API 调用都有额外延迟。实际端到端的扩缩容通常在 30 秒到几分钟不等,具体取决于 Metrics Server 的采集周期、API 响应速度以及控制器的调度情况。
在每个周期内,HPA 控制器会:

  1. 根据 scaleTargetRef 找到目标资源(如 Deployment)
  2. 通过 .spec.selector 选择 Pod
  3. 从对应的 Metrics API 获取指标数据
  4. 计算期望副本数
  5. 通过 Scale 子资源调整副本数

核心算法:期望副本数怎么算?

HPA 计算期望副本数的公式是:

期望副本数 = ceil(当前副本数 × 当前指标值 / 期望指标值)

举个例子:当前有 10 个 Pod,CPU 平均利用率是 50%,期望利用率是 80%:

期望副本数 = ceil(10 × 50% / 80%) = ceil(6.25) = 7

所以 HPA 会把副本数从 10 缩到 7。

如果配置了多个指标,HPA 会分别计算每个指标的期望副本数,取最大值作为最终结果。但这里有一个容易被忽略的细节:当某个指标无法获取时

  • 扩容时:只要还有其他指标推荐扩容,HPA 仍然可以扩容
  • 缩容时:如果某个指标无法获取且剩余指标推荐缩容,HPA 会跳过缩容

也就是说:指标获取失败会阻止缩容,但不会阻止扩容。这个设计是为了防止指标采集抖动导致误缩容,但排障时如果发现 HPA“只扩不缩”,记得检查一下是不是有指标挂了。

但这里要特别提醒:对于 ContainerResource 类型的指标,情况有所不同。在较老版本中,ContainerResource 指标缺失会导致 HPA 完全无法计算任何决策。这个行为在近期版本中已经被修复,使其与 Resource 类型指标的行为保持一致。如果你使用的是 1.26 或更早版本,需要特别注意这一点。

另外还有一个关键细节:HPA 在计算之前还会考虑 Pod 是否就绪(Ready)或正在终止(Terminating) ——未就绪或正在终止的 Pod 会被排除在指标计算之外。而且,Pod 变成 Ready 后,还需要经过 --horizontal-pod-autoscaler-initial-readiness-delay(默认 30 秒)才会被纳入指标计算。这个延迟是为了避免 Pod 刚启动时指标不稳定就触发扩缩容。如果 Ready 的 Pod 数量太少,HPA 也可能不会执行扩缩容。这点排障时也值得留意。

容忍度(Tolerance):那个让你困惑的 10%

HPA 中存在一个容忍度(Tolerance) ,默认是 0.1(即 10%)。

无论是否配置了 tolerance 字段,HPA 始终存在容忍度机制,默认值为 10% 。它的作用是:当 当前指标值 / 期望指标值 的比例在 0.9 到 1.1 之间时,HPA 不会触发任何扩缩容动作。

这也就是为什么你配置了 CPU 阈值 50%,实际到了 55% 还没扩容——因为 55/50 = 1.1,刚好在容忍边界上。

这里要特别留意版本差异:在较旧的 Kubernetes 版本中,这个 10% 的容忍度是集群级别的,只能通过 kube-controller-manager 的 --horizontal-pod-autoscaler-tolerance 全局参数修改。

从 Kubernetes 1.33 开始,HPAConfigurableTolerance 特性门控以 Alpha 状态引入。你需要显式启用该特性门控才能使用。从 Kubernetes 1.35 开始,该特性门控被提升为 Beta 并默认开启。启用后,你可以在每个 HPA 上单独配置容忍度,而且扩容和缩容可以设不同的值:

behavior:
  scaleDown:
    tolerance: 0.05   # 缩容容忍度 5%
  scaleUp:
    tolerance: 0      # 扩容容忍度 0%,更灵敏

Behavior 配置:精细控制扩缩容节奏

从 Kubernetes 1.23 开始,behavior 字段正式 GA。你可以通过它精细控制扩容和缩容的策略。

下面是一个完整示例:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-app
spec:
  minReplicas: 2
  maxReplicas: 50
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: 1000    # 每个 Pod 平均 1000 QPS
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60   # 扩容观察 60 秒
      policies:
      - type: Percent
        value: 100
        periodSeconds: 60      # 每 60 秒最多扩容 100%
      - type: Pods
        value: 4
        periodSeconds: 60      # 每 60 秒最多扩容 4 个 Pod
      selectPolicy: Max        # 取两种策略中扩容数量更大的
    scaleDown:
      stabilizationWindowSeconds: 300  # 缩容观察 5 分钟
      policies:
      - type: Percent
        value: 10
        periodSeconds: 60      # 每 60 秒最多缩容 10%
      selectPolicy: Min

这个配置的关键点:

  • 扩容时最多每 60 秒翻一倍或加 4 个 Pod
  • 缩容时有 5 分钟稳定窗口,且每 60 秒最多缩 10%
  • 适合有流量突发的关键业务:快速扩容应对高峰,缓慢缩容防止下一个高峰

注意:如果你打算把上面这个 YAML 直接用到 1.33 或 1.34 集群中,注意 tolerance 字段需要显式开启 HPAConfigurableTolerance 特性门控才会生效;1.35+ 默认开启。如果特性门控未开启,tolerance 字段会被静默忽略,不会报错——这个坑非常隐蔽。

版本演进:别再用 v1 和 v2beta2 了

版本 状态 说明
autoscaling/v1 已弃用 只支持单个 CPU 指标
autoscaling/v2beta2 Kubernetes 1.26 中正式移除 1.25 是最后一个支持版本
autoscaling/v2 GA(Kubernetes 1.23+) 当前推荐使用

简单案例:

v1

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler 
metadata:
  name: hpa_name
spec:
  maxReplicas: 5
  minReplicas: 2 
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  targetCPUUtilizationPercentage: 60

v2

apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: hpa_name
spec :
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  minReplicas: 3
  maxReplicas: 10 
  metrics:
  - type: Resource
    resource:
      name: cpu
      targetAverageUtilization: 80
    resource:
      name: memory
      targetAverageValue: 800Mi

v2

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: hpa-nginx
spec:
  maxReplicas: 10 # 最大扩容到10个节点(pod)
  minReplicas: 1 # 最小扩容1个节点(pod)
  metrics:
  - resource:
      name: cpu
      target:
        averageUtilization: 50 # CPU 平局资源使用率达到50%就开始扩容,低于50%就是缩容
        # 设置内存
        # AverageValue:50
        type: Utilization
    type: Resource
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hpa-nginx
---
apiVersion: v1
kind: Service
metadata:
  name: hpa-test
spec:
  type: NodePort
  ports:
    - name: "http"
      port: 80
      targetPort: 80
      nodePort: 30080
  selector:
    service: hpa-test
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hpa-test
spec:
  replicas: 1
  selector:
    matchLabels:
      service: hpa-test
  template:
    metadata:
      labels:
        service: hpa-test
    spec:
      containers:
        - name: hpa-test
          image: nginx:latest
          resources:
            requests:
              cpu: 100m
              memory: 100Mi
            limits:
              cpu: 200m
              memory: 200Mi

主要参数解释如下:

  • scaleTargetRef:目标作用对象,可以是Deployment、ReplicationController或ReplicaSet。
  • minReplicas和maxReplicas:Pod副本数量的最小值和最大值,系统将在这个范围内进行自动扩缩容操作,并维持每个Pod的内存使用率为50%,这个值就是上面设置的阈值averageUtilization。
  • metrics:目标指标值。在metrics中通过参数type定义指标的类型;通过参数target定义相应的指标目标值,系统将在指标数据达到目标值时(考虑容忍度的区间,见前面算法部分的说明)触发扩缩容操作。
  • 对于CPU使用率,在target参数中设置averageUtilization定义目标平均CPU使用率。
  • 对于内存资源,在target参数中设置AverageValue定义目标平均内存使用值。

压测:

ab -n 10000 -c 800 http://192.168.1.1:30080/

最佳实践

  • 先定好 Resource Request:HPA 计算 CPU 利用率是基于 Request 而不是 Limit。Request 设得太大,利用率永远上不去;设得太小,Pod 容易被杀死。我推荐设置 Request = Limit 来保证资源独占。每个容器都要设,漏了一个 HPA 就可能不工作。
  • 配合 Cluster Autoscaler:HPA 只管 Pod 扩缩,节点资源不够了还得靠 CA。两者要协同工作。
  • 别一开始就把规则搞复杂:先选一两个高相关指标,经过压测和灰度验证后再推广。
  • 配置 PodDisruptionBudget:防止 HPA 在节点维护时瞬间杀死所有 Pod。
  • 关注冷启动延迟:镜像太大、启动脚本太复杂,扩容上去也扛不住流量。

要点总结

  • HPA v2(autoscaling/v2)是当前标准,支持 Resource、ContainerResource、Pods、Object、External 五种指标类型
  • 核心算法:期望副本数 = ceil(当前副本数 × 当前指标 / 期望指标),多指标时取最大值
  • HPA 始终存在容忍度机制,默认 10%(集群级别)——1.33 引入可配置容忍度(Alpha,需显式启用),1.35 起默认开启
  • 缩容默认 5 分钟稳定窗口,扩容默认立即执行——这是为了防止抖动