Files
kodekloud-engineer/aws-44.md

10 KiB
Raw Permalink Blame History

Task 44

The DevOps team is tasked with setting up a highly available web application using AWS. To achieve this, they plan to use an Auto Scaling Group (ASG) to ensure that the required number of EC2 instances are always running, and an Application Load Balancer (ALB) to distribute traffic across these instances. The goal of this task is to set up an ASG that automatically scales EC2 instances based on CPU utilization, and an ALB that directs incoming traffic to the instances. The EC2 instances should have Nginx installed and running to serve web traffic.

Create an EC2 launch template named xfusion-launch-template that specifies the configuration for the EC2 instances, including the Amazon Linux 2 AMI, t2.micro instance type, and a security group that allows HTTP traffic on port 80. Add a User Data script to the launch template to install Nginx on the EC2 instances when they are launched. The script should install Nginx, start the Nginx service, and enable it to start on boot. Create an Auto Scaling Group named xfusion-asg that uses the launch template and ensures a minimum of 1 instance, desired capacity is 1 instance and a maximum of 2 instances are running based on CPU utilization. Set the target CPU utilization to 50%. Create a target group named xfusion-tg, an Application Load Balancer named xfusion-alb and configure it to listen on port 80. Ensure the ALB is associated with the Auto Scaling Group and distributes traffic across the instances. Configure health checks on the ALB to ensure it routes traffic only to healthy instances. Verify that the ALB's DNS name is accessible and that it displays the default Nginx page served by the EC2 instances.

Solution

ASG + ALB Auto-Scaling Task (xfusion)

The full auto-scaling stack — launch template → ASG with target-tracking → ALB/TG wired to the ASG. The concept that ties it together: you don't register instances into the target group manually; you attach the TG to the ASG (--target-group-arns), and the ASG auto-registers every instance it launches (and deregisters on scale-in). And scaling "based on CPU at 50%" = a target-tracking scaling policy on the ASGAverageCPUUtilization metric — not a manual CloudWatch alarm.

Version note — Amazon Linux 2 is EOL

The task says "Amazon Linux 2 AMI," but AL2 hit end-of-life June 30, 2026 (already past). The AMIs are still launchable (they don't vanish at EOL, they just stop getting patches), so if the grader specifically checks for AL2, use the AL2 alias below. But for anything real you'd use AL2023. Both aliases given — pick per what the grader wants. On AL2 the Nginx install uses amazon-linux-extras; on AL2023 it's dnf. The user-data below is written for AL2 to match the task literally.

Run on aws-client.

Phase 1 — Security group (HTTP 80)

REGION=us-east-1

VPC_ID=$(aws ec2 describe-vpcs --filters Name=isDefault,Values=true \
  --region $REGION --query 'Vpcs[0].VpcId' --output text)

SG_ID=$(aws ec2 create-security-group \
  --group-name xfusion-web-sg \
  --description "HTTP 80 for xfusion ASG" \
  --vpc-id $VPC_ID --region $REGION \
  --query 'GroupId' --output text)

aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID --protocol tcp --port 80 --cidr 0.0.0.0/0 --region $REGION

Phase 2 — Launch template (with base64 user-data)

# AL2 AMI via SSM alias (EOL but still launchable). AL2023 alternative commented.
AMI_ID=$(aws ssm get-parameters \
  --names /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 \
  --region $REGION --query 'Parameters[0].Value' --output text)
# AL2023: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64

# User-data for AL2 (amazon-linux-extras) — must be base64 for launch templates
USERDATA=$(base64 -w0 << 'EOF'
#!/bin/bash
amazon-linux-extras install nginx1 -y
systemctl start nginx
systemctl enable nginx
EOF
)
# (AL2023 user-data would be: dnf install -y nginx; systemctl enable --now nginx)

aws ec2 create-launch-template \
  --launch-template-name xfusion-launch-template \
  --region $REGION \
  --launch-template-data "{
    \"ImageId\": \"$AMI_ID\",
    \"InstanceType\": \"t2.micro\",
    \"SecurityGroupIds\": [\"$SG_ID\"],
    \"UserData\": \"$USERDATA\"
  }"

Key point: in a launch template, UserData must be base64-encoded (unlike run-instances --user-data file:// which encodes for you). base64 -w0 produces a single line with no wrapping — critical, since embedded newlines break the JSON.

Phase 3 — Target group + ALB + listener

read -r SUBNET1 SUBNET2 <<< $(aws ec2 describe-subnets \
  --filters "Name=vpc-id,Values=$VPC_ID" "Name=map-public-ip-on-launch,Values=true" \
  --region $REGION --query 'Subnets[].SubnetId | [0:2]' --output text)

# Target group with explicit health check on "/"
TG_ARN=$(aws elbv2 create-target-group \
  --name xfusion-tg \
  --protocol HTTP --port 80 \
  --vpc-id $VPC_ID --target-type instance \
  --health-check-protocol HTTP \
  --health-check-path / \
  --health-check-interval-seconds 30 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 2 \
  --region $REGION \
  --query 'TargetGroups[0].TargetGroupArn' --output text)

# ALB across two AZs, wearing the web SG
ALB_ARN=$(aws elbv2 create-load-balancer \
  --name xfusion-alb \
  --subnets $SUBNET1 $SUBNET2 \
  --security-groups $SG_ID \
  --scheme internet-facing --type application \
  --region $REGION \
  --query 'LoadBalancers[0].LoadBalancerArn' --output text)

# Listener on 80 -> forward to TG
aws elbv2 create-listener \
  --load-balancer-arn $ALB_ARN \
  --protocol HTTP --port 80 \
  --default-actions Type=forward,TargetGroupArn=$TG_ARN \
  --region $REGION

The health check (task req #5) is configured on the target group: HTTP GET / every 30s, healthy after 2 passes, unhealthy after 2 fails. The ALB only routes to targets the TG reports healthy — that's the "route traffic only to healthy instances" requirement.

Phase 4 — Auto Scaling Group (attached to the TG)

aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name xfusion-asg \
  --launch-template "LaunchTemplateName=xfusion-launch-template,Version=\$Latest" \
  --min-size 1 --max-size 2 --desired-capacity 1 \
  --vpc-zone-identifier "$SUBNET1,$SUBNET2" \
  --target-group-arns $TG_ARN \
  --health-check-type ELB \
  --health-check-grace-period 90 \
  --region $REGION

The two crucial bits here:

  • --target-group-arns $TG_ARN is what "associates the ALB with the ASG." The ASG now auto-registers each instance it launches into the TG (and deregisters on scale-in). You never call register-targets manually — that's the whole point of ASG+ALB integration.
  • --health-check-type ELB makes the ASG use the load balancer's health check to decide instance health, not just the EC2 status check. So an instance whose Nginx died gets replaced even though the VM is "running." --health-check-grace-period 90 gives user-data time to install Nginx before health checks start counting (otherwise the ASG kills the instance mid-bootstrap).

Phase 5 — Target-tracking scaling policy (CPU 50%)

cat > /tmp/tt-config.json << 'EOF'
{
  "PredefinedMetricSpecification": {
    "PredefinedMetricType": "ASGAverageCPUUtilization"
  },
  "TargetValue": 50.0
}
EOF

aws autoscaling put-scaling-policy \
  --auto-scaling-group-name xfusion-asg \
  --policy-name xfusion-cpu-target-tracking \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration file:///tmp/tt-config.json \
  --region $REGION

Target tracking is the modern way to do "scale based on CPU at 50%." It auto-creates the CloudWatch alarms behind the scenes and adjusts capacity to keep average CPU near 50% — scale out above, scale in below, within the min/max (12) bounds. Far cleaner than manually wiring step-scaling policies to CloudWatch alarms (the old way).

Verify

# ASG has the instance + correct sizing
aws autoscaling describe-auto-scaling-groups \
  --auto-scaling-group-names xfusion-asg --region $REGION \
  --query 'AutoScalingGroups[0].{Min:MinSize,Max:MaxSize,Desired:DesiredCapacity,
    Instances:Instances[].{Id:InstanceId,Health:HealthStatus,State:LifecycleState},
    TGs:TargetGroupARNs}'

# Poll target health until healthy
for n in $(seq 1 12); do
  H=$(aws elbv2 describe-target-health --target-group-arn $TG_ARN --region $REGION \
    --query 'TargetHealthDescriptions[0].TargetHealth.State' --output text)
  echo "target: $H"; [ "$H" = "healthy" ] && break; sleep 20
done

# Hit the ALB DNS — should return the Nginx page
ALB_DNS=$(aws elbv2 describe-load-balancers --load-balancer-arns $ALB_ARN \
  --region $REGION --query 'LoadBalancers[0].DNSName' --output text)
echo "http://$ALB_DNS"
curl -I "http://$ALB_DNS"

Want: ASG showing min1/max2/desired1 with one instance Healthy/InService, the TG target reaching healthy, and curl -I on the ALB DNS returning HTTP/1.1 200 OK with an nginx Server header. That 200 through the ALB DNS is the task's success proof — the whole chain (ASG launches instance → user-data installs Nginx → TG health check passes → ALB routes to it) works.

Timing & debug

Two stacked waits: user-data installing Nginx (~60-90s) plus the TG health-check threshold (2 × 30s). Even after the ALB is active, give it ~3 min for the target to go healthy. If it stays unhealthy:

  1. Target.Timeout → SG not allowing 80 from the ALB, or Nginx not up. Since ALB and instances share xfusion-web-sg here and it allows 80 from 0.0.0.0/0, the ALB can reach the instance; if still failing, Nginx didn't install — check /var/log/cloud-init-output.log.
  2. Target.ResponseCodeMismatch → Nginx serving something other than 200 on /, or user-data failed (on AL2023, amazon-linux-extras doesn't exist — wrong user-data for the AMI).
  3. ASG launches then immediately terminates instances → health-check grace period too short; the ELB health check failed a bootstrapping instance. Raise --health-check-grace-period.

Note on launch template vs launch configuration

This uses a launch template, not the deprecated launch configuration. AWS ended new-account access to launch configurations; templates are the current standard and support versioning (Version=$Latest), mixed instances, and newer features. Always reach for create-launch-template.