Blog

Implementing AWS CDK CI/CD with CDK Pipelines

02 Sep, 2020
Xebia Background Header Wave

The AWS Cloud Development Kit (AWS CDK) makes it easy to build cloud infrastructure. And CDK Pipelines makes it “painless” to deploy cloud infrastructure. So let’s create an AWS CDK CI/CD pipeline, and build and run our application on AWS.

AWS CDK CI/CD Pipeline

AWS CDK CI/CD Pipeline
An AWS CDK CI/CD Pipeline, essentially, downloads the application sources, builds the CloudFormation-templates, updates itself and deploys the application. Note that the self-update enables easy addition/removal of pipeline stages.
The entire pipeline, amazingly enough, is implemented by two constructs: CdkPipeline and Stage. CdkPipeline defines the CodePipeline and associated stages and actions. Stage defines the applications stack-set.

CDK Pipelines ECS Deployment

Let’s test CDK Pipelines by deploying to AWS ECS. Since ECS is well-supported by CDK and CloudFormation, I expect a pretty painless implementation.

The application source, Dockerfile and cloud infrastructure are provided at GitHub.

ECS CDK CI/CD Pipeline
The ECS CI/CD pipeline includes an app build stage to create container images. Because the images are built after the CloudFormation-templates, we have to figure out how to specify them. Since the CdkPipeline.addApplicationStagedoesn’t support parameterOverrides, I implemented a tagging convention. All images are tagged with the GitHub commit id. And since the application- and infrastructure code are versioned together, we build valid CloudFormation-templates.

Remark It’s possible to use parameterOverrides by creating the CodePipeline stages yourself. Since this defeats the purpose of CdkPipeline, I don’t recommend doing so.
Building the pipeline is pretty painless. All CI/CD infrastructure is defined by the following code.

import * as cdk from '@aws-cdk/core';
import * as codepipeline from '@aws-cdk/aws-codepipeline';
import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions';
import * as codebuild from '@aws-cdk/aws-codebuild';
import * as ecr from '@aws-cdk/aws-ecr'
import * as iam from '@aws-cdk/aws-iam';
import * as pipelines from '@aws-cdk/pipelines';
import { LocalDeploymentStage } from './local-deployment';

export class CicdInfraStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const sourceArtifact = new codepipeline.Artifact();
    const cdkOutputArtifact = new codepipeline.Artifact();

    // The core CdkPipeline is created and consists of DownloadSources, cdk synth and cdk deploy <this stack>
    const pipeline = new pipelines.CdkPipeline(this, 'CdkPipeline', {
      pipelineName: 'cdk-cdkpipeline',
      cloudAssemblyArtifact: cdkOutputArtifact,
      sourceAction: new codepipeline_actions.GitHubSourceAction({
        actionName: 'DownloadSources',
        owner: 'binxio',
        repo: 'blog-cdk-cicd-cdkpipeline',
        oauthToken: cdk.SecretValue.secretsManager('/github.com/binxio', {
          jsonField: 'token'
        }),
        output: sourceArtifact,
      }),
      synthAction: pipelines.SimpleSynthAction.standardNpmSynth({
        sourceArtifact: sourceArtifact,
        cloudAssemblyArtifact: cdkOutputArtifact,
        subdirectory: 'cdk',
      }),
    });

    const repository = new ecr.Repository(this, 'Repository', {
      repositoryName: 'cdk-cicd/app',
    });
    const buildRole = new iam.Role(this, 'DockerBuildRole', {
      assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com'),
    });
    repository.grantPullPush(buildRole);

    // The docker build stage is added
    const buildStage = pipeline.addStage('AppBuild')
    buildStage.addActions(new codepipeline_actions.CodeBuildAction({
      actionName: 'DockerBuild',
      input: sourceArtifact,
      project: new codebuild.Project(this, 'DockerBuild', {
        role: buildRole,
        environment: {
          buildImage: codebuild.LinuxBuildImage.STANDARD_4_0,
          privileged: true,
        },
        buildSpec: this.getDockerBuildSpec(repository.repositoryUri),
      }),
    }));

    // The deployment of Stage 'Local' is added
    const localStage = new LocalDeploymentStage(this, 'AppDeployLocal');
    pipeline.addApplicationStage(localStage);
  }

  getDockerBuildSpec(repositoryUri: string): codebuild.BuildSpec {
    return ...
  }
}

And after the CI/CD Pipeline stack deployment, the pipeline executes. Painlessly.
ECS CodePipeline execution
Discussion


AWS CDK keeps getting better. The constructs composition model is powerful and creates impressive products such as CDK Pipelines. CDK Pipelines, however, still lacks some scaling options. Currently, for example, each pipeline creates it’s own Artifacts bucket.
In this experiment I tested the known path. More experiments are required to determine CDK Pipelines quality with existing infra deployments, complex Stage (stack-set) deployments and cross-account deployments.

Conclusion

Implementing a AWS CDK CI/CD pipeline is nearly painless with CDK Pipelines. So start using AWS CDK to build and run your application on AWS.

Laurens Knoll
As a cloud consultant I enjoy taking software engineering practices to the cloud. Continuously improving the customers systems, tools and processes by focusing on integration and quality.
Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts