Lab 11: Infrastructure as Code with CloudFormation and SAM
Objective
Build and deploy AWS infrastructure using CloudFormation templates and the AWS Serverless Application Model (SAM), progressing from a basic VPC stack to a parameterized, multi-resource template and a serverless application.
Architecture Diagram
This lab builds two separate CloudFormation stacks. The first stack provisions networking and compute resources using a CloudFormation template. The second stack deploys a serverless API using SAM. The components and their relationships are as follows:
Stack 1: lab11-network (CloudFormation)
├── VPC (10.0.0.0/16)
│ ├── Public Subnet (10.0.1.0/24, us-east-1a)
│ │ ├── Internet Gateway (attached to VPC)
│ │ ├── Route Table (0.0.0.0/0 -> IGW)
│ │ ├── Security Group (allow SSH port 22, HTTP port 80)
│ │ └── EC2 Instance (t2.micro, Amazon Linux 2023)
│ └── Private Subnet (10.0.2.0/24, us-east-1b)
│
├── Parameters: EnvironmentName, VpcCidr
├── Outputs (exported): VPC ID, Public Subnet ID, Private Subnet ID
Stack 2: lab11-sam-api (SAM)
├── API Gateway (REST API)
│ └── GET /items -> Lambda Function (Python 3.12)
│ └── DynamoDB Table (lab11-items)
You start by writing a minimal CloudFormation template for a VPC with two subnets and deploying it. You then add parameters and update the stack using a change set. Next, you add an EC2 instance and security group to the template. You add outputs that export resource IDs for cross-stack reference. You then build a serverless API using SAM with Lambda, API Gateway, and DynamoDB. You test the SAM application locally and deploy it to AWS. Finally, you delete both stacks and verify cleanup.
Prerequisites
- Completed Lab 03: VPC Setup (VPC, subnets, route tables, and security groups that you will now define as code)
- Completed Lab 04: EC2 Instances (EC2 instance launch and configuration)
- Completed Lab 09: Building Serverless Applications (Lambda functions, API Gateway, and DynamoDB)
- Completed Module 11: Infrastructure as Code lesson content
- Signed in to the AWS Management Console as the
bootcamp-adminIAM user - AWS CloudShell available (or the AWS CLI installed and configured locally)
- AWS SAM CLI installed (installation guide)
- Docker installed and running (required for
sam local invoke)
Duration
90 minutes
Instructions
Step 1: Write and Deploy a Basic CloudFormation Template (Guided)
In this step, you write a CloudFormation template that creates a VPC with a public subnet and a private subnet, then deploy it as a stack using the AWS CLI.
-
Open CloudShell by choosing the terminal icon in the navigation bar, or use your local terminal with the AWS CLI configured.
-
Create a working directory for your templates:
mkdir -p ~/lab11
cd ~/lab11
- Create the initial CloudFormation template. This template defines a VPC, two subnets, an internet gateway, and a route table for the public subnet:
cat > vpc-template.yaml << 'EOF'
AWSTemplateFormatVersion: "2010-09-09"
Description: Lab 11 - VPC with public and private subnets
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: lab11-vpc
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: lab11-igw
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: us-east-1a
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: lab11-public-subnet
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: us-east-1b
Tags:
- Key: Name
Value: lab11-private-subnet
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: lab11-public-rt
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
EOF
- Validate the template before deploying. CloudFormation checks the template syntax and reports any errors:
aws cloudformation validate-template \
--template-body file://vpc-template.yaml
Expected output (abbreviated):
{
"Parameters": [],
"Description": "Lab 11 - VPC with public and private subnets"
}
- Create the stack:
aws cloudformation create-stack \
--stack-name lab11-network \
--template-body file://vpc-template.yaml \
--region us-east-1
- Wait for the stack to finish creating. This command blocks until the stack reaches
CREATE_COMPLETEor fails:
aws cloudformation wait stack-create-complete \
--stack-name lab11-network \
--region us-east-1
echo "Stack creation complete."
- Verify the stack status and list the resources CloudFormation created:
aws cloudformation describe-stacks \
--stack-name lab11-network \
--query "Stacks[0].StackStatus" \
--output text
Expected output:
CREATE_COMPLETE
- List all resources in the stack:
aws cloudformation list-stack-resources \
--stack-name lab11-network \
--query "StackResourceSummaries[*].[LogicalResourceId,ResourceType,ResourceStatus]" \
--output table
You should see eight resources (VPC, InternetGateway, AttachGateway, PublicSubnet, PrivateSubnet, PublicRouteTable, PublicRoute, PublicSubnetRouteTableAssociation) all with status CREATE_COMPLETE.
Tip: Compare this experience to Lab 03 where you created each resource individually with separate CLI commands. CloudFormation created all eight resources from a single template, handled the dependency ordering automatically, and will delete them all as a unit when you delete the stack.
Step 2: Add Parameters and Update with a Change Set (Guided)
In this step, you add parameters to your template so you can reuse it across environments without editing the YAML each time. You then update the running stack using a change set, which lets you preview exactly what will change before committing.
- Update the template to add parameters for the environment name and VPC CIDR block. Replace the contents of
vpc-template.yaml:
cat > vpc-template.yaml << 'EOF'
AWSTemplateFormatVersion: "2010-09-09"
Description: Lab 11 - Parameterized VPC with public and private subnets
Parameters:
EnvironmentName:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- prod
Description: Environment name used for resource tagging
VpcCidr:
Type: String
Default: 10.0.0.0/16
AllowedPattern: "^(\\d{1,3}\\.){3}\\d{1,3}/\\d{1,2}$"
ConstraintDescription: Must be a valid CIDR block (for example, 10.0.0.0/16)
Description: CIDR block for the VPC
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub "${EnvironmentName}-lab11-vpc"
- Key: Environment
Value: !Ref EnvironmentName
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub "${EnvironmentName}-lab11-igw"
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Select
- 0
- !Cidr [!Ref VpcCidr, 4, 8]
AvailabilityZone: us-east-1a
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub "${EnvironmentName}-lab11-public-subnet"
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Select
- 1
- !Cidr [!Ref VpcCidr, 4, 8]
AvailabilityZone: us-east-1b
Tags:
- Key: Name
Value: !Sub "${EnvironmentName}-lab11-private-subnet"
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${EnvironmentName}-lab11-public-rt"
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
EOF
- Create a change set to preview the update. A change set shows you exactly what CloudFormation will modify before you apply the changes:
aws cloudformation create-change-set \
--stack-name lab11-network \
--change-set-name add-parameters \
--template-body file://vpc-template.yaml \
--parameters \
ParameterKey=EnvironmentName,ParameterValue=dev \
ParameterKey=VpcCidr,ParameterValue=10.0.0.0/16 \
--region us-east-1
- Wait for the change set to finish computing:
aws cloudformation wait change-set-create-complete \
--stack-name lab11-network \
--change-set-name add-parameters \
--region us-east-1
- Review the change set to see what will change:
aws cloudformation describe-change-set \
--stack-name lab11-network \
--change-set-name add-parameters \
--query "Changes[*].ResourceChange.{Action:Action,LogicalId:LogicalResourceId,ResourceType:ResourceType,Replacement:Replacement}" \
--output table
You should see Modify actions on resources where tags changed, and the Replacement column should show False for tag-only changes. This confirms that updating tags does not replace your resources.
- Execute the change set to apply the changes:
aws cloudformation execute-change-set \
--stack-name lab11-network \
--change-set-name add-parameters \
--region us-east-1
- Wait for the update to complete:
aws cloudformation wait stack-update-complete \
--stack-name lab11-network \
--region us-east-1
echo "Stack update complete."
- Verify the stack now has parameters:
aws cloudformation describe-stacks \
--stack-name lab11-network \
--query "Stacks[0].Parameters" \
--output table
You should see EnvironmentName with value dev and VpcCidr with value 10.0.0.0/16.
Tip: Always use change sets when updating stacks, even in development. This habit prevents surprises in production where an unexpected resource replacement could cause downtime. Review the CloudFormation best practices for more recommendations.
Step 3: Add an EC2 Instance with a Security Group (Semi-Guided)
Goal: Add an EC2 instance in the public subnet and a security group that allows inbound SSH (port 22) and HTTP (port 80) traffic. The instance should use the Amazon Linux 2023 AMI and the t2.micro instance type.
Resources to add:
| Resource Type | Logical Name | Purpose |
|---|---|---|
AWS::EC2::SecurityGroup | WebServerSecurityGroup | Controls inbound traffic to the EC2 instance |
AWS::EC2::Instance | WebServerInstance | A t2.micro instance in the public subnet |
Requirements:
- The security group must belong to the lab11 VPC (use
!Ref VPCfor theVpcIdproperty) - Add two inbound rules using
SecurityGroupIngress: one for SSH (port 22, TCP,0.0.0.0/0) and one for HTTP (port 80, TCP,0.0.0.0/0) - Tag the security group with the environment name using
!Sub - The EC2 instance must use the AMI ID
ami-0953476d60561c955(Amazon Linux 2023 in us-east-1) - Place the instance in the public subnet using the
SubnetIdproperty with!Ref PublicSubnet - Attach the security group using the
SecurityGroupIdsproperty (this takes a list) - Tag the instance with the environment name
Hint: The
SecurityGroupIdsproperty onAWS::EC2::Instanceexpects a list of security group IDs. Use YAML list syntax:SecurityGroupIds: - !GetAtt WebServerSecurityGroup.GroupId
Hint: Use
!Sub "${EnvironmentName}-lab11-web-server"for the instance Name tag to keep naming consistent with the rest of the template.
Hint: Review the AWS::EC2::SecurityGroup resource reference for the
SecurityGroupIngressproperty structure. Each rule needsIpProtocol,FromPort,ToPort, andCidrIp.
Update the stack after adding the resources. Use a change set to preview the additions before applying:
aws cloudformation create-change-set \
--stack-name lab11-network \
--change-set-name add-ec2 \
--template-body file://vpc-template.yaml \
--parameters \
ParameterKey=EnvironmentName,ParameterValue=dev \
ParameterKey=VpcCidr,ParameterValue=10.0.0.0/16 \
--region us-east-1
aws cloudformation wait change-set-create-complete \
--stack-name lab11-network \
--change-set-name add-ec2 \
--region us-east-1
aws cloudformation describe-change-set \
--stack-name lab11-network \
--change-set-name add-ec2 \
--query "Changes[*].ResourceChange.{Action:Action,LogicalId:LogicalResourceId,ResourceType:ResourceType}" \
--output table
You should see two Add actions: one for WebServerSecurityGroup and one for WebServerInstance.
Execute the change set:
aws cloudformation execute-change-set \
--stack-name lab11-network \
--change-set-name add-ec2 \
--region us-east-1
aws cloudformation wait stack-update-complete \
--stack-name lab11-network \
--region us-east-1
echo "EC2 instance added."
Verify your work by confirming the instance is running:
INSTANCE_ID=$(aws cloudformation describe-stack-resource \
--stack-name lab11-network \
--logical-resource-id WebServerInstance \
--query "StackResourceDetail.PhysicalResourceId" \
--output text)
aws ec2 describe-instances \
--instance-ids $INSTANCE_ID \
--query "Reservations[0].Instances[0].{State:State.Name,PublicIp:PublicIpAddress,SubnetId:SubnetId}" \
--output table
You should see the instance in the running state with a public IP address.
Reference links:
Step 4: Add Outputs for Cross-Stack Reference (Guided)
In this step, you add outputs to your template that expose the VPC ID and subnet IDs for consumption by other stacks. This pattern, where a "network stack" exports identifiers and an "application stack" imports them via Fn::ImportValue, is how teams share infrastructure across independently managed stacks.
- Add the following
Outputssection to the bottom of yourvpc-template.yamlfile (after theResourcessection):
Outputs:
VpcId:
Description: VPC ID for cross-stack reference
Value: !Ref VPC
Export:
Name: !Sub "${EnvironmentName}-lab11-vpc-id"
PublicSubnetId:
Description: Public subnet ID for cross-stack reference
Value: !Ref PublicSubnet
Export:
Name: !Sub "${EnvironmentName}-lab11-public-subnet-id"
PrivateSubnetId:
Description: Private subnet ID for cross-stack reference
Value: !Ref PrivateSubnet
Export:
Name: !Sub "${EnvironmentName}-lab11-private-subnet-id"
WebServerPublicIp:
Description: Public IP address of the web server
Value: !GetAtt WebServerInstance.PublicIp
- Update the stack with a change set:
aws cloudformation create-change-set \
--stack-name lab11-network \
--change-set-name add-outputs \
--template-body file://vpc-template.yaml \
--parameters \
ParameterKey=EnvironmentName,ParameterValue=dev \
ParameterKey=VpcCidr,ParameterValue=10.0.0.0/16 \
--region us-east-1
aws cloudformation wait change-set-create-complete \
--stack-name lab11-network \
--change-set-name add-outputs \
--region us-east-1
- Review the change set. Adding outputs does not modify any resources, so you may see an empty changes list or only output-related changes:
aws cloudformation describe-change-set \
--stack-name lab11-network \
--change-set-name add-outputs \
--query "Changes" \
--output json
- Execute the change set:
aws cloudformation execute-change-set \
--stack-name lab11-network \
--change-set-name add-outputs \
--region us-east-1
aws cloudformation wait stack-update-complete \
--stack-name lab11-network \
--region us-east-1
echo "Outputs added."
- View the stack outputs:
aws cloudformation describe-stacks \
--stack-name lab11-network \
--query "Stacks[0].Outputs" \
--output table
You should see four outputs: VpcId, PublicSubnetId, PrivateSubnetId, and WebServerPublicIp.
- Verify the exported values are available for cross-stack reference:
aws cloudformation list-exports \
--query "Exports[?starts_with(Name, 'dev-lab11')]" \
--output table
You should see three exports: dev-lab11-vpc-id, dev-lab11-public-subnet-id, and dev-lab11-private-subnet-id. Any other stack in the same Region and account can now import these values using Fn::ImportValue.
Tip: The
WebServerPublicIpoutput does not have anExportblock because IP addresses change when instances are stopped and started. Export only stable identifiers like VPC IDs and subnet IDs. For the cross-stack reference pattern in production, see the CloudFormation outputs documentation.
Step 5: Deploy a Serverless API with SAM (Semi-Guided)
Goal: Use the AWS Serverless Application Model (SAM) to define and deploy a serverless API consisting of a Lambda function, an API Gateway endpoint, and a DynamoDB table. The API should accept GET requests at /items and return a list of items from the DynamoDB table.
SAM resource types to use:
| SAM Resource Type | Logical Name | Purpose |
|---|---|---|
AWS::Serverless::Function | GetItemsFunction | Lambda function that reads from DynamoDB |
AWS::Serverless::SimpleTable | ItemsTable | DynamoDB table with a single-attribute primary key |
Requirements:
-
Create a new directory for the SAM project:
~/lab11/sam-api/ -
Write a SAM template file at
~/lab11/sam-api/template.yaml -
The template must include
Transform: AWS::Serverless-2016-10-31 -
Use a
Globalssection to set the function runtime topython3.12, timeout to30, and an environment variableTABLE_NAMEthat references the DynamoDB table -
The
GetItemsFunctionmust have:- A
Handlerproperty pointing to your Python handler (for example,app.handler) - A
CodeUriproperty pointing to asrc/directory - An
Eventssection with anApievent type forGET /items - A
Policiessection using theDynamoDBReadPolicySAM policy template scoped to the items table
- A
-
The
ItemsTablemust have a primary key namedItemIdof typeString -
Include an
Outputssection that exports the API endpoint URL -
Write the Lambda handler at
~/lab11/sam-api/src/app.py:- Read the
TABLE_NAMEenvironment variable - Use
boto3to scan the DynamoDB table - Return a JSON response with
statusCode,headers(includingContent-Type: application/json), andbody
- Read the
Hint: The SAM template
Transformdeclaration tells CloudFormation to process SAM-specific resource types. Your template should start with:AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Description: Lab 11 - SAM serverless API
Hint: The
Eventssection on aAWS::Serverless::Functiondefines what triggers the function. For an API Gateway trigger:Events: GetItems: Type: Api Properties: Path: /items Method: get
Hint: SAM policy templates simplify IAM permissions.
DynamoDBReadPolicygrantsGetItem,Query,Scan, andBatchGetItemon the specified table. Reference it as:Policies: - DynamoDBReadPolicy: TableName: !Ref ItemsTable
Hint: For the API URL output, SAM automatically creates a
ServerlessRestApiresource. Reference it with:Outputs: ApiUrl: Description: API Gateway endpoint URL Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod"
Create the project structure:
mkdir -p ~/lab11/sam-api/src
Write your template.yaml and src/app.py files, then proceed to Step 6 to build, test, and deploy.
Reference links:
- SAM template anatomy
- AWS::Serverless::Function
- AWS::Serverless::SimpleTable
- SAM policy templates
- SAM Globals section
Step 6: Test Locally and Deploy the SAM Application (Guided)
In this step, you build the SAM application, test the Lambda function locally using sam local invoke, and deploy to AWS using sam deploy --guided. This workflow mirrors what a CI/CD pipeline does automatically (Module 12), but here you run each step manually to understand what happens at each stage.
- Navigate to the SAM project directory:
cd ~/lab11/sam-api
- Build the application. The
sam buildcommand installs dependencies and prepares deployment artifacts:
sam build
Expected output (abbreviated):
Building codeuri: /home/cloudshell-user/lab11/sam-api/src runtime: python3.12 ...
Build Succeeded
- Create a test event file to simulate an API Gateway GET request:
mkdir -p events
cat > events/get-items.json << 'EOF'
{
"httpMethod": "GET",
"path": "/items",
"pathParameters": null,
"queryStringParameters": null,
"headers": {
"Content-Type": "application/json"
},
"body": null,
"requestContext": {
"httpMethod": "GET",
"path": "/items"
}
}
EOF
- Test the function locally with
sam local invoke. This command runs the function in a Docker container that simulates the Lambda execution environment:
sam local invoke GetItemsFunction --event events/get-items.json
Tip: The first invocation downloads the Lambda runtime Docker image, which may take a minute. Subsequent invocations reuse the cached image and run faster. If you see a connection error for DynamoDB, that is expected because the local environment does not have access to your AWS DynamoDB table. The function will work correctly after deployment.
- Deploy the application to AWS using the guided deployment flow:
sam deploy --guided
- When prompted, enter the following configuration values:
| Prompt | Value |
|---|---|
| Stack Name | lab11-sam-api |
| AWS Region | us-east-1 |
| Confirm changes before deploy | y |
| Allow SAM CLI IAM role creation | y |
| Disable rollback | n |
| GetItemsFunction may not have authorization defined, Is this okay? | y |
| Save arguments to configuration file | y |
| SAM configuration file | samconfig.toml |
| SAM configuration environment | default |
-
SAM displays a change set summary showing the resources it will create. Review the list and confirm the deployment by entering
ywhen prompted. -
Wait for the deployment to complete. SAM displays the stack outputs when finished. Look for the
ApiUrloutput value. -
Test the deployed API:
API_URL=$(aws cloudformation describe-stacks \
--stack-name lab11-sam-api \
--query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" \
--output text)
echo "API URL: $API_URL"
curl "${API_URL}/items"
You should see a JSON response with an empty items list (or any items you have added to the table). The response confirms that API Gateway, Lambda, and DynamoDB are all working together.
- Add a test item to the DynamoDB table to verify the full read path:
TABLE_NAME=$(aws cloudformation describe-stack-resource \
--stack-name lab11-sam-api \
--logical-resource-id ItemsTable \
--query "StackResourceDetail.PhysicalResourceId" \
--output text)
aws dynamodb put-item \
--table-name $TABLE_NAME \
--item '{"ItemId": {"S": "item-001"}, "Name": {"S": "Test Item"}, "Description": {"S": "Created in Lab 11"}}' \
--region us-east-1
- Query the API again to confirm the item appears:
curl "${API_URL}/items"
You should see a JSON response containing the item you just added.
Tip: The
samconfig.tomlfile that SAM created stores your deployment configuration. Future deployments usesam deploywithout the--guidedflag, and SAM reads the saved settings automatically. This is useful in CI/CD pipelines where you want repeatable, non-interactive deployments.
Step 7: Delete Both Stacks and Verify Cleanup (Guided)
Delete all resources created during this lab to avoid unexpected charges. You must delete the SAM stack first because it is independent, then delete the network stack.
- Delete the SAM application stack:
aws cloudformation delete-stack \
--stack-name lab11-sam-api \
--region us-east-1
aws cloudformation wait stack-delete-complete \
--stack-name lab11-sam-api \
--region us-east-1
echo "SAM stack deleted."
- Delete the network stack. CloudFormation will terminate the EC2 instance, delete the security group, subnets, route tables, internet gateway, and VPC in the correct order:
aws cloudformation delete-stack \
--stack-name lab11-network \
--region us-east-1
aws cloudformation wait stack-delete-complete \
--stack-name lab11-network \
--region us-east-1
echo "Network stack deleted."
- Verify that both stacks are deleted:
aws cloudformation list-stacks \
--stack-status-filter DELETE_COMPLETE \
--query "StackSummaries[?contains(StackName, 'lab11')].[StackName,StackStatus,DeletionTime]" \
--output table
You should see both lab11-network and lab11-sam-api with status DELETE_COMPLETE.
- Confirm that no lab11 resources remain active:
aws ec2 describe-vpcs \
--filters "Name=tag:Name,Values=*lab11*" \
--query "Vpcs[*].[VpcId,Tags[?Key=='Name'].Value|[0]]" \
--output table
This command should return an empty table, confirming the VPC and all associated resources are gone.
- Delete the CloudWatch Log groups created by the SAM Lambda function:
aws logs describe-log-groups \
--log-group-name-prefix "/aws/lambda/lab11-sam-api" \
--query "logGroups[*].logGroupName" \
--output text | tr '\t' '\n' | while read LOG_GROUP; do
aws logs delete-log-group --log-group-name "$LOG_GROUP"
echo "Deleted log group: $LOG_GROUP"
done
- Clean up the local working directory:
rm -rf ~/lab11
Warning: If you skip the cleanup steps, the EC2 instance in the network stack will incur hourly charges, and the DynamoDB table in the SAM stack may incur charges for stored data. Always delete lab stacks when you are finished.
Validation
Confirm that you completed the lab successfully by verifying each of the following:
- The
lab11-networkCloudFormation stack was created with a VPC, public subnet, private subnet, internet gateway, and route table - The template uses
ParametersforEnvironmentNameandVpcCidrwith validation constraints - A change set was created and reviewed before each stack update
- The template includes an
AWS::EC2::SecurityGroupwith SSH and HTTP inbound rules - The template includes an
AWS::EC2::Instancein the public subnet with the security group attached - The stack
Outputssection exports the VPC ID, public subnet ID, and private subnet ID - The exported values appear in
aws cloudformation list-exports - A SAM template defines a
AWS::Serverless::Function,AWS::Serverless::SimpleTable, and API event source - The SAM template uses a
Globalssection for shared function configuration -
sam local invokeexecuted the function in a local Docker container -
sam deploy --guidedcreated thelab11-sam-apistack with API Gateway, Lambda, and DynamoDB -
curlto the API Gateway endpoint returned a JSON response with items from DynamoDB - Both stacks were deleted and
aws cloudformation list-stacksconfirmsDELETE_COMPLETEstatus
Cleanup
See Step 7 above for the complete cleanup procedure. If you skipped Step 7, return to it now and execute all cleanup commands to avoid unexpected charges.
Challenge
Extend your infrastructure as code skills with the following enhancements:
-
Add a NAT Gateway to the network stack. Modify
vpc-template.yamlto add an Elastic IP, a NAT Gateway in the public subnet, and a private route table with a default route through the NAT Gateway. Associate the private subnet with the new route table. Use a change set to preview and apply the update. Verify that the private subnet route table has a route to the NAT Gateway by runningaws ec2 describe-route-tables. -
Add a POST endpoint to the SAM API. Create a second Lambda function in the SAM template that accepts POST requests at
/itemsand writes a new item to the DynamoDB table. Use theDynamoDBCrudPolicySAM policy template instead ofDynamoDBReadPolicy. Redeploy withsam deployand test withcurl -X POST. -
Use cross-stack references. Create a third CloudFormation template that imports the VPC ID and public subnet ID exported by the network stack using
Fn::ImportValue. Use the imported values to launch a second EC2 instance in the same VPC. This demonstrates how teams can share infrastructure across independently managed stacks.
AWS Bootcamp: From Novice to Architect Author: Samuel Ogunti License: CC BY-NC 4.0