技术博客

Karpenter v1 生产实战:智能节点自动扩缩容彻底替代 Cluster Autoscaler

Karpenter 于 2024 年底发布 v1.0 GA,2025-2026 年已在大量 AWS EKS 和 Azure AKS 生产集群验证。相比传统 Cluster Autoscaler,Karpenter 在节点扩容速度上快 3-4 倍(45-60 秒 vs 3-4 分钟),支持直接按 Pod 需求混合选择实例类型,并通过主动整合(Consolidation)自动回收空闲节点降低云成本。本文讲解 Karpenter v1 的核心 API(NodePool / EC2NodeClass)、整合策略配置、Spot 实例混用、以及 Karpenter vs Cluster Autoscaler 的实际对比。

KarpenterKubernetes自动扩缩容AWSEKSNodePoolSpot实例成本优化云原生

Karpenter 解决了一个长期困扰 Kubernetes 运维的问题:传统 Cluster Autoscaler 太慢、太笨

Cluster Autoscaler 需要通过 ASG(Auto Scaling Group)来扩容节点,整个流程涉及 ASG → EC2 API → 节点注册 → Ready,通常需要 3-4 分钟。在流量突发场景下,这意味着大量 Pod 长时间处于 Pending 状态,影响用户体验。

Karpenter 跳过 ASG,直接调用 EC2 API 创建实例,同时能根据 Pending Pod 的实际需求(CPU、内存、GPU、架构)选择最合适的实例类型,扩容时间压缩到 45-60 秒。


Karpenter vs Cluster Autoscaler 对比

维度                    Karpenter v1          Cluster Autoscaler
─────────────────────────────────────────────────────────────────
扩容速度               45-60 秒              3-4 分钟
实例类型选择            动态选择(按 Pod 需求)  预设 ASG 实例类型
Spot 实例支持           原生混合              需额外配置多 ASG
缩容/整合               主动整合(Consolidation)被动缩容(空闲 N 分钟)
GPU 感知                ✓(读取 Pod 资源请求)   需要手动配置 taint
自定义实例选择逻辑       NodePool requirements  ASG 优先级配置
云平台支持               AWS(GA)、Azure(GA)、GKE 不可用(GKE 有原生版本)

适用建议(2026):
  AWS EKS → Karpenter(强烈推荐)
  Azure AKS → Karpenter(推荐)
  GKE → Cluster Autoscaler(Karpenter 暂无 GKE GA 版本)

核心概念

Karpenter v1 的两个核心 CRD:

NodePool(节点池,cluster-scoped)
  定义"什么样的节点"和"什么时候扩容"
  控制:实例架构/类别/大小范围、操作系统、整合策略、容量限制
  
EC2NodeClass(EC2 节点类,cluster-scoped)
  定义"具体的 AWS 配置"
  控制:AMI 选择、安全组、子网、IAM 角色、用户数据(UserData)

关系:NodePool 通过 nodeClassRef 引用 EC2NodeClass
每个 NodePool 可以配置不同的整合策略和实例范围

安装 Karpenter(EKS 场景)

# 前置条件:EKS 集群(v1.28+)、AWS CLI 已配置

# 设置环境变量
export CLUSTER_NAME=my-eks-cluster
export AWS_DEFAULT_REGION=cn-hangzhou    # 或 ap-northeast-1 等
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

# 创建 Karpenter 所需的 IAM 角色(使用 IRSA)
eksctl create iamserviceaccount \
  --cluster ${CLUSTER_NAME} \
  --namespace karpenter \
  --name karpenter \
  --role-name "KarpenterControllerRole-${CLUSTER_NAME}" \
  --attach-policy-arn "arn:aws:iam::${AWS_ACCOUNT_ID}:policy/KarpenterControllerPolicy-${CLUSTER_NAME}" \
  --approve

# 安装 Karpenter(Helm)
helm repo add karpenter https://charts.karpenter.sh/
helm repo update

helm install karpenter oci://public.ecr.aws/karpenter/karpenter \
  --version "1.5.0" \
  --namespace "karpenter" \
  --create-namespace \
  --set "serviceAccount.annotations.eks\.amazonaws\.com/role-arn=arn:aws:iam::${AWS_ACCOUNT_ID}:role/KarpenterControllerRole-${CLUSTER_NAME}" \
  --set "settings.clusterName=${CLUSTER_NAME}" \
  --set "settings.interruptionQueue=${CLUSTER_NAME}" \
  --wait

# 验证安装
kubectl get pods -n karpenter
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter -f

EC2NodeClass 配置

apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: default
spec:
  # AMI 选择:使用 EKS Optimized AL2023
  amiFamily: AL2023
  
  # AMI 选择策略:自动使用最新的 EKS Optimized AMI(推荐)
  amiSelectorTerms:
  - alias: al2023@latest
  
  # 子网(通过 tag 选择)
  subnetSelectorTerms:
  - tags:
      karpenter.sh/discovery: "${CLUSTER_NAME}"
      # 或者指定特定子网 ID
      # aws-ids: "subnet-abc123,subnet-def456"
  
  # 安全组
  securityGroupSelectorTerms:
  - tags:
      karpenter.sh/discovery: "${CLUSTER_NAME}"
  
  # 节点 IAM Role(需要有 EC2 SSM 权限和 ECR 拉取权限)
  role: "KarpenterNodeRole-${CLUSTER_NAME}"
  
  # 根磁盘配置
  blockDeviceMappings:
  - deviceName: /dev/xvda
    ebs:
      volumeSize: 100Gi
      volumeType: gp3
      iops: 3000
      throughput: 125
      encrypted: true
      deleteOnTermination: true
  
  # 自定义 UserData(可选,用于节点初始化脚本)
  # userData: |
  #   #!/bin/bash
  #   echo "KUBELET_EXTRA_ARGS=--max-pods=110" >> /etc/default/kubelet

  # 实例存储(可选,用于需要本地 NVMe 的工作负载)
  instanceStorePolicy: RAID0    # 把实例存储配置为 RAID 0

NodePool 配置(核心)

通用 NodePool

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  # 整合策略(最重要的配置)
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized   # 空闲或利用率低时整合
    consolidateAfter: 30s                           # 节点空闲 30s 后开始整合
    # budgets:控制同时被整合/删除的节点数量,防止大规模节点重建影响业务
    budgets:
    - nodes: "10%"    # 最多同时中断 10% 的节点
      # 可以按时间控制(如只在业务低峰期整合)
      # schedule: "0 9 * * MON-FRI"    # 工作日 9:00
      # duration: 8h
  
  # 节点容量限制(防止意外扩容过多)
  limits:
    cpu: "1000"       # 整个 NodePool 最多 1000 个 CPU 核
    memory: "4000Gi"  # 最多 4TB 内存
  
  template:
    metadata:
      labels:
        node-type: general
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      
      requirements:
      # 架构(x86_64 + ARM64 混用,Graviton 实例更便宜)
      - key: kubernetes.io/arch
        operator: In
        values: ["amd64", "arm64"]
      
      # 容量类型(按需 + Spot,Spot 价格便宜 60-80%)
      - key: karpenter.sh/capacity-type
        operator: In
        values: ["on-demand", "spot"]
      
      # 实例类别(避免旧一代实例)
      - key: karpenter.k8s.aws/instance-category
        operator: In
        values: ["c", "m", "r"]    # c=计算优化, m=通用, r=内存优化
      
      # 实例代次(只用最新一代)
      - key: karpenter.k8s.aws/instance-generation
        operator: Gt
        values: ["3"]
      
      # 实例大小(避免过小或过大的实例)
      - key: karpenter.k8s.aws/instance-size
        operator: NotIn
        values: ["nano", "micro", "small", "medium", "metal"]
      
      # 操作系统
      - key: kubernetes.io/os
        operator: In
        values: ["linux"]
      
      # 节点终止等待时间
      expireAfter: 720h   # 节点 30 天后自动轮换(安全最佳实践)

专用 NodePool:高可用(仅按需实例)

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: stable
spec:
  disruption:
    # WhenEmpty:只在完全空闲时才整合(保护有状态工作负载)
    consolidationPolicy: WhenEmpty
    consolidateAfter: 5m
    budgets:
    - nodes: "1"    # 每次最多中断 1 个节点
  
  limits:
    cpu: "200"
    memory: "800Gi"
  
  # 设置较低的 weight,让 default NodePool 优先被选择
  weight: 1    # 数值越大优先级越高
  
  template:
    metadata:
      labels:
        node-type: stable
      annotations:
        # 标记节点为高可用节点(方便运维识别)
        team: "platform"
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      
      requirements:
      - key: karpenter.sh/capacity-type
        operator: In
        values: ["on-demand"]    # 只使用按需实例,不用 Spot
      - key: kubernetes.io/arch
        operator: In
        values: ["amd64"]
      - key: karpenter.k8s.aws/instance-category
        operator: In
        values: ["m", "r"]

GPU NodePool(AI 工作负载)

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: gpu
spec:
  disruption:
    consolidationPolicy: WhenEmpty    # GPU 节点只在空闲时回收
    consolidateAfter: 10m
    budgets:
    - nodes: "1"
  
  limits:
    cpu: "500"
    nvidia.com/gpu: "32"    # 最多 32 张 GPU
  
  template:
    metadata:
      labels:
        node-type: gpu
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      
      requirements:
      - key: karpenter.k8s.aws/instance-category
        operator: In
        values: ["g", "p"]    # g=GPU 实例, p=高性能 GPU 实例
      - key: karpenter.k8s.aws/instance-generation
        operator: Gt
        values: ["4"]
      - key: karpenter.sh/capacity-type
        operator: In
        values: ["on-demand", "spot"]
      
      # GPU 节点自动打 taint,防止普通 Pod 调度到 GPU 节点
      taints:
      - key: nvidia.com/gpu
        value: "true"
        effect: NoSchedule

Pod 配置:影响 Karpenter 节点选择

# Karpenter 会读取 Pod 的资源请求、nodeSelector、affinity 来选择合适的实例
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-api
spec:
  template:
    spec:
      containers:
      - name: api
        resources:
          requests:
            cpu: "2"
            memory: "4Gi"
          limits:
            cpu: "4"
            memory: "8Gi"
      
      # 选择 stable NodePool(按需实例)
      nodeSelector:
        node-type: stable
      
      # 或使用 nodeAffinity 更灵活
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: node-type
                operator: In
                values: ["general", "stable"]
      
      # 拓扑分散(配合 Karpenter 多可用区)
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: web-api

监控和排查

# 查看 Karpenter 创建/删除的节点事件
kubectl get events -n karpenter --sort-by='.lastTimestamp'

# 查看 NodePool 状态
kubectl get nodepools
kubectl describe nodepool default

# 查看当前节点分布(由哪个 NodePool 创建)
kubectl get nodes -L karpenter.sh/nodepool,node.kubernetes.io/instance-type,topology.kubernetes.io/zone

# 查看 Karpenter 正在监控的 Pending Pod
kubectl get pods -A --field-selector=status.phase=Pending

# 实时查看 Karpenter 日志(看整合决策过程)
kubectl logs -n karpenter \
  -l app.kubernetes.io/name=karpenter \
  -f --tail=100 | grep -E "provisioning|disruption|consolidat"

# 查看 Karpenter 指标(如果集成了 Prometheus)
# karpenter_nodepool_usage                  - NodePool 当前资源使用
# karpenter_nodes_total                     - 节点总数
# karpenter_provisioner_scheduling_duration - 调度决策耗时
# karpenter_disruption_disruptions_total    - 整合操作次数

成本优化实践

Spot 实例使用建议:

无状态 Web 服务、API:
  → 使用 Spot(80% 请求可以跑在 Spot 上)
  → 设置 PodDisruptionBudget 限制同时中断数量
  → 配合 HPA 快速补充被中断的 Spot 节点

有状态服务(数据库、消息队列):
  → 使用按需实例(stable NodePool)
  → 不要用 Spot(中断风险过高)

批处理 / AI 训练:
  → 积极使用 Spot(可接受中断后重试)
  → 使用 checkpointing 减少重试成本

实际成本节省(参考数据):
  Spot 实例 vs 按需:节省 60-80%
  Karpenter 整合 vs 手动管理:额外节省 20-30%(因为空闲节点被回收更快)
  综合:大多数企业用 Karpenter 后云成本降低 30-50%

小结

Karpenter v1 在 2026 年已经是 AWS EKS 集群的首选节点自动扩缩容方案。它的核心价值不只是“扩得快”,更在于 WhenEmptyOrUnderutilized 整合策略能持续主动缩容空闲节点——这才是长期降低云成本的关键。

从 Cluster Autoscaler 迁移到 Karpenter 的主要工作是配置 NodePool 和 EC2NodeClass,逻辑比 ASG 配置更清晰,只需要用 Kubernetes 资源声明意图,Karpenter 负责实现。