5/5 - (1 vote)

Bolt Senior Engineer shared the ability to use infrastructure as code with AWS CDK using a web game as an example.

architecture pattern

The architecture shown below accurately represents the resources used for the web version of the familiar game Snake (image 1):Key features:

  1. It’s a serverless architecture: no reserved computing power and no upfront costs.
  2. Amazon API Gateway serves as the only entry point into the application.
  3. S3 storage is used to host the static website and media content (in our case, the game itself).
  4. AWS Lambda is used as the API runtime.
  5. Dynamo DB saves the state of the system (in this case, the game score).
  6. For the sake of simplicity of the example, I have to bypass the integration with Cognito, which in the real solution was used to identify users.

According to the diagram above, the repository contains three projects:

/aws-snake
/snake-API # node/express API
/snake-client # normal JavaScript game
/snake-iac # AWS CDK project

The original commercial project is built on .NET Core, but for the current sample, I’m using TypeScript for three reasons:

  1. TypeScript looks like a cross between Java/C# and JavaScript.
  2. JavaScript-like syntax is almost universally understood.
  3. To prove that the CDK really works regardless of the chosen programming language.

Getting started with CDK

Getting started with AWS CKD is quite simple, you can either follow the official getting started instructions or follow these steps:

    • Create an AWS account.
    • Create an AWS Access Key.
    • Install AWS CLI .
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2. pkg -target /
  • Configure AWS CLI using a pre-generated key
    aws configure
    
  • Install NodeLTS.
    brew install node #mac 
    sudo apt-get install nodejs #debian
    
  • Install the AWS CDK toolkit.
    npm install -g aws-cdk
    
  • Set the target runtime (in our case TypeScript) .
    npm install -g typescript
  • Create an empty CDK project.
    mkdir test -iac 
    cd test -iac
    cdk init app --language typescript
    
  • Get familiar with CDK commands.
    cdk list # List all program stacks 
    cdk synthesize # Synthesize and print the CloudFormation for the current stack 
    cdk bootstrap # Deploying the CDK tool stack in an AWS environment 
    cdk diff # Compares the local stack with the expanded stack and prints the difference
    cdk deploy # Deploying a stack in an AWS account
    cdk destroy # Destroy the unrolled stack
    

That’s all! Now you know all the necessary CDK commands. Although basically you will only use two:

cdk diff

And

cdk deploy

Stack setup

A CloudFormation stack is a collection of AWS resources managed as a single entity, configured as a JSON or YAML template
. Basically, the AWS CDK wraps the CloudFormation stack, allowing you to create it using a general purpose programming language.

cdk init app

creates an empty stack with the corresponding code file depending on the target language. In our case, this

lib/snake-iac-stack. ts
import *  as cdk from '@aws-cdk/core' ; export class  SnakeIacStack extends  cdk . Stack {
constructor ( scope: cdk. Construct , id: string, props?: cdk. StackProps ) {
super ( scope, id, props ) ; 
// The pre that defines your stack goes here 
}
}

Adding a new resource to the stack is simply creating a new class instance, for example, to create an S3 store to host our JavaScript game, we simply add the following lines:

// pre that defines your stack goes here 
// Snake Web Client 
const snakeClient = new  s3. Bucket ( this , nameIt ( "website-s3" ) , {
 bucketName: nameIt ( "website-s3" ) ,
 versioned : false
 publicReadAccess: true ,
 websiteIndexDocument: "index.html" ,
 removalPolicy: cdk. RemovalPolicy . DESTROY // remove on stack destruction
}) ;

Note that it is very important to set a universal naming convention for all resources, for example:

< environment > - < project > - < resource >

To do this, we use the following helper function:

const envName = 'test' ;
const nameIt = ( name: string ) = >  ` $ { envName } -snake- $ { name } ` . toLowerCase () ;

Combination of resources

An essential feature of the CDK is the ability to logically link resources together using them as dependencies. For example, we can set the s3 configured above as the default route (entry point) to our web application:

// API Gateway 
const snakeClientIntegration = new  integration. HttpProxyIntegration ( {
 method:gate. HttpMethod . RECEIVE ,
 url: snakeClientBucket. bucketWebsiteUrl ,
}) ;
const httpApi= new  gate. HttpApi ( this , nameIt ( "Api-GateWay" ) , {
 apiName: nameIt ( "Api-GateWay" ) ,
 defaultIntegration: snakeClientIntegration,
}) ;

Next, we can set the API routes in the same way:

const apiGateway = new  gate. HttpApi ( this , nameIt ( "Api-GateWay" ) , {
// ... //
}) ;
// Snake API 
const apiLambda = new  lambda. Function ( this , nameIt ( "api-lambda" ) , {
// ... //
}) ;
const snakeApiIntegration = new integration. LambdaProxyIntegration ({
 handler: apiLambda, // api integration
}) ;
APIGateway. addRoutes ({ // api route 
 path: "/api/{proxy+}" ,
 methods: [ gate. HttpMethod . ANY ] ,
 integration: snakeApiIntegration
}) ;
APIGateway. addRoutes ({ // swagger route 
 path: "/swagger/{proxy+}" ,
 methods: [ gate. HttpMethod . GET ] ,
 integration: snakeApiIntegration
}) ;

The same goes for managing access control, granting database access to an API component:

// persistence layer 
const table = new  dynamodb. Table ( this , nameIt ( 'DynamoDb - Table ' ) , {
// ... //
}) ;table. grantReadWriteData ( apiLambda ) ; // give api access to dynamo DB table

To complete the stack configuration sample,  refer to   the project repository

Stack View

First, you can find the CloudFormation stacks in the AWS web console (image 2):

Image 2. CloudFormation stacks in the AWS console
Second, the same list of stacks can be obtained by running the following command in the terminal:

aws cloudformation list-stacks

Configuration Drift Handling

Unfortunately, this is a weakness in both AWS CDK and CloudFormation, as configuration drift detection and handling is not yet “out of the box” (see open  question  ).
Handling configuration drift is possible, but not trivial (see  article  ).
Therefore, I would suggest starting with preventing the possibility of making unauthorized changes to the configuration.
Ideally, all changes to the infrastructure should be made programmatically, on behalf of a separate account used exclusively by the CDK application.
This approach can also be automated (see open  question  ).

CI/CD

AWS CDK can be easily integrated into continuous delivery workflows. This sample uses GitHub Actions, while we used Azure DevOps for the original project, the differences are not significant.
The CI/CD configuration sample consists of two important elements:

  1. Script to compile deployed packages (./build.sh)
  2. Setting up GitHub Workflow as a YAML file describing delivery to multiple environments (test, prod) with admin approval (/.github/workflows/ci-cd.yml)

The resulting delivery result is shown below (image 3):
Image 3. GitHub Workflow Delivery Result
Refer to the CI/CD guide in the Readme.MD for setup.

Output

The AWS CDK has set a precedent for the DevOps practice of the future, and the ongoing development of the Terraform CDK proves that there is a broad interest in using general purpose languages ​​to implement “infrastructure as code”.
This approach makes the discipline of continuous delivery more accessible, allows developers to be involved in maintaining the infrastructure, and also use existing quality control tools like static analysis, or even unit tests.