KarpenterπŸ”₯ Production-Critical⭐ Beginner to Staff

If Karpenter Runs on One Node, How Does It Know When Other Nodes Need More Capacity?

Why Karpenter is a Deployment not a DaemonSet, how it provisions EC2 instances without touching Node Groups, why terraform destroy never deletes Karpenter nodes, and the one configuration mistake that lets Karpenter consolidate its own floor.

β€œKarpenter does not watch your nodes. It watches one thing β€” and that one thing changes everything.”

By Ravi Kapoor|June 21, 2026|20 min read|Has prevented at least 6 very expensive Friday afternoon surprises

β€” EKS Cluster, Black Friday, 2:17 AM β€”

PagerDuty fires. β€œ47 pods Pending β€” Unschedulable.”

You open the laptop. Traffic is 4x normal. Makes sense. You check everything:

βœ… Karpenter pod is running

βœ… NodePool shows no errors

βœ… EC2NodeClass looks fine

βœ… IAM permissions intact

Yet 47 pods are stuck. Karpenter is alive but provisioning nothing.

$ kubectl logs -n karpenter deploy/karpenter | grep -i error

no instance type satisfies requirements: m5.large: InsufficientInstanceCapacity, m5.xlarge: InsufficientInstanceCapacity

You don't know why yet. You're about to.

The NodePool had been locked to two instance types three weeks earlier β€” β€œto control costs.” Both were sold out across every availability zone. It was Black Friday. AWS was not holding them in reserve.

Karpenter was working perfectly. The configuration was the failure. And the reason it took 45 minutes to diagnose β€” instead of 4 minutes β€” is that most engineers don't fully understand what Karpenter is, what it watches, and what it controls. This article fixes that.

The Interview Question

Six months after that incident, you're interviewing for a Senior Platform Engineer role. The interviewer is taking notes, then stops and asks:

β€œIf Karpenter runs as a single Deployment on one node, how does it know when pods on other nodes need more capacity?”

Most people answer this by describing what they wish Karpenter did: monitoring node metrics, watching CPU and memory, responding to thresholds. That answer is completely wrong. And the interviewer knows it.

The correct answer requires understanding one architectural decision that Karpenter makes β€” and that decision changes everything about how you configure, debug, and trust it in production.

The Simple Answer (The Trap)

Here is the naive mental model of how Karpenter works β€” the one most people carry into interviews:

Karpenter watches CPU/memory on each node
β–Ό
Detects a node is under pressure
β–Ό
Decides more capacity is needed
β–Ό
Creates a new Managed Node Group
β–Ό
Pods migrate to the new nodes

Every single step in this diagram is wrong. The interviewer is not satisfied.

Interviewer keeps going:

❓ β€œWhat Kubernetes object does Karpenter actually watch to decide when to provision a node?”

❓ β€œIf Karpenter launches an EC2 instance directly β€” is a Managed Node Group created?”

❓ β€œWhat prevents Karpenter from provisioning a thousand nodes during a misconfigured load test?”

❓ β€œIf your Terraform team runs terraform destroy β€” what happens to the Karpenter-provisioned nodes?”

❓ β€œWhat happens to the cluster if the one node running Karpenter gets terminated?”

Let's answer all five. But first β€” the mental model that makes everything click.

The Mental Model β€” Read This Before Anything Else

Before any technical depth, here is the analogy. Think of how a staffing agency works for a company with a permanent team and occasional surge demand.

Staffing Agency WorldKarpenter World
Permanent core team (always present)Managed Node Group β€” baseline nodes Terraform manages
HR system receives "we need 3 more engineers"Kubernetes Scheduler marks pods as Pending / Unschedulable
Staffing agency (external, called on demand)Karpenter β€” the provisioner, called by the API Server event
Agency calls talent pool, hires people in hoursKarpenter calls AWS EC2 API, launches instances in minutes
New hire's contract is NOT in the company HR systemKarpenter node is NOT in Terraform state
Job spec: "Python, 5 years, remote-ok"NodePool constraints: instance family, arch, AZ, capacity type
Employment contract: salary, benefits, officeEC2NodeClass: AMI, subnet, security groups, IAM role
Agency can lay off temp workers independentlyKarpenter can consolidate and terminate idle nodes
Permanent employees cannot be let go by the agencyManaged Node Group nodes are outside Karpenter's control

Hold that model. Karpenter is the staffing agency. The API Server is the HR system that tells it there is an open role. EC2 is the talent pool. The new hire (node) is real β€” but the agency (Terraform) that built the company never wrote them into its books.

What Karpenter Replaces

Before Karpenter, EKS scaling meant Cluster Autoscaler. Cluster Autoscaler has one job: adjust the node count inside pre-existing Node Groups.


  Traditional EKS scaling:

  EKS Cluster
  β”‚
  β”œβ”€β”€ Managed Node Group A  (max: 5)
  β”‚   β”œβ”€β”€ Node 1
  β”‚   β”œβ”€β”€ Node 2
  β”‚   └── Node 3    ← Cluster Autoscaler adds nodes here
  β”‚
  └── Managed Node Group B  (max: 3)
      β”œβ”€β”€ Node 4
      └── Node 5    ← Or here. Only inside existing groups.

  Cluster Autoscaler can scale within groups.
  It cannot create new groups. It cannot pick instance types.
  It scales the count. Karpenter picks the instance.

Cluster Autoscaler cannot pick instance types. It cannot create new node groups. It cannot respond to topology constraints or GPU requirements automatically. When your node group hits max capacity and you need a different instance type, Cluster Autoscaler waits. You configure. You wait more.

Karpenter removes the node group middleman entirely.

Q1: Does Karpenter Create New Node Groups?

The wrong answer: yes, Karpenter creates and manages Managed Node Groups dynamically.

This is like saying a hotel concierge builds a new hotel wing every time a guest needs a room. The concierge calls room service. The wing was never part of the plan.

Karpenter calls the AWS EC2 Fleet API directly and launches individual instances. No Managed Node Group is created. No CloudFormation stack. No Auto Scaling Group. The instance bootstraps using the EKS node bootstrap script, registers itself with the cluster, and gets tagged with karpenter.sh/nodepool. That tag is how Karpenter tracks ownership. The node is real. It just has no group.


  What actually happens:

  New pod arrives β€” no node has capacity
       β”‚
       β–Ό
  Kubernetes Scheduler: "I cannot place this pod"
       β”‚
       β–Ό
  Pod status β†’ Pending (condition: Unschedulable)
       β”‚
       β–Ό
  Karpenter watches API Server β€” sees the Pending pod
       β”‚
       β–Ό
  Karpenter calculates: what instance satisfies this pod's
  requests + limits + nodeSelector + tolerations + affinity?
       β”‚
       β–Ό
  Karpenter calls AWS EC2 Fleet API directly
       β”‚
       β–Ό
  EC2 instance launches, bootstraps, joins EKS cluster
       β”‚
       β–Ό
  Kubernetes Scheduler places the pod on the new node
       β”‚
       β–Ό
  Pod is Running. No Node Group was created.

🧠 Memory Trick

Cluster Autoscaler scales groups. Karpenter provisions instances. One works within structure. The other creates structure on demand. They are not the same class of tool with different names.

Q2: Why Is Karpenter a Deployment, Not a DaemonSet?

The wrong answer: β€œbecause it needs to monitor resource usage on every node.”

This is the staffing agency equivalent of requiring the recruiter to sit at every employee's desk simultaneously to see if anyone needs help. The recruiter needs one desk and a phone. They respond to requests β€” they don't surveil the building.

Karpenter is a Kubernetes controller. Controllers watch the API Server for events and reconcile state. They do not run on every node β€” they run once (or twice for HA) and react. One replica is enough because the API Server is the single source of truth for everything Karpenter needs to know.

DaemonSets are for node-local agents: things that need access to a specific node's filesystem, kernel, or network interfaces. Examples:

DaemonSet (one pod per node)Deployment (one or few pods)
aws-node (CNI plugin)Karpenter
kube-proxyArgoCD Application Controller
Fluent Bit (log shipper)Cert Manager
Node Exporter (metrics)ExternalDNS
Falco (runtime security)AWS Load Balancer Controller

🚨 Interview Trap

β€œKarpenter needs to be a DaemonSet to watch node resource usage in real time.” Karpenter never looks at node resource usage. It does not watch CPU or memory metrics. The HPA watches metrics. Karpenter watches for Pending pods β€” a completely different signal from a completely different source. If you say β€œDaemonSet” in an interview, you've revealed that your mental model of Karpenter is built on the wrong foundation.

Q3: How Does Karpenter Know When to Provision a New Node?

The wrong answer: β€œit monitors CPU and memory thresholds on existing nodes.”

Karpenter has never looked at a CPU metric. That is the HPA's job. Karpenter does not care how loaded your nodes are. It cares about exactly one thing: pods that the Kubernetes Scheduler declared it could not place.

Here is the sequence that actually happens:


  Karpenter lives on ONE node.
  It watches ONE thing: the Kubernetes API Server.

  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚                 Kubernetes API Server               β”‚
  β”‚   (stores pod state, node state, all events)        β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚  watches for Pending pods
                            β–Ό
                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                     β”‚  Karpenter   β”‚  ← runs as a Deployment
                     β”‚  (1-2 pods)  β”‚    on managed node group
                     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚  calls
                            β–Ό
                     AWS EC2 Fleet API
                            β”‚
                            β–Ό
                     New EC2 instance
                            β”‚
                     joins EKS cluster
  1. A new pod arrives with resource requests the current nodes cannot satisfy.
  2. The Kubernetes Scheduler tries every node. None fits. It marks the pod Pending with condition Unschedulable.
  3. Karpenter is watching the API Server for exactly this event.
  4. Karpenter reads the pod spec: CPU requests, memory requests, nodeSelector, tolerations, topology spread constraints, affinity rules.
  5. Karpenter calculates the cheapest instance type from its NodePool that satisfies all of those constraints.
  6. Karpenter calls the AWS EC2 Fleet API. The instance launches.
  7. The new node registers with the cluster. The Scheduler places the pending pod on it.

Karpenter does not care which node the pod was trying to run on. It does not care how loaded your existing nodes are. It cares only that a pod is waiting and no current node will take it.

πŸ”₯ Production Reality

Because Karpenter responds to Pending pods β€” not to node utilization β€” it can provision a node for a pod that has been Pending for under 30 seconds. Cluster Autoscaler's scale-up can take 3–5 minutes: it checks utilization, decides to scale, updates the ASG, waits for the instance, waits for it to join. In high-traffic scenarios, Karpenter's approach wins by minutes. Those minutes are the difference between a self-healing incident and a user-visible outage.

Q4: Will Terraform Delete Karpenter Nodes?

The wrong answer: yes, terraform apply or terraform destroy will remove them.

Terraform can only destroy what it created. Karpenter nodes were born outside Terraform's state file β€” like contractors hired by a staffing agency that bypassed the company's HR system. Terraform's destroy command has no idea they exist.


  Terraform manages:              Karpenter manages:
  ─────────────────               ─────────────────
  VPC                             Worker nodes (EC2 instances)
  EKS Cluster                     NodePool CRDs
  IAM Roles/Policies              EC2NodeClass CRDs
  Baseline Node Group             Node lifecycle (launch, drain, terminate)
  Karpenter Helm install

  terraform.tfstate knows about   terraform.tfstate does NOT know about
  everything on the left.         anything on the right.

When you run terraform destroy, Terraform reads its state file. It sees the VPC, the EKS cluster, the IAM roles, the managed node group. It does not see the 20 EC2 instances Karpenter launched last night. It deletes everything it knows about β€” and leaves the Karpenter nodes as orphaned EC2 instances with no cluster to connect to.

Those orphaned instances keep running and billing you until you terminate them manually or until Karpenter terminates them (which it cannot, because the cluster it talks to is gone).

⚑ Pro Tip

Before running terraform destroy on a cluster using Karpenter, first drain and delete all Karpenter-managed nodes: kubectl delete nodes -l karpenter.sh/nodepool. Karpenter will terminate the underlying EC2 instances cleanly. Then run your Terraform destroy. No orphaned instances, no surprise bills.

Q5: What Is the Difference Between NodePool and EC2NodeClass?

The wrong answer: β€œthey're just the new names for Node Groups.”

NodePool says what you want. EC2NodeClass says how to build it. Confusing them is like confusing a job description with an employment contract. One defines the requirements. The other defines the terms.

NodePool β€” the scheduling constraints

NodePool defines what Karpenter is allowed to provision: which instance families, which architectures, which availability zones, which capacity types (Spot vs On-Demand), what taints to apply, and β€” critically β€” a resource budget cap that prevents runaway scaling.

nodepool.yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand", "spot"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64"]
        - key: node.kubernetes.io/instance-type
          operator: In
          values: ["m5.large","m5.xlarge","m5.2xlarge",
                   "m6i.large","m6i.xlarge","m6i.2xlarge",
                   "c5.xlarge","c5.2xlarge"]
  disruption:
    consolidationPolicy: WhenUnderutilized
    consolidateAfter: 30s
  limits:
    cpu: "200"
    memory: 400Gi

EC2NodeClass β€” the AWS-specific config

EC2NodeClass defines the AWS details that every instance launched by this NodePool will use: which AMI family, which subnets (via tags), which security groups, which IAM instance profile.

ec2nodeclass.yaml
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiFamily: AL2023
  role: "KarpenterNodeRole-my-cluster"
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: "my-cluster"
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "my-cluster"
  amiSelectorTerms:
    - alias: al2023@latest

One NodePool references one EC2NodeClass. You can have multiple NodePools pointing at the same EC2NodeClass β€” for example, a GPU NodePool and a CPU NodePool both using the same subnet and security group config, but with different instance type constraints.

🧠 Memory Trick

NodePool = hiring criteria (what skills, what seniority, remote or not). EC2NodeClass = employment terms (salary band, office, benefits). You can post multiple job openings using the same employment terms β€” different roles, same company structure.

Q6: What Happens If the Node Running Karpenter Is Terminated?

Most people answer: β€œthe whole cluster breaks.” The honest answer is: it depends entirely on one configuration decision.

Karpenter is a Deployment. If its node goes down, Kubernetes reschedules it on another available node. Existing workloads keep running. New pending pods wait β€” but only for the time it takes Karpenter to restart. That is usually under 60 seconds.

But there is a scenario where this goes badly wrong.


  The Karpenter bootstrap problem:

  Who manages Karpenter?    β†’  A managed node group (Terraform)
  Who manages app nodes?    β†’  Karpenter
  Who manages Karpenter's node?  β†’  NOT Karpenter

  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  Managed Node Group (Terraform)                      β”‚
  β”‚  β”œβ”€β”€ karpenter-xxxx  ← lives here, safe from         β”‚
  β”‚  └── coredns-xxxx      Karpenter consolidation       β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  Karpenter-provisioned nodes                         β”‚
  β”‚  β”œβ”€β”€ app-pod-a                                       β”‚
  β”‚  └── app-pod-b                                       β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

  If Karpenter runs on a node it manages β†’ it can
  consolidate its own floor β†’ evict itself β†’ deadlock.

If Karpenter manages all nodes in the cluster β€” no baseline managed node group β€” and consolidation is enabled, Karpenter can look at the node it runs on, decide it is underutilized, and begin draining it. Karpenter gets evicted. Its pod becomes Pending. No node is available for it to reschedule onto. No Karpenter means no provisioner. The cluster cannot scale. New pods pile up as Pending indefinitely.

This is the Karpenter bootstrap problem β€” directly analogous to the static pod bootstrap problem. The provisioner cannot provision for itself.

πŸ˜… Senior Engineer Confession

More Karpenter migrations than anyone admits have hit this exact deadlock. The setup works fine during testing β€” because there are always enough nodes running. Then consolidation kicks in at 3 AM, the last node with Karpenter on it drains, and the on-call engineer wakes up to a cluster that can run existing pods but cannot add new ones. The fix is always the same: keep a managed node group for Karpenter itself. Write it down before you forget.

🚨 Interview Trap

β€œKarpenter can manage itself because it's just another pod.” It cannot provision the node it runs on. If Karpenter's node is the only node, and that node is terminated, Karpenter has no floor to land on. The solution is a small managed node group β€” 2–3 nodes β€” that is explicitly excluded from Karpenter's consolidation scope using a nodeSelector on the Karpenter Deployment and a taint on those nodes. Always have a floor that isn't made of Karpenter.

Two Production Disasters

Disaster 1: The NodePool That Wouldn't Scale on Black Friday

An e-commerce company, 50-node EKS cluster, Black Friday traffic. Three weeks earlier, a platform engineer had locked the NodePool to m5.large and m5.xlarge only β€” β€œfor cost predictability.”

At 2:17 AM under 4x traffic load, both instance types were sold out across all three availability zones. AWS EC2 Fleet API returned InsufficientInstanceCapacityfor every provisioning attempt. Karpenter retried, got the same error, and stopped. 47 pods stayed Pending. The site partially degraded for 45 minutes.

The fix: added six additional instance types to the NodePool requirements β€” m6i.large, m6i.xlarge, c5.xlarge, c5.2xlarge, r5.large, r5.xlarge. Pods scheduled within 90 seconds of the change.

The restriction was written to save money. It cost an estimated $200k in lost revenue over 45 minutes. Karpenter's entire value is picking the right instance automatically. Locking it to one type defeats the purpose entirely.

Disaster 2: Karpenter Consolidated Its Own Floor

A platform team enabling Karpenter node consolidation for the first time on a production cluster. They set consolidationPolicy: WhenUnderutilized with consolidateAfter: 30s. The Karpenter Deployment had no node selector β€” it could run on any node, including Karpenter-managed nodes.

At 3:10 AM, traffic dropped to night-time levels. Karpenter scanned the cluster. The node running Karpenter was underutilized. Karpenter began draining it. The Karpenter pod was evicted. Its pod became Pending. No provisioner to schedule it onto a new node. No new nodes were provisioned. Karpenter stayed Pending for 6 hours until the on-call engineer came online at 9 AM.

πŸ”₯ Production Reality

During those 6 hours, the existing application pods kept running β€” Kubernetes does not need Karpenter to maintain what is already scheduled. But any new deployment, any scaling event, any pod crash that needed a new node β€” all of it queued silently as Pending. No alerts fired because the cluster was technically healthy. The fix: add nodeSelector: {node.kubernetes.io/purpose: system} to the Karpenter Deployment and apply a matching taint to the managed node group. Karpenter never touches nodes it cannot land on.

The Wall of Shame

Six mistakes. All extremely common. Several made by senior engineers who knew better.

1. Locking NodePool to one or two specific instance types

β€œThis is the Karpenter equivalent of running a taxi company with only one make and model of car β€” during a city-wide shortage of that model. The taxis don't arrive. Karpenter's entire value is choosing the right instance. You hired a chef and told them only one ingredient is allowed.”

What happens: When that instance type is unavailable (happens routinely on AWS during high-demand periods), all pending pods wait indefinitely.

Fix: Use instance families and architectures in requirements, not specific types. Let Karpenter pick from 8–10 options.

2. No resource limits on the NodePool

β€œThis is giving an intern an unlimited corporate card and going on holiday for two weeks. During a load test with a pod autoscaling bug that creates infinite replicas, Karpenter will provision 40 nodes in 8 minutes and bill you accordingly. The default NodePool has no limits. You have to add them yourself.”

What happens: Runaway scaling during any load spike or misconfigured HPA. AWS bill is the alert.

Fix: Always set spec.limits.cpu and spec.limits.memory on every NodePool before deploying to production.

3. Running Karpenter and Cluster Autoscaler simultaneously

β€œTwo GPS apps giving contradictory directions while you're driving at 100 km/h. They do not coordinate. CAS tries to scale up a node group. Karpenter provisions a direct instance. The pod gets double-provisioned or stuck between two conflicting decisions neither tool designed for. The cluster becomes unpredictably over- or under-provisioned.”

What happens: Unpredictable scaling behavior, duplicate provisioning, wasted compute spend.

Fix: Disable Cluster Autoscaler completely before enabling Karpenter. They are not complementary tools.

4. Letting Karpenter run on nodes it manages

β€œBuilding your house on stilts β€” and hiring the same contractor to remove stilts for cost savings. Karpenter decides the stilt it's standing on is underutilized. It removes it. It falls. Nobody designed a plan for what happens after that.”

What happens: Karpenter evicts itself during consolidation, becomes Pending with no provisioner to reschedule it, silent cluster scaling deadlock.

Fix: Keep a small managed node group. Pin Karpenter to it with nodeSelector and a matching taint.

5. Enabling consolidation without PodDisruptionBudgets

β€œLetting the building manager reorganize all offices simultaneously over one weekend. When everyone returns Monday, the database replica, the ingress controller, and the primary application are all in the middle of moving at the same time. Nobody planned for three critical services to be unavailable simultaneously.”

What happens: Karpenter drains nodes aggressively. Stateful services, single-replica deployments, and critical system pods can go down together.

Fix: Set PodDisruptionBudgets on all critical workloads before enabling WhenUnderutilized consolidation.

6. Not cleaning up Karpenter nodes before running terraform destroy

β€œDemolishing the building around the contractors the staffing agency sent β€” because they're not on the building permit. The building is gone. The contractors are still on the lot. Still billing. Terraform deleted the cluster. The Karpenter EC2 instances are still running.”

What happens: Orphaned EC2 instances continue running and billing after the EKS cluster is destroyed. They have no cluster to talk to.

Fix: Run kubectl delete nodes -l karpenter.sh/nodepool before any terraform destroy.

Best Practices

  1. Always maintain a baseline managed node group for system components. Karpenter, CoreDNS, aws-node, kube-proxy β€” pin them to managed nodes with a nodeSelector and taint. Never let Karpenter consolidate its own floor.
  2. Set resource limits on every NodePool before production. spec.limits.cpu and spec.limits.memory are not optional. They are the only thing preventing runaway provisioning during a load test or HPA misconfiguration.
  3. Allow 8–10 instance types, not 1–2. Use instance families (m5.*, m6i.*, c5.*) and let Karpenter optimize within them. AWS instance availability is not guaranteed. Diversity is your hedge.
  4. Set PodDisruptionBudgets before enabling consolidation. Every stateful workload, every single-replica Deployment, and every system component should have a PDB before you turn on WhenUnderutilized.
  5. Disable Cluster Autoscaler completely when migrating to Karpenter. They do not coexist gracefully. Pick one. For new clusters, Karpenter. For existing clusters, migrate then disable CAS in a single planned window.
  6. Delete Karpenter-managed nodes before terraform destroy. kubectl delete nodes -l karpenter.sh/nodepool β€” run this first, wait for termination, then run your Terraform commands.
  7. Use karpenter.sh/do-not-disrupt: "true" for critical pods. This annotation tells Karpenter never to evict this pod during consolidation. Use it on anything that cannot tolerate voluntary disruption.

FAQ

Does Karpenter replace the Kubernetes Scheduler?

No. The Scheduler still decides where pods run. Karpenter ensures that the node the Scheduler wants to use actually exists. Scheduler picks the node. Karpenter provides it. They do completely different jobs and are not in competition.

What Kubernetes version does Karpenter require?

Karpenter v1.x requires Kubernetes 1.26 or later. The EKS-specific Karpenter documentation always lists the supported matrix β€” check it before upgrading your cluster, because Karpenter and cluster versions are tightly coupled.

Is Karpenter specific to AWS?

The core Karpenter project is cloud-agnostic. The AWS provider (which handles EC2NodeClass and EC2 Fleet API calls) is AWS-specific. Azure has an AKS Karpenter provider. GKE uses a different autoscaling model (GKE Autopilot). For EKS, Karpenter is the recommended autoscaler as of 2024.

What is the difference between Karpenter and Cluster Autoscaler?

Cluster Autoscaler adjusts node count within pre-existing Node Groups. It reacts to unschedulable pods and node underutilization. Karpenter provisions individual EC2 instances directly, picks the optimal instance type per pod, and manages node lifecycle end-to-end. Karpenter responds faster (seconds vs minutes) and requires no pre-configured node groups. For new EKS clusters, Karpenter is the recommended choice.

Does Karpenter support GPU instances?

Yes. Add GPU instance types (p3.2xlarge, g4dn.xlarge, etc.) to your NodePool requirements and set the appropriate nvidia.com/gpu resource request on your pods. Karpenter will select a GPU instance when a pod requests GPU resources. Use a separate NodePool for GPU workloads with the correct AMI configured in EC2NodeClass.

People Also Ask

Can Karpenter provision Spot instances?β–Ό
Yes. Set karpenter.sh/capacity-type: spot in your NodePool requirements. Karpenter handles Spot interruption notices automatically β€” when AWS sends a two-minute interruption warning, Karpenter drains the node gracefully and provisions a replacement. You can mix Spot and On-Demand in the same NodePool by including both values in the capacity-type requirement; Karpenter will prefer Spot when available and fall back to On-Demand.
Can Karpenter and Cluster Autoscaler run at the same time?β–Ό
Technically yes, but it is not recommended. They will both react to the same Pending pods and can provision duplicate capacity, leading to over-provisioning and inconsistent behavior. If you are migrating from Cluster Autoscaler, the correct approach is to disable CAS on node groups that Karpenter will manage, migrate in stages, and remove CAS entirely once Karpenter is stable.
What is node consolidation in Karpenter?β–Ό
Consolidation is Karpenter's cost optimization feature. When enabled with consolidationPolicy: WhenUnderutilized, Karpenter identifies nodes that are running below their capacity and replaces multiple underutilized nodes with fewer, better-packed ones. It drains the nodes (respecting PodDisruptionBudgets), terminates the EC2 instances, and reschedules the pods on remaining or newly-provisioned nodes. It can reduce your EC2 bill significantly overnight β€” and cause incidents if PDBs are not set.
What happens to Karpenter nodes when you run terraform destroy?β–Ό
They become orphaned EC2 instances. Terraform only destroys resources it created and tracked in its state file. Karpenter nodes were provisioned via direct EC2 API calls β€” they are not in Terraform state. After the EKS cluster is destroyed, these instances have no cluster to connect to but keep running and billing you until manually terminated. Always delete Karpenter nodes first: kubectl delete nodes -l karpenter.sh/nodepool.
Does Karpenter work on Azure or GCP?β–Ό
Azure has a Karpenter provider for AKS (in preview as of 2024). GCP uses a different model β€” GKE Autopilot handles node provisioning natively without Karpenter. The core Karpenter project is provider-agnostic, but the AWS provider is the most mature and production-ready implementation. For EKS on AWS, Karpenter is the first-class choice.
How does Karpenter handle availability zone preferences?β–Ό
Karpenter reads topology constraints from the pending pod spec β€” topologySpreadConstraints and nodeAffinity zone preferences. It then selects an AZ that satisfies those constraints and picks a subnet in that AZ (configured via tags in EC2NodeClass). If a specific instance type is unavailable in the preferred AZ, Karpenter can try another AZ depending on the pod's constraints. This is why specifying multiple instance types matters β€” it gives Karpenter room to maneuver across AZs.
What is the karpenter.sh/do-not-disrupt annotation?β–Ό
It is a pod or node annotation that instructs Karpenter never to voluntarily evict the pod or drain the node during consolidation or upgrade operations. Apply it as karpenter.sh/do-not-disrupt: "true" on pods that cannot tolerate disruption β€” stateful leaders, jobs in progress, critical singletons. It does not protect against node failures or Spot interruptions β€” only against Karpenter's voluntary disruption operations.
What happens if Karpenter's own pod crashes?β–Ό
Kubernetes restarts it automatically β€” it is a Deployment with a restart policy. Existing workloads on Karpenter-managed nodes keep running unaffected. New pending pods will wait until Karpenter recovers, which typically takes under 60 seconds. For high availability, run Karpenter with 2 replicas using a podAntiAffinityrule to spread them across nodes β€” a single replica is a single point of failure for your cluster's scaling capability.

Interview Corner

Questions You Should Be Able to Answer at Any Level

Q: What does Karpenter watch to decide when to provision a node?

Pending pods. Specifically, pods that the Kubernetes Scheduler has marked Unschedulable. Karpenter watches the API Server for this event β€” not node CPU, not memory utilization, not any metric. When a pod cannot be placed, that is Karpenter's signal.

Q: Why is Karpenter deployed as a Deployment and not a DaemonSet?

Karpenter is a Kubernetes controller. It watches the API Server for cluster-level events and acts once centrally. It does not need to run on every node β€” it does not touch the nodes it manages at the OS or filesystem level. DaemonSets are for node-local agents that need direct access to a node's resources (kube-proxy, CNI, log shippers).

Q: Does Karpenter create Managed Node Groups?

No. Karpenter calls the AWS EC2 Fleet API directly to launch individual EC2 instances. The instances bootstrap and register with the EKS cluster. No Auto Scaling Group, no CloudFormation stack, no Managed Node Group is created. Karpenter tracks ownership via the karpenter.sh/nodepool tag on the EC2 instance.

Q: What is the difference between NodePool and EC2NodeClass?

NodePool defines scheduling constraints β€” which instance families, architectures, availability zones, capacity types, taints, and crucially the resource budget limit. EC2NodeClass defines the AWS-specific provisioning details β€” which AMI, which subnets, which security groups, which IAM instance profile. One NodePool references one EC2NodeClass. Multiple NodePools can share one EC2NodeClass.

Q: Will terraform destroy delete Karpenter-provisioned nodes?

No. Karpenter nodes are launched directly via EC2 API calls and are not in Terraform state. terraform destroy only acts on resources it created. After the EKS cluster is destroyed, Karpenter-provisioned instances become orphaned EC2 instances that continue running and billing. Always delete them first with kubectl delete nodes -l karpenter.sh/nodepool.

Q: What is the Karpenter bootstrap problem?

Karpenter cannot provision the node it runs on. If Karpenter manages all nodes in the cluster and consolidation is enabled, it can drain its own node, evict itself, and then have no node available to reschedule onto. The cluster keeps running but loses its ability to scale. The fix is a small managed node group pinned for system components that Karpenter never touches.

Q: How does Karpenter decide which instance type to launch?

It reads the pending pod's resource requests, limits, nodeSelector, tolerations, topology spread constraints, and affinity rules. It cross-references these against the NodePool requirements (allowed instance types, architectures, AZs). From the matching candidates, it selects the most cost-efficient option. This is why allowing more instance types gives Karpenter more optimization opportunities.

Q: How does Karpenter handle Spot interruptions?

When AWS sends a Spot interruption notice (two minutes before termination), Karpenter cordons and drains the node gracefully, respecting PodDisruptionBudgets. It then provisions a replacement node (Spot or On-Demand depending on availability) before the interruption happens. This makes Spot instances far more practical in production than managing interruptions manually.

🎀 The 60-Second Answer

🎀 Say This Out Loud Until You Own It

β€œKarpenter is a Kubernetes controller β€” not a node agent β€” so it runs as a Deployment, not a DaemonSet. It lives on one node and watches the Kubernetes API Server for one specific event: pods that the Scheduler has marked Unschedulable. When it sees that event, it reads the pod spec and calculates the exact EC2 instance type that satisfies the pod's requests, tolerations, and topology constraints.

Then it calls the AWS EC2 Fleet API directly and launches that instance. No Managed Node Group is created. No CloudFormation stack. The instance bootstraps, joins the cluster, and the Scheduler places the pending pod. The whole thing happens in under 60 seconds for most workloads.

NodePool defines what Karpenter can provision: allowed instance families, availability zones, capacity types, and a resource limit that caps total provisioning. EC2NodeClass defines the AWS-specific details: AMI, subnets, security groups. Always set NodePool resource limits before production β€” there are no defaults.

The critical thing most people miss: Karpenter cannot provision the node it runs on. If consolidation is enabled and Karpenter runs on a node it manages, it can drain its own floor, evict itself, and the cluster loses its provisioner silently. Always keep a small managed node group for Karpenter itself. Also β€” Karpenter nodes are not in Terraform state. Run kubectl delete nodes -l karpenter.sh/nodepool before any terraform destroy, or you'll be paying for orphaned EC2 instances until someone notices.”

If you can say that without looking at notes, you genuinely understand Karpenter. That is the offer.

Key Takeaways

  • β†’Karpenter watches the API Server for Pending/Unschedulable pods β€” not node CPU or memory.
  • β†’Karpenter is a Deployment (controller), not a DaemonSet. One replica watches the whole cluster.
  • β†’It calls the EC2 Fleet API directly. No Managed Node Groups are created.
  • β†’NodePool = scheduling constraints. EC2NodeClass = AWS provisioning config.
  • β†’Always set spec.limits.cpu and spec.limits.memory on NodePools. No default cap exists.
  • β†’Karpenter nodes are not in Terraform state β€” delete them before terraform destroy.
  • β†’Karpenter cannot provision the node it runs on β€” keep a baseline managed node group.
  • β†’Consolidation + no PodDisruptionBudgets = a production incident waiting for 3 AM.

The 47 pending pods at 2:17 AM were not a Karpenter failure. Karpenter was doing exactly what it was configured to do: provision instances from the allowed list. The allowed list had two items. Both items were sold out. The cluster was working correctly. The configuration was the failure.

The interview question β€” β€œhow does Karpenter know when pods on other nodes need more capacity?” β€” is not really about Karpenter's architecture. It is about whether you understand the API-Server-centric model of Kubernetes. Everything in Kubernetes flows through the API Server. Karpenter is just the part that listens for the signal that the Scheduler already broadcasts.

Understand that, and every Karpenter configuration decision β€” NodePool constraints, resource limits, Spot vs On-Demand, consolidation policy β€” becomes obvious reasoning, not memorized settings.

About the author

Ravi Kapoor

Senior DevOps Engineer & Technical Writer

CKA & AWS SA-Pro Certified9 yrs β€” Atlassian & FintechKubernetes open-source contributor

Ravi is a senior DevOps engineer with 9 years of experience building cloud-native infrastructure at Atlassian and multiple fintech companies. CKA and AWS Solutions Architect Professional certified, he has managed Kubernetes clusters serving millions of daily users and contributes to open-source tooling.

Targeting a Platform or DevOps Role?

AiResumeFit matches your resume to Kubernetes, cloud, and DevOps job descriptions β€” improving your ATS score in seconds.

Optimize My Resume β†’