流量大了自动加 Pod!K8s HPA 扩缩容与 ResourceQuota/LimitRange 资源管控实战

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 的两个条件:

  1. 必须安装 Metrics Server
  2. 必须为 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

观察过程:

  1. CPU 飙升到 100% 超过 80% 阈值

  2. 约 20 秒后 HPA 自动新增 Pod(2 → 3 → 4 → 5)

  3. 即使 CPU 持续高负载,Pod 数不超过 maxReplicas=5

  4. 停止压测后,负载降低,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 两种配额类型

  1. 对象数量:限制 Pod、Service、ConfigMap、Secret、Deployment 等资源个数
  2. 计算资源:限制 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

相关推荐
承渊政道1 小时前
Linux网络学习【深入理解多路转接select、poll的代码改写】
linux·ubuntu·select·多路转接·poll
雪落漂泊1 小时前
Linux进程(四)概念:命令行参数和环境变量篇
linux·运维·服务器
方便面不加香菜1 小时前
Docker Container -- 容器命令详解
运维·docker·容器
qq_377572771 小时前
linux x86 arm loongarch
linux
Dragon~Snow2 小时前
Centos Stream10 安装 MySQL-9.7.2-linux-glibc2.28-x86_64.tar
linux·mysql·centos
say_fall2 小时前
【Linux系统编程】Linux进程信号:从“外卖到了“到内核里的一张位图
linux·运维·服务器·车载系统·智能硬件
Z21-X2 小时前
云原生运维实战笔记:企业网站综合上云部署与云运维服务全景解析
运维·笔记·学习·云原生
坤坤子吖2 小时前
Linux网络编程:UDP进阶,从字典服务器到简单聊天室
linux·服务器·网络·c++·笔记·学习
Rabitebla2 小时前
【Linux学习笔记】进程篇(九):进程的创建与退出——fork、写时拷贝与退出码
linux·c++·笔记·学习