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 DaemonSet support — 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 the kubelet running 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

CriterionManaged node groupsSelf-managedFargate profiles
Who administers EC2's lifecycleAWS (create, update, terminate)You, with your own toolingNo one — no EC2 visible
Can you use a custom AMIYes, via launch templateYes, no restrictionNo
Supports DaemonSetYesYesNo
SSH into the instanceYesYesNo — there's no instance to connect to
Operational workLow — the recommended defaultHigh — full control, full maintenanceNone over servers, but real network and compatibility limits
Typical caseMost production workloads, this guide's starting point if it migrated andes-cargo-status-apiVery specific AMI/hardware requirements, an already-mature internal platformSporadic 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

  1. Amazon EKS — Manage compute resources by using nodes — the official comparison of compute options, the source for this lesson's decision table.
  2. Amazon EKS — Amazon EKS managed node groups — complete reference for managed node groups.
  3. 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).
  4. eksctl — Nodegroups — complete syntax for eksctl create nodegroup/eksctl create fargateprofile.
  5. aws-serverless-and-containers-guide (NIEVA), Module 7 — the ECS/Fargate model and EcsTaskExecutionRole/StatusApiTaskRole, this lesson's direct comparison point for the Pod execution role.