LambdaのCDをCodePipelineとCloudFormationで構築してみる

おはようございます、もきゅりんです。

タイトル通りですが、CodePipelineを利用したLambdaのCD(Continuous Deployment)をCloudFormation(以下CFn)で構築してみたのでまとめました。

Lambdaを手元でわちゃわちゃやった後に、デプロイしたいときはリポジトリにプッシュすればよろしおす、便利よね、ということでCFnにしてみました。

色々なやり方があるとは思いますが、元ネタはこちらです。

AWS CodePipeline を使用して Lambda アプリケーションの継続的な配信パイプラインを構築する

この例では、CodeCommitのGitリポジトリを利用します。

前提条件

  • CodeCommitが利用できる状態であること
  • AWS CLIインストール&設定済み

CodeCommitリポジトリの作成については、『CodeCommit ユーザーガイド』の「セットアップ」を参照してください。

ファイルをコミットしてCodeCommitにプッシュ

プッシュするファイルを作成していきます。

現在の時刻を返すLambda関数。

# lambda_function.py
from datetime import datetime, timedelta, timezone

def lambda_handler(event, context):

    JST = timezone(timedelta(hours=+9), 'JST')
    time = datetime.now(JST)
    print("What time is it now...?")
    print(time)

アプリケーションを定義するSAMテンプレート。

SAMとは何ぞや?という話はここではしませんので、気になる方は、下記が参考ドキュメントです。

AWSサーバーレスアプリケーションモデル(AWS SAM)とは何ですか?

# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Outputs the time
Resources:
  TimeFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: lambda_function.lambda_handler
      Runtime: python3.7
      CodeUri: ./

上記のファイルをコミットし、CodeCommitにプッシュします。

$ git add .
$ git commit -m "add project files"
$ git push

CodePipelineの作成

下記のテンプレートで作成します。

BuildSpecの内容については、実際に利用する場合は、適宜修正を施す必要がありそうです。

なお、IAMRoleですが結構雑に設定しているので、ちゃんと利用する場合は、最小権限の原則?(そんな称し方でいいのか??)に準拠しましょう。

# lambda_codepipeline_demo.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: CodePipeline For Lambda Deploy

Parameters:
  CodeCommitRepositoryName:
    Type: String
  PipelineName:
    Type: String
  BucketName:
    Type: String


Resources:
  # CodeWatchEventを実行できるIAMRole
  AmazonCloudWatchEventRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: cwe-pipeline-execution
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: codepipeline:StartPipelineExecution
                Resource: !Join
                  - ""
                  - - "arn:aws:codepipeline:"
                    - !Ref "AWS::Region"
                    - ":"
                    - !Ref "AWS::AccountId"
                    - ":"
                    - !Ref "PipelineName"

  # CloudFormationに適用するIAMRole
  CFnLambdaPipeline:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AdministratorAccess

  # CodeBuildに適用するIAMRole
  CodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonS3FullAccess
      Policies:
        - PolicyName: CodeBuildAccess
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Resource: "*"
                Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents

  # CodePipelineに適用するIAMRole
  CodePipelineServiceRole:
    Type: AWS::IAM::Role
    Properties:
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: SamplePipelinePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Resource:
                  - !Sub arn:aws:s3:::${ArtifactBucket}/*
                Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetObjectVersion
                  - s3:GetBucketVersioning
              - Resource: "*"
                Effect: Allow
                Action:
                  - cloudformation:*
                  - codecommit:*
                  - codedeploy:*
                  - codebuild:*
                  - s3:*
                  - ecs:*
                  - elasticloadbalancing:*
                  - autoscaling:*
                  - iam:PassRole

  # S3Bucket
  ArtifactBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref BucketName

  # CloudWatchEventの実行ルール
  AmazonCloudWatchEventRule:
    Type: AWS::Events::Rule
    Properties:
      EventPattern:
        source:
          - aws.codecommit
        detail-type:
          - "CodeCommit Repository State Change"
        resources:
          - !Join
            - ""
            - - "arn:aws:codecommit:"
              - !Ref "AWS::Region"
              - ":"
              - !Ref "AWS::AccountId"
              - ":"
              - !Ref "CodeCommitRepositoryName"
        detail:
          event:
            - referenceCreated
            - referenceUpdated
          referenceType:
            - branch
          referenceName:
            - master
      Targets:
        - Arn: !Join
            - ""
            - - "arn:aws:codepipeline:"
              - !Ref "AWS::Region"
              - ":"
              - !Ref "AWS::AccountId"
              - ":"
              - !Ref "PipelineName"

          RoleArn: !GetAtt AmazonCloudWatchEventRole.Arn
          Id: codepipeline-AppPipeline

  # CodeBuildProject
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Source:
        Type: CODEPIPELINE
        BuildSpec: |
          version: 0.2
          phases:
            install:
              runtime-versions:
                python: 3.7
              commands:
                - aws cloudformation package --template-file template.yaml --s3-bucket $BUCKET_NAME --output-template-file outputtemplate.yaml
          artifacts:
            type: zip
            files:
              - template.yaml
              - outputtemplate.yaml
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/docker:18.09.0-1.7.0
        Type: LINUX_CONTAINER
        EnvironmentVariables:
          - Name: BUCKET_NAME
            Value: !Ref BucketName
      Name: !Ref AWS::StackName
      ServiceRole: !Ref CodeBuildServiceRole

  # CodePipeLine
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      RoleArn: !GetAtt CodePipelineServiceRole.Arn
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      Stages:
        - Name: Source
          Actions:
            - Name: Source
              ActionTypeId:
                Category: Source
                Owner: AWS
                Version: 1
                Provider: CodeCommit
              Configuration:
                RepositoryName: !Ref CodeCommitRepositoryName
                PollForSourceChanges: false
                BranchName: master
              RunOrder: 1
              OutputArtifacts:
                - Name: SourceArtifact
        - Name: Build
          Actions:
            - Name: Build
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: 1
                Provider: CodeBuild
              Configuration:
                ProjectName: !Ref CodeBuildProject
              RunOrder: 1
              InputArtifacts:
                - Name: SourceArtifact
              OutputArtifacts:
                - Name: BuildArtifact
        - Name: Deploy
          Actions:
            - InputArtifacts:
                - Name: BuildArtifact
              Name: deploy
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Version: 1
                Provider: CloudFormation
              RunOrder: 2
              Configuration:
                ActionMode: CHANGE_SET_REPLACE
                ChangeSetName: changeset
                RoleArn: !GetAtt CFnLambdaPipeline.Arn
                Capabilities: CAPABILITY_IAM
                StackName: lambda-pipeline-changeset
                TemplatePath: BuildArtifact::outputtemplate.yaml
            - InputArtifacts:
                - Name: BuildArtifact
              Name: execute-changeset
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Version: 1
                Provider: CloudFormation
              RunOrder: 3
              Configuration:
                ActionMode: CHANGE_SET_EXECUTE
                StackName: lambda-pipeline-changeset
                ChangeSetName: changeset
                RoleArn: !GetAtt CFnLambdaPipeline.Arn

スタックを作成します。

BUCKETNAMEは好きなバケット名で入力して下さい。

どうでもいいのですが、最近はdeployでスタック作成しています。

aws cloudformation deploy --template-file lambda_codepipeline_demo.yaml --capabilities CAPABILITY_NAMED_IAM \
--stack-name lambda-deploy --parameter-overrides \
CodeCommitRepositoryName=YOUR_REPOSITORY_NAME PipelineName=lambda-deploy-pipeline BucketName=BUCKETNAME

CodePipelineを見に行くと、こんな感じで進行しました。

pipeline1

pipeline2

lambdaのコンソールにいくとこんなのがデプロイされます。

lambda1

叩くと時間が表示されます。

lambda2

その後、マスターブランチに変更をプッシュして、デプロイをトリガーするとクリクリとパイプラインが発動します。

結果のステータスをSlackとかに投げてあげたりすると、また親切ですねー。

以上です、どなたかのお役に立てば幸いです。

参考

AWS CodePipeline を使用して Lambda アプリケーションの継続的な配信パイプラインを構築する

AWS Serverless Application Model (SAM)

Updating My AWS CodeBuild Project from Ubuntu 14.04 to 18.04

CodePipeline パイプライン構造のリファレンス

AWS CloudFormation 設定プロパティのリファレンス