Learn Karpenter - NodePool & Scheduling
Episode 4 of 23

Learn Karpenter - NodePool & Scheduling

The NodePool is the heart of Karpenter scheduling. This episode breaks down the structure of spec.template, requirements, disruption, and weight, then how Karpenter matches pods with pools and labels the nodes it creates.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 3 you successfully made Karpenter provision your first node: a NodePool named default pointing to an EC2NodeClass, the nginx workload scheduled right away, and NodeClaims appearing. Now let's dissect the component that most determines Karpenter's behavior: NodePool.

This episode breaks down the complete NodePool structure — spec.template, requirements, disruption, and weight — then explains how Karpenter matches pods with the right pool, and what labels/annotations it attaches to the nodes it creates.

NodePool Structure

The NodePool consists of two major parts: spec.template, which defines the node, and spec.disruption, which governs when a node can be replaced. There's also spec.weight for preference between pools. Let's look at a complete example:

Complete NodePool
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: general
spec:
  weight: 10
  template:
    metadata:
      labels:
        team: platform
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64"]
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot", "on-demand"]
        - key: node.kubernetes.io/instance-type
          operator: In
          values: ["m5.large", "m6i.large", "m6a.large"]
      taints:
        - key: workloads
          value: "true"
          effect: PreferNoSchedule
  disruption:
    consolidationPolicy: WhenUnderutilized
    expireAfter: 720h

spec.template: Node Specification

spec.template.metadata holds the labels and annotations to be attached to new nodes, for example team: platform for cost grouping. spec.template.spec contains three important things:

  • nodeClassRef — pointer to the EC2NodeClass that provides the infrastructure.
  • requirements — filters for the instance types and capacity allowed to be selected.
  • taints — taints attached to the node, complete with the tolerations pods must have to be allowed in.

requirements: Instance Filter

A requirement takes the form of key, operator, and values. Available operators: In, NotIn, Gt, Lt, and Exists. Commonly used keys:

KeyExample valuesMeaning
node.kubernetes.io/instance-typem5.large, m6i.largeFilter instance type
karpenter.sh/capacity-typeon-demand, spotCapacity type
topology.kubernetes.io/zoneap-southeast-1aAvailability zone
kubernetes.io/archamd64, arm64CPU architecture
karpenter.sh/instance-categorygeneral, computeFamily category
karpenter.sh/instance-familym5, c6iInstance family

The combination of requirements determines Karpenter's "search space" for instances. The more specific, the more limited the options — the broader, the greater the chance of getting low prices via binpacking.

disruption: Managing the Node Lifecycle

spec.disruption controls when a node may be terminated:

  • consolidationPolicy: WhenUnderutilized — underutilized nodes are consolidated, pods are moved to more optimal nodes.
  • expireAfter — a node is considered expired and replaced after a certain period; a value of 720h means 30 days.
  • budgets — limits how many nodes may be disrupted at the same time.

weight: Preference Between Pools

When several NodePools are equally suitable for a pod, spec.weight decides the winner. The higher the weight value (scale 0-100), the more that pool is preferred. If weights are equal, Karpenter picks the pool that comes first in order.

How Karpenter Matches Pods with NodePools

Scheduling in Karpenter essentially inverts the standard scheduler logic: instead of finding a node for a pod, Karpenter looks for an instance combination that can satisfy all queued pods. The process involves:

Resource Requests

The total CPU, memory, GPU, and storage requests from all pods are summed to determine the minimum instance size. Pods with complementary requests can be bin-packed onto one node.

Labels and Node Selectors

Labels requested by a pod via nodeSelector or nodeAffinity are matched against the labels from the NodePool's spec.template. Example: a pod with nodeSelector disktype: ssd only matches a pool that defines the disktype: ssd label.

Taints and Tolerations

Nodes from a NodePool can be tainted. Pods without a matching toleration will be rejected by that pool. This is the most common way to separate workloads: a Spot-only pool with a taint, where only pods prepared for interruption are allowed in.

Note

Karpenter also honors other Kubernetes constraints like node affinity, pod affinity/anti-affinity, and topology spread constraints. All of these further narrow the instance options — so don't be surprised if a combination of constraints makes Karpenter pick a larger instance.

Labels and Annotations on Created Nodes

When a node is born from a pool, Karpenter attaches labels and annotations that become an observability window:

Label/AnnotationTypeContent
karpenter.sh/nodepoollabelName of the NodePool the node came from
karpenter.sh/capacity-typelabelspot or on-demand
node.kubernetes.io/instance-typelabelSelected instance type
topology.kubernetes.io/zonelabelAZ where the instance was launched
karpenter.sh/registeredannotationMarks the node as registered
karpenter.sh/do-not-disruptannotationProtects a pod from consolidation
Inspect nodes created by Karpenter
kubectl get nodes -l karpenter.sh/nodepool=general -o wide
kubectl describe node <node-name>
kubectl get nodeclaims

The karpenter.sh/nodepool and karpenter.sh/capacity-type labels are the ones commonly used for cost tagging and node grouping. Adding the karpenter.sh/do-not-disrupt annotation to an important pod makes Karpenter skip it during consolidation.

Creating Multiple NodePools

A common practice in production: several NodePools for different needs — one for general workloads, one Spot-only and cheaper, another for GPU. Example split:

NodePoolcapacity-typeinstance-familyweight
generalon-demandm5, m6i, m6a10
spotspotc5, c6i20
gpuon-demandg5, p4d0

With a higher weight, the spot pool is chosen first as long as the pods tolerate the Spot taint. This is the most frequently used cost-saving strategy.

Closing

Episode 4 dissected the NodePool — the core of Karpenter's scheduling policy. You understood the spec.template structure with requirements and taints, disruption policies like consolidationPolicy and expireAfter, weight for preference between pools, and the mechanism Karpenter uses to match pods — resource requests, labels, and taints — with the right pool. The karpenter.sh labels and annotations become an invaluable observability tool.

Key takeaways:

  • spec.template — node definition: labels, nodeClassRef, requirements, and taints.
  • Requirements — filter instance type, capacity, zone, and architecture with the In, NotIn, Gt, Lt, Exists operators.
  • Disruption — consolidationPolicy and expireAfter determine when a node is replaced.
  • Weight — priority between pools; the higher, the more preferred.
  • karpenter.sh labels — karpenter.sh/nodepool and karpenter.sh/capacity-type are must-knows for operations.

In episode 5 we move to the infrastructure side: EC2NodeClass. We'll cover AMI selection, security groups, subnets, instance profiles, userData, and how to combine multiple NodeClasses for different workloads. See you in the next episode!

Learn Karpenter - NodePool & Scheduling | Learn Karpenter