AWS SAA-C03: Containers & Orchestration — Study Guide
Part of the AWS SAA-C03 Complete Study Guide. Practice these concepts with verified answers in the AWS/Amazon exam hub, or take timed practice tests on ExamRoll.io.
Choosing Between ECS and EKS, and Between EC2 and Fargate
The first architectural decision for any container workload on AWS is picking the orchestrator and the compute mode. Amazon ECS is an AWS-proprietary orchestrator with tight integration into IAM, ALB/NLB, CloudWatch, Service Discovery, and VPC networking. It has no control-plane fee and is the fastest path to production for teams that do not need Kubernetes-specific tooling. Tasks are units of one or more containers sharing a lifecycle; services are long-running managed task sets fronted by a scheduler. Amazon EKS runs upstream-conformant Kubernetes at a flat $0.10/hour per cluster, and it is the correct choice when workloads must remain portable, use the Kubernetes API, or leverage the CNCF ecosystem (Helm, CRDs, Operators). The control plane — API server, etcd, controller manager, scheduler — is patched, backed up, and made highly available across three AZs by AWS.
Both orchestrators accept two compute modes. AWS Fargate runs each task or pod on isolated Firecracker microVMs; there are no AMIs to patch, no Auto Scaling Group capacity providers to tune, no bin-packing decisions, no SSH-accessible hosts. You pay per vCPU-second and GB-second of the task. EC2 launch type / managed node groups means you own the instances, with the flexibility and operational burden that implies.
Fargate is the correct default whenever a scenario emphasizes “serverless,” “least operational overhead,” or “no infrastructure to manage,” provided the workload fits its constraints: maximum 16 vCPU / 120 GB memory per task, no GPU, no privileged containers, no DaemonSets, no HostPort/HostNetwork, no Windows host-level customization. Choose managed node groups or EC2 launch type when you need GPUs, DaemonSets that require host access, custom AMIs, Windows Server containers with GMSA, sub-second bin-packing efficiency, or aggressive Spot-based cost optimization.
| Requirement | Correct choice |
|---|---|
| Kubernetes API + upstream tooling | EKS |
| Simplest AWS-native orchestrator | ECS |
| No infrastructure management | Fargate (either orchestrator) |
| GPUs, DaemonSets, custom kernel modules | Managed node groups / EC2 |
| Windows containers with GMSA | EC2 launch type |
| Spot-based cost optimization at scale | Managed node groups with Spot |
| EBS-backed persistent volumes on EKS | Managed node group |
| Bursty, unpredictable pods | Fargate |
The classic trap is picking EC2 nodes because they “feel” cheaper. For spiky or low-utilization workloads Fargate is often cheaper once you factor in idle capacity plus engineering time for AMI patching, node draining, and cluster autoscaler configuration — all of which Fargate removes.
Task Definitions, Placement, and Capacity Providers
A canonical ECS Fargate task definition captures the essentials — CPU/memory sizing, an execution role for pulling images and writing logs, awsvpc network mode, and CloudWatch Logs wiring:
family: checkout-service
requiresCompatibilities: [FARGATE]
networkMode: awsvpc
cpu: "1024"
memory: "2048"
executionRoleArn: arn:aws:iam::111122223333:role/ecsTaskExecutionRole
containerDefinitions:
- name: checkout
image: 111122223333.dkr.ecr.us-east-1.amazonaws.com/checkout:1.4.2
portMappings: [{ containerPort: 8080 }]
logConfiguration:
logDriver: awslogs
options:
awslogs-group: /ecs/checkout
awslogs-region: us-east-1
On ECS with EC2, task placement strategies decide how tasks distribute across instances. They compose in order:
| Strategy | Behavior | Typical use |
|---|---|---|
spread | Even distribution across a field (e.g., attribute:ecs.availability-zone) | HA across AZs |
binpack | Pack onto fewest instances by CPU or memory | Cost optimization |
random | Random placement | Rarely appropriate |
Placement constraints such as distinctInstance or memberOf using the cluster query language further restrict candidates.
Capacity providers decouple services from raw Auto Scaling Groups. FARGATE and FARGATE_SPOT are managed providers; custom providers wrap an ASG and enable managed scaling so ECS adjusts the ASG desired count based on the number of provisioned tasks. A capacity provider strategy splits tasks by weight and base — for example, guaranteeing two on-demand Fargate tasks and splitting the rest 1:4 with Fargate Spot for interruption-tolerant workloads:
capacityProviderStrategy:
- capacityProvider: FARGATE
base: 2
weight: 1
- capacityProvider: FARGATE_SPOT
weight: 4
Autoscaling: Pods, Tasks, and Nodes
Fargate removes node management, but it does not automatically scale the number of tasks or pods. This distinction is the single most-abused Fargate misconception: serverless applies to the host layer, never to your service’s desired count. You still own service-level autoscaling.
For ECS, use Application Auto Scaling. Target tracking is the idiomatic default because it creates the scale-out and scale-in CloudWatch alarms automatically and respects cooldowns. Sensible metrics are ECSServiceAverageCPUUtilization, ECSServiceAverageMemoryUtilization, and ALBRequestCountPerTarget:
aws application-autoscaling register-scalable-target \
--service-namespace ecs \
--resource-id service/prod-cluster/checkout \
--scalable-dimension ecs:service:DesiredCount \
--min-capacity 2 --max-capacity 30
aws application-autoscaling put-scaling-policy \
--policy-name cpu-target-50 \
--service-namespace ecs \
--resource-id service/prod-cluster/checkout \
--scalable-dimension ecs:service:DesiredCount \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration \
'{"TargetValue":50.0,"PredefinedMetricSpecification":{"PredefinedMetricType":"ECSServiceAverageCPUUtilization"}}'
Step scaling and scheduled scaling remain available for non-linear response curves or known traffic windows.
For EKS on EC2 nodes, the pod dimension is handled by the Horizontal Pod Autoscaler (HPA), which adjusts replica counts based on CPU, memory, or custom metrics. HPA alone cannot add nodes — it will happily raise replicas past cluster capacity, at which point new pods enter Pending with FailedScheduling events. That is why HPA on EC2 must always be paired with either the Kubernetes Cluster Autoscaler or Karpenter. Forgetting this pairing is a frequent design error: HPA reports “scaled to 20 replicas” while half of them are stuck pending. A minimal Cluster Autoscaler configuration discovers node groups through ASG tags:
- --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/prod-eks
- --balance-similar-node-groups
- --skip-nodes-with-system-pods=false
On Fargate profiles this problem disappears — each pod becomes its own microVM, so the node-scaling dimension is eliminated entirely. This is precisely why Fargate is the correct pick for bursty workloads with unpredictable pod counts.
Fargate Profiles and Their Feature Limits
A Fargate profile in EKS is a selector — namespace plus optional pod labels — that instructs EKS to schedule matching pods on Fargate rather than on a node:
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata: { name: microservices, region: us-east-1 }
fargateProfiles:
- name: fp-app
selectors:
- namespace: app
labels: { compute: fargate }
The critical caveat is that EKS on Fargate does not support the full Kubernetes feature surface:
- DaemonSets do not run. With no node to daemonize on, log shippers like
fluentdDaemonSets must be replaced with sidecars or the built-in Fluent Bit log router. - Privileged containers, HostPort, and HostNetwork are unavailable.
- Persistent storage is limited to EFS via the CSI driver. EBS is not supported because EBS volumes require attachment to a specific EC2 instance.
- GPU workloads are not supported.
NodePortservices and service meshes that rely on privileged init containers do not work.
Assuming feature parity leads to failed migrations of StatefulSets that need EBS, ingress controllers that use hostPort, or GPU inference workloads.
Ingress: ALB vs NLB via the AWS Load Balancer Controller
The AWS Load Balancer Controller is a Kubernetes controller that translates Ingress and Service objects into real ALBs and NLBs. For HTTP/HTTPS microservices with path- or host-based routing, create a single Ingress of class alb. One ALB fronting many services (via alb.ingress.kubernetes.io/group.name) is dramatically cheaper than one ALB per service and is the canonical cost-effective pattern:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/group.name: shop
spec:
rules:
- http:
paths:
- path: /customers
pathType: Prefix
backend: { service: { name: customers, port: { number: 80 }}}
- path: /orders
pathType: Prefix
backend: { service: { name: orders, port: { number: 80 }}}
Use an NLB (Service of type LoadBalancer with service.beta.kubernetes.io/aws-load-balancer-type: external and target type nlb-ip) for TCP, UDP, TLS pass-through, static IP requirements, or extreme Layer 4 throughput. Putting gRPC-over-HTTP/2 or a plain-TCP database proxy behind an ALB is fine only for gRPC (ALB supports HTTP/2 and gRPC); ALB cannot terminate arbitrary TCP or any UDP. Attempting to serve UDP game traffic through an ALB is a protocol mismatch that must be caught at design time.
Pod-Level IAM with IRSA
IAM Roles for Service Accounts (IRSA) is the correct mechanism to grant AWS API permissions to individual pods. Attaching the node’s instance profile to give pods S3 access is wrong because every pod on the node inherits those permissions, breaking least privilege. IRSA works by federating the cluster’s OIDC provider with IAM: create the OIDC provider once, then create an IAM role whose trust policy trusts that provider for a specific namespace:serviceaccount subject.
eksctl utils associate-iam-oidc-provider --cluster prod --approve
eksctl create iamserviceaccount \
--cluster prod --namespace payments --name orders-sa \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess \
--approve
The service account is annotated with eks.amazonaws.com/role-arn: arn:aws:iam::...:role/orders-role, and pods that mount it receive short-lived STS credentials through a projected token. Skipping the OIDC provider association or omitting the annotation silently falls back to the node role — a subtle security regression that is easy to miss in review.
The VPC CNI plugin complements this by assigning each pod a routable ENI/IP address in the VPC subnet. Pods can then talk directly to RDS, ElastiCache, or VPC endpoints using security groups, and CloudTrail sees the pod’s actual IP for auditing.
Persistent and Ephemeral Storage
Storage choice depends on access mode, durability, and launch type.
EBS (via the EBS CSI driver on EKS, or task-attached EBS on ECS) provides ReadWriteOnce block volumes for single-pod stateful workloads such as a Postgres primary. EFS provides ReadWriteMany NFS storage that is regional, multi-AZ, and mountable by many pods simultaneously — the correct choice whenever the requirement mentions “highly available, fault tolerant, shared among multiple containers,” or shared ML artifacts. FSx for Lustre handles high-throughput HPC; FSx for NetApp ONTAP and FSx for Windows File Server handle enterprise NFS/SMB.
Fargate tasks receive 20 GB of ephemeral storage by default, configurable up to 200 GB via ephemeralStorage.sizeInGiB on platform 1.4.0+. Assuming Fargate always has “plenty of scratch space” is a trap: a vendor container that writes 50 GB of intermediate files may fit expanded ephemeral, but if the requirement is 50 GB of shared or durable storage across task restarts or across tasks, ephemeral is wrong because it is destroyed on task stop and never shared. Fargate does not support EBS. If a Fargate workload needs persistent or shared storage, EFS is essentially the only supported option.
For ECS, mount EFS directly in the task definition; access points enforce POSIX UID/GID and root directory per task, providing safe multi-tenancy on a single file system, and IAM authorization scopes elasticfilesystem:ClientMount:
"volumes": [{
"name": "scratch",
"efsVolumeConfiguration": {
"fileSystemId": "fs-0abc123",
"transitEncryption": "ENABLED",
"authorizationConfig": {
"accessPointId": "fsap-0def456",
"iam": "ENABLED"
}
}
}],
"containerDefinitions": [{
"name": "app",
"mountPoints": [{
"sourceVolume": "scratch",
"containerPath": "/data"
}]
}]
For EKS, wire EFS through a StorageClass:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata: { name: efs-sc }
provisioner: efs.csi.aws.com
parameters:
provisioningMode: efs-ap
fileSystemId: fs-0123456789abcdef0
directoryPerms: "700"
A recurring trap: selecting Fargate for a stateful workload while forgetting to install the EFS CSI driver and StorageClass — pods start but PersistentVolumeClaims stay Pending forever. Conversely, choosing EC2 nodes solely to attach EBS when the requirement is shared multi-writer storage is also wrong; EBS io2 attached to one node cannot be shared across AZs.
ECR Image Scanning and Lifecycle
Amazon ECR offers two scan modes. Basic scanning uses the open-source Clair database and runs on push (or on demand) at no charge. Enhanced scanning integrates Amazon Inspector, continuously monitors for both OS and language-package CVEs, and reports findings into Security Hub. For “scan for CVEs, scan new images on creation, fewest changes to workloads,” the correct action is enabling scan on push at the repository level — no pipeline change required because the push event triggers the scan:
aws ecr put-image-scanning-configuration \
--repository-name payments-api \
--image-scanning-configuration scanOnPush=true
CI/CD then calls describe-image-scan-findings and fails builds on CRITICAL or HIGH counts. Skipping scanning means unpatched Log4j, OpenSSL, or glibc CVEs run in production; the mitigation cost of not scanning dwarfs the near-zero cost of enabling it.
Lifecycle policies cap storage cost and enforce tag hygiene:
{
"rules": [{
"rulePriority": 1,
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 14
},
"action": { "type": "expire" }
}]
}
Migrating Kubernetes + MongoDB Workloads
When lifting a self-hosted Kubernetes + MongoDB stack to AWS under constraints “do not change application code” and “minimal operational overhead,” two decisions align. First, the compute layer moves to EKS because Kubernetes API compatibility means manifests and Helm charts port unchanged; use Fargate profiles for the stateless services to eliminate node ownership.
Second, MongoDB itself should not be rehosted onto self-managed EC2 or StatefulSets — that reintroduces backups, sharding, failover, and patching as operational burdens. Use Amazon DocumentDB (with MongoDB compatibility), which speaks the MongoDB 3.6/4.0/5.0 wire protocol so existing drivers and connection strings work with minimal change.
The compatibility caveat matters. DocumentDB emulates the MongoDB API surface but is not MongoDB. Certain aggregation operators, specific index types, change stream semantics, and features introduced in newer MongoDB versions may fail. The correct pre-migration step is running the DocumentDB compatibility tool (compat.py) against the application to confirm every operation is supported. Choosing DynamoDB instead would force a data-model rewrite; choosing RDS would break the document model entirely. Both violate the “no code change” constraint.
Hybrid Options: ECS Anywhere and EKS Anywhere
ECS Anywhere registers on-premises servers (or other clouds’ VMs) as external instances into an ECS cluster. The control plane stays in AWS; the SSM Agent and ECS Agent on the external instance connect outbound. One deployment pipeline, one task definition, and one set of IAM roles cover both cloud and on-prem workloads.
EKS Anywhere installs a conformant Kubernetes distribution on your hardware (typically vSphere or bare metal), optionally with EKS Connector visibility into the AWS console. Choose ECS Anywhere for lightweight, task-definition-based hybrid; choose EKS Anywhere when regulatory or latency constraints require Kubernetes locally with the same tooling as EKS in the cloud. Both keep orchestration consistent — the fundamental value is that teams avoid maintaining two CI/CD systems or two mental models.
Multi-Cluster Visibility with the EKS Connector
Organizations frequently operate a mix of EKS clusters, self-managed Kubernetes on EC2, and on-premises clusters. The Amazon EKS Connector registers any conformant Kubernetes cluster into the EKS console, giving a single pane of glass for nodes, workloads, and cluster metadata. It is essentially a lightweight agent plus an SSM channel — the low-overhead answer for “central view of all clusters.” Building equivalent visibility with self-hosted Rancher, Anthos, or a custom Prometheus/Grafana federation is feasible but far more operationally expensive and does not integrate with IAM or console-based access.
EKS Connector is strictly a visibility layer; it does not manage or upgrade the remote cluster, which is exactly what “central view with least overhead” implies.
Putting the Patterns Together
For an ecommerce workload with a load-balanced front end, containerized middle tier, and relational store where “as little manual intervention as possible” is the criterion, the canonical stack is ALB → ECS on Fargate → Aurora Serverless v2. Every tier eliminates instance-level ownership: the ALB is fully managed, Fargate removes hosts, and Aurora Serverless v2 scales in ACUs without instance sizing.
For a microservices platform where the team “cannot manage additional infrastructure,” the correct combination is ECS or EKS with Fargate, plus a fully managed data service. Selecting EC2 launch type or self-managed node groups contradicts the constraint even though the workload would technically run.
For lift-and-shift of Kubernetes + MongoDB, the answer converges on EKS + DocumentDB: Kubernetes API preserves deployment methods, DocumentDB preserves driver compatibility, and Fargate profiles preserve minimal ops — provided the workload’s Kubernetes feature usage and MongoDB command surface fall within the supported envelopes described above.
← Previous: Compute · All domains · Next: Serverless →
Practice Containers questions → · Timed practice on ExamRoll.io →
Pass the whole exam — not just this question
You found this answer. Get every verified question and explanation in one place, and save hours of prep. Free to start.
Pass your exam →