ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Kubernetes自动缩放实战:HPA、VPA与Cluster Autoscaler全解析

Kubernetes自动缩放实战:HPA、VPA与Cluster Autoscaler全解析 忙到连喝水的时间都没有的那个下午线上服务的响应曲线突然起飞。我一边盯着 Grafana 的告警一边直接kubectl scale deployment xxx --replicas5硬顶五分钟之后又觉得顶多了再缩回来。事后复盘时我意识到一件事如果不把 Kubernetes 自动缩放这套机制从原理到落地彻底吃透类似的扩容全靠手速的尴尬迟早还会重演。Kubernetes 自动缩放不是某一个单独功能它是一整套围绕指标采集、控制器决策、调度器配合、节点供给的闭环系统。这篇文章我会从 v1.26 环境的实际视角出发讲清楚 HPA、VPA、Cluster Autoscaler 各自的角色和边界然后带着你从零打通指标管道写出一套能基于 CPU 和 QPS 自动扩缩容的 HPA 配置再用压测验证最后把我踩过的生产环境坑一一摊开。适合已经会用 kubectl 部署应用、想真正把自动缩放用稳而不是装个 HPA 就当交差的读者。1. 自动缩放不是单一功能HPA、VPA、Cluster Autoscaler 的边界1.1 先弄清楚三种工具分别管什么很多朋友一上来就问HPA 怎么配其实 Kubernetes 生态里的自动缩放至少拆成三层。HPA 管的是副本数量它只调整 Deployment、StatefulSet 这批工作负载的replicas字段不会去动单个 Pod 的 CPU 或内存配置VPA 管的是 Pod 的requests/limits也就是每个 Pod 应该分配多少资源Cluster Autoscaler 管的是节点数量节点不够、Pod 调度不上去的时候它负责把新节点拉起来。我习惯用一个类比HPA 是给摊位加人手VPA 是给每个人加大饭量CA 是加开摊位。三者的作用对象完全不同却经常被混着讨论。你可以只装 HPA 就跑业务但流量一旦超过节点容量HPA 扩出来的 Pod 全躺在 Pending 状态等于白扩你也可以只装 CA但它不会因为某个 Deployment 变热而主动加节点它只盯着调度器里排队的 Pod。工具作用对象伸缩维度主要判断依据响应速度HPADeployment / StatefulSet副本数CPU、内存、自定义业务指标分钟级VPAPod 的资源请求值单 Pod 的 requests/limits历史资源用量统计分钟级需重启 PodCluster AutoscalerNode节点数Pod 是否 Unschedulable / Pending分钟到十几分钟1.2 为什么很多人只用了 HPA 还是炸我见过不止一次这样的场景某局点流量突然上涨HPA 把 Pod 从 3 个扩到 20 个但集群一共 4 台节点资源早就打满了结果kubectl get pods里一大片 Pending。这种状态下请求照样进不来因为真正 Ready 的副本数并没有增加。只看 HPA 的REPLICAS数字很容易产生错觉——副本数是 20可用副本数是 10这是两个概念。另一种情况是请求量大但 CPU 不高。比如网关服务大量时间在等下游数据库返回CPU 一直只有 20%QPS 却翻了三倍。CPU 型 HPA 触发不了直到请求堆积导致 CPU 上来才开始扩此时影响已经发生。这类业务必须把 QPS、连接数、队列深度等业务指标接进 HPA而不是只盯着 CPU。还有一类问题内存压力。HPA 默认配 CPU 指标内存涨上去但 CPU 没动副本不扩单个 Pod 反复 OOMKilled。这背后可能是应用的请求量没变但某个 Pod 的内存泄漏或缓存持续增长此时真正该用的是 VPA 或者应用层治理而不是继续加副本。1.3 我的选型建议无状态 API 服务我的固定组合是 HPA 加 CAHPA 优先基于 QPS 这类业务指标CPU 作为兜底指标。有状态组件或者单体应用VPA 更合适但默认用updateMode: Off先跑一个月看推荐值别一上来就 Auto 让它自动重启 Pod。不管选哪种maxReplicas必须设上限不设上限的 HPA 等于把成本开关交给流量同时maxReplicas也要匹配底层节点池的容量上限否则 HPA 和 CA 会互相顶着上限空转。2. 先把指标管道打通Metrics Server 与 HPA 的取数逻辑2.1 HPA 到底从哪拿数据如果你的 kubeadm 集群刚在 v1.26.0 上初始化完成日志里看到[preflight] running pre-flight checks只是第一步真正影响自动缩放的是指标管道。HPA 控制器本身不直接采集指标它通过metrics.k8s.io这个 API 读取数据。metrics.k8s.io由 Metrics Server 实现而 Metrics Server 又从每个节点的 kubelet Summary API 拿数据底层其实是 cAdvisor 采集的容器资源使用量。整条链路是kubelet 采集容器指标 - Metrics Server 聚合 -metrics.k8s.ioAPI 暴露 - HPA controller 周期性读取。所以排错的时候有个非常实用的判断标准如果kubectl top nodes或kubectl top pods没输出那 HPA 一定也是没有数据可用。先解决top命令再玩 HPA这句话能省掉后面一半的排错时间。2.2 安装 Metrics Serverv1.26 环境实测Metrics Server 官方安装方式一直是 apply 一份 components.yamlkubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml装完验证三件事kubectl -n kube-system get pods | grep metrics-server kubectl top nodes kubectl top pods -A如果 kubeadm 集群里kubectl top nodes报 x509 证书错误大概率是 Metrics Server 访问 kubelet 时证书校验没过。先看日志kubectl -n kube-system logs -l k8s-appmetrics-server --tail-1常见报错长这样x509: cannot validate certificate for x.x.x.x because it doesnt contain any IP SANs。kubelet 默认签发的证书可能没有包含节点 IP 的 SANMetrics Server 校验失败。我自己的处理方式分两种测试环境直接在 Metrics Server Deployment 的 args 里追加--kubelet-insecure-tls跳过校验生产环境则要给 kubelet 配置正确的 serve cert或者给 Metrics Server 挂上对应的 CA 证书。不要图省事在生产环境直接加 insecure 参数虽然能用但证书校验形同虚设。2.3 HPA 的计算公式和两个关键参数HPA 的扩缩容计算不复杂核心公式是desiredReplicas ceil(currentReplicas * (currentMetricValue / desiredMetricValue))这里的currentMetricValue在不同的 target 类型下含义不同。最常用的是Utilization类型它表示所有副本的某种资源平均利用率。举个例子现在有 3 个副本平均 CPU 利用率 90%HPA 的averageUtilization设的是 60%那么期望副本数就是ceil(3 * 90 / 60) 5HPA 会把副本扩到 5 个。反过来5 个副本平均利用率降到 30%期望副本数就是ceil(5 * 30 / 60) 3但缩容不一定立刻发生要等稳定窗口。这里有个必须强调的细节Utilization是平均值不是总用量。它等于 Pod 当前 CPU 使用量除以 Pod 的requests.cpu再乘以 100。所以 Deployment 里的requests字段必须写而且要写得合理。requests.cpu写得越小同样的 CPU 使用量换算出来的利用率百分比就越高HPA 就越容易扩容。我见过不少项目把 requests 拍脑袋写成 50m结果压测时 CPU 利用率飙到 800%HPA 疯狂扩容其实应用真实消耗也就 200m这就是资源画像没做好。使用 v1.26 时还要注意 API 版本。autoscaling/v2在 v1.23 已经 GAv1.26 完全可以放心用包含多指标数组、behavior字段这些能力。老教程里大量出现的autoscaling/v1就不要再用了字段限制太多不支持自定义指标。2.4 指标新鲜度压测时为什么总觉得没反应Metrics Server 默认每 15 秒从 kubelet 拉一次数据并且聚合窗口大约是 1 分钟HPA controller 默认每 15 秒计算一次期望副本数。这意味着从负载真正升高到HPA 发起扩容最快也要几十秒实际压测时通常要等 1 到 3 分钟才能看到副本数变化。压测的时候别每 5 秒刷一次就说没生效给系统一点反应时间。3. 第一套 HPA基于 CPU 利用率从 yaml 写到压测3.1 Deployment 先约定好资源请求先用一个简单的 PHP 服务做 Demo镜像是官方压测常用的registry.k8s.io/hpa-example它对 CPU 压力很敏感适合演示 CPU 型 HPA。apiVersion: apps/v1 kind: Deployment metadata: name: hpa-demo spec: replicas: 1 selector: matchLabels: app: hpa-demo template: metadata: labels: app: hpa-demo spec: containers: - name: php-apache image: registry.k8s.io/hpa-example:latest ports: - containerPort: 80 resources: requests: cpu: 200m limits: cpu: 500mrequests.cpu: 200m是 HPA 计算利用率百分比的基准limits.cpu: 500m是为了防止压测时容器把节点 CPU 打满。这里多说一句生产环境这两个值都不该拍脑袋写最好先压测得到基线再留 20% 到 30% 余量。requests 写太小HPA 会过度扩容limits 写太大节点 CPU 可能被某个异常 Pod 打爆。接着创建 Service让后面的压测工具能访问到kubectl expose deployment hpa-demo --port80 --target-port803.2 HPA 配置逐行拆解apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: hpa-demo spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: hpa-demo minReplicas: 1 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60 behavior: scaleDown: stabilizationWindowSeconds: 300averageUtilization: 60表示期望每个 Pod 平均 CPU 利用率维持在 60% 左右。60% 是常见起步值生产上我会根据业务对延迟的容忍度在 50% 到 70% 之间选。minReplicas测试时设 1 没问题但生产无状态服务我建议至少 2否则节点故障就是完整故障窗口。maxReplicas不能拍脑袋写 20 或 50要结合单节点能跑多少 Pod、以及底层节点池上限来定如果后面接 Cluster AutoscalermaxReplicas对应的资源总量必须小于等于节点池最大容量。创建之后先看看状态是不是正常的kubectl get hpa hpa-demo kubectl describe hpa hpa-demo正常会出现Status: AbleToScale和ScalingActiveTARGETS 列能看到 CPU 利用率的当前值。3.3 压测制造真实 CPU 负载压测工具直接用 busybox 跑一个循环请求kubectl run load-generator --imagebusybox:1.36 -- /bin/sh -c while true; do wget -q -O- http://hpa-demo; done这里有个小坑官方教程里用的 busybox:1.28 在部分集群里会报 DNS 解析失败bad address hpa-demo。我在实际测试中全部改用 busybox:1.36 后基本不再出现。如果还是连不上 Service先检查 Service 是否存在再用kubectl get endpoints hpa-demo确认有后端端点。开启压测后用 watch 模式观察kubectl get hpa hpa-demo -w过一两分钟TARGETS 列的 CPU 利用率会开始上涨然后 REPLICAS 从 1 变成 2、3、4。kubectl describe hpa hpa-demo的 Events 里能看到类似这样的记录New size: 3; reason: CPU resource usage (target average value: 60m, current average usage: 120m)这代表 HPA 认为 60% 是目标现在平均已经 120%所以要扩。整个过程是渐进的不是一次性从 1 跳到 10所以压测后要耐心盯着看。3.4 压测结束后的缩容观察压测结束后删掉 load-generatorkubectl delete pod load-generator缩容不会马上发生。HPA 默认的缩容稳定窗口是 5 分钟控制器需要持续观察到负载低于目标才会开始逐步缩。如果等了 10 分钟还没缩到最低值再看看是不是behavior配置里加了更长的稳定窗口或者指标管道里还有残留数据。3.5 经验CPU HPA 只适合做兜底CPU 型 HPA 最大的问题是滞后。很多服务在等待 IO、等待下游响应时 CPU 并不高但 QPS 已经涨上去了。等 CPU 开始显著上升通常请求队列已经堆积了一波。所以我的结论很明确CPU HPA 是兜底不是主力。真正承载流量洪峰的业务指标 HPA看下一节。4. 自定义指标 HPA基于 QPS 的扩容才是生产级玩法4.1 为什么 CPU 指标扛不住流量型业务拿网关服务举例它大部分时间在转发请求、等待下游返回CPU 利用率可能只有 20%但 QPS 已经翻了三倍。按 CPU 扩容HPA 会等到 CPU 真正涨上去才动手那一刻服务响应已经劣化。更合理的做法是直接拿每秒请求数做扩容依据——例如每个 Pod 目标承载 1000 QPS超过就扩容。Prometheus 能采集到这些业务指标但 HPA 默认读不到中间需要一层适配器把 Prometheus 指标暴露成custom.metrics.k8s.ioAPI这层适配器就是 Prometheus Adapter。4.2 组件结构整条链路变成业务服务暴露/metrics- Prometheus 抓取 - Prometheus Adapter 查询并转换成 Kubernetes 自定义指标 API - HPA controller 按指标计算副本数。部署 Prometheus 不在本文展开假设你已经有一套。Adapter 我一般用 helm 安装helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm upgrade --install prometheus-adapter prometheus-community/prometheus-adapter -f adapter-values.yaml -n monitoring4.3 核心配置Adapter 规则解析真正考验人的是adapter-values.yaml里那段 rules 规则。下面是我常用的配置目标是暴露一个按 Pod 维度聚合的 QPS 指标rules: default: false custom: - seriesQuery: http_requests_total{namespace!,pod!} resources: overrides: namespace: { resource: namespace } pod: { resource: pod } name: matches: ^(.*)_total$ as: ${1}_qps metricsQuery: sum(rate(.Series{.LabelMatchers}[1m])) by (.GroupBy)逐行拆解一下。seriesQuery告诉 Adapter 去 Prometheus 里找哪些指标这里限定必须有namespace和pod标签因为 HPA 需要知道这些指标属于哪个命名空间下的哪些 Pod。resources.overrides把 Prometheus 的标签映射成 Kubernetes 的资源维度这是分组判断的关键。name规则把http_requests_total改成http_requests_qps后缀_total代表 counter 类型转成_qps更贴合速率语义。metricsQuery才是核心算式用rate计算每秒增量[1m]表示 1 分钟窗口然后按 namespace、pod 维度分组求和。最高频的配置错误就是忘记用rate直接把 counter 的累计值暴露给 HPA。这样副本数只会永远上涨因为累计值只增不减每当扩容后新 Pod 加入总累计值还在涨永远到不了目标。这个坑我帮别人排过不下三次。4.4 HPA 配置目标设为每个 Pod 的平均 QPSapiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: qps-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: hpa-demo minReplicas: 2 maxReplicas: 20 metrics: - type: Pods pods: metric: name: http_requests_qps target: type: AverageValue averageValue: 1000type: Pods的语义是把匹配到的 Pod 指标值求平均再和目标值比较。AverageValue: 1000表示每个 Pod 平均承担 1000 QPS超过就扩容。这种配置对 Web 服务非常直观扩容逻辑和容量规划完全对得上。如果你的指标不是按 Pod 维度而是对某个 Service 或整个命名空间统计的可以用type: Object或type: External。比如外部消息队列的积压数没有 Pod 标签就用 external metrics。思路一样只是指标来源不同。4.5 验证和压测先验证 API 是否通了kubectl get apiservice | grep custom.metrics kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/http_requests_qps | jq如果返回空先在 Prometheus 里直接执行http_requests_total{namespacedefault,pod!}确认指标存在。指标存在但 Adapter 不生效多半是 label 映射写错。如果 APIService 是 503顺序排查 Adapter Pod 日志、Prometheus 连通性、规则语法。压测工具我常用 hey直接用并发控制不额外限 QPShey -z 2m -c 200 http://hpa-demo.default.svc.cluster.local/同时观察kubectl get hpa qps-hpa -w高并发压测下QPS 指标会快速超过平均值 1000副本数开始往上走。4.6 多指标并存取最大值生产环境我不建议只配一个 QPS 指标最好把 CPU 也放进去。HPA 会分别计算每个指标对应的期望副本数然后取所有结果里的最大值。也就是说只要 CPU 或 QPS 任何一个指标要求扩容它就扩容但缩容呢必须所有指标都低于目标并稳定一段时间后才会发生。很多人以为多指标是取交集其实是取并集最大者理解这一点对设置 target 很重要。metrics: - type: Pods pods: metric: name: http_requests_qps target: type: AverageValue averageValue: 1000 - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 605. VPA 与 Cluster AutoscalerHPA 之外的配套5.1 VPA 到底解决什么问题很多团队不知道 Pod 的 requests 该填多少导致长期资源浪费或者频繁 OOM。VPA 会基于 Pod 的历史资源用量给出推荐值你可以在updateMode: Off模式下只观察推荐值也可以切到Auto让它自动调整并重启 Pod。一个最小 VPA 配置长这样apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: demo-vpa spec: targetRef: apiVersion: apps/v1 kind: Deployment name: hpa-demo updatePolicy: updateMode: Off resourcePolicy: containerPolicies: - containerName: * minAllowed: cpu: 100m memory: 64Mi maxAllowed: cpu: 4 memory: 4Gimode: Off是安全模式VPA 只写 recommendation不改 Pod。我一般建议先跑一个月 Off看推荐值是否稳定再决定要不要切 Auto。Auto 会重建 Pod而且如果和 HPA 同时作用于 CPU 指标两个控制器会打架HPA 调整副本数VPA 调整单个 Pod 的 requests同一个利用率指标被两边同时控制最终可能震荡。官方文档也明确提醒过这一点。5.2 Cluster Autoscaler 的部署要点CA 看的是 Pending Pod。HPA 把副本数从 3 扩到 20如果节点资源不足新 Pod 会一直处于 PendingCA 收到这个信号后触发节点容量扩容。这个联动在云上托管集群里非常方便一般只需要在节点组配置最小实例数和最大实例数即可。如果是 kubeadm 自建 v1.26 且跑在裸金属上CA 需要对接一套能自动创建节点的基础设施方案不是直接跑一个二进制就能凭空变出节点。自己部署 CA 时几个关键参数建议这样设置--scale-down-unneeded-time10m --scale-down-utilization-threshold0.5 --nodesmin:1:max:10:default-poolscale-down-unneeded-time设 10 分钟以上防止节点刚缩完流量又涨回来反复创建节点更花钱scale-down-utilization-threshold表示节点利用率低于 50% 才考虑缩容避免把正在承载流量的节点缩掉max建议比 HPA 上限所需节点数多 2 个给调度留缓冲。5.3 HPA 加 CA 的完整时序从流量上涨到服务真正扩容链路是这样的流量上涨 - HPA 扩容 Pod - 新 Pod Pending - CA 申请新节点 - 节点 Ready - Pod 调度 - 服务恢复。整个过程通常需要 5 到 15 分钟。如果核心服务的 minReplicas 只设了 1流量尖峰到来时这段时间里服务能力是不足的。所以核心服务我建议维持至少 2 个 Pod再配一个 PodDisruptionBudget 保护缩容和节点维护时不至于全部实例同时不可用。5.4 三种工具的搭配建议服务类型HPA 方式VPACAAPI 网关QPS 主指标 CPU 兜底不用需要常规业务服务CPU 或 QPS可 Off 观察需要数据库 / Redis不建议 HPA可 Off 观察后人工调整不要自动缩6. 生产环境踩坑与调优抖动、告警与成本控制6.1 副本数像过山车三个经典抖动原因第一个是指标窗口太短。如果 PromQL 里用[30s]窗口做 QPS指标本身波动一大HPA 就会一会儿扩一会儿缩。生产上我会把窗口放到 2 到 5 分钟用一定的响应延迟换稳定性。第二个是 target 和实际负载贴得太近。比如 target 是 60%实际稳定在 55% 到 65% 之间反复跳动HPA 就会一直在这两个边缘试探。解决办法是调低 target 留出缓冲或者调长stabilizationWindowSeconds。第三个是 Pod 冷启动阶段指标偏低。容器起来了但业务还没就绪CPU 使用率为 0HPA 看到平均值被拉低可能误判负载降了并缩容。要解决这个问题核心是 readinessProbe 做好Pod 就绪后再接流量同时 scaleDown 稳定窗口不要小于 5 分钟。6.2 用 behavior 精确控制伸缩速率behavior字段是 v2 API 提供的v1.26 完全支持。我的常用配置是扩容激进、缩容保守behavior: scaleUp: stabilizationWindowSeconds: 0 policies: - type: Pods value: 4 periodSeconds: 60 - type: Percent value: 100 periodSeconds: 60 selectPolicy: Max scaleDown: stabilizationWindowSeconds: 600 policies: - type: Pods value: 1 periodSeconds: 60扩容时我不希望等稳定窗口指标一旦超过目标就立刻扩每分钟最多增加 4 个 Pod 或当前副本数的 100%取较大的那个因为流量尖峰时优先保证可用性。缩容时设置了 10 分钟稳定窗口每分钟最多缩 1 个避免流量稍微回落就把刚扩的副本缩掉结果流量反弹又要重新扩容来回折腾更费钱。6.3 告警必须盯住这三个信号第一个是 HPA 打满上限。这个指标非常关键它代表系统容量已经到顶再涨就只能靠 CA 或人工干预。PromQL 大致是kube_horizontalpodautoscaler_status_current_replicas kube_horizontalpodautoscaler_spec_max_replicas第二个是 Pod 长时间 Pending。如果kube_pod_container_status_waiting_reason{reasonUnschedulable}持续出现说明节点容量不够CA 可能还没来得及或扩不动了。第三个是 HPA 指标异常。TARGETS 长时间展示 Unknown 或者一直为零基本就是指标管道断了这类告警能帮你提前发现 Metrics Server 或 Adapter 挂掉而不是等到流量冲击时才发现扩不起来。6.4 成本控制弹性扩缩容不是给你烧钱的理由maxReplicas一定要基于容量规划来算。方法很简单先压测出单 Pod 的容量比如每个 Pod 能扛 1000 QPS业务峰值是 8000 QPSmaxReplicas 至少 8再加 20% 余量就是 10。随手写 20 不是不行但每次压测触发扩容都要真金白银买单。容量测算要用requests而不是limits。Prometheus 监控集群容量时也要按 requests 统计因为调度器判断节点能否容纳 Pod看的是 requests 总和。按 limits 算会严重高估可用容量因为大部分 Pod 根本用不满 limits。还要注意缩容策略别太激进缩得越快流量反弹时重新扩容的建 Pod 成本、发布成本都算进去了反而不省钱。6.5 排错顺序和常见报错遇到 HPA 不工作我习惯按这个顺序排查# 1. 看 HPA 整体状态和事件 kubectl get hpa -A kubectl describe hpa name # 2. 看 Metrics Server 是否正常 kubectl top nodes kubectl top pods # 3. 自定义指标额外看 API 原始输出 kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/metric-name | jq # 4. 看控制器日志 kubectl -n kube-system logs -l componentkube-controller-manager --tail-1几个常见报错我直接列出来。Failed to get cpu utilization: unable to get metrics for resource cpu基本就是 Metrics Server 没装好或者目标 Pod 还没 Ready。invalid metric value for Pods type一般是自定义指标返回的不是数字或者 Prometheus 查询结果为空检查 Adapter 的 metricsQuery。HPA 状态显示ScalingLimited说明已经到maxReplicas上限了不是故障是容量规划告警。写到这里我想起自己最早装 HPA 的那天以为把 yaml apply 上去就万事大吉结果第二天线上副本数纹丝不动。后来我养成了一个习惯每次压测完成后都会把 HPA 的 Events 和 metrics API 的原始输出一起截图存档。自动缩放看起来只是几个字段但真正让它稳定工作的是一整条指标和调度链路。先用 CPU 兜底再加上 QPS 这类业务指标最后用 CA 解决容量问题这套组合会在你不需要盯着kubectl scale的时候替你挡住很多麻烦。
返回列表