Part 3 of 1225 min read · 8 diagramsAI-assisted

Compute: EC2 & Auto Scaling

Table of Contents#

  1. EC2 — The Original AWS Service
  2. Instance Types — Reading the Naming Convention
  3. Instance Families, By Workload Shape
  4. AMIs — Amazon Machine Images
  5. Launching an Instance, CLI End to End
  6. User Data — Bootstrapping an Instance at Boot
  7. Instance Lifecycle States
  8. EBS-Backed vs Instance-Store-Backed
  9. Placement Groups — Controlling Physical Proximity
  10. Pricing Models, Applied to EC2 Specifically
  11. Spot Instances — Mechanics and Interruption Handling
  12. Auto Scaling Groups — The Core Concept
  13. Launch Templates
  14. Scaling Policies — Target Tracking, Step, and Scheduled
  15. Health Checks and Instance Replacement
  16. Lifecycle Hooks — Doing Work Before Termination
  17. Mixed Instances Policies — Spot and On-Demand Together
  18. Systems Manager — Operating Instances Without SSH
  19. IMDSv2 — Securing the Instance Metadata Service
  20. Capacity Reservations and Dedicated Hosts
  21. Compute Optimizer — Data-Driven Rightsizing
  22. Warm Pools — Solving the Cold-Start Problem for ASGs
  23. EC2 Compute Best Practices — The Consolidated Checklist
  24. Part 3 CLI Cheat Sheet
  25. Common Mistakes
  26. Worked Practice Problems
  27. Summary and What's Next

EC2 — The Original AWS Service#

Elastic Compute Cloud (EC2) rents virtual machines by the second. It's the direct, concrete implementation of the horizontal/vertical scaling concepts from the Capacity Planning series (Part 1) — an EC2 instance is simply "a box," and everything in this part is about getting the right-sized box, in the right quantity, automatically.

Diagram

Instance Types — Reading the Naming Convention#

EC2 instance type names look cryptic at first but follow a strict, learnable pattern — worth being able to decode any instance type name on sight.

m6i.2xlarge
│ │ │ └─ Size (nano, micro, small, medium, large, xlarge, 2xlarge...)
│ │ └─── Generation (6th generation)
│ └───── Additional capability (i = Intel, a = AMD, g = ARM/Graviton)
└─────── Family (m = general purpose)
LetterMeaning
tBurstable (T-series) — cheap, earns "CPU credits," throttles hard once credits run out
mGeneral purpose — balanced CPU/memory
cCompute optimized — high CPU-to-memory ratio
rMemory optimized — high memory-to-CPU ratio
i / dStorage optimized — high-speed local NVMe storage
g / pGPU/accelerated computing
g suffix (e.g. m7g)AWS Graviton (ARM) processor — often meaningfully cheaper per unit of performance
# List available instance types in a region, filtered by family
aws ec2 describe-instance-types \
  --filters "Name=instance-type,Values=m6i.*" \
  --query 'InstanceTypes[].{Type:InstanceType,vCPU:VCpuInfo.DefaultVCpus,MemMiB:MemoryInfo.SizeInMiB}' \
  --output table

# Check current On-Demand pricing for an instance type
aws pricing get-products \
  --service-code AmazonEC2 \
  --filters 'Type=TERM_MATCH,Field=instanceType,Value=m6i.large' 'Type=TERM_MATCH,Field=location,Value=US East (N. Virginia)' \
  --region us-east-1

Instance Families, By Workload Shape#

FamilyBest fitReal example
t3/t4g (burstable)Low, spiky baseline usage (dev boxes, small APIs)A staging environment that's idle most of the day
m6i/m7g (general)Typical web/app servers with balanced needsA stateless API server fleet
c6i/c7g (compute)CPU-bound workloadsA video transcoding worker, a build server
r6i/r7g (memory)In-memory caches, large data-processing jobsA self-hosted Redis node, a large in-memory analytics job
i4i/d3 (storage)Very high local disk I/OA self-hosted database wanting local NVMe over network EBS
g5/p4 (GPU)ML training/inference, renderingA model-training job

Why choosing the wrong family is such a common, costly real mistake, worth naming explicitly: picking a memory-optimized instance for a CPU-bound batch job (or vice versa) means paying for a resource dimension the workload never actually uses — directly connects to the "rightsizing" cost-optimization discussion in Part 12.


AMIs — Amazon Machine Images#

An AMI is a template for launching an instance — the OS, pre-installed software, and configuration baked in at a point in time. Every RunInstances call requires one.

# Find the latest official Amazon Linux 2023 AMI
aws ec2 describe-images \
  --owners amazon \
  --filters "Name=name,Values=al2023-ami-*-x86_64" "Name=state,Values=available" \
  --query 'sort_by(Images, &CreationDate)[-1].[ImageId,Name]' \
  --output table

# Build your own custom AMI from a configured, running instance
# (a "golden image" — bakes in your app/config so new instances
# boot ready, instead of running slow bootstrap scripts every time)
aws ec2 create-image \
  --instance-id i-0123456789abcdef0 \
  --name "my-app-v1.4.2" \
  --no-reboot

Why "golden AMIs" matter for both speed and reliability, worth stating explicitly: baking configuration into the AMI itself means a new instance is ready to serve traffic the moment it boots, instead of waiting through a lengthy user data bootstrap script (next section) — directly reducing the "cold start" scaling-lag problem already discussed generically in the Capacity Planning series (Part 3). Tools like HashiCorp Packer automate golden-AMI builds as part of a CI/CD pipeline (Automation series), keeping the image itself version-controlled and reproducible.


Launching an Instance, CLI End to End#

# Full worked example: launch a tagged, IAM-role-attached instance
# into a specific subnet with a specific security group
aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type m6i.large \
  --key-name my-keypair \
  --subnet-id subnet-0123456789abcdef0 \
  --security-group-ids sg-0123456789abcdef0 \
  --iam-instance-profile Name=MyAppRole \
  --block-device-mappings '[{"DeviceName":"/dev/xvda","Ebs":{"VolumeSize":30,"VolumeType":"gp3","Encrypted":true}}]' \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=app-server-1},{Key=Environment,Value=production}]'

# List running instances with useful, filtered columns
aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --query 'Reservations[].Instances[].{ID:InstanceId,Type:InstanceType,IP:PrivateIpAddress,Name:Tags[?Key==`Name`]|[0].Value}' \
  --output table

# Stop, start, reboot, and terminate
aws ec2 stop-instances --instance-ids i-0123456789abcdef0
aws ec2 start-instances --instance-ids i-0123456789abcdef0
aws ec2 reboot-instances --instance-ids i-0123456789abcdef0
aws ec2 terminate-instances --instance-ids i-0123456789abcdef0

User Data — Bootstrapping an Instance at Boot#

User data is a script AWS runs automatically the first time an instance boots — the classic, simpler alternative (or complement) to a golden AMI.

# Launch an instance with a user-data bootstrap script,
# installing and starting a web server on first boot
aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type t3.micro \
  --user-data '#!/bin/bash
yum update -y
yum install -y nginx
systemctl enable nginx
systemctl start nginx'

A genuinely important, real tradeoff worth stating explicitly: user data runs on EVERY boot cycle by default in some configurations and always at LEAST once on first launch — meaning it directly adds to how long a newly-launched instance takes before it's actually ready to serve traffic, which matters a great deal during an Auto Scaling scale-out event under load. This is precisely why teams graduate from user-data-heavy bootstrapping toward golden AMIs as they scale — trading a slower, more flexible boot-time setup for a faster, pre-baked one.


Instance Lifecycle States#

Diagram

Why "stopped" and "terminated" are worth distinguishing precisely, a common point of confusion: a STOPPED EC2 instance still exists (its EBS root volume persists, and you're billed for that storage, though not for compute) and can be started again later with the same instance ID; a TERMINATED instance is gone permanently — its EBS root volume is deleted by default (unless explicitly configured to persist), and the instance ID can never be reused.


EBS-Backed vs Instance-Store-Backed#

TypeRoot volume locationData on stop/terminate
EBS-backed (the default, near-universal choice today)Network-attached block storage (EBS, Part 5)Survives a STOP; deleted on TERMINATE unless configured otherwise
Instance-store-backedPhysical disk attached directly to the underlying host hardwareLost immediately on STOP or any host hardware failure — genuinely ephemeral

Instance store is still used deliberately for specific, high-throughput-local-disk workloads (some i4i/d3 family use cases) where the ephemeral nature is an accepted tradeoff for raw local NVMe speed — but for nearly everything else, EBS-backed is the right default.


Placement Groups — Controlling Physical Proximity#

A less commonly known but genuinely useful tool for controlling WHERE, physically, your instances land relative to each other.

Diagram
aws ec2 create-placement-group --group-name critical-app-spread --strategy spread

Pricing Models, Applied to EC2 Specifically#

Directly extending the general pricing overview from Part 1, now made concrete for EC2:

# Purchase a Savings Plan (commits to a $/hour spend for 1-3 years,
# in exchange for a significant discount vs On-Demand)
aws savingsplans create-savings-plan \
  --savings-plan-offering-id <offering-id> \
  --commitment 5.0

# Check current Reserved Instance recommendations based on
# actual historical usage
aws ce get-reservation-purchase-recommendation \
  --service "Amazon Elastic Compute Cloud - Compute"

Spot Instances — Mechanics and Interruption Handling#

Spot instances use AWS's unused capacity at a steep discount (often 70-90% off On-Demand), with one real catch: AWS can reclaim that capacity with only a 2-minute warning.

Diagram
# Poll for a spot interruption notice from within the instance
curl -s http://169.254.169.254/latest/meta-data/spot/instance-action

# Request Spot instances directly
aws ec2 request-spot-instances \
  --instance-count 3 \
  --type one-time \
  --launch-specification file://spot-spec.json

Why Spot is such a strong real-world cost lever specifically for stateless, horizontally-scaled fleets, worth stating explicitly: a fleet of 20 stateless web servers can lose any individual instance to a Spot interruption with near-zero impact — the Auto Scaling Group (next section) simply launches a replacement — while the exact same interruption risk would be unacceptable for a single, non-redundant database primary. This is a direct, practical application of the "design for failure" resilience-pattern philosophy (Reliability & Architecture Patterns series) — Spot doesn't create risk so much as it demands you already be resilient to individual-instance failure, which a well-designed fleet should be anyway.


Auto Scaling Groups — The Core Concept#

An Auto Scaling Group (ASG) is the concrete AWS implementation of the "self-healing, horizontally scaled fleet" concept covered generically across the Capacity Planning and Kubernetes series.

Diagram
# Create an Auto Scaling Group from a launch template
aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name app-asg \
  --launch-template LaunchTemplateName=app-lt,Version='$Latest' \
  --min-size 2 --max-size 10 --desired-capacity 4 \
  --vpc-zone-identifier "subnet-aaa,subnet-bbb,subnet-ccc" \
  --target-group-arns arn:aws:elasticloadbalancing:...:targetgroup/app-tg

# Check current ASG state
aws autoscaling describe-auto-scaling-groups \
  --auto-scaling-group-names app-asg \
  --query 'AutoScalingGroups[0].{Desired:DesiredCapacity,Min:MinSize,Max:MaxSize,Instances:length(Instances)}'

Why spreading an ASG across multiple subnets in multiple AZs is a non-negotiable default, worth stating explicitly, directly reusing the multi-AZ fault-tolerance principle from Part 1: an ASG confined to a single AZ's subnet means an entire AZ outage takes the whole fleet down at once — spreading across 3 AZs means losing one AZ only removes roughly a third of capacity, which the ASG can then compensate for by launching replacements in the surviving AZs.


Launch Templates#

A launch template is the reusable, versioned specification an ASG uses to know exactly HOW to launch each new instance — the AMI, instance type, security groups, IAM role, and user data, all in one named, versioned object.

# Create a launch template
aws ec2 create-launch-template \
  --launch-template-name app-lt \
  --version-description "v1" \
  --launch-template-data '{
    "ImageId": "ami-0abcdef1234567890",
    "InstanceType": "m6i.large",
    "IamInstanceProfile": {"Name": "MyAppRole"},
    "SecurityGroupIds": ["sg-0123456789abcdef0"],
    "UserData": "IyEvYmluL2Jhc2gKZWNobyBoZWxsbw=="
  }'

# Create a NEW version when the AMI updates, without touching
# the ASG itself — the ASG references '$Latest' or a pinned version
aws ec2 create-launch-template-version \
  --launch-template-name app-lt \
  --source-version 1 \
  --launch-template-data '{"ImageId": "ami-0newversion12345678"}'

Scaling Policies — Target Tracking, Step, and Scheduled#

Directly extending the reactive-autoscaling discussion already covered generically in the Capacity Planning series (Part 3) — these are the actual AWS policy types implementing it.

Diagram
# Target tracking policy: keep average CPU utilization near 60%
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name app-asg \
  --policy-name cpu-target-tracking \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "PredefinedMetricSpecification": {"PredefinedMetricType": "ASGAverageCPUUtilization"},
    "TargetValue": 60.0
  }'

# Scheduled scaling: scale up before a known daily 9am traffic peak
aws autoscaling put-scheduled-update-group-action \
  --auto-scaling-group-name app-asg \
  --scheduled-action-name morning-scale-up \
  --recurrence "0 9 * * *" \
  --min-size 6 --desired-capacity 8

Health Checks and Instance Replacement#

An ASG can check instance health two ways — worth understanding precisely which one is actually happening in a given setup, since they catch very different failure classes.

Health check typeWhat it verifiesCatches
EC2 status checksIs the underlying hardware/hypervisor/instance itself healthy?Hardware failures, hung kernels
ELB (Load Balancer) health checksDoes the APPLICATION respond correctly to a health-check request (Part 8)?Application-level failures (e.g. the process is running, but the app is deadlocked or returning 500s)
# Configure the ASG to use ELB health checks, not just EC2 status
aws autoscaling update-auto-scaling-group \
  --auto-scaling-group-name app-asg \
  --health-check-type ELB \
  --health-check-grace-period 120

Why relying only on EC2 status checks is a genuinely common, dangerous gap, worth stating explicitly: an instance can be perfectly healthy at the hardware/OS level while the application process on it is completely deadlocked, returning errors on every request — EC2 status checks alone would never catch this, letting a broken instance keep receiving traffic indefinitely. ELB health checks close this gap by checking the actual application behavior, directly connecting to the "readiness probe" concept already covered for Kubernetes in the Kubernetes Deep Dive series.


Lifecycle Hooks — Doing Work Before Termination#

A genuinely useful, less commonly known ASG feature: pausing an instance in a "Terminating:Wait" state before it's actually removed, giving your application time to gracefully drain.

aws autoscaling put-lifecycle-hook \
  --auto-scaling-group-name app-asg \
  --lifecycle-hook-name graceful-drain \
  --lifecycle-transition autoscaling:EC2_INSTANCE_TERMINATING \
  --heartbeat-timeout 120 \
  --default-result CONTINUE

Why this matters for connection draining specifically, worth stating explicitly: without a lifecycle hook, a scale-in event can terminate an instance mid-request, abruptly dropping active connections — the hook gives the instance a real window to finish in-flight work and deregister cleanly, directly connecting to the graceful-shutdown discussion implicit in the rolling-deployment coverage in the Automation series.


Mixed Instances Policies — Spot and On-Demand Together#

A genuinely powerful, real-world cost pattern: run a BASELINE of guaranteed On-Demand capacity, with additional capacity filled opportunistically by cheaper Spot instances.

aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name app-asg-mixed \
  --mixed-instances-policy '{
    "LaunchTemplate": {
      "LaunchTemplateSpecification": {"LaunchTemplateName": "app-lt", "Version": "$Latest"},
      "Overrides": [{"InstanceType": "m6i.large"}, {"InstanceType": "m6a.large"}, {"InstanceType": "m5.large"}]
    },
    "InstancesDistribution": {
      "OnDemandBaseCapacity": 2,
      "OnDemandPercentageAboveBaseCapacity": 25,
      "SpotAllocationStrategy": "capacity-optimized"
    }
  }' \
  --min-size 2 --max-size 20 --desired-capacity 4

Why listing MULTIPLE instance type overrides matters, worth stating explicitly — a genuinely important resilience detail: Spot capacity availability varies by specific instance type at any given moment; giving the ASG several acceptable, similarly-sized instance types to choose from (instead of just one) dramatically increases the odds of successfully finding Spot capacity, directly reusing the "avoid a single point of failure" principle, just applied to capacity availability rather than hardware.


Systems Manager — Operating Instances Without SSH#

AWS Systems Manager (SSM) Session Manager lets you get a shell on an instance without opening any inbound SSH port at all — a genuinely important security upgrade worth knowing well, directly connecting to the network-security best-practices theme carried through this whole series.

# Start an interactive shell session — no SSH key, no open port 22 needed
aws ssm start-session --target i-0123456789abcdef0

# Run a command across MANY instances at once, fleet-wide,
# without needing to SSH into each one individually
aws ssm send-command \
  --targets "Key=tag:Environment,Values=production" \
  --document-name "AWS-RunShellScript" \
  --parameters 'commands=["systemctl status nginx"]'

# Patch instances fleet-wide on a schedule
aws ssm create-association \
  --name "AWS-RunPatchBaseline" \
  --targets "Key=tag:Environment,Values=production" \
  --schedule-expression "cron(0 2 ? * SUN *)"

Why eliminating open SSH ports entirely is worth treating as a real best practice, not just a nicety: every open inbound port is attack surface (directly connects to the Zero Trust and least-privilege themes covered again in Part 9) — SSM Session Manager authenticates and authorizes through IAM (Part 2) instead, fully logged in CloudTrail, with zero need for a bastion host, a security group inbound rule for port 22, or SSH key distribution and rotation at all.


IMDSv2 — Securing the Instance Metadata Service#

Worth a dedicated, precise callout, since it's a genuinely important, frequently-tested EC2 security detail. The Instance Metadata Service (IMDS) — already referenced earlier in this part for instance profiles and Spot interruption notices — has two versions, and the difference between them matters a great deal.

Diagram
# Require IMDSv2 (reject IMDSv1 requests entirely) on a new instance
aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 --instance-type t3.micro \
  --metadata-options "HttpTokens=required,HttpPutResponseHopLimit=1"

# Enforce it on an EXISTING running instance
aws ec2 modify-instance-metadata-options \
  --instance-id i-0123456789abcdef0 \
  --http-tokens required --http-put-response-hop-limit 1

# Fetching credentials the IMDSv2 way, for reference
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/

Why a real, widely-known incident makes this concrete, worth citing directly: the 2019 Capital One breach involved exactly this attack path — a misconfigured web application firewall allowed a Server-Side Request Forgery attack that reached the EC2 instance metadata service (using IMDSv1) and stole IAM credentials, which were then used to access S3 data at scale. Requiring IMDSv2 (setting HttpTokens=required) closes this specific, real attack path — worth treating as a mandatory default on every new instance and launch template, not an optional hardening step.


Capacity Reservations and Dedicated Hosts#

Two more specialized compute purchasing options worth knowing by name for completeness, beyond On-Demand/Reserved/Spot already covered.

OptionWhat it guaranteesBest fit
On-Demand Capacity ReservationReserves capacity for a SPECIFIC instance type in a SPECIFIC AZ, guaranteed available when needed — pay whether you use it or notA critical, must-not-fail-to-launch workload (e.g. guaranteed DR failover capacity)
Dedicated HostAn entire physical server reserved for your exclusive use — full visibility into sockets/coresLicensing models that require per-socket/per-core tracking (some legacy enterprise software licenses), or strict compliance requirements around physical isolation
Dedicated InstanceRuns on hardware dedicated to your account, but WITHOUT the host-level visibility/control of a Dedicated HostA lighter-weight compliance requirement than a full Dedicated Host
# Reserve guaranteed capacity for a specific instance type/AZ —
# directly useful for a Disaster Recovery series-style
# Pilot Light or Warm Standby strategy needing GUARANTEED
# failover capacity, not just "hopefully available" On-Demand
aws ec2 create-capacity-reservation \
  --instance-type m6i.large --instance-platform Linux/UNIX \
  --availability-zone us-east-1a --instance-count 5

Why Capacity Reservations matter for a Disaster Recovery Warm Standby strategy specifically, worth connecting explicitly to the Disaster Recovery series: ordinary On-Demand EC2 launches are NOT strictly guaranteed to succeed during a genuine regional capacity crunch (a rare but real occurrence, often during widespread regional incidents when many customers are simultaneously trying to scale) — a Capacity Reservation removes this uncertainty entirely for the exact instance count/type/AZ a DR failover plan depends on.


Compute Optimizer — Data-Driven Rightsizing#

AWS Compute Optimizer analyzes actual historical CPU/memory/network utilization and recommends more cost-appropriate instance types — the concrete AWS tool implementing the "rightsizing" cost-optimization theme referenced throughout this series.

aws compute-optimizer get-ec2-instance-recommendations \
  --instance-arns arn:aws:ec2:us-east-1:123456789012:instance/i-0123456789abcdef0

# Bulk export recommendations across the whole account/organization
aws compute-optimizer export-ec2-instance-recommendations \
  --s3-destination-config '{"bucket":"compute-optimizer-exports","keyPrefix":"ec2/"}'

Why this beats manual rightsizing intuition, worth stating explicitly: Compute Optimizer bases recommendations on actual observed CPU, memory (if the CloudWatch agent is installed), network, and disk I/O utilization over a real historical window — not a guess based on the instance family's marketed specs — regularly surfacing genuinely non-obvious findings, like an instance that "looks" appropriately sized on paper but is actually running at 8% average CPU utilization and could be safely and substantially downsized.


Warm Pools — Solving the Cold-Start Problem for ASGs#

Directly extending the "cold-start problem" already introduced generically in the Capacity Planning series (Part 3) — Warm Pools keep a set of PRE-INITIALIZED instances in a stopped or standby state, ready to be put into service far faster than launching and bootstrapping a brand-new instance from scratch.

Diagram
aws autoscaling put-warm-pool \
  --auto-scaling-group-name app-asg \
  --pool-state Stopped \
  --min-size 2 --max-group-prepared-capacity 6

Why this specifically helps workloads with a genuinely slow bootstrap process, worth stating precisely: an application with heavy startup work (large in-memory cache warming, a slow JVM/framework startup, a lengthy configuration-fetch step) benefits the most — the Warm Pool absorbs that startup latency AHEAD of the actual demand spike, rather than paying for it in real time exactly when speed matters most, directly reducing the same cold-start lag the Capacity Planning series flagged as a real autoscaling limitation.


EC2 Compute Best Practices — The Consolidated Checklist#

  • Match instance family to actual resource bottleneck (CPU/memory/storage/GPU) — verify with Compute Optimizer rather than intuition alone.
  • Require IMDSv2 on every instance and launch template — a cheap, essentially zero-downside fix closing a real, historically-exploited attack path.
  • Use golden AMIs for anything scaling frequently — trading upfront build complexity for dramatically faster, more reliable scale-out.
  • Spread every Auto Scaling Group across at least 3 AZs, with ELB (not just EC2) health checks configured.
  • Prefer SSM Session Manager over open inbound SSH — eliminates an entire class of attack surface and key-management burden.
  • Use mixed instances policies (On-Demand baseline + Spot burst) for stateless, horizontally-redundant fleets to capture real cost savings without availability risk.
  • Configure lifecycle hooks for graceful termination on any ASG serving live user traffic.
  • Reserve guaranteed capacity for anything a DR plan structurally depends on — ordinary On-Demand capacity is not a strict guarantee during a regional capacity crunch.
  • Consider Warm Pools for workloads with genuinely slow bootstrap times, to reduce cold-start lag during real demand spikes.

Part 3 CLI Cheat Sheet#

AreaCommandPurpose
Instancesaws ec2 run-instancesLaunch a new instance
Instancesaws ec2 describe-instancesList/inspect instances
Instancesaws ec2 stop-instances / start-instances / terminate-instancesLifecycle control
AMIsaws ec2 create-imageBuild a golden AMI from a running instance
AMIsaws ec2 describe-imagesFind available AMIs
Metadataaws ec2 modify-instance-metadata-optionsEnforce IMDSv2
ASGaws autoscaling create-auto-scaling-groupCreate an Auto Scaling Group
ASGaws autoscaling put-scaling-policyAdd a target-tracking/step scaling policy
ASGaws autoscaling put-scheduled-update-group-actionAdd scheduled scaling
ASGaws autoscaling put-lifecycle-hookAdd graceful-termination handling
ASGaws autoscaling put-warm-poolEnable a warm pool for faster scale-out
Spotaws ec2 request-spot-instancesRequest Spot capacity directly
Systems Manageraws ssm start-sessionShell access with no open SSH port
Systems Manageraws ssm send-commandRun a command fleet-wide
Costaws compute-optimizer get-ec2-instance-recommendationsData-driven rightsizing recommendations
Capacityaws ec2 create-capacity-reservationReserve guaranteed capacity

Common Mistakes#

MistakeWhy It's WrongFix
Picking an instance family based on price alone, ignoring workload shapeA CPU-bound job on a memory-optimized instance wastes money on unused memory capacityMatch the instance family to the actual resource bottleneck of the workload
Relying only on lengthy user data bootstrap scripts at real scaleSlows down every scale-out event, worsening the cold-start problem under loadBake configuration into a golden AMI once the setup stabilizes
Confining an Auto Scaling Group to a single subnet/AZAn entire AZ outage takes down the whole fleet at onceSpread the ASG across subnets in at least 3 AZs
Using only EC2 status checks for ASG healthMisses application-level failures (deadlocks, 500s) where the instance itself looks healthyConfigure ELB health checks so the ASG checks actual application behavior
Running stateful, non-redundant workloads on Spot instancesA 2-minute interruption notice isn't enough time to safely handle non-redundant, stateful lossReserve Spot for stateless, horizontally-redundant workloads; keep stateful primaries on On-Demand/Reserved
Opening inbound SSH (port 22) to instances for routine accessUnnecessary attack surface and key-management burdenUse SSM Session Manager instead, authenticated through IAM with zero open inbound ports

Worked Practice Problems#

Problem 1: An Auto Scaling Group configured with only EC2 status health checks shows all instances as "healthy" and "InService," yet users are reporting 500 errors from the application. What's the most likely gap, and what's the fix?

Answer: EC2 status checks only verify the underlying hardware/hypervisor/instance is running — they say nothing about whether the application process on top of it is actually functioning correctly. An application can be fully deadlocked or returning 500 errors on every request while still passing EC2 status checks perfectly, since the instance and OS are genuinely healthy even though the app isn't. The fix is switching the ASG's health check type to ELB, so instance health is determined by whether the application actually responds correctly to the load balancer's health-check requests — this would immediately start flagging and replacing the broken instances instead of leaving them in service indefinitely.

Problem 2: A team wants to reduce EC2 costs for their stateless, horizontally-scaled API fleet (currently 100% On-Demand) without risking availability during a traffic spike. What specific ASG configuration would you recommend, and why does it balance both goals?

Answer: A mixed instances policy with an On-Demand base capacity covering the normal steady-state load, and additional scale-out capacity filled via Spot instances using the capacity-optimized allocation strategy across several similarly-sized instance type overrides. This balances both goals because the guaranteed baseline (On-Demand) ensures a stable floor of capacity that's never subject to Spot interruption, while the burst capacity needed only during traffic spikes — which is exactly when Spot instances are most valuable, since even a lost Spot instance is quickly replaced by the ASG — captures the bulk of the cost savings. Listing multiple instance type overrides further reduces interruption risk by giving the ASG many pools of Spot capacity to draw from instead of depending on just one instance type's availability.

Problem 3: During a scale-in event, a team notices that in-flight user requests are occasionally dropped mid-response when an instance is terminated by the Auto Scaling Group. What ASG feature addresses this directly, and how does it work?

Answer: An ASG lifecycle hook on the EC2_INSTANCE_TERMINATING transition. Without it, the ASG can terminate an instance immediately once it's selected for scale-in, abruptly cutting off any in-flight requests. With the hook configured, the instance instead enters a Terminating:Wait state for a configurable heartbeat timeout window, during which the application (or an automation script watching for this state) can deregister the instance from the load balancer's target group, allow existing connections to finish draining, and only then signal completion — giving genuinely graceful shutdown behavior instead of an abrupt cutoff.

Problem 4: A security audit flags that several production EC2 instances are still using IMDSv1, and cites a real risk of credential theft via Server-Side Request Forgery. The application team argues their application has no known SSRF vulnerability today, so this seems like a low-priority finding. How would you respond to that reasoning, and what's the fix?

Answer: The argument "no KNOWN SSRF vulnerability today" is exactly the reasoning that a real, widely-cited incident (the 2019 Capital One breach) also shared in hindsight — SSRF vulnerabilities are commonly introduced by dependencies, third-party libraries, or future code changes an application team may not fully control or foresee, and IMDSv1's complete lack of a required PUT step means ANY future SSRF-capable request path becomes a direct credential-theft path with zero additional attacker effort. Requiring IMDSv2 (HttpTokens=required) is a cheap, essentially zero-downside structural fix that closes this entire attack class regardless of whether an SSRF vulnerability exists today or is introduced six months from now — worth treating as a mandatory baseline, not a risk-accepted "we'll fix it if we ever find an SSRF bug" item.

Problem 5: A team is designing their Disaster Recovery Warm Standby environment (per the strategy already covered in the Disaster Recovery series) and plans to rely on standard On-Demand EC2 launches to scale up the standby region during an actual failover event. A colleague raises a concern that this plan could fail at the exact moment it's needed most. What's the concern, and what AWS feature directly addresses it?

Answer: The concern is that On-Demand EC2 capacity, while normally reliable, is not strictly guaranteed to be available on demand during a genuine, large-scale regional capacity crunch — which is precisely the kind of scenario a real disaster recovery failover might coincide with (e.g. many customers simultaneously scaling up during a widespread regional incident). An On-Demand Capacity Reservation for the specific instance type, count, and AZ the DR plan depends on removes this uncertainty entirely, guaranteeing that capacity is actually available and reserved specifically for the failover scenario, rather than hoping ordinary On-Demand capacity will be available at the exact moment it's needed most.


Summary and What's Next#

  • EC2 instance types follow a decodable naming pattern (family, generation, capability, size) — matching the family to the actual workload bottleneck (CPU/memory/storage/GPU) is a real cost and performance decision, not a minor detail.
  • Golden AMIs trade slower per-instance-boot flexibility for dramatically faster scale-out readiness compared to relying solely on user data bootstrap scripts.
  • Spot instances offer steep discounts in exchange for a 2-minute interruption notice — an excellent fit for stateless, horizontally-redundant fleets, and a poor fit for non-redundant stateful workloads.
  • Auto Scaling Groups are the concrete AWS implementation of the self-healing, horizontally-scaled fleet pattern — always spread across multiple AZs, always configured with ELB (not just EC2) health checks.
  • Target tracking, step, and scheduled scaling policies map directly onto the reactive vs. predictive autoscaling concepts already covered generically in the Capacity Planning series.
  • Lifecycle hooks provide genuinely graceful termination, and mixed instances policies blend On-Demand reliability with Spot cost savings.
  • Systems Manager Session Manager eliminates the need for open inbound SSH entirely, authenticating instance access through IAM instead — a real security best practice worth adopting by default.

Continue to Part 4 (04-networking-vpc-deep-dive.md) for a comprehensive deep dive into AWS networking — VPCs, subnets, routing, connectivity between networks, and the security boundaries that govern every one of the instances covered in this part.