# AWS Cloud Architecture — Part 3: Compute: EC2 & Auto Scaling

> **Series:** AWS Cloud Architecture (3 of 12)
> **Part 1:** `01-fundamentals-and-account-structure.md` — Fundamentals & Account Structure
> **Part 2:** `02-iam-and-identity.md` — IAM & Identity
> **Part 3:** This file — Compute: EC2 & Auto Scaling
> **Part 4:** `04-networking-vpc-deep-dive.md` — Networking: VPC Deep Dive
> **Part 5:** `05-storage-s3-ebs-efs.md` — Storage: S3, EBS & EFS
> **Part 6:** `06-managed-databases-and-data-services.md` — Managed Databases & Data Services
> **Part 7:** `07-containers-and-serverless.md` — Containers & Serverless
> **Part 8:** `08-load-balancing-cdn-and-dns.md` — Load Balancing, CDN & DNS
> **Part 9:** `09-security-and-compliance.md` — Security & Compliance
> **Part 10:** `10-monitoring-logging-and-tracing.md` — Monitoring, Logging & Tracing
> **Part 11:** `11-cicd-iac-and-messaging.md` — CI/CD, IaC & Messaging
> **Part 12:** `12-multi-region-dr-migration-and-cheatsheet.md` — Multi-Region, DR, Migration & Cheat Sheet
> **Questions:** `questions.md`

## Table of Contents

1. [EC2 — The Original AWS Service](#ec2--the-original-aws-service)
2. [Instance Types — Reading the Naming Convention](#instance-types--reading-the-naming-convention)
3. [Instance Families, By Workload Shape](#instance-families-by-workload-shape)
4. [AMIs — Amazon Machine Images](#amis--amazon-machine-images)
5. [Launching an Instance, CLI End to End](#launching-an-instance-cli-end-to-end)
6. [User Data — Bootstrapping an Instance at Boot](#user-data--bootstrapping-an-instance-at-boot)
7. [Instance Lifecycle States](#instance-lifecycle-states)
8. [EBS-Backed vs Instance-Store-Backed](#ebs-backed-vs-instance-store-backed)
9. [Placement Groups — Controlling Physical Proximity](#placement-groups--controlling-physical-proximity)
10. [Pricing Models, Applied to EC2 Specifically](#pricing-models-applied-to-ec2-specifically)
11. [Spot Instances — Mechanics and Interruption Handling](#spot-instances--mechanics-and-interruption-handling)
12. [Auto Scaling Groups — The Core Concept](#auto-scaling-groups--the-core-concept)
13. [Launch Templates](#launch-templates)
14. [Scaling Policies — Target Tracking, Step, and Scheduled](#scaling-policies--target-tracking-step-and-scheduled)
15. [Health Checks and Instance Replacement](#health-checks-and-instance-replacement)
16. [Lifecycle Hooks — Doing Work Before Termination](#lifecycle-hooks--doing-work-before-termination)
17. [Mixed Instances Policies — Spot and On-Demand Together](#mixed-instances-policies--spot-and-on-demand-together)
18. [Systems Manager — Operating Instances Without SSH](#systems-manager--operating-instances-without-ssh)
19. [IMDSv2 — Securing the Instance Metadata Service](#imdsv2--securing-the-instance-metadata-service)
20. [Capacity Reservations and Dedicated Hosts](#capacity-reservations-and-dedicated-hosts)
21. [Compute Optimizer — Data-Driven Rightsizing](#compute-optimizer--data-driven-rightsizing)
22. [Warm Pools — Solving the Cold-Start Problem for ASGs](#warm-pools--solving-the-cold-start-problem-for-asgs)
23. [EC2 Compute Best Practices — The Consolidated Checklist](#ec2-compute-best-practices--the-consolidated-checklist)
24. [Part 3 CLI Cheat Sheet](#part-3-cli-cheat-sheet)
25. [Common Mistakes](#common-mistakes)
20. [Worked Practice Problems](#worked-practice-problems)
21. [Summary and What's Next](#summary-and-whats-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.

```mermaid
graph TD
    Request["API call:<br/>RunInstances"] --> Hypervisor["AWS's hypervisor<br/>(Nitro System) launches<br/>a virtual machine on<br/>physical hardware"]
    Hypervisor --> Instance["Running EC2 instance,<br/>with its own private/public<br/>IP, EBS volumes attached,<br/>and IAM instance profile"]
```

---

## 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)
```

| Letter | Meaning |
|---|---|
| `t` | Burstable (T-series) — cheap, earns "CPU credits," throttles hard once credits run out |
| `m` | General purpose — balanced CPU/memory |
| `c` | Compute optimized — high CPU-to-memory ratio |
| `r` | Memory optimized — high memory-to-CPU ratio |
| `i` / `d` | Storage optimized — high-speed local NVMe storage |
| `g` / `p` | GPU/accelerated computing |
| `g` suffix (e.g. `m7g`) | AWS Graviton (ARM) processor — often meaningfully cheaper per unit of performance |

```bash
# 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

| Family | Best fit | Real 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 needs | A stateless API server fleet |
| `c6i`/`c7g` (compute) | CPU-bound workloads | A video transcoding worker, a build server |
| `r6i`/`r7g` (memory) | In-memory caches, large data-processing jobs | A self-hosted Redis node, a large in-memory analytics job |
| `i4i`/`d3` (storage) | Very high local disk I/O | A self-hosted database wanting local NVMe over network EBS |
| `g5`/`p4` (GPU) | ML training/inference, rendering | A 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.

```bash
# 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

```bash
# 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.

```bash
# 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

```mermaid
stateDiagram-v2
    [*] --> pending
    pending --> running
    running --> stopping
    stopping --> stopped
    stopped --> pending
    running --> shutting_down
    shutting_down --> terminated
    stopped --> [*]
    terminated --> [*]
```

**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

| Type | Root volume location | Data 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-backed** | Physical disk attached directly to the underlying host hardware | Lost 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.

```mermaid
graph TD
    Cluster["CLUSTER placement group:<br/>pack instances close together<br/>on the SAME underlying hardware<br/>for lowest possible network<br/>latency between them"] --> ClusterUse["Best for: tightly-coupled<br/>HPC/low-latency workloads<br/>(e.g. a distributed training job)"]

    Spread["SPREAD placement group:<br/>force instances onto DIFFERENT<br/>underlying hardware racks"] --> SpreadUse["Best for: a small number of<br/>CRITICAL instances that must<br/>never share a single point<br/>of hardware failure"]

    Partition["PARTITION placement group:<br/>group instances into logical<br/>partitions, each on separate<br/>hardware — used by large<br/>distributed systems"] --> PartitionUse["Best for: large distributed<br/>data stores (e.g. a self-hosted<br/>Kafka or Cassandra cluster)<br/>that already handle their own<br/>partition-aware replication"]
```

```bash
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:

```bash
# 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**.

```mermaid
sequenceDiagram
    participant AWS
    participant Instance as Spot Instance
    participant App as Your Application

    AWS->>Instance: Capacity needed elsewhere -<br/>interruption notice issued
    Instance->>Instance: EC2 Instance Metadata Service<br/>now exposes a termination notice
    App->>Instance: Application polls IMDS,<br/>sees the 2-minute warning
    App->>App: Gracefully drains connections,<br/>checkpoints work, deregisters<br/>from load balancer
    Instance->>Instance: Instance reclaimed after<br/>the 2-minute window
```

```bash
# 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.

```mermaid
graph TD
    ASG["Auto Scaling Group<br/>(desired: 4, min: 2, max: 10)"] --> I1["Instance 1"]
    ASG --> I2["Instance 2"]
    ASG --> I3["Instance 3"]
    ASG --> I4["Instance 4"]
    ASG -.->|"one instance fails<br/>a health check"| Replace["ASG automatically<br/>terminates it and<br/>launches a replacement -<br/>maintaining desired count"]
```

```bash
# 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.

```bash
# 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.

```mermaid
graph TD
    TT["TARGET TRACKING:<br/>'keep average CPU at 60%' -<br/>AWS automatically calculates<br/>how much to scale"] --> TTUse["Simplest, most common —<br/>a true 'set it and forget it'<br/>policy"]
    Step["STEP SCALING:<br/>different scaling AMOUNTS<br/>for different ALARM<br/>SEVERITY thresholds"] --> StepUse["More control for workloads<br/>with sharply non-linear<br/>scaling needs"]
    Sched["SCHEDULED SCALING:<br/>scale at a KNOWN time<br/>(e.g. before a daily<br/>traffic peak)"] --> SchedUse["Directly implements the<br/>'predictive scaling'<br/>concept from Capacity<br/>Planning series, Part 3"]
```

```bash
# 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 type | What it verifies | Catches |
|---|---|---|
| **EC2 status checks** | Is the underlying hardware/hypervisor/instance itself healthy? | Hardware failures, hung kernels |
| **ELB (Load Balancer) health checks** | Does 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) |

```bash
# 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.

```bash
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.

```bash
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.

```bash
# 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.

```mermaid
graph TD
    V1["IMDSv1: a simple,<br/>UNAUTHENTICATED HTTP GET<br/>request to 169.254.169.254<br/>returns credentials"] --> V1Risk["🚨 Vulnerable to Server-Side<br/>Request Forgery (SSRF) —<br/>a compromised app that can<br/>be tricked into making an<br/>HTTP request can steal the<br/>instance's IAM credentials"]

    V2["IMDSv2: requires a<br/>SESSION TOKEN, fetched via<br/>a PUT request first, then<br/>used as a header on the<br/>actual GET request"] --> V2Safe["✅ A classic SSRF<br/>vulnerability (which can<br/>only make simple GET-style<br/>requests) generally CANNOT<br/>complete the required PUT<br/>step — closing the attack<br/>path"]
```

```bash
# 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.

| Option | What it guarantees | Best fit |
|---|---|---|
| **On-Demand Capacity Reservation** | Reserves capacity for a SPECIFIC instance type in a SPECIFIC AZ, guaranteed available when needed — pay whether you use it or not | A critical, must-not-fail-to-launch workload (e.g. guaranteed DR failover capacity) |
| **Dedicated Host** | An entire physical server reserved for your exclusive use — full visibility into sockets/cores | Licensing models that require per-socket/per-core tracking (some legacy enterprise software licenses), or strict compliance requirements around physical isolation |
| **Dedicated Instance** | Runs on hardware dedicated to your account, but WITHOUT the host-level visibility/control of a Dedicated Host | A lighter-weight compliance requirement than a full Dedicated Host |

```bash
# 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.

```bash
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.

```mermaid
graph TD
    Demand["Sudden traffic spike"] --> Normal["WITHOUT a Warm Pool:<br/>ASG launches a brand-new<br/>instance — full boot +<br/>user-data bootstrap time<br/>before it's ready"]
    Demand2["Sudden traffic spike"] --> Warm["WITH a Warm Pool:<br/>ASG pulls a PRE-BOOTED,<br/>pre-bootstrapped instance<br/>from the pool — ready in<br/>a fraction of the time"]
```

```bash
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

| Area | Command | Purpose |
|---|---|---|
| Instances | `aws ec2 run-instances` | Launch a new instance |
| Instances | `aws ec2 describe-instances` | List/inspect instances |
| Instances | `aws ec2 stop-instances` / `start-instances` / `terminate-instances` | Lifecycle control |
| AMIs | `aws ec2 create-image` | Build a golden AMI from a running instance |
| AMIs | `aws ec2 describe-images` | Find available AMIs |
| Metadata | `aws ec2 modify-instance-metadata-options` | Enforce IMDSv2 |
| ASG | `aws autoscaling create-auto-scaling-group` | Create an Auto Scaling Group |
| ASG | `aws autoscaling put-scaling-policy` | Add a target-tracking/step scaling policy |
| ASG | `aws autoscaling put-scheduled-update-group-action` | Add scheduled scaling |
| ASG | `aws autoscaling put-lifecycle-hook` | Add graceful-termination handling |
| ASG | `aws autoscaling put-warm-pool` | Enable a warm pool for faster scale-out |
| Spot | `aws ec2 request-spot-instances` | Request Spot capacity directly |
| Systems Manager | `aws ssm start-session` | Shell access with no open SSH port |
| Systems Manager | `aws ssm send-command` | Run a command fleet-wide |
| Cost | `aws compute-optimizer get-ec2-instance-recommendations` | Data-driven rightsizing recommendations |
| Capacity | `aws ec2 create-capacity-reservation` | Reserve guaranteed capacity |

---

## Common Mistakes

| Mistake | Why It's Wrong | Fix |
|---|---|---|
| Picking an instance family based on price alone, ignoring workload shape | A CPU-bound job on a memory-optimized instance wastes money on unused memory capacity | Match the instance family to the actual resource bottleneck of the workload |
| Relying only on lengthy `user data` bootstrap scripts at real scale | Slows down every scale-out event, worsening the cold-start problem under load | Bake configuration into a golden AMI once the setup stabilizes |
| Confining an Auto Scaling Group to a single subnet/AZ | An entire AZ outage takes down the whole fleet at once | Spread the ASG across subnets in at least 3 AZs |
| Using only EC2 status checks for ASG health | Misses application-level failures (deadlocks, 500s) where the instance itself looks healthy | Configure ELB health checks so the ASG checks actual application behavior |
| Running stateful, non-redundant workloads on Spot instances | A 2-minute interruption notice isn't enough time to safely handle non-redundant, stateful loss | Reserve Spot for stateless, horizontally-redundant workloads; keep stateful primaries on On-Demand/Reserved |
| Opening inbound SSH (port 22) to instances for routine access | Unnecessary attack surface and key-management burden | Use 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.
