饮墨

子安饮墨馀三斗,留与卿儿作赋来

用 Karpenter 实现 Kubernetes 节点秒级弹性伸缩:替代 Cluster Autoscaler 的 3 个关键优势与实操

痛点:Cluster Autoscaler 扩缩太慢,业务高峰白等 3 分钟

跑 Kubernetes 集群的运维都遇到过这个场景:流量突增,Pod 因为资源不足处于 Pending 状态,Cluster Autoscaler (CA) 开始工作——但从检测到 Pending Pod、选择 Node Group、调用云厂商 API 创建实例、到节点 Ready,整个链路动辄 3-5 分钟。对于电商大促、直播间涌入等场景,这个延迟直接等于丢用户。

CA 的核心问题不止是慢:

  • Node Group 预定义限制:你必须提前规划好哪些机型组合放在哪个 ASG 里,灵活性差
  • 扩缩决策粗粒度:CA 按 Node Group 为单位扩容,无法按 Pod 实际需求精确选型
  • 缩容保守:默认 10 分钟冷却期,闲置节点占着资源烧钱

AWS 在 2023 年将 Karpenter 捐赠给 CNCF 并升级为通用方案后,2025-2026 年它已经成为 EKS/GKE/AKS 上节点弹性的事实标准。今天用实操带你完成迁移。

方案:Karpenter — Just-in-Time 节点供给

Karpenter 的设计哲学是 "按 Pod 需求即时供给节点"

对比维度 Cluster Autoscaler Karpenter
扩容触发 定时轮询 Pending Pod(10-60s) Watch API Server,秒级响应
机型选择 预定义 Node Group 固定机型 实时从数十种机型中智能选优
缩容策略 整个节点空闲后等待冷却期 主动整合(Consolidation),持续优化
配置复杂度 多个 ASG + Launch Template 一个 NodePool CRD 搞定
成本优化 手动配 Spot 比例 自动混合 On-Demand/Spot,选最便宜可用实例

实操步骤

第 1 步:安装 Karpenter(以 EKS 为例)

# 环境变量
export KARPENTER_VERSION="1.2.0"
export CLUSTER_NAME="prod-cluster"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export TEMPOUT=$(mktemp)

# 创建 Karpenter 所需 IAM 角色和 SQS 中断队列
curl -fsSL "https://raw.githubusercontent.com/aws/karpenter-provider-aws/v${KARPENTER_VERSION}/website/content/en/docs/getting-started/getting-started-with-karpenter/cloudformation.yaml" > "${TEMPOUT}"

aws cloudformation deploy \
  --stack-name "Karpenter-${CLUSTER_NAME}" \
  --template-file "${TEMPOUT}" \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameter-overrides "ClusterName=${CLUSTER_NAME}"

# 用 Helm 安装 Karpenter
helm registry logout public.ecr.aws || true
helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter \
  --version "${KARPENTER_VERSION}" \
  --namespace kube-system \
  --set "settings.clusterName=${CLUSTER_NAME}" \
  --set "settings.interruptionQueue=Karpenter-${CLUSTER_NAME}" \
  --set controller.resources.requests.cpu=1 \
  --set controller.resources.requests.memory=1Gi \
  --wait

第 2 步:定义 NodePool 和 EC2NodeClass

# nodepool.yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64"]
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]  # 优先 Spot,Spot 不可用自动切 OD
        - 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"      # 集群 CPU 上限
    memory: 2000Gi
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 60s  # 空闲 60 秒即触发整合
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: default
spec:
  role: "KarpenterNodeRole-${CLUSTER_NAME}"
  amiSelectorTerms:
    - alias: al2023@latest  # Amazon Linux 2023 最新 AMI
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${CLUSTER_NAME}"
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${CLUSTER_NAME}"
  blockDeviceMappings:
    - deviceName: /dev/xvda
      ebs:
        volumeSize: 100Gi
        volumeType: gp3
        iops: 3000
        throughput: 125
kubectl apply -f nodepool.yaml

第 3 步:验证弹性伸缩效果

# 部署一个压测 Deployment,触发扩容
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: inflate
spec:
  replicas: 0
  selector:
    matchLabels:
      app: inflate
  template:
    metadata:
      labels:
        app: inflate
    spec:
      containers:
        - name: inflate
          image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
          resources:
            requests:
              cpu: "1"
              memory: 1.5Gi
EOF

# 扩到 20 副本,观察 Karpenter 秒级创建节点
kubectl scale deployment inflate --replicas=20

# 观察节点创建过程(通常 30-90 秒内完成)
kubectl get nodes -w

# 查看 Karpenter 日志确认选型逻辑
kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter -c controller --tail=50

第 4 步:迁移后移除旧 Cluster Autoscaler

# 确认所有工作负载已迁移到 Karpenter 管理的节点
kubectl get nodes -L karpenter.sh/nodepool

# 缩容旧的 Managed Node Group(逐步排空)
aws eks update-nodegroup-config \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name old-managed-ng \
  --scaling-config minSize=0,maxSize=0,desiredSize=0

# 卸载 Cluster Autoscaler
helm uninstall cluster-autoscaler -n kube-system

避坑指南

1. Spot 中断处理必须配 SQS 队列

Karpenter 依赖 SQS 接收 EC2 Spot 中断通知和实例维护事件,提前 2 分钟优雅迁移 Pod。如果没配 interruptionQueue,Spot 回收时 Pod 会被强杀。

# 验证 SQS 队列正常接收事件
aws sqs get-queue-attributes \
  --queue-url "https://sqs.us-east-1.amazonaws.com/${AWS_ACCOUNT_ID}/Karpenter-${CLUSTER_NAME}" \
  --attribute-names ApproximateNumberOfMessages

2. PDB(Pod Disruption Budget)配置不当导致缩容卡死

Karpenter 的 Consolidation 机制会主动迁移 Pod 以释放节点,如果你的 PDB 设置了 maxUnavailable: 0,节点永远无法被清空。建议:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: my-app-pdb
spec:
  minAvailable: "80%"   # 而非 maxUnavailable: 0
  selector:
    matchLabels:
      app: my-app

3. 节点启动耗时优化:AMI 预热 + 快速启动

默认节点从创建到 Ready 需要 60-90 秒,瓶颈在拉取容器镜像。两个加速手段:

  • 自定义 AMI 预烘焙:把大镜像(如 GPU 驱动、ML 框架)烤进 AMI
  • EC2 Fast Launch:预创建 EBS 快照加速启动
# EC2NodeClass 中启用 Fast Launch
spec:
  amiSelectorTerms:
    - id: ami-xxxx  # 预烘焙的自定义 AMI

总结

Karpenter 相比 Cluster Autoscaler 的核心提升:扩容从分钟级降到秒级,成本通过智能选型 + 主动整合平均降低 30-50%

迁移建议:

  1. 先在非生产环境跑 1-2 周,观察选型和 Consolidation 行为
  2. 生产环境用 disruption.budgets 限制同时中断的节点百分比
  3. 监控 karpenter_nodes_createdkarpenter_pods_startup_duration_seconds 两个核心指标
  4. 如果跑 GPU 工作负载,单独建一个 NodePool 限制 instance-family: ["p", "g"]

Karpenter 已是 CNCF Incubating 项目,多云支持逐步完善。现在迁移,正是时候。

您还没有登录,请登录后发表评论。