開発日報

窓際エンジニアの開発備忘。日報は嘘です。

【連載】AWSCDK for TypeScriptによるAWS環境構築入門 第三回 ~ CodePiplineでLambdaを自動デプロイしてみる ~

はじめに

この連載ではAWSCDKを用いたaws環境構築の基本を学びます。

前回はawscdkを使用してECS環境を構築しました。
今回はcodepiplineをcdkで構築し、lambda関数の自動デプロイ環境を構築しましょう。

完成品のソースコードこちら

連載記事一覧

連載記事一覧

初期処理

開発用のディレクトリを作成し、ディレクトリ内でcdkの雛形を作成しましょう。

$ mkdir cdk-pipline
$ cd cdk-pipline
$ cdk init --language=typescript

次に、必要な依存ライブラリをインストールしましょう。

$ npm install @aws-cdk/aws-codebuild @aws-cdk/aws-codecommit @aws-cdk/aws-codepipeline @aws-cdk/aws-codepipeline-actions @aws-cdk/aws-s3 @aws-cdk/aws-lambda @aws-cdk/aws-codedeploy

色々めんどくさくなるので、今回はテストは考えず、「test/cdk-pipline.test.ts」は削除しておきましょう。

Lambda Stack

Lambda Stackを作成し、lambda関数と実行環境の定義を行いましょう。

実行されるlambda関数の定義

まず、「cdk-pipline」ディレクトリ直下に「lambda」ディレクトリを作成し、そこにindex.jsを以下の内容で作成します。

// file: lambda/index.js

exports.handler = async (event) => {
  // TODO implement
  const response = {
      statusCode: 200,
      body: JSON.stringify('Hello from Lambda!!'),
  };
  return response;
};

また.gitignoreに以下を追記して、index.jsがgitの管理対象に含まれる様にしましょう。

!*index.js

Lambda Stackの作成

libディレクトリ配下にlambda-stack.tsを作成しLambdaStackの定義をしましょう。

// file: lib/lambda-stack.ts
import codedeploy = require('@aws-cdk/aws-codedeploy');
import lambda = require('@aws-cdk/aws-lambda');
import { App, Stack, StackProps } from '@aws-cdk/core';

export class LambdaStack extends Stack {
  public readonly lambdaCode: lambda.CfnParametersCode;

  constructor(app: App, id: string, props?: StackProps) {
    super(app, id, props);

    this.lambdaCode = lambda.Code.cfnParameters();

    const func = new lambda.Function(this, 'Lambda', {
      code: this.lambdaCode,
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_8_10,
    });

    const version = func.addVersion(new Date().toISOString());
    const alias = new lambda.Alias(this, 'LambdaAlias', {
      aliasName: 'Prod',
      version,
    });

    new codedeploy.LambdaDeploymentGroup(this, 'DeploymentGroup', {
      alias,
      deploymentConfig:codedeploy.LambdaDeploymentConfig.LINEAR_10PERCENT_EVERY_1MINUTE,
    });
  }
}

Pipeline Stack

Pipeline Stackを作成し、ソースコードをgitにpushした際に自動でビルド・デプロイが走る仕組みを作りましょう。

Pipeline Stackの作成

libディレクトリ配下のcdk-pipline.tsファイルを以下の様に編集しましょう。

// lib/cdk-pipeline-stack.ts

import codebuild = require('@aws-cdk/aws-codebuild');
import codecommit = require('@aws-cdk/aws-codecommit');
import codepipeline = require('@aws-cdk/aws-codepipeline');
import codepipeline_actions = require('@aws-cdk/aws-codepipeline-actions');
import lambda = require('@aws-cdk/aws-lambda');
import s3 = require('@aws-cdk/aws-s3');
import { App, Stack, StackProps } from '@aws-cdk/core';

export interface PipelineStackProps extends StackProps {
  readonly lambdaCode: lambda.CfnParametersCode;
}

export class PipelineStack extends Stack {
  constructor(app: App, id: string, props: PipelineStackProps) {
    super(app, id, props);

    const repo = codecommit.Repository.fromRepositoryName(this, 'ImportedRepo',
      'awscdk-pipline');

    const cdkBuild = new codebuild.PipelineProject(this, 'CdkBuild', {
      buildSpec: codebuild.BuildSpec.fromObject({
        version: '0.2',
        phases: {
          install: {
            commands: 'npm install',
          },
          build: {
            commands: [
              'npm run build',
              'npm run cdk synth -- -o dist'
            ],
          },
        },
        artifacts: {
          'base-directory': 'dist',
          files: [
            'LambdaStack.template.json',
          ],
        },
      }),
      environment: {
        buildImage: codebuild.LinuxBuildImage.UBUNTU_14_04_NODEJS_8_11_0,
      },
    });
    const lambdaBuild = new codebuild.PipelineProject(this, 'LambdaBuild', {
      buildSpec: codebuild.BuildSpec.fromObject({
        version: '0.2',
        phases: {
          install: {
            commands: [
              'cd lambda',
              'npm install',
            ],
          },
          build: {
            commands: 'npm run build',
          },
        },
        artifacts: {
          'base-directory': 'lambda',
          files: [
            'index.js',
            'node_modules/**/*',
          ],
        },
      }),
      environment: {
        buildImage: codebuild.LinuxBuildImage.UBUNTU_14_04_NODEJS_8_11_0,
      },
    });

    const sourceOutput = new codepipeline.Artifact();
    const cdkBuildOutput = new codepipeline.Artifact('CdkBuildOutput');
    const lambdaBuildOutput = new codepipeline.Artifact('LambdaBuildOutput');
    new codepipeline.Pipeline(this, 'Pipeline', {
      stages: [
        {
          stageName: 'Source',
          actions: [
            new codepipeline_actions.CodeCommitSourceAction({
              actionName: 'CodeCommit_Source',
              repository: repo,
              output: sourceOutput,
            }),
          ],
        },
        {
          stageName: 'Build',
          actions: [
            new codepipeline_actions.CodeBuildAction({
              actionName: 'Lambda_Build',
              project: lambdaBuild,
              input: sourceOutput,
              outputs: [lambdaBuildOutput],
            }),
            new codepipeline_actions.CodeBuildAction({
              actionName: 'CDK_Build',
              project: cdkBuild,
              input: sourceOutput,
              outputs: [cdkBuildOutput],
            }),
          ],
        },
        {
          stageName: 'Deploy',
          actions: [
            new codepipeline_actions.CloudFormationCreateUpdateStackAction({
              actionName: 'Lambda_CFN_Deploy',
              templatePath: cdkBuildOutput.atPath('LambdaStack.template.json'),
              stackName: 'LambdaDeploymentStack',
              adminPermissions: true,
              parameterOverrides: {
                ...props.lambdaCode.assign(lambdaBuildOutput.s3Location),
              },
              extraInputs: [lambdaBuildOutput],
            }),
          ],
        },
      ],
    });
  }
}

一点気をつけて頂きたいのは

const repo = codecommit.Repository.fromRepositoryName(this, 'ImportedRepo','cdk-pipline');

の「cdk-pipline」の部分がソースを取得するリポジトリ名となっていることにご注意ください。

後述しますが、今回はaws code commitに「cdk-pipline」という名前でリポジトリを作成し、こちらからソースを取得・変更の検知を行います。

メインプログラム

最後に、AWSCDKのエントリーポイントとなるMainプログラムを作成しましょう。
binディレクトリ配下のcdk-pipline.tsを以下の様に編集します。

// bin/cdk-pipline.ts
#!/usr/bin/env node

import { App } from '@aws-cdk/core';
import { LambdaStack } from '../lib/lambda-stack';
import { PipelineStack } from '../lib/cdk-pipline-stack';

const app = new App();

const lambdaStack = new LambdaStack(app, 'LambdaStack');
new PipelineStack(app, 'PipelineDeployingLambdaStack', {
  lambdaCode: lambdaStack.lambdaCode,
});

app.synth();

ここまでで一通りの実装は完了しました。
最後に以下のコマンドでビルドを行いましょう。

$ npm run build

ビルドが完了しましたら、ローカルリポジトリにコミットしておきましょう。

$ git add . 
$ git commit -m "first commit"

CodeCommitへのソースコードアップ

AWS CodeCommitに「cdk-pipline」という名称でリポジトリを作成し、 これまでに作成したソースコードをPushしてください。

f:id:yuuu1993g:20191022230058p:plain

$ git remote add origin https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/cdk-pipline

$ git push --set-upstream origin master

f:id:yuuu1993g:20191022230207p:plain

AWS環境へ反映

では今までに作成したlambdaとcodepiplineを実際にAWSに反映させてみましょう。

$ cdk deploy PipelineDeployingLambdaStack

作成に成功しましたら、lambda関数が作成されていることや、適当にREADME.mdあたりを変更してcommit→pushした際に自動でデプロイが走っていることをcodepiplineのawsコンソールから確認してみましょう。

削除は以下の感じで。

$ cdk destroy PipelineDeployingLambdaStack

まとめ

今回はcodepiplineをcdkで構築し、lambda関数の自動デプロイ環境を構築しました。
次回は未定。