跳过正文
Kubernetes 资源管理实战——QoS、ResourceQuota、VPA 体系化实践

Kubernetes 资源管理实战——QoS、ResourceQuota、VPA 体系化实践

·739 字·4 分钟·
目录
K8s 完全指南 - 这篇文章属于一个选集。
§ : 本文

生产里因为资源配置翻车的事见过太多:没设 limits 的服务把节点内存吃光触发驱逐、requests 拉太高导致 Pod 调度不上去、HPA 配错了根本不扩。这篇按我自己的习惯把 K8s 资源管理体系从头捋一遍。

QoS 三级:K8s 的资源保障优先级
#

K8s 根据容器的 requests 和 limits 设置,自动将 Pod 分为三个 QoS 等级,在节点资源紧张时决定谁先被驱逐。

Guaranteed(最高保障)
#

所有容器都设置了相同的 CPU 和内存 requests/limits,且 requests == limits:

resources:
  requests:
    cpu: "500m"
    memory: "512Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"

特点:节点 OOM 时最后被杀,适合核心数据库、关键业务服务。代价是资源利用率低(不能 burst)。

Burstable(可突发)
#

至少一个容器设置了 requests,但 requests != limits(或者只设了 limits 没设 requests):

resources:
  requests:
    cpu: "100m"
    memory: "128Mi"
  limits:
    cpu: "1000m"
    memory: "1Gi"

特点:平时按 requests 调度,空闲时可以 burst 到 limits。节点压力大时,按 OOM score 决定驱逐顺序(使用内存超出 requests 越多,越容易被驱逐)。适合大多数 Web 服务。

BestEffort(无保障)
#

所有容器都没有设置任何 requests 和 limits:

# 没有 resources 字段,或者 resources: {}

特点:节点资源紧张时第一个被驱逐。只适合临时 Job 或非关键批处理任务,生产服务禁止使用


requests vs limits:设计原则
#

理解这两个概念的本质是做好资源管理的前提。

requests:调度器用来决定把 Pod 放到哪个节点。节点的 Allocatable 减去所有 Pod 的 requests 之和就是剩余可调度资源。

limits:容器运行时的上限。超过 CPU limits 会被 throttle(限速),超过内存 limits 会被 OOMKill。

# 查看节点的可分配资源
kubectl describe node <node-name> | grep -A 5 "Allocatable"

# 查看节点上已分配的资源(requests 之和)
kubectl describe node <node-name> | grep -A 10 "Allocated resources"

CPU 和内存的本质区别
#

CPU 是可压缩资源:超出 limits 时,进程被限速但不会被杀,只是变慢。1000m = 1 核,500m = 半核。

内存是不可压缩资源:超出 limits 时,进程直接被 OOMKill(exit code 137),没有缓冲区。

这个区别决定了设置策略:

# 推荐的生产配置策略
resources:
  requests:
    cpu: "100m"      # 保守设置,只占调度资源,不影响 burst
    memory: "256Mi"  # 接近实际使用量,给调度器准确参考
  limits:
    cpu: "2000m"     # CPU 可以设大,超了只是慢不会崩
    memory: "512Mi"  # 内存必须留足裕量,超了就 OOMKill

OOMKilled 排查实战
#

有一次凌晨告警,一个 Python 服务频繁重启,查看 Pod 状态:

kubectl describe pod <pod-name> -n production
Last State: Terminated
  Reason: OOMKilled
  Exit Code: 137
  Started: Sun, 12 Apr 2026 02:14:23 +0800
  Finished: Sun, 12 Apr 2026 02:19:45 +0800

容器 OOM vs 节点 OOM 的区分

  • OOMKilled + Exit Code 137:容器超出了自己的 memory limit,只有这个 Pod 受影响
  • 节点日志 kernel: Out of memory: Kill process:节点级别 OOM,会触发驱逐
# 查看容器实际内存使用(需要 metrics-server)
kubectl top pod <pod-name> -n production --containers

# 查看 Pod 的 OOM 事件
kubectl get events -n production --field-selector reason=OOMKilling

确定合理的 memory limit

  1. kubectl top pod 观察正常负载下的内存使用(P95)
  2. limit 设置为 P95 * 1.5 到 2 倍(留 buffer)
  3. 如果内存一直线性增长,先排查泄漏,不要无脑加 limit

HPA:水平自动扩缩
#

基础配置
#

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: myapp-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60   # 目标 CPU 利用率 60%
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 70
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30   # 扩容稳定窗口(快速响应)
      policies:
      - type: Percent
        value: 100
        periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300  # 缩容稳定窗口(避免抖动)
      policies:
      - type: Percent
        value: 20
        periodSeconds: 60

targetAverageUtilization 计算公式
#

HPA 的扩缩决策公式:

期望副本数 = ceil(当前副本数 × (当前平均利用率 / 目标利用率))

例如:当前 4 个副本,CPU 利用率 80%,目标 60%:

期望副本数 = ceil(4 × (80 / 60)) = ceil(5.33) = 6

重要:这里的"利用率"是相对于 requests 的百分比,不是节点 CPU 的百分比。如果 requests 设得很小,即使容器实际 CPU 不高,利用率百分比也会很大,导致 HPA 频繁扩容。

# 查看 HPA 当前状态
kubectl get hpa -n production
kubectl describe hpa myapp-hpa -n production

ResourceQuota + LimitRange:命名空间资源隔离
#

ResourceQuota:总量限制
#

apiVersion: v1
kind: ResourceQuota
metadata:
  name: production-quota
  namespace: production
spec:
  hard:
    # 计算资源
    requests.cpu: "40"
    requests.memory: 80Gi
    limits.cpu: "100"
    limits.memory: 200Gi
    # 对象数量
    pods: "200"
    services: "50"
    persistentvolumeclaims: "30"
    # 存储
    requests.storage: 500Gi

LimitRange:单个容器的默认值和范围
#

apiVersion: v1
kind: LimitRange
metadata:
  name: production-limitrange
  namespace: production
spec:
  limits:
  - type: Container
    # 未设置 requests/limits 时的默认值
    default:
      cpu: "500m"
      memory: "512Mi"
    defaultRequest:
      cpu: "100m"
      memory: "128Mi"
    # 允许设置的范围
    max:
      cpu: "4"
      memory: "8Gi"
    min:
      cpu: "50m"
      memory: "64Mi"
  - type: Pod
    max:
      cpu: "8"
      memory: "16Gi"

最佳实践:每个命名空间都应该配 LimitRange,防止忘记设 resources 的服务成为 BestEffort 类型,被随时驱逐。


VPA:垂直自动扩缩
#

VPA(Vertical Pod Autoscaler)自动推荐和调整 requests,解决手动设置不准确的问题。

推荐模式(生产常用)
#

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: myapp-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  updatePolicy:
    updateMode: "Off"   # Off = 只推荐,不自动修改
  resourcePolicy:
    containerPolicies:
    - containerName: myapp
      minAllowed:
        cpu: 50m
        memory: 64Mi
      maxAllowed:
        cpu: 4
        memory: 4Gi
# 查看 VPA 推荐值
kubectl describe vpa myapp-vpa -n production
# 输出中的 Recommendation.containerRecommendations 包含:
# LowerBound: 保守下限
# Target: 推荐值
# UpperBound: 保守上限

VPA 与 HPA 的配合
#

重要限制:VPA 和 HPA 不能同时基于 CPU/内存扩缩,否则会互相打架(VPA 调大 requests -> HPA 认为利用率下降 -> 缩容 -> VPA 推荐降低 requests…)。

正确配合方式

场景推荐方案
单纯水平扩缩HPA(CPU/内存)
单纯垂直调优VPA(Auto 模式)
既要水平又要垂直HPA(CPU)+ VPA(内存,需配置 containerPolicies 排除 CPU)
自定义指标扩缩KEDA(替代 HPA,功能更强)

资源画像实战:识别浪费
#

# 查看所有 Pod 的实际资源使用
kubectl top pods -A --sort-by=memory

# 查看某命名空间所有容器的 requests vs 实际使用
kubectl top pod -n production --containers

# 识别资源严重浪费的服务(requests 远大于实际使用)
# 用 Prometheus 查询(需要 metrics-server 或 kube-state-metrics)

Prometheus 查询资源 Slack(浪费):

# CPU requests 使用率(低说明 requests 设太高)
sum(rate(container_cpu_usage_seconds_total{namespace="production"}[5m])) by (pod)
/
sum(kube_pod_container_resource_requests{resource="cpu", namespace="production"}) by (pod)

# 内存 requests 使用率
sum(container_memory_working_set_bytes{namespace="production"}) by (pod)
/
sum(kube_pod_container_resource_requests{resource="memory", namespace="production"}) by (pod)

我在做成本优化时,用这个查询发现某几个服务的 CPU requests 利用率不到 5%,把 requests 从 500m 降到 50m 后,腾出了大量可调度空间,延缓了节点扩容。


常见陷阱总结
#

  1. requests 设 0:Pod 变成 BestEffort,随时可能被驱逐
  2. limits » requests(差距超过 10x):实际运行时很容易触碰 limits 被 OOMKill,但调度器以为资源充足
  3. CPU limits 设太低:Java/Go 服务启动时 CPU 会 spike,limits 太低导致启动极慢(不是挂了,是被 throttle 了)
  4. 没有配 LimitRange:新人提交的 Pod 忘记写 resources,成为 BestEffort
  5. HPA + VPA 同时基于 CPU:互相干扰,导致副本数和资源配置不稳定

资源管理是 K8s 集群稳定性的基础。先把 requests/limits 设合理,再上 HPA,最后用 VPA 做持续优化,这个顺序不能乱。

Wenzhuo Huang
作者
Wenzhuo Huang
搞运维的工程师,写代码的运维人。专注 Kubernetes、AWS、GitOps 与基础设施可靠性。这个博客既是我的技术笔记本,也是我踩过的坑的受害者档案。
K8s 完全指南 - 这篇文章属于一个选集。
§ : 本文

相关文章

Kubernetes 网络深度解析——CNI、kube-proxy、NetworkPolicy 完全指南

·962 字·5 分钟
K8s 网络是很多工程师的知识盲区,平时不出问题就忽略,一出问题就完全不知道从哪下手。我在多次生产网络故障的排查中,深刻理解了 K8s 网络的每一层。这篇文章从 Pod 网络模型讲到 NetworkPolicy 实战,帮你建立完整的 K8s 网络知识体系。

Kubernetes 从零开始:工程师视角的入门指南

·1353 字·7 分钟
Docker Compose 能运行多个容器,为什么还需要 Kubernetes?本文从这个问题出发,用类比的方式讲清楚 Pod/Deployment/Service/Ingress 等核心概念,给出最常用的 kubectl 命令和完整的入门部署示例。

Rook-Ceph on Kubernetes 运维实战:从部署到故障恢复

·1349 字·7 分钟
当你需要在 Kubernetes 上提供 block、file、object 三种存储时,Rook-Ceph 是几乎没有替代品的方案。但它的复杂度也是所有 K8s 存储方案里最高的。这篇文章是我在一套裸金属 Rook-Ceph 生产集群上两年运维经验的整理,包括几次把集群从悬崖边拉回来的复盘。