AWS EKS Node Groups Explained: Managed vs Self-Managed vs Fargate
EKS gives you three ways to run Kubernetes worker compute: managed node groups, self-managed node groups, and Fargate. For most teams, managed node groups are the right default. AWS handles provisioning, AMI patching, and graceful node replacement. Self-managed nodes are for teams that need deep OS-level control. Fargate removes EC2 entirely, but comes with meaningful trade-offs you need to understand before committing.
Managed node groups are right for most production clusters. AWS manages the lifecycle; you control the configuration.
Self-managed node groups are worth the complexity only when you need a custom AMI, a compliance-hardened OS, or specific kubelet flags that managed node groups do not expose.
Fargate is the right pick for ephemeral batch jobs, strict pod-level isolation, or when you want zero EC2 instances. It is not suited for stateful workloads or DaemonSets.
Simple explanation
A Kubernetes cluster has two parts: the control plane, which decides where workloads run, and the worker nodes, which actually run them. In EKS, AWS manages the control plane. The worker nodes are EC2 instances where your containers live, and node groups are how you manage those instances as a unit.
A node group is a named set of EC2 instances that share the same configuration. All instances in a group use the same instance type, the same base AMI, the same IAM role, and the same scaling bounds. When you need more capacity, more instances join the group. When a node becomes unhealthy, it gets replaced without affecting the rest.
Concrete example: A retail platform runs two node groups. The first is api-workers: four m5.xlarge on-demand instances for the customer-facing API that must stay available. The second is job-workers: a Spot node group that scales from 0 to 20 nodes for background order-processing jobs that can tolerate interruption. The Kubernetes scheduler routes pods to the right group using node labels. Both groups share the same EKS control plane.
That separation is the core reason to use multiple node groups. Different workloads have different cost, availability, and resource requirements, and grouping nodes lets you manage those differences cleanly.
Think of a node group like a fleet of identical company vehicles. Every car in the fleet is the same model (instance type), runs the same software loadout (AMI), and comes from the same leasing arrangement (IAM role). When you need more capacity, the fleet grows. When one breaks down, it gets replaced with an identical unit. The fleet manager (EKS) decides which driver (pod) gets which car. You manage the fleet, not individual vehicles.
What node groups are in EKS
Understanding the full picture requires knowing how the pieces relate to each other.
The EKS control plane is a managed Kubernetes API server. It stores cluster state, makes scheduling decisions, and exposes the Kubernetes API. You do not run or patch this. AWS does. Creating Your First EKS Cluster covers what happens at cluster creation time.
Worker nodes are EC2 instances that register with the control plane. Each runs kubelet (the agent that receives scheduling instructions and manages containers), containerd (the container runtime), and the VPC CNI plugin (which gives each pod a real VPC IP address from your subnet). The EKS Networking Model covers how pod networking works in detail.
A node group is the administrative unit that manages a set of worker nodes as a single entity. It maps to an EC2 Auto Scaling Group (ASG) behind the scenes. When you adjust the node group’s desired capacity, you are adjusting the ASG’s desired count. When AWS replaces a node during an AMI update, the ASG handles the EC2 lifecycle.
Pods are the smallest deployable unit in Kubernetes, typically one or a few containers with shared networking and storage. The Kubernetes scheduler assigns each pod to a node based on available resources, node labels, taints, and tolerations.
When a pod cannot be scheduled because no node has enough free CPU or memory, it enters Pending state. That pending status is the signal that tells Cluster Autoscaler or Karpenter to add more nodes.
How it works
Here is the step-by-step flow from node group creation to pods running at scale.
1. You create a node group. EKS creates an EC2 Auto Scaling Group with a Launch Template specifying the instance type, AMI, IAM role, and user data script. The user data script runs at boot time and registers the instance as a Kubernetes node by calling the EKS API with your cluster’s endpoint and CA certificate.
2. EC2 instances join the cluster.
Once an instance boots and the bootstrap script runs, kubelet starts and registers the node with the Kubernetes API server. Within a few seconds the node appears in kubectl get nodes with Ready status. The node IAM role covers EC2-level permissions like pulling images from ECR. IAM Roles for Service Accounts is a separate mechanism for giving pods their own AWS identity.
3. Pods are scheduled.
When you deploy a workload, the Kubernetes scheduler looks at all Ready nodes, filters out any that do not match the pod’s resource requests, labels, and tolerations, and assigns the pod to the best-fit node. The kubelet on that node pulls the container image and starts the containers.
4. Scaling happens.
The Cluster Autoscaler watches for Pending pods and increases the node group’s desired capacity when pods cannot be scheduled. When nodes are underutilised for a sustained period (default: 10 minutes), it drains and removes them. Karpenter takes a different approach: it provisions EC2 instances directly via the EC2 Fleet API, bypassing ASGs entirely. This is faster and lets Karpenter pick the optimal instance type for each batch of pending workloads. For new clusters, Karpenter is the recommended autoscaling solution.
EKS Auto Mode (launched late 2024) is a newer option where AWS manages the entire compute layer, including node provisioning, patching, and scaling via managed node pools. It removes the need to run Karpenter or Cluster Autoscaler manually. If you are building a new cluster without specific node group requirements, it is worth evaluating. This guide focuses on node groups, which remain the standard for existing clusters and teams that need direct EC2 configuration control.
Managed vs self-managed vs Fargate
| Dimension | Managed Node Group | Self-Managed Node Group | Fargate Profile |
|---|---|---|---|
| Provisioning | AWS creates and manages the ASG and Launch Template | You create the ASG and bootstrap script | AWS provisions per pod, no EC2 to manage |
| AMI / OS patching | AWS provides AMI updates; you trigger the rollout | You build, version, and apply AMIs yourself | Fully managed by AWS |
| Custom AMIs | Supported via Launch Templates (AL2, AL2023, Bottlerocket variants) | Full control over any AMI and bootstrap logic | Not applicable |
| Graceful node drain on update | Automatic | Manual or scripted | N/A |
| Operational overhead | Low | High | Very low |
| Scaling model | EC2 ASG (min/max/desired) | EC2 ASG (you manage) | Per-pod, no ASG |
| Spot support | Yes, multiple instance types supported | Yes, full control via mixed instances policy | No |
| DaemonSet support | Yes | Yes | No |
| Multiple instance types | Yes for Spot; single type for on-demand by default | Fully configurable | Not applicable |
| Best use case | Most production workloads | Compliance-hardened AMIs, custom kernel, advanced OS config | Ephemeral batch jobs, strict pod isolation |
| Main limitation | Less customisation than self-managed | High maintenance burden | No DaemonSets, no Spot, higher per-pod cost at scale |
What this means in practice: Managed node groups cover the vast majority of EKS use cases. The only strong reasons to choose self-managed are custom AMIs with specific OS configurations (FIPS-compliant images, DoD-hardened builds), custom bootstrap scripts, or kubelet flags not exposed through the managed API. Fargate is useful for batch workloads and strict isolation requirements, but its limitations around DaemonSets, Spot, and privileged containers rule it out for many common Kubernetes patterns.
When to use this
When managed node groups are the right default#
Use managed node groups when you want AWS to handle provisioning, AMI updates, and graceful node draining. That covers almost every team. Good fit for:
- API servers, background workers, and microservices running standard container workloads
- Teams without dedicated platform engineering capacity to manage EC2 lifecycle
- Production clusters where rolling AMI updates need to happen without pod disruption
- Any cluster using Cluster Autoscaler or Karpenter, both of which integrate cleanly with managed node groups
Example: A SaaS company with a small DevOps team runs all EKS workloads on managed node groups. They trigger AMI updates monthly, and the rollout runs automatically with zero downtime because pods are drained before each node is replaced.
When self-managed node groups make sense#
Self-managed nodes are appropriate when managed node groups cannot satisfy a specific requirement:
- Custom AMIs — your security team requires a CIS-hardened Amazon Linux image or a FIPS 140-2 compliant build not derivable from the EKS-optimised base
- Custom bootstrap — you need to run scripts before node registration, or set kubelet flags not exposed through the managed API
- Specific kernel versions — some observability or security tooling (eBPF-based agents, for instance) requires a minimum kernel version the standard EKS AMI may not yet ship
- Third-party OS — you need Flatcar Container Linux or another distribution not available as a managed AMI
Be honest about the cost. Self-managed nodes mean you own the patching pipeline, drain automation, and health monitoring. For most teams, that is not a worthwhile trade.
When Fargate is a better fit#
Fargate makes sense when:
- You want to run ephemeral batch jobs without maintaining a persistent node group, so compute appears when needed and disappears when the job finishes
- You need strong pod-level isolation, since each Fargate pod runs on dedicated hardware, which satisfies certain compliance requirements
- You are running small, infrequent workloads and do not want to pay for idle EC2 capacity
- You want to eliminate EC2 management entirely for a subset of workloads while keeping node groups for everything else
Fargate is not suitable for workloads that need DaemonSets (logging agents, security scanners), stateful workloads with complex volume requirements, privileged containers, or steady-state services where on-demand EC2 is cheaper per unit of compute.
Managed vs self-managed node groups
The core trade-off is convenience vs control. Managed node groups give you a well-paved path that handles the hard operational work. Self-managed nodes give you a blank canvas.
Managed node groups are like leasing a car with a full-service plan. You choose the model and how many you want, and the dealer handles servicing, recalls, and replacements. Self-managed node groups are like buying a vehicle outright and doing your own maintenance. You can modify anything, but you also own every breakdown.
Custom AMIs and bootstrap customisation. Managed node groups support custom AMIs via Launch Templates. You can provide your own Amazon Linux 2 or AL2023 AMI as long as it derives from the EKS-optimised base and includes the required EKS components. What you cannot do is run an entirely arbitrary AMI or modify the bootstrap script in arbitrary ways. Self-managed nodes impose no such constraint. If your security team has an approved hardened image not based on the EKS-optimised AMI, or if you need custom pre-registration scripts, self-managed is the only viable path.
Compliance hardening. Some regulated industries require specific OS benchmarks (CIS Level 2, DISA STIG) or cryptographic modules (FIPS 140-2). These often require a custom AMI build pipeline. Self-managed node groups let you plug that pipeline directly into the node lifecycle. See Securing EKS Clusters for more on hardening the cluster layer.
Maintenance burden. With managed node groups, triggering an AMI update is a single API call, and AWS handles draining and replacing each node in sequence. With self-managed nodes, you build or source this automation yourself: detecting when a new AMI is available, validating your custom AMI against the new base, testing the image, running a rolling replacement, and monitoring for failures. For most teams, that represents significant initial build time and ongoing maintenance. See Upgrading EKS Clusters Safely for how node group upgrades fit into the broader EKS upgrade lifecycle.
Self-managed nodes are not inherently more capable for most workloads. If you cannot name the specific requirement that managed node groups fail to meet, the operational overhead is not justified. Teams that choose self-managed “for more control” often spend months maintaining patching automation they did not need. Default to managed and revisit only when you hit a real blocker.
Which should you choose? Default to managed unless you have a specific, concrete requirement it cannot satisfy. The Managed vs Self-Managed Kubernetes comparison goes deeper on the architectural and total cost of ownership implications.
Node group scaling and capacity choices
Every node group has three scaling parameters:
- desiredCapacity — the current target node count. The ASG tries to maintain this.
- minSize — the floor. Scale-in events will not go below this.
- maxSize — the ceiling. Scale-out events will not exceed this.
Kubernetes does not control the ASG directly. The bridge between pod scheduling pressure and EC2 capacity is either the Cluster Autoscaler or Karpenter.
Cluster Autoscaler watches for pods stuck in Pending state and increases the node group’s desiredCapacity when pods cannot be scheduled. When nodes are underutilised for a sustained period, it drains pods and reduces desiredCapacity. Cluster Autoscaler requires specific EC2 tags on the ASG to discover node groups:
tags:
k8s.io/cluster-autoscaler/enabled: "true"
k8s.io/cluster-autoscaler/<cluster-name>: "owned"Karpenter provisions and terminates EC2 instances directly, bypassing ASGs. This makes it faster to respond to pending pods and lets it pick the right instance type for each batch of pending workloads rather than being constrained to types pre-defined in a node group. For new clusters, Karpenter is the recommended autoscaling solution. Horizontal Pod Autoscaling scales the number of pods, not nodes. A production cluster typically needs both.
Think of pending pods like customers waiting for a table at a busy restaurant. They have been added to the waiting list (Kubernetes accepted the workload) but cannot be seated because all tables are full. The autoscaler is the manager watching the queue: when the wait gets long enough, they open a new section and seat the next group. When the restaurant is half-empty at closing time, they close those sections again.
Node auto repair. EKS includes node health monitoring that automatically replaces managed nodes failing health checks, such as a node where kubelet has become unresponsive. This reduces the risk of silent node failures going undetected without manual intervention.
Why pending pods matter. A pod stuck in Pending state means Kubernetes accepted the workload but cannot find a suitable node. Causes include all nodes at capacity, no node matching the pod’s node selector or toleration, or min/max settings preventing the ASG from adding capacity. Monitoring pending pod count is an important operational signal. See Monitoring EKS Clusters for the key metrics to track.
Instance type selection
Instance type choice affects three things: compute capacity, cost, and pod density. Pod density is the one most commonly overlooked.
The VPC CNI plugin gives each pod a real IP address from your VPC subnet. The number of IPs available per node is bounded by the number of ENIs the instance supports and the number of IP addresses per ENI. The formula is roughly:
max pods = (number of ENIs x (IPs per ENI - 1)) + 2| Instance type | vCPU | Memory | Max ENIs | Max pods (VPC CNI) | Notes |
|---|---|---|---|---|---|
| t3.medium | 2 | 4 GB | 3 | 17 | Burstable CPU, avoid for sustained workloads |
| m5.large | 2 | 8 GB | 3 | 29 | Good general-purpose starting point |
| m5.xlarge | 4 | 16 GB | 4 | 58 | Common production choice |
| m5.2xlarge | 8 | 32 GB | 4 | 58 | Same pod limit as xlarge; only useful if individual pods need more CPU/RAM |
| c5.2xlarge | 8 | 16 GB | 4 | 58 | Compute-optimised, good for CPU-heavy workloads |
| r5.xlarge | 4 | 32 GB | 4 | 58 | Memory-optimised, caches and in-memory processing |
m5.xlarge and m5.2xlarge have the same max pod count (58). If you run many small pods and scale up from xlarge to 2xlarge, you will not fit more pods per node. You hit the ENI IP limit before you run out of CPU or memory. Teams with many microservices often discover this only after provisioning much larger instances. To raise the ceiling, enable VPC CNI prefix delegation or use more nodes of a smaller size.
For production clusters, m5.large or m5.xlarge are common starting points. Avoid t3 burstable instances for sustained workloads. CPU credits can exhaust under load, causing throttling at the worst possible moment. For CPU-intensive jobs, c5 instances offer better price-performance. For memory-heavy workloads like caches or in-memory state, r5 is the natural fit. See EC2 Instance Types Explained for a broader breakdown of instance families.
Spot instances in node groups
Spot instances offer spare EC2 capacity at discounts of 60 to 90 percent compared to on-demand pricing. The risk is that AWS can reclaim Spot capacity with a two-minute warning when that capacity is needed elsewhere.
When Spot is safe:
- Stateless services with multiple replicas, where interrupted instances are replaced and traffic shifts to surviving replicas
- Batch and data processing jobs that checkpoint progress and can resume after interruption
- CI/CD runners where a failed job means a retry, not a user-visible outage
- Dev and staging environments where brief interruptions are acceptable
When Spot is not safe:
- Databases or stateful services where interruption risks data loss
- Pods with long startup times (JVM warm-up, large model loading) where a two-minute drain window may not be enough
- Single-replica workloads with no redundancy
- Any pod that cannot tolerate an unplanned restart
Mixed instance types matter. Specifying multiple instance types in a Spot node group increases the chance that capacity is available when you need it. If m5.large is unavailable, AWS can use m4.large or m5.xlarge instead. A Spot group locked to a single instance type is fragile.
eksctl create nodegroup \
--cluster my-cluster \
--region eu-west-1 \
--name spot-workers \
--spot \
--instance-types m5.large,m5.xlarge,m4.large,m4.xlarge \
--nodes-min 0 \
--nodes-max 20If Spot capacity is unavailable in your region, new Spot instances cannot be provisioned and scale-out stalls entirely. Do not run a Spot-only cluster. Always maintain an on-demand node group for baseline capacity and treat Spot as additional scale on top of that floor.
Always use a taint on Spot node groups so that only workloads you have explicitly opted in will be scheduled there. Without a taint, critical workloads can land on Spot nodes by accident. For deeper background on how Spot pricing and interruptions work at the EC2 level, see Spot Instances on AWS.
Configuring a managed node group
Adding a managed node group to an existing cluster with eksctl:
eksctl create nodegroup \
--cluster my-cluster \
--region eu-west-1 \
--name general-workers \
--node-type m5.xlarge \
--nodes 3 \
--nodes-min 2 \
--nodes-max 10 \
--managedA more complete configuration using an eksctl cluster file, with separate on-demand and Spot node groups:
managedNodeGroups:
- name: general-workers
instanceType: m5.xlarge
minSize: 2
maxSize: 10
desiredCapacity: 3
privateNetworking: true
availabilityZones:
- eu-west-1a
- eu-west-1b
- eu-west-1c
labels:
role: general
tags:
k8s.io/cluster-autoscaler/enabled: "true"
k8s.io/cluster-autoscaler/my-cluster: "owned"
iam:
withAddonPolicies:
autoScaler: true
cloudWatch: true
updateConfig:
maxUnavailable: 1
- name: spot-workers
spot: true
instanceTypes:
- m5.large
- m5.xlarge
- m4.large
- m4.xlarge
minSize: 0
maxSize: 20
desiredCapacity: 2
labels:
role: spot
lifecycle: spot
taints:
- key: spot
value: "true"
effect: NoScheduleupdateConfig.maxUnavailable: 1 means at most one node is replaced at a time during an AMI update, which is slower but safer for smaller clusters. privateNetworking: true places nodes in private subnets, which is the standard production configuration. The Auto Scaling Groups guide covers the underlying EC2 ASG mechanism that node groups build on.
Common mistakes
- Putting every workload in one node group. A single node group for everything means you cannot differentiate scaling behaviour, instance types, or Spot/on-demand ratio by workload type. It also concentrates blast radius: an AMI rollout problem or misconfiguration affects all workloads at once.
- Ignoring labels and taints. Without labels on node groups and matching node selectors on workloads, Kubernetes schedules pods wherever it likes. GPU nodes fill with general web traffic. Spot nodes get stateful databases. Use labels to route workloads intentionally, and taints to create explicit opt-in behaviour for risky node types like Spot.
- Setting min and max too close together. A node group with
minSize: 3andmaxSize: 4can barely respond to demand spikes. Set the max to a realistic ceiling that covers peak traffic. You do not pay for capacity in the ASG that is not running. - Misunderstanding pod density and ENI limits. On a
t3.medium, the maximum is 17 pods per node. Teams deploying many small microservices often hit this ceiling before they run out of CPU or memory. Check the max pod count for your target instance type before committing to it. - Running Spot without a fallback on-demand group. If Spot capacity is unavailable in your region, scale-out stalls. Always maintain an on-demand node group for baseline capacity.
- Choosing self-managed nodes without a concrete reason. Self-managed nodes are not inherently more powerful or flexible in ways that matter for most workloads. If you cannot name the specific requirement that managed node groups fail to meet, the operational overhead is not justified.
Summary
- Node groups are sets of EC2 instances that serve as Kubernetes worker nodes, managed through an EC2 Auto Scaling Group.
- Managed node groups are the right default. AWS handles provisioning, AMI updates, and graceful node draining.
- Self-managed node groups give full OS and bootstrap control but require significant operational investment. Choose only when you have a specific requirement that managed node groups cannot meet.
- Fargate removes EC2 management entirely but does not support DaemonSets, Spot instances, or privileged containers. Verify the limitations before adopting it.
- Multiple node groups per cluster is normal. Separate groups for different workload profiles improve scheduling, cost control, and blast radius isolation.
- Instance type choice affects both compute capacity and pod density due to ENI IP address limits. Check max pod counts before choosing an instance type.
- Karpenter is the recommended autoscaling approach for new clusters. Cluster Autoscaler remains a solid choice for existing setups.
Frequently asked questions
What is an EKS node group?
A node group is a set of EC2 instances that serve as Kubernetes worker nodes in an EKS cluster. All instances in a group share the same instance type, AMI, IAM role, and Auto Scaling configuration. EKS manages node groups through an underlying EC2 Auto Scaling Group.
What is the difference between managed and self-managed node groups?
With managed node groups, AWS provisions the EC2 instances, handles AMI updates, and drains pods automatically during node replacement. With self-managed node groups, you create and manage the underlying EC2 Auto Scaling Group yourself, giving you full control over the AMI, bootstrap script, and kubelet configuration at the cost of significantly more operational work.
Should I use Fargate or node groups in EKS?
Node groups (managed) are the default for most workloads. Fargate is better when you need pod-level isolation, want zero EC2 management, or run ephemeral batch jobs. Fargate does not support DaemonSets, has no Spot option, and can cost more for steady-state workloads. If you are unsure, start with managed node groups.
Can one EKS cluster have multiple node groups?
Yes. Multiple node groups per cluster is standard practice. Teams typically run separate node groups for different workload profiles: a general-purpose on-demand group for API servers, a Spot group for batch jobs, and a GPU group for inference workloads, all sharing one control plane.
Do managed node groups support Spot instances?
Yes. You can create a managed node group with Spot capacity by setting capacityType to SPOT and specifying multiple instance types. AWS handles the Spot interruption notice and automatically drains the node before reclaiming capacity.