{"id":80779,"date":"2026-09-01T10:13:34","date_gmt":"2026-09-01T04:43:34","guid":{"rendered":"https:\/\/www.tothenew.com\/blog\/?p=80779"},"modified":"2026-09-01T15:53:03","modified_gmt":"2026-09-01T10:23:03","slug":"migrating-from-eks-managed-node-groups-to-karpenter-a-practical-guide-to-smarter-autoscaling","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/migrating-from-eks-managed-node-groups-to-karpenter-a-practical-guide-to-smarter-autoscaling\/","title":{"rendered":"Migrating from EKS Managed Node Groups to Karpenter: A Practical Guide to Smarter Autoscaling"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>Running Kubernetes workloads on Amazon EKS is easy, but running them in a cost-effective way is a different challenge. Many organizations start their Amazon EKS clusters with Managed Node Groups based on Auto Scaling Groups (ASGs). This is a reliable approach but often results in over-provisioning of infrastructure as worker nodes are kept running even when workloads are minimal.<br \/>\nIn one of our production projects, we have switched from the traditional EKS Managed Node Groups to Karpenter, Kubernetes&#8217; next generation node provisioning solution. The main objective was to optimize the AWS infrastructure costs, without affecting application availability and improving efficiency of scaling.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\" wp-image-80819\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/07\/karpenterimg-300x150.png\" alt=\"krpntr\" width=\"380\" height=\"190\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/07\/karpenterimg-300x150.png 300w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/karpenterimg-1024x512.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/karpenterimg-768x384.png 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/karpenterimg-624x312.png 624w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/karpenterimg.png 1280w\" sizes=\"(max-width: 380px) 100vw, 380px\" \/><\/p>\n<p>In this blog I will explain the:<\/p>\n<ul>\n<li>Why did we switch from ASG-based scaling to Karpenter?<\/li>\n<li>How Karpenter works<\/li>\n<li>Implementation, step by step<\/li>\n<li>Example NodePool and EC2NodeClass setup<\/li>\n<li>Deployment instructions<\/li>\n<li>Cost optimization best practices<\/li>\n<li>How to stop an environment using Karpenter<\/li>\n<\/ul>\n<h2><\/h2>\n<h3>Why did we switch from ASG-based scaling to Karpenter?<\/h3>\n<p>When you use the EKS Node Groups the worker nodes are set up using EC2 Auto Scaling Groups. The Cluster Autoscaler is able to increase or decrease the number of nodes. However it does this one instance at a time. That often means there are some nodes just sitting there doing nothing. The Cluster Autoscaler and EKS Node Groups have this issue with capacity.<br \/>\nSome challenges we saw:<\/p>\n<ul>\n<li>For long durations of time, nodes were underutilized.<\/li>\n<li>It took a little longer to decide on scaling.<\/li>\n<li>Less flexibility with fixed instance types.<\/li>\n<li>EC2 continued to run at low workloads leading to higher infrastructure costs.<\/li>\n<\/ul>\n<p>Karpenter solves these problems by provisioning nodes on demand, when Kubernetes needs them.<br \/>\nInstead of scaling an Auto Scaling Group on pending pods, Karpenter dynamically launches the most suitable EC2 instance.<\/p>\n<h3><\/h3>\n<h3>Solution Architecture<\/h3>\n<p>In this migration, ASG-based workload scaling was replaced by Karpenter.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\" wp-image-80791\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/07\/karpenter3-300x164.png\" alt=\"krpent3\" width=\"560\" height=\"305\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/07\/karpenter3-300x164.png 300w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/karpenter3-1024x559.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/karpenter3-768x419.png 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/karpenter3-624x340.png 624w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/karpenter3.png 1408w\" sizes=\"(max-width: 560px) 100vw, 560px\" \/><\/p>\n<p>This means that Kubernetes can provision infrastructure on demand, rather than having nodes running permanently.<\/p>\n<h3><\/h3>\n<h3>Prerequisites<\/h3>\n<p>Before installing karpenter, ensure that these followings are available:<\/p>\n<ul>\n<li>Amazon EKS Cluster<\/li>\n<li>Existing Managed Node Group (for system workloads)<\/li>\n<li>IAM Role for Karpenter Controller<\/li>\n<li>OIDC Provider enabled<\/li>\n<li>AWS SQS interruption queue<\/li>\n<li>AWS VPC subnets and Security Groups tagged for Karpenter discovery<\/li>\n<li>Helm installed<\/li>\n<li>kubectl configured<\/li>\n<\/ul>\n<h3><\/h3>\n<h3>Step 1 \u2013 Karpenter Installation<\/h3>\n<p>Deploy Karpenter with Helm.<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">helm upgrade --install karpenter oci:\/\/public.ecr.aws\/karpenter\/karpenter \\\r\n  --namespace kube-system \\\r\n  --create-namespace<\/pre>\n<p>Check installation.<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">kubectl get pods -n kube-system<\/pre>\n<p>You should see two running Karpenter controller pods.<\/p>\n<h3>Step 2 \u2013 Deploy the Karpenter Controller<\/h3>\n<p>In our deployment we use:<\/p>\n<ul>\n<li>IAM Role through ServiceAccount<\/li>\n<li>Webhook enabled<\/li>\n<li>Two controller replicas<\/li>\n<li>Metrics endpoint<\/li>\n<li>Health probes<\/li>\n<li>High Availability<\/li>\n<li>Anti-affinity configuration<\/li>\n<li>Environment tolerations<\/li>\n<\/ul>\n<p>Example (excerpt of our deployment):<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">apiVersion: apps\/v1\r\nkind: Deployment\r\n\r\nmetadata:\r\n  name: karpenter\r\n\r\nspec:\r\n  replicas: 2\r\n\r\ntemplate:\r\n  spec:\r\n    serviceAccountName: karpenter\r\n\r\n    containers:\r\n    - name: controller\r\n      image: &lt;your-ecr&gt;\/karpenter\/controller:1.6.2\r\n\r\n      env:\r\n      - name: CLUSTER_NAME\r\n        value: &lt;cluster-name&gt;\r\n\r\n      - name: INTERRUPTION_QUEUE\r\n        value: &lt;SQS-Queue-Name&gt;<\/pre>\n<p>Also included in our full deployment:<\/p>\n<ul>\n<li>ServiceAccount<\/li>\n<li>RBAC<\/li>\n<li>ClusterRoles<\/li>\n<li>RoleBindings<\/li>\n<li>PodDisruptionBudget<\/li>\n<li>Services<\/li>\n<li>Webhook configuration<\/li>\n<li>Readiness and Liveness probes<\/li>\n<\/ul>\n<p>These are included in the complete karpenter.yaml manifest.<br \/>\nDeploy it using:<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">kubectl apply -f karpenter.yaml<\/pre>\n<h3>Step 3 \u2013 Create a NodePool<\/h3>\n<p>A NodePool describes how Karpenter will provision nodes.<br \/>\nOur configuration provisions are:<\/p>\n<ul>\n<li>ARM64 instances<\/li>\n<li>Linux operating system<\/li>\n<li>On-Demand capacity<\/li>\n<li>t4g.medium instances<\/li>\n<li>environment labeling<\/li>\n<li>Environment taints<\/li>\n<li>Automatic consolidation<\/li>\n<\/ul>\n<p>Sample NodePool:<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">apiVersion: karpenter.sh\/v1\r\nkind: NodePool\r\n\r\nmetadata:\r\n  name: &lt;nodepool-name&gt;\r\n\r\nspec:\r\n  template:\r\n\r\n    metadata:\r\n      labels:\r\n        ENV: &lt;environment-name&gt;\r\n\r\n    spec:\r\n\r\n      taints:\r\n      - key: Environment\r\n        value: &lt;environment-name&gt;\r\n        effect: NoExecute\r\n\r\n      requirements:\r\n      - key: kubernetes.io\/arch\r\n        operator: In\r\n        values:\r\n        - arm64\r\n\r\n      - key: karpenter.k8s.aws\/instance-family\r\n        operator: In\r\n        values:\r\n        - t4g\r\n\r\n      - key: karpenter.k8s.aws\/instance-size\r\n        operator: In\r\n        values:\r\n        - medium<\/pre>\n<p>Deploy:<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">kubectl apply -f nodepool.yaml<\/pre>\n<h3>Step 4 \u2013 Configure the EC2NodeClass<\/h3>\n<p>EC2NodeClass controls how to launch EC2 instances.<br \/>\nWe have the following set up:<\/p>\n<ul>\n<li>Amazon Linux 2023 AMI<\/li>\n<li>IAM role for node<\/li>\n<li>EBS disk encrypted with GP3<\/li>\n<li>Tagged subnets<\/li>\n<li>Tag-based security groups<\/li>\n<li>Custom AMI Environment Tags<\/li>\n<\/ul>\n<p>Sample configuration:<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">apiVersion: karpenter.k8s.aws\/v1\r\nkind: EC2NodeClass\r\n\r\nmetadata:\r\n  name: &lt;ec2nodeclass-name&gt;\r\n\r\nspec:\r\n\r\n  amiFamily: AL2023\r\n\r\n  role: &lt;eks-node-role&gt;\r\n\r\n  blockDeviceMappings:\r\n\r\n  - deviceName: \/dev\/xvda\r\n\r\n    ebs:\r\n      volumeSize: 50Gi\r\n      volumeType: gp3\r\n      encrypted: true\r\n\r\n  subnetSelectorTerms:\r\n  - tags:\r\n      karpenter.sh\/discovery: &lt;environment-tag&gt;\r\n\r\n  securityGroupSelectorTerms:\r\n  - tags:\r\n      karpenter.sh\/discovery: &lt;environment-tag&gt;<\/pre>\n<h3>Step 5 \u2013 Verify Karpenter<\/h3>\n<p>To check NodePools:<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">kubectl get nodepool<\/pre>\n<p>To check EC2NodeClass:<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">kubectl get ec2nodeclass<\/pre>\n<p>To check NodeClaims:<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">kubectl get nodeclaims<\/pre>\n<p>To watch nodes getting created:<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">kubectl get nodes -w<\/pre>\n<p>Create a deployment with a higher replica count and watch Karpenter automatically provision new nodes.<\/p>\n<p>This is the output for Nodepool and EC2NodeClass:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\" wp-image-80816\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2026\/07\/Ec2nodepool-Nodeclass-300x100.png\" alt=\"Ec2nodepool\" width=\"393\" height=\"131\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2026\/07\/Ec2nodepool-Nodeclass-300x100.png 300w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/Ec2nodepool-Nodeclass-1024x341.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/Ec2nodepool-Nodeclass-768x256.png 768w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/Ec2nodepool-Nodeclass-1536x512.png 1536w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/Ec2nodepool-Nodeclass-2048x683.png 2048w, \/blog\/wp-ttn-blog\/uploads\/2026\/07\/Ec2nodepool-Nodeclass-624x208.png 624w\" sizes=\"(max-width: 393px) 100vw, 393px\" \/><\/p>\n<h3>Step 6 \u2013 Enable Consolidation<\/h3>\n<p>One of the biggest benefits of Karpenter is automatic consolidation.<br \/>\nOur NodePool includes:<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">disruption:\r\n\r\n  consolidationPolicy: WhenEmptyOrUnderutilized\r\n\r\n  consolidateAfter: 10m<\/pre>\n<p>This means:<\/p>\n<ul>\n<li>Empty nodes are automatically terminated.<\/li>\n<li>Consolidating under-utilized nodes.<\/li>\n<li>Idle EC2 costs are greatly reduced.<\/li>\n<li>Kubernetes workloads runs without any manual intervention.<\/li>\n<\/ul>\n<h3><\/h3>\n<h3>Stopping a Karpenter Environment<\/h3>\n<p>Unlike traditional ASGs where you manually scale the number of nodes, Karpenter automatically scales node scaling with workload demand.<br \/>\nTo completely shut down an environment running on karpenter, first scale all application workloads to zero replicas so that no pods require worker nodes.<br \/>\nFor example:<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">kubectl scale deployment &lt;deployment-name&gt; \\\r\n--replicas=0 \\\r\n-n &lt;namespace&gt;<\/pre>\n<p>If using StatefulSets:<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">kubectl scale statefulset &lt;statefulset-name&gt; \\\r\n--replicas=0 \\\r\n-n &lt;namespace&gt;<\/pre>\n<p>If your workloads are managed by helm:<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">helm upgrade &lt;release-name&gt; \\\r\n&lt;chart-name&gt; \\\r\n--set replicaCount=0<\/pre>\n<p>Upon deletion of all application pods, Karpenter will detect the nodes are empty and will automatically terminate them according to the configured consolidation policy.<br \/>\nYou can also set the NodePool CPU limit to 0 if your NodePool uses CPU limits, so that when the environment is stopped, Karpenter does not provision any other nodes.<br \/>\nExample:<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">limits:\r\n  cpu: 0<\/pre>\n<p>Apply the updated configuration:<\/p>\n<pre style=\"background-color: #1e1e1e; color: #e0e0e0; padding: 20px; margin: 12px 0; font-family: monospace; font-size: 14px; line-height: 1.6; border-radius: 10px; border: none;\">kubectl apply -f nodepool.yaml<\/pre>\n<p>This way no new nodes are created until the CPU limit is increased again.<\/p>\n<h3><\/h3>\n<h3>Benefits Achieved<\/h3>\n<p>After we moved to Karpenter we saw a few operational improvements:<\/p>\n<ul>\n<li>Faster node provisioning<\/li>\n<li>Better pod scheduling<\/li>\n<li>Removing permanently idle worker nodes<\/li>\n<li>Automatically deleting idle nodes<\/li>\n<li>Flexibility on instance provisioning<\/li>\n<li>Easier infrastructure management<\/li>\n<li>Amazon EC2 cost reductions<\/li>\n<\/ul>\n<p>Instead of fixed-capacity Auto Scaling Groups, Karpenter constantly provisions infrastructure according to the actual needs of the workload.<\/p>\n<h3><\/h3>\n<h3>Best Practices<\/h3>\n<p>When using Karpenter, consider the following suggestions:<\/p>\n<ul>\n<li>Have one small managed node group for system pods.<\/li>\n<li>Apply taints and labels to segregate environments.<\/li>\n<li>Enable Consolidate for maximum savings.<\/li>\n<li>Use GP3 encrypted type for the EBS volumes.<\/li>\n<li>Tag the security group and subnets.<\/li>\n<li>Regularly monitor the karpenter logs and NodeClaims.<\/li>\n<li>Control scale of infrastructure by setting sensible CPU limits on NodePools.<\/li>\n<li>Test the scaling behaviour in non-prod environments then production.<\/li>\n<\/ul>\n<h2><\/h2>\n<h2>Conclusion<\/h2>\n<p>The transitioning from EKS Managed Node Groups with Auto Scaling Groups to karpenter has significantly improved the efficiency of our Kubernetes infrastructure. Karpenter eliminates the need for always-on worker nodes, dynamically provisioning the right compute resources to meet the needs of your pods and automatically scaling down unused capacity when workloads decrease.<br \/>\nWe built a more responsive Kubernetes platform and reduced infrastructure costs and operational overhead by combining NodePools, EC2NodeClasses, clever consolidation and workload-aware provisioning.<br \/>\nIf you are running Amazon EKS with variable workloads, Karpenter is a compelling alternative to traditional ASG based scaling. Begin in a non-production environment to validate your NodePool configurations and observe the provisioning behavior and then gradually rollout the implementation across your clusters. If you&#8217;re already using Karpenter or are considering a migration, please share your experience or questions in the comments \u2013 I&#8217;d love to hear how you&#8217;re optimizing your Kubernetes infrastructure.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Running Kubernetes workloads on Amazon EKS is easy, but running them in a cost-effective way is a different challenge. Many organizations start their Amazon EKS clusters with Managed Node Groups based on Auto Scaling Groups (ASGs). This is a reliable approach but often results in over-provisioning of infrastructure as worker nodes are kept running [&hellip;]<\/p>\n","protected":false},"author":2249,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":13},"categories":[2348],"tags":[1217,248,8748,1586,5301,6259,5303,8747,3965,8750,8749],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/80779"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/2249"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=80779"}],"version-history":[{"count":26,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/80779\/revisions"}],"predecessor-version":[{"id":82002,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/80779\/revisions\/82002"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=80779"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=80779"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=80779"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}