Lambda関数をBlue/GreenデプロイメントするCodePipelineをCloudFormationで自動構築する

# 前提条件
以下の記事で手動構築していたCodePipelineのCI/CDパイプラインをCloudFormationで自動構築してみる記事。なので、構成やアプリケーションの内容は読んで把握しておくことが望ましい。

[Amazon API Gateway/ALBのバックエンドで動くLambda関数をJava(Eclipse＋maven)で実装する](https://qiita.com/neruneruo/items/8673af2a3ae0f8076627)
[ALBのバックエンドで動作するJava実装のLambda関数をBlue/GreenデプロイメントするCodePipelineを作る](https://qiita.com/neruneruo/items/73906cdc3c2aa699644f)

最終的に目指すCI/CDパイプラインの構成イメージは以下。Lambda関数のフロントにALBがいる想定だが、このパイプラインでは触らない(厳密には、リスナーとターゲットグループは触る)ので割愛する。
![LambdaCFn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/48e2cec8-5425-7778-3801-04cab8b48578.png)


記事中には、過去のCloudFormation関連の記事同様、プロパティに関する補足をしている部分がある。
備忘のためにリファレンスに書かれていないデフォルト値も整理しておくが、2020年4月時点の情報であり、後でAWSが仕様を変えたとしても追従する予定はないので、挙動が違ったら[ユーザーガイドのリファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-reference.html)を見直してほしい。あと、今回の構成以外の構成以外のデフォルト値まで調査はしていないのであしからず。

# CodePipelineのデプロイステージ
過去のCloudFormationの記事と同様に作っていくことになるが、今回の主な違いはデプロイステージである。アクションプロバイダ(ActionTypeIdのProvider)が今回は```CloudFormation```になる。

上記のケースでのアクション構造のリファレンスは[ここ](https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/action-reference-CloudFormation.html)。

|プロパティ|デフォルト値|
|:--|:--|
| ActionMode | 必須 ※リファレンス記載 |
| StackName | 必須 ※リファレンス記載 |
| Capabilities | 条件により必須 ※リファレンス記載<br>今回のように```ActionMode: CREATE_UPDATE``` に設定する場合、```CAPABILITY_NAMED_IAM, CAPABILITY_AUTO_EXPAND```を設定する。 |
| ChangeSetName | 条件により必須 ※リファレンス記載<br>今回のように```ActionMode: CREATE_UPDATE``` に設定する場合、不要 |
| RoleArn | 条件により必須 ※リファレンス記載<br>今回のように```ActionMode: CREATE_UPDATE``` に設定する場合、必須 |
| TemplatePath | 条件により必須 ※リファレンス記載<br>今回のように```ActionMode: CREATE_UPDATE``` に設定する場合、必須 |
| OutputFileName | なし |
| ParameterOverrides | なし |
| TemplateConfiguration | なし |

# 最小権限にするためのIAM設定
これまでの記事では、毎回「適当にS3にフルアクセスできるような権限でも用意しておいて」なノリで書いていて、最小権限の考え方と外れていたので、少し真面目に考えてみる。
今回、アーティファクトのやり取りのためにS3を新規に作ったり、CodeBuildのためにCloudWatch Logsのロググループを作ったりしているので、それだけを既存のCodeBuildのサービスロールに追加してあげることにしよう。

なお、```${Prefix}```, ```${CodeBuildPolicyNameSuffix}```, ```${CodeBuildLogGroupNameSuffix}```, ```${S3BucketName}```,  ```${CodeBuildRole}``` はParametersで事前に設定しておけるようにしておこう。

```YAML
  CODEBUILDIAMPOLYCY:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: !Sub ${Prefix}${CodeBuildPolicyNameSuffix}
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: AllowS3Bucket
            Effect: Allow
            Action:
              - "s3:GetBucketAcl"
              - "s3:GetBucketLocation"
            Resource: !GetAtt S3BUCKET.Arn
          - Sid: AllowS3Object
            Effect: Allow
            Action:
              - "s3:PutObject"
              - "s3:GetObject"
              - "s3:GetObjectVersion"
            Resource: !Join [ "/", [ !GetAtt S3BUCKET.Arn, "*" ] ]
          - Sid: AllowCloudWatchLogs
            Effect: Allow
            Action:
              - "logs:CreateLogGroup"
              - "logs:CreateLogStream"
              - "logs:PutLogEvents"
            Resource: !GetAtt CODEBUILDLOGGROUP.Arn
      Roles:
        - !Sub ${CodeBuildRole}
  S3BUCKET: 
    Type: AWS::S3::Bucket
    Properties: 
      BucketName: !Sub ${S3BucketName}
  CODEBUILDLOGGROUP:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub ${Prefix}${CodeBuildLogGroupNameSuffix}
```

気を付けなければいけないのが、IAMについては、CodePipelineとの依存関係をCloudFormationが自動で解決してくれないということ。
何も考えずに設定してしまうと、IAMとCodePipelineの作成順序が変わってしまう可能性があり、CodePipeline実行時にまだIAMポリシの設定が完了していなくてエラーになってしまう。

CodePipelineのリソース定義に

```YAML
  PIPELINE:
    Type: AWS::CodePipeline::Pipeline
    DependsOn: CODEBUILDIAMPOLYCY
    Properties: 
      …
```

と、忘れず入れておくようにしよう。

# CloudFormationテンプレート全体
ちょっと長いけど↓こんな感じになる。
今回は、ParametersのPrefixとS3BucketNameを修正すれば良いようにしたので、

```YAML
AWSTemplateFormatVersion: "2010-09-09"
Description:
  CI/CD Pipeline for Lambda Create

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "LambdaTest"
  S3BucketName:
    Description: "S3 bucket name for Artifact"
    Type: "String"
    Default: "lambdatest-artifact-bucket"
  PipelineNameSuffix:
    Description: "PipelineName on CodePipeline"
    Type: "String"
    Default: "-Pipeline"
  RepositoryNameSuffix:
    Description: "RepositoryName on CodeCommit"
    Type: "String"
    Default: ""
  BuildProjectNameSuffix:
    Description: "BuildProjectName on CodeBuild"
    Type: "String"
    Default: "-Build-Project"
  StackNameSuffix: 
    Description: "StackName on CloudFormation(SAM)"
    Type: "String"
    Default: "-CodePileline-Stack"
  CodeBuildRole:
    Description: "CodeBuild Service Role"
    Type: "String"
    Default: "codebuild-LambdaTestProject-service-role"
  CodeBuildPolicyNameSuffix:
    Description: "IAM Policy Name for CodeBuild"
    Type: "String"
    Default: "-CodeBuild-Polycy"
  CodeBuildLogGroupNameSuffix:
    Description: "LogGroup Name for CodeBuild"
    Type: "String"
    Default: "-CodeBuild-LogGroup"


Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "S3 Configuration"
        Parameters:
          - S3BucketName
      - Label:
          default: "Pipeline Configuration"
        Parameters:
          - PipelineNameSuffix
          - RepositoryNameSuffix
          - BuildProjectNameSuffix
          - StackNameSuffix
      - Label:
          default: "IAM Role/Policy Configuration"
        Parameters:
          - CodeBuildRole
          - CodeBuildPolicyNameSuffix
      - Label:
          default: "Log Configuration"
        Parameters:
          - CodeBuildLogGroupNameSuffix

Resources:
  # ------------------------------------------------------------#
  #  IAM Policy 
  # ------------------------------------------------------------#
  CODEBUILDIAMPOLYCY:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: !Sub ${Prefix}${CodeBuildPolicyNameSuffix}
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: AllowS3Bucket
            Effect: Allow
            Action:
              - "s3:GetBucketAcl"
              - "s3:GetBucketLocation"
            Resource: !GetAtt S3BUCKET.Arn
          - Sid: AllowS3Object
            Effect: Allow
            Action:
              - "s3:PutObject"
              - "s3:GetObject"
              - "s3:GetObjectVersion"
            Resource: !Join [ "/", [ !GetAtt S3BUCKET.Arn, "*" ] ]
          - Sid: AllowCloudWatchLogs
            Effect: Allow
            Action:
              - "logs:CreateLogGroup"
              - "logs:CreateLogStream"
              - "logs:PutLogEvents"
            Resource: !GetAtt CODEBUILDLOGGROUP.Arn
      Roles:
        - !Sub ${CodeBuildRole}

  # ------------------------------------------------------------#
  #  S3 Bucket 
  # ------------------------------------------------------------#
  S3BUCKET: 
    Type: AWS::S3::Bucket
    Properties: 
      BucketName: !Sub ${S3BucketName}

  # ------------------------------------------------------------#
  #  Cloud Watch Log Group
  # ------------------------------------------------------------#
  CODEBUILDLOGGROUP:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub ${Prefix}${CodeBuildLogGroupNameSuffix}

  # ------------------------------------------------------------#
  #  CodeBuild
  # ------------------------------------------------------------#
  CODEBUILD:
    Type: AWS::CodeBuild::Project
    Properties: 
      Name: !Sub ${Prefix}${BuildProjectNameSuffix}
      Source: 
        Type: CODEPIPELINE
        BuildSpec: buildspec.yml
      Artifacts: 
        Type: CODEPIPELINE
      Environment: 
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
      LogsConfig:
        CloudWatchLogs:
          GroupName: !Sub ${Prefix}${CodeBuildLogGroupNameSuffix}
          Status: ENABLED
      Cache: 
        Type: LOCAL
        Modes:
          - LOCAL_CUSTOM_CACHE
      ServiceRole: !Sub arn:aws:iam::${AWS::AccountId}:role/service-role/codebuild-LambdaTestProject-service-role

  # ------------------------------------------------------------#
  #  CodePipeline
  # ------------------------------------------------------------#
  PIPELINE:
    Type: AWS::CodePipeline::Pipeline
    DependsOn: CODEBUILDIAMPOLYCY
    Properties: 
      Name: !Sub ${Prefix}${PipelineNameSuffix}
      ArtifactStore: 
        Location: !Ref S3BUCKET
        Type: S3
      RoleArn: !Sub arn:aws:iam::${AWS::AccountId}:role/CodePipelineRole
      Stages: 
        - Name: Source
          Actions:
            - RunOrder: 1
              Name: Source
              ActionTypeId: 
                Category: Source
                Owner: AWS
                Provider: CodeCommit
                Version: 1
              Configuration:
                RepositoryName: !Sub ${Prefix}${RepositoryNameSuffix}
                BranchName: master
              OutputArtifacts: 
                - Name: SourceArtifact
        - Name: Build
          Actions:
            - RunOrder: 2
              Name: Build
              ActionTypeId: 
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              Configuration:
                ProjectName: !Ref CODEBUILD
              InputArtifacts: 
                - Name: SourceArtifact
              OutputArtifacts: 
                - Name: BuildArtifact
        - Name: Deploy
          Actions:
            - RunOrder: 3
              Name: Deploy
              ActionTypeId: 
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: 1
              Configuration:
                StackName: !Sub ${Prefix}${StackNameSuffix}
                ActionMode: CREATE_UPDATE
                RoleArn: !Sub arn:aws:iam::${AWS::AccountId}:role/CloudFormationLambdaPipeline
                TemplatePath: BuildArtifact::output-template.yml
                Capabilities: CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND
              InputArtifacts: 
                - Name: BuildArtifact
```

↓の部分が、アプリのbuildspec.ymlと連動しなければいけないのがちょっとイケてない……。
また、このテンプレートで作るS3バケットの名前をあらかじめbuildspec.ymlの中で指定しなければいけないのもな……。この辺を、Outputと連動させられるようになると良い感じなのだが。

```YAML
TemplatePath: BuildArtifact::output-template.yml
```