October 03, 2019
In this post I’m going to break down some of the problems we’ve had, and the strategy we made for setting up a CI/CD managed, versioned deploy system for static sites in AWS using S3 and CloudFront. While this will work for your personal site I don’t recommend it as it is unlikely to stay within the free tier of AWS for very long.
There are a lot of resources available to set up a personal site on S3 on the free tier using Route53 for DNS. This approach didn’t work for our needs for several reasons:
Atomic deployment
We don’t want users being served in the middle of a deploy and getting the wrong assets. An example of this would be if a request came in for index.html
and app.js
during a non-atomic deploy, different versions of the assets could be served for that request. While S3 object uploads are atomic, there isn’t a guarantee that a folder sync will be.
Instant rollback
We wanted the ability to rollback a faulty deploy in seconds. With the setup described in most guides this would not be possible without going outside of the CI/CD pipeline and manually syncing compiled assets to S3.
The main problem is using S3 as a web server has limitations. The bucket name needs to match the DNS CNAME
.
Two Distributions:
The blue/green deploy strategy is outlined in Martin Fowler’s famed blog post. The problem is that distributions in AWS can’t be aliased to the same CNAME
. So we can’t have two distributions pointing to the same domain.
Two Buckets:
Similar idea here, but one distribution and 2 buckets. The issue we ran into was with the bucket naming “latching” on to the bucket with the actual site name (i.e. app.your-website.com). This might have worked with more finessing, but we discovered the folder solution while working with this.
Version folders in single bucket
While working on different distribution settings, we discovered the “origin path” setting for using S3 origins. This enables using a folder in a bucket as the “root” for the distribution. From there the distribution default object and error behaviors take effect. In CI we can use the AWS cli to manage actions to the infrastructure.
Since we are syncing to a new folder on the site bucket we don’t have to worry about users getting served different content between deploys, or worse, those results getting cached and served for hours.
Failure recovery becomes as simple as changing the distribution path back to the last good deploy and invalidating the cache. This can easily be done in a script, enabling fast recovery.
Ingredients:
Setup:
This is where you’ll be putting the static assets for your app
You’ll need to enable the static site hosting option
Access Policy set to public read
{
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [
{
"Sid": "1",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::app.your-website-name.com/*"
}
]
}
s3-website
. This will ensure your assets are served properlyYou’ll also need to enable CNAME
aliasing to your domain name here. It’s very simple to get the cert for SSL (we did it by DNS verification) to enable HTTPS
Set default object to index.html
(optional) Create error page for rerouting 404s to index
(optional) Create behavior for redirecting to HTTPS
AWS orb
version: 2.1
orbs:
aws-cli: circleci/[email protected]
jobs:
build:
docker:
- image: circleci/node:10.15.0
working_directory: ~/repo
executor: aws-cli/default
steps:
...
- build:
...
- aws-cli/install
- aws-cli/configure:
profile-name: circleci
configure-default-region: false
- deploy:
...
Deploy script
Here is where things get a little tricky. The basic steps are:
Limit growth of deploy bucket
You’ll notice that we are never deleting versions from our deploy bucket. This can be useful for a few versions but not past a certain point. We could limit the bucket to have at most 10 versions for the extreme worst case (our last 9 versions all have serious flaws and we need to roll back to the 10th version).
Script rollback processes
This one is pretty simple. Right now a developer still needs to manually change the CloudFront distribution to roll back. A script could easily look for the previous release and change it.
Better logging
We currently don’t have any special logging around the deploy processes, this will be invaluable when things go wrong in the future.