找回密码
立即注册
搜索
发回帖 发新帖

4964

积分

0

好友

638

主题
发表于 2026-2-24 04:09:17 | 查看: 307| 回复: 0

云服务器账单是很多技术团队的长期痛点。将业务迁移到 Kubernetes 后,有时资源利用率不升反降——开发人员为了“保险起见”设置了过高的 Request,运维团队为了“稳定运行”又过度预购了节点,结果导致整个集群的平均 CPU 利用率长期在 15% 到 25% 之间徘徊。以当前主流云厂商的定价估算,一个 8核16G 的按量计费实例每月费用大约在 800 至 1200 元,一个拥有 50 个节点的集群,年成本轻松达到 50 至 70 万元。如果能将资源利用率从 20% 提升至 50%,节点数量几乎可以直接减半,省下的费用足以支撑一个工程师团队。

Kubernetes 原生提供了 HPA(Horizontal Pod Autoscaler)和 VPA(Vertical Pod Autoscaler)两套弹性伸缩机制,结合社区生态中的 KEDA(事件驱动弹性)和 Karpenter(节点级弹性),可以构建一套覆盖从 Pod 到 Node 的全链路弹性伸缩体系。然而,这些组件并非“安装即用”,错误的配置可能比不配置更危险——HPA 震荡导致服务抖动、VPA 频繁重启 Pod 影响可用性、Cluster Autoscaler 缩容缓慢造成资源浪费,这些都是生产环境中实实在在踩过的坑。

本文将从资源管理的基础概念讲起,逐一拆解 HPA v2、VPA、KEDA、Karpenter 的工作原理与生产级配置,最后提供一套经过验证的集群成本优化综合方案。

一、概述

1.1 技术特点

  • 全链路弹性:覆盖 Pod 水平扩缩容、Pod 垂直资源调整、节点自动伸缩三个层面。
  • 2026 技术栈:基于 Kubernetes 1.32+、HPA v2 API、VPA 1.2+、KEDA 2.16+、Karpenter 1.2+。
  • 成本量化:每个优化手段都附带成本节省的估算逻辑,并非空谈理论。
  • 生产验证:所有配置均在 200+ 节点的生产集群上运行验证,非实验室环境产物。

1.2 适用场景

  • 场景一:集群资源利用率长期低于 30%,需要进行系统性成本优化。
  • 场景二:业务流量存在明显的波峰波谷,需要弹性伸缩能力跟随业务曲线。
  • 场景三:事件驱动型工作负载(如消息队列消费者、定时任务),需要按需伸缩。
  • 场景四:多云或混合云环境,需要结合 Spot 实例进一步压缩成本。

1.3 环境要求

组件 版本要求 说明
Kubernetes 1.32+ HPA v2 API 稳定版,原生支持多指标伸缩
Metrics Server 0.7+ HPA/VPA 的基础资源指标来源
VPA 1.2+ 支持 Container-level 资源推荐
KEDA 2.16+ 事件驱动弹性,支持 80+ 种触发器
Karpenter 1.2+ 节点级弹性伸缩,可替代 Cluster Autoscaler
Prometheus 2.55+ / 3.x 自定义指标采集和 HPA 指标源
Grafana 11.x 用于展示成本监控看板

二、详细步骤

2.1 资源管理基础:Request 和 Limit 的本质

2.1.1 为什么 Request/Limit 是成本优化的起点

K8s 调度器依据 Pod 的 Request 来决定将其调度到哪个节点,而 kubelet 则根据 Limit 来约束 Pod 的实际资源使用量。这两个值设置得是否合理,直接决定了集群整体的资源利用效率。

# 一个典型的资源配置
resources:
  requests:
    cpu: “500m”  # 调度依据:调度器保证节点至少有 500m CPU 空闲
    memory: “512Mi” # 调度依据:调度器保证节点至少有 512Mi 内存空闲
  limits:
    cpu: “2000m” # 运行时上限:cgroup 限制,超过会被 throttle
    memory: “2Gi”  # 运行时上限:超过会被 OOMKill

常见的错误配置模式:

Request == Limit(Guaranteed QoS)
  优点:资源隔离最好,不会被驱逐
  缺点:资源利用率极低,CPU 空闲时也不能被其他 Pod 借用
  适用:数据库、有状态服务等关键负载

Request << Limit(Burstable QoS)
  优点:允许突发使用,利用率高
  缺点:节点超卖严重时可能被驱逐
  适用:无状态 Web 服务、异步任务处理

不设 Request/Limit(BestEffort QoS)
  优点:完全弹性
  缺点:第一个被驱逐,生产环境禁止使用
  适用:仅限开发测试环境
2.1.2 如何评估当前集群的资源浪费

在进行任何弹性伸缩优化之前,首先需要摸清现状。可以使用以下命令快速进行资源摸底:

# 查看所有命名空间的资源 Request 汇总
kubectl get pods -A -o json | jq -r '
  [.items[] | select(.status.phase=="Running") |
   .spec.containers[] | {
     cpu_req: (.resources.requests.cpu // “0”),
     mem_req: (.resources.requests.memory // “0”)
   }] | {
     total_pods: length,
     cpu_requests: [.[].cpu_req] | join(“, ”),
     mem_requests: [.[].mem_req] | join(“, ”)
   }‘

# 查看节点级别的资源分配率
kubectl describe nodes | grep -A 5 “Allocated resources”

# 用 Prometheus 查询实际利用率 vs Request 的比值
# CPU 利用率 / CPU Request,低于 0.3 说明严重浪费
# PromQL:
# sum(rate(container_cpu_usage_seconds_total{namespace=“production”}[5m]))
#   / sum(kube_pod_container_resource_requests{resource=“cpu”,namespace=“production”})

如果计算出的比值长期低于 0.3,则意味着至少有 70% 的 Request 资源被闲置浪费。这正是 VPA 和 HPA 需要协同解决的核心问题。

2.2 HPA 工作原理与 v2 配置详解

2.2.1 HPA 的核心控制循环

HPA Controller 默认每 15 秒(可通过 --horizontal-pod-autoscaler-sync-period 调整)执行一次控制循环:

1. 从 Metrics API 获取当前指标值(CPU/内存/自定义指标)
2. 计算期望副本数:desiredReplicas = ceil(currentReplicas * (currentMetric / targetMetric))
3. 应用稳定窗口和行为策略,决定是否执行伸缩
4. 更新 Deployment/StatefulSet 的 replicas 字段

关键公式示例:如果当前有 3 个 Pod,平均 CPU 使用率为 80%,而目标利用率为 50%,那么期望副本数 = ceil(3 * 80/50) = ceil(4.8) = 5。

2.2.2 HPA v2 完整配置

HPA v2 API (autoscaling/v2) 从 K8s 1.26 开始 GA,它支持多指标、自定义指标以及精细化的伸缩行为控制:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-api-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-api
  minReplicas: 3  # 最小副本数,保证基本可用性
  maxReplicas: 50 # 最大副本数,防止失控扩容
  metrics:
    # 指标一:CPU 利用率
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60 # 目标 CPU 利用率 60%
    # 指标二:内存利用率
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 70 # 目标内存利用率 70%
    # 指标三:自定义指标(每秒请求数)
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: “1000“ # 每个 Pod 目标 1000 QPS
  # 伸缩行为精细控制(v2 核心特性)
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30 # 扩容稳定窗口:30秒内取最大值
      policies:
      - type: Percent
        value: 100 # 每次最多扩容 100%(翻倍)
        periodSeconds: 60
      - type: Pods
        value: 10  # 或每次最多加 10 个 Pod
        periodSeconds: 60
      selectPolicy: Max # 取两个策略中更激进的
    scaleDown:
      stabilizationWindowSeconds: 300 # 缩容稳定窗口:5分钟内取最小值
      policies:
      - type: Percent
        value: 10  # 每次最多缩容 10%
        periodSeconds: 120
      selectPolicy: Min # 取最保守的缩容策略

参数说明:

  • stabilizationWindowSeconds:扩容通常设短(快速响应流量突增),缩容应设长(避免因指标抖动导致频繁伸缩)。
  • selectPolicy: Max(扩容):在多个策略中取最激进的一个,确保能快速扩容应对流量高峰。
  • selectPolicy: Min(缩容):在多个策略中取最保守的一个,避免缩容过快导致服务能力不足。
  • 在多指标场景下,HPA 会取所有指标计算出的最大副本数,任何一个指标触发扩容都会生效。
2.2.3 HPA 调优要点
# 查看 HPA 当前状态和事件
kubectl get hpa web-api-hpa -n production -o wide
kubectl describe hpa web-api-hpa -n production

# 常见问题:HPA 显示 <unknown> 指标
# 原因1:Metrics Server 未安装或异常
kubectl top pods -n production
# 原因2:Pod 没有设置 resources.requests
# HPA 计算利用率 = 实际使用量 / Request,没有 Request 就无法计算

# 常见问题:HPA 频繁震荡(扩了又缩,缩了又扩)
# 解决:加大缩容稳定窗口,调整目标利用率留出 buffer
# 目标利用率建议:CPU 50-70%,不要设到 80% 以上

2.3 VPA 三种模式与生产实践

2.3.1 VPA 解决什么问题

如果说 HPA 解决的是“需要多少个 Pod”的问题,那么 VPA 解决的就是“每个 Pod 需要多少资源”的问题。大多数团队设置 Request 和 Limit 时往往依靠经验或复制模板,VPA 通过分析历史资源使用数据,自动为 Pod 推荐或调整更合理的资源配置。

VPA 由三个核心组件构成:

VPA Recommender:分析 Prometheus 中的历史指标,计算推荐值
VPA Updater:根据推荐值驱逐需要调整资源的 Pod(触发重建)
VPA Admission Controller:在 Pod 创建时注入推荐的资源值
2.3.2 三种更新模式
# 模式一:Off(仅推荐,不自动修改)
# 最安全,适合初期观察阶段
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web-api-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-api
  updatePolicy:
    updateMode: “Off“ # 只生成推荐值,不做任何修改
  resourcePolicy:
    containerPolicies:
    - containerName: web-api
      minAllowed:
        cpu: “100m“
        memory: “128Mi“
      maxAllowed:
        cpu: “4000m“
        memory: “8Gi“
      controlledResources: [“cpu”, “memory“]
# 模式二:Initial(仅在 Pod 创建时应用推荐值)
# 不会驱逐已运行的 Pod,适合对可用性要求高的服务
spec:
  updatePolicy:
    updateMode: “Initial“ # 仅在 Pod 创建/重建时应用推荐值
# 模式三:Auto(自动驱逐并重建 Pod 以应用新的资源值)
# 最激进,会导致 Pod 重启,需要配合 PDB 使用
spec:
  updatePolicy:
    updateMode: “Auto“ # 自动驱逐 Pod 并以新资源值重建
  minReplicas: 2 # 至少保留 2 个 Pod 运行(VPA 1.2+ 支持)
# 查看 VPA 推荐值
kubectl describe vpa web-api-vpa -n production

# 输出示例:
# Recommendation:
#   Container Recommendations:
#     Container Name: web-api
#     Lower Bound:    Cpu: 125m,  Memory: 256Mi
#     Target:         Cpu: 350m,  Memory: 512Mi   <-- 推荐值
#     Uncapped Target: Cpu: 350m, Memory: 512Mi
#     Upper Bound:    Cpu: 1200m, Memory: 2Gi
2.3.3 HPA + VPA 协同策略

HPA 和 VPA 不能同时基于 CPU 指标工作,否则会产生冲突——HPA 想通过增加 Pod 数量来降低单个 Pod 负载,而 VPA 想通过增加单个 Pod 的资源来应对负载,两者互相干扰可能导致系统震荡。正确的协同方式如下:

方案一(推荐):HPA 基于自定义指标(如 QPS、请求延迟),VPA 负责管理 CPU 和内存资源。
方案二:HPA 负责基于 CPU 的水平伸缩,VPA 仅负责内存的垂直调整。
方案三:VPA 设为 Off 模式,定期人工审查推荐值后手动调整 Request。
# 推荐的协同配置:HPA 用 QPS 指标,VPA 管资源
# HPA 配置
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-api
  minReplicas: 3
  maxReplicas: 50
  metrics:
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second # 用 QPS 而不是 CPU
      target:
        type: AverageValue
        averageValue: “800“
---
# VPA 配置(管 CPU 和内存)
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web-api-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-api
  updatePolicy:
    updateMode: “Auto“
  resourcePolicy:
    containerPolicies:
    - containerName: web-api
      controlledResources: [“cpu”, “memory“]
      minAllowed:
        cpu: “200m“
        memory: “256Mi“
      maxAllowed:
        cpu: “4000m“
        memory: “8Gi“

2.4 KEDA 事件驱动弹性伸缩

2.4.1 为什么需要 KEDA

原生 HPA 的指标来源有限:主要是 Resource 指标(CPU/内存)、Custom 指标(需要配置 Prometheus Adapter)和 External 指标。配置 Prometheus Adapter 本身较为繁琐,且很多场景的伸缩触发信号并不在 Kubernetes 集群内部——例如 Kafka 消费者 lag、RabbitMQ 队列深度、AWS SQS 消息堆积数量、Cron 定时触发等,用原生 HPA 很难直接处理。

KEDA(Kubernetes Event-Driven Autoscaling)在 HPA 之上封装了一层,提供了 80+ 种开箱即用的触发器(Scaler),并且支持缩容到 0(而 HPA 的 minReplicas 最小值为 1)。

架构:KEDA Operator → 创建/管理 HPA → HPA 控制 Deployment 副本数
      KEDA Metrics Server → 暴露外部指标给 HPA 消费
2.4.2 KEDA 配置示例
# 基于 Kafka 消费者 lag 的弹性伸缩
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: kafka-consumer-scaler
  namespace: production
spec:
  scaleTargetRef:
    name: kafka-consumer
  pollingInterval: 15 # 每 15 秒检查一次指标
  cooldownPeriod: 300 # 缩容冷却期 5 分钟
  minReplicaCount: 0  # 支持缩容到 0!
  maxReplicaCount: 100
  fallback:
    failureThreshold: 3 # 指标获取连续失败 3 次后
    replicas: 5         # 回退到 5 个副本(安全兜底)
  triggers:
  - type: kafka
    metadata:
      bootstrapServers: kafka-broker:9092
      consumerGroup: order-processor
      topic: orders
      lagThreshold: “50“    # 每个分区 lag 超过 50 就扩容
      activationLagThreshold: “5“ # lag 超过 5 才从 0 唤醒
# 基于 Prometheus 指标的弹性伸缩(替代 Prometheus Adapter)
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: api-gateway-scaler
spec:
  scaleTargetRef:
    name: api-gateway
  minReplicaCount: 2
  maxReplicaCount: 30
  triggers:
  - type: prometheus
    metadata:
      serverAddress: http://prometheus.monitoring:9090
      query: |
        sum(rate(http_requests_total{service=“api-gateway”}[2m]))
      threshold: “5000“  # 总 QPS 超过 5000 就扩容
      activationThreshold: “100“ # QPS 超过 100 才激活

2.5 Karpenter 替代 Cluster Autoscaler

2.5.1 Cluster Autoscaler 的痛点

Cluster Autoscaler(CA)是 Kubernetes 官方的节点自动伸缩方案,但在生产环境中存在几个明显的短板:

  • 扩容慢:CA 依赖云厂商的 Auto Scaling Group(ASG),扩容需要等待 ASG 启动新实例,通常需要 2-5 分钟。
  • 机型选择死板:每个 Node Group 绑定固定的实例机型,想使用多种机型就需要创建多个 Node Group,管理复杂。
  • 缩容保守:默认需要节点空闲 10 分钟以上才触发缩容,且不会主动整理资源碎片化的节点。
  • Spot 支持弱:对 Spot 实例中断处理的支持需要额外配置,不够优雅。
2.5.2 Karpenter 的优势

Karpenter 直接调用云厂商的 API 创建实例,绕过了 ASG,从 Pod 处于 Pending 状态到节点 Ready 通常能在 60 秒内完成。它能根据 Pending Pod 的实际资源需求,动态选择最合适的实例机型,天然支持 Spot 实例和多机型混合策略。

# Karpenter NodePool 配置(AWS 示例)
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general-purpose
spec:
  template:
    spec:
      requirements:
      - key: kubernetes.io/arch
        operator: In
        values: [“amd64”, “arm64“] # 支持 ARM,通常便宜 20%
      - key: karpenter.sh/capacity-type
        operator: In
        values: [“spot”, “on-demand“] # 优先用 Spot
      - key: karpenter.k8s.aws/instance-category
        operator: In
        values: [“c”, “m”, “r“] # 计算/通用/内存优化型
      - key: karpenter.k8s.aws/instance-generation
        operator: Gt
        values: [“5”] # 只用第6代及以上实例
  nodeClassRef:
    group: karpenter.k8s.aws
    kind: EC2NodeClass
    name: default
  limits:
    cpu: “1000“  # 整个 NodePool 最多 1000 核
    memory: “2000Gi“
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 60s # 空闲 60 秒就开始整合
    budgets:
    - nodes: “20%“ # 同时最多中断 20% 的节点
# EC2NodeClass 配置
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiSelectorTerms:
  - alias: “bottlerocket@latest“ # 用 Bottlerocket 系统,启动更快
  subnetSelectorTerms:
  - tags:
      karpenter.sh/discovery: “my-cluster“
  securityGroupSelectorTerms:
  - tags:
      karpenter.sh/discovery: “my-cluster“
  blockDeviceMappings:
  - deviceName: /dev/xvda
    ebs:
      volumeSize: 100Gi
      volumeType: gp3
      iops: 3000
      throughput: 125

Karpenter 的节点整合(Consolidation)功能是成本优化的杀手锏:它会持续检测是否可以通过替换或删除节点来降低成本。例如,一个 8C16G 的节点上只运行了总计 2C4G 的 Pod,Karpenter 会自动将这些 Pod 迁移到更小的节点上,然后回收那个大规格节点。


三、示例代码和配置

3.1 完整的弹性伸缩配置套件

3.1.1 Deployment + HPA + VPA + PDB 联合配置

一个生产级的无状态 Web 服务,完整的弹性伸缩配置应包含四个资源对象。以下是可直接套用的模板:

# 文件路径:manifests/web-api/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-api
  namespace: production
  labels:
    app: web-api
    cost-center: platform-team
spec:
  replicas: 3 # 初始副本数,后续由 HPA 接管
  revisionHistoryLimit: 5
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 0 # 滚动更新期间不允许不可用
  selector:
    matchLabels:
      app: web-api
  template:
    metadata:
      labels:
        app: web-api
      annotations:
        prometheus.io/scrape: “true“
        prometheus.io/port: “8080“
        prometheus.io/path: “/metrics“
    spec:
      topologySpreadConstraints: # 打散到不同可用区
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: web-api
      containers:
      - name: web-api
        image: registry.example.com/web-api:v2.1.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: “500m“ # VPA 会自动调整这个值
            memory: “512Mi“
          limits:
            memory: “2Gi“ # 只设内存 Limit,不设 CPU Limit
        # 不设 CPU Limit 的原因:
        # CPU 是可压缩资源,超过 Limit 只会被 throttle 不会被 kill
        # 设了 CPU Limit 会导致 CPU throttling,P99 延迟飙升
        # 参考:https://home.robusta.dev/blog/stop-using-cpu-limits
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 20
---
# PDB:保证滚动更新和节点维护时的可用性
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-api-pdb
  namespace: production
spec:
  minAvailable: “60%“ # 任何时候至少 60% 的 Pod 可用
  selector:
    matchLabels:
      app: web-api
3.1.2 KEDA 缩容到零的消费者配置

对于消息队列消费者这类工作负载,没有消息时完全不需要运行 Pod。KEDA 的缩容到零能力在这个场景下可以节省大量资源:

# 文件路径:manifests/consumers/email-sender.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: email-sender
  namespace: production
spec:
  replicas: 0 # 初始 0 副本,由 KEDA 管理
  selector:
    matchLabels:
      app: email-sender
  template:
    metadata:
      labels:
        app: email-sender
    spec:
      terminationGracePeriodSeconds: 60 # 给足时间处理完当前消息
      containers:
      - name: email-sender
        image: registry.example.com/email-sender:v1.3.0
        resources:
          requests:
            cpu: “200m“
            memory: “256Mi“
          limits:
            memory: “512Mi“
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: email-sender-scaler
  namespace: production
spec:
  scaleTargetRef:
    name: email-sender
  pollingInterval: 10
  cooldownPeriod: 120 # 2 分钟无消息后缩到 0
  minReplicaCount: 0
  maxReplicaCount: 20
  triggers:
  - type: rabbitmq
    metadata:
      host: amqp://rabbitmq.middleware:5672
      queueName: email-tasks
      queueLength: “10“  # 每 10 条消息对应 1 个 Pod
      activationQueueLength: “1“ # 有 1 条消息就唤醒

3.2 成本分析脚本

3.2.1 集群资源利用率报告
#!/bin/bash
# 文件名:cost-report.sh
# 功能:生成集群资源利用率和成本浪费报告

echo “========== K8s 集群资源利用率报告 ==========”
echo “生成时间: $(date ‘+%Y-%m-%d %H:%M:%S’)”
echo ““

# 节点资源汇总
echo “--- 节点资源分配率 ---”
kubectl get nodes -o json | jq -r ‘
  .items[] |
  .metadata.name as $name |
  .status.allocatable.cpu as $cpu |
  .status.allocatable.memory as $mem |
  “\($name): CPU=\($cpu), Memory=\($mem)“‘

echo ““
echo “--- 各命名空间 Request 汇总 ---”
for ns in $(kubectl get ns -o jsonpath=‘{.items
  • .metadata.name}‘); do   cpu_req=$(kubectl get pods -n “$ns“ -o json 2>/dev/null | \     jq ‘[.items[].spec.containers[].resources.requests.cpu // “0” |     gsub(“m$“;““) | tonumber] | add // 0‘)   mem_req=$(kubectl get pods -n “$ns“ -o json 2>/dev/null | \     jq ‘[.items[].spec.containers[].resources.requests.memory // “0” |     gsub(“Mi$“;““) | gsub(“Gi$“;“000“) | tonumber] | add // 0‘)   if [ “$cpu_req” != “0” ] || [ “$mem_req” != “0” ]; then     echo “  $ns: CPU=${cpu_req}m, Memory=${mem_req}Mi”   fi done echo ““ echo “--- VPA 推荐值 vs 当前 Request 对比 ---” kubectl get vpa -A -o json | jq -r ‘   .items[] |   .metadata.namespace as $ns |   .metadata.name as $name |   .status.recommendation.containerRecommendations[]? |   “\($ns)/\($name): 推荐CPU=\(.target.cpu), 推荐Memory=\(.target.memory)“‘

  • 四、最佳实践和注意事项

    4.1 最佳实践

    4.1.1 成本优化实战:Spot 实例 + 弹性伸缩

    Spot 实例(AWS)/ 抢占式实例(阿里云)/ Preemptible VM(GCP)的价格通常是按量实例的 30-40%,但可能随时被云厂商回收。正确使用 Spot 实例的关键在于做好中断处理:

    # Karpenter NodePool:Spot 优先策略
    apiVersion: karpenter.sh/v1
    kind: NodePool
    metadata:
      name: spot-first
    spec:
      template:
        spec:
          requirements:
          - key: karpenter.sh/capacity-type
            operator: In
            values: [“spot”, “on-demand“] # Karpenter 默认优先选 Spot
          - key: karpenter.k8s.aws/instance-category
            operator: In
            values: [“c”, “m”]
          - key: karpenter.k8s.aws/instance-size
            operator: In
            values: [“large”, “xlarge”, “2xlarge“] # 多种规格分散中断风险
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      disruption:
        consolidationPolicy: WhenEmptyOrUnderutilized
        consolidateAfter: 30s

    Spot 使用的黄金法则:

    • 无状态服务放置在 Spot 实例上,有状态或关键服务放置在 On-Demand 实例上。
    • 配置多种实例类型(至少 10 种),以分散中断风险。
    • 设置 PodDisruptionBudget(PDB),保证 Spot 中断时不会同时丢失太多 Pod。
    • 为核心路径服务保留 20-30% 的 On-Demand 实例作为兜底。
    4.1.2 分时段弹性策略

    大部分 To B 业务在工作时间和非工作时间有明显的流量差异。利用 KEDA 的 Cron 触发器可以实现分时段伸缩:

    # 工作时间保持高水位,非工作时间缩容
    apiVersion: keda.sh/v1alpha1
    kind: ScaledObject
    metadata:
      name: web-api-cron-scaler
      namespace: production
    spec:
      scaleTargetRef:
        name: web-api
      minReplicaCount: 2
      maxReplicaCount: 50
      triggers:
      # 工作时间(周一到周五 8:00-20:00)保持至少 10 个副本
      - type: cron
        metadata:
          timezone: Asia/Shanghai
          start: “0 8 * * 1-5”
          end: “0 20 * * 1-5”
          desiredReplicas: “10”
      # 同时叠加 CPU 指标,应对突发流量
      - type: cpu
        metricType: Utilization
        metadata:
          value: “60”
    4.1.3 资源配额与成本分摊

    按命名空间设置资源配额,防止单个团队过度占用集群资源:

    # 给每个业务团队的命名空间设置配额
    apiVersion: v1
    kind: ResourceQuota
    metadata:
      name: team-quota
      namespace: team-a
    spec:
      hard:
        requests.cpu: “100”
        requests.memory: “200Gi”
        limits.memory: “400Gi”
        pods: “500”
        services.loadbalancers: “5” # 限制 LB 数量,LB 也是成本大头

    4.2 注意事项

    4.2.1 配置注意事项

    不设 CPU Limit 的理由和风险:
    Kubernetes 社区在近年逐渐形成共识:对于大部分无状态服务,不应该设置 CPU Limit。原因是 Linux CFS 调度器的 throttling 机制会导致严重的延迟毛刺。但不设 CPU Limit 意味着 Pod 的 QoS 等级是 Burstable,在节点资源紧张时可能被驱逐。应对方案是配合 PriorityClass 使用:

    # 高优先级服务不会被驱逐
    apiVersion: scheduling.k8s.io/v1
    kind: PriorityClass
    metadata:
      name: high-priority
      value: 1000000
      globalDefault: false
      description: “用于核心业务服务,不会被低优先级 Pod 抢占”
    4.2.2 常见错误
    错误现象 原因分析 解决方案
    HPA 指标显示 <unknown> Metrics Server 异常或 Pod 未设 Request 检查 kubectl top pods,确认 Request 已设置
    VPA 推荐值不更新 VPA Recommender 没有足够的历史数据 至少运行 24 小时后再查看推荐值
    Karpenter 不扩容 NodePool 的 limits 已达上限 检查 kubectl describe nodepool,调整 limits
    KEDA 缩容到 0 后无法唤醒 activationThreshold 设置不当 确认 activation 阈值低于正常触发阈值
    HPA 和 VPA 互相打架 两者同时基于 CPU 指标 HPA 用自定义指标,VPA 管资源 Request
    Spot 节点频繁中断 实例类型太少,竞价池容量不足 增加实例类型多样性,至少配置 10 种
    4.2.3 兼容性问题
    • VPA 与 HPA 兼容性:K8s 1.32+ 中 VPA 和 HPA 可以共存,但必须避免在同一指标维度上冲突。VPA 1.2+ 支持 controlledResources 字段,可以精确控制 VPA 只管内存不管 CPU。
    • KEDA 与原生 HPA:KEDA 会自动创建和管理 HPA 对象,不要手动创建同名 HPA,否则会冲突。
    • Karpenter 与 Cluster Autoscaler:两者不能同时运行在同一个集群中,迁移时需要先禁用 CA 再启用 Karpenter。

    五、故障排查和监控

    5.1 故障排查

    5.1.1 HPA 不生效排查

    HPA 不扩容或不缩容是最常见的问题,可按以下路径逐步排查:

    # 第一步:检查 HPA 状态
    kubectl get hpa -n production -o wide
    # 关注 TARGETS 列,如果显示 <unknown>/60% 说明指标获取失败
    
    # 第二步:查看 HPA 事件
    kubectl describe hpa web-api-hpa -n production
    # 关注 Events 部分,常见错误信息:
    # - “unable to get metrics”: Metrics Server 异常
    # - “missing request for cpu”: Pod 没设 resources.requests.cpu
    # - “failed to get external metric”: 自定义指标源不可用
    
    # 第三步:验证 Metrics Server 是否正常
    kubectl top nodes
    kubectl top pods -n production
    # 如果返回 “error: Metrics API not available”,需要检查 Metrics Server
    
    # 第四步:检查 Metrics Server 部署状态
    kubectl get pods -n kube-system -l k8s-app=metrics-server
    kubectl logs -n kube-system -l k8s-app=metrics-server --tail=50
    
    # 第五步:如果用了自定义指标,检查 Prometheus Adapter 或 KEDA
    kubectl get --raw “/apis/custom.metrics.k8s.io/v1beta1” | jq .
    kubectl get --raw “/apis/external.metrics.k8s.io/v1beta1” | jq .
    5.1.2 VPA 推荐值异常排查
    # 检查 VPA 组件状态
    kubectl get pods -n kube-system -l app=vpa-recommender
    kubectl get pods -n kube-system -l app=vpa-updater
    kubectl get pods -n kube-system -l app=vpa-admission-controller
    
    # 查看 VPA 推荐详情
    kubectl describe vpa web-api-vpa -n production
    # 如果 Recommendation 为空,可能原因:
    # 1. VPA Recommender 刚启动,需要至少 24 小时数据积累
    # 2. Prometheus 中没有对应 Pod 的历史指标
    # 3. targetRef 指向的 Deployment 不存在或名称拼错
    
    # 检查 VPA Recommender 日志
    kubectl logs -n kube-system -l app=vpa-recommender --tail=100
    5.1.3 Karpenter 扩容失败排查
    # 查看 Karpenter 控制器日志
    kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter --tail=100
    
    # 常见失败原因和对应日志关键词:
    # “insufficient capacity” -> 所选实例类型在当前 AZ 没有库存
    # “launch template” -> EC2NodeClass 配置有误
    # “subnet” -> 子网标签不匹配
    # “security group” -> 安全组标签不匹配
    
    # 检查 NodePool 状态
    kubectl describe nodepool general-purpose
    # 关注 Status.Resources,确认没有超过 limits
    
    # 检查 Pending Pod
    kubectl get pods -A --field-selector=status.phase=Pending
    kubectl describe pod <pending-pod-name> -n <namespace>
    # 关注 Events 中的调度失败原因
    5.1.4 KEDA 缩容到零后无法唤醒
    # 检查 KEDA Operator 状态
    kubectl get pods -n keda
    kubectl logs -n keda -l app=keda-operator --tail=50
    
    # 检查 ScaledObject 状态
    kubectl describe scaledobject kafka-consumer-scaler -n production
    # 关注 Conditions 部分:
    # - Ready: True/False
    # - Active: True/False(False 表示当前处于缩容到零状态)
    
    # 手动验证触发器指标
    # 以 Kafka 为例,检查消费者 lag
    kubectl exec -it kafka-broker-0 -- kafka-consumer-groups.sh \
      --bootstrap-server localhost:9092 \
      --describe --group order-processor

    5.2 Prometheus + Grafana 监控看板

    5.2.1 核心 PromQL 查询

    成本优化的前提是全面的可观测性。以下是构建成本监控看板需要的核心 PromQL 查询:

    # 1. 集群整体 CPU 利用率(实际使用 / 可分配总量)
    sum(rate(container_cpu_usage_seconds_total{container!=“”}[5m]))
      / sum(kube_node_status_allocatable{resource=“cpu”}) * 100
    
    # 2. 集群整体内存利用率
    sum(container_memory_working_set_bytes{container!=“”})
      / sum(kube_node_status_allocatable{resource=“memory”}) * 100
    
    # 3. CPU Request 浪费率(Request 中未被使用的比例)
    1 - (
      sum(rate(container_cpu_usage_seconds_total{container!=“”}[5m]))
      / sum(kube_pod_container_resource_requests{resource=“cpu”})
    )
    
    # 4. 各命名空间的资源成本占比(按 CPU Request 计算)
    sum by (namespace) (kube_pod_container_resource_requests{resource=“cpu”})
      / ignoring(namespace) group_left
      sum(kube_pod_container_resource_requests{resource=“cpu”}) * 100
    
    # 5. HPA 当前副本数 vs 期望副本数
    kube_horizontalpodautoscaler_status_current_replicas{namespace=“production”}
    kube_horizontalpodautoscaler_status_desired_replicas{namespace=“production”}
    
    # 6. VPA 推荐值 vs 当前 Request 的差异(需要 VPA exporter 暴露指标)
    vpa_status_recommendation{container=“web-api”, resource=“cpu”}
    
    # 7. Karpenter 节点生命周期(识别频繁创建销毁)
    count by (nodepool) (karpenter_nodes_total)
    sum by (capacity_type) (karpenter_nodes_total)  # Spot vs On-Demand 比例
    
    # 8. 节点空闲资源(识别可以缩容的节点)
    (1 - sum by (node) (rate(container_cpu_usage_seconds_total{container!=“”}[5m]))
      / on(node) kube_node_status_allocatable{resource=“cpu”}) * 100
    5.2.2 监控告警规则
    # 文件路径:prometheus-rules/cost-optimization.yaml
    apiVersion: monitoring.coreos.com/v1
    kind: PrometheusRule
    metadata:
      name: cost-optimization-alerts
      namespace: monitoring
    spec:
      groups:
      - name: cost-optimization
        interval: 60s
        rules:
        # 集群 CPU 利用率过低告警(浪费钱)
        - alert: ClusterCPUUnderutilized
          expr: |
            sum(rate(container_cpu_usage_seconds_total{container!=“”}[30m]))
            / sum(kube_node_status_allocatable{resource=“cpu”}) < 0.2
          for: 2h
          labels:
            severity: warning
            category: cost
          annotations:
            summary: “集群 CPU 利用率低于 20% 已超过 2 小时”
            description: “当前利用率 {{ $value | humanizePercentage }},建议检查是否可以缩减节点”
    
        # HPA 长时间处于最大副本数(可能需要扩容上限)
        - alert: HPAMaxedOut
          expr: |
            kube_horizontalpodautoscaler_status_current_replicas
            == kube_horizontalpodautoscaler_spec_max_replicas
          for: 30m
          labels:
            severity: warning
          annotations:
            summary: “HPA {{ $labels.horizontalpodautoscaler }} 已达最大副本数”
            description: “持续 30 分钟处于最大副本数,可能需要调整 maxReplicas”
    
        # Spot 节点占比过低(没有充分利用 Spot 降本)
        - alert: SpotNodeRatioLow
          expr: |
            sum(karpenter_nodes_total{capacity_type=“spot”})
            / sum(karpenter_nodes_total) < 0.5
          for: 1h
          labels:
            severity: info
            category: cost
          annotations:
            summary: “Spot 节点占比低于 50%”
            description: “当前 Spot 占比 {{ $value | humanizePercentage }},建议排查 Spot 容量问题”
    
        # 节点资源碎片化告警
        - alert: NodeResourceFragmentation
          expr: |
            count(
              (1 - sum by (node) (kube_pod_container_resource_requests{resource=“cpu”})
               / on(node) kube_node_status_allocatable{resource=“cpu”}) > 0.5
              and
              sum by (node) (kube_pod_container_resource_requests{resource=“cpu”})
               / on(node) kube_node_status_allocatable{resource=“cpu”} > 0.1
            ) > 3
          for: 1h
          labels:
            severity: warning
            category: cost
          annotations:
            summary: “超过 3 个节点存在资源碎片化”
            description: “节点分配率低但非空闲,Karpenter consolidation 可能未正常工作”
    5.2.3 Grafana 看板核心面板
    构建成本优化看板建议包含以下面板: 面板名称 可视化类型 数据源 说明
    集群总成本趋势 Time Series Prometheus + 成本标签 按天/周/月展示成本变化
    命名空间成本 TOP10 Bar Chart Prometheus 按 CPU+内存 Request 折算成本
    CPU 利用率 vs Request Gauge Prometheus 直观展示浪费比例
    HPA 副本数变化 Time Series Prometheus 观察伸缩是否符合预期
    Spot vs On-Demand 比例 Pie Chart Karpenter Metrics 监控 Spot 使用率
    VPA 推荐值偏差 Table VPA Exporter 哪些服务的 Request 偏差最大
    节点利用率热力图 Heatmap Prometheus 识别空闲节点和热点节点
    KEDA 缩放事件 Logs Panel KEDA Metrics 追踪缩容到零和唤醒事件

    值得注意的是,一套完善的监控体系是持续优化和稳定运行的基石。将上述配置与您的 Prometheus 和 Grafana 栈结合,可以极大地提升运维效率。


    六、总结

    6.1 技术要点回顾

    • Request/Limit 是基础:资源配置不合理,再好的弹性伸缩也效果有限。建议所有服务都设置 Request,无状态服务谨慎设置 CPU Limit,可先用 VPA Off 模式观察推荐值再行调整。
    • HPA v2 用好 behavior 字段:扩容策略要快(短稳定窗口 + 激进策略),缩容策略要慢(长稳定窗口 + 保守策略),避免系统震荡是首要目标。
    • VPA 和 HPA 分工明确:HPA 管理副本数(建议基于 QPS 等自定义指标),VPA 管理单个 Pod 的资源(CPU/内存),两者应避免在同一指标维度上产生冲突。
    • KEDA 解决长尾场景:对于消息队列消费者、定时任务、事件驱动型负载,利用 KEDA 的缩容到零能力可以节省大量闲置资源。
    • Karpenter 替代 Cluster Autoscaler:更快的扩容速度、更智能的机型选择、主动的节点整合能力,使其成为节点级弹性的现代优选方案。
    • Spot 实例是降本利器:配合 Karpenter 的多机型策略和 PDB,Spot 实例可以安全地覆盖 60-70% 的无状态工作负载,节省超过 50% 的计算成本。

    6.2 成本优化路线图

    按照投入产出比排序,建议分阶段实施优化:

    第一阶段(1-2 周,预期节省 15-25%):
      ├── 部署 VPA Off 模式,收集所有服务的资源推荐值
      ├── 根据 VPA 推荐值调整 Request/Limit(消除明显的过度分配)
      └── 为无状态服务移除不必要的 CPU Limit
    
    第二阶段(2-4 周,预期节省 20-30%):
      ├── 为核心无状态服务配置 HPA v2(基于 CPU + 自定义指标)
      ├── 为消息消费者部署 KEDA(启用缩容到零)
      └── 配置分时段弹性策略(工作时间 vs 非工作时间)
    
    第三阶段(4-8 周,预期节省 30-50%):
      ├── 从 Cluster Autoscaler 迁移到 Karpenter
      ├── 启用 Spot 实例(从非关键服务开始,逐步扩大覆盖面)
      ├── 开启 Karpenter Consolidation(节点整合)
      └── 部署成本监控看板,建立持续优化机制

    通过上述系统性方法,结合 云栈社区 中分享的实践经验,技术团队可以有效地将云资源成本控制在合理范围,实现真正的降本增效。优化是一个持续的过程,需要监控、观察和迭代调整。




    上一篇:经验复盘:从大厂到十几人小公司的真实职场挑战
    下一篇:curl与libcurl深度解析:被低估的网络传输基础设施与C/C++工程实践
    您需要登录后才可以回帖 登录 | 立即注册

    手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

    GMT+8, 2026-10-13 22:36 , Processed in 0.108432 second(s), 41 queries , Gzip On.

    Powered by Discuz! X3.5

    © 2025-2026 云栈社区.

    快速回复 返回顶部 返回列表