水平 Pod 自动扩缩
在 Kubernetes 中,*HorizontalPodAutoscaler* 会自动更新工作负载资源(例如 Deployment 或 StatefulSet),目的是自动根据需求伸缩工作负载。
水平伸缩(Horizontal scaling)是指应对负载增加的方法是部署更多Pod。这与*垂直*伸缩(vertical scaling)不同,在 Kubernetes 中垂直伸缩意味着为工作负载已运行的 Pod 分配更多资源(例如:内存或 CPU)。
如果负载降低,并且 Pod 数量高于配置的最小值,HorizontalPodAutoscaler 会指示工作负载资源(Deployment、StatefulSet 或其他类似资源)缩容。
Horizontal Pod Autoscaling 不适用于无法伸缩的对象(例如:DaemonSet)。
HorizontalPodAutoscaler 是作为 Kubernetes API 资源和控制器实现的。该资源决定了控制器的行为。horizontal pod autoscaling 控制器运行在 Kubernetes 的控制平面内,会周期性地根据平均 CPU 利用率、平均内存利用率或任何其他你指定的自定义指标等观测到的指标来调整其目标(例如 Deployment)的期望规模。
有一个使用 horizontal pod autoscaling 的操作演练示例。
HorizontalPodAutoscaler 如何工作?
图 1. HorizontalPodAutoscaler 控制 Deployment 及其 ReplicaSet 的规模
Kubernetes 将 horizontal pod autoscaling 实现为一个间歇运行的控制回路(它不是一个连续过程)。该间隔由 kube-controller-manager
的 --horizontal-pod-autoscaler-sync-period
参数设置(默认间隔为 15 秒)。
在每个周期内,控制器管理器都会根据每个 HorizontalPodAutoscaler 定义中指定的指标来查询资源利用率。控制器管理器找到 scaleTargetRef
定义的目标资源,然后根据目标资源的 .spec.selector
标签选择 Pod,并从资源指标 API(针对每个 Pod 的资源指标)或自定义指标 API(针对所有其他指标)获取指标。
对于每个 Pod 的资源指标(例如 CPU),控制器会从资源指标 API 为 HorizontalPodAutoscaler 针对的每个 Pod 获取指标。然后,如果设置了目标利用率值,控制器会将利用率值计算为每个 Pod 中容器的等效资源请求的百分比。如果设置了目标原始值,则直接使用原始指标值。然后,控制器计算所有目标 Pod 的利用率或原始值(取决于指定目标的类型)的平均值,并生成用于调整期望副本数量的比率。
请注意,如果 Pod 的某些容器未设置相关的资源请求,则该 Pod 的 CPU 利用率将未定义,并且自动扩缩器不会对该指标采取任何行动。有关自动扩缩算法如何工作的更多信息,请参阅下面的算法细节部分。
对于每个 Pod 的自定义指标,控制器的工作方式类似于每个 Pod 的资源指标,不同之处在于它使用原始值而不是利用率值。
对于对象指标和外部指标,会获取一个描述相关对象的单一指标。该指标与目标值进行比较,以生成如上所述的比率。在
autoscaling/v2
API 版本中,在进行比较之前,该值可以选择性地除以 Pod 的数量。
HorizontalPodAutoscaler 的常见用途是配置其从聚合 API(metrics.k8s.io
、custom.metrics.k8s.io
或 external.metrics.k8s.io
)获取指标。metrics.k8s.io
API 通常由一个名为 Metrics Server 的插件提供,需要单独启动。有关资源指标的更多信息,请参阅Metrics Server。
对指标 API 的支持部分解释了这些不同 API 的稳定性保证和支持状态。
HorizontalPodAutoscaler 控制器访问支持扩缩的相应工作负载资源(例如 Deployments 和 StatefulSet)。这些资源都具有一个名为 scale
的子资源,这是一个允许你动态设置副本数量并检查每个当前状态的接口。有关 Kubernetes API 中子资源的一般信息,请参阅Kubernetes API 概念。
算法细节
从最基本的角度来看,HorizontalPodAutoscaler 控制器根据期望指标值与当前指标值之间的比率进行操作
例如,如果当前指标值为 200m
,期望值为 100m
,则副本数量将翻倍,因为 \( { 200.0 \div 100.0 } = 2.0 \)。
如果当前值是 50m
,则副本数量将减半,因为 \( { 50.0 \div 100.0 } = 0.5 \)。如果比率足够接近 1.0(在可配置的容忍度内,默认为 0.1),控制平面会跳过任何扩缩操作。
当指定 targetAverageValue
或 targetAverageUtilization
时,通过计算 HorizontalPodAutoscaler 的扩缩目标中所有 Pod 的给定指标的平均值来计算 currentMetricValue
。
在检查容忍度并决定最终值之前,控制平面还会考虑是否有任何指标缺失,以及有多少 Pod 处于Ready
状态。所有设置了删除时间戳的 Pod(设置了删除时间戳的对象正在被关停/移除)会被忽略,所有失败的 Pod 会被丢弃。
如果某个特定的 Pod 缺少指标,则会被暂时搁置;缺少指标的 Pod 将用于调整最终的扩缩数量。
在基于 CPU 进行扩缩时,如果任何 Pod 尚未变为就绪状态(仍在初始化,或者可能不健康),*或者*该 Pod 最近的指标点在其变为就绪状态之前产生,那么该 Pod 也将被搁置。
由于技术限制,HorizontalPodAutoscaler 控制器在确定是否搁置某些 CPU 指标时,无法精确确定 Pod 第一次变为就绪状态的时间。相反,如果一个 Pod 尚未就绪,并且自其启动以来在一段短的、可配置的时间窗口内转换到就绪状态,则控制器会认为该 Pod“尚未就绪”。此值通过 --horizontal-pod-autoscaler-initial-readiness-delay
标志进行配置,默认为 30 秒。一旦 Pod 变为就绪状态,如果在自其启动以来一段更长的、可配置的时间内发生了任何转换到就绪状态的情况,则控制器会认为这是第一次转换。此值通过 --horizontal-pod-autoscaler-cpu-initialization-period
标志进行配置,默认为 5 分钟。
然后,使用上面未被搁置或丢弃的剩余 Pod 计算 \( currentMetricValue \over desiredMetricValue \) 基本扩缩比率。
如果存在任何缺失的指标,控制平面会更保守地重新计算平均值,在缩容的情况下假设这些 Pod 消耗了 100% 的期望值,在扩容的情况下假设消耗了 0%。这会减弱任何潜在扩缩的幅度。
此外,如果存在任何尚未就绪的 Pod,并且在不考虑缺失指标或尚未就绪 Pod 的情况下工作负载会扩容,则控制器会保守地假设尚未就绪的 Pod 消耗了 0% 的期望指标,进一步减弱扩容的幅度。
在考虑了尚未就绪的 Pod 和缺失的指标后,控制器会重新计算使用率。如果新的比率反转了扩缩方向,或者在容忍度范围内,则控制器不会采取任何扩缩行动。在其他情况下,新的比率用于决定对 Pod 数量进行的任何更改。
请注意,即使使用新的使用率,通过 HorizontalPodAutoscaler 状态报告的平均利用率的*原始*值,不会考虑尚未就绪的 Pod 或缺失的指标。
如果在一个 HorizontalPodAutoscaler 中指定了多个指标,则会对每个指标执行此计算,然后选择期望副本数中最大的那个。如果这些指标中的任何一个无法转换为期望副本数(例如,由于从指标 API 获取指标时出错),并且可获取的指标建议缩容,则会跳过扩缩。这意味着如果一个或多个指标给出大于当前值的 desiredReplicas
,HPA 仍然能够扩容。
最后,在 HPA 扩缩目标之前,会记录扩缩建议。控制器会考虑可配置时间窗口内的所有建议,并从该窗口内选择最高的建议。此值可以使用 --horizontal-pod-autoscaler-downscale-stabilization
标志进行配置,默认值为 5 分钟。这意味着缩容将逐渐发生,从而平滑快速波动的指标值的影响。
API 对象
Horizontal Pod Autoscaler 是 Kubernetes autoscaling
API 组中的一个 API 资源。当前的稳定版本位于 autoscaling/v2
API 版本中,该版本支持基于内存和自定义指标进行扩缩。在使用 autoscaling/v1
时,autoscaling/v2
中引入的新字段会保留为注解。
创建 HorizontalPodAutoscaler API 对象时,请确保指定的名称是有效的DNS 子域名。有关 API 对象的更多详细信息,请参阅HorizontalPodAutoscaler 对象。
工作负载规模的稳定性
当使用 HorizontalPodAutoscaler 管理一组副本的规模时,由于评估指标的动态性,副本数量可能会频繁波动。这有时被称为*抖动(thrashing)*或*扑动(flapping)*。这类似于控制论中的*滞后(hysteresis)*概念。
滚动更新期间的自动扩缩
Kubernetes 允许你对 Deployment 执行滚动更新。在这种情况下,Deployment 会为你管理底层的 ReplicaSet。为你配置 Deployment 的自动扩缩时,你会将 HorizontalPodAutoscaler 绑定到单个 Deployment。HorizontalPodAutoscaler 管理 Deployment 的 replicas
字段。deployment 控制器负责设置底层 ReplicaSet 的 replicas
,以便在回滚和之后,它们副本数之和达到合适的数量。
如果你对具有自动扩缩副本数的 StatefulSet 执行滚动更新,StatefulSet 会直接管理其 Pod 集合(没有类似于 ReplicaSet 的中间资源)。
对资源指标的支持
任何 HPA 目标都可以基于扩缩目标中 Pod 的资源使用情况进行扩缩。定义 Pod 规约时,应指定 cpu
和 memory
等资源请求。这用于确定资源利用率,并由 HPA 控制器用于向上或向下扩缩目标。要使用基于资源利用率的扩缩,请按如下方式指定指标源:
type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
使用此指标,HPA 控制器会将扩缩目标中 Pod 的平均利用率保持在 60%。利用率是资源当前使用量与 Pod 请求的资源量之间的比率。有关利用率如何计算和求平均值的更多详细信息,请参阅算法部分。
说明
由于所有容器的资源使用量都被加总,总的 Pod 利用率可能无法准确反映单个容器的资源使用情况。这可能导致单个容器运行在高使用率下,而 HPA 不会扩容,因为 Pod 的总体使用量仍在可接受的范围内。容器资源指标
Kubernetes v1.30 [stable]
(默认启用: true)HorizontalPodAutoscaler API 还支持容器指标源,HPA 可以跟踪一组 Pod 中单个容器的资源使用情况,以便扩缩目标资源。这允许你为特定 Pod 中最重要的容器配置扩缩阈值。例如,如果你有一个 Web 应用程序和一个提供日志记录的 Sidecar 容器,你可以根据 Web 应用程序的资源使用情况进行扩缩,而忽略 Sidecar 容器及其资源使用。
如果你修改目标资源以具有包含不同容器集的新 Pod 规约,并且新添加的容器也应用于扩缩,则应修改 HPA 规约。如果指标源中指定的容器不存在,或者只存在于部分 Pod 中,则这些 Pod 将被忽略,并重新计算建议。有关计算的更多详细信息,请参阅算法。要使用容器资源进行自动扩缩,请按如下方式定义指标源:
type: ContainerResource
containerResource:
name: cpu
container: application
target:
type: Utilization
averageUtilization: 60
在上面的示例中,HPA 控制器会扩缩目标,使得所有 Pod 中 application
容器的 CPU 平均利用率为 60%。
说明
如果你更改了 HorizontalPodAutoscaler 正在跟踪的容器名称,你可以按特定顺序进行更改,以确保在应用更改时扩缩仍然可用且有效。在你更新定义容器的资源(例如 Deployment)之前,应更新关联的 HPA 以跟踪新的和旧的容器名称。这样,HPA 就能在整个更新过程中计算出扩缩建议。
将容器名称更改滚动部署到工作负载资源后,通过从 HPA 规约中移除旧容器名称来清理。
基于自定义指标进行扩缩
Kubernetes v1.23 [stable]
(之前的 autoscaling/v2beta2
API 版本曾将此能力作为 Beta 特性提供)
只要你使用 autoscaling/v2
API 版本,就可以配置 HorizontalPodAutoscaler 基于自定义指标(非 Kubernetes 或任何 Kubernetes 组件内置的指标)进行扩缩。HorizontalPodAutoscaler 控制器随后会从 Kubernetes API 查询这些自定义指标。
有关要求,请参阅对指标 API 的支持。
基于多个指标进行扩缩
Kubernetes v1.23 [stable]
(之前的 autoscaling/v2beta2
API 版本曾将此能力作为 Beta 特性提供)
只要你使用 autoscaling/v2
API 版本,就可以为 HorizontalPodAutoscaler 指定多个指标进行扩缩。然后,HorizontalPodAutoscaler 控制器会评估每个指标,并根据该指标提出新的规模。HorizontalPodAutoscaler 会采用每个指标推荐的最大规模,并将工作负载设置为该规模(前提是不超过你配置的总最大值)。
对指标 API 的支持
默认情况下,HorizontalPodAutoscaler 控制器从一系列 API 获取指标。为了让它能够访问这些 API,集群管理员必须确保:
API 聚合层已启用。
相应的 API 已注册:
对于资源指标,这是
metrics.k8s.io
API,通常由 metrics-server 提供。它可以作为集群插件启动。对于自定义指标,这是
custom.metrics.k8s.io
API。它由指标解决方案供应商提供的“适配器”API 服务器提供。请检查你的指标管线,看看是否有可用的 Kubernetes 指标适配器。对于外部指标,这是
external.metrics.k8s.io
API。它可能由上面提供的自定义指标适配器提供。
有关这些不同指标路径及其差异的更多信息,请参阅有关HPA V2、custom.metrics.k8s.io 和 external.metrics.k8s.io 的相关设计提案。
有关如何使用它们的示例,请参阅使用自定义指标的演练和使用与 Kubernetes 对象无关的指标的演练。
可配置的扩缩行为
Kubernetes v1.23 [stable]
(之前的 autoscaling/v2beta2
API 版本曾将此能力作为 Beta 特性提供)
如果你使用 v2
HorizontalPodAutoscaler API,则可以使用 behavior
字段(请参阅API 参考)来配置单独的扩容和缩容行为。你可以通过在 behavior
字段下设置 scaleUp
和/或 scaleDown
来指定这些行为。
扩缩策略允许你在扩缩时控制副本的变化速率。此外,可以使用两个设置来防止抖动:你可以指定一个用于平滑副本计数的*稳定窗口(stabilization window)*,以及一个用于忽略低于指定阈值的微小指标波动的容忍度。
扩缩策略
可以在规约的 behavior
部分指定一个或多个扩缩策略。当指定多个策略时,默认选择允许最大变化量的策略。以下示例展示了缩容时的这种行为:
behavior:
scaleDown:
policies:
- type: Pods
value: 4
periodSeconds: 60
- type: Percent
value: 10
periodSeconds: 60
periodSeconds
指示策略必须在过去某个时间段内保持为真的时长。你可以为 periodSeconds
设置的最大值为 1800(半小时)。第一个策略*(Pod)*允许在一分钟内最多缩容 4 个副本。第二个策略*(百分比)*允许在一分钟内最多缩容当前副本的 10%。
由于默认选择允许最大变化量的策略,只有当 Pod 副本数大于 40 时才会使用第二个策略。当副本数为 40 或更少时,将应用第一个策略。例如,如果当前有 80 个副本并且目标需要缩容到 10 个副本,那么在第一步将减少 8 个副本。在下一次迭代中,当副本数为 72 时,Pod 数量的 10% 是 7.2,但该数字向上取整为 8。在自动扩缩器控制器的每次循环中,要更改的 Pod 数量会根据当前副本数重新计算。当副本数降至 40 以下时,将应用第一个策略*(Pod)*,每次减少 4 个副本。
可以指定扩缩方向的 selectPolicy
字段来更改策略选择。将值设置为 Min
会选择允许副本数量变化最小的策略。将值设置为 Disabled
会完全禁用该方向的扩缩。
稳定窗口
当用于扩缩的指标持续波动时,稳定窗口用于限制副本数量的抖动。自动扩缩算法使用此窗口推断先前的期望状态,从而避免对工作负载规模进行不必要的更改。
例如,在以下示例片段中,为 scaleDown
指定了一个稳定窗口。
behavior:
scaleDown:
stabilizationWindowSeconds: 300
当指标指示目标应该缩容时,算法会查看先前计算的期望状态,并使用指定间隔内的最高值。在上面的示例中,将考虑过去 5 分钟内的所有期望状态。
这近似于一个滚动最大值,并避免了扩缩算法频繁移除 Pods,结果却在片刻之后又触发重新创建等效的 Pod。
容忍度
Kubernetes v1.33 [alpha]
(默认启用: false)tolerance
字段配置了指标变化的阈值,防止自动扩缩器对低于该值的变化进行扩缩。
此容忍度定义为所需指标值周围的变化量,低于此变化量将不会触发扩缩。例如,考虑一个 HorizontalPodAutoscaler,它被配置为目标内存消耗为 100MiB,向上扩缩容忍度为 5%。
behavior:
scaleUp:
tolerance: 0.05 # 5% tolerance for scale up
使用此配置,HPA 算法仅在内存消耗高于 105MiB 时(即:高于目标值 5%)才会考虑向上扩缩。
如果未设置此字段,HPA 会应用默认的集群范围容忍度 10%。此默认值可以通过 kube-controller-manager 的 --horizontal-pod-autoscaler-tolerance
命令行参数更新,对向上和向下扩缩都有效。(你不能使用 Kubernetes API 配置此默认值。)
默认行为
要使用自定义扩缩,并非所有字段都必须指定。只需指定需要自定义的值即可。这些自定义值将与默认值合并。默认值与 HPA 算法中的现有行为相符。
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
- type: Pods
value: 4
periodSeconds: 15
selectPolicy: Max
对于向下扩缩,稳定窗口为 300 秒(如果提供了 --horizontal-pod-autoscaler-downscale-stabilization
标志,则使用其值)。向下扩缩只有一条策略,允许移除当前正在运行的副本数的 100%,这意味着扩缩目标可以缩容到允许的最小副本数。对于向上扩缩,没有稳定窗口。当指标表明目标应向上扩缩时,目标会立即向上扩缩。有 2 条策略,最多允许每 15 秒添加 4 个 pods 或当前正在运行副本数的 100%,直到 HPA 达到其稳态。
示例:修改向下扩缩稳定窗口
要提供自定义的 1 分钟向下扩缩稳定窗口,以下行为将添加到 HPA 中
behavior:
scaleDown:
stabilizationWindowSeconds: 60
示例:限制缩容速率
要将 HPA 移除 Pods 的速率限制为每分钟 10%,以下行为将添加到 HPA 中
behavior:
scaleDown:
policies:
- type: Percent
value: 10
periodSeconds: 60
为确保每分钟移除的 Pods 不超过 5 个,你可以添加一个具有固定大小 5 的第二条缩容策略,并将 selectPolicy
设置为 minimum。将 selectPolicy
设置为 Min
意味着自动扩缩器会选择影响 Pods 数量最少的策略。
behavior:
scaleDown:
policies:
- type: Percent
value: 10
periodSeconds: 60
- type: Pods
value: 5
periodSeconds: 60
selectPolicy: Min
示例:禁用缩容
selectPolicy
的值 Disabled
会禁用给定方向的扩缩。因此,要防止缩容,将使用以下策略
behavior:
scaleDown:
selectPolicy: Disabled
kubectl 中对 HorizontalPodAutoscaler 的支持
HorizontalPodAutoscaler 与每个 API 资源一样,受到 kubectl
的标准支持。你可以使用 kubectl create
命令创建新的自动扩缩器。你可以通过 kubectl get hpa
列出自动扩缩器,或通过 kubectl describe hpa
获取详细描述。最后,你可以使用 kubectl delete hpa
删除自动扩缩器。
此外,还有一个特殊的 kubectl autoscale
命令用于创建 HorizontalPodAutoscaler 对象。例如,执行 kubectl autoscale rs foo --min=2 --max=5 --cpu-percent=80
将为 ReplicaSet foo 创建一个自动扩缩器,目标 CPU 利用率设置为 80%
,副本数在 2 到 5 之间。
隐式维护模式停用
你可以隐式停用目标的 HPA,而无需更改 HPA 配置本身。如果目标的期望副本数设置为 0,并且 HPA 的最小副本数大于 0,HPA 会停止调整目标(并将其自身的 ScalingActive
Condition 设置为 false
),直到你通过手动调整目标的期望副本数或 HPA 的最小副本数来重新激活它。
将 Deployments 和 StatefulSets 迁移到横向自动扩缩
启用 HPA 时,建议从 Deployment 和/或 StatefulSet 的 清单 中移除 spec.replicas
的值。如果不这样做,则每当对该对象应用更改时(例如通过 kubectl apply -f deployment.yaml
),Kubernetes 将根据 spec.replicas
键的值来扩缩当前 Pods 数量。这可能不是期望的行为,并且在 HPA 处于活动状态时可能会导致问题,从而产生抖动或翻动行为。
请记住,移除 spec.replicas
可能会导致 Pods 数量的一次性降级,因为此键的默认值为 1(参考 Deployment 副本数)。更新后,除了 1 个 Pod 之外,所有其他 Pods 将开始它们的终止过程。之后的任何部署应用都将像正常情况一样,并遵守所需的滚动更新配置。你可以通过根据修改部署的方式选择以下两种方法之一来避免这种降级
kubectl apply edit-last-applied deployment/<deployment_name>
- 在编辑器中,移除
spec.replicas
。当你保存并退出编辑器时,kubectl
将应用更新。在此步骤不会发生 Pods 数量的变化。 - 现在你可以从清单中移除
spec.replicas
。如果你使用源代码管理,也请提交你的更改或根据你跟踪更新的方式采取任何其他适当的修改源代码的步骤。 - 从现在开始,你可以运行
kubectl apply -f deployment.yaml
下一步
如果你在集群中配置了自动扩缩,你可能还需要考虑使用节点自动扩缩来确保你运行了正确的节点数量。
更多关于 HorizontalPodAutoscaler 的信息
- 阅读横向 Pod 自动扩缩的演练示例。
- 阅读关于
kubectl autoscale
的文档。 - 如果你想编写自己的自定义指标适配器,请查看样板开始。
- 阅读 HorizontalPodAutoscaler 的API 参考。