K8s HPA 自动扩缩容与资源配额实战:从 Metrics Server 到 ResourceQuota、LimitRange
学习参考:
业务流量白天高峰要 20 个 Pod,半夜低谷只要 2 个------手动改副本数太累。K8s 提供了 **HPA(Horizontal Pod Autoscaler)**自动根据 CPU/内存负载扩缩 Pod 数量。但扩缩容的前提是你得先告诉 K8s 每个 Pod 该用多少资源(requests/limits),而多团队共享集群时还要用 ResourceQuota 给每个 namespace 分配总资源、用 LimitRange 给单个容器设默认值。本文从 Metrics Server 部署开始,完整跑通 HPA CPU 扩缩、内存扩缩、scaleUp/scaleDown 精细管控,最后讲 ResourceQuota 和 LimitRange。
一、Metrics Server:K8s 的监控数据采集
1.1 为什么需要 Metrics Server
K8s 本身不存储监控指标。你要知道节点和 Pod 的 CPU/内存用了多少,HPA 要根据什么来扩缩容------都靠 Metrics Server。
- Metrics Server 通过 Kubelet 收集资源指标
- 通过 Metrics API 暴露给 apiserver
- 供 HPA/VPA 使用,也支持
kubectl top命令 - 每 15 秒采集一次,轻量(每节点 1m CPU + 2MB 内存)
- 不适用于长期存储,要持久化监控请用 Prometheus
1.2 部署 Metrics Server
bash
# 下载
root@master30:~# wget -O metrics-server-components.yaml https://laoma.cloud/class/course-materials/softwares/stage03/metrics-server-components-v0.7.1.yaml
# 修改镜像地址
root@master30:~# sed -i 's/registry.k8s.io/hub.laoma.cloud/g' metrics-server-components.yaml
# 部署
root@master30:~# kubectl apply -f metrics-server-components.yaml
root@master30:~# kubectl get pods -n kube-system | grep metrics
metrics-server-6556f8cb6c-78mhq 1/1 Running 0 45s
注意:测试环境需要加
--kubelet-insecure-tls参数跳过 TLS 校验。
1.3 使用
bash
# 查看节点资源使用
root@master30:~# kubectl top node
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
master30.laoma.cloud 97m 4% 1328Mi 35%
worker31.laoma.cloud 49m 2% 912Mi 24%
worker32.laoma.cloud 45m 2% 926Mi 24%
# 查看 Pod 资源使用
root@master30:~# kubectl top pods -n kube-system
NAME CPU(cores) MEMORY(bytes)
calico-node-crfr7 27m 57Mi
coredns-6d56c8448f-nhjpg 3m 12Mi
kube-apiserver-master.laoma.cloud 46m 355Mi
metrics-server-6556f8cb6c-78mhq 1m 11Mi
单位:1 CPU = 1000m(毫核)
二、HPA 基于 CPU 使用率自动扩缩容
2.1 HPA 是什么
HPA 根据定义的指标动态调整 Pod 数量,支持 Deployment、ReplicaSet、StatefulSet。
适用场景:
- 流量波动大的无状态服务(Nginx、API、网关、微服务)
- 需要提高并发、削峰填谷
核心特点:
- 不修改 Pod 配置,只改副本数
- 实时、秒级扩缩
- 最常用、最稳定
关键参数(kube-controller-manager 控制):
| 参数 | 默认值 | 说明 |
|---|---|---|
--horizontal-pod-autoscaler-sync-period |
15s | 指标采样周期 |
--horizontal-pod-autoscaler-initial-readiness-delay |
30s | Pod 启动就绪窗口期,不参与 HPA 计算 |
--horizontal-pod-autoscaler-downscale-stabilization |
300s | 缩容冷却时间 |
实验环境为了快速演示,可以调小:
bash
root@master30:~# vim /etc/kubernetes/manifests/kube-controller-manager.yaml
# 增加:
# --horizontal-pod-autoscaler-sync-period=10s
# --horizontal-pod-autoscaler-initial-readiness-delay=20s
# --horizontal-pod-autoscaler-downscale-stabilization=30s
# 静态 Pod 自动重启生效
2.2 创建 HPA
bash
# 准备 Deployment
root@master30:~# kubectl create deployment web --image=hub.laoma.cloud/library/nginx
# 创建 HPA:最少 2 个,最多 5 个,CPU 目标 80%
root@master30:~# kubectl autoscale deployment web --max=5 --min=2 --cpu-percent=80
等价 YAML(autoscaling/v1,只支持 CPU):
yaml
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: web
spec:
maxReplicas: 5
minReplicas: 2
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
targetCPUUtilizationPercentage: 80
bash
root@master30:~# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
web Deployment/web cpu: <unknown>/80% 2 5 2 34s
# TARGETS 是 <unknown>!
TARGETS 显示 unknown 的两个条件:
- 必须安装 Metrics Server
- 必须为 Pod 设置 resources.limits(HPA 按 limits 的百分比算)
2.3 给 Pod 设置资源限制
bash
root@master30:~# kubectl edit deployment web
# 添加:
resources:
limits:
cpu: 100m
memory: 200Mi

bash
root@master30:~# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
web Deployment/web cpu: 0%/80% 2 5 2 83s
# TARGETS 正常显示了
# 暴露 Service
root@master30:~# kubectl expose deployment web --port=80 --target-port=80
2.4 压测验证扩容
bash
# 监控窗口
root@master30:~# \
while true
do
clear; date; echo
kubectl get hpa; echo
kubectl get pod; echo
kubectl top pods
sleep 1
done
# 上 CPU 压力
root@master30:~# apt install -y apache2-utils
root@master30:~# while true; do ab -n 300000 -c 100 http://10.100.255.92/; sleep 1; done
观察过程:
-
CPU 飙升到 100% 超过 80% 阈值
-
约 20 秒后 HPA 自动新增 Pod(2 → 3 → 4 → 5)
-
即使 CPU 持续高负载,Pod 数不超过 maxReplicas=5
-
停止压测后,负载降低,Pod 数逐步缩回到 minReplicas=2
cpu: 100%/80% 2 5 2 → cpu: 95%/80% 2 5 3 → cpu: 99%/80% 2 5 4 → cpu: 100%/80% 2 5 5
三、HPA 基于内存使用率自动扩缩容
基于内存扩缩容需要用 autoscaling/v2 API(v1 只支持 CPU)。
3.1 准备会占内存的应用
让 Nginx 返回一个大文件来占用内存:
bash
# 在 worker 节点准备大文件
[root@worker31 ~]# mkdir /www && dd if=/dev/zero of=/www/big.img bs=1M count=200
[root@worker32 ~]# mkdir /www && dd if=/dev/zero of=/www/big.img bs=1M count=200
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: big-file
mountPath: /usr/share/nginx/html
resources:
limits:
cpu: 100m
memory: 200Mi
volumes:
- name: big-file
hostPath:
path: /www
3.2 创建内存 HPA
yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web
spec:
minReplicas: 2
maxReplicas: 5
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
metrics:
- resource:
name: memory
target:
type: Utilization
averageUtilization: 60
type: Resource
bash
root@master30:~# kubectl apply -f hpa-mem.yaml
root@master30:~# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
web Deployment/web memory: 2%/60% 2 5 1 13m
3.3 压测
bash
# 访问大文件,内存占用上来
root@master30:~# while true; do ab -n 300000 -c 100 http://10.99.129.137/big.img; sleep 1; done

观察: 内存使用率从 2% 飙到 86% 超过 60% 阈值,Pod 从 2 逐步扩到 5。停止压测后缩回到 2。
⚠️ 内存压力太大时 Pod 可能被 OOMKilled。
四、HPA 精细管控:scaleUp / scaleDown
从 K8s 1.18 起,可以通过 spec.behavior 精细控制扩缩容速度,避免流量毛刺导致反复扩缩。
4.1 扩容(scaleUp)
yaml
spec:
behavior:
scaleUp:
stabilizationWindowSeconds: 0 # 扩容稳定窗口,默认0(立刻扩)
policies:
- type: Percent
value: 100 # 每周期最多新增当前副本的100%(翻倍)
periodSeconds: 15
- type: Pods
value: 4 # 每周期最多新增4个Pod
periodSeconds: 15
selectPolicy: Max # 两条策略取允许更多的
默认扩容策略解读:
- 稳定窗口 0 秒:指标一超标立刻扩
- 每 15 秒最多翻倍 或 最多加 4 个 Pod
selectPolicy: Max:取两条策略中允许扩容更多的那个
4.2 缩容(scaleDown)
yaml
spec:
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 缩容稳定窗口,默认300秒(5分钟)
policies:
- type: Percent
value: 100
periodSeconds: 15
selectPolicy: Max
默认缩容策略解读:
- 稳定窗口 5 分钟:窗口内取最大期望副本数作为目标,防止流量抖动反复删 Pod
- 每 15 秒最多删 100% 副本(理论上一次缩到 minReplicas)
4.3 扩缩容对比
| 配置项 | scaleUp(扩容) | scaleDown(缩容) |
|---|---|---|
| stabilizationWindowSeconds 默认值 | 0 | 300(5 分钟) |
| 稳定窗口取值逻辑 | 窗口内取最小期望副本 | 窗口内取最大期望副本 |
| 默认 policies 条数 | 2 条(Percent + Pods) | 1 条(仅 Percent) |
| 触发阈值 | 利用率 > target × 1.1 | 利用率 < target × 0.9 |
| 设计思想 | 优先快速扩容保障业务 | 谨慎缩容,避免流量反弹 |
4.4 平缓缩容配置示例
yaml
spec:
behavior:
scaleDown:
stabilizationWindowSeconds: 600 # 等10分钟确认低负载
policies:
- type: Percent
value: 10 # 每分钟最多缩10%
periodSeconds: 60
- type: Pods
value: 2 # 每分钟最多删2个
periodSeconds: 60
selectPolicy: Min # 保守:取删得更少的
五、VPA(Vertical Pod Autoscaler)
VPA 不改 Pod 数量,而是动态调整 Pod 的 CPU/内存 requests 和 limits。
- 适合资源配置不合理的服务、长期运行的稳定服务
- 生效需要重启 Pod(有侵入性)
- 不能和 HPA 同时对同一资源使用(会冲突)
| 项目 | HPA 横向 | VPA 纵向 |
|---|---|---|
| 扩缩方式 | 增删 Pod 数量 | 调整 CPU/内存 request/limit |
| 是否重启 Pod | 不重启 | 一般需要重启 |
| 适合负载 | 流量波动大 | 资源配置不合理 |
| 生产使用 | 非常普遍 | 较少 |
六、ResourceQuota:命名空间资源配额
多团队共享集群时,有人会用超公平分配的资源量。ResourceQuota 对每个命名空间的资源消耗总量提供限制。
6.1 两种配额类型
- 对象数量:限制 Pod、Service、ConfigMap、Secret、Deployment 等资源个数
- 计算资源:限制 CPU、内存、存储的总量
6.2 创建对象数量配额
bash
root@master30:~# kubectl create quota myquota --hard=pods=2,services=3,secrets=5,persistentvolumeclaims=10
root@master30:~# kubectl get resourcequotas
NAME AGE REQUEST LIMIT
my-quota 10s persistentvolumeclaims: 0/10, pods: 0/2, secrets: 1/5, services: 0/3
等价 YAML:
yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: myquota
spec:
hard:
pods: "2"
services: "3"
secrets: "5"
persistentvolumeclaims: "10"
测试: 创建 3 副本 Deployment,但配额只允许 2 个 Pod:
bash
root@master30:~# kubectl create deployment web --image=nginx --replicas=3
root@master30:~# kubectl get all
pod/web-96d5df5c8-dl7qk 1/1 Running
pod/web-96d5df5c8-j7fhh 1/1 Running
# 第3个 Pod 创建失败
root@master30:~# kubectl describe rs web-96d5df5c8
Error creating: pods "xxx" is forbidden: exceeded quota: myquota, requested: pods=1, used: pods=2, limited: pods=2
6.3 创建计算资源配额
yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: myquota
spec:
hard:
requests.cpu: 1000m
requests.memory: "2048Mi"
limits.cpu: 1000m
limits.memory: "2048Mi"
| 资源名称 | 含义 |
|---|---|
requests.cpu |
所有非终止 Pod 的 CPU 请求总和不超过此值 |
requests.memory |
所有非终止 Pod 的内存请求总和不超过此值 |
limits.cpu |
所有非终止 Pod 的 CPU 限制总和不超过此值 |
limits.memory |
所有非终止 Pod 的内存限制总和不超过此值 |
单位: CPU 1 核 = 1000m;内存 Ki/Mi/Gi(1024 进制)或 k/M/G(1000 进制)。
⚠️ 关键规则: 如果命名空间设置了计算资源配额,创建 Pod 时必须指定 requests 和 limits,否则直接拒绝创建。
七、requests 和 limits 到底是什么
yaml
resources:
requests: # 调度器保证的最少资源
cpu: 200m
memory: 256Mi
limits: # cgroup 硬上限,超过会被限流或 OOMKill
cpu: 500m
memory: 512Mi
- requests:调度器按 requests 选节点,保证 Pod 能分到这么多资源
- limits:运行时 cgroup 限制,CPU 超过会被限流(不会杀),内存超过会被 OOMKill
7.1 CPU 是可压缩资源
CPU 超限不会杀进程,只是被"减速"------调度时间片减少。
yaml
# 限制 200m CPU,实际跑满
resources:
limits:
cpu: 200m
bash
root@master30:~# kubectl top pods
NAME CPU(cores) MEMORY(bytes)
stress 201m 0Mi # 稳定在 200m 左右,不会超过
7.2 内存是不可压缩资源
内存超限直接 OOMKill,Pod 重启:
yaml
# limit 256Mi,但实际用 512M
resources:
limits:
memory: "256Mi"
args: ['-m','1','--vm-bytes','512M']
bash
root@master30:~# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
stress 0/1 OOMKilled 1 (2s ago) 3s
stress 0/1 CrashLoopBackOff 1 4s
limits 总和可以超过节点总资源(超卖): CPU 是可压缩的,超卖没问题。内存超卖时,K8s 会先杀没设 request 的 Pod,再杀超过 request 最多的 Pod。
八、LimitRange:给单个容器设默认值和上下限
命名空间设了 ResourceQuota 后,不写 requests/limits 的 Pod 直接拒绝。LimitRange 就是给这个命名空间设"默认值"和"上下限"。
yaml
apiVersion: v1
kind: LimitRange
metadata:
name: mylimit
spec:
limits:
- type: Container
max: # 容器最大不能超过
memory: 1024Mi
cpu: 1
min: # 容器最小不能低于
memory: 128Mi
cpu: 100m
default: # 默认 limit
memory: 512Mi
cpu: 500m
defaultRequest: # 默认 request
memory: 256Mi
cpu: 200m
规则:
- 不写 resources → 自动套用 default / defaultRequest
- 写的 limit 小于 min 或大于 max → 拒绝创建
- 只写 limit 不写 request → request = limit
- 只写 request 不写 limit → limit = default
- 必须满足:min ≤ request ≤ limit ≤ max
bash
root@master30:~# kubectl describe limitranges mylimit
Type Resource Min Max Default Request Default Limit
---- -------- --- --- --------------- -------------
Container cpu 100m 1 200m 500m
Container memory 128Mi 1Gi 256Mi 512Mi
限定资源类型
| Type | Resource | 说明 |
|---|---|---|
| Container | cpu、memory | 限定单个容器 |
| Pod | cpu、memory | 限定 Pod 所有容器总和 |
| PersistentVolumeClaim | storage | 限定 PVC 申请存储大小 |
九、总结
| 组件 | 一句话 |
|---|---|
| Metrics Server | K8s 内置监控采集,给 HPA 和 kubectl top 供数据 |
| HPA | 根据 CPU/内存负载自动增删 Pod 数量 |
| VPA | 自动调 Pod 的 requests/limits,需要重启,和 HPA 冲突 |
| ResourceQuota | 限制整个 namespace 的资源总量 |
| requests | 调度器保证的最少资源,选节点用 |
| limits | cgroup 硬上限,CPU 超了限流,内存超了 OOMKill |
| LimitRange | 给 namespace 内容器设默认值和上下限 |
环境清理:
kubectl delete ns metric quota