Karpenter
Karpenter is a Kubernetes node provisioner from AWS. It watches for pods the scheduler cannot place, launches an EC2 instance that fits them, and removes the instance again when the pods go. In the Web App Blueprint it supplies every node the application runs on.
What it does
Karpenter has no node groups and no Auto Scaling groups. An EC2NodeClass describes what a node looks like: the AMI family, the IAM role, the subnets and the security groups. A NodePool describes what Karpenter may buy: architectures, capacity types, instance families and a hard resource limit. For an unschedulable pod the controller prices the offerings that satisfy its requests and launches the cheapest one that fits. Consolidation does the reverse, removing empty nodes and repacking under-used ones onto fewer or smaller instances.
How BuiltForProd uses it
The karpenter unit installs the Karpenter chart (oci://public.ecr.aws/karpenter/karpenter, version 1.14.1, which is also the controller version) into kube-system with two controller replicas. Credentials come from an EKS Pod Identity association; the upstream submodule that ships with the EKS registry module also creates the node IAM role, its access entry, and the SQS queue that receives Spot interruption notices so a node is drained before EC2 reclaims it.
One EC2NodeClass and one NodePool, both named default, define the capacity:
| Setting | Value |
|---|---|
| Image | Amazon Linux 2023, latest release of the family; amd64 and Linux only |
| Instance families | m6i, m7i, m6a, m7a; bare-metal sizes excluded |
| Capacity types | Spot and on-demand |
| vCPU ceiling | 32 in dev, 64 in staging, 128 in prod |
| Consolidation | Empty or under-used nodes go after one minute; at most 10% disrupted at once |
| Node lifetime | 720 hours, after which the node is drained and replaced on a fresh AMI |
| Instance metadata | IMDSv2 required, hop limit 1, so pods cannot reach the node's credentials |
With both capacity types allowed, Karpenter takes Spot whenever Spot capacity exists for a type that fits and falls back to on-demand. The vCPU ceiling is the bill's upper bound: at the limit Karpenter stops launching and the excess pods stay Pending, so a runaway workload cannot scale without end. Each row is an @optional: value; the ceiling is set per stage in the stage stack file.
Subnets and security groups are selected by id, not by discovery tag: the subnets are the landing zone's private subnets from Parameter Store at /acme/usw2/dev/vpc/private_subnet_ids, and the security group is the cluster's node security group. No discovery tag is written to anything the landing zone owns.
Why application pods land here
The managed node group is tainted CriticalAddonsOnly=true:NoSchedule and carries only the system components, each of which sets the matching toleration. The application chart sets no toleration, no node selector and no affinity, so its pods cannot schedule onto the baseline and Karpenter is the only capacity open to them. The NodePool deliberately applies no taint of its own, which keeps that door open. Cluster Autoscaler sizes the baseline underneath, and the two never contend because each touches only the nodes it owns.
An apply creates no instances. The first node appears when the first application pod does.
Terms you will see
| Term | Meaning |
|---|---|
| NodePool | The object listing what Karpenter may launch and how much of it. |
| EC2NodeClass | The object describing the AMI, role, subnets and security groups of a node. |
| Consolidation | Removing or repacking under-used nodes to cut cost. |
| Spot | Spare EC2 capacity at a discount that AWS can reclaim. |
| Interruption queue | The SQS queue carrying reclaim notices, so the node is drained in time. |
Where to read more
- Cluster Autoscaler for the node group beneath it
- EKS for the cluster and its baseline node group
- Kubernetes for the workloads that ask for this capacity