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

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

------------

## 構成図
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-09-15.33.33-640x239.png)

------------

## ハンズオン
### 構築のながれ
#### 1.S3作成：CodeCommitリポジトリに反映させるZipファイルを保存
#### 2.CodeCommit作成：CFnを利用してCodeCommit構築
#### 3.CodePipeline作成:CFnを利用してCodePipeline構築

-----------

### 1.S3作成：CodeCommitリポジトリに反映させるZipファイルを保存用
S3を構築するためのテンプレートは以前のブログ　[CloudFormationでCodeCommit 作成時にリポジトリを反映する構築ハンズオン](https://cloud5.jp/cfn-by-codecommit-creating-repository/)を参照ください。
<br>
ここでは`S3`を作成した後、`S3`に配置するオブジェクトの説明をします。
保存するのは`pipeline_settings`をZip化した`cicd.zip`をS3に配置します。

![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-09-11.23.38-640x191.png)

【Zip化するファイル概要説明】
```yaml
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構築する際に設定をしていきます。

```yaml
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種類のアクセス方法ハンズオン](https://cloud5.jp/3type-acces-method-in-codecommit/)

```python
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は手を加えずに済みます。

```json
{
  "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でデプロイの際に利用するテンプレートが記述されています。

```yaml
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"
```
<br>

### 2.CodeCommit作成：CFnを利用してCodeCommit構築
手順1で用意したZipファイルを`S3`に保存後、CFnを利用してCodeCommitを構築します。
<br>
CodeCommitを構築するためのテンプレートは以前のブログ　[CloudFormationでCodeCommit 作成時にリポジトリを反映する構築ハンズオン](https://cloud5.jp/cfn-by-codecommit-creating-repository/)を参照ください。
<br>
構築後`CodeCommit`の画面に遷移して下記リポジトリが作成されているかを確認する
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-07-21.47.41-640x165.png)
リポジトリを押下後、配下の資材も構築されていることが確認できます。
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-09-14.01.19.png)
<br>
### 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リソースを構築する
<br />
【再掲　CodeCommit リポジトリに含まれているファイル群】
```yaml
pipeline_settings
|_①buildspec.yml            # CodeBuildでビルド実行するコマンドが記述されている
|_②index.py                    # Lambdaで実行されるコードを記述している
|_③param.json                # ビルド実行で構築されるLambdaのパラメータを記述している
|_④template_deploy.yml # ビルド実行で構築されるLambdaがyamlで記述されている
```
#### 3.3.CodePipeline CFn テンプレート
```yaml
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}/*

```
<br />

### 挙動の確認
#### ①CodePipeline の Source / Build / Deploy が成功している
※失敗する場合は`詳細`を押下することで、エラー解決への糸口にしましょう
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-09-14.19.47-237x640.png)
<br />
#### ②CFn を　確認して CodePipeline 経由での自動デプロイがされていることを確認する
手順2で実施した `cfn-codepipeline-inamura` と、手順3で実施した　`cfn-codepipeline-inamura` の他に赤枠で囲った部分が、CodePipelineによって自動デプロイされた部分です。
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-09-14.22.37.png)
<br />
#### ③Lambda を確認する
`index.py` を選択することで `Lambda`を実行可能なことを確認する
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-09-14.29.30.png)
<br />
#### ④CodeCommit で `index.py`を修正して、再度パイプラインを実行する
##### ④-1　リポジトリへ移動する
`CodeCommit` > 左ペイン：`ソース` から　`リポジトリ`を選択 > 作成したリポジトリを選択(今回は `cfn-codecommit-inamura`)
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-09-14.34.35-640x101.png)
<br />
##### ④-2　`index.py`を編集画面へ移動する
リポジトリ選択後 > `pipeline_settings`を押下 > `index.py`を押下 > `編集`を押下
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-09-14.39.18-640x137.png)
<br />
##### ④-3　`index.py`を編集し`変更のコミット`する
8行目：`Hello Lambda!`から`Hello CodePipeline!`に修正
作成者名・メールアドレスを入力して`変更のコミット`を押下する
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-09-14.42.59-640x439.png)
<br />
#### ⑤CodePipeline の`変更をリリースする`を実行する
`CodePipeline` > 左ペイン：`パイプライン` > 作成したパイプラインを選択(今回は `cfn-codepipeline-inamura`) > `変更をリリースする`を押下 
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-09-14.47.10-640x142.png)
CodePipelineを押下することで、挙動①スクショのように再度、`Source`→`Build`→`Deploy`とパイプラインが動いていることが確認できる。
<br />
#### ⑥Lambda を確認する
CodePipelineが全て成功したことを確認後、Lambdaを確認してコードが変更されていることを確認する（赤枠部分）
![](https://cloud5.jp/wp-content/uploads/2023/01/スクリーンショット-2023-01-09-14.54.52.png)
<br />

------------

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

------------

## 参考URL
[CloudFormationでLambdaの自動デプロイ環境を構築する](https://qiita.com/ytaka95/items/5899c44c85e71fdc5273#param_devjson-param_prodjson)
[AWS Code シリーズ](https://qiita.com/leomaro7/items/41cbe8aa7c32298ec665)