Skip to main content
When you first install Karpenter, you set up a default NodePool. The NodePool sets constraints on the nodes that can be created by Karpenter and the pods that can run on those nodes. A NodePool can be configured to:
  • Define taints to limit which pods run on Karpenter-managed nodes
  • Define startup taints that are temporary and removed by another system (e.g., a DaemonSet)
  • Limit node creation to certain zones, instance types, and architectures
  • Set defaults for node expiration
Objects for configuring kubelet features have been moved from the NodePool spec to the EC2NodeClass spec so other Karpenter providers are not required to support those fields.
Things to know about NodePools:
  • Karpenter won’t do anything if there is not at least one NodePool configured.
  • Each NodePool is looped through when finding a placement for a pod.
  • If Karpenter encounters a taint in the NodePool that is not tolerated by a pod, it won’t use that NodePool to provision the pod.
  • If Karpenter encounters a startup taint, the taint is applied to the node, but pods don’t need to tolerate it.
  • It is recommended to create mutually exclusive NodePools. If multiple NodePools match, Karpenter uses the one with the highest weight.

Full NodePool example

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  # Template describing NodeClaim resources Karpenter will provision
  template:
    metadata:
      labels:
        billing-team: my-team
      annotations:
        example.com/owner: "my-team"
    spec:
      # Reference to the cloud provider NodeClass
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default

      # Taints added to provisioned nodes
      taints:
        - key: example.com/special-taint
          effect: NoSchedule

      # Startup taints: applied to nodes but pods don't need to tolerate them
      startupTaints:
        - key: example.com/another-taint
          effect: NoSchedule

      # Maximum node lifetime before expiration
      expireAfter: 720h

      # Maximum draining duration before forceful cleanup
      terminationGracePeriod: 48h

      # Requirements constraining provisioned nodes
      requirements:
        - key: "karpenter.k8s.aws/instance-category"
          operator: In
          values: ["c", "m", "r"]
          minValues: 2
        - key: "karpenter.k8s.aws/instance-family"
          operator: In
          values: ["m5","m5d","c5","c5d","c4","r4"]
          minValues: 5
        - key: "karpenter.k8s.aws/instance-cpu"
          operator: In
          values: ["4", "8", "16", "32"]
        - key: "karpenter.k8s.aws/instance-hypervisor"
          operator: In
          values: ["nitro"]
        - key: "karpenter.k8s.aws/instance-generation"
          operator: Gte
          values: ["3"]
        - key: "topology.kubernetes.io/zone"
          operator: In
          values: ["us-west-2a", "us-west-2b"]
        - key: "kubernetes.io/arch"
          operator: In
          values: ["arm64", "amd64"]
        - key: "karpenter.sh/capacity-type"
          operator: In
          values: ["spot", "on-demand", "reserved"]

  # Disruption controls
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 1m
    budgets:
      - nodes: 10%
      # Disable disruption on weekdays during business hours
      - schedule: "0 9 * * mon-fri"
        duration: 8h
        nodes: "0"

  # Resource limits for the pool
  limits:
    cpu: "1000"
    memory: 1000Gi

  # Priority when multiple NodePools match a pod
  weight: 10
status:
  nodes: 5
  resources:
    cpu: "20"
    memory: "8192Mi"
    ephemeral-storage: "100Gi"
  conditions:
    - type: Ready
      status: "True"

Field reference

spec.template.metadata.labels

Arbitrary key/value pairs applied to all nodes provisioned by this NodePool.

spec.template.metadata.annotations

Arbitrary key/value pairs applied as annotations to all nodes.
Points to the cloud provider NodeClass resource. For AWS, this references an EC2NodeClass.
nodeClassRef:
  group: karpenter.k8s.aws
  kind: EC2NodeClass
  name: default
See EC2NodeClasses for details.

spec.template.spec.taints

Taints added to provisioned nodes. Pods must tolerate these taints to be scheduled. See Taints and Tolerations.

spec.template.spec.startupTaints

Taints applied to nodes on launch to indicate a condition (like network setup) that must be met before other pods schedule. Some other system (e.g., a DaemonSet) must remove these taints. Pods don’t need to tolerate startup taints to be provisioned by this NodePool.
Failure to provide accurate startupTaints can cause Karpenter to continually provision new nodes. When a new node joins and an unknown startup taint is added, Karpenter treats the pending pod as unschedulable and tries to provision yet another node.
The maximum time a node can live on the cluster before Karpenter deletes it. Nodes begin draining when their expiration is reached.Set to Never to disable expiration. Default is 720h (30 days).
expireAfter: 720h
Changes to expireAfter on the NodePool cause drift on existing NodeClaims — they will be replaced with the updated value.
The maximum time Karpenter waits for a node to drain before forcefully cleaning it up. Pods with blocking PDBs or the karpenter.sh/do-not-disrupt annotation are respected until this period elapses, at which point they are forcibly deleted.
terminationGracePeriod: 48h
Changes to this field cause drift on existing NodeClaims.
Requirements constrain the parameters of provisioned nodes. These are combined with pod scheduling constraints such as topologySpreadConstraints, nodeAffinity, podAffinity, and nodeSelector.Supported operators: In, NotIn, Exists, DoesNotExist, Gt, Lt, Gte, Lte.
requirements:
  - key: kubernetes.io/arch
    operator: In
    values: ["amd64"]
  - key: kubernetes.io/os
    operator: In
    values: ["linux"]
  - key: karpenter.sh/capacity-type
    operator: In
    values: ["on-demand"]
  - key: karpenter.k8s.aws/instance-category
    operator: In
    values: ["c", "m", "r"]
  - key: karpenter.k8s.aws/instance-generation
    operator: Gte
    values: ["3"]
There is a limit of 100 on the total number of requirements on both the NodePool and the NodeClaim. spec.template.metadata.labels are also propagated as requirements when a NodeClaim is created, so you can’t have more than 100 requirements and labels combined.

Well-known labels

The following labels can be used in spec.template.spec.requirements or in pod scheduling constraints.
LabelDescription
node.kubernetes.io/instance-typeSpecific instance type (e.g., m5.large)
karpenter.k8s.aws/instance-familyInstance family (e.g., m5)
karpenter.k8s.aws/instance-categoryInstance category (e.g., c, m, r)
karpenter.k8s.aws/instance-generationGeneration number within a category
karpenter.k8s.aws/instance-cpuNumber of vCPUs
karpenter.k8s.aws/instance-memoryMemory in MiB
Generally, instance types should be a list, not a single value. Leaving these requirements undefined is recommended as it maximizes choices for efficient pod placement.
  • Key: topology.kubernetes.io/zone
  • Example value: us-east-1c
List available zones with:
aws ec2 describe-availability-zones --region <region-name>
The zone us-east-1a for your AWS account may not correspond to the same physical location as us-east-1a in another account. Use Zone IDs for globally consistent references.
  • Key: kubernetes.io/arch
  • Values: amd64, arm64
  • Key: kubernetes.io/os
  • Values: linux, windows
  • Key: karpenter.sh/capacity-type
  • Values: spot, on-demand, reserved
When multiple capacity types are compatible, Karpenter prioritizes reserved first, then spot, then on-demand. If a capacity type is unavailable, Karpenter falls back to the next priority within milliseconds.
The reserved capacity type refers to capacity reservations (on-demand capacity reservations and capacity blocks), not reserved instances (RIs).
karpenter.sh/capacity-type can also be used as a topology key for topology spread constraints.
  • Key: karpenter.k8s.aws/instance-tenancy
  • Values: default, dedicated
If a NodeClaim requires dedicated tenancy, it launches on a Dedicated Instance.

minValues

The minValues field in requirements tells the scheduler to consider at least that number of unique values for a given key when scheduling pods. This ensures the scheduler maintains flexibility to avoid over-optimization onto a single instance type. If Karpenter cannot meet the specified minimum, behavior depends on the --min-values-policy flag:
  • Strict — fail the scheduling loop, fall back to another NodePool or fail scheduling entirely
  • BestEffort — relax minValues until they can be met
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m", "r"]
          minValues: 2
        - key: karpenter.k8s.aws/instance-family
          operator: Exists
          minValues: 5
        - key: node.kubernetes.io/instance-type
          operator: Exists
          minValues: 10
        - key: karpenter.k8s.aws/instance-generation
          operator: Gte
          values: ["3"]
When minValues is defined multiple times for the same key, Karpenter uses the maximum of all defined values.

spec.disruption

The disruption block controls how Karpenter removes or replaces nodes.
spec.disruption.consolidationPolicy
string
Determines which nodes are eligible for consolidation.
  • WhenEmptyOrUnderutilized — consider all nodes; remove or replace when empty or underutilized
  • WhenEmpty — only consider nodes with no workload pods
spec.disruption.consolidateAfter
duration
How long Karpenter waits after a pod is added or removed before considering the node for consolidation. Set to Never to disable consolidation.Default: 0s
spec.disruption.budgets
array
Rate limits on how many nodes Karpenter can disrupt at once. Karpenter uses the most restrictive budget. See Disruption budgets for details.
budgets:
  - nodes: 10%
  # Block all disruptions on weekday business hours
  - schedule: "0 9 * * mon-fri"
    duration: 8h
    nodes: "0"

spec.replicas

Optional. Enables static capacity mode where the NodePool maintains a fixed number of nodes regardless of pod demand.
spec:
  replicas: 10
Static NodePool constraints:
  • Cannot be removed once set (a NodePool cannot switch between static and dynamic modes)
  • Only limits.nodes is supported in the limits section
  • weight cannot be set
  • Nodes are not considered for consolidation
  • Scaling bypasses node disruption budgets (but respects PodDisruptionBudgets)
Scale nodes with:
kubectl scale nodepool <name> --replicas=<count>

spec.limits

Constrains the total resources that this NodePool can consume. If unspecified, no limit is enforced (cloud provider quotas apply).
limits:
  cpu: "1000"
  memory: 1000Gi
  nvidia.com/gpu: 2
For static NodePools, only limits.nodes is supported.
Karpenter provisioning is highly parallel so limit checking is eventually consistent. This can result in brief overruns during rapid scale-out events.
Check current consumption:
kubectl get nodepool -o=jsonpath='{.items[0].status}'

spec.weight

Sets the priority for this NodePool relative to others. Higher weight = higher priority. When no weight is specified, it defaults to 0. See Weighted NodePools for examples.
weight cannot be set on static NodePools (those with spec.replicas configured).

status

FieldDescription
status.nodesCurrent number of nodes managed by this NodePool
status.resourcesCurrent consumption of cpu, memory, and ephemeral-storage
status.conditionsReadiness conditions

Status conditions

ConditionDescription
NodeClassReadyThe referenced NodeClass is ready
ValidationSucceededCRD validation passed
ReadyAll conditions are true; NodePool is considered for scheduling
If a NodePool is not ready, it will not be considered for scheduling.

Examples

A NodePool that only provisions GPU nodes and requires pods to explicitly tolerate the GPU taint:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: gpu
spec:
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
  template:
    spec:
      requirements:
      - key: node.kubernetes.io/instance-type
        operator: In
        values: ["p3.8xlarge", "p3.16xlarge"]
      taints:
      - key: nvidia.com/gpu
        value: "true"
        effect: NoSchedule
Pods must tolerate nvidia.com/gpu to schedule on these nodes.
Maintain a fixed pool of 10 nodes in a single availability zone:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: static-capacity
spec:
  replicas: 10
  template:
    spec:
      requirements:
      - key: node.kubernetes.io/instance-type
        operator: In
        values: ["m5.large", "m5.xlarge"]
      - key: topology.kubernetes.io/zone
        operator: In
        values: ["us-west-2a"]
  limits:
    nodes: 15
  disruption:
    budgets:
    - nodes: 20%
Per the Cilium docs, place a taint on nodes to allow Cilium to configure networking before other pods start:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: cilium-startup
spec:
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
  template:
    spec:
      startupTaints:
      - key: node.cilium.io/agent-not-ready
        value: "true"
        effect: NoExecute

Build docs developers (and LLMs) love