CodePipelineを利用したLambdaの自動デプロイ構築ハンズオン


この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので十分ご注意ください。

はじめに

Lambdaを毎度手動デプロイも慣れてきましたが、オペミス等の観点からデプロイを自動化するCICDパイプラインを構築してみたいと思います。
構成は、CodePipelineを利用して、SourceはCodeCommit、BuildはCodeBuild、DeployはCloudFormationを利用します。
コミットによる変更後、CodePipeline変更をリリースするを押下することで、Lambdaが自動的にデプロイされます。


構成図


ハンズオン

構築のながれ

1.S3作成:CodeCommitリポジトリに反映させるZipファイルを保存

2.CodeCommit作成:CFnを利用してCodeCommit構築

3.CodePipeline作成:CFnを利用してCodePipeline構築


1.S3作成:CodeCommitリポジトリに反映させるZipファイルを保存用

S3を構築するためのテンプレートは以前のブログ CloudFormationでCodeCommit 作成時にリポジトリを反映する構築ハンズオンを参照ください。

ここではS3を作成した後、S3に配置するオブジェクトの説明をします。
保存するのはpipeline_settingsをZip化したcicd.zipをS3に配置します。

【Zip化するファイル概要説明】

pipeline_settings
|_①buildspec.yml            # CodeBuildでビルド実行するコマンドが記述されている
|_②index.py                    # Lambdaで実行されるコードを記述している
|_③param.json                # ビルド実行で構築されるLambdaのパラメータを記述している
|_④template_deploy.yml # ビルド実行で構築されるLambdaがyamlで記述されている

①buildspec.yml について

CodeBuildでビルドを実行する際のコマンドが記述されています。
aws aws cloudformation packageを利用することで、指定した場所にあるpipeline_settings/template_deploy.ymlや、S3バケット$S3_BUCKETをとりまとめて、テンプレートファイルを出力してくれます。
環境変数の$S3_BUCKETは、CodePipelineをCFn構築する際に設定をしていきます。

version: 0.2

phases:
  build:
    commands:
      - |
        aws cloudformation package \
          --template-file pipeline_settings/template_deploy.yml \
          --s3-bucket $S3_BUCKET \
          --output-template-file $PACKAGED_TEMPLATE_FILE_PATH

artifacts:
  files:
    - $PACKAGED_TEMPLATE_FILE_PATH
    - pipeline_settings/*
  discard-paths: yes

②index.pyについて

CodeBuildして構築されたLambdaで利用するコードを記載しています。
今回は最終的にCodeCommitの画面でindex.pyを編集 → CodePipelineの変更をリリースする押下 → 編集が反映される で一連の流れを確認していきたいと思います。

※CodeCommitのGUIに直接アクセスする方法以外は下記ブログを参照ください
CodeCommit で利用できる3種類のアクセス方法ハンズオン

import json

def lambda_handler(event, context):
    # TODO implement
    print(event)
    return {
        'statusCode': 200,
        'body': json.dumps('Hello Lambda!')
    }

③param.jsonについて

ビルド実行後、CFnでデプロイの際に利用するLambdaのパラメータ値を記述しています。
Lambdaに変更(コード以外の部分)が生じた場合、こちらのjsonを修正するだけで④template_deploy.ymlは手を加えずに済みます。

{
  "Parameters": {
    "FunctionName": "cfn-lmd-inamura",
    "Description": "cfn-lmd-inamura",
    "Handler": "index.lambda_handler",
    "MemorySize": "128",
    "Runtime": "python3.9",
    "Timeout": "10",
    "TagsName": "inamura"
  }
}

④template_deploy.yml

ビルド実行後、CFnでデプロイの際に利用するテンプレートが記述されています。

AWSTemplateFormatVersion: '2010-09-09'
Description:
  Lambda Create

# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  FunctionName:
    Type: String
  Description:
    Type: String
  Handler:
    Type: String
  MemorySize:
    Type: String
  Runtime:
    Type: String
  Timeout:
    Type: String
  TagsName:
    Type: String

# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  Lambda
# ------------------------------------------------------------#
  Lambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      Description: !Ref Description
      FunctionName: !Ref FunctionName
      Handler: !Ref Handler
      MemorySize: !Ref MemorySize
      Runtime: !Ref Runtime
      Timeout: !Ref Timeout
      Role: !GetAtt LambdaRole.Arn
      Tags: 
        - Key: "User"
          Value: !Ref TagsName

  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${FunctionName}-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action: "sts:AssumeRole"
            Principal:
              Service: lambda.amazonaws.com
      Policies:
        - PolicyName: !Sub "${FunctionName}-policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                  - "logs:CreateLogGroup"
                Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*"

# ------------------------------------------------------------#
# Output Parameters
#------------------------------------------------------------#          
Outputs:
  LambdaArn:
    Value: !GetAtt Lambda.Arn
    Export:
      Name: !Sub "${FunctionName}-arn"
  LambdaName:
    Value: !Ref FunctionName
    Export:
      Name: !Sub "${FunctionName}-name"

2.CodeCommit作成:CFnを利用してCodeCommit構築

手順1で用意したZipファイルをS3に保存後、CFnを利用してCodeCommitを構築します。

CodeCommitを構築するためのテンプレートは以前のブログ CloudFormationでCodeCommit 作成時にリポジトリを反映する構築ハンズオンを参照ください。

構築後CodeCommitの画面に遷移して下記リポジトリが作成されているかを確認する

リポジトリを押下後、配下の資材も構築されていることが確認できます。

3.CodePipeline作成:CFnを利用してCodePipeline構築

3.1.CodePipelineで構築するリソース群

リソース名 説明
CodePipeline CodePipeline構築(CodeCommit → CodeBuild → CFn(デプロイ)
CodeBuil CodeBuild構築
IAM Role CodePipeline用ロール
IAM Role CodeBuild用ロール
IAM Role CFn(デプロイ)用ロール

3.2.CodePipelineの流れ

Sourceフェイズ:手順2で構築したCodeCommitのリポジトリ情報をCodeBuildに渡す

Buildフェイズ①buildspec.ymlの内容から④template_deploy.yml + ③param.jsonをとりまとめ、S3(CodePipelineテンプレートのArtifactStoreBucketで指定)に CFnテンプレート(packaged.yml)を出力する

Deployフェイズ:出力されたCFnテンプレートから、新規CFnリソースを構築する

【再掲 CodeCommit リポジトリに含まれているファイル群】

pipeline_settings
|_①buildspec.yml            # CodeBuildでビルド実行するコマンドが記述されている
|_②index.py                    # Lambdaで実行されるコードを記述している
|_③param.json                # ビルド実行で構築されるLambdaのパラメータを記述している
|_④template_deploy.yml # ビルド実行で構築されるLambdaがyamlで記述されている

3.3.CodePipeline CFn テンプレート

AWSTemplateFormatVersion: '2010-09-09'
Description:
  CodeCommit Create
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Lambda Configuration"
        Parameters:
        - PipelineName
        - BuildName
        - PackagedTemplateFile
        - BuildSpec
        - ArtifactStoreBucket
        - ModuleStackName
        - DeployParamFile
# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  PipelineName:
    Type: String
    Default: cfn-codepipeline-inamura
  BuildName:
    Type: String
    Default: cfn-codebuild-inamura
  PackagedTemplateFile:
    Type: String
    Default: packaged.yml
  ArtifactStoreBucket:
    Type: String
    Default: cfn-s3-20230103-inamura
  BuildSpec:
    Type: String
    Default: pipeline_settings/buildspec.yml
  ModuleStackName:
    Type: String
    Default: cfn-codepipeline-lambda-inamura
  DeployParamFile:
    Type: String
    Default: param.json

# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  CodeBuild
# ------------------------------------------------------------#
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: CODEPIPELINE
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:4.0
        EnvironmentVariables:
          - Name: PACKAGED_TEMPLATE_FILE_PATH
            Value: !Ref PackagedTemplateFile
          - Name: S3_BUCKET
            Value: !Ref ArtifactStoreBucket
      Name: !Ref BuildName
      ServiceRole: !GetAtt CodeBuildRole.Arn
      Source:
        Type: CODEPIPELINE
        BuildSpec: !Ref BuildSpec
# ------------------------------------------------------------#
#  CodeBuild Role
# ------------------------------------------------------------#
  CodeBuildRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
      Path: /
      Policies:
        - PolicyName: CodeBuildAccess
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Sid: CloudWatchLogsAccess
                Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource:
                  - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/*

              - Sid: S3Access
                Effect: Allow
                Action:
                  - s3:PutObject
                  - s3:GetObject
                  - s3:GetObjectVersion
                Resource:
                  - !Sub arn:aws:s3:::${ArtifactStoreBucket}
                  - !Sub arn:aws:s3:::${ArtifactStoreBucket}/*

              - Sid: CloudFormationAccess
                Effect: Allow
                Action: cloudformation:ValidateTemplate
                Resource: "*"

# ------------------------------------------------------------#
#  CodePipeline
# ------------------------------------------------------------#
  CodePipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Name: !Ref PipelineName
      RoleArn: !GetAtt CodePipelineRole.Arn
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactStoreBucket
      Stages:
        - Name: Source
          Actions:
            - Name: Source
              ActionTypeId:
                Category: Source
                Owner: AWS
                Version: 1
                Provider: CodeCommit
              Configuration:
                RepositoryName: !ImportValue cfn-codecommit-inamura-name
                BranchName: main
                PollForSourceChanges: false
              OutputArtifacts:
                - Name: SourceOutput

        - Name: Build
          Actions:
            - InputArtifacts:
                - Name: SourceOutput
              Name: Package
              ActionTypeId:
                Category: Build
                Provider: CodeBuild
                Owner: AWS
                Version: 1
              OutputArtifacts:
                - Name: BuildOutput
              Configuration:
                ProjectName: !Ref BuildName
      # ------------------------------------------------------------#
      #  CFnDeploy
      # ------------------------------------------------------------#
        - Name: Deploy
          Actions:
            - Name: CreateChangeSet
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: '1'
              InputArtifacts:
                - Name: BuildOutput
              Configuration:
                ActionMode: CHANGE_SET_REPLACE
                RoleArn: !GetAtt PipelineDeployRole.Arn
                StackName: !Ref ModuleStackName
                ChangeSetName: !Sub ${ModuleStackName}-changeset
                Capabilities: CAPABILITY_NAMED_IAM
                TemplatePath: !Sub BuildOutput::${PackagedTemplateFile}
                TemplateConfiguration: !Sub BuildOutput::${DeployParamFile}
              RunOrder: '1'

            - Name: ExecuteChangeSet
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: '1'
              InputArtifacts:
                - Name: BuildOutput
              Configuration:
                ActionMode: CHANGE_SET_EXECUTE
                ChangeSetName: !Sub ${ModuleStackName}-changeset
                StackName: !Ref ModuleStackName
              RunOrder: '2'
# ------------------------------------------------------------#
#  CodePipeline Role
# ------------------------------------------------------------#
  CodePipelineRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
      Path: /
      Policies:
        - PolicyName: CodePipelineAccess
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Sid: S3GetObject
                Effect: Allow
                Action: s3:*
                Resource:
                  - !Sub arn:aws:s3:::${ArtifactStoreBucket}
                  - !Sub arn:aws:s3:::${ArtifactStoreBucket}/*
              - Sid: S3PutObject
                Effect: Allow
                Action: s3:*
                Resource:
                  - !Sub arn:aws:s3:::${ArtifactStoreBucket}
                  - !Sub arn:aws:s3:::${ArtifactStoreBucket}/*
              - Sid: CodeCommit
                Effect: Allow
                Action: codecommit:*
                Resource: !ImportValue cfn-codecommit-inamura-arn
              - Sid: CodeBuildStartBuild
                Effect: Allow
                Action:
                  - codebuild:StartBuild
                  - codebuild:BatchGetBuilds
                Resource: !Sub arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/${BuildName}
              - Sid: CFnActions
                Effect: Allow
                Action:
                  - cloudformation:DescribeStacks
                  - cloudformation:DescribeChangeSet
                  - cloudformation:CreateChangeSet
                  - cloudformation:ExecuteChangeSet
                  - cloudformation:DeleteChangeSet
                Resource:
                  - !Sub arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${ModuleStackName}/*
              - Sid: PassRole
                Effect: Allow
                Action:
                  - iam:PassRole
                Resource: !GetAtt PipelineDeployRole.Arn

# ------------------------------------------------------------#
#  CFnDeploy Role
# ------------------------------------------------------------#
  PipelineDeployRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service: cloudformation.amazonaws.com
      Path: /
      Policies:
        - PolicyName: !Sub ${PipelineName}DeployPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - lambda:*
                Resource: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:*
              - Effect: Allow
                Action:
                  - iam:CreateRole
                  - iam:DeleteRole
                  - iam:GetRole
                  - iam:PassRole
                  - iam:DeleteRolePolicy
                  - iam:PutRolePolicy
                  - iam:GetRolePolicy
                Resource: !Sub arn:aws:iam::${AWS::AccountId}:role/*
              - Effect: Allow
                Action: s3:GetObject
                Resource:
                  - !Sub arn:aws:s3:::${ArtifactStoreBucket}
                  - !Sub arn:aws:s3:::${ArtifactStoreBucket}/*

挙動の確認

①CodePipeline の Source / Build / Deploy が成功している

※失敗する場合は詳細を押下することで、エラー解決への糸口にしましょう

②CFn を 確認して CodePipeline 経由での自動デプロイがされていることを確認する

手順2で実施した cfn-codepipeline-inamura と、手順3で実施した cfn-codepipeline-inamura の他に赤枠で囲った部分が、CodePipelineによって自動デプロイされた部分です。

③Lambda を確認する

index.py を選択することで Lambdaを実行可能なことを確認する

④CodeCommit で index.pyを修正して、再度パイプラインを実行する

④-1 リポジトリへ移動する

CodeCommit > 左ペイン:ソース から リポジトリを選択 > 作成したリポジトリを選択(今回は cfn-codecommit-inamura)

④-2 index.pyを編集画面へ移動する

リポジトリ選択後 > pipeline_settingsを押下 > index.pyを押下 > 編集を押下

④-3 index.pyを編集し変更のコミットする

8行目:Hello Lambda!からHello CodePipeline!に修正
作成者名・メールアドレスを入力して変更のコミットを押下する

⑤CodePipeline の変更をリリースするを実行する

CodePipeline > 左ペイン:パイプライン > 作成したパイプラインを選択(今回は cfn-codepipeline-inamura) > 変更をリリースするを押下

CodePipelineを押下することで、挙動①スクショのように再度、SourceBuildDeployとパイプラインが動いていることが確認できる。

⑥Lambda を確認する

CodePipelineが全て成功したことを確認後、Lambdaを確認してコードが変更されていることを確認する(赤枠部分)


さいごに

CodeCommit でソースコードを集約して CodeBuild でビルドしてアーティファクトにするイメージが掴めていなかったため、検索しても「何が何を構成するのに必要なのか?」が分からず、無意な時間を過ごしました。
構築して動作した後に、どこのパラメータがどこに反映しているかなど逆輸入的な理解の穴埋めなどが多々あり、時間をかけた意味はあったのかなと個人的には思います。
なにが分かるわけではないので、分からないものに蓋をするのではなく検証しながら今年も学んでいこうと思います。


参考URL

CloudFormationでLambdaの自動デプロイ環境を構築する
AWS Code シリーズ

Last modified: 2023-01-09

Author