Alright, let’s talk about the actual compute in your cluster: the nodes. In EKS, you’ve got three main flavors for getting your worker nodes running: Managed Node Groups (MNGs), self-managed nodes (usually via the aws-iam-authenticator and some CloudFormation voodoo), and the serverless oddball, Fargate. Each has a superpower and a corresponding kryptonite. Your job is to pick which trade-off you want to live with.

Managed Node Groups: The Easy Button (Mostly)

This is AWS saying, “Look, you have enough to worry about. Let me handle the grimy details of the EC2 instances for you.” And 90% of the time, you should listen. An MNG isn’t just an Auto Scaling Group (ASG) that EKS knows about; it’s a tightly integrated abstraction that handles a ton of boilerplate for you.

Why you’ll love it: It automatically labels your nodes, registers them with your cluster, and, crucially, handles the dreaded node bootstrapping—that process where each new instance runs a script to join the Kubernetes cluster. Ever had to update a launch template because the bootstrap script changed? With an MNG, you just update the node group version, and AWS handles the rolling update. It’s glorious.

Here’s the kicker, and it’s a big one: MNGs must use the official AWS EKS Optimized AMI. You can’t just roll your own hardened OS image. This is the price of admission for the convenience. You get what you get. You can customize the bootstrap process with a–bootstrap-cli-flag, but the underlying OS is out of your hands. For most folks, this is fine. For places with draconian compliance requirements, it’s a non-starter.

Creating one is stupidly simple. No, really.

aws eks create-nodegroup --cluster-name my-awesome-cluster \
  --nodegroup-name ng-standard \
  --subnets subnet-abc123 subnet-def456 \
  --node-role arn:aws:iam::123456789012:role/NodeInstanceRole \
  --ami-type AL2_x86_64 \ # Amazon Linux 2
  --instance-types t3.medium \
  --scaling-config minSize=2,maxSize=5,desiredSize=2

The magic is in the --node-role. The IAM role you pass here gets magically projected onto the nodes, so your pods can have IAM permissions (via IRSA). It’s one of those things that feels like witchcraft until you understand it, and then it’s just brilliant engineering.

Self-Managed Nodes: For Control Freaks and Masochists

This is the “roll your own” option. You create an ASG, a launch template, and you are responsible for everything. The AMI, the bootstrap script, the security groups, the node registration—the whole shebang.

You do this for one reason: total control. Need a specific kernel version? A custom CNI? A non-standard disk layout? This is your only way. The process is… finicky. You’re essentially replicating what an MNG does manually. The bootstrap script is the heart of it, and it’s a fickle heart.

Here’s a trimmed-down example of the UserData in a CloudFormation LaunchTemplate. Notice how we have to fetch the auth config and the bootstrap script ourselves.

LaunchTemplate:
  Type: AWS::EC2::LaunchTemplate
  Properties:
    LaunchTemplateData:
      IamInstanceProfile:
        Arn: !GetAtt NodeInstanceProfile.Arn
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash
            set -o xtrace
            /etc/eks/bootstrap.sh ${ClusterName} --b64-cluster-ca ${BootstrapClusterCA} --apiserver-endpoint ${ClusterEndpoint}

The pitfalls are everywhere. Did you get the IAM permissions right? Did the bootstrap script change and break your nodes on a scale-out? Did you remember to add all the necessary labels? You will mess this up at least once. I certainly have. The upside is that once you get it working, you have a perfectly reproducible, auditable process.

Fargate: The Serverless Mirage

Fargate is AWS’s promise: “Just give me a pod manifest, and I’ll run it. No servers, no nodes, no OS patching. It’s magic!” And for the right workload, it is. For the wrong one, it’s a budget-burning nightmare.

The core concept is the Fargate Profile. You define a profile that says, “Any pod scheduled on a namespace with these labels should run on Fargate.” EKS then, behind the scenes, provisions a microVM (a “node,” but not really) just for that pod.

Why it’s brilliant: True pay-per-use compute. No more worrying about node capacity. Arguably the most secure runtime because each pod is maximally isolated.

Why it’s frustrating: The list of limitations is long and painful. No DaemonSets. No privileged pods. No host networking. Your storage options are limited (mostly just EFS). The cold start time is noticeable. And the cost… oh, the cost. It’s fantastic for low-traffic, on-and-off APIs. It’s bankrupting for a high-throughput, always-on data processing service.

Defining a profile is straightforward. You’re essentially drawing a box and saying “pods in here go to Fargate.”

aws eks create-fargate-profile --cluster-name my-cluster \
  --fargate-profile-name my-profile \
  --pod-execution-role-arn arn:aws:iam::123456789012:role/eksFargatePodExecutionRole \
  --subnets subnet-abc123 subnet-def456 \
  --selectors namespace=my-namespace

The pod-execution-role is crucial—it’s the role that gives Fargate the permissions to pull your images from ECR.

The Verdict: Which One Should You Use?

My advice? Start with Managed Node Groups for everything. They are the sane default. Only venture into self-managed territory when an MNG actively prevents you from meeting a hard requirement. Use Fargate sparingly, for specific, well-understood workloads that fit its constrained model perfectly. A hybrid approach is common: MNGs for your core applications and Fargate for supporting services like metrics servers or low-traffic webhooks. Just remember, in Kubernetes, as in life, there are no free lunches—only different menus with different prices.