AWS for DevOps — IaC Practice Exercises
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:
-
!Subreferences undefined parameter —${Environment}is used in!Subbut there is noEnvironmentparameter defined. Add aParameterssection withEnvironment. !Refused with an attribute —!Ref MyBucket.WebsiteURLis wrong.!Refonly returns the resource ID (bucket name). To get an attribute likeWebsiteURL, use!GetAtt:Value: !GetAtt MyBucket.WebsiteURL- Hard-coded bucket name —
BucketName: my-app-bucketwill 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"}
]
- Which change is the most dangerous? Why?
- What should you do before executing this change set?
Show Answer
-
WebServer with
Replacement: Trueis 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. -
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
- What happened?
- Why is this a security concern?
- How do you fix it? Give both options.
Show Answer
-
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. -
Security concern: SSH access from
0.0.0.0/0means anyone on the internet can attempt to connect to port 22. This is a major security risk — brute force attacks, unauthorized access. -
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.ymlCloudFormation 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.Vpcwith 2 AZs, public + private subnets, 1 NAT gateway- Exports:
vpcobject
Stack 2: DatabaseStack (depends on NetworkStack)
- Receives
vpcas a constructor prop rds.DatabaseInstancein private subnetssecretsmanager.Secretfor DB credentials- Exports:
dbEndpoint,dbSecret
Stack 3: AppStack (depends on NetworkStack + DatabaseStack)
- Receives
vpc,dbEndpoint,dbSecretas props ecsPatterns.ApplicationLoadBalancedFargateServicewith environment variables pointing to DBs3.Bucketwithcloudfront.Distributionfor 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