← Back to list

Smart Auto-Scaling for Amazon EKS: Meet Karpenter

If you're running workloads on Kubernetes, you've probably faced the constant balancing act between over-provisioning (wasting money) and…

Seçil Nur Sucu in VNGRS · 2025-09-11 08:27 · 3 claps · 9.6 min read
#karpenter #aws-eks #scaling-with-karpenter #amazon-eks
Open on Medium ↗
Wiki topics: ECO · Economy · General ☁️ · DevOps & Cloud 🏃 · Running & Endurance

Smart Auto-Scaling for Amazon EKS: Meet Karpenter

If you're running workloads on Kubernetes, you've probably faced the constant balancing act between over-provisioning (wasting money) and under-provisioning (hurting performance). This is where Karpenter comes in — an intelligent, high-performance autoscaler purpose-built for Kubernetes.

What is Karpenter?

Karpenter is an open-source autoscaler supported by Amazon Web Services (AWS). While it's optimized for Amazon EKS (Elastic Kubernetes Service), it's also compatible with other cloud providers and even on-prem environments.

What Does Karpenter Do?

  • Monitors Pod Requests: It watches for unscheduled pods that couldn’t be placed due to resource shortages.
  • Creates New Nodes: It quickly launches the optimal EC2 instances based on your pods' needs.
  • Terminates Unused Nodes: Frees up cost by automatically shutting down underutilized or idle nodes.

Karpenter steps in when "unschedulable" pods arise in a Kubernetes cluster because they cannot fit onto existing nodes.

Karpenter analyzes the resources required by these unschedulable pods.

Based on these needs, it automatically provisions new nodes of the appropriate size and type "just-in-time" to host those specific pods.

This new capacity, combined with the existing capacity, forms an "optimized capacity" where the pods can run.

Consequently, Karpenter dynamically and efficiently manages cluster capacity by preventing unnecessary resources and ensuring pods run quickly.

Key Benefits

  • Fast Response Time: Instantly reacts to workload changes.
  • Flexibility: Doesn’t rely on pre-defined node groups — scales precisely based on pod requirements.
  • Cost Efficiency: Reduces unnecessary spending on idle resources.
  • High Availability: Ensures your applications always have the resources they need.

Karpenter vs. Traditional Cluster Autoscaler

Karpenter stands out by offering pod-level precision scaling, meaning you no longer need to guess node group sizes or types. It provisions the right compute for each workload automatically.

The following table provides a side-by-side comparison between Karpenter and the traditional Cluster Autoscaler, highlighting key differences.

Step-by-Step: Deploying Karpenter on Amazon EKS

Here’s a quick-start guide to create a demo EKS cluster and deploy Karpenter using eksctl, helm, and a few commands. The entire process takes less than 1 hour and costs less than $0.25 if you clean up afterward.

Prerequisites

The following tools must be installed:

  • AWS CLI
  • kubectl (Kubernetes CLI)
  • eksctl (v0.202.0 or newer)
  • helm

Then the AWS CLI must be configured using the following command:

aws configure

To verify that it has been configured correctly, the following command should be executed:

aws sts get-caller-identity

Defining Environment Variables

export KARPENTER_NAMESPACE="kube-system"
export KARPENTER_VERSION="1.5.0"
export K8S_VERSION="1.32"
export AWS_PARTITION="aws"
export CLUSTER_NAME="karpenter-demo"
export AWS_DEFAULT_REGION="eu-west-1"
export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
export TEMPOUT="$(mktemp)"
export ALIAS_VERSION="$(aws ssm get-parameter --name "/aws/service/eks/optimized-ami/${K8S_VERSION}/amazon-linux-2023/x86_64/standard/recommended/image_id" --query Parameter.Value | xargs aws ec2 describe-images --query 'Images[0].Name' --image-ids | sed -r 's/^.*(v[[:digit:]]+).*$/\1/')"
echo "${KARPENTER_NAMESPACE}" "${KARPENTER_VERSION}" "${K8S_VERSION}" "${CLUSTER_NAME}" "${AWS_DEFAULT_REGION}" "${AWS_ACCOUNT_ID}" "${TEMPOUT}" "${ALIAS_VERSION}"

Create a Basic Cluster with eksctl

Download and deploy the CloudFormation template

To deploy the necessary AWS infrastructure for Karpenter, run the following command,this command performs the following actions:

  • Downloads the CloudFormation template for Karpenter from the specified version.
  • Saves the template to a temporary file defined by the TEMPOUT variable.
  • Deploys the template using the AWS CLI, creating or updating a CloudFormation stack named Karpenter-${CLUSTER_NAME}.
  • Grants the stack permission to create IAM resources (CAPABILITY_NAMED_IAM).
  • Passes the cluster name as a parameter to the template via --parameter-overrides.
curl -fsSL https://raw.githubusercontent.com/aws/karpenter-provider-aws/v"${KARPENTER_VERSION}"/website/content/en/preview/getting-started/getting-started-with-karpenter/cloudformation.yaml  > "${TEMPOUT}" \
&& aws cloudformation deploy \
--stack-name "Karpenter-${CLUSTER_NAME}" \
--template-file "${TEMPOUT}" \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides "ClusterName=${CLUSTER_NAME}"

This can be verified by navigating to the AWS CloudFormation Console, selecting the appropriate region, and checking the Stacks section.

Create EKS Cluster via Managed Node Groups

An Amazon EKS cluster can be provisioned with support for Karpenter using a declarative eksctl configuration. The required IAM roles, OIDC integration, and pod identity associations are defined within the configuration file. A managed node group is also included, along with the necessary EKS add-ons such as the pod identity agent. With this setup, the cluster is prepared for dynamic node provisioning by Karpenter.

To create an EKS cluster configured for Karpenter, use the following eksctl configuration:

eksctl create cluster -f - <<EOF
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: ${CLUSTER_NAME}
  region: ${AWS_DEFAULT_REGION}
  version: "${K8S_VERSION}"
  tags:
    karpenter.sh/discovery: ${CLUSTER_NAME}

iam:
  withOIDC: true
  podIdentityAssociations:
  - namespace: "${KARPENTER_NAMESPACE}"
    serviceAccountName: karpenter
    roleName: ${CLUSTER_NAME}-karpenter
    permissionPolicyARNs:
    - arn:${AWS_PARTITION}:iam::${AWS_ACCOUNT_ID}:policy/KarpenterControllerPolicy-${CLUSTER_NAME}

iamIdentityMappings:
- arn: "arn:${AWS_PARTITION}:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${CLUSTER_NAME}"
  username: system:node:{{EC2PrivateDNSName}}
  groups:
  - system:bootstrappers
  - system:nodes
  ## If you intend to run Windows workloads, the kube-proxy group should be specified.
  # For more information, see https://github.com/aws/karpenter/issues/5099.
  # - eks:kube-proxy-windows

managedNodeGroups:
- instanceType: m5.large
  amiFamily: AmazonLinux2023
  name: ${CLUSTER_NAME}-ng
  desiredCapacity: 2
  minSize: 1
  maxSize: 10

addons:
- name: eks-pod-identity-agent
EOF

The following commands set and print out the values of:

CLUSTER_ENDPOINT – the endpoint URL of your EKS (Elastic Kubernetes Service) cluster.

KARPENTER_IAM_ROLE_ARN – the ARN of the IAM role.

export CLUSTER_ENDPOINT="$(aws eks describe-cluster --name "${CLUSTER_NAME}" --query "cluster.endpoint" --output text)"
export KARPENTER_IAM_ROLE_ARN="arn:${AWS_PARTITION}:iam::${AWS_ACCOUNT_ID}:role/${CLUSTER_NAME}-karpenter"

echo "${CLUSTER_ENDPOINT} ${KARPENTER_IAM_ROLE_ARN}"

The EKS cluster status can be confirmed by visiting the Amazon EKS Console, selecting the relevant region, and reviewing the Clusters section.

Note for the curious

How eksctl sets up an EKS cluster using CloudFormation

The eksctl CLI reads the YAML configuration file provided by the user.

Based on this configuration, it generates multiple CloudFormation templates in the background.

These templates define the EKS control plane, IAM roles, VPC (if not already created), node groups, and add-ons.

eksctl then automatically creates and deploys these CloudFormation stacks to AWS account.

The stacks can be seen in the AWS CloudFormation Console.

If any step fails, eksctl will log the error and show which part of the infrastructure failed to deploy.

To allow Karpenter to launch Spot Instances, a service-linked IAM role for spot.amazonaws.com must be created. This can be done by running the following command prior to deploying the Karpenter controller, in order to avoid the ServiceLinkedRoleCreationNotPermitted error.

aws iam create-service-linked-role --aws-service-name spot.amazonaws.com || true

# If the role has already been successfully created, you will see:
# An error occurred (InvalidInput) when calling the CreateServiceLinkedRole operation: Service role name AWSServiceRoleForEC2Spot has been taken in this account, please try a different suffix.

Windows Support Notice

In order to run Windows workloads, Windows support should be enabled in your EKS Cluster.

Install Karpenter via Helm

To remove cached credentials for the public ECR Helm registry, the following command can be used.

This is useful when switching accounts, automating logout in scripts, or ensuring a clean state before or after Helm operations involving OCI registries.

helm registry logout public.ecr.aws

The following commands are used to install Karpenter using Helm.

helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter --version "${KARPENTER_VERSION}" --namespace "${KARPENTER_NAMESPACE}" --create-namespace \
  --set "settings.clusterName=${CLUSTER_NAME}" \
  --set "settings.interruptionQueue=${CLUSTER_NAME}" \
  --set controller.resources.requests.cpu=1 \
  --set controller.resources.requests.memory=1Gi \
  --set controller.resources.limits.cpu=1 \
  --set controller.resources.limits.memory=1Gi \
  --wait

As the OCI Helm chart is signed by Cosign as part of the release process you can verify the chart before installing it by running the following command.

cosign verify public.ecr.aws/karpenter/karpenter:1.5.2 \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  --certificate-identity-regexp='https://github\.com/aws/karpenter-provider-aws/\.github/workflows/release\.yaml@.+' \
  --certificate-github-workflow-repository=aws/karpenter-provider-aws \
  --certificate-github-workflow-name=Release \
  --certificate-github-workflow-ref=refs/tags/v1.5.2 \
  --annotations version=1.5.2

If cosign is not installed, it can be installed using the following command:

brew install cosign

Create a NodePool

In Karpenter, a NodePool is a core resource that defines how nodes should be provisioned. It replaces the need for rigid node groups (like in traditional cluster autoscalers) and enables more dynamic, responsive, and cost-effective scaling.

Without a NodePool, Karpenter wouldn’t know:

  • What kind of instances are suitable for your workloads
  • Where (which VPC/subnet) to launch them
  • Whether to use on-demand or spot capacity
  • Which instance types to avoid or prefer
  • How much total capacity it’s allowed to scale to

In short, NodePools define the provisioning boundaries and preferences, allowing Karpenter to make intelligent, automated scaling decisions.

To define a NodePool and an EC2NodeClass, run the following command:

cat <<EOF | envsubst | kubectl apply -f -
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64"]
        - key: kubernetes.io/os
          operator: In
          values: ["linux"]
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m", "r"]
        - key: karpenter.k8s.aws/instance-generation
          operator: Gt
          values: ["2"]
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
      expireAfter: 720h # 30 * 24h = 720h
  limits:
    cpu: 1000
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 1m
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: default
spec:
  role: "KarpenterNodeRole-${CLUSTER_NAME}" # replace with your cluster name
  amiSelectorTerms:
    - alias: "al2023@${ALIAS_VERSION}"
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${CLUSTER_NAME}" # replace with your cluster name
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: "${CLUSTER_NAME}" # replace with your cluster name
EOF

Testing Auto-Scaling with a Sample Deployment

To test auto-scaling behavior, it is necessary to create and scale a deployment.

Creating a Kubernetes Deployment

The following manifest creates a Kubernetes Deployment named inflate initially configured with zero replicas, enabling manual scaling during auto-scaling tests. The deployment utilizes the pause:3.7 container image from the EKS Distro public registry, primarily to simulate CPU resource consumption without running actual workloads. Each pod requests 1 vCPU, ensuring node provisioning occurs when replicas are scaled up. Security best practices are enforced through the use of a non-root user, specific UID/GID settings, and by disabling privilege escalation. Additionally, the terminationGracePeriodSeconds is set to 0, ensuring pods are terminated immediately during scaling or deletion events.

Apply the Deployment manifest using the following command:

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: inflate
spec:
  replicas: 0
  selector:
    matchLabels:
      app: inflate
  template:
    metadata:
      labels:
        app: inflate
    spec:
      terminationGracePeriodSeconds: 0
      securityContext:
        runAsUser: 1000
        runAsGroup: 3000
        fsGroup: 2000
      containers:
      - name: inflate
        image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
        resources:
          requests:
            cpu: 1
        securityContext:
          allowPrivilegeEscalation: false
EOF

Scaling Workload Up

The following command increases the number of replicas from 0 to 5, causing five pods to be scheduled.

Because each pod requests 1 vCPU, this scaling action creates resource demand that Karpenter can detect.

This approach is commonly used in testing scenarios to verify that nodes are automatically provisioned when workload demands increase.

kubectl scale deployment inflate --replicas 5

To monitor Karpenter in action, use the following command:

kubectl logs -f -n "${KARPENTER_NAMESPACE}" -l app.kubernetes.io/name=karpenter -c controller

After scaling the deployment, the EC2 instances can be observed in the AWS Management Console under the EC2 > Instances section.

In this example, two m5.large instances named karpenter-demo-karpenter-demo-ng-Node are part of the managed node group.

Additionally, a c6a.2xlarge instance named according to its internal DNS indicates that it was provisioned by Karpenter in response to increased resource demand.

All instances show the Running state, confirming that the cluster scaled as expected.

This provides visual confirmation that Karpenter successfully provisioned new compute capacity to meet the workload requirements.

Scaling Workload Down & Observe Node Termination

Now, delete the deployment using the following command:

kubectl delete deployment inflate

After a short period, Karpenter will automatically terminate the now-empty nodes due to consolidation.

Clean Up

To avoid additional charges, remove the demo infrastructure from your AWS account by running the following commands:

helm uninstall karpenter - namespace "${KARPENTER_NAMESPACE}"
aws cloudformation delete-stack - stack-name "Karpenter-${CLUSTER_NAME}"
eksctl delete cluster - name "${CLUSTER_NAME}"

Warnings and Operational Considerations

DNS Policy Notice

Karpenter uses the ClusterFirst pod DNS policy by default. This is the Kubernetes cluster default and this ensures that Karpenter can reach-out to internal Kubernetes services during its lifetime. There may be cases where you do not have the DNS service that you are using on your cluster up-and-running before Karpenter starts up. The most common case of this is you want Karpenter to manage the node capacity where your DNS service pods are running.

If you need Karpenter to manage the DNS service pods’ capacity, this means that DNS won’t be running when Karpenter starts-up. In this case, you will need to set the pod DNS policy to Default with — set dnsPolicy=Default. This will tell Karpenter to use the host’s DNS resolution instead of the internal DNS resolution, ensuring that you don’t have a dependency on the DNS service pods to run. More details on this issue can be found in the following Github issues: #2186 and #4947.

Warning

Karpenter creates a mapping between CloudProvider machines and CustomResources in the cluster for capacity tracking. To ensure this mapping is consistent, Karpenter utilizes the following tag keys:

  • karpenter.sh/managed-by
  • karpenter.sh/nodepool
  • kubernetes.io/cluster/${CLUSTER_NAME}

Because Karpenter takes this dependency, any user that has the ability to Create/Delete these tags on CloudProvider machines will have the ability to orchestrate Karpenter to Create/Delete CloudProvider machines as a side effect. We recommend that you enforce tag-based IAM policies on these tags against any EC2 instance resource (i-*) for any users that might have CreateTags/DeleteTags permissions but should not have RunInstances/TerminateInstances permissions.

Final Thoughts

Karpenter gives you fine-grained control, faster reaction time, and smarter cost optimization — all while simplifying the scaling complexity. If you’re using Amazon EKS, trying out Karpenter is a no-brainer.

Scale smarter. Save more. Ship faster.

References

Karpenter Official Doc

Linkedin/karpenter-aws-eks-how-improve-your-application-soumyadip-chatterjee-rheuc


메타데이터
post_id
577f8a8d3c1a
slug
smart-auto-scaling-for-amazon-eks-meet-karpenter-577f8a8d3c1a
url
https://medium.com/vngrs/smart-auto-scaling-for-amazon-eks-meet-karpenter-577f8a8d3c1a
canonical_url
https://medium.com/vngrs/smart-auto-scaling-for-amazon-eks-meet-karpenter-577f8a8d3c1a
author_url
https://medium.com/@secilns
status
ok
fetched_at
2026-06-09 21:21:26