AWS re/Start Lab · Compute & High Availability

Scaling and Load Balancing Your Architecture

Built a fault-tolerant, horizontally scalable web tier on AWS by combining a custom AMI, an Application Load Balancer in public subnets across two AZs, and an Auto Scaling Group in private subnets driven by a CloudWatch target-tracking policy on CPU utilization.

Lab Summary

Captured a snapshot of Web Server 1 as a custom AMI, fronted the application with an internet-facing ALB spanning two public subnets, and replaced the single server with an Auto Scaling group of t3.micro instances running in private subnets. A target tracking policy at 50% average CPU drives scaling decisions, with health checks delegated to the ELB.

Fault Tolerance

ALB spreads traffic across two AZs (us-west-2a, us-west-2b). The ASG keeps a minimum of two instances and the ELB health check replaces any instance that stops responding on HTTP/80.

Elastic Capacity

Min=2, Desired=2, Max=4. When average CPU crosses the 50% target, CloudWatch's auto-generated AlarmHigh fires and the ASG launches additional instances from the launch template until load drops.

Target Architecture

Defense in depth: the only public-facing surface is the load balancer. Application instances live in private subnets and only accept traffic from the ALB's security group.

Edge / Public
LabELB (Application Load Balancer, internet-facing) in Public Subnet 1 + Public Subnet 2 · Web Security Group (HTTP 80)
Routing
Target group lab-target-group · type Instance · HTTP:80 · health checks per target on the application port
Compute / Private
Lab Auto Scaling Group launches t3.micro instances from Web Server AMI into Private Subnet 1 + Private Subnet 2 · health-check type ELB
Control plane
CloudWatch target-tracking policy at 50% average CPU · auto-creates AlarmHigh (scale out) and AlarmLow (scale in)
Why this shape? Putting the instances in private subnets eliminates direct internet exposure. The ALB is the only ingress point, so security group rules and WAF policies have a single chokepoint to enforce. The Auto Scaling group restores capacity automatically when an instance dies or when load drops one below the desired count.

Step-by-Step Walkthrough

All work was done from the AWS Management Console. The lab provided the VPC, subnets, security groups, and a pre-running Web Server 1 instance.

01

Create an AMI from Web Server 1

The AMI freezes the boot disk of the running web server so the Auto Scaling group can launch identical replicas later. From EC2 > Instances, selected Web Server 1 and chose Actions > Image and templates > Create image, naming it Web Server AMI. AWS started the snapshot process and returned the new AMI ID.

Why an AMI? Horizontal scaling needs identical, repeatable instances. The AMI is the immutable artifact the launch template references — any instance the ASG creates will start from this exact image.
02

Create the Application Load Balancer

From EC2 > Load Balancers, created LabELB with the following configuration:

SettingValue
TypeApplication Load Balancer (HTTP / Layer 7)
SchemeInternet-facing
IP typeIPv4
VPCvpc-086e5acc2e93e0c11 (Lab VPC)
Availability ZonesPublic Subnet 1 (us-west-2a) + Public Subnet 2 (us-west-2b)
Security groupWeb Security Group (HTTP 80 from 0.0.0.0/0)
ListenerHTTP:80 → target group lab-target-group

The target group lab-target-group was created in the same flow with target type Instance, protocol HTTP:80, and the lab's default health-check settings. The ALB's public DNS name became the entry point for the application:

LabELB-642450134.us-west-2.elb.amazonaws.com

03

Create the Launch Template

The launch template tells the Auto Scaling group how to launch each instance. Created lab-app-launch-template with the following:

SettingValue
AMIWeb Server AMI (the one from Task 1)
Instance typet3.micro
Key pairNone (no SSH access from outside)
Security groupWeb Security Group
GuidanceProvide guidance for EC2 Auto Scaling enabled
04

Create the Auto Scaling Group

The core of the lab. Used the Create Auto Scaling group link from the launch template's Next Steps panel and applied this configuration:

SettingValue
NameLab Auto Scaling Group
Launch templatelab-app-launch-template · Version Default
VPCLab VPC
SubnetsPrivate Subnet 1 (10.0.1.0/24) + Private Subnet 2 (10.0.3.0/24)
Load balancingAttach to existing target group lab-target-group | HTTP
Health check typeELB (not EC2)
Group sizeMin = 2 · Desired = 2 · Max = 4
Scaling policyTarget tracking · Average CPU utilization · target = 50%
TagName = Lab Instance

Once submitted, the ASG entered Updating capacity and started launching the first two instances into the private subnets.

Why ELB health checks? An ELB health check validates the instance actually responds on the application port (HTTP/80). An EC2 health check only looks at hypervisor-level status checks, which can pass even when the web server process is dead. Choosing ELB ties the ASG's replacement logic to real application availability.
05

Verify balancing and health checks

After the ASG finished launching the first batch, both new instances registered with lab-target-group and passed their HTTP health checks. At that point Web Server 1 (the source of the AMI) was deleted — the ASG had replaced its role with two identical instances across both AZs.

The ALB DNS name from Task 2 served the lab's Load Test application across both instances. Traffic was distributed by the ALB on each request.

06

Trigger auto scaling with CPU load

The Load Test page inside the application drives CPU upward on whichever instance serves the request. The target-tracking policy from Task 4 created two CloudWatch alarms automatically:

  • TargetTracking-Lab Auto Scaling Group-AlarmHigh-... — fires when average CPU stays above the 50% target, signaling scale out.
  • TargetTracking-Lab Auto Scaling Group-AlarmLow-... — fires when average CPU drops well below the target, signaling scale in.

On a freshly created cluster with idle instances, AlarmLow is the one In alarm and AlarmHigh is OK — which is the state the screenshot below captures, before the Load Test pushed CPU up.

Evidence note — honest read. The original screenshot file was named alarmahigh.png, but it actually shows the baseline state: AlarmLow is the one in alarm and AlarmHigh is OK. The file has been renamed to 06-cloudwatch-alarms-baseline.png in this report.

Expected transition during the Load Test (not captured as a screenshot): after a few minutes of sustained CPU above the 50% target, AlarmHigh moves to In alarm and the ASG launches additional instances up to the configured maximum of 4. Once the load stops, CPU drops back, AlarmLow returns to In alarm, and the ASG gradually terminates instances down to the minimum of 2.

Key Learnings

What Was Actually Learned

Capturing a running EC2 instance as a custom AMI to use as the immutable artifact for horizontal scaling.
Designing a two-AZ topology with an internet-facing ALB in public subnets and the application fleet in private subnets.
Wiring an Auto Scaling group to a launch template and a target group, and choosing the ELB health-check type so unhealthy instances are replaced based on real HTTP availability.
Declaring a target tracking policy on average CPU and letting CloudWatch generate the AlarmHigh / AlarmLow pair automatically.
Reading the two-alarm state correctly: in an idle cluster the low alarm is the one firing, not the high one.

Technical Conclusion

The combined pattern — AMI + ALB + ASG + target tracking — is the canonical AWS shape for a stateless web tier. The launch template separates what an instance looks like from when to create one; the ASG owns lifecycle and replacement; the ALB owns ingress and traffic distribution; CloudWatch owns the feedback loop.

The most subtle choice is the ELB health-check type: it shifts the definition of "healthy" from the hypervisor up to the application layer, which is what fault tolerance actually needs in practice. The optional CLI challenge from the lab guide was not executed in this run — the console flow was enough to verify the architecture end to end.