ALBのバックエンドで動作するJava実装のLambda関数をBlue/GreenデプロイメントするCodePipelineを作る

# 前提条件
以下の記事で作ったアプリケーションをベースに、CodePipelineでCI/CDパイプラインを実装するため、内容を把握しておいてほしい。

[Amazon API Gateway/ALBのバックエンドで動くLambda関数をJava(Eclipse＋maven)で実装する](https://qiita.com/neruneruo/items/8673af2a3ae0f8076627)

また、ALBのリスナー、ターゲットグループはデプロイ内部で作る都合上、削除しておく。
空っぽのALBのARNが準備されていれば良い(これもデプロイで一緒に作ってしまっても良いけど、ALBの作成って時間かかるので……)。

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

# 1. CodeCommitのリポジトリの準備
今回、Eclipse＋mavenベースなので、git操作もEclipse上で実施することを前提とする。
AWS ToolkitのCodeCommitを右クリックしてリポジトリを作成したら、作ったリポジトリを右クリックしてリポジトリをクローンし、そこにスケルトンのファイルを全部放り込んでビルドし直す。
うまくビルドできたら、Commitして準備完了。

# 2. CodeBuildのプロジェクトの準備
ここはあまり悩む部分はない。

| 項目 | 内容 |
|:--|:--|
| ソースプロバイダ | AWS CodeCommit |
| リポジトリ | 1.で作ったリポジトリ |
| リファレンスタイプ | お好みで。 |
| 環境イメージ | マネージド型イメージ(AL2のStandard3.0でOK) |
| サービスロール | 適当にCodeCommitやS3にアクセスできる権限のあるもの |
| ビルド仕様 | buildspecファイルを使用する(詳細は口述) |
| アーティファクト | Amazon S3。バケットは適当に準備しておく |

buildspecについては、CodeCommitから取ってくるので、リポジトリのトップに以下のファイルを入れておく。

```YAML:buildspec.yml
version: 0.2

phases:
  install:
    runtime-versions:
      java: corretto8
  build:
    commands:
      - echo Build started on `date`
      - mvn package
  post_build:
    commands:
      - echo Build ended on `date`
      - echo CloudFormation Package started on `date`
      - aws cloudformation package --template-file template.yml --output-template-file output-template.yml --s3-bucket [用意したS3バケット名]
      - echo CloudFormation Package ended on `date`
artifacts:
  type: zip
  files:
    - output-template.yml
cache:
  paths:
    - '/root/.m2/**/*'
```

上記のtemplate.ymlを元に、この後のデプロイフェーズのインプットになるoutput-template.ymlを作成する。以下のtemplateのCodeUriを、CodePipelineが扱える形式に書き換えてくれるようだ(なので、実はCodeUriは設定しなくても良いかもしれない)。
Roleには、Lambdaが実行できる適当なロールを設定する。

```YAML:template.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: Test for Lambda CI/CD Pipeline

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "LambdaTestCodepipelineStack"

Globals:
    Function:
        Timeout: 60

Resources:
  LambdaTest:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: LambdaTest
      Handler: com.amazonaws.lambda.demo.LambdaFunctionHandler::handleRequest
      Runtime: java8
      MemorySize: 128
      Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
      CodeUri: ./target/LambdaTest-1.0.0.jar
      AutoPublishAlias: Prod
      DeploymentPreference:
        Type: Canary10Percent5Minutes
  HttpListener:
    Type: 'AWS::ElasticLoadBalancingV2::Listener'
    Properties:
      DefaultActions:
      - TargetGroupArn: !Ref TargetGroup
        Type: forward
      LoadBalancerArn: [前回用意したALBのARN]
      Port: 80
      Protocol: HTTP
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    DependsOn: LambdaTestInvokePermission
    Properties:
      Name: LambdaTestTargetGroup
      TargetType: lambda
      Targets:
      - Id: !Ref LambdaTestAliasProd
      HealthCheckEnabled: true
  LambdaTestInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref LambdaTestAliasProd
      Action: 'lambda:InvokeFunction'
      Principal: elasticloadbalancing.amazonaws.com
```

Blue/Greenデプロイメントするにあたりキモになるのが、AutoPublishAlias, DeploymentPreference, TargetGroupに設定するTargetsのIdあたりだが、後で解説する。

# 3. CodePipelineのデプロイステージに設定するIAMロール
今回のCodePipelineは、CodePipelineからCloudFormationを呼び出すので、CloudFormationのサービスロールを作る必要がある。

ロールの作成で、信頼されたエンティティの選択→「AWS サービス」としてCloudFormationを選び、「AWSLambdaExecute」ポリシと、以下のポリシをアタッチする(インラインポリシーでもポリシー作ってアタッチでも良い。今回はALBのバックエンドのデプロイなので```apigateway:*```は不要な気がする)。

```JSON
{
    "Statement": [
        {
            "Action": [
                "apigateway:*",
                "elasticloadbalancing:DescribeTargetGroups",
                "elasticloadbalancing:DescribeTargetHealth",
                "elasticloadbalancing:CreateTargetGroup",
                "elasticloadbalancing:DeleteTargetGroup",
                "elasticloadbalancing:ModifyTargetGroup",
                "elasticloadbalancing:RegisterTargets",
                "elasticloadbalancing:DeRegisterTargets",
                "elasticloadbalancing:DescribeListeners",
                "elasticloadbalancing:CreateListener",
                "elasticloadbalancing:DeleteListener",
                "codedeploy:*",
                "lambda:*",
                "cloudformation:CreateChangeSet",
                "iam:GetRole",
                "iam:CreateRole",
                "iam:DeleteRole",
                "iam:PutRolePolicy",
                "iam:AttachRolePolicy",
                "iam:DeleteRolePolicy",
                "iam:DetachRolePolicy",
                "iam:PassRole",
                "s3:GetObject",
                "s3:GetObjectVersion",
                "s3:GetBucketVersioning"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ],
    "Version": "2012-10-17"
}
```

# 4. CodePipelineの設定
ここも、素直に設定をしていけば良い。

| 大項目 | 中項目 | 内容 |
|:--|:--|:--|
| パイプラインの設定 | パイプライン名 | 適当につけてOK |
| パイプラインの設定 | サービスロール | CodePipelineを実行するために適当に設定したロール |
| ソースステージ | ソースプロバイダ | CodeCommit | 
| ソースステージ | リポジトリ名 | 1. で作成したリポジトリ |
| ソースステージ | ブランチ | 適切なブランチ |
| ソースステージ | 検出オプションを変更する | Amazon CloudWatch Event |
| ビルドステージ | プロバイダー | AWS CodeBuild |
| ビルドステージ | プロジェクト名 | 2. で作成したプロジェクト |
| デプロイステージ | デプロイプロバイダー | AWS CloudFormation |
| デプロイステージ | アクションモード | スタックを作成または更新する |
| デプロイステージ | スタック名 | 適当に設定する |
| デプロイステージ | テンプレートアーティファクト名 | BuildArtifact::output-template.yml |
| デプロイステージ | 能力 | CAPABILITY_NAMED_IAM とCAPABILITY_AUTO_EXPANDを選択(CloudFormationの中でデプロイするために必要らしい) |
| デプロイステージ | ロール名 | 3. で作成したIAMロール |

これでパイプラインを流すと、CloudFormationの中で良い感じにLambdaをバージョニングしてエイリアスを設定してカナリアリリースをしてくれる。

プロパティの```AutoPublishAlias```で設定したエイリアスを使って、新旧のLambda関数のバージョン間を```DeploymentPreference```で設定した比率でトランザクションコントロールしながらBlue/GreenデプロイメントをしてくれるCodeDeployが作成されるのだ。

TargetGroupのIdとLambdaのパーミッション設定のFunctionNameには、このCodeDeployで生成されるバージョン指定済みのLambda関数のARNの設定が必要で、```!Ref [関数名][エイリアス]```で参照できるようになる(これを指定しないと、$LATESTを向いてしまうのでうまくBlue/Greenデプロイメントができなかった。自分で作っていないリソースを!Refするのはすごい違和感がある……)。
他にも、AlarmsやらHooksやらを使ってより安全にデプロイできるようであるが、まだ調査中であるため、今回は割愛する。

あと、API Gatewayの場合は```AWS::Serverless::Function```のEventsのプロパティでTypeにAPIを指定すると簡単に組み込めるようだが、ALBはなぜか手軽には設定できないんだよなぁ……謎である(理解してしまえば別に難しくはないのだが、調べてここに辿り着くまでだいぶ時間を要した)。

CodeCommitにデプロイするとCanaryReleaseが始まるので、以下のような感じでトラフィックが移り変わる様子を確認してみよう。別にブラウザでF5連打してみても良いけど。

```shell
$ while true; do date; curl http://[ALBのDNS名]; echo; sleep 1; done
```

さて、あとはこのパイプラインをCloudFormationでワンアクションで流せるようにテンプレ化すれば完璧だ！
以下の記事でテンプレート化もしたので、興味があれば是非。

[Lambda関数をBlue/GreenデプロイメントするCodePipelineをCloudFormationで自動構築する](https://qiita.com/neruneruo/items/3f2514b92137a43515c0)