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

2019.06.10

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

タイトル通りですが、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 設定プロパティのリファレンス