The Multi-Region road: CloudFront

The Multi-Region road: CloudFront

In this blog, I will look at how Amazon CloudFront fit in a multi-region design.

As a part of the series The Multi-Region road, you can check out the other parts:

  • Part 1 - a reflection on what to consider before starting a multi-region architecture.

Amazon CloudFront

CloudFront is the Content Delivery Network of AWS, where you can store content closer to the users improving latency and reducing the load on your origins.

CloudFront can be used for many use cases. For this series, I am interested mainly in a multi-region setup.

It is worth mentioning that with CloudFront, I can apply my security needs at the edge before they reach their origins.

One thing that I can do is intercept data before the application process. So, for example, I could leverage the Lambda@Edge where I intercept data and encrypt all sensitive data. Then, when it reaches my services like APIGW, Lambda or DynamoDB, it is already encrypted.

I wrote a PoC a while ago, and the repository is available here.

There are many ways for configuring secure access and restricting access to content:

Multi-Region

Usually, CloudFront is used in front of S3 and/or API endpoints:

2020-11-13-static-website-parameters-aws-website-api-behind-cloudfront.png

What happens if a bucket is deleted or the files are removed? What about if the entire region goes down? What about if the API reaches some quotas?

In this case, I can Optimizing high availability with CloudFront origin failover

cloudfront-failover.png

This configuration is achievable by specifying a group of origins. When CloudFront is unsuccessful in connecting, for example, to the primary origin and if an error status code is returned (500, 502, 503, 504, 403, or 404), CloudFront will then attempt the same request with the secondary origin.

CloudFront with two Origins

Some serverless patterns collection for CloudFront can be found here.

A failover pattern:

multi-region-cloudfront-failover.jpeg

can be done in this way:

AWSTemplateFormatVersion: 2010-09-09
Transform: 'AWS::Serverless-2016-10-31'
Description: An Amazon Cloudfront distribution

##########################################################################
#  Parameters                                                            #
##########################################################################
Parameters:
  PrimaryEndpoint:
    Type: String
  SecondaryEndpoint:
    Type: String

Resources:
##########################################################################
#  CloudFront::CachePolicy                                               #
##########################################################################
  1hCachePolicy:
    Type: AWS::CloudFront::CachePolicy
    Properties:
      CachePolicyConfig:
        Comment: Cache for 1h
        Name: !Ref AWS::StackName
        DefaultTTL: 360
        MaxTTL: 360
        MinTTL: 360
        Name: 1h
        ParametersInCacheKeyAndForwardedToOrigin:
          CookiesConfig:
            CookieBehavior: none
          EnableAcceptEncodingBrotli: false
          EnableAcceptEncodingGzip: false
          HeadersConfig:
            HeaderBehavior: whitelist
            Headers:
              - x-forwarded-for
          QueryStringsConfig:
            QueryStringBehavior: none

##########################################################################
#  CloudFront::Distribution                                              #
##########################################################################
  CloudfrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        PriceClass: PriceClass_100
        IPV6Enabled: true
        HttpVersion: http2
        OriginGroups:
          Items:
            - Id: Failover
              FailoverCriteria: 
                StatusCodes:
                  Items:
                    - 502 # Bad Gateway Exception
                    - 503 # Service Unavailable Exception
                    - 504 # Endpoint Request Timed-out Exception
                  Quantity: 3
              Members: 
                Items: 
                  - OriginId: PrimaryRegion
                  - OriginId: SecondaryRegion
                Quantity: 2
          Quantity: 1
        Origins:
          - Id: PrimaryRegion
            DomainName: !Ref PrimaryEndpoint
            OriginPath: /api
            CustomOriginConfig:
              HTTPSPort: 443
              OriginProtocolPolicy: https-only
              OriginSSLProtocols: 
                - TLSv1.2
          - Id: SecondaryRegion
            DomainName: !Ref SecondaryEndpoint
            OriginPath: /api
            CustomOriginConfig:
              HTTPSPort: 443
              OriginProtocolPolicy: https-only
              OriginSSLProtocols: 
                - TLSv1.2
        Enabled: true
        CacheBehaviors:
          - AllowedMethods:
              - GET
              - HEAD
            CachedMethods:
              - GET
              - HEAD
            Compress: true
            PathPattern: /api
            TargetOriginId: Failover
            ViewerProtocolPolicy: https-only
            CachePolicyId: !Ref 1hCachePolicy
        DefaultCacheBehavior:
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
          Compress: true
          TargetOriginId: PrimaryRegion
          ViewerProtocolPolicy: https-only
          CachePolicyId: !Ref 1hCachePolicy
      Tags: 
        - Key: Name
          Value: !Ref AWS::StackName

Outputs:
  DistributionDomainName:
    Description: "Distribution domain name"
    Value: !GetAtt CloudfrontDistribution.DomainName
    Export:
      Name: DistributionDomainName

Once you have deployed two APIGW in two different regions, you can pass the domains to CloudFront to configure and achieve a multi-region setup failover. CloudFront already geographically routes the incoming request to the nearest edge location using DNS so that the users will be hitting an edge location near them. Then CloudFront will be using the latency-based answer from Route 53 to access the nearest endpoint.

In case I want to put in the mix some content hosted in S3:

multi-region-mixed.jpeg

I highly suggest using the S3 Multi-Region Access Points to provide a single global endpoint to access data stored in multiple S3 buckets in different AWS Regions. Application requests made to an S3 Multi-Region Access Point’s global endpoint automatically route over the AWS global network to the S3 bucket with the lowest network latency.

Conclusion

This is the first step of my multi-region quest, and even if there are other variants of failover, perhaps using Lambda@Edge, I have found the failover explained in this post more straightforward for my needs.

Everything fails, and I think the importance of the CDN is underrated most of the time. Still, thanks to the failover configuration and the S3 Multi-Region Access Points, I now have solved the first step of moving this architecture to a multi-region setup.

sample.png