Continuous integration and delivery (CI/CD) using CDK Pipelines

To Nha Notes | Oct. 24, 2023, 3:46 p.m.

CDK Pipelines is a construct library module designed to simplify the continuous delivery of AWS CDK applications.

Whenever you check your AWS CDK app's source code into AWS CodeCommit, GitHub, or AWS CodeStar, CDK Pipelines can automatically build, test, and deploy your new version¹. This means that any changes you make to your code can be quickly and automatically reflected in your live application.

Definining the pipeline

In lib/my-pipeline-stack.ts (may vary if your project folder isn't named my-pipeline):

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { CodePipeline, CodePipelineSource, ShellStep } from 'aws-cdk-lib/pipelines';

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

    const pipeline = new CodePipeline(this, 'Pipeline', {
      pipelineName: 'MyPipeline',
      synth: new ShellStep('Synth', {
        input: CodePipelineSource.gitHub('OWNER/REPO', 'main'),
        commands: ['npm ci', 'npm run build', 'npx cdk synth']
      })
    });
  }
}

In bin/my-pipeline.ts (may vary if your project folder isn't named my-pipeline):

#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { MyPipelineStack } from '../lib/my-pipeline-stack';

const app = new cdk.App();
new MyPipelineStack(app, 'MyPipelineStack', {
  env: {
    account: '111111111111',
    region: 'eu-west-1',
  }
});

app.synth();

Application stages

To define a multi-stack AWS application that can be added to the pipeline all at once, define a subclass of Stage. (This is different from CdkStage in the CDK Pipelines module.)

The stage contains the stacks that make up your application. If there are dependencies between the stacks, the stacks are automatically added to the pipeline in the right order. Stacks that don't depend on each other are deployed in parallel. You can add a dependency relationship between stacks by calling stack1.addDependency(stack2).

Stages accept a default env argument, which becomes the default environment for the stacks inside it. (Stacks can still have their own environment specified.).

An application is added to the pipeline by calling addStage() with instances of Stage. A stage can be instantiated and added to the pipeline multiple times to define different stages of your DTAP or multi-Region application pipeline.

We will create a stack containing a simple Lambda function and place that stack in a stage. Then we will add the stage to the pipeline so it can be deployed.

Create the new file lib/my-pipeline-lambda-stack.ts to hold our application stack containing a Lambda function.

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Function, InlineCode, Runtime } from 'aws-cdk-lib/aws-lambda';

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

      new Function(this, 'LambdaFunction', {
        runtime: Runtime.NODEJS_18_X,
        handler: 'index.handler',
        code: new InlineCode('exports.handler = _ => "Hello, CDK";')
      });
    }
}

Create the new file lib/my-pipeline-app-stage.ts to hold our stage.

import * as cdk from 'aws-cdk-lib';
import { Construct } from "constructs";
import { MyLambdaStack } from './my-pipeline-lambda-stack';

export class MyPipelineAppStage extends cdk.Stage {

    constructor(scope: Construct, id: string, props?: cdk.StageProps) {
      super(scope, id, props);

      const lambdaStack = new MyLambdaStack(this, 'LambdaStack');
    }
}

Edit lib/my-pipeline-stack.ts to add the stage to our pipeline.

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { CodePipeline, CodePipelineSource, ShellStep } from 'aws-cdk-lib/pipelines';
import { MyPipelineAppStage } from './my-pipeline-app-stage';

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

    const pipeline = new CodePipeline(this, 'Pipeline', {
      pipelineName: 'MyPipeline',
      synth: new ShellStep('Synth', {
        input: CodePipelineSource.gitHub('OWNER/REPO', 'main'),
        commands: ['npm ci', 'npm run build', 'npx cdk synth']
      })
    });

    pipeline.addStage(new MyPipelineAppStage(this, "test", {
      env: { account: "111111111111", region: "eu-west-1" }
    }));
  }
}

Every application stage added by addStage() results in the addition of a corresponding pipeline stage, represented by a StageDeployment instance returned by the addStage() call. You can add pre-deployment or post-deployment actions to the stage by calling its addPre() or addPost() method.

// import { ManualApprovalStep } from 'aws-cdk-lib/pipelines';

const testingStage = pipeline.addStage(new MyPipelineAppStage(this, 'testing', {
  env: { account: '111111111111', region: 'eu-west-1' }
}));

    testingStage.addPost(new ManualApprovalStep('approval'));

For Python projects, remember to install the CDK CLI globally (as there is no package.json to automatically install it for you):

declare const source: pipelines.IFileSetProducer; // the repository source

const pipeline = new pipelines.CodePipeline(this, 'Pipeline', {
  synth: new pipelines.ShellStep('Synth', {
    input: source,
    commands: [
      'pip install -r requirements.txt',
      'npm install -g aws-cdk',
      'cdk synth',
    ],
  })
});

Code pipeline example for DMS stack

export class DMSPipelineStack extends cdk.Stack {

  constructor(scope: Construct, id: string, props: DMSPipelineStackProps) {

    super(scope, id, props);

    const context = scope.node.tryGetContext(props.stage);

    const dmsFullloadStage = new DMSPipelineStage(this, 'RebotDMSFullloadStage', props);

    const dmsPipeline = new CodePipeline(this, 'DMSPipeline', {

      pipelineName: 'DMSPipeline',

      synth: new ShellStep('Synth', {

        input: CodePipelineSource.connection('owner/repo', 'branch', {

          connectionArn: `${context.dms.githubConnectionArn}`,

        }),

        installCommands: [

          'npm install -g aws-cdk@latest',

          'cd ./lib/dms/lambdas',

          'npm install --save .',

          'cd ../../../',

        ],

        commands: [

          'npm ci',

          'npm run build',

          `cdk synth ${dmsFullloadStage.getStackName()}`

        ],

      }),

      dockerEnabledForSynth: true,

      selfMutation: false,

    });

    dmsPipeline.addStage(dmsFullloadStage);

  }

}

CDK application deployments

After you have defined the pipeline and the synth step, you can add one or more CDK Stages which will be deployed to their target environments. To do so, call pipeline.addStage() on the Stage object:

declare const pipeline: pipelines.CodePipeline;
// Do this as many times as necessary with any account and region
// Account and region may different from the pipeline's.
pipeline.addStage(new MyApplicationStage(this, 'Prod', {
  env: {
    account: '123456789012',
    region: 'eu-west-1',
  }
}));

Example not in your language?

CDK Pipelines will automatically discover all Stacks in the given Stage object, determine their dependency order, and add appropriate actions to the pipeline to publish the assets referenced in those stacks and deploy the stacks in the right order.

If the Stacks are targeted at an environment in a different AWS account or Region and that environment has been bootstrapped , CDK Pipelines will transparently make sure the IAM roles are set up correctly and any requisite replication Buckets are created.

Provisioning the pipeline

Important: be sure to git commit and git push before deploying the Pipeline stack using cdk deploy!

The reason is that the pipeline will start deploying and self-mutating right away based on the sources in the repository, so the sources it finds in there should be the ones you want it to find.

Run the following commands to get the pipeline going:

$ git commit -a
$ git push
$ cdk deploy PipelineStack

 

References

https://docs.aws.amazon.com/cdk/v2/guide/cdk_pipeline.html

https://docs.aws.amazon.com/cdk/api/v1/docs/pipelines-readme.html

https://aws.amazon.com/blogs/developer/cdk-pipelines-continuous-delivery-for-aws-cdk-applications/

https://github.com/aws-samples/cdk-pipelines-demo

https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.pipelines/README.html