Skip to content
This repository has been archived by the owner on Jun 23, 2021. It is now read-only.

CloudFormation

James Hood edited this page Oct 14, 2019 · 4 revisions

This application uses AWS CloudFormation to define and deploy the application to AWS. This page includes patterns and best practices for working with CloudFormation that are used in this application. Note, if you are new to AWS CloudFormation, you may want to read this introduction page from the AWS CloudFormation User Guide before continuing.

Organize resources into nested stacks

More complex component stacks use nested stacks to organize the resources within that component. For example, the backend stack has a nested stack for API resources and another one for database resources. Similarly, the ops stack has a nested stack for alarms and another one for dashboards.

In addition to general organization of resources, nested stacks are also useful for

  1. helping you to avoid hitting the CloudFormation stack resource limit. This is very important since CloudFormation does not support refactoring resources between stacks, so fixing this after hitting the limit can be painful.
  2. tagging a group of related resources with the same AWS tags. When you tag a nested stack, the same tags are applied to all resources within the nested stack that support tagging.

Note: In this example application, the DynamoDB table storing the backend data is created in a nested stack along with the rest of the backend top-level stack. However, some teams opt to create their database resources in a completely separate top-level stack with its own CD pipeline in order to minimize deployments to database resources.

Examples in this project:

  1. The backend template contains nested stacks organizing API resources and database resources into separate nested stacks.
  2. The ops template contains nested stacks organizing alarm resources and dashboard resources into separate nested stacks.

Use pseudo parameters for increased portability

Pseudo parameters are parameters that are predefined by AWS CloudFormation. You do not declare them in your template. Use them the same way as you would a parameter, as the argument for the Ref function. Pseudo parameters are useful for making your template portable so you can use it to deploy stacks in different accounts, regions, and even AWS partitions, e.g., AWS GovCloud regions. Pseudo parameters like AWS::AccountId, AWS::Region, and AWS::Partition should be used in place of hardcoded values whenever possible. To learn more about CloudFormation pseudo parameters, see the AWS CloudFormation User Guide.

Examples in this project:

  1. Setting the access log destination log group ARN
  2. Mapping an API Gateway resource method to a Lambda function

Let CloudFormation generate resource names

In CloudFormation, you can define explicit names for resources, such as the name of an Amazon DynamoDB table. However, if you choose not to fill in an explicit name for a resource, CloudFormation will generate a unique resource name for you. In general, it is better to let CloudFormation name resources for you. This ensures the template is portable and can be used to deploy multiple stacks in the same account/region without encountering a name conflict error.

CloudFormation's automatic naming algorithm also takes into account naming constraints for specific resource types. For example, S3 bucket names have many constraints such as compliance with DNS naming conventions and not allowing uppercase letters.

To learn more about CloudFormation resource names, see the AWS CloudFormation User Guide.

Referencing Stack Resources

When using CloudFormation to provision AWS resources for your application, you will need to reference the names and attributes of resources created by CloudFormation in your CloudFormation templates as well as within your Lambda function code, e.g., specifying the name of a DynamoDB table to read/write.

CloudFormation provides a number of tools for referencing resource attributes within templates and Lambda functions, but it can be unclear which tools to use in which situations. Here are the best practices we've learned when building a complex serverless application:

Share application resource attributes via SSM Parameter Store

In general, application resource attributes that need to be referenced elsewhere in the application should be saved in AWS SSM Parameter Store. There are several advantages to using SSM Parameter Store:

  1. Its default tier is free and allows up to 10,000 parameters to be stored.
  2. It supports parameter hierarchies, making it easy to organize parameters and also bulk fetch all parameters for an application.
  3. It supports parameter value history for easy auditing.
  4. CloudFormation supports creating SSM parameters and using dynamic references to retrieve parameter values and embed them into your template.
  5. You can pass more parameter values to a Lambda function via SSM Parameter Store than Lambda environment variables, which have a 4KB limit. Also, Lambda environment variables are not supported in all regions where Lambda is supported, e.g., China regions.

SSM parameters allow you to share application resource attributes across stack templates and within your Lambda function code.

Some things to note about using SSM Parameter Store:

  1. Due to SSM Parameter Store's API throughput limits, it is important to use a caching client library when retrieving SSM parameters. For example, aws-ssm-java-caching-client is an SSM Parameter Store caching client library for Java and ssm-cache-python is an SSM Parameter Store caching client library for Python.
  2. CloudFormation's AWS::SSM::Parameter resource type cannot be used to create SecureString (encrypted) parameters. While CloudFormation resource attributes are ok to store in plain text in SSM Parameter Store, if your template has sensitive data passed as a parameter which needs to be stored, you can store it in AWS Secrets Manager using the AWS::SecretsManager::Secret resource type.

Examples in this project:

  1. Saving the Applications DynamoDB table name to SSM and referencing it in a different stack template as well as retrieving it within Lambda function code.
  2. Saving the Applications DynamoDB table stream ARN to SSM and referencing it in the analytics stack template to attach a DDB stream fanout app to it.

Exception: Generic, reusable templates

SSM Parameter Store works best when sharing resources across a specific application, like the one in this project. However, you can also create a CloudFormation template that represents a generic, self-contained "app" which is meant to be reused across many applications. Apps like these can be published to the AWS Serverless Application Repository and embedded in applications as nested applications.

Since these kinds of self-contained apps are generally special-purpose and small, they can use Lambda function environment variables internally and CloudFormation template outputs to surface resource attributes to the parent template that is nesting them. The parent template could then choose to save one of the outputs to SSM Parameter Store if it wanted to share it more widely across the rest of the application.

Examples in this project:

  1. The analytics stack nests the general purpose aws-dynamodb-stream-eventbridge-fanout app from the AWS Serverless Application Repository. The app attaches to the given DynamoDB stream ARN and forwards events to AWS EventBridge.
  2. Since the aws-dynamodb-stream-eventbridge-fanout app is written to be generic and reusable, it uses Lambda environment variables to pass resource attributes to its internal Lambda function and surfaces attributes to the parent template via template outputs.