LambdaをCodePipeline(CodeCommit→CodeBuild→CloudFormation)でCDする方法

# 何をしたいか
[この記事](https://qiita.com/RyujiKawazoe/items/38411271230f9e112253)とほぼ一緒ですが、今のUIとは少し違っていたので、その覚え書き。
以下のような構成のファイルがCodeCommitにあるときに、自動的に全部のLambdaを更新したい。

```
.
├── buildspec.yml
├── cloudformation-template-init.json
├── cloudformation-template.json
├── lambda1
│   └── lambda_function.py
├── lambda2
│   └── lambda_function.py
├── requirements_lambda1.txt
└── requirements_lambda2.txt
```

#### ファイル説明

* lambda1/lambda_function.py
* lambda2/lambda_function.py

デプロイするlambda、とりあえず最低限のもの

```
import json                                                                                                                                                                                                 
import numpy as np

def lambda_handler(event, context):
    arr = np.asarray([1,2,3])
    return {
        'statusCode': 200,
        'body': json.dumps(str(arr))
    }
```

* requirements_lambda1.txt
* requirements_lambda2.txt

lambdaが使うパッケージ、これも最低限のもの

```
numpy==1.15.1
```

* buildspec.yml
* cloudformation-template-init.json
* cloudformation-template.json

それぞれ「CodeBuild」「CloudFormationの初期設定」「CodePipelineのCloudFormationのテンプレート」で使うファイル
必要なタイミングで説明。

# やること
作業内容は

* CodePipelineで指定するCloudFormationのスタックを作る
* CodeBuildを作ってlambdaの実行に必要なライブラリをpip installする
* CodePipelineでデプロイまでつなげる

というような流れでやります。
### CodePipelineで指定するCloudFormationのスタックを作る
CodePipelineでパイプラインを作る前に、CloudFormationのスタックを作っておく必要があります。
本来は不要で(だと思いま)すが、ブラウザでCodePipelineの設定を行い、デプロイにCloudformationを選んだ場合、既存のスタックの選択が必須になっているので事前に作っておきます。→ 詳しくは余談へ記載
ここでは一応それっぽいスタック(2個のlambdaを作るスタック)を作っておきます。
（多分ですが、CodePipelineでは、スタックさえ選択できればいいので、ゴミみたいな内容のスタックでも問題無いと思います。）

とりあえず以下を使って、Cloudformationで２つのLambdaを管理するスタックを作ります。
どうせCodePipelineでデプロイするときに上書きされるので、Lambdaには意味の無いコードをデプロイしています。

cloudformation-template-init.json

```
{                                                                                                                                                                                                           
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "hoge",
  "Resources" : { 
    "SampleLambda1": {
      "Type" : "AWS::Lambda::Function",
      "Properties" : { 
        "Code": {
          "ZipFile": { "Fn::Join": ["", [
            "nothing"
          ]]}
        },
        "FunctionName": "SampleLambda1",
        "Runtime": "python3.6",
        "Role": "****YOUR LAMBDA ROLE****",
        "Handler": "lambda_function.lambda_handler",
        "Description": "", 
        "Timeout": 3,
        "MemorySize": 128 
      }   
    },  
    "SampleLambda2": {
      "Type" : "AWS::Lambda::Function",
      "Properties" : { 
        "Code": {
          "ZipFile": { "Fn::Join": ["", [
            "nothing"
          ]]}
        },
        "FunctionName": "SampleLambda2",
        "Runtime": "python3.6",
        "Role": "****YOUR LAMBDA ROLE****",
        "Handler": "lambda_function.lambda_handler",
        "Description": "", 
        "Timeout": 3,
        "MemorySize": 128 
      }   
    }   
  }
}

```
![Screenshot from 2019-03-18 10-43-07.png](https://qiita-image-store.s3.amazonaws.com/0/219595/3a67d9b8-8e3e-7186-a13e-91d4ee398caf.png)

![Screenshot from 2019-03-18 14-28-53.png](https://qiita-image-store.s3.amazonaws.com/0/219595/5ecf9e70-8d36-4349-b239-b3e169ec8745.png)

スタックの名前を適当につけて、何も変更せず作りきってしまいます。
作成後にLambdaを確認すると、２つのLambdaが作られていることが確認できます。↓

![Screenshot from 2019-03-18 10-42-11.png](https://qiita-image-store.s3.amazonaws.com/0/219595/82c665ef-0f88-34fd-6d62-8af36e7f49e3.png)

![Screenshot from 2019-03-18 10-41-36.png](https://qiita-image-store.s3.amazonaws.com/0/219595/3540faec-20e8-d0cc-4b04-48ed0ecb92ee.png)



### CodeBuildを作ってlambdaの実行に必要なライブラリをpip installする

#### CodeBuildのビルド設定
以下のようなビルド設定にします。
pipで必要なライブラリをインストールして、artifact1とartifact2と言う名前の成果物を作ります。
CodePipelineとつなげるとき、この「artifact1」「artifact2」と言う名前が重要になってくるので覚えておきましょう。

buildspec.yml

```
version: 0.2                                                                                                                                                                                                

phases:
  install:
    commands:
      - echo 'install phase'
      - pip install -r requirements_lambda1.txt -t ./lambda1
      - pip install -r requirements_lambda2.txt -t ./lambda2
  build:
    commands:
      - echo 'build phase'
      - echo 'todo test'

artifacts:
  secondary-artifacts:
    artifact1:
      base-directory: 'lambda1'
      discard-paths: no
      files:
        - '**/*'
    artifact2:
      base-directory: 'lambda2'
      discard-paths: no
      files:
        - '**/*'
```

#### CodeBuild作成
CodeBuildでビルドを作ります。

* buildspec.ymlファイルを正しく選ぶ(今回はリポジトリ直下にあるのでデフォルトにする)こと
* artifactの受け渡しはCodePipeline側でやるのでNo artifactsにすること※  
※今回のbuildspec.ymlにはprimary artifactを設定してないので失敗します。  
  ビルド成果物を確認したいときはbuildspec.ymlにprimary artifactを設定しましょう。

あたりが注意ポイントです。


・ビルドに名前をつける
![Screenshot from 2019-03-18 10-49-01.png](https://qiita-image-store.s3.amazonaws.com/0/219595/9afc97d4-7824-6f3c-b1d7-f7dfc298c620.png)

・buildspec.ymlを実行するためのイメージを選ぶ(pip installするためにpython)
![Screenshot from 2019-03-18 10-48-53.png](https://qiita-image-store.s3.amazonaws.com/0/219595/d10ed4ac-23dd-a007-9366-443726ff8fb1.png)

・buildspec.ymlを選ぶ(ソース直下にあるのでデフォルトのまま)
![Screenshot from 2019-03-18 10-46-42.png](https://qiita-image-store.s3.amazonaws.com/0/219595/a1f0920b-dc6b-a342-c874-36f5ec008d88.png)

・成果物は無し
![Screenshot from 2019-03-18 10-50-08.png](https://qiita-image-store.s3.amazonaws.com/0/219595/75ef916a-a0a6-9c35-80b7-c190c3dab569.png)

ここで一度ビルドを実行してみて、pip installできるか確認しても良いです。
S3にartifactを保存するように設定している場合は、そこに目的のものが出来上がっているかで確認できます。

### CodePipelineでデプロイまでつなげる

以下のように作っていきます。

・CodeCommitのリポジトリを選ぶ
![Screenshot from 2019-03-18 11-08-58.png](https://qiita-image-store.s3.amazonaws.com/0/219595/0d5242b6-4830-4cc7-a500-8c48151eb63e.png)

・ビルド方法に先程作ったビルドプロジェクトを選ぶ
![Screenshot from 2019-03-18 11-09-15.png](https://qiita-image-store.s3.amazonaws.com/0/219595/0db2471a-12c0-40a7-2ead-ad6ba7173ecc.png)

・詳細なデプロイ設定ができないのでとりあえずスキップ
![Screenshot from 2019-03-18 11-10-56.png](https://qiita-image-store.s3.amazonaws.com/0/219595/b0c37c5e-a9ed-7f70-3a3a-55ecd4d4591e.png)
![Screenshot from 2019-03-18 11-11-04.png](https://qiita-image-store.s3.amazonaws.com/0/219595/3f52f9b3-cca5-cbf6-612f-81f71a723c00.png)

完成したパイプラインを選択して「編集する」から、ビルドステージのアクションを編集します。
次のデプロイステージにビルド成果物を引き渡すために、出力アーティファクトの項目にbuildspec.ymlで指定した**artifact1**と**artifact2**を**同じ名前**で設定します。([参考](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/sample-pipeline-multi-input-output.html))
> パイプラインの JSON ファイルで指定されている出力成果物の名前は、buildspec ファイルで定義されているセカンダリアーティファクトの名前と一致していなければなりません。

![Screenshot from 2019-03-18 11-11-26.png](https://qiita-image-store.s3.amazonaws.com/0/219595/14fdcbcb-d5af-60ad-2e1a-52c5b7484ba1.png)

そして、ステージの追加から、Deployステージを追加し、CloudFormationを選び、デプロイの設定を行います。
入力アーティファクトはビルドから受け取るartifact1とartifact2、cloudformation-template.jsonを使うためにSourceArtifactを指定。
「スタックを作成または更新」を選び、既存のスタックとして、上記で作成済みのスタックを選択。
テンプレートは、SourceArtifactの直下にあるcloudformation-template.jsonを指定。
![Screenshot from 2019-03-18 11-12-02.png](https://qiita-image-store.s3.amazonaws.com/0/219595/332ea732-bc2d-cfb8-c102-a8304eb08d3c.png)

Lambdaのデプロイが行えるロールを選択(高度な設定のパラメータの上書きについては後述)
![Screenshot from 2019-03-18 11-12-59.png](https://qiita-image-store.s3.amazonaws.com/0/219595/c43bab0b-02df-9cd5-c26e-5c27ee998820.png)

ここで指定している
「cloudformation-template.json」と「高度-パラメータの上書き」は以下のようになっています。
詳しくは[この記事](https://qiita.com/RyujiKawazoe/items/38411271230f9e112253)をご覧ください。

※cloudformation-template.jsonに書いてあるlambdaの設定(メモリとかタイムアウトとか名前とか)は、ドキュメントを見ながら書くより、ブラウザで自分が作りたいlambdaを作って、関数のエクスポートからSAMファイルを取ったり
`aws lambda get-function-configuration --function-name ○○`
で設定すべきパラメータを確認するのが楽だと思います。

* cloudformation-template.json

```
                                                                                                                                                                                                           
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "hoge",
  "Parameters": {
    "LatestS3BucketLambda1": {
      "Type": "String"
    },  
    "LatestS3KeyLambda1": {
      "Type": "String"
    },  
    "LatestS3BucketLambda2": {
      "Type": "String"
    },  
    "LatestS3KeyLambda2": {
      "Type": "String"
    }   
   },  
  "Resources" : { 
    "SampleLambda1": {
      "Type" : "AWS::Lambda::Function",
      "Properties" : { 
        "Code": {
          "S3Bucket": {
            "Ref": "LatestS3BucketLambda1"
          },
          "S3Key": {
            "Ref": "LatestS3KeyLambda1"
          }
        },
        "FunctionName": "SampleLambda1",
        "Runtime": "python3.6",
        "Role": "****YOUR LAMBDA ROLE****",
        "Handler": "lambda_function.lambda_handler",
        "Description": "", 
        "Timeout": 3,
        "MemorySize": 128 
      }   
    },  
    "SampleLambda2": {
      "Type" : "AWS::Lambda::Function",
      "Properties" : { 
        "Code": {
          "S3Bucket": {
            "Ref": "LatestS3BucketLambda2"
          },
          "S3Key": {
            "Ref": "LatestS3KeyLambda2"
          }
        },
        "FunctionName": "SampleLambda2",
        "Runtime": "python3.6",
        "Role": "****YOUR LAMBDA ROLE****",
        "Handler": "lambda_function.lambda_handler",
        "Description": "",
        "Timeout": 3,
        "MemorySize": 128
      }
    }
  }
```

* 高度-パラメータの上書き

```
{
    "LatestS3BucketLambda1": {
        "Fn::GetArtifactAtt": [
            "artifact1",
            "BucketName"
        ]
    },
    "LatestS3KeyLambda1": {
        "Fn::GetArtifactAtt": [
            "artifact1",
            "ObjectKey"
        ]
    },
    "LatestS3BucketLambda2": {
        "Fn::GetArtifactAtt": [
            "artifact2",
            "BucketName"
        ]
    },
    "LatestS3KeyLambda2": {
        "Fn::GetArtifactAtt": [
            "artifact2",
            "ObjectKey"
        ]
    }
}
```

# 確認
CodePipelineの「変更をリリース」を押し、成功したことを確認し、適当にLambdaを実行します。
Sample-Lambda-Stackに所属していることと、ソースがデプロイされていること、numpyが使えてるっぽいことが確認できるはずです。
失敗している場合は、CodeBuildのビルドログと、Cloudformationのイベントログ・テンプレート・パラメータが意図したものになっているか確認してみましょう。
もしかしたらIAMで何かしらの権限が必要だったりするかもしれないです。

![Screenshot from 2019-03-18 11-06-54.png](https://qiita-image-store.s3.amazonaws.com/0/219595/ffdad0a5-81d6-587c-d099-179f494f8e92.png)

# 余談
> 本来は不要で(だと思いま)すが、ブラウザでCodePipelineの設定を行い、デプロイにCloudformationを選んだ場合、既存のスタックの選択が必須になっているので事前に作っておきます。

について

例えば最後まで作った状態で、CloudFormationから「Sample-Lambda-Stack」を削除したとします。
（スタックを消したので「SampleLambda1」と「SampleLambda2」も一緒に消えます）
この状態でCodePipelineを見ると、「Sample-Lambda-Stackが無いから、このPipelineはおかしい」と言われます。
その警告を無視して、CodePipelineを実行すると「Sample-Lambda-Stack」の名前でスタックを作ってくれます。
（結果として「SampleLambda1」と「SampleLambda2」も作られます。）
この「スタックがなければ自動で作る」という動きについては、CodePipelineのCloudFormationの設定で「スタックの作成または更新」と設定しているので、正しいように思います。
そもそも「スタックを作成する」のであれば、既存のスタックなんて無いはずなので、CodePipelineの設定で、既存のスタックを必須で選択させること自体が、なにかおかしい気がします。
それかそういう設定はCLIでやれって意味なのかもしれないです。

# 参考
[CodePipeLineを使ってLambdaへの自動デプロイ](https://qiita.com/RyujiKawazoe/items/38411271230f9e112253)
[CodePipeline と複数の入力ソースおよび出力アーティファクトサンプルとの CodeBuild の統合](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/sample-pipeline-multi-input-output.html)
[CodePipeline パイプラインでのパラメーターオーバーライド関数の使用](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline-parameter-override-functions.html)