Skip to main content
EC2NodeClasses enable configuration of AWS-specific settings for nodes provisioned by Karpenter. Each NodePool must reference an EC2NodeClass using spec.template.spec.nodeClassRef. Multiple NodePools can reference the same EC2NodeClass.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default

Full EC2NodeClass example

apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: default
spec:
  # AMI family — controls UserData generation and default block device mappings
  amiFamily: AL2

  # Required: selects AMIs for Karpenter to use
  amiSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${CLUSTER_NAME}"
        environment: test
    - name: my-ami
    - id: ami-123
    - ssmParameter: my-custom-parameter

  # Required: selects subnets for node network interfaces
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${CLUSTER_NAME}"
        environment: test
    - id: subnet-09fa4a0a8f233a921

  # Required: selects security groups attached to instances
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${CLUSTER_NAME}"
        environment: test
    - name: my-security-group
    - id: sg-063d7acfb4b06c82c

  # IAM role for node identity (specify role OR instanceProfile)
  role: "KarpenterNodeRole-${CLUSTER_NAME}"

  # Kubelet configuration
  kubelet:
    podsPerCore: 2
    maxPods: 20
    systemReserved:
      cpu: 100m
      memory: 100Mi
      ephemeral-storage: 1Gi
    kubeReserved:
      cpu: 200m
      memory: 100Mi
      ephemeral-storage: 3Gi
    evictionHard:
      memory.available: 5%
      nodefs.available: 10%
      nodefs.inodesFree: 10%
    evictionSoft:
      memory.available: 500Mi
      nodefs.available: 15%
      nodefs.inodesFree: 15%
    evictionSoftGracePeriod:
      memory.available: 1m
      nodefs.available: 1m30s
      nodefs.inodesFree: 2m
    evictionMaxPodGracePeriod: 60
    imageGCHighThresholdPercent: 85
    imageGCLowThresholdPercent: 80
    cpuCFSQuota: true
    clusterDNS: ["10.0.1.100"]

  # Propagates tags to all EC2 resources
  tags:
    team: team-a
    app: team-a-app

  # Configures Instance Metadata Service (IMDS)
  metadataOptions:
    httpEndpoint: enabled
    httpProtocolIPv6: disabled
    httpPutResponseHopLimit: 1
    httpTokens: required

  # EBS volume configuration
  blockDeviceMappings:
    - deviceName: /dev/xvda
      ebs:
        volumeSize: 100Gi
        volumeType: gp3
        iops: 10000
        encrypted: true
        kmsKeyID: "1234abcd-12ab-34cd-56ef-1234567890ab"
        deleteOnTermination: true
        throughput: 125
        snapshotID: snap-0123456789

  # Use instance-store volumes for ephemeral storage
  instanceStorePolicy: RAID0

  # Custom bootstrapping UserData
  userData: |
    echo "Hello world"

  # Enable 1-minute CloudWatch metrics
  detailedMonitoring: true

  # Whether to assign a public IP address
  associatePublicIPAddress: true

spec.amiSelectorTerms

AMI selector terms are required and configure which AMIs Karpenter uses. Terms are ORed together; conditions within a single term are ANDed.
amiSelectorTerms:
  - tags:
      karpenter.sh/discovery: "${CLUSTER_NAME}"
      environment: test
  - name: my-ami
  - id: ami-123
  - ssmParameter: my-custom-parameter

alias

Use an alias to automatically select EKS-optimized AMIs. An alias is formatted as family@version. Supported families:
  • al2
  • al2023
  • bottlerocket
  • windows2019
  • windows2022
  • windows2025
The version can be latest or pinned to a specific release:
# Pin AL2023 to a specific release date
amiSelectorTerms:
  - alias: al2023@v20240703
# Pin Bottlerocket to a semantic version
amiSelectorTerms:
  - alias: [email protected]
Using latest is not recommended for production. A new AMI release will cause Karpenter to drift all out-of-date nodes. Evaluate new AMIs in a lower environment before rolling them into production.
An alias term is mutually exclusive — it cannot be specified alongside other selector terms. When an alias is used, amiFamily is inferred automatically.
Select by tag:
amiSelectorTerms:
  - tags:
      karpenter.sh/discovery/MyClusterName: '*'
Select by name with wildcard:
amiSelectorTerms:
  - name: "*EKS*"
Select by ID:
amiSelectorTerms:
  - id: "ami-123"
  - id: "ami-456"
Select by name and owner:
amiSelectorTerms:
  - name: my-ami
    owner: self
  - name: my-ami
    owner: "0123456789"
Select using a custom SSM parameter:
amiSelectorTerms:
  - ssmParameter: "my-custom-parameter"
When using a custom SSM parameter, expand the ssm:GetParameter permissions on the Karpenter IAM role to include your parameter. The default policy only permits access to AWS public parameters.

spec.amiFamily

Controls UserData generation and default block device mappings. Only required when not using an alias in amiSelectorTerms (the alias infers the family automatically).
Support for the Ubuntu AMIFamily was dropped in Karpenter v1.0.0. To continue using Ubuntu AMIs, use amiSelectorTerms to select Ubuntu AMIs and either manage UserData yourself with the Custom family, or use the AL2 family (which has an identical UserData format, though long-term compatibility is not guaranteed).
amiFamily: AL2023
Generated UserData:
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="//"

--//
Content-Type: application/node.eks.aws

# Karpenter Generated NodeConfig
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  cluster:
    name: test-cluster
    apiServerEndpoint: https://example.com
    certificateAuthority: ca-bundle
    cidr: 10.100.0.0/16
  kubelet:
    config:
      maxPods: 110
    flags:
      - --node-labels=karpenter.sh/capacity-type=on-demand,karpenter.sh/nodepool=test

--//--
AL2 support is dropped at Kubernetes 1.33. Kubernetes version 1.32 is the last version for which Amazon EKS releases AL2 AMIs.

spec.subnetSelectorTerms

Selects which subnets Karpenter uses when launching instances. When launching nodes, Karpenter automatically chooses the subnet that matches the desired zone. If multiple subnets exist for a zone, the one with the most available IP addresses is used. Terms are ORed together; conditions within a single term are ANDed.
subnetSelectorTerms:
  - tags:
      karpenter.sh/discovery: "${CLUSTER_NAME}"
      environment: test
  - id: subnet-09fa4a0a8f233a921
Subnets can be specified by any tag, including Name. Wildcard (*) matching is supported.
Select by tag key:
subnetSelectorTerms:
  - tags:
      karpenter.sh/discovery/MyClusterName: '*'
Select by name and tag:
subnetSelectorTerms:
  - tags:
      Name: my-subnet
      MyTag: ''
Select using multiple terms (OR):
subnetSelectorTerms:
  - tags:
      Name: "my-subnet-1"
  - tags:
      Name: "my-subnet-2"
Select using wildcard:
subnetSelectorTerms:
  - tags:
      Name: "*Public*"
Select by ID:
subnetSelectorTerms:
  - id: "subnet-09fa4a0a8f233a921"
  - id: "subnet-0471ca205b8a129ae"

spec.securityGroupSelectorTerms

Selects which security groups are attached to launched instances. Terms are ORed; conditions within a term are ANDed.
securityGroupSelectorTerms:
  - tags:
      karpenter.sh/discovery: "${CLUSTER_NAME}"
      environment: test
  - name: my-security-group
  - id: sg-063d7acfb4b06c82c
When launching nodes, Karpenter attaches all security groups that match the selector. Avoid using the kubernetes.io/cluster/$CLUSTER_NAME tag for discovery — it may cause failures with the AWS Load Balancer controller, which only supports a single security group with that tag key. Use karpenter.sh/discovery: $CLUSTER_NAME instead.

spec.role and spec.instanceProfile

You must specify exactly one of role or instanceProfile.
spec.role
string
The IAM role name for node identity. Karpenter will manage the instance profile on your behalf.
role: "KarpenterNodeRole-$CLUSTER_NAME"
spec.instanceProfile
string
A pre-provisioned IAM instance profile. Karpenter will not manage this profile — you must create it and assign a role to it yourself.
instanceProfile: "KarpenterNodeInstanceProfile-${CLUSTER_NAME}"
For private clusters without public internet access, spec.instanceProfile is required. IAM doesn’t support private VPC endpoints, so spec.role cannot be used in that context.

spec.kubelet

Optional kubelet configuration applied to all nodes using this EC2NodeClass.
kubelet:
  podsPerCore: 2
  maxPods: 20
  systemReserved:
    cpu: 100m
    memory: 100Mi
    ephemeral-storage: 1Gi
  kubeReserved:
    cpu: 200m
    memory: 100Mi
    ephemeral-storage: 3Gi
  evictionHard:
    memory.available: 5%
    nodefs.available: 10%
    nodefs.inodesFree: 10%
  evictionSoft:
    memory.available: 500Mi
    nodefs.available: 15%
    nodefs.inodesFree: 15%
  evictionSoftGracePeriod:
    memory.available: 1m
    nodefs.available: 1m30s
    nodefs.inodesFree: 2m
  evictionMaxPodGracePeriod: 60
  imageGCHighThresholdPercent: 85
  imageGCLowThresholdPercent: 80
  cpuCFSQuota: true
  clusterDNS: ["10.0.1.100"]
If you need to configure a kubelet field not exposed in spec.kubelet, set it via spec.userData. For example, to configure maxPods and registryPullQPS on AL2023:
spec:
  amiSelectorTerms:
    - alias: al2023@v20240807
  kubelet:
    maxPods: 42
  userData: |
    apiVersion: node.eks.aws/v1alpha1
    kind: NodeConfig
    spec:
      kubelet:
        config:
          registryPullQPS: 10
podsPerCore calculates max pod density dynamically by multiplying the value by the number of vCPUs. It’s also passed to --pods-per-core on kubelet startup.maxPods overrides the default pod density limit. Useful for small instances needing higher density or large instances needing lower density. Consider enabling prefix assignment mode for small instance types.When both are set, the minimum of podsPerCore-derived density and maxPods is used.
Karpenter automatically configures system and kube reserved resources. Override the defaults with:
kubelet:
  systemReserved:
    cpu: 100m
    memory: 100Mi
    ephemeral-storage: 1Gi
  kubeReserved:
    cpu: 200m
    memory: 100Mi
    ephemeral-storage: 3Gi
Configure hard and soft eviction thresholds:
kubelet:
  evictionHard:
    memory.available: 500Mi
    nodefs.available: 10%
    nodefs.inodesFree: 10%
    imagefs.available: 5%
    pid.available: 7%
  evictionSoft:
    memory.available: 1Gi
    nodefs.available: 15%
    nodefs.inodesFree: 15%
    imagefs.available: 10%
    pid.available: 10%
SignalDescription
memory.availablenode.status.capacity[memory] - node.stats.memory.workingSet
nodefs.availablenode.stats.fs.available
nodefs.inodesFreenode.stats.fs.inodesFree
imagefs.availablenode.stats.runtime.imagefs.available
imagefs.inodesFreenode.stats.runtime.imagefs.inodesFree
pid.availablenode.stats.rlimit.maxpid - node.stats.rlimit.curproc

spec.blockDeviceMappings

Controls the EBS volumes attached to provisioned nodes. Karpenter uses AMIFamily-specific defaults when not specified.
blockDeviceMappings:
  - deviceName: /dev/xvda
    ebs:
      volumeSize: 100Gi
      volumeType: gp3
      iops: 10000
      encrypted: true
      kmsKeyID: "1234abcd-12ab-34cd-56ef-1234567890ab"
      deleteOnTermination: true
      throughput: 125
      snapshotID: snap-0123456789
AL2 / AL2023:
blockDeviceMappings:
  - deviceName: /dev/xvda
    ebs:
      volumeSize: 20Gi
      volumeType: gp3
      encrypted: true
Bottlerocket:
blockDeviceMappings:
  # Root/control volume
  - deviceName: /dev/xvda
    ebs:
      volumeSize: 4Gi
      volumeType: gp3
      encrypted: true
  # Data volume: containers, images, logs
  - deviceName: /dev/xvdb
    ebs:
      volumeSize: 20Gi
      volumeType: gp3
      encrypted: true
Windows2019 / Windows2022 / Windows2025:
blockDeviceMappings:
  - deviceName: /dev/sda1
    ebs:
      volumeSize: 50Gi
      volumeType: gp3
      encrypted: true
Custom: No default block device mappings.

spec.metadataOptions

Controls exposure of the Instance Metadata Service (IMDS) on launched instances. Defaults:
metadataOptions:
  httpEndpoint: enabled
  httpProtocolIPv6: disabled
  httpPutResponseHopLimit: 1  # Disables IMDS access from containers not on the host network
  httpTokens: required

spec.tags

Propagates additional tags to all EC2 resources Karpenter creates (instances, EBS volumes, launch templates). Default tags added by Karpenter:
Name: <node-name>
karpenter.sh/nodeclaim: <nodeclaim-name>
karpenter.sh/nodepool: <nodepool-name>
karpenter.k8s.aws/ec2nodeclass: <ec2nodeclass-name>
kubernetes.io/cluster/<cluster-name>: owned
eks:eks-cluster-name: <cluster-name>
Additional tags:
tags:
  InternalAccountingTag: 1234
  dev.corp.net/app: Calculator
  dev.corp.net/team: MyTeam
You can override the default Name tag, but overriding restricted tag domains (karpenter.sh, karpenter.k8s.aws, kubernetes.io/cluster) is not allowed.

spec.userData

Custom scripts or configuration to pass to nodes at startup. Karpenter merges your UserData with its generated defaults based on AMIFamily.
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: bottlerocket-example
spec:
  amiFamily: Bottlerocket
  userData: |
    [settings.kubernetes]
    "kube-api-qps" = 30
    "shutdown-grace-period" = "30s"
    [settings.kubernetes.eviction-hard]
    "memory.available" = "20%"
For AL2023 and Bottlerocket, values set by Karpenter’s generated configuration take precedence over values in spec.userData. This includes cluster name, endpoint, certificate authority, taints, labels, and kubelet configuration.

spec.instanceStorePolicy

Controls how instance-store volumes are handled. By default, they are ignored. Set to RAID0 to use instance-store volumes for faster node ephemeral storage:
instanceStorePolicy: RAID0
This sets the allocatable ephemeral storage to the total size of all instance-store volumes. Useful for workloads requiring low-latency NVMe SSD storage.
Since kubelet and containerd use the instance-store filesystem when RAID0 is configured, consider using a smaller root EBS volume.

spec.detailedMonitoring

Enables EC2 detailed monitoring, which provides 1-minute CloudWatch metrics.
detailedMonitoring: true

spec.ipPrefixCount

Sets the number of IPv4 (or IPv6, for IPv6 clusters) prefixes assigned to the network interface of each launched instance. When configured, Karpenter can schedule more pods per node by using IP prefix delegation. See the EC2 Launch Template Network Interface Spec for details.
ipPrefixCount: 1
When using spec.associatePublicIPAddress: true with EFA workloads that request multiple EFA resources, the instance will fail to launch — EC2 requires a single ENI when setting associatePublicIPAddress. Segregate EFA workloads into a separate NodePool/EC2NodeClass pair.

spec.capacityReservationSelectorTerms

This field is in Beta.
Selects on-demand capacity reservations (ODCRs) to make available to NodePools using this EC2NodeClass. Karpenter prioritizes this capacity before falling back to on-demand and spot.
capacityReservationSelectorTerms:
  - tags:
      karpenter.sh/discovery: ${CLUSTER_NAME}
  - id: cr-123
  - instanceMatchCriteria: open

status

FieldDescription
status.subnetsResolved subnets (sorted by available IP count, descending)
status.securityGroupsResolved security groups
status.amisResolved AMIs with architecture requirements
status.capacityReservationsResolved capacity reservations
status.instanceProfileInstance profile generated from spec.role
status.conditionsReadiness conditions

Status conditions

ConditionDescription
SubnetsReadySubnets discovered successfully
SecurityGroupsReadySecurity groups discovered successfully
InstanceProfileReadyInstance profile discovered successfully
AMIsReadyAMIs discovered successfully
ReadyAll conditions true; NodePools referencing this class are considered for scheduling
If a NodeClass is not ready, NodePools that reference it will not be considered for scheduling.

Build docs developers (and LLMs) love