4、工作负载资源优化:Pod资源规格调整、HPA/VPA配置、闲置资源回收

说到工作负载的资源优化,我其实挺有感触的。早期做Kubernetes迁移时,团队最常问的就是:“这个Pod到底该给多少资源?” 给少了,OOMKill;给多了,老板看账单心疼。说白了,这就是个精细活。

这一章,咱们就聊聊怎么把Pod的资源规格调准,怎么用HPA和VPA让系统自动伸缩,以及怎么把那些“占着茅坑不拉屎”的闲置资源给回收掉。

4.1 Pod资源规格调整:别拍脑袋,要讲数据

我见过太多团队,给Pod配资源全靠“感觉”。Java应用一律给4C8G,Node.js服务给1C2G。结果呢?一大半资源都在空转。

正确的做法是什么? 先跑起来,再看数据。

4.1.1 理解requests和limits

这两个概念,我再说一遍,因为太重要了。

  • requests:调度时的“承诺”。K8s根据这个值决定把Pod放在哪个节点上。说白了,这是“最低保障”。
  • limits:运行时的“天花板”。Pod最多能用这么多,超过就会被限流或杀掉。

我个人习惯,生产环境里requests和limits不要设成一样的。为什么?你想想看,如果所有Pod的requests都等于limits,节点资源利用率会非常低。因为Pod实际用不了那么多,但K8s以为它全占了。

我的经验: 对于CPU密集型的批处理任务,我会把requests设低一些,limits设高一些。这样既能保证调度,又能让Pod在突发时“借”到资源。但对于数据库这类有状态服务,requests和limits最好一致,避免被限流导致性能抖动。

4.1.2 基于历史数据的资源调整

别靠猜。用数据说话。

我一般会用 kubectl top pod 或者Prometheus + Grafana来观察Pod的实际资源消耗。观察周期至少一周,覆盖业务高峰期和低谷期。

举个例子,一个Web服务,过去7天的CPU平均使用率是200m,峰值是500m。那requests设250m,limits设600m,就挺合理。

# 查看Pod资源使用
kubectl top pod -n production

# 输出示例
NAME                    CPU(cores)   MEMORY(bytes)
web-service-7d9f8c6b9   250m         128Mi
web-service-7d9f8c6b9   180m         110Mi
web-service-7d9f8c6b9   320m         145Mi

嗯,这里要注意:内存是“不可压缩资源”。一旦超过limits,Pod会被OOMKill。所以内存的limits我通常会留20%-30%的buffer。

4.2 HPA配置:让Pod数量自动“呼吸”

HPA(Horizontal Pod Autoscaler)是K8s里最常用的弹性手段。它的逻辑很简单:根据CPU、内存或自定义指标,自动调整Pod的副本数。

但配置HPA,有几个坑我得提醒你。

4.2.1 指标选择与阈值设定

很多人只配CPU指标。这其实不够。我遇到过好几次,CPU没上去,但请求延迟已经飙升了。为什么?因为瓶颈可能在数据库连接池或外部API调用上。

我建议: 至少配两个指标。CPU + 请求QPS,或者CPU + 内存。

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: 1000

你看,这个配置里,CPU利用率超过70%或者每秒请求数超过1000,都会触发扩容。

避坑指南: 我曾经把HPA的冷却时间设得太短,结果业务流量一波动,Pod频繁扩缩,导致数据库连接数爆了。后来我把 --horizontal-pod-autoscaler-downscale-stabilization 设成了5分钟,情况就好多了。扩容要快,缩容要慢,这是铁律。

4.2.2 自定义指标与Prometheus适配

内置的CPU/内存指标,说实话,太粗糙了。真正精细的弹性,得靠自定义指标。

比如,我想根据“队列深度”来扩容。队列里的任务超过100个,就加Pod。这需要Prometheus Adapter把Prometheus里的指标暴露给K8s。

# Prometheus Adapter配置示例
seriesQuery: 'rabbitmq_queue_messages_ready{namespace="production"}'
resources:
  overrides:
    namespace:
      resource: "namespace"
    queue:
      resource: "service"
name:
  matches: "rabbitmq_queue_messages_ready"
  as: "queue_messages_ready"
metricsQuery: 'sum(rabbitmq_queue_messages_ready{<<.LabelMatchers>>}) by (<<.GroupBy>>)'

这样,HPA就能根据 queue_messages_ready 这个指标来决策了。

4.3 VPA配置:自动调整Pod规格

HPA管的是“数量”,VPA(Vertical Pod Autoscaler)管的是“大小”。VPA会自动调整Pod的requests和limits。

说实话,VPA在生产环境里,我用的比较谨慎。因为它调整规格时需要重建Pod,这会导致服务中断。

4.3.1 VPA的三种模式

模式 行为 适用场景
Off 只给出建议,不自动调整 刚接触VPA,先看看建议值
Auto 自动调整,Pod会重建 无状态服务,可以接受短暂中断
Initial 只在Pod创建时调整 有状态服务,不想频繁重建

我个人习惯,先用Off模式跑一周,看看VPA给出的建议值是否合理。确认没问题后,再切换到Auto模式。

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web-service-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: web-service
  updatePolicy:
    updateMode: "Off"  # 先看建议
  resourcePolicy:
    containerPolicies:
    - containerName: '*'
      minAllowed:
        cpu: 100m
        memory: 100Mi
      maxAllowed:
        cpu: 2
        memory: 2Gi
我的经验: VPA和HPA最好不要同时用。它们会打架。VPA调整Pod规格后,HPA的指标会变化,导致扩缩容行为异常。如果必须同时用,建议HPA基于自定义指标(如QPS),VPA基于CPU/内存。

4.4 闲置资源回收:别让钱打水漂

这是我最想强调的一点。很多团队只关注“怎么给够资源”,却忽略了“怎么回收资源”。

你想想看,一个测试环境的Pod,跑了一个月没人用,是不是在烧钱?一个Job跑完了,Pod还在那里挂着,是不是浪费?

4.4.1 使用Cluster Autoscaler回收节点

当节点利用率低时,Cluster Autoscaler会把节点上的Pod驱散,然后缩容节点。这能省下不少云资源费用。

但要注意,不是所有Pod都能被驱散。你需要给Pod加上合适的 PodDisruptionBudget

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-service-pdb
spec:
  minAvailable: 2  # 至少保留2个Pod
  selector:
    matchLabels:
      app: web-service

这样,Cluster Autoscaler在缩容时,会保证你的服务至少还有2个Pod在运行。

4.4.2 使用KubeCost或开源工具分析闲置资源

我推荐用 KubeCostGoldilocks 来分析集群里的资源浪费情况。

KubeCost能告诉你:哪个命名空间最费钱?哪个Deployment的资源利用率最低?

Goldilocks则能给出VPA建议,告诉你哪些Pod的资源规格可以下调。

一个真实的案例: 我帮一个客户做FinOps优化时,用KubeCost发现他们有个命名空间,30%的Pod在过去30天里CPU利用率不到10%。这些Pod大多是“僵尸服务”——开发人员忘了关的测试实例。我们一次性清理了50多个Pod,每月省下了近2000美元。

4.4.3 设置资源配额与限制范围

别让开发者随意创建Pod。用 ResourceQuotaLimitRange 来约束。

apiVersion: v1
kind: ResourceQuota
metadata:
  name: dev-quota
  namespace: development
spec:
  hard:
    requests.cpu: "10"
    requests.memory: "20Gi"
    limits.cpu: "20"
    limits.memory: "40Gi"
    pods: "50"
---
apiVersion: v1
kind: LimitRange
metadata:
  name: dev-limits
  namespace: development
spec:
  limits:
  - default:
      cpu: "500m"
      memory: "512Mi"
    defaultRequest:
      cpu: "200m"
      memory: "256Mi"
    type: Container

这样,开发者在dev命名空间里,最多只能用10核CPU和20Gi内存。每个Pod默认的requests是200m CPU和256Mi内存。想超?没门。

4.5 总结与最佳实践

好了,这一章的内容不少。我帮你捋一下核心要点:

  1. Pod规格调整:别靠猜,用 kubectl top 或Prometheus看实际数据。requests和limits要有合理差距。
  2. HPA配置:至少配两个指标,扩容快、缩容慢。自定义指标更精准。
  3. VPA配置:先用Off模式看建议,再切Auto。别和HPA同时用。
  4. 闲置资源回收:用Cluster Autoscaler、KubeCost、ResourceQuota来“挤水分”。

最后说一句:资源优化不是一次性工作。业务在变,流量在变,你的资源策略也得跟着变。定期复盘,持续优化,这才是FinOps的精髓。

下一章,咱们聊聊集群层面的成本控制,包括节点选型、Spot实例和混合部署策略。到时候见。