Namespace based deployment
Estimated Reading Time: 20 minutes
Difficulty: Intermediate
This post is purely illustrative and is to be considered a deployment option that may suit an organization’s CICD workflow and feedback loop for Website and API resource deployment.
With one development team working on a single project in multiple branches, it may be advantageous to deploy each branch in an isolated manner that grants other developers and product owners access to a work in progress remotely.
A use case for this is a client-rendered SPA or Static Website with an API being deployed in AWS, three developers working on three separate new features in one Micro-frontend. The organization feedback loop standard is to publish to the development environment for visual review after completing code review.
The development environment would be written over multiple times with each commit, leaving reviewers to gamble as to which feature they are experiencing.
50,000ft view of namespace deployment
Technically namespace deployment is already performed in the manner of subdomains, resource tagging and prefixing, and other methods. If building a Stack on each commit, and tearing it down after a few hours is a desired or current workflow, this can be achieved via namespaced deployment as shown in the “dev” site below (for which I used a random phrase generator).
- dev.example.com
- infinity-mouse.example.com
- highfalutin-cord.example.com
- makeshift-foot.example.com
- qa.example.com
- stage.example.com
- http://www.example.com
So how do we do that in AWS?
We are going to need a few things first, but it can be done using the AWS CDK. These items should be complete or available before attempting to deploy.
- AWS CDK installed
- AWS credentials (best defined via profile)
- A domain name that points to AWS nameservers
- A CNAME for the wildcard domain (*.example.com – created by default)
- A wildcard SSL certificate issued for the domain through ACM in the us-east-1 region as this is required by CloudFront for HTTPS (we want to test on HTTPS)
Create constants from Environment Variables
These environment variables will help with the naming of the resources while authoring the stack, these should be required.
- DOMAIN_NAME
- APPLICATION_NAME
- NAMESPACE
- CERTIFICATE_ARN
Add tags to all resources in the Stack
Stack-created resources may have unique names that do not include the Namespace as a helper construct may have generated them. Applying tags to all resources makes them easier to find when searching, and can be found all at once using the AWS Tag Editor.
To add tags to all resources in one go, it can be done when initializing the stack (in the bin dir).
const app = new cdk.App();
new AwsCdkNamespaceDeploymentWebStack(app, `${NAMESPACE}`, {});
cdk.Tags.of(app).add('NAMESPACE', NAMESPACE);
cdk.Tags.of(app).add('APPLICATION_NAME', APPLICATION_NAME);
Gotcha #1 – Plan to use “HostedZones.fromLookup()“? env needs to be set at the stack level!!!
This seems like an anti-pattern and doesn’t follow the convention of most constructs however, it was easy enough to remedy using process.env vs hard coding values. These environment variables get set regardless of how the AWS account and Region are loaded
export class AwsCdkNamespaceDeploymentWebStack extends CDK.Stack {
constructor(scope: Construct, id: string, props?: CDK.StackProps) {
super(scope, id, {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
});
Gotcha #2 S3 Bucket naming conventions
Since S3 bucket names are unique, they have a ton of restrictions on the bucket name. This should be ok, as the bucket name needs to match the domain name to be resolved by Route53 (not so much by CloudFront). If the final domain is infinity-mouse.example.com, then the bucket name should be infinity-mouse.example.com.
The bucket name must also be lowercased.
const websiteBucket: Bucket = new Bucket(this, `${NAMESPACE} S3 Bucket`, {
websiteIndexDocument: 'index.html',
bucketName: domainWithNamespace.toLowerCase(),
removalPolicy: CDK.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
Deploying Static Assets
For client-side rendering SPAs and Static Websites, the assets will either be compiled into or worked on in a directory in the project. CDK makes this deployment easy through the BucketDeployment construct.
const bucketDeployment: BucketDeployment = new BucketDeployment(
this,
`${NAMESPACE} S3 Bucket Deployment`,
{
sources: [Source.asset('./dist')],
destinationBucket: websiteBucket,
}
);
An Origin Access Identity (OAI) must be created to grant S3 bucket read access to CloudFront.
The OAI allows the S3 bucket to remain private while allowing exposure through the controls of CloudFront.
const originAccessIdentity: OriginAccessIdentity = new OriginAccessIdentity(
this,
`${NAMESPACE} CloudFront OAI`
);
// grant read rights to the S3 Bucket for the OAI
websiteBucket.grantRead(originAccessIdentity);
ApiGateway stageName needs to be set
In the ApiGateway documentation, it is stated that ApiGateway creates a “prod” stageName by default when creating a RestAPI. While this may be the case, errors were being thrown until this was set manually. This will need further review, however, this stage name could be anything as the environments are isolated from one another.
const api = new RestApi(this, `${NAMESPACE} Rest Api`, {
description: `API for ${NAMESPACE} environment`,
deployOptions: {
stageName: 'prod',
},
restApiName : NAMESPACE
});
CloudFront Target and Aaaa record
For the solution to work properly the CloudFront distribution needs to be the next in command once a request reaches the hosted zone. The logic is saying, on the arrival of a request to this hosted zone (the root domain), send the request to an AWS service, in this case, CloudFront to handle routing, serving content, etc.
The use of Aaaa record is to use the IPv6 address of CloudFormation, there are benefits to IPv6 all of which can easily be found in IPv4 vs IPv6 comparison articles.
There it is!
This exercise was fun, and a bit challenging to say the least. Hopefully, it proves to be a good starting point for someone looking for this style of deployment.
The repository associated with this article will more than likely be refactored to clean it up a bit, possibly a neater file structure, the use of constructs, and or child stacks.
SHOW ME THE CODE!!!
https://github.com/kamauwashington/aws-cdk-namespaced-web-api
