A Little Cloudformation Intro
Cloudformation is an AWS service where you declare cloud resources like an EC2 instance or an S3 bucket in JSON or YAML files. These JSON or YAML files are called templates. Here's a YAML template the describes an S3 bucket:
# saved as bucket.yaml
AWSTemplateFormatVersion: 2010-09-09
Resources:
S3Bucket:
Type: AWS::S3::Bucket
With this template, you provision an S3 bucket by creating a stack using the awscli
.
aws cloudformation create-stack \
--stack-name mys3bucket \
--template-body file://bucket.yaml
This command takes a short amount of time to complete. You can check the status of the stack with the describe-stacks
command.
aws cloudformation describe-stacks \
--stack-name mys3bucket
describe-stacks
returns StackStatus: CREATE_IN_PROGRESS
while the stack provisions the S3 bucket and StackStatus: CREATE_COMPLETE
when the stack finishes provisioning the S3 bucket.
Once complete, the S3 bucket declared in the template is available. You can verify by listing your s3 buckets.
aws s3 ls
You should see a new bucket prefixed with the stack name (mys3bucket
). Naming is hard, so Cloudformation handled naming the S3 bucket for you. In our template, we did not specify a name for a bucket. So by default, Cloudformation creates a pseudo-random name using the stack's name (mys3ubkcet
) and logical resource name (S3Bucket
).
To delete the S3 bucket, call the delete-stack
command on the mys3bucket
stack.
aws cloudformation delete-stack \
--stack-name mys3bucket
You've now successfully created and deleted an S3 bucket using Cloudformation!
What about updating configuration?
While creating and delete resources is an important step in managing your infrastructure with code, there are times you need to update existing resources created from stacks. Luckily, Cloudformation manages updates as well. For instance, say we forgot to add an environment tag to the provisioned S3 bucket in the mys3bucket
stack from earlier. You can change the template to add a tag:
# saved as bucket-production.yaml
AWSTemplateFormatVersion: 2010-09-09
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: environment
Value: production
Now use the update-stack
command to apply the change.
aws cloudformation update-stack \
--stack-name mys3bucket \
--template-body file://bucket-production.yaml
Similarly, if you removed the tags from the template and upload the template to the existing stack, Cloudformation will remove the tags from the S3 bucket.
Change Sets
A downside with update-stack
is the lack of visibility into the steps Cloudformation takes to change an existing stack's resources. Sometimes cloudformation doesn't take the correct steps you had expected.
Let's look at RDS databases. Like S3 buckets, Cloudformation generates a pseudo-random name for an RDS database template that does not specify the DBName
property. If we wanted to name the database, we could add the DBName
property to the template and update the stack.
Unfortunately, a change for DBName
recreates the database. Any data I had on the original instance is lost.
This situation is where change sets help.
Change sets allow you to preview what Cloudformation intends to change. The change set reports what resources will be created, modified, and removed. If you decide the changes are what you had expected, you can choose to execute the change set which applies the changes.
Let's use change sets to repeat updating the s3 bucket configuration from the prior section.
First, create the change set.
aws cloudformation create-change-set \
--stack-name mys3bucket \
--template-body file://bucket-production.yaml \
--change-set-name new-env-tags
create-change-set
likes to take its time to create itself. Once done, you can describe the ChangeSet to see what's going to change.
aws cloudformation describe-change-set \
--stack-name mys3bucket \
--change-set-name new-env-tags
Here's the output from the command:
{
"Changes": [
{
"Type": "Resource",
"ResourceChange": {
"Action": "Modify",
"LogicalResourceId": "S3Bucket",
"PhysicalResourceId": "mys3bucket-s3bucket-XXXXXX",
"ResourceType": "AWS::S3::Bucket",
"Replacement": "False",
"Scope": [
"Properties",
"Tags"
],
"Details": [
{
"Target": {
"Attribute": "Properties",
"Name": "AccessControl",
"RequiresRecreation": "Never"
},
"Evaluation": "Static",
"ChangeSource": "DirectModification"
},
{
"Target": {
"Attribute": "Tags",
"RequiresRecreation": "Never"
},
"Evaluation": "Static",
"ChangeSource": "DirectModification"
}
]
}
}
],
// ... removed code ...
}
If the output blinds you, know you're not alone. Once you've finished rubbing your eyes, focus on the Replacement
field.
Replacement
determines whether Cloudformation will update the existing cloud resource or replace the cloud resource. In the output above, the value is False
, which means the S3Bucket
resource will not be replaced. Phew!
When it's early morning, and you're trying to fix a production incident, you'll want to know what this field says. Ignoring this field tends to lead to bloody feet.
You can find more examples of change sets in the cloudformation docs.
Assuming you want to apply the change, you can call execute-change-set
.
aws cloudformation execute-change-set --stack-name mys3bucket --change-set-name new-env-tags
If you decided against the change, then delete the ChangeSet.
aws cloudformation delete-change-set --stack-name mys3bucket --change-set-name new-env-tags
ChangeSets help you preview changes before you make them. As your infrastructure grows, ChangeSets help you reduce deployment risks.
One Template Defines Many Resources and Creates Many Stacks
So far, I've discussed how a single template creates a single stack to showcase the basics of Cloudformation. You might believe that every time you want a new resource, you would create a new template. And if you felt your DRY detector firing, you aren't alone.
Luckily, this is not the case. A single template can create multiple stacks. Each stack created from the same template will have unique resources. For instance, if we used bucket.yaml
to create two stacks, we will create two individual S3 buckets.
aws cloudformation create-stack \
--stack-name my-first-bucket \
--template-body file://bucket-production.yaml
aws cloudformation create-stack \
--stack-name my-second-bucket \
--template-body file://bucket-production.yaml
Creating many stacks from a single template allows you to create a staging environment for your small team or provide service components like databases and caches for a large engineering organization.
But to do that, I need to discuss another feature of templates: parameters.
Generalizing with Parameters
bucket-production.yaml
is useful to create and manage S3 buckets for production. If production buckets are the only type of buckets you need, then you're all set. But, if you need a bucket for a staging environment, then we can use parameters. Parameters allow a template to take different arguments, similar to how a function in a programming language can take different arguments.
# saved as environmnet-bucket.yml
AWSTemplateFormatVersion: 2010-09-09
# Turn environment value into a parameter
Parameters:
Environment:
Type: String
Description: The environment
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
Tags:
- Key: "environment"
# Use the Parameter as the value
Value: !Ref Environment
The environment-bucket.yml
template is similar to bucket-production.yml
but environment-bucket.yml
uses a parameter to set the environment
tag. In the template, the !Ref Environment
snippet will be replaced with the value of the Environment
parameter when the template is uploaded to Cloudformation. You can think of the code !Ref Environment
snippet like an interpolated variable in ruby or a variable within an fstring
in python.
With this new template, creating a stack now requires the Environment
parameter which you can pass in with the --parameters
flag. Creating a stack with the template with the Environment
parameter set to "staging" will create a new S3 bucket tagged environment: staging
.
aws cloudformation create-stack \
--stack-name my-prod-bucket \
--template-body file://environment-bucket.yaml \
--parameters Environment=production
aws cloudformation create-stack \
--stack-name my-stg-bucket \
--template-body file://environment-bucket.yaml \
--parameters Environment=staging
Now you have a generic S3 bucket template that adds a tag with an environment of your choice. Rejoice in reuse!
For more ideas on building templates, read the templates at awslabs/aws-cloudformation-templates .
A Review
We use templates to declare cloud resources using plain JSON or YAML. These templates are used to spawn stacks that provision the resources declared in the templates. When we want to change cloud resources, we modify the template and update the stack. When we no longer need the resources, we delete the stack, and all the resources are removed.
If we're unsure what a change to a stack or template will look like, we can rely on change sets. Creating a change set describes the changes Cloudformation plans to make to the cloud resources. Remember to look for the Replacement
field!
We also know now that a single template can launch multiple stacks to create identically configured cloud resources. If we add parameters to templates, we can make generalized templates that are easier to reuse.