AWS Cloud Operations Blog

Handling Region parity with infrastructure as code

AWS CloudFormation allows you to create and manage resources with templates. AWS provides a number of Regions where its services and features are available. Although it can be beneficial to deploy the same AWS CloudFormation template in multiple Regions, customers who operate in multiple Regions face challenges due to parity differences among services and their features. This is especially true of Regions in different partitions, such as AWS GovCloud (US) and AWS China.

We released AWS CloudFormation templates on GitHub that offer strategies to handle these parity differences. Let’s review how they work.


Codifying Region parity

Consider an application running in Europe (London) known as eu-west-2 and Europe (Paris) known as eu-west-3. AWS CodeStar, a service you can use to quickly develop, build, and deploy applications on AWS, is, at the time of this writing, unavailable in eu-west-3. Rather than limiting the application to the features and services common to both Regions, you might find it beneficial to use the most appropriate features and services in each Region. In this example, that might mean using AWS CodeStar in eu-west-2, and Amazon CloudWatch in eu-west-3.

This Region parity difference can be codified using condition statements:

Conditions:
  cRegionDoesNotSupportCodeStar: !Or
    - !Condition cStandardRegionDoesNotSupportCodeStar
    - !Condition cGovCloudRegionDoesNotSupportCodeStar
    - !Condition cChinaRegionDoesNotSupportCodeStar
  cStandardRegionDoesNotSupportCodeStar: !Or
    - !Equals [!Ref AWS::Region, af-south-1]
    - !Equals [!Ref AWS::Region, ap-east-1]
    - !Equals [!Ref AWS::Region, ap-south-1]
    - !Equals [!Ref AWS::Region, ap-northeast-3]
    - !Equals [!Ref AWS::Region, eu-south-1]
    - !Equals [!Ref AWS::Region, eu-west-3]
    - !Equals [!Ref AWS::Region, me-south-1]
    - !Equals [!Ref AWS::Region, sa-east-1]
  cGovCloudRegionDoesNotSupportCodeStar: !Or
    - !Equals [!Ref AWS::Region, us-gov-west-1]
    - !Equals [!Ref AWS::Region, us-gov-east-1]
  cChinaRegionDoesNotSupportCodeStar: !Or
    - !Equals [!Ref AWS::Region, cn-northwest-1]
    - !Equals [!Ref AWS::Region, cn-north-1]

Because the !Or operator is limited to 10 conditions, you can create conditions for each partition (cStandardRegionDoesNotSupportCodeStar, cGovCloudRegionDoesNotSupportCodeStar, and cChinaRegionDoesNotSupportCodeStar) and then aggregate them into the overall condition (cRegionDoesNotSupportCodeStar). Be sure to update these condition statements as new Regions are created and new features and services are supported in those Regions.

AWS CloudFormation resource evaluation

AWS CloudFormation evaluates the resource type before the condition. Even with a condition, the following code throws an Unrecognized resource types error in a Region that does not support AWS CodeStar Notifications:

  # This code does not work in a Region that does not support AWS CodeStar!
  rCodeCommitRepoNotification:
    Condition: cRegionSupportsCodeStar
    Type: AWS::CodeStarNotifications::NotificationRule

Instead of directly applying the condition to the resource unsupported in the Region (in this example, AWS CodeStar Notifications), you can move the resource to a nested stack (codestar.yml). Create a second nested stack (cloudwatch.yml) with the alternative solution that uses a resource supported in the Region (in this example, an Amazon CloudWatch event rule).

cloudwatch.yml:

  rCodeCommitRepoNotification:
    Type: AWS::Events::Rule
    ...

codestar.yml:

  rCodeCommitRepoNotification:
    Type: AWS::CodeStarNotifications::NotificationRule
    ...

In the top-level stack (notification-service.yml), apply the condition to select the nested stack based on Region parity.

notification-service.yml:

  rNotificationStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: 
        !Sub
        # in standard Regions, Amazon S3 domain is s3.amazonaws.com
        # in AWS GovCloud (US) Regions, Amazon S3 domain is s3.{AWS::Region}.amazonaws.com
        # in AWS China Regions, Amazon S3 domain is s3.{AWS::Region}.amazonaws.com.cn
        - https://${pBucket}.s3${region_specific}.${AWS::URLSuffix}/${notification_service}.yml
        - notification_service: !If [ cRegionDoesNotSupportCodeStar, cloudwatch, codestar ]
          region_specific: !If [ cRegionSpecificS3Endpoint, !Sub ".${AWS::Region}", "" ]
     ...

The cRegionDoesNotSupportCodeStar condition in the TemplateURL property tells AWS CloudFormation that if the Region does not support AWS CodeStar to use the Amazon CloudWatch nested stack instead. Otherwise use the AWS CodeStar nested stack. AWS CloudFormation does not evaluate the resources in a nested stack that is conditioned out. Using this approach, the nested stack that contains AWS CodeStar resources can still be included in a Region that does not support AWS CodeStar, so long as the condition in the top-level stack calls a different nested stack (in this case, Amazon CloudWatch).

The cRegionSpecificS3Endpoint condition in the TemplateURL property tells AWS CloudFormation to use the Amazon S3 endpoint appropriate for the selected Region. AWS GovCloud (US) and AWS China Regions require a Region-specific Amazon S3 endpoint, but standard Regions do not. For more information, see Amazon Simple Storage Service endpoints and quotas, Using GovCloud Endpoints, Beijing Region Endpoints, and Ningxia Region Endpoints.

Pseudo parameters for AWS::Region and AWS::URLSuffix ensure the correct Region names and URL suffixes are used for each environment. For example, in Europe (London), AWS::Region will evaluate to eu-west-2 and AWS::URLSuffix will evaluate to amazonaws.com. In AWS China (Beijing), AWS::Region will evaluate to cn-north-1 and AWS::URLSuffix will evaluate to amazonaws.com.cn.

Conclusion

In this post, we covered how to use infrastructure as code to handle parity differences between AWS Regions. We can codify these parity differences with condition statements that associate features and services with the Regions that support them. Instead of applying conditions directly to unsupported resources, we can move the unsupported resources to a nested stack. Then, we can apply conditions to the nested stack, thereby avoiding an Unrecognized resource types error in AWS CloudFormation.

Deploy this solution today to handle Region parity differences in your application. The code is open source. Join us on GitHub to contribute!

About the author

Brian Landry is a Senior Consultant at AWS working out of San Diego, CA. He enjoys traveling, attending sporting events, and visiting breweries.