当 Kubernetes 集群的流量像潮汐一样起伏时,手动加节点既慢又贵。Karpenter 是 AWS 开源、现由 CNCF 托管的 Kubernetes 节点自动伸缩器,它能直接观察 Pending Pod 的需求,绕过传统节点组,即时供应最合适规格的节点,让集群弹性伸缩真正”刚刚好”,同时大幅压低云成本。
一、传统 Cluster Autoscaler 的痛点
在 Karpenter 之前,绝大多数团队用 Cluster Autoscaler(CA)。它的核心思路是”节点组驱动”:你必须预先在云厂商侧建好 Auto Scaling Group(ASG)或托管实例组,CA 检测到 Pending Pod 后,再去扩对应的节点组。这种机制有三个绕不开的短板。
- 粒度粗:扩容的最小单位是一个节点组,里面机型是固定模板,无法为不同工作负载挑不同机型。
- 扩容慢:要经过”节点组扩容 → 新实例启动 → 注册到集群”多步,冷启动常常要几分钟。
- 装箱率低:CA 不了解 Pod 真实的资源形状,经常出现”大节点上只放一个小 Pod”的浪费。
Karpenter 的思路完全不同:它直接读取调度器标记的 unschedulable Pod,计算资源需求,调用云 API 即时起一个”刚好够用”的节点(JIT Provisioning),节点就绪后立刻绑定,闲置时再自动回收整合。想深入 Kubernetes 基础可以先看我们的 Kubernetes 入门实战。
二、Karpenter 工作原理
Karpenter v1 由控制器 + 一组 CRD 组成,核心是 NodePool 与 NodeClaim。其闭环大致如下:
- 调度器发现无法放置的 Pod,打上
karpenter.sh/unschedulable标记; - Karpenter 的调度器插件读取这些 Pod 的资源请求、亲和性、污点容忍;
- 根据
NodePool里定义的机型约束,挑出成本最优或延迟最优的实例规格; - 调用云厂商 API 起节点,节点启动后自动加标签并加入集群;
- 持续做 consolidation(整合):当若干节点可被更少的节点容纳时,优雅驱逐并回收空节点。
这意味着你不再需要为每种 workload 维护一个节点组,Karpenter 自己会”算账”。
三、快速部署:Helm 安装
以 AWS EKS 为例,先添加仓库并安装。注意控制面需要能调用 EC2 的权限(通过 IAM Roles for Service Accounts 注入),并给节点打上默认污点,避免系统组件被调度到临时节点上。
# 添加并安装 Karpenter(v1 版本)
helm repo add karpenter https://charts.karpenter.sh
helm repo update
helm upgrade --install karpenter karpenter/karpenter \
--namespace karpenter --create-namespace \
--set settings.clusterName=my-eks-cluster \
--set settings.clusterEndpoint=$(aws eks describe-cluster \
--name my-eks-cluster --query status.endpoint --output text) \
--set controller.resources.requests.cpu=1 \
--set controller.resources.requests.memory=1Gi \
--wait
部署完成后,用 kubectl get pods -n karpenter 确认控制器处于 Running。若你用的是轻量集群,可参考我们 k3s 轻量级 Kubernetes 实战 的思路先搭好底座。
四、配置 NodePool:定义节点的”游戏规则”
NodePool 取代了旧版 Provisioner,用来声明”允许起哪些节点”。下面这份配置允许 Karpenter 在按需与 Spot 之间自由选择 AMD/ARM 实例,并设置了 30 天过期轮换与资源上限。
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
metadata:
labels:
karpenter.sh/capacity-type: spot-or-ondemand
spec:
taints:
- key: karpenter.sh/uncordoned
effect: NoSchedule
requirements:
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"]
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["5"]
limits:
cpu: "1000"
memory: 1000Gi
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
expireAfter: 720h
其中 capacity-type 同时允许 spot 与 on-demand,是降本的关键开关;expireAfter 让节点定期滚动,避免长期运行的”配置漂移”。
五、实战:让工作负载自动触发扩缩容
写一个带明确资源请求的 Deployment,再把它放大,就能直观看到 Karpenter 起节点。
apiVersion: apps/v1
kind: Deployment
metadata:
name: load-demo
spec:
replicas: 1
selector:
matchLabels: { app: load-demo }
template:
metadata:
labels: { app: load-demo }
spec:
containers:
- name: app
image: nginx:1.27
resources:
requests: { cpu: "1", memory: 1Gi }
limits: { cpu: "1", memory: 1Gi }
把副本数拉到 50:
kubectl apply -f deployment.yaml
kubectl scale deployment/load-demo --replicas=50
# 观察 Karpenter 即时起的新节点
kubectl get nodes -l karpenter.sh/initialized=true -w
# 缩回后,节点会在 consolidation 或 ttl 到期后被自动回收
kubectl scale deployment/load-demo --replicas=1
你会发现节点数量跟着 Pending Pod 曲线走,而不是跟着你手动改节点组。配合 CI 流水线做压测验证也很方便,参考 GitHub Actions 自动化实战 把这套扩容验证固化进流水线。
六、成本优化四板斧
- 用 Spot 实例:非核心、可中断的负载放 Spot,通常能省 60%–90% 费用,配合 Pod 的
toleration与topologySpreadConstraints提升容错。 - 开启 Consolidation:让 Karpenter 把碎片化的 Pod 重新装箱,回收半空的节点。
- 选对机型形状:CPU 密集选 c 系、内存密集选 r 系,避免”买内存送 CPU”的浪费。
- 过期与滚动:用
expireAfter周期性翻新节点,规避长期漏洞与漂移。
成本与可用性的取舍,本质上和 服务器安全加固 里”按需开放、最小暴露”的思路一脉相承:只给刚好够的资源,只开刚好够的口子。
七、Karpenter 与 Cluster Autoscaler 对比
| 维度 | Cluster Autoscaler | Karpenter |
|---|---|---|
| 扩容单位 | 节点组(ASG/MIG) | 单节点(JIT) |
| 机型选择 | 固定模板 | 按 Pod 需求动态选 |
| 启动延迟 | 分钟级 | 秒级到分钟级 |
| 装箱优化 | 弱 | 强(consolidation) |
| Spot 支持 | 需多个节点组 | 原生混合 |
| 运维负担 | 维护多节点组 | 维护 NodePool 规则 |
八、生产落地检查清单与常见误区
| 误区 / 风险 | 正确做法 |
|---|---|
| 系统 Pod 被调度到临时 Spot 节点 | 给 kube-system 加污点容忍豁免,或为关键组件单独 NodePool |
| Spot 中断导致服务雪崩 | 设置 PDB + 多 AZ 分散 + 中断处理(terminationGracePeriod) |
| NodePool 无上限被刷爆账单 | 务必配置 limits.cpu/memory 与预算告警 |
| 节点回收时 Pod 被强制杀 | 开启 graceful eviction,给应用 preStop 与宽限时间 |
落地时建议把节点扩缩容指标接进 Prometheus + Grafana 监控,用一眼可见的曲线验证 Karpenter 是否真的在帮你省钱。容器层的入口流量治理可搭配 Traefik 实战 与 Cilium 零信任网络 形成完整栈。
九、与 HPA / KEDA 配合:Pod 层与节点层双层弹性
Karpenter 解决的是”节点够不够”的问题,而副本数由 Horizontal Pod Autoscaler(HPA)或 KEDA 决定。两者配合,才能形成”Pod 先扩、节点紧随补位”的顺滑曲线,而不是各管一段。一个典型的 HPA 配置如下:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: load-demo-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: load-demo
minReplicas: 2
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
当 HPA 把副本顶上去、现有节点放不下时,Karpenter 会立刻补节点;副本回落后,多余节点又被整合回收。我们之前在 KEDA 实战:事件驱动伸缩 里讲过的事件驱动扩容,同样可以叠加在这套节点底座之上。需要特别注意的是:不要让 HPA 的上限远高于 Karpenter 的 limits,否则会出现”Pod 想扩、节点却被卡住”的死锁。
十、从 Cluster Autoscaler 平滑迁移
别一刀切。推荐四步走,让业务全程无感:
- 并行部署:先在集群里装好 Karpenter,但只给它一个单独 NodePool,用节点标签把少数非核心负载调度过去试水;
- 观察验证:盯一段时间这些负载的扩缩容周期与账单,确认稳定可靠;
- 灰度迁移:用节点亲和性或污点,逐步把更多负载迁到 Karpenter 管理的节点;
- 下线旧方案:最后再停用 Cluster Autoscaler 背后的旧节点组,完成切换。
整个过程可随时回滚——只要把流量切回原来的节点组即可。对于已经在跑关键业务的集群,这种”先旁路、后切换”的节奏远比一次性替换安全。
小结:Karpenter 把”节点该是什么样”的决策权从运维手里交还给调度器,用按需即时供应 + 自动整合,换来了更顺滑的弹性与更低的账单。对于流量波动明显、希望把云成本压到极致的 Kubernetes 团队,它几乎是必选项。先用一个非核心的负载做灰度,观察几个扩缩容周期,再逐步把核心负载迁过来,是最稳妥的节奏。




