找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1513

积分

0

好友

193

主题
发表于 昨天 04:09 | 查看: 4| 回复: 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 追踪缩容到零和唤醒事件

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


    六、总结

    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-2-25 09:10 , Processed in 2.790142 second(s), 43 queries , Gzip On.

    Powered by Discuz! X3.5

    © 2025-2026 云栈社区.

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