Learn Karpenter - Setup & Installation
Episode 3 of 23

Learn Karpenter - Setup & Installation

The first hands-on episode: create an EKS cluster, install the CRDs and Karpenter controller via Helm, build an IAM role on the least-privilege principle, then verify the installation with minimal NodePool and EC2NodeClass examples.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 2 you understood Karpenter's architecture: a controller that detects Unschedulable pods, selects a NodePool, then launches nodes via the cloud provider. Now it's time for real practice. This episode guides you from zero — creating an EKS cluster, installing the CRDs and Karpenter controller via Helm, building a minimal IAM role, all the way to verification with your first NodePool and EC2NodeClass examples.

Make sure the tools from episode 0 are installed and your AWS credentials are valid. Let's start with cluster creation.

Creating an EKS Cluster

Karpenter 1.14.0 supports Kubernetes 1.29 to 1.36. Create a cluster with one managed nodegroup that acts as a vended nodegroup — the term for eksctl's built-in nodegroup that delivers a usable cluster before Karpenter takes over. This nodegroup is intentionally kept small and only runs system pods.

Create EKS cluster with eksctl
eksctl create cluster \
  --name learn-karpenter \
  --version 1.32 \
  --region ap-southeast-1 \
  --nodegroup-name system \
  --node-type m5.large \
  --nodes 1 \
  --nodes-min 1 \
  --nodes-max 1 \
  --managed

The process takes 10-15 minutes. Once finished, make sure the cluster is healthy:

Verify cluster
kubectl cluster-info
kubectl get nodes

Building an IAM Role with a Minimal Policy

Karpenter calls the AWS API to launch and terminate instances. The IAM role for the controller is created on the least-privilege principle and attached via IRSA (IAM Roles for Service Accounts).

Minimal policy for the controller
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:RunInstances",
        "ec2:CreateFleet",
        "ec2:TerminateInstances",
        "ec2:DescribeInstances",
        "ec2:DescribeSubnets",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeInstanceTypes"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": ["iam:PassRole"],
      "Resource": "arn:aws:iam::ACCOUNT_ID:role/KarpenterNodeRole-*"
    }
  ]
}

Create the policy via the aws CLI, then attach it to the service account:

Create policy and IRSA
aws iam create-policy \
  --policy-name KarpenterControllerPolicy \
  --policy-document file://policy.json
 
eksctl create iamserviceaccount \
  --cluster learn-karpenter \
  --namespace karpenter \
  --name karpenter \
  --role-name karpenter-irsa \
  --attach-policy-arn arn:aws:iam::ACCOUNT_ID:policy/KarpenterControllerPolicy \
  --approve

Warning

Make sure the policy.json file uses the correct account number, and never put AWS access keys inside the cluster. IRSA ensures credentials are managed by IAM, not stored as secrets in Kubernetes.

Nodes launched by Karpenter also need an instance profile with a role to communicate with EKS — this role is usually named KarpenterNodeRole-... . In episode 5 we'll see how EC2NodeClass points to this instance profile via the role field.

Installing the Karpenter CRDs and Controller

The karpenter Helm chart installs the controller along with the NodePool, NodeClaim, and EC2NodeClass CRDs.

Install Karpenter via Helm
helm upgrade --install karpenter oci://ghcr.io/aws/karpenter/charts/karpenter \
  --namespace karpenter \
  --create-namespace \
  --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::ACCOUNT_ID:role/karpenter-irsa \
  --set controller.resources.requests.cpu=1 \
  --set controller.resources.requests.memory=1Gi \
  --version 1.14.0

Verify the controller and CRDs:

Verify installation
kubectl get pods -n karpenter
kubectl get crd nodepools.karpenter.sh
kubectl get crd nodeclaims.karpenter.sh
kubectl get crd ec2nodeclasses.karpenter.k8s.aws

A healthy controller is marked by pods in Running status. To confirm the version, check the image section on the deployment kubectl get deployment -n karpenter karpenter -o yaml — it should be tagged 1.14.0.

Setting Up Tag Discovery for Subnet and Security Group

Before creating Karpenter resources, make sure the subnet and security group can be "discovered" by Karpenter. The most common method is tag discovery:

Tag subnet and security group
aws ec2 create-tags \
  --resources SUBNET_ID SECURITY_GROUP_ID \
  --tags Key=karpenter.sh/discovery,Value=learn-karpenter

Replace SUBNET_ID and SECURITY_GROUP_ID with the values in your cluster. This is how Karpenter learns which subnets and security groups it's allowed to use — we'll study the selector details in episode 5.

Minimal NodePool and EC2NodeClass Examples

Now define your first two resources. The EC2NodeClass first, because the NodePool points to it.

apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: default
spec:
  amiFamily: AL2
  role: "KarpenterNodeRole-eks-learn-karpenter"
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: learn-karpenter
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: learn-karpenter

Apply both:

Apply NodePool and EC2NodeClass
kubectl apply -f ec2nodeclass.yaml
kubectl apply -f nodepool.yaml
kubectl get nodepools

Notice the YAML structure: the NodePool points to the EC2NodeClass via nodeClassRef, while the capacity-type requirement is set to on-demand. We'll dissect the full NodePool structure in episode 4.

Verifying with a Test Workload

Now prove that Karpenter works. Deploy a workload that doesn't fit on the system nodegroup, then observe:

Deploy and observe NodeClaim
kubectl create deployment nginx --image=nginx --replicas=3
kubectl get pods -o wide
kubectl get nodeclaims

If everything is correct, Karpenter will create a NodeClaim, launch a new instance, and the nginx pods will be scheduled onto that node. The label karpenter.sh/nodepool=default will appear on the new node — check with kubectl get nodes -l karpenter.sh/nodepool=default -o wide.

Important

If the pods stay Pending and no NodeClaim appears, the first debug step: check the controller logs with kubectl logs -n karpenter deployment/karpenter and make sure the subnet or security group tag discovery is correct. These two are the most common causes of provisioning failures on AWS.

Closing

Episode 3 guided you from zero until Karpenter actually provisions nodes. You created an EKS cluster, built a minimal IAM role, installed the CRDs and controller via Helm, set up tag discovery, then applied your first NodePool and EC2NodeClass, which were immediately tested with an nginx deployment.

Key takeaways:

  • Vended nodegroup — one system nodegroup is enough to deliver the cluster before Karpenter takes over.
  • Least privilege — a minimal IAM role via IRSA, never storing access keys in the cluster.
  • Tag discovery — subnets and security groups must be tagged so Karpenter can find them.
  • Two key resources — the NodePool points to the EC2NodeClass via nodeClassRef, and capacity-type is set in requirements.
  • Verification — nodeclaims appear and the new node carries the karpenter.sh/nodepool label.

In episode 4 we break down NodePool and scheduling: the spec.template structure, requirements, disruption, weight, and how Karpenter matches pods with pools — including the karpenter.sh labels and annotations attached to nodes. See you in the next episode!

Learn Karpenter - Setup & Installation | Learn Karpenter