AWS for DevOps — Practice Projects: Exercises

5 minute read

Practice Exercises

Test your understanding of the five practice projects with these hands-on challenges.


Exercise 1 — VPC Design Challenge

Scenario: Your company needs a VPC that supports three environments (dev, staging, prod) with the following requirements:

  • All environments share one VPC
  • Each environment needs public and private subnets
  • Environments must be isolated from each other using NACLs
  • Only the prod environment needs a NAT Gateway

Task: Design the CIDR allocation scheme and write the AWS CLI commands.

Show Answer

CIDR Allocation:

Environment Public Subnets Private Subnets
Dev 10.0.0.0/24, 10.0.1.0/24 10.0.10.0/24, 10.0.11.0/24
Staging 10.0.2.0/24, 10.0.3.0/24 10.0.12.0/24, 10.0.13.0/24
Prod 10.0.4.0/24, 10.0.5.0/24 10.0.14.0/24, 10.0.15.0/24
# Create VPC
$ aws ec2 create-vpc --cidr-block 10.0.0.0/16 \
    --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=multi-env-vpc}]'

# Create NACL for dev isolation
$ aws ec2 create-network-acl \
    --vpc-id vpc-xxx \
    --tag-specifications 'ResourceType=network-acl,Tags=[{Key=Name,Value=dev-nacl}]'

# Allow only dev CIDR ranges in dev NACL
$ aws ec2 create-network-acl-entry \
    --network-acl-id acl-dev \
    --rule-number 100 --protocol -1 \
    --cidr-block 10.0.0.0/22 \
    --rule-action allow --ingress

# Deny staging and prod ranges
$ aws ec2 create-network-acl-entry \
    --network-acl-id acl-dev \
    --rule-number 200 --protocol -1 \
    --cidr-block 10.0.2.0/22 \
    --rule-action deny --ingress

Key Insight: Using /22 blocks for each environment gives room for growth while maintaining clear isolation boundaries.


Exercise 2 — Blue/Green ECS Deployment

Scenario: Your ECS Fargate service needs zero-downtime deployments using a blue/green strategy instead of rolling updates.

Task: Modify the ECS service from Project 2 to use CodeDeploy blue/green deployments with two target groups.

Show Answer
# Create a second target group (green)
$ aws elbv2 create-target-group \
    --name fargate-app-tg-green \
    --protocol HTTP --port 3000 \
    --vpc-id vpc-xxx --target-type ip \
    --health-check-path "/"

# Create CodeDeploy application
$ aws deploy create-application \
    --application-name ecs-fargate-app \
    --compute-platform ECS

# Create deployment group
$ aws deploy create-deployment-group \
    --application-name ecs-fargate-app \
    --deployment-group-name prod \
    --service-role-arn arn:aws:iam::123456789012:role/CodeDeployECSRole \
    --deployment-config-name CodeDeployDefault.ECSLinear10PercentEvery1Minutes \
    --ecs-services clusterName=prod-cluster,serviceName=fargate-app \
    --load-balancer-info targetGroupPairInfoList="[{targetGroups=[{name=fargate-app-tg},{name=fargate-app-tg-green}],prodTrafficRoute={listenerArns=[arn:aws:elasticloadbalancing:...:listener/app/prod-alb/abc123/def456]}}]" \
    --blue-green-deployment-configuration '{
      "terminateBlueInstancesOnDeploymentSuccess": {
        "action": "TERMINATE",
        "terminationWaitTimeInMinutes": 5
      },
      "deploymentReadyOption": {
        "actionOnTimeout": "CONTINUE_DEPLOYMENT",
        "waitTimeInMinutes": 0
      }
    }'

Flow: New version deploys to green target group → traffic shifts gradually (10% per minute) → old blue tasks terminate after 5 minutes.


Exercise 3 — Add DynamoDB Streams to Serverless API

Scenario: You need to add real-time data processing to the Serverless API from Project 3. Every time an item is created or updated, a Lambda function should process the change and send a notification.

Task: Enable DynamoDB Streams and create a stream processing Lambda.

Show Answer
# Enable DynamoDB Streams
$ aws dynamodb update-table \
    --table-name Items \
    --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES
{
    "TableDescription": {
        "LatestStreamArn": "arn:aws:dynamodb:us-east-1:123456789012:table/Items/stream/2026-03-30T06:00:00.000"
    }
}

# Create stream processor Lambda
$ cat > stream-processor.py << 'EOF'
import json
import boto3

sns = boto3.client('sns')

def handler(event, context):
    for record in event['Records']:
        event_name = record['eventName']  # INSERT, MODIFY, REMOVE
        new_image = record.get('dynamodb', {}).get('NewImage', {})

        if event_name == 'INSERT':
            item_name = new_image.get('name', {}).get('S', 'Unknown')
            sns.publish(
                TopicArn='arn:aws:sns:us-east-1:123456789012:info-alerts',
                Subject=f'New item created: {item_name}',
                Message=json.dumps(record, default=str)
            )

    return {'statusCode': 200}
EOF

# Deploy and create event source mapping
$ zip stream-processor.zip stream-processor.py
$ aws lambda create-function \
    --function-name stream-processor \
    --runtime python3.12 \
    --handler stream-processor.handler \
    --role arn:aws:iam::123456789012:role/lambda-api-role \
    --zip-file fileb://stream-processor.zip

$ aws lambda create-event-source-mapping \
    --function-name stream-processor \
    --event-source-arn arn:aws:dynamodb:us-east-1:123456789012:table/Items/stream/2026-03-30T06:00:00.000 \
    --starting-position LATEST \
    --batch-size 10

Result: Every item change in DynamoDB automatically triggers the stream processor Lambda.


Exercise 4 — Add Nested Stacks to CloudFormation

Scenario: The single CloudFormation template from Project 4 is getting large. Refactor it into nested stacks for better maintainability.

Task: Split the template into: networking stack, security stack, compute stack, and database stack.

Show Answer

Parent stack (main.yaml):

AWSTemplateFormatVersion: '2010-09-09'
Description: Parent stack - 3-Tier Application

Parameters:
  EnvironmentName:
    Type: String
    Default: prod
  TemplatesBucket:
    Type: String
    Description: S3 bucket containing nested templates

Resources:
  NetworkingStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub 'https://${TemplatesBucket}.s3.amazonaws.com/networking.yaml'
      Parameters:
        EnvironmentName: !Ref EnvironmentName

  SecurityStack:
    Type: AWS::CloudFormation::Stack
    DependsOn: NetworkingStack
    Properties:
      TemplateURL: !Sub 'https://${TemplatesBucket}.s3.amazonaws.com/security.yaml'
      Parameters:
        VpcId: !GetAtt NetworkingStack.Outputs.VPCId

  ComputeStack:
    Type: AWS::CloudFormation::Stack
    DependsOn: SecurityStack
    Properties:
      TemplateURL: !Sub 'https://${TemplatesBucket}.s3.amazonaws.com/compute.yaml'
      Parameters:
        PrivateSubnets: !GetAtt NetworkingStack.Outputs.PrivateSubnets
        ALBSecurityGroup: !GetAtt SecurityStack.Outputs.ALBSecurityGroupId

  DatabaseStack:
    Type: AWS::CloudFormation::Stack
    DependsOn: SecurityStack
    Properties:
      TemplateURL: !Sub 'https://${TemplatesBucket}.s3.amazonaws.com/database.yaml'
      Parameters:
        DBSubnets: !GetAtt NetworkingStack.Outputs.DBSubnets
        DBSecurityGroup: !GetAtt SecurityStack.Outputs.DBSecurityGroupId
# Upload templates to S3
$ aws s3 mb s3://cfn-templates-123456
$ aws s3 cp networking.yaml s3://cfn-templates-123456/
$ aws s3 cp security.yaml s3://cfn-templates-123456/
$ aws s3 cp compute.yaml s3://cfn-templates-123456/
$ aws s3 cp database.yaml s3://cfn-templates-123456/

# Deploy parent stack
$ aws cloudformation create-stack \
    --stack-name prod-3tier-nested \
    --template-body file://main.yaml \
    --parameters ParameterKey=TemplatesBucket,ParameterValue=cfn-templates-123456 \
    --capabilities CAPABILITY_NAMED_IAM

Benefits: Each team can own their stack, templates are reusable, and changes are isolated.


Exercise 5 — Composite Alarm with Auto-Scaling

Scenario: Create a composite CloudWatch alarm that only triggers when BOTH CPU is high AND memory is high (indicating a real resource constraint, not just a CPU spike from a batch job).

Task: Create individual alarms and combine them into a composite alarm that triggers auto-scaling.

Show Answer
# Individual CPU alarm (don't attach action — let composite handle it)
$ aws cloudwatch put-metric-alarm \
    --alarm-name "CPU-High-Component" \
    --namespace AWS/EC2 \
    --metric-name CPUUtilization \
    --statistic Average --period 300 \
    --threshold 80 --comparison-operator GreaterThanThreshold \
    --evaluation-periods 2

# Individual Memory alarm (requires CloudWatch Agent)
$ aws cloudwatch put-metric-alarm \
    --alarm-name "Memory-High-Component" \
    --namespace CWAgent \
    --metric-name mem_used_percent \
    --statistic Average --period 300 \
    --threshold 80 --comparison-operator GreaterThanThreshold \
    --evaluation-periods 2

# Composite alarm — BOTH must be in ALARM state
$ aws cloudwatch put-composite-alarm \
    --alarm-name "Resource-Constrained-Composite" \
    --alarm-rule 'ALARM("CPU-High-Component") AND ALARM("Memory-High-Component")' \
    --alarm-actions arn:aws:sns:us-east-1:123456789012:critical-alerts \
    --alarm-description "Both CPU and memory are high — genuine resource constraint"

# Add auto-scaling action
$ aws autoscaling put-scaling-policy \
    --auto-scaling-group-name prod-asg \
    --policy-name scale-on-resource-constraint \
    --policy-type StepScaling \
    --adjustment-type ChangeInCapacity \
    --step-adjustments MetricIntervalLowerBound=0,ScalingAdjustment=2

Why Composite? A CPU spike during a batch job shouldn’t trigger scaling. But if both CPU and memory are consistently high, you have a genuine capacity problem.


Bonus Challenge

Combine all five projects into a single, fully automated deployment:

  1. Use CloudFormation (Project 4) to create the VPC (Project 1)
  2. Deploy the ECS service (Project 2) inside that VPC
  3. Add a serverless microservice (Project 3) alongside it
  4. Monitor everything (Project 5)

The goal: One aws cloudformation create-stack command deploys your entire production environment.


Key Takeaways

Project Core Lesson
VPC Network design is foundational — get it right first
ECS CI/CD Automation eliminates human error in deployments
Serverless Pay for value, not idle capacity
CloudFormation Infrastructure as Code makes environments reproducible
Monitoring You can’t fix what you can’t see

Next Chapter → Next Steps

Updated: