Karpenter 调度机制完全指南:从 Pod 约束到 NodePool 分层调度实战
【免费下载链接】karpenter-provider-awsKarpenter is a Kubernetes Node Autoscaler built for flexibility, performance, and simplicity.项目地址: https://gitcode.com/GitHub_Trending/ka/karpenter-provider-aws
本指南以 Karpenter(karpenter-provider-aws 仓库)v1.0 版本文档为核心,系统讲解 Karpenter 的调度模型:如何通过"云提供商约束 → NodePool 约束 → Pod 约束"三层叠加,实现精准的节点选择;如何利用资源请求、nodeSelector、节点亲和性、拓扑分布、污点与容忍、持久卷拓扑等 Kubernetes 标准调度能力;以及如何通过加权 NodePool、限制(limits)与高级调度技巧(Exists操作符、Spot/On-Demand 比例拆分)满足生产环境的精细化需求。读完本文,你将掌握用声明式 YAML 配置 Karpenter 调度策略的完整方法,并能结合源码理解其底层调度决策逻辑。
分层约束模型:理解 Karpenter 如何决策
Karpenter 的调度哲学建立在一个核心概念上:分层的约束叠加(layered constraints)。如果你的 Pod 对运行位置和运行方式没有任何要求,Karpenter 可以从云提供商提供的全部资源中自由选择节点。但借助这一分层约束模型,你可以确保 Pod 获得精确的类型与数量的资源。
产生约束需求的典型场景包括:
- 需要运行在依赖应用或存储所在的可用区(Zone)
- 需要特定种类的处理器或其他硬件(如 GPU、NVMe 本地存储)
- 希望使用拓扑分布(Topology Spread)等技术保障高可用
约束由三个层次依次叠加而成:
- 云提供商层:定义其云环境可用的全部实例类型、架构、可用区与购买方式(如 Spot、On-Demand)。这是最宽泛的一层。
- 集群管理员层:通过创建一个或多个 NodePool 增加约束。NodePool 定义了节点的最小规格模板。
- 应用开发者层:在 Pod/Deployment 的 spec 中添加调度约束。
关键原则是:Pod 的调度约束必须落在某个 NodePool 的约束范围之内,否则 Pod 无法被部署。例如,如果 NodePool 限制了只能使用某个特定可用区,而 Pod 请求了另一个可用区,那么该 Pod 将无法被调度。从调度实现的角度看,NodePool 中的spec.template.spec.requirements会被作为 NodeClaim 的创建模板,Karpenter 在调度 Pod 时会将 Pod 的约束与 NodePool 的约束做交集(intersection)计算,只有交集非空才会启动节点。
Pod 侧可以请求的约束类型包括:
- 资源请求(Resource requests):请求一定量的内存或 CPU。
- 节点选择(Node selection):通过
nodeSelector选择带有特定标签的节点。 - 节点亲和性(Node affinity):将 Pod 吸引到具有特定属性的节点上。
- 拓扑分布(Topology spread):利用拓扑分布保障应用的可用性。
- Pod 亲和/反亲和(Pod affinity/anti-affinity):根据其他 Pod 的调度情况,将 Pod 吸引或排斥到某些拓扑域。
Karpenter 完整支持 Kubernetes 标准调度约束,这意味着你可以定义一套同时适用于既有容量(existing capacity,由 kube-scheduler 调度)与 Karpenter 新置备容量(provisioned capacity)的规则。Karpenter 还支持 Kubernetes 官方的 Well-Known Labels, Annotations and Taints,这些标签对调度非常有用。
资源请求:实例类型选择的输入
在 Pod spec 中,你可以同时为 Pod 所需的资源(如 CPU 与内存)设置请求(requests)与上限(limits):
apiVersion: v1 kind: Pod metadata: name: myapp spec: containers: - name: app image: myimage resources: requests: memory: "128Mi" cpu: "500m" limits: memory: "256Mi" cpu: "1000m"在上例中,容器请求了 128MiB 内存与 0.5 CPU,上限设置为 256MiB 内存与 1 CPU。实例类型的选择计算只使用requests,而limits可以用于启用资源超卖(oversubscription)。这是 Karpenter 与常规调度器的一个重要区别:它基于请求量来做装箱(bin packing)决策,因此合理设置 requests 直接影响节点选型与成本。
有关 Kubernetes 支持的资源类型细节,可参考 Managing Resources for Containers。
加速器/GPU 资源
加速器(如 GPU)的取值包括:
nvidia.com/gpuamd.com/gpuaws.amazon.com/neuronhabana.ai/gaudi
Karpenter 支持 GPU 等加速器。在工作负载清单中附加资源需求,即可将依赖 GPU 的 Pod 调度到合适的节点上。以下是一个工作负载清单(如 Pod)中的加速器资源示例:
spec: template: spec: containers: - resources: limits: nvidia.com/gpu: "1"注意:如果置备 GPU 节点,你需要为这些节点部署相应的 GPU 设备插件 DaemonSet。没有 DaemonSet 运行,Karpenter 将看不到这些节点完成初始化(节点会一直处于未就绪状态)。相关文档:
nvidia.com/gpu:NVIDIA device plugin for Kubernetesamd.com/gpu:AMD GPU device plugin for Kubernetesaws.amazon.com/neuron:Kubernetes environment setup for Neuronhabana.ai/gaudi:Habana device plugin for Kubernetes
Pod ENI 资源(Security Groups for Pods)
Pod ENI 是 AWS VPC CNI 插件的一项功能,允许将弹性网络接口(ENI)直接分配给 Pod。启用后,vpc.amazonaws.com/pod-eni扩展资源会被添加到受支持的节点上。Pod ENI 可以独立使用,但最常与 Security Groups for Pods 配合使用。
注意:在 Karpenter 中启用 Pod ENI 之前,必须先启用 AWS VPC CNI 插件中的 Pod ENI 支持,请参考 Security Groups for Pods 文档。
注意:如果你启用了 Security Groups per Pod)。
以下是在 Deployment 清单中定义 pod-eni 资源的示例:
spec: template: spec: containers: - resources: limits: vpc.amazonaws.com/pod-eni: "1"Windows 支持提醒:Security Groups for Pods 目前不支持 Windows 节点(见 Security Groups for Pods)。
选择节点:标签、nodeSelector 与亲和性
通过nodeSelector你可以请求一个匹配指定键值对的节点,这些键值对既可以是 well-known 标签,也可以是你自定义的标签。
affinity可用于定义更复杂的约束,完整的规范见 Node Affinity。
标签
Well-known 标签既可以作为 NodePool 的 requirements,也可以作为 Pod 的调度约束。你也可以通过在 NodePool 上指定requirements或labels来定义自己的自定义标签,然后在 Pod 上通过nodeAffinity或nodeSelector选择它们。
警告:务必确保标签域(domain)正确。像karpenter.k8s.aws/instance-family这样的 well-known 标签会强制执行节点属性,但可能与node.kubernetes.io/instance-family混淆——后者对 Karpenter 来说是未知的,会被当作自定义标签处理,不会强制执行节点属性。
Well-Known 标签一览
| 标签 | 示例 | 描述 |
|---|---|---|
| topology.kubernetes.io/zone | us-east-2a | 可用区由云提供商定义(aws) |
| node.kubernetes.io/instance-type | g4dn.8xlarge | 实例类型由云提供商定义(aws) |
| node.kubernetes.io/windows-build | 10.0.17763 | Windows OS 构建版本,格式为 "MajorVersion.MinorVersion.BuildNumber",WS2019 为10.0.17763,WS2022 为10.0.20348 |
| kubernetes.io/os | linux | 操作系统由实例上的 GOOS 值 定义 |
| kubernetes.io/arch | amd64 | 架构由实例上的 GOARCH 值 定义 |
| karpenter.sh/nodepool | default | 用于置备该节点的 NodePool 名称 |
| karpenter.sh/capacity-type | spot | 容量类型包括spot、on-demand |
| karpenter.k8s.aws/ec2nodeclass | default | [AWS 专用] 用于置备该节点的 EC2NodeClass 名称 |
| karpenter.k8s.aws/instance-hypervisor | nitro | [AWS 专用] 使用特定 Hypervisor 的实例类型 |
| karpenter.k8s.aws/instance-encryption-in-transit-supported | true | [AWS 专用] 支持(或不支持)传输中加密的实例类型 |
| karpenter.k8s.aws/instance-category | g | [AWS 专用] 相同类别的实例类型,通常为代数前的字符串 |
| karpenter.k8s.aws/instance-generation | 4 | [AWS 专用] 实例类别内的实例类型代数 |
| karpenter.k8s.aws/instance-family | g4dn | [AWS 专用] 属性相似但资源数量不同的实例类型 |
| karpenter.k8s.aws/instance-size | 8xlarge | [AWS 专用] 资源数量相似但属性不同的实例类型 |
| karpenter.k8s.aws/instance-cpu | 32 | [AWS 专用] 实例的 CPU 数量 |
| karpenter.k8s.aws/instance-cpu-manufacturer | aws | [AWS 专用] CPU 制造商名称 |
| karpenter.k8s.aws/instance-memory | 131072 | [AWS 专用] 实例内存的 MiB 数 |
| karpenter.k8s.aws/instance-ebs-bandwidth | 9500 | [AWS 专用] 实例可用的 EBS 最大带宽(Mbps) |
| karpenter.k8s.aws/instance-network-bandwidth | 131072 | [AWS 专用] 实例可用的基线带宽(Mbps) |
| karpenter.k8s.aws/instance-pods | 110 | [AWS 专用] 实例支持的 Pod 数量 |
| karpenter.k8s.aws/instance-gpu-name | t4 | [AWS 专用] 实例上的 GPU 名称(如果有) |
| karpenter.k8s.aws/instance-gpu-manufacturer | nvidia | [AWS 专用] GPU 制造商名称 |
| karpenter.k8s.aws/instance-gpu-count | 1 | [AWS 专用] 实例上的 GPU 数量 |
| karpenter.k8s.aws/instance-gpu-memory | 16384 | [AWS 专用] GPU 内存的 MiB 数 |
| karpenter.k8s.aws/instance-local-nvme | 900 | [AWS 专用] 实例本地 NVMe 存储的 GiB 数 |
| topology.k8s.aws/zone-id | use1-az1 | [AWS 专用] 全局一致的可用区 ID |
这些 AWS 专用标签在源码中有明确定义与注册,见 pkg/apis/v1/labels.go:例如karpenter.k8s.aws/instance-family(LabelInstanceFamily = apis.Group + "/instance-family")、topology.k8s.aws/zone-id(LabelTopologyZoneID = "topology.k8s.aws/zone-id")等都被插入到WellKnownLabels集合中,成为调度器可感知的约束键。
注意:Karpenter 会将以下废弃标签翻译为稳定等价标签:failure-domain.beta.kubernetes.io/zone、failure-domain.beta.kubernetes.io/region、beta.kubernetes.io/arch、beta.kubernetes.io/os和beta.kubernetes.io/instance-type。
用户自定义标签
Karpenter 感知多个 well-known 标签,这些标签由实例类型详情推导而来。如果你使用一个 Karpenter 不认识的标签指定nodeSelector或必需的nodeAffinity,Karpenter 不会启动带这些标签的节点,Pod 将保持 Pending 状态。要让 Karpenter 知道它可以为这些标签调度,必须在 NodePool 的 requirements 中使用Exists操作符声明该标签:
requirements: - key: user.defined.label/type operator: Exists注意:目前 NodePool 和 NodeClaim 上的 requirements 总数限制为 100。需要注意,spec.template.metadata.labels在创建 NodeClaim 时也会被传播为 requirements,因此 NodePool 上设置的 requirements 与 labels 合计不能超过 100。
Node selectors
以下是一个用于选择节点的nodeSelector示例:
nodeSelector: topology.kubernetes.io/zone: us-west-2a karpenter.sh/capacity-type: spot该示例同时使用了 well-known 标签(topology.kubernetes.io/zone)和对 Karpenter 而言的 well-known 标签(karpenter.sh/capacity-type)。
如果你想创建自定义标签,应该在 NodePool 层级定义,然后 Pod 声明该自定义标签。关于nodeSelector的细节参见 Kubernetes 文档。
Preferences:偏好如何被处理
Karpenter 感知偏好(preferences),包括节点亲和性、Pod 亲和性、Pod 反亲和性与 Pod 拓扑分布,并在大多数情况下将它们当作 requirements 处理。Karpenter 在以下两种场景使用这些偏好:
- 判断 Pod 能否调度到某个节点(无拓扑要求时)
- 判断 Pod 能否被迁移(shift)到新节点
Karpenter 在构造 Pod 的 requirements 时,会先把偏好亲和性(preferred affinities)当作必需亲和性(required affinities)处理。当这些 requirements 无法满足时,Pod 的偏好会按权重升序逐个放宽(权重最低的最先放宽),然后用剩余的 requirements 再次尝试。
警告:在构造调度到节点的拓扑 requirements 时,Karpenter不会把偏好亲和性解释为必需。如果这些偏好是必需的,应使用必需亲和性(见下文 Node affinity 文档)。
Node affinity
以下示例演示如何使用 Node affinity 来包含(In)或排除(NotIn)对象。设置规则时,以下两种 Node affinity 类型决定了每条规则的硬/软程度:
- requiredDuringSchedulingIgnoredDuringExecution:必须满足的硬规则。
- preferredDuringSchedulingIgnoredDuringExecution:偏好,但 Pod 可以在不保证满足的节点上运行。
注意:Pod 上的偏好亲和性可能导致创建的节点多于预期,因为 Karpenter 倾向于创建新节点来满足偏好(详见上文 Preferences 文档)。
每种规则的IgnoredDuringExecution部分告诉 Pod:即使节点上的条件发生变化、规则不再匹配,Pod 也继续运行。你可以将这两个概念理解为required和preferred(Kubernetes 从未实现这些规则的其他变体)。
下面所有示例都假设 NodePool 没有阻止使用这些可用区的约束。第一个约束表示可以使用us-west-2a或us-west-2b,第二个约束使得只能使用us-west-2b:
affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: "topology.kubernetes.io/zone" operator: "In" values: ["us-west-2a", "us-west-2b"] - key: "topology.kubernetes.io/zone" operator: "In" values: ["us-west-2b"]把第二个操作符改为NotIn,Pod 将只能运行在us-west-2a:
- key: "topology.kubernetes.io/zone" operator: "In" values: ["us-west-2a", "us-west-2b"] - key: "topology.kubernetes.io/zone" operator: "NotIn" values: ["us-west-2b"]继续扩展示例:nodeAffinity允许你定义多个 term,如果第一个 term 不满足就尝试下一个。下面这个例子中,如果us-west-2a不可用,第二个 term 会让 Pod 运行在us-west-2d的 Spot 实例上:
affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: # OR - key: "topology.kubernetes.io/zone" # AND operator: "In" values: ["us-west-2a", "us-west-2b"] - key: "topology.kubernetes.io/zone" # AND operator: "NotIn" values: ["us-west-2b"] - matchExpressions: # OR - key: "karpenter.sh/capacity-type" # AND operator: "In" values: ["spot"] - key: "topology.kubernetes.io/zone" # AND operator: "In" values: ["us-west-2d"]一般而言,Karpenter 会按顺序遍历每个nodeSelectorTerms,采用第一个可用的。如果第一个nodeSelectorTerms置备失败,它会用第二个重试;如果全部失败,则 Pod 置备失败。Karpenter 会随时间退避(backoff)并重试,因此当容量变为可用时,无需用户干预即可调度该 Pod。
污点与容忍
污点(Taints)与亲和性相反:在节点上设置污点,是告诉调度器除非 Pod 明确声明可以容忍该污点,否则不要在该节点上运行 Pod。以下示例展示了一个设置了污点的 NodePool,只运行需要 GPU 的 Pod:
apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: gpu spec: template: spec: requirements: - key: karpenter.k8s.aws/instance-family operator: In values: - p3 taints: - key: nvidia.com/gpu value: "true" effect: "NoSchedule"要让 Pod 请求运行在由此 NodePool 创建的节点上,可以在 Pod 上设置如下容忍(toleration):
apiVersion: v1 kind: Pod metadata: name: mygpupod spec: containers: - name: gpuapp resources: requests: nvidia.com/gpu: 1 limits: nvidia.com/gpu: 1 image: mygpucontainer tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule"关于 Taints and Tolerations 的细节参见 Kubernetes 文档。
拓扑分布(Topology Spread)
利用 Kubernetes 的topologySpreadConstraints,你可以让 Pod 互相"推开",从而限制故障的爆炸半径。可以把它理解为 Pod 亲和性的演进:在允许分散的同时建立 Pod 与节点之间的关系。
注意:偏好型拓扑分布(ScheduleAnyway)可能导致创建的节点多于预期,因为 Karpenter 倾向于创建新节点来满足分布约束(详见 Preferences 文档)。
示例:
spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: "topology.kubernetes.io/zone" whenUnsatisfiable: ScheduleAnyway labelSelector: matchLabels: dev: jjones - maxSkew: 1 topologyKey: "kubernetes.io/hostname" whenUnsatisfiable: ScheduleAnyway labelSelector: matchLabels: dev: jjones - maxSkew: 1 topologyKey: "karpenter.sh/capacity-type" whenUnsatisfiable: ScheduleAnyway labelSelector: matchLabels: dev: jjones将此配置加入 PodSpec 会产生如下效果:
- Pod 会跨可用区、主机和容量类型(
topologyKey)分布。 dev的labelSelector会把所有带dev=jjones标签的 Pod 纳入拓扑计算。建议使用 selector 匹配 Deployment 中的所有 Pod。- 每台主机上的 Pod 数量差不超过 1(
maxSkew)。例如,如果有三个节点和五个 Pod,Pod 的分布可以是 1、2、2 或 2、1、2 等;如果改为 5 个 Pod,则可以是 5、0、0 或 3、2、0、或 2、1、2 等。
Karpenter 支持的三个topologyKey取值为:
topology.kubernetes.io/zonekubernetes.io/hostnamekarpenter.sh/capacity-type
(其中karpenter.sh/capacity-type作为拓扑键的能力在 nodepools 文档 中有明确说明。)
详见 Pod Topology Spread Constraints。
注意:NodePool 不会尝试平衡或再平衡其节点的可用区分布。可用区平衡可以通过为需要多可用区持久性的 Pod 定义分区(zonal)拓扑分布约束来实现,NodePool 会在优化计算成本的同时尊重这些约束。
Pod 亲和/反亲和
通过 PodSpec 上的podAffinity与podAntiAffinity配置,你可以告知 Karpenter 调度器:希望 Pod 相对于不同的拓扑域一起调度或分开调度。
注意:Pod 上的偏好亲和性可能导致创建的节点多于预期(详见 Preferences 文档)。
示例:
spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: system operator: In values: - backend topologyKey: topology.kubernetes.io/zone podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: app: inflate topologyKey: kubernetes.io/hostname上述亲和性规则会使 Pod 只在已经运行了带system=backend标签 Pod 的可用区调度;反亲和规则则会使它避免运行在任何带有app=inflate标签 Pod 的节点上。如果该反亲和 term 位于 Deployment 的 PodSpec 上,且 Deployment 自身带有匹配的app=inflate标签,则 Deployment 的多个 Pod 不会运行在同一节点上。
详见 Inter-pod affinity and anti-affinity。
持久卷拓扑(Persistent Volume Topology)
Karpenter 会自动检测存储调度需求,并将其纳入节点启动决策。
在下面的示例中,StorageClass为us-west-2a和us-west-2b定义了分区拓扑,并使用 binding modeWaitForFirstConsumer。当 Pod 被创建时,Karpenter 会沿着Pod → PersistentVolumeClaim → StorageClass的引用链,识别出该 Pod 需要在us-west-2a和us-west-2b中存储。它随机选择us-west-2a,在该可用区置备节点,并等待 kube-scheduler 将 Pod 绑定到该节点。随后 CSI 驱动根据PersistentVolumeClaim创建PersistentVolume,并为其赋予us-west-2a的节点亲和性规则。
之后,如果该 Pod 被删除,新 Pod 创建并请求同一 PVC,Karpenter 会识别出该 PVC 已有对应的PersistentVolume,并把其所在可用区us-west-2a纳入 Pod 的调度 requirements:
apiVersion: v1 kind: Pod metadata: name: app spec: containers: ... volumes: - name: storage persistentVolumeClaim: claimName: ebs-claim --- kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: ebs provisioner: ebs.csi.aws.com volumeBindingMode: WaitForFirstConsumer allowedTopologies: - matchLabelExpressions: - key: topology.ebs.csi.aws.com/zone values: ["us-west-2a", "us-west-2b"] --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: ebs-claim spec: accessModes: - ReadWriteOnce storageClassName: ebs resources: requests: storage: 4Gi注意(AWS 专用):EBS CSI 驱动使用topology.ebs.csi.aws.com/zone而不是标准的topology.kubernetes.io/zone标签。Karpenter 感知标签别名(label aliasing),会在内存中将该标签翻译为topology.kubernetes.io/zone。为 EBS CSI 驱动配置StorageClass时,必须使用topology.ebs.csi.aws.com/zone。
注意:拓扑键topology.kubernetes.io/region不受支持——遗留的 in-tree CSI 提供商会指定该标签。请改用 out-of-tree CSI 提供商,详见 Kubernetes 官方迁移说明。
加权 NodePool:控制调度优先级
Karpenter 允许你通过.spec.weight字段为 NodePool 排序,使 Karpenter 调度器优先尝试调度某个 NodePool。权重语义与 Pod/Node 亲和性中的 weight 类似:权重越高优先级越高,不指定权重等价于权重 0。当多个 NodePool 与 Pod 匹配时,Karpenter 使用权重最高的 NodePool(见 nodepools 文档)。
Savings Plans 与 Reserved Instances
如果你购买了 Savings Plan 或 Reserved Instances,你可能希望 Karpenter 优先使用这些预留容量而非其他实例类型。
为此,你需要告诉 Karpenter 控制器:优先使用哪些实例类型,以及使用这些实例类型最多能置备多少容量。可以通过 NodePool 的.spec.limits字段限制该 NodePool 可启动的容量,再结合.spec.weight,让 Karpenter 在回退到通用实例类型之前,优先从预留实例 NodePool 拉取容量:
apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: reserved-instance spec: weight: 50 limits: cpu: 100 template: spec: requirements: - key: "node.kubernetes.io/instance-type" operator: In values: ["c4.large"] --- apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: default spec: template: spec: requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand"] - key: kubernetes.io/arch operator: In values: ["amd64"]这里limits.cpu: 100用DecimalSI值描述(Kubernetes API 会将其强转为字符串,建议避免使用整数以防 GitOps 偏差),limits到达上限后该 NodePool 将不再置备新实例,直到有节点被终止释放容量。limits检查是最终一致(eventually consistent)的,快速扩容时可能出现轻微超限。
Fallback:集群级默认配置
不指定 nodeSelector 或 affinity 的 Pod 理论上可以被分配到任何配置的节点。但在某些场景下,你可能要求这些 Pod 调度到特定容量类型或架构,而给所有工作负载 Pod 都加上 nodeSelector 或 affinity 又过于繁琐甚至不可行。此时可以通过给一个 NodePool 设置较高的.spec.weight,并将其限制到特定容量类型或架构,从而为没有节点配置限制的 Pod 设置节点默认配置:
apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: default spec: weight: 50 template: spec: requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand"] - key: kubernetes.io/arch operator: In values: ["amd64"] --- apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: arm64-specific spec: template: spec: requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand"] - key: kubernetes.io/arch operator: In values: ["arm64"] - key: node.kubernetes.io/instance-type operator: In values: ["a1.large", "a1.xlarge"]注意:基于 Karpenter 执行 Pod 批处理(batching)与装箱(bin packing)的方式,Karpenter 并不保证总是选择给定需求下的最高优先级 NodePool。例如,如果 Pod 无法用最高优先级的 NodePool 调度,Karpenter 会强制使用较低优先级的 NodePool 创建节点,使该批次中的其他 Pod 也能调度到该节点;如果已有容量可用,kube-scheduler 也会直接调度 Pod,而不是让 Karpenter 置备新节点。
高级调度技巧
基于节点资源的调度
你可能希望 Pod 请求 Kubernetes 原生不作为可调度资源提供的节点属性,例如高性能网络(High Performance Networking)或 NVMe 本地存储。此时可以利用 Karpenter 的 Well-Known 标签,在 NodePool 或工作负载层面通过 Requirements、NodeSelectors 或 Affinities 应用它们。
Pod 示例——要求任意 NVMe 磁盘:
... affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: "karpenter.k8s.aws/instance-local-nvme" operator: "Exists" ...NodePool 示例:
... requirement: - key: "karpenter.k8s.aws/instance-local-nvme" operator: "Exists" ...Pod 示例——要求至少 100GB NVMe 磁盘:
... affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: "karpenter.k8s.aws/instance-local-nvme" operator: Gt values: ["99"] ...NodePool 示例:
... requirement: - key: "karpenter.k8s.aws/instance-local-nvme" operator: Gt values: ["99"] ...注意:Karpenter 目前还无法在调度 Pod 时考虑 ephemeral-storage 请求——它只是在请求节点属性并顺带获得相应数量的资源。你可能需要调整 CPU 或内存等可调度资源以达到理想的匹配,尤其是在启用了 Consolidation 的情况下。另外,你的 NodeClass 需要支持在存在 NVMe 实例存储时自动格式化并挂载它。
Pod 示例——要求至少 50 Gbps 网络带宽:
... affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: "karpenter.k8s.aws/instance-network-bandwidth" operator: Gt values: ["49999"] ...NodePool 示例:
... requirement: - key: "karpenter.k8s.aws/instance-network-bandwidth" operator: Gt values: ["49999"] ...注意:使用Gt/Lt操作符时,请确保 values 低于目标资源的实际标签值(例如用49999匹配 50000 Mbps 的带宽)。
Exists操作符:工作负载隔离
Exists操作符可以在 NodePool 上实现跨节点的工作负载隔离:
apiVersion: karpenter.sh/v1 kind: NodePool spec: template: spec: requirements: - key: company.com/team operator: Exists ...有了该 requirement,工作负载可以为同一个键(如company.com/team)指定自定义值(如team-a、team-b等)作为必需的nodeAffinity或nodeSelector。Karpenter 会根据 Pod 的节点需求,动态地将该键值对应用到它启动的节点上。
如果每个可以匹配该 NodePool 的 Pod 组都在其nodeAffinity或nodeSelector中指定了这个键,你就可以根据其值将 Pod 隔离到不同节点上——无需为每个工作负载子集创建独立的 NodePool,即可实现更动态的隔离。例如,为以下 Deployment 提供nodeSelector,即可将各自 Pod 隔离到不同节点。
Team A Deployment:
apiVersion: v1 kind: Deployment metadata: name: team-a-deployment spec: replicas: 5 template: spec: nodeSelector: company.com/team: team-aTeam A Node:
apiVersion: v1 kind: Node metadata: labels: company.com/team: team-aTeam B Deployment:
apiVersion: apps/v1 kind: Deployment metadata: name: team-b-deployment spec: replicas: 5 template: spec: nodeSelector: company.com/team: team-bTeam B Node:
apiVersion: v1 kind: Node metadata: labels: company.com/team: team-b注意:如果工作负载匹配 NodePool 但未指定标签,Karpenter 会为节点生成一个随机标签。
On-Demand/Spot 比例拆分
利用 Karpenter 为节点分配标签的能力,并结合跨这些标签的拓扑分布,可以实现一种将工作负载按期望比例拆分到 On-Demand 与 Spot 实例上的简易方法。
做法是为 Spot 和 On-Demand 各创建一个 NodePool,它们在一个名为capacity-spread的新标签上使用互不相交(disjoint)的值。下面的示例中,Spot NodePool 提供 4 个唯一值,On-Demand NodePool 提供 1 个值。当我们跨这个新标签均匀分布时,最终会得到 4:1 的 Spot 与 On-Demand 节点比例。
警告:这与带指定比例的拓扑分布并不完全相同。我们是在构造"虚拟域"并均匀分布,而这些"虚拟域"与 Spot/On-Demand 的比例恰好与期望的 Spot/On-Demand 比例一致。例如,若使用下面的示例启动 Pod,Karpenter 会启动带capacity-spread标签值 1、2、3、4、5 的节点,kube-scheduler会跨这些节点均匀调度,从而得到期望的比例。
NodePools:
apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: spot spec: template: spec: requirements: - key: "karpenter.sh/capacity-type" operator: In values: ["spot"] - key: capacity-spread operator: In values: - "2" - "3" - "4" - "5" --- apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: on-demand spec: template: spec: requirements: - key: "karpenter.sh/capacity-type" operator: In values: ["on-demand"] - key: capacity-spread operator: In values: - "1"工作负载拓扑分布约束:
topologySpreadConstraints: - maxSkew: 1 topologyKey: capacity-spread whenUnsatisfiable: DoNotSchedule labelSelector: ...结合 NodePool 配置的最佳实践
调度约束最终要与 NodePool 协同生效。综合 nodepools 文档 与 examples/v1 示例目录 中的真实配置(如 general-purpose.yaml、spot.yaml、multiple-arch.yaml),落地时建议注意:
- requirements 与 Pod 约束取交集:节点选择同时使用 NodePool 与 Pod 的 requirements,二者无交集则不启动节点;未对某 well-known 标签定义 requirement 时,云提供商可用的任意值都可被选择。
- 实例类型尽量用列表而非单值:
node.kubernetes.io/instance-type、karpenter.k8s.aws/instance-family、instance-category、instance-generation等一般应作为列表,甚至不定义,以最大化高效装箱的选择空间。 - minValues 保证灵活性下限:NodePool 的 requirements 支持
minValues(ALPHA),要求调度器对某个键至少考虑 N 个唯一值,否则该 NodePool 的调度循环会失败并回退到其他 NodePool。 - 限制上限用 limits:
spec.limits可约束 NodePool 消耗的总资源(cpu/memory/GPU 等),配合weight实现"优先用预留容量,超出即回退"的策略。 - 推荐最小约束集:官方建议对通用负载至少指定
kubernetes.io/arch、kubernetes.io/os、karpenter.sh/capacity-type、karpenter.k8s.aws/instance-category、karpenter.k8s.aws/instance-generation等少量 requirements,避免出现异常行为或极端实例类型。
小结
Karpenter 的调度能力覆盖了从最基础的资源请求到复杂的多 NodePool 加权优先级:资源请求决定实例选型的输入,nodeSelector/节点亲和性/拓扑分布/Pod 亲和反亲和/持久卷拓扑等 Kubernetes 标准约束全部受支持,偏好会按权重逐级放宽,污点与容忍配合 NodePool 实现硬件隔离,Exists操作符与capacity-spread标签则提供了工作负载动态隔离与 Spot/On-Demand 比例拆分的轻量方案。将 Pod 约束与 NodePool 的requirements、limits、weight组合使用,即可在保证"Pod 约束必须落在 NodePool 约束范围内"的前提下,构建灵活、可控且成本优化的集群调度策略。本文对应的完整调度概念文档位于 website/content/en/v1.0/concepts/scheduling.md,配套的 NodePool 详解见 website/content/en/v1.0/concepts/nodepools.md。
【免费下载链接】karpenter-provider-awsKarpenter is a Kubernetes Node Autoscaler built for flexibility, performance, and simplicity.项目地址: https://gitcode.com/GitHub_Trending/ka/karpenter-provider-aws
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考