AWS for DevOps — Compute Exercises

4 minute read

3.6 — Practice Exercises

Test your understanding of EC2 instances, AMIs, security groups, user data, and Auto Scaling.


Exercise 1 — Launch and Connect

Launch a t3.micro instance with Amazon Linux 2023, tag it Name=test-server, and connect to it via SSH. After connecting, check the instance metadata to confirm the instance type.

Show Answer
# Launch the instance
$ aws ec2 run-instances \
    --image-id ami-0abcdef1234567890 \
    --instance-type t3.micro \
    --key-name devops-key \
    --security-group-ids sg-0abc123def456 \
    --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=test-server}]'

# Wait for it to be running
$ aws ec2 wait instance-running --instance-ids i-0abc123def456

# Get the public IP
$ aws ec2 describe-instances --instance-ids i-0abc123def456 \
    --query 'Reservations[0].Instances[0].PublicIpAddress' --output text
3.110.45.67

# Connect
$ ssh -i devops-key.pem ec2-user@3.110.45.67

# Check metadata from inside the instance (IMDSv2)
$ TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
    -H "X-aws-ec2-metadata-token-ttl-seconds: 300")
$ curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
    http://169.254.169.254/latest/meta-data/instance-type
t3.micro

Cleanup:

$ aws ec2 terminate-instances --instance-ids i-0abc123def456

Exercise 2 — Security Group Chaining

Create three security groups for a three-tier architecture: alb-sg (allows 443 from internet), app-sg (allows 8080 from alb-sg only), and db-sg (allows 5432 from app-sg only).

Show Answer
# Create the three security groups
$ aws ec2 create-security-group \
    --group-name alb-sg --description "ALB" --vpc-id vpc-0abc123
$ aws ec2 create-security-group \
    --group-name app-sg --description "App tier" --vpc-id vpc-0abc123
$ aws ec2 create-security-group \
    --group-name db-sg --description "Database tier" --vpc-id vpc-0abc123

# ALB: allow HTTPS from internet
$ aws ec2 authorize-security-group-ingress \
    --group-id sg-alb \
    --ip-permissions \
        'IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges=[{CidrIp=0.0.0.0/0}]'

# App: allow 8080 from ALB SG only
$ aws ec2 authorize-security-group-ingress \
    --group-id sg-app \
    --ip-permissions \
        'IpProtocol=tcp,FromPort=8080,ToPort=8080,UserIdGroupPairs=[{GroupId=sg-alb}]'

# DB: allow 5432 from App SG only
$ aws ec2 authorize-security-group-ingress \
    --group-id sg-db \
    --ip-permissions \
        'IpProtocol=tcp,FromPort=5432,ToPort=5432,UserIdGroupPairs=[{GroupId=sg-app}]'

Why this is secure: The database cannot be reached from the internet. Traffic must flow: Internet → ALB → App → DB. Each tier only accepts connections from the tier directly in front of it.


Exercise 3 — User Data Script

Write a user data script that installs Docker, pulls the nginx:latest image, and runs it on port 80. The instance should serve a custom page that displays its hostname.

Show Answer
#!/bin/bash
set -euxo pipefail

# Install Docker
yum update -y
yum install -y docker
systemctl enable docker
systemctl start docker

# Create a custom index page
HOSTNAME=$(hostname)
mkdir -p /tmp/html
cat > /tmp/html/index.html <<EOF
<html>
<body>
<h1>Hello from ${HOSTNAME}</h1>
<p>This page is served by Nginx in Docker on EC2.</p>
</body>
</html>
EOF

# Run Nginx with the custom page
docker run -d \
    --name web \
    --restart unless-stopped \
    -p 80:80 \
    -v /tmp/html:/usr/share/nginx/html:ro \
    nginx:latest

To launch with this script:

$ aws ec2 run-instances \
    --image-id ami-0abcdef1234567890 \
    --instance-type t3.micro \
    --key-name devops-key \
    --security-group-ids sg-web \
    --user-data file://setup.sh

To verify: Visit http://<public-ip> — you should see “Hello from ip-10-0-1-50” (or whatever the hostname is).


Exercise 4 — Create a Custom AMI

You have a running instance i-0abc123 with Nginx installed. Create a custom AMI from it, then launch two new instances from that AMI.

Show Answer
# Create the AMI (stop first for consistency)
$ aws ec2 stop-instances --instance-ids i-0abc123
$ aws ec2 wait instance-stopped --instance-ids i-0abc123

$ aws ec2 create-image \
    --instance-id i-0abc123 \
    --name "nginx-golden-$(date +%Y%m%d)" \
    --description "Nginx pre-installed on Amazon Linux 2023"
{
    "ImageId": "ami-0golden123"
}

# Wait for the AMI to be available
$ aws ec2 wait image-available --image-ids ami-0golden123

# Launch two instances from the custom AMI
$ aws ec2 run-instances \
    --image-id ami-0golden123 \
    --instance-type t3.micro \
    --count 2 \
    --key-name devops-key \
    --security-group-ids sg-web \
    --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=web-from-ami}]'

Result: Both instances launch with Nginx already installed — no user data needed. Boot time is much faster because there’s nothing to install.


Exercise 5 — Auto Scaling Group

Create an Auto Scaling Group with min=2, max=6, desired=2 that scales out when CPU exceeds 70%. The ASG should spread across two availability zones and use ELB health checks.

Show Answer
# Step 1 — Create a launch template
$ aws ec2 create-launch-template \
    --launch-template-name exercise-template \
    --launch-template-data '{
        "ImageId": "ami-0golden123",
        "InstanceType": "t3.micro",
        "KeyName": "devops-key",
        "SecurityGroupIds": ["sg-app"],
        "MetadataOptions": {"HttpTokens": "required"}
    }'

# Step 2 — Create the ASG
$ aws autoscaling create-auto-scaling-group \
    --auto-scaling-group-name exercise-asg \
    --launch-template LaunchTemplateName=exercise-template,Version='$Latest' \
    --min-size 2 \
    --max-size 6 \
    --desired-capacity 2 \
    --vpc-zone-identifier "subnet-1a,subnet-1b" \
    --target-group-arns arn:aws:elasticloadbalancing:ap-south-1:123456789012:targetgroup/app-tg/abc123 \
    --health-check-type ELB \
    --health-check-grace-period 300

# Step 3 — Add a target tracking scaling policy
$ aws autoscaling put-scaling-policy \
    --auto-scaling-group-name exercise-asg \
    --policy-name cpu-70-target \
    --policy-type TargetTrackingScaling \
    --target-tracking-configuration '{
        "PredefinedMetricSpecification": {
            "PredefinedMetricType": "ASGAverageCPUUtilization"
        },
        "TargetValue": 70.0,
        "ScaleInCooldown": 300,
        "ScaleOutCooldown": 60
    }'

# Verify
$ aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-names exercise-asg \
    --query 'AutoScalingGroups[0].{Min:MinSize,Max:MaxSize,Desired:DesiredCapacity,AZs:AvailabilityZones}'
{
    "Min": 2,
    "Max": 6,
    "Desired": 2,
    "AZs": ["ap-south-1a", "ap-south-1b"]
}

What happens under load:

  1. CPU exceeds 70% → ASG launches additional instances (up to 6)
  2. CPU drops below 70% → ASG terminates excess instances (down to 2)
  3. If an instance fails the ELB health check → ASG replaces it automatically

Next Chapter → Networking (VPC)

Updated: