Module 7: Eks Specifics For Production
3. Node groups: managed, self-managed, and Fargate profiles
Description
The previous lesson made clear that nodes — the hardware or VMs where your Pods actually run — are your responsibility in EKS, no matter how much of the control plane AWS administers. What that lesson didn't cover is that "having nodes" isn't a single decision: AWS offers three distinct ways of having compute behind an EKS cluster, with very different levels of control and operational work between them. This lesson dissects them one by one, with real eksctl and Terraform syntax for each — this module's first lesson with real YAML/CLI volume, all verified against official documentation, nothing executed.
Connection to the module
In kind (Module 1), this decision didn't exist — your kind-config.yaml declared one control-plane and two workers, and kind created all of them as Docker containers on the same machine, with no "managed" vs. "self-managed" option in play, because there's no real cloud behind it managing anything. EKS, being a real AWS service with real EC2 and Fargate available, does have to offer you that choice — and each one implies a different contract over who does what work.
The three forms of compute, at a glance
HOW MUCH YOU ADMINISTER, FROM LESS TO MORE
Fargate profiles Managed node groups Self-managed nodes
──────────────── ──────────────────── ───────────────────
No EC2 visible EC2, but AWS helps with EC2, you administer
to you — AWS runs the lifecycle (create, everything: AMI,
every Pod in its own update, terminate) — patching, manual
isolated micro-VM you still choose the scaling or with
instance type and AMI your own tooling
Ideal for: sporadic Ideal for: most teams, Ideal for: special
workloads, strong AWS's recommended default cases — mandatory
isolation, not wanting starting point for almost custom AMI,
to administer any anything specific hardware,
server full control over
the boot cycle
EKS managed node groups: the recommended starting point
A managed node group is a group of EC2 instances AWS creates and administers on your behalf, backed by an Auto Scaling Group you don't touch directly. "Managed" doesn't mean "invisible" — they're still real EC2 instances, in your account, showing up on your bill — it means AWS gives you one-click tools for the operations that generate the most friction: creating the node group with the EKS-optimized AMI already ready, updating all nodes to a new Kubernetes version with no downtime orchestrated by hand, and draining (cordon/drain) nodes automatically before terminating them.
With eksctl (representative):
eksctl create nodegroup \
--cluster andes-cargo-cluster \
--name andes-cargo-managed-ng \
--node-type t3.medium \
--nodes 2 \
--nodes-min 2 \
--nodes-max 4 \
--managed
With Terraform (representative) — the same resource, for anyone who already built infrastructure in this ecosystem with terraform-and-iac-guide:
resource "aws_eks_node_group" "andes_cargo_managed_ng" {
cluster_name = "andes-cargo-cluster"
node_group_name = "andes-cargo-managed-ng"
node_role_arn = aws_iam_role.eks_node_role.arn
subnet_ids = var.private_subnet_ids
scaling_config {
desired_size = 2
min_size = 2
max_size = 4
}
instance_types = ["t3.medium"]
}
Notice the node_role_arn field: a managed node group needs, just like an ECS task role you already know from aws-serverless-and-containers-guide, its own IAM role so every EC2 instance can register against the control plane and download container images — the official managed policy is AmazonEKSWorkerNodePolicy, the node-level equivalent of the same pattern EcsTaskExecutionRole solved for ECS.
Self-managed nodes: full control, full work
A self-managed node is, in essence, a normal EC2 instance you launch, configure, and join to the cluster with your own tools — a launch template, your own Auto Scaling Group, or even a loose instance — with AWS not mediating the lifecycle for you. AWS still gives you the EKS-optimized AMI as a starting point, but from there, updating those nodes' Kubernetes version, patching the operating system, or replacing a damaged instance are tasks your team orchestrates, not an EKS console button.
With Terraform (representative) — the typical pattern: a launch template + your own Auto Scaling Group:
resource "aws_launch_template" "andes_cargo_self_managed" {
name_prefix = "andes-cargo-self-managed-"
image_id = data.aws_ssm_parameter.eks_ami.value
instance_type = "t3.medium"
user_data = base64encode(<<-EOF
#!/bin/bash
/etc/eks/bootstrap.sh andes-cargo-cluster
EOF
)
}
resource "aws_autoscaling_group" "andes_cargo_self_managed" {
desired_capacity = 2
min_size = 2
max_size = 4
vpc_zone_identifier = var.private_subnet_ids
launch_template {
id = aws_launch_template.andes_cargo_self_managed.id
version = "$Latest"
}
}
The /etc/eks/bootstrap.sh script — invoked inside the instance's user data — is the detail separating "just some EC2" from "an EKS node": it's the one that registers the instance against andes-cargo-cluster's control plane at boot time. When does a team choose this path, knowing it means more work? Concrete cases: you need a custom AMI EKS doesn't offer in optimized form (for example, with industry-specific security hardening pre-installed), you need boot hardware or configuration the managed node group flow doesn't expose, or you already have a mature internal EC2 fleet-management platform you'd rather reuse than learn AWS's model.
Fargate profiles: no server to administer, neither managed nor un-managed
A Fargate profile is the third path, and the most different of the three: there's no EC2 node — managed or self-managed — that you administer at all. Every Pod meeting a Fargate profile's selection criteria boots in its own isolated micro-VM, entirely administered by AWS. It's the same serverless compute model aws-serverless-and-containers-guide already showed you for ECS/Fargate — applied here to Kubernetes Pods instead of ECS tasks.
A Fargate profile doesn't deploy over "the whole cluster": it selects which Pods use Fargate, via namespace (required) and, optionally, labels — up to five selectors per profile, and a Pod matching any of them gets scheduled on Fargate.
With eksctl (representative), a profile that sends any Pod in the andes-cargo namespace to Fargate:
eksctl create fargateprofile \
--cluster andes-cargo-cluster \
--name andes-cargo-fargate-profile \
--namespace andes-cargo
And with an additional label, so only explicitly marked Pods within that same namespace use Fargate:
eksctl create fargateprofile \
--cluster andes-cargo-cluster \
--name andes-cargo-fargate-batch \
--namespace andes-cargo \
--labels workload-type=batch
Three documented limits, verified against AWS Docs, worth knowing before choosing this path:
- Fargate Pods only get scheduled in private subnets, with no direct route to an Internet Gateway — they need a NAT Gateway to reach the internet.
- No
DaemonSetsupport — a pattern this module hasn't used yet, but that tools like the AWS Load Balancer Controller (Module 7.6) or the EKS Pod Identity agent (Module 7.5) do need to run on every node; those components, if the cluster is only Fargate, require a different installation strategy. - Every Fargate profile needs its own Pod execution role — an IAM role analogous, in spirit, to
EcsTaskExecutionRole: it lets thekubeletrunning on Fargate's infrastructure download images and register against the cluster, not lets your application code do anything — separate, again, from the role your application code uses (the IRSA/Pod Identity pattern lesson 5 picks back up).
Decision table: when to use each one
| Criterion | Managed node groups | Self-managed | Fargate profiles |
|---|---|---|---|
| Who administers EC2's lifecycle | AWS (create, update, terminate) | You, with your own tooling | No one — no EC2 visible |
| Can you use a custom AMI | Yes, via launch template | Yes, no restriction | No |
Supports DaemonSet | Yes | Yes | No |
| SSH into the instance | Yes | Yes | No — there's no instance to connect to |
| Operational work | Low — the recommended default | High — full control, full maintenance | None over servers, but real network and compatibility limits |
| Typical case | Most production workloads, this guide's starting point if it migrated andes-cargo-status-api | Very specific AMI/hardware requirements, an already-mature internal platform | Sporadic workloads, batch, or teams prioritizing zero server maintenance over flexibility |
For andes-cargo-status-api — an always-on HTTP service, low operational complexity, no custom AMI need — a managed node group would be the default choice in a real migration: it's the middle ground AWS recommends for the vast majority of workloads, with no loss of ability to run a DaemonSet if the team needs one later (an observability agent, for example).
Common mistakes
Assuming "managed" means "no real EC2 instances," confusing it with Fargate (vocabulary). What happens: someone hears "managed node group" and assumes that, since it sounds like "administered," there are no real servers involved — the typical confusion is with Fargate, which does eliminate the visible instance. How to spot it: if you expect to see no EC2 instance on your bill after creating a managed node group. How to fix it: a managed node group does create real EC2 instances, visible in your account and on your bill — the only thing AWS manages is the operational lifecycle (creation, updating, orderly termination), not the server's existence itself. Only Fargate eliminates the visible instance entirely.
Choosing Fargate without first checking whether the cluster needs any DaemonSet (planning). What happens: a team migrates their entire workload to Fargate profiles, and later discovers they need to install an observability or runtime-security agent that depends on a DaemonSet to run on every node. How to spot it: if your list of add-ons or platform tools includes something the documentation explicitly describes as a DaemonSet. How to fix it: review that list before committing to Fargate as the cluster's only form of compute — many real teams use a combination (managed node groups for what needs DaemonSet, Fargate profiles for occasional workloads that don't), not an exclusive choice.
Thinking a Kubernetes Fargate profile is identical, with no difference, to the Fargate you already know from ECS (continuity with aws-serverless-and-containers-guide). What happens: someone who already built an ECS Fargate task in the previous guide assumes the syntax and selection model are the same here. How to spot it: if you look for a taskDefinition or desiredCount field inside an EKS Fargate profile. How to fix it: the underlying concept — serverless compute, no visible server — is the same, but the selection mechanism is completely different: ECS Fargate boots explicitly defined tasks; an EKS Fargate profile doesn't define what to run, it only defines which Pods, already defined in Kubernetes, qualify to run there, via namespace/labels — it's a filter over Kubernetes objects, not a workload definition in itself.
Exercises
Exercise 1 — Classify five scenarios. For each one, decide whether managed node groups, self-managed, or Fargate profiles is the most reasonable choice: (a) a team needs to run a runtime-security DaemonSet on every node; (b) a batch-processing workload that runs 20 minutes, twice a day, and the team doesn't want to pay for idle EC2 instances the rest of the time; (c) a team with a mandatory corporate AMI per security policy, with specific hardening EKS doesn't offer out of the box; (d) the andes-cargo-status-api service, always-on, with no special AMI requirements.
See solution
(a) Managed node groups (or self-managed) — Fargate doesn't support DaemonSet. (b) Fargate profiles — sporadic compute, no server left idle between runs. (c) Self-managed — requires full control over the AMI, beyond what a managed node group's launch template allows in standard form. (d) Managed node groups — the recommended starting point for an always-on workload with no special requirements, the same criterion this lesson applied to Andes Cargo.
Exercise 2 — Explain the Pod execution role without using the word "infrastructure." A colleague asks you why a Fargate profile needs its own IAM role if "Pods already have their own role via IRSA." Answer them in two or three sentences, without using the word "infrastructure."
See solution
A complete answer sounds, roughly, like this: "They're two different questions, just like you already saw with EcsTaskExecutionRole and StatusApiTaskRole on ECS. Before your Pod even exists, someone has to be able to download the container image and register that Pod against the cluster — that's what the Fargate profile's Pod execution role solves. Once the Pod is running, what your code can do against other AWS services is a completely different permission, solved by IRSA or Pod Identity — the same role-separation pattern you already know, applied to a new layer."
Exercise 3 — Predict the result of a DaemonSet on a Fargate-only cluster. If an EKS cluster has only Fargate profiles (no node groups), and someone applies a DaemonSet manifest, what would you expect to happen? Justify your answer with what you learned about Fargate's limitations in this lesson.
See solution
The DaemonSet would find no valid node to schedule onto — Fargate doesn't support the DaemonSet pattern because there's no fixed set of "real" nodes to guarantee "one Pod per node" against: every Fargate Pod lives in its own isolated micro-VM, with no persistent shared node a DaemonSet could anchor to. In practice, the DaemonSet object would get created in etcd with no syntax error, but its Pods would stay indefinitely unscheduled (Pending), because the scheduler would find no compatible node.
Summary and next step
This lesson dissected the three ways of having compute behind an EKS cluster: managed node groups (the recommended starting point, AWS administers real EC2 instances' lifecycle), self-managed nodes (full control, full work, for very specific AMI or hardware requirements), and Fargate profiles (no visible server at all, with real network and compatibility limits like the lack of DaemonSet support). You confirmed, with real eksctl and Terraform syntax, how each one gets declared, and applied the criterion to andes-cargo-status-api: a managed node group, no special requirements.
Before moving on you should be able to: explain, without confusing them, what AWS manages in each of the three compute forms; name a real Fargate profiles limit affecting an architecture decision; and justify, in one sentence, why andes-cargo-status-api would choose a managed node group in a real migration.
Next lesson: node autoscaling — Karpenter versus Cluster Autoscaler. There you're going to see the exact distinction from the HorizontalPodAutoscaler you already executed in Module 3: that one scales Pods over nodes that already exist; this one scales the nodes themselves.
Resources
- Amazon EKS — Manage compute resources by using nodes — the official comparison of compute options, the source for this lesson's decision table.
- Amazon EKS — Amazon EKS managed node groups — complete reference for managed node groups.
- Amazon EKS — Define which Pods use AWS Fargate when launched — the exact source for the Fargate profile limits cited in this lesson (private subnets, no
DaemonSet, namespace/label selectors). eksctl— Nodegroups — complete syntax foreksctl create nodegroup/eksctl create fargateprofile.aws-serverless-and-containers-guide(NIEVA), Module 7 — the ECS/Fargate model andEcsTaskExecutionRole/StatusApiTaskRole, this lesson's direct comparison point for the Pod execution role.