12 min readJan 8, 2026
–
Kubernetes has become the standard platform for orchestrating containerized applications. It offers powerful abstractions for deploying, scaling and managing workloads. However, the compute layer beneath it often remains difficult to optimize. Managing the worker nodes that provide actual capacity can be challenging, when clusters experience rapid or unpredictable changes in demand.
Traditionally, teams relied on the Kubernetes Cluster Autoscaler to add or remove capacity. This tool provided basic automation, but it also introduced limitations that will be explored in the next section.
Karpenter was created to provide a more flexible and efficient way to manage cluster capacity. It is an open source and high performance provisioning engine that …
12 min readJan 8, 2026
–
Kubernetes has become the standard platform for orchestrating containerized applications. It offers powerful abstractions for deploying, scaling and managing workloads. However, the compute layer beneath it often remains difficult to optimize. Managing the worker nodes that provide actual capacity can be challenging, when clusters experience rapid or unpredictable changes in demand.
Traditionally, teams relied on the Kubernetes Cluster Autoscaler to add or remove capacity. This tool provided basic automation, but it also introduced limitations that will be explored in the next section.
Karpenter was created to provide a more flexible and efficient way to manage cluster capacity. It is an open source and high performance provisioning engine that responds directly to pods that cannot be scheduled. Instead of depending on predefined cloud node groups, Karpenter evaluates the specific needs of pending pods and launches compute resources that match those requirements. This model allows clusters to scale quickly and cost effectively while meeting the unique constraints of each workload.
In the upcoming sections, we will look at the key building blocks of Karpenter such as NodeClasses, NodePools and NodeClaims. Also, we will explore in detail how Karpenter schedules new capacity and manages node lifecycles within a Kubernetes environment.
Press enter or click to view image in full size
Generated by ChatGPT
Advantages of Karpenter over Cluster Autoscaler
The Kubernetes Cluster Autoscaler has served as the traditional tool for adjusting cluster capacity, but its design is closely tied to cloud provider node groups and it becomes difficult to manage diverse workloads. Karpenter introduces a more flexible and responsive model for provisioning compute resources that offers below advantages.
- **Direct and faster provisioning: **Cluster Autoscaler must work through predefined node groups that adds delay to the provisioning process. Karpenter observes pending pods directly and creates new nodes based on the exact resource requirements. This results in significantly faster reaction times and more efficient cluster scaling during workload spikes.
- **Greater flexibility in instance selection: **Cluster Autoscaler can only scale the instance types defined in each node group. Karpenter can choose from the full range of cloud provider instance types and select the best option at the moment of provisioning. This gives the scheduler more freedom to match compute resources to workload needs such as memory optimized, compute optimized or specialized architectures.
- **Simplified infrastructure management: **Karpenter removes the need for large numbers of node groups and instead uses a declarative and constraint based approach. This simplifies cluster configuration and reduces the burden of maintaining cloud specific autoscaling constructs.
- **Improved cost efficiency: **Karpenter can reduce wasted capacity by selecting the most suitable instance type in real time. It can consider options such as burstable instances or spot capacity when appropriate.
- **Better support for dynamic workloads: **Modern applications often require rapid changes in compute capacity. Karpenter is designed to respond quickly and adaptively that makes it a strong fit for environments with unpredictable traffic, batch processing, event driven systems and high scale workloads.
NodeClasses: Cloud-Specific Configuration
A NodeClass represents the cloud specific configuration that is applied to the nodes Karpenter creates. While Kubernetes focuses on abstract concepts such as pods and nodes, a NodeClass connects those nodes to real infrastructure in your cloud provider account. It captures details such as subnet, security group, instance profile, tags, labels and other settings that are required to launch instances correctly and securely. NodePools and NodeClaims reference a NodeClass so that scheduling logic can remain focused on workload intent, while the NodeClass handles provider details.
Some important attributes you typically find in a provider specific NodeClass (for example EC2NodeClass on AWS) include:
- **spec.amiFamily: **Specifies the AMI family to use when provisioning nodes. Determines the machine image and base configuration applied at launch.
- **spec.role: **Defines the IAM instance profile that the node will assume in order to access cloud APIs, fetch images, write logs and perform required operations.
- **spec.subnetSelectorTerms: **Selects which subnets the nodes can be launched into based on matching labels or criteria. This controls node placement, routing behaviour and overall network topology.
- **spec.securityGroupSelectorTerms: **Determines which security groups are attached to the nodes when they are created. This governs firewall rules, network access and communication boundaries for the nodes.
- **spec.tags: **Provides key value tags applied to provisioned instances and associated resources. Supports cost tracking, ownership metadata and automation workflows.
- **spec.blockDeviceMappings: **Describes storage settings such as root volume type, size and encryption parameters applied to new nodes.
- **spec.userData: **Optionally supplies bootstrap instructions or startup configuration that runs when a node initializes such as cluster join scripts or agent setup.
The following is an example of an AWS specific NodeClass using the EC2NodeClass resource. This is only an illustration, but it shows the typical structure you would define before wiring it with a NodePool:
apiVersion: karpenter.k8s.aws/v1beta1kind: EC2NodeClassmetadata: name: default-ec2-nodeclassspec: amiFamily: AL2 role: "karpenter-node-role" subnetSelectorTerms: - tags: Environment: "prod" NetworkType: "private" securityGroupSelectorTerms: - tags: KubernetesCluster: "my-eks-cluster" tags: Owner: "platform-team" CostCenter: "kubernetes" blockDeviceMappings: - deviceName: /dev/xvda ebs: volumeSize: 100Gi volumeType: gp3 encrypted: true detailedMonitoring: true
NodePools: Defining Node Constraints
A NodePool in Karpenter describes the intent for how new nodes should look and behave from the point of view of Kubernetes workloads. It focuses on scheduling constraints and policies. It tells Karpenter what kinds of nodes are allowed for a group of pods including labels, taints, instance type preferences and zone choices. This separation allows platform teams to encode infrastructure standards in NodeClasses and application or capacity strategies in NodePools.
When pods are pending and cannot be scheduled on existing nodes, Karpenter checks which NodePools satisfy the pod requirements and then chooses a suitable combination of NodePool and NodeClass to create new nodes. Additionally, NodePools define behaviour such as consolidation, node expiration and limits that control how the cluster grows and shrinks over time.
These are some important attributes of the NodePool custom resource:
- **spec.template.metadata.labels: **Default labels that will be applied to nodes created from this pool. These help select or identify nodes for specific workloads.
- **spec.template.spec.nodeClassRef: **A reference to the NodeClass that provides cloud provider configuration for the nodes in this pool.
- **spec.template.spec.requirements: **A list of scheduling requirements that restrict which instance families, sizes, architectures or zones are valid. These are expressed using standard Kubernetes style key, operator and values fields.
- **spec.disruption: **Settings that control how Karpenter performs consolidation, replacement or expiration. For example, you can choose when to remove underutilized nodes or how long nodes should live.
- **spec.limits: **Optional soft limits on total resources. It is used to keep the overall capacity of a pool within a desired range.
The following is an example NodePool that uses the **default-ec2-nodeclass **from the previous section and defines some common constraints and behaviours:
apiVersion: karpenter.sh/v1beta1kind: NodePoolmetadata: name: general-purposespec: template: metadata: labels: workload-type: general spec: nodeClassRef: name: default-ec2-nodeclass requirements: - key: "karpenter.k8s.aws/instance-family" operator: In values: ["m5", "m6i"] - key: "karpenter.k8s.aws/instance-size" operator: In values: ["large", "xlarge", "2xlarge"] - key: "topology.kubernetes.io/zone" operator: In values: ["us-east-1a", "us-east-1b"] disruption: consolidationPolicy: WhenUnderutilized expireAfter: 720h limits: cpu: "500" memory: 1000Gi
NodeClaims: The Node Request Lifecycle
A NodeClaim represents the lifecycle of a single node request in Karpenter. While NodePools describe the constraints for groups of nodes and NodeClasses define the cloud provider configuration, a NodeClaim is the actual request for a new node. Each NodeClaim corresponds to one physical or virtual machine and acts as the bridge between Kubernetes and the cloud provider. It captures the chosen instance type, zone, labels, capacity and the current state of node provisioning.
NodeClaims allow Karpenter to track the entire lifespan of a node from the moment pods cannot be scheduled through provisioning and registration until the node becomes ready. Since NodeClaims are created automatically, users rarely need to write or modify them directly but they are extremely valuable for understanding scheduling decisions, debugging scaling events or verifying cloud provider configuration.
How NodeClaims Are Created Internally
The lifecycle begins when Karpenter detects pods stuck in a Pending state. The controller continuously watches the Kubernetes API server and identifies pods that cannot be scheduled due to insufficient resources. Once detected, Karpenter runs its bin packing algorithm to evaluate pod requirements. It considers CPU, memory, volume requirements, taints, labels and all NodePool constraints. It selects the smallest and most cost efficient node shape that satisfies all conditions.
After determining the ideal configuration, Karpenter creates a NodeClaim object. The NodeClaim contains fields that define the instance type, zone and references to the NodeClass that holds cloud provider settings. The controller reads the NodeClaim and issues cloud provider API calls such as ec2 RunInstances for AWS. The instance boots, joins the cluster as a Kubernetes Node and the NodeClaim progresses through states such as launching, registering and ready. Once the node reports Ready, Karpenter schedules the pending pods onto it.
Get Anish Kumar’s stories in your inbox
Join Medium for free to get updates from this writer.
The following is a simplified example of a NodeClaim object that Karpenter generates:
apiVersion: karpenter.sh/v1kind: NodeClaimmetadata: name: default-sfpsl labels: karpenter.sh/nodepool: default topology.kubernetes.io/zone: us-east-1a node.kubernetes.io/instance-type: m5.largespec: nodeClassRef: name: default-ec2-nodeclass # Other internal provisioning detailsstatus: providerID: aws:///us-east-1a/i-0xxxxxxxxxxxxxxxx nodeName: ip-10-0-12-34.us-east-1.compute.internal capacity: cpu: 2 memory: 7800Mi pods: 110 conditions: - type: Ready status: "True"
Karpenter’s Scheduling Workflow
Karpenter’s scheduling workflow is proactive and works alongside the default Kubernetes scheduler. Instead of waiting for predefined node groups to scale, Karpenter analyzes real time pod demand and provisions the most suitable node based on workload and infrastructure constraints.
Press enter or click to view image in full size
Generated using ChatGPT
The following steps describe the Karpenter’s scheduling workflow:
- **Pending pods: **A new pod is created by a Deployment or other controller but the Kubernetes scheduler cannot place it on any existing node because there is not enough available capacity. The pod enters a Pending state.
- **Karpenter observation: **The Karpenter controller continuously watches the Kubernetes API server and detects the unschedulable pod.
- **Constraint aggregation: **Karpenter gathers all scheduling constraints from the pending pod that gives Karpenter a complete picture of the pod’s needs.
- Resource requests such as CPU, memory and GPU.
- Node selectors.
- Node affinity rules.
- Tolerations for taints applied by NodePools.
- Topology spread constraints.
- Volume requirements if applicable.
- **NodePool evaluation: **Karpenter checks all available NodePools and determines which ones can satisfy the aggregated pod constraints. Only NodePools that match all requirements proceed to the next step.
- **Bin packing and optimal selection: **For the compatible NodePools, Karpenter performs a bin packing simulation:
- It groups batches of pending pods.
- It evaluates all valid instance types and zones allowed by the NodePool.
- It chooses the smallest and most cost efficient instance shape that satisfies all constraints.
- **NodeClaim creation and cloud launch: **After selecting the best node configuration, Karpenter creates a NodeClaim. The NodeClaim includes:
- Chosen instance type.
- Selected availability zone.
- Reference to the NodeClass for provider configuration. Karpenter then calls the cloud provider API to launch the actual instance. The instance starts, bootstraps, and joins the cluster as a Kubernetes Node.
- **Final scheduling: **Once the new node becomes Ready, the Kubernetes scheduler places the previously pending pods onto it. The scaling cycle completes and applications resume normal operation.
Karpenter’s Disruption Workflow
Disruption in Karpenter ensures that existing nodes are gracefully terminated and replaced in order to maintain efficiency, security and cost effectiveness within the cluster. While scaling brings new nodes into the system, disruption handles the responsible removal of nodes that are no longer needed or no longer compliant with cluster configuration. This workflow allows clusters to remain optimized over time without manual intervention. Disruptions are performed safely and strategically that always respects workload availability and Pod Disruption Budgets.
Automated Disruption Methods
- **Drift: **Nodes are disrupted when their current configuration no longer matches the NodeClass/EC2NodeClass. This includes differences in AMI, user data, tags or other provider settings. Drift ensures compliance with infrastructure as code updates.
- **Consolidation: **Karpenter inspects cluster utilization and identifies opportunities to reduce costs.
- WhenEmpty: Removes nodes that have no workload pods.
- WhenEmptyOrUnderutilized: Attempts to pack the node’s pods onto other nodes or replace the node with a smaller or cheaper equivalent instance.
- **Expiration: **Nodes are replaced after the time specified in spec.template.spec.expireAfter of the NodePool. This ensures regular node recycling for security patches, kernel updates and base image changes.
- **Interruption: **Nodes are disrupted when a cloud provider issues events such as spot interruptions, maintenance events or hardware failures. Karpenter detects these signals and proactively replaces the affected nodes to maintain application availability.
The Disruption Process
- **Identification: **The Disruption Controller identifies a node that meets criteria for disruption such as expiration or drift.
- **Taint and Block: **Karpenter applies the taint karpenter.sh/disrupted:NoSchedule to the node. This prevents any new pods from landing on the node during the disruption workflow.
- **Simulation and Replacement: **Karpenter performs a scheduling simulation to verify that all pods on the node can be safely rescheduled. This simulation ensures:
- Ensures Pod Disruption Budgets (PDBs) are respected.
- Determines whether existing nodes can host the pods.
- Decides if a replacement node must be created via a new NodeClaim. If the simulation fails, disruption is postponed.
- **Draining and Termination: **If simulation succeeds, Karpenter drains the node by evicting its pods. After draining the node:
- Karpenter issues a cloud provider API call (such as EC2 terminate).
- The NodeClaim finalizer ensures that cloud resources are fully cleaned up.
- The node is removed from the cluster.
Kapenter Setup and Usage
This section ties together all previous concepts by walking through a practical end to end setup of Karpenter on Amazon EKS. By the end, you will understand how Karpenter installation, NodeClasses, NodePools, and NodeClaims come together to provision right sized compute for real workloads in response to demand.
Installation and IAM Setup
Karpenter relies on AWS IAM permissions to launch, manage and terminate EC2 instances. These permissions are assigned through IRSA (IAM Roles for Service Accounts), ensuring that the Karpenter controller can securely authenticate with AWS.
The Karpenter IAM Role
- **EC2 permissions: **Karpenter needs to launch, describe and terminate instances and query networking resources:
- ec2:RunInstances
- ec2:TerminateInstances
- ec2:DescribeInstances
- ec2:DescribeInstanceTypes
- ec2:DescribeInstanceTypeOfferings
- ec2:DescribeImages
- ec2:DescribeSubnets
- ec2:DescribeVpcs
- ec2:DescribeSecurityGroups
- ec2:DescribeLaunchTemplates
- ec2:DescribeLaunchTemplateVersions
- ec2:DescribeAvailabilityZones
- ec2:DescribeSpotPriceHistory
- ec2:CreateTags
- ec2:DeleteTags
- **IAM permissions: **Karpenter itself should not assume the node role, but it must be allowed to attach it to new EC2 instances:
- iam:PassRole
- iam:GetInstanceProfile
- iam:ListInstanceProfiles
- **Pricing: **To choose the most cost effective instance types, Karpenter may query AWS pricing:
- pricing:GetProducts
- **SSM and SSM Parameter Store: **If you rely on SSM parameters or use SSM to manage AMIs:
- ssm:GetParameter
- ssm:GetParameters
- ssm:GetParametersByPath
- **EKS (cluster level information): **Karpenter sometimes needs to query details about the EKS cluster:
- eks:DescribeCluster
Install Karpenter via Helm
Once IAM and IRSA are configured, install the controller:
helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter \ --namespace karpenter --create-namespace \ --set settings.clusterName=<your-cluster-name> \ --set settings.clusterEndpoint=<your-cluster-endpoint> \ --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=<KarpenterRoleARN>
This deploys Karpenter and gives it the necessary permissions to manage node provisioning in your EKS cluster.
Define NodeClass and NodePool
NodeClass: Cloud Provider Configuration
This is a simplified EC2NodeClass example that incorporates concepts explained earlier such as subnet selection, security groups, AMI family, tags:
apiVersion: karpenter.k8s.aws/v1beta1kind: EC2NodeClassmetadata: name: default-ec2-nodeclassspec: amiFamily: AL2 role: "karpenter-node-role" subnetSelectorTerms: - tags: Environment: prod securityGroupSelectorTerms: - tags: KubernetesCluster: my-eks-cluster tags: Owner: platform-team CostCenter: kubernetes
NodePool: Workload Constraints and Behaviour
Next, create the NodePool that describes how nodes should be provisioned:
apiVersion: karpenter.sh/v1beta1kind: NodePoolmetadata: name: defaultspec: template: metadata: labels: workload-type: general spec: nodeClassRef: name: default-ec2-nodeclass requirements: - key: "karpenter.k8s.aws/instance-family" operator: In values: ["m5", "m6i"]
Running a Deployment that Triggers Scaling
Imagine deploying an application with a very high memory requirement. None of your existing nodes can fit these pods. So this is where Karpenter demonstrates its intelligent autoscaling capability.
apiVersion: apps/v1kind: Deploymentmetadata: name: high-mem-appspec: replicas: 3 selector: matchLabels: app: high-mem-app template: metadata: labels: app: high-mem-app spec: containers: - name: app-container image: public.ecr.aws/eks-distro/kubernetes/pause:3.2 resources: requests: memory: "8Gi" cpu: "1"
Each pod requests 8 GiB of memory. With 3 replicas, we need 24 GiB of memory in total. Within seconds, a correctly sized node is created by karpenter that fits all workload pods in the most cost effective way.
Conclusion
Karpenter represents a major advancement in the way Kubernetes clusters manage and scale compute resources. It moves away from the older autoscaling approach that relies on predefined node groups and instead uses a workload aware provisioning model that responds directly to real time demand. This allows clusters to react faster and to launch nodes that match the exact needs of the workloads that leads to better performance and more efficient use of compute capacity.
Throughout this guide, we explored the key building blocks that make this possible. The NodeClass resource provides the cloud provider settings that define how nodes are created. The NodePool resource captures the scheduling intent and the constraints used to guide provisioning decisions. The NodeClaim resource represents the lifecycle of each individual node request. These concepts allow Karpenter to make smart provisioning decisions, optimize scheduling and gracefully manage node replacement through its disruption process.
For any organization running dynamic and modern cloud native applications that adopts Karpenter can lead to simpler operations, lower compute costs and a more responsive Kubernetes environment. As clusters continue to grow in size and complexity, a provisioning engine that can think in real time and react to actual workload needs becomes essential. Karpenter delivers exactly that and stands out as a powerful step forward for efficient and intelligent cluster management.
If you find my work valuable and would like to support it, you are welcome to sponsor me.
Happy Learning!!!