2. Karpenter核心架构与工作原理
Karpenter 的定位不是“自动扩缩容插件”,而是一个直接面向云 API 的节点供给控制器。它绕过了传统 Cluster Autoscaler 依赖的“节点组 + 云厂商伸缩组”两层抽象,把“需要什么规格的节点”这一决策权收回到 Kubernetes 集群内部。理解 Karpenter,关键是理解它的一组 CRD 如何分工,以及这些 CRD 如何驱动一条从 Pod 到云主机的完整链路。
一、核心 CRD 的职责划分
Karpenter 的 API 表面上有多个对象,但真正承担“配置—决策—执行—落地”职责的是四类:
- Provisioner / NodePool:定义“允许创建什么样的节点”。它包含节点模板(AMI、安全组、子网、IAM 实例配置文件)、容量类型(On-Demand / Spot)、实例族与规格约束、拓扑约束(可用区、架构)、启动 TTL、过期时间、整合策略等。在 Karpenter v1 中,NodePool 取代了旧版 Provisioner,并引入了
nodeClassRef指向独立的 NodeClass 对象。 - NodeClass:云厂商相关的节点配置,例如 AWS 的
EC2NodeClass,负责 AMI 选择、子网发现、安全组、块设备映射、用户数据等。把云厂商细节从 NodePool 中剥离,使 NodePool 更接近“调度策略”而非“云资源模板”。 - NodeClaim:Karpenter 内部创建的“节点申请单”。当调度器发现 Pending Pod 无法被现有节点满足时,Karpenter 会生成一个 NodeClaim,描述所需资源量、实例类型偏好、拓扑要求。NodeClaim 是 Karpenter 与云 API 之间的中间态,也是节点生命周期的载体。
- Machine / Node:NodeClaim 被云厂商满足后,对应一台真实实例;Karpenter 将其注册为 Kubernetes Node,并通过 NodeClaim 持续跟踪该节点的状态、容量与生命周期。
这四类对象的关系可以用一句话概括:NodePool 定义规则,NodeClass 定义云资源,NodeClaim 表达需求,Node 承载工作负载。它们不是并列的配置项,而是一条有方向的供给链。
二、调度决策:Karpenter 如何选择节点
Karpenter 的调度决策不是“先扩节点再调度”,而是“先看 Pod 需求,再反推节点规格”。当 Pod 处于 Pending 状态时,Karpenter 会读取其 resource requests、nodeSelector、affinity、toleration、topologySpreadConstraints,并与所有 NodePool 的约束做匹配。匹配过程会过滤掉不满足条件的实例类型,然后在剩余候选中按成本、可用容量、Spot 中断概率等维度排序。
一个常见的教学示例是:某工作负载请求 cpu: 4、memory: 16Gi,并带有 nodeSelector: {arch: amd64}。假设 NodePool 允许 m5.xlarge、m5.2xlarge、c5.2xlarge 三种实例,且 Spot 可用。Karpenter 会优先选择能满足需求且成本最低的组合,而不是固定绑定某一种实例。如果 Spot 容量不足,它会自动回退到 On-Demand,而不是让 Pod 一直 Pending。
这里有一个容易被忽略的细节:Karpenter 的“调度”并不等同于 kube-scheduler 的调度。kube-scheduler 负责把 Pod 绑定到已有 Node;Karpenter 负责在 Node 不存在时创建 Node。两者是接力关系,而不是替代关系。Karpenter 创建 Node 后,kube-scheduler 仍然会参与最终的 Pod 绑定。
三、节点生命周期:从创建到回收
Karpenter 管理的节点不是“创建后就不管”,而是持续被评估和替换。节点生命周期大致分为四个阶段:
- 创建:NodeClaim 被云 API 满足,实例启动并注册为 Node。Karpenter 会等待节点 Ready,并确认其容量与 NodeClaim 一致。
- 运行:节点承载 Pod。Karpenter 持续监控节点利用率、Pod 分布、Spot 中断信号。
- 整合:当节点利用率偏低,或存在更便宜的实例类型可以容纳现有 Pod 时,Karpenter 会尝试“整合”(consolidation)。整合不是简单缩容,而是先创建替代节点,再驱逐旧节点上的 Pod,最后终止旧节点。
- 过期与回收:NodePool 可以设置
expireAfter,例如720h,强制节点在运行一定时间后被替换,以滚动更新 AMI 或避免长期运行的配置漂移。节点被终止后,对应的 NodeClaim 也会被清理。
整合策略是 Karpenter 可靠性治理的关键。它有两种模式:WhenEmpty 和 WhenUnderutilized。前者只在节点为空时回收,保守但安全;后者允许在节点仍有 Pod 时进行替换,能显著提升利用率,但需要配合 PodDisruptionBudget 和优雅终止,否则可能影响可用性。
四、与云厂商的交互机制
Karpenter 与云厂商的交互集中在 NodeClass 和云 API 调用上。以 AWS 为例,EC2NodeClass 定义了 AMI 选择器、子网选择器、安全组选择器、IAM 实例配置文件、块设备映射和用户数据。Karpenter 通过这些选择器动态发现可用资源,而不是硬编码子网 ID 或安全组 ID。
当 NodeClaim 需要创建实例时,Karpenter 会调用 RunInstances,并传入实例类型、子网、安全组、IAM 配置文件、用户数据等参数。实例启动后,Karpenter 通过 DescribeInstances 跟踪状态,并在节点注册后更新 NodeClaim 状态。当节点需要终止时,Karpenter 调用 TerminateInstances,并确保节点先从 Kubernetes 中优雅摘除。
Spot 实例的交互更复杂。Karpenter 会订阅 Spot 中断通知(例如通过 SQS 或 EventBridge),在中断发生前主动驱逐 Pod 并创建替代节点。这一机制使 Spot 从“不可靠的低价资源”变成“可治理的容量来源”,但前提是工作负载本身支持中断,并且有合理的 PDB 与多副本策略。
五、核心 CRD 的配置要点
| CRD | 关键字段 | 作用 |
|---|---|---|
| NodePool | requirements、taints、disruption、limits |
定义节点准入条件、污点、整合策略与资源上限 |
| EC2NodeClass | amiSelectorTerms、subnetSelectorTerms、securityGroupSelectorTerms、role |
定义云资源发现规则与实例配置 |
| NodeClaim | resources、requirements、status |
表达单次节点申请的资源需求与执行状态 |
| Node | labels、capacity、conditions |
承载 Pod 并反馈节点健康与容量 |
理解这些字段的联动关系,比记住字段名更重要。例如,NodePool 的 limits 可以限制 CPU 和内存总量,防止 Karpenter 无限制扩容;disruption.consolidationPolicy 决定整合的激进程度;expireAfter 决定节点最长存活时间。这些参数共同构成节点供给的“边界条件”,也是可靠性治理的抓手。
六、常见误区与排障方向
第一个误区是把 Karpenter 当成“更快的 Cluster Autoscaler”。实际上,Karpenter 的决策粒度是 Pod 级别,而不是节点组级别。它不依赖 ASG,也不等待节点组扩容,因此扩容速度更快,但配置错误的影响也更直接。
第二个误区是忽略 NodePool 与 NodeClass 的引用关系。如果 NodePool 的 nodeClassRef 指向不存在的 NodeClass,或者 NodeClass 的 AMI 选择器匹配不到任何 AMI,NodeClaim 会一直处于未满足状态。排障时应先检查 NodeClaim 的 status.conditions,再检查 NodeClass 的选择器是否命中资源。
第三个误区是整合策略过于激进。如果 WhenUnderutilized 配合较短的 consolidateAfter,可能导致节点频繁创建和销毁,反而增加成本与中断。建议先以 WhenEmpty 观察,再逐步放开。
掌握 Karpenter 的核心架构,本质上就是掌握“配置—决策—执行—回收”这条链路。NodePool 和 NodeClass 决定能做什么,NodeClaim 决定正在做什么,Node 决定实际承载了什么。把这条链路理解清楚,后续的弹性策略、成本优化和可靠性治理才有稳定的基础。