AWS for DevOps — IaC Practice Exercises

4 minute read

8.6 — IaC Practice Exercises

Test your understanding of CloudFormation templates, intrinsic functions, stacks, change sets, drift detection, and CDK.


Exercise 1 — Template Debugging

This CloudFormation template has errors. Find and fix them:

AWSTemplateFormatVersion: "2010-09-09"

Resources:
  MyBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-app-bucket

  MyQueue:
    Type: AWS::SQS::Queue
    Properties:
      QueueName: !Sub "queue-${Environment}"

Outputs:
  BucketUrl:
    Value: !Ref MyBucket.WebsiteURL
Show Answer

Three issues:

  1. !Sub references undefined parameter — ${Environment} is used in !Sub but there is no Environment parameter defined. Add a Parameters section with Environment.

  2. !Ref used with an attribute — !Ref MyBucket.WebsiteURL is wrong. !Ref only returns the resource ID (bucket name). To get an attribute like WebsiteURL, use !GetAtt:
    Value: !GetAtt MyBucket.WebsiteURL
    
  3. Hard-coded bucket name — BucketName: my-app-bucket will cause conflicts in multi-environment deployments. Either remove it (let CloudFormation generate a unique name) or use !Sub:
    BucketName: !Sub "my-app-bucket-${Environment}-${AWS::AccountId}"
    

Exercise 2 — Intrinsic Functions

Write the CloudFormation YAML for each requirement. Use the appropriate intrinsic function:

# Requirement
A Create an S3 bucket named logs-<AccountId>-<Region>
B Choose instance type: t3.large for production, t3.micro otherwise
C Get the VPC CIDR block from a resource named MyVPC
D Import the VPC ID exported by another stack as shared-vpc-id
Show Answer

A — !Sub with pseudo parameters:

BucketName: !Sub "logs-${AWS::AccountId}-${AWS::Region}"

B — Condition + !If:

Conditions:
  IsProduction: !Equals [!Ref EnvironmentType, production]

Resources:
  WebServer:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !If [IsProduction, t3.large, t3.micro]

C — !GetAtt:

Value: !GetAtt MyVPC.CidrBlock

D — !ImportValue:

VpcId: !ImportValue "shared-vpc-id"

Exercise 3 — Change Set Analysis

You run describe-change-set and see this output:

[
  {"Action": "Modify", "Resource": "WebServer", "Replacement": "True"},
  {"Action": "Modify", "Resource": "WebSG", "Replacement": "False"},
  {"Action": "Add", "Resource": "MonitoringAlarm"}
]
  1. Which change is the most dangerous? Why?
  2. What should you do before executing this change set?
Show Answer
  1. WebServer with Replacement: True is the most dangerous. This means the EC2 instance will be deleted and recreated. Any data on the instance’s local storage will be lost. The public IP will change. Active connections will drop.

  2. Before executing:

    • Check what property change is causing the replacement (e.g., AMI change, subnet change)
    • Ensure any important data on the instance is backed up or stored on EBS/S3
    • If the instance has an Elastic IP, verify it will be re-associated
    • Consider performing this change during a maintenance window
    • If the change is unexpected, modify the template to avoid the replacement

Exercise 4 — Drift Remediation

Your drift detection report shows:

Resource: WebSecurityGroup
Status: MODIFIED
PropertyPath: /SecurityGroupIngress/2
ExpectedValue: (not present)
ActualValue: {"IpProtocol":"tcp","FromPort":22,"ToPort":22,"CidrIp":"0.0.0.0/0"}
DifferenceType: ADD
  1. What happened?
  2. Why is this a security concern?
  3. How do you fix it? Give both options.
Show Answer
  1. What happened: Someone manually added an inbound rule allowing SSH (port 22) from anywhere (0.0.0.0/0) to the security group through the AWS Console.

  2. Security concern: SSH access from 0.0.0.0/0 means anyone on the internet can attempt to connect to port 22. This is a major security risk — brute force attacks, unauthorized access.

  3. Fix options:

    Option A — Revert the manual change (recommended):

    # Update the stack with the original template (no SSH rule)
    $ aws cloudformation update-stack --stack-name my-app-prod --template-body file://template.yml
    

    CloudFormation removes the manually added rule, restoring the template state.

    Option B — Update the template to match (if SSH is needed): Add the SSH rule to the template but with a restricted CIDR:

    - IpProtocol: tcp
      FromPort: 22
      ToPort: 22
      CidrIp: 10.0.0.0/8   # Corporate network only!
    

    Then update the stack. Never allow SSH from 0.0.0.0/0 in the template.


Exercise 5 — CDK Design

A team wants to deploy a three-tier application using CDK:

  • Frontend: S3 static website + CloudFront
  • API: ECS Fargate behind an ALB
  • Database: RDS PostgreSQL in private subnets

They want separate stacks for networking, application, and database so they can update them independently. How would you structure the CDK app? List the stacks, their key constructs, and how they share values.

Show Answer

CDK App Structure:

my-app/
├── bin/my-app.ts           ← instantiates all 3 stacks
├── lib/
│   ├── network-stack.ts    ← VPC, subnets, NAT
│   ├── database-stack.ts   ← RDS, secrets
│   └── app-stack.ts        ← ECS, ALB, S3, CloudFront

Stack 1: NetworkStack

  • ec2.Vpc with 2 AZs, public + private subnets, 1 NAT gateway
  • Exports: vpc object

Stack 2: DatabaseStack (depends on NetworkStack)

  • Receives vpc as a constructor prop
  • rds.DatabaseInstance in private subnets
  • secretsmanager.Secret for DB credentials
  • Exports: dbEndpoint, dbSecret

Stack 3: AppStack (depends on NetworkStack + DatabaseStack)

  • Receives vpc, dbEndpoint, dbSecret as props
  • ecsPatterns.ApplicationLoadBalancedFargateService with environment variables pointing to DB
  • s3.Bucket with cloudfront.Distribution for frontend
  • Auto-scaling on CPU utilization

bin/my-app.ts:

const network = new NetworkStack(app, 'Network');
const database = new DatabaseStack(app, 'Database', {
  vpc: network.vpc,
});
const appStack = new AppStack(app, 'App', {
  vpc: network.vpc,
  dbEndpoint: database.dbEndpoint,
  dbSecret: database.dbSecret,
});

Key design decisions:

  • Pass objects between stacks via constructor props (CDK handles cross-stack references automatically)
  • Database in its own stack — can update independently without redeploying the app
  • Network stack rarely changes — stable foundation

Next Chapter → Serverless

Updated: