SAM+CodeDeployでlambdaへデプロイする。

前回まではCodeDeployを用いてEC2インスタンスへのデプロイを学習しました。
今回はデプロイ対象をlambdaでやってみたいと思います。CodeDeployのアプリケーション作成画面でlambdaを選択できるので同じ要領かと思っていましたが、SAMというやつを一緒に使うみたいです。

[公式チュートリアル](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/tutorial-lambda-sam.html)は普通に分かりやすいです。なので、今回はテンプレートyamlファイルの設定項目をここで指定していきましょう。

_contents_

* AWS SAM
* AWS CloudFormation
* 公式チュートリアル実施時の備忘録/トラブルシューティング
* template.ymlの解読(本題)

# AWS SAM

AWS Serverless Application Model(AWS SAM)は、yaml形式で記述したテンプレートを元にして種々のAWSリソースを用いたアプリケーションを構築することができます。
ローカル上から利用するために、`aws-sam-cli`を利用します。

# AWS CloudFront

* テンプレート : アプリケーション全体の設計図(yamlベースもしくはjsonベース:コメントが書けるのでyaml推奨)
* スタック : テンプレートを元にCloudFormationが構築したリソースの集合全体（＝環境自体）

CloudFormationで生成する環境は、スタック単位で管理されスタックの破棄とともに紐づいたリソースを全て削除することができる。

# 公式チュートリアルやってみた

## SAM CLIのインストールorアップグレード
 
公式には以下のようにありますが多分`pip`でいけると思います。
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html

```:アップグレード
$ pip install --upgrade awscli
$ pip install --upgrade aws-sam-cli
$ sam --version
```

`awscli`を先に最新版にしたほうがいいです。`botocore`のバージョンが一致しないと以下のようにエラーします。

```:エラーメッセージ
ERROR: awscli 1.16.304 has requirement botocore==1.13.40, but you'll have botocore 1.13.44 which is incompatible.
Installing collected packages: botocore, boto3, aws-sam-translator, tomlkit, aws-sam-cli
  Found existing installation: botocore 1.13.40
    Uninstalling botocore-1.13.40:
ERROR: Could not install packages due to an EnvironmentError: [Errno 13] Permission denied: 'RECORD'
Consider using the `--user` option or check the permissions.
```

## デプロイの仕組み

最終的にはディレクトリごとzipにするので、デプロイ用のディレクトリを作り、以下の構成にします。

```
$ tree                      
SAM-Tutorial
├── afterAllowTraffic.js
├── beforeAllowTraffic.js
├── myFunction.js
└── template.yml
```

|ファイル|説明|
|:---:|:---:|
|myFunction.js|デプロイ対象のファイル|
|template.yml|デプロイの流れを書いた仕様書(これが要)|
|beforeAllowTraffic.js|AllowTraffic前に実行するテスト|
|afterAllowTraffic.js|AllowTraffic後に実行するテスト|

template.ymlを元にパッケージを作成します。実行すると、`SAM-Tutorial`ディレクトリ`内に`package.yml`が生成し、S3バケットには`c2fe022d92156eeb459d45f02a766921`が上がっています。

```shell
$ sam package --template-file template.yml --output-template-file package.yml --s3-bucket YOUR_BUCKET
Uploading to c2fe022d92156eeb459d45f02a766921  4113 / 4113.0  (100.00%)

Successfully packaged artifacts and wrote output template to file package.yml.
```

続いてデプロイです。`--capabilities CAPABILITY_IAM`はCloudFrontからロールを作成するのに必要な権限付与みたいな感じ必要です。

```shell
sam deploy --template-file package.yml --stack-name my-date-time-app2 --capabilities CAPABILITY_IAM

	Deploying with following values
	===============================
	Stack name                 : my-date-time-app2
	Region                     : None
	Confirm changeset          : False
	Deployment s3 bucket       : None
	Capabilities               : ["CAPABILITY_IAM"]
	Parameter overrides        : {}
#以下略
```



当たり前なのですが、cliを実行しているIAMユーザに適切に権限を与えていないと途中でエラーします。SAMは、実質CloudFormationを利用しているので、IAMユーザには`CloudFormationFullAccess`を適応してください。（FullAccessでなくてもいいのかもしれないが面倒なので未調査）
なお、今回の検証用のユーザは以下のロール適用しました。
（多分不要なものも含んでます。CodeCommiteとか今回使ってないし）

* AWSCodeCommitFullAccess
* AWSLambdaFullAccess
* IAMFullAccess
* AmazonS3FullAccess
* AWSCodeDeployFullAccess
* AWSCloudFormationFullAccess

また、実行途中で失敗した場合、自動的にロールバックされ、ステータスが`ROLLBACK_COMPLETE`になります。この状態はから再度`sam deploy`はできません。一度CloudFormationコンソールから削除します（cliでもできるはず）。
ステータスの見方についての公式ページ→https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-console-view-stack-data-resources.html

```:エラーメッセージ
..../a417d920-239f-11ea-9fca-0ef015a9ecfc is in ROLLBACK_COMPLETE state and can not be updated.
```

デプロイ中はCodeDeployコンソールで進行状況が把握できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/c5af86f4-7372-85f2-cc63-872e3210b6dc.png)

デプロイに成功したら、デプロイされたlambdaをコンソールから確認してみましょう。`パッケージ名＋関数名＋英数字`となっているのでarnを取得してcliからinvokeしてみる。

```
$ aws lambda invoke --function arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:my-date-time-app2-myDateTimeFunction-1S9F2G73JFP32 --payload "{\"option\": \"date\", \"period\": \"today\"}" out.txt
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}

$ SAM-Tutorial ls
afterAllowTraffic.js	myDateTimeFunction.js	package.yml
beforeAllowTraffic.js	out.txt			template.yml

$ SAM-Tutorial cat out.txt    
{"statusCode":200,"headers":{"Content-type":"application/json"},"body":"{\"month\":12,\"day\":21,\"year\":2019}"}%
```

この後、ローカルでmyDateTimeFunction.jsファイルを更新した場合、上記と同じ手順で`aws package`し、`aws deploy`する。

なお、CodeDeployを通してデプロイしたlambdaでもコンソール上で直接変更はできる。ただこの方法で更新したものはversionとして変更履歴に保存されないので注意。

# ここからが本題

## template.ymlの解読

template.ymlのオプション指定を解読していきます。

### Transform（必須）
使用するSAMのバージョンを指定し、使用できるSAM構文とCloudFormationがそれをどのように処理するかを定義します。`AWS::Serverless-2016-10-31`を指定する。

### Globals
グローバル領域。複数のリソースに対して共通の定義をできる。例えば全ての関数でランタイムは`python3.8`でメモリは256を指定したいといった場合、`Globals`に書くことで以降の`AWS::Serverless::Function`の各Functionの項目に書かなくても継承してくれる。

`Grobals`で一括定義できるのは以下のみ。

* AWS::Serverless::Function
* AWS::Serverless::Api
* AWS::Serverless::SimpleTable

`Grobals`をデフォルト値のようにして`AWS::Serverless::Function`側にも定義した場合その関数だけは指定した値に上書きできるとかあるのだろうか？

なお、それぞれ何を指定できるかは公式参照→https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html

### Description
その名の通り説明。

### Metadata
メタデータ。追加情報です。まだ使い道がわからない。。。

### Parameters
テンプレートを実行するときに、ユーザーに入力させるパラメータを定義できます。

### Mappings
連想配列/辞書を定義できます。

### Conditions
条件を設定します。真の場合にのみリソースを生成するなどの条件分岐が可能です。
パラメータと組み合わせた例が以下。

```yml:例
Parameters: 
  EnvType: 
    Description: Environment type.
    Default: test
    Type: String
    AllowedValues: 
      - prod
      - test
Conditions: 
  CreateProdResources: !Equals [ !Ref EnvType, prod ]
Resources: 
  # CreateProdResourcesがfalseの場合にはインスタンスは作られない。
  EC2Instance: 
    Type: "AWS::EC2::Instance"
    Condition: CreateProdResources
    Properties: 
      ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
```

### Output
スタック構築後に、指定した情報を出力する。

### Resources(必須)

準備するリソースを指定する。いくつかピックアップして調べてみる。

#### AutoPublishAlias

```yml:チュートリアルのtemplate.yml
# Instructs your myDateTimeFunction is published to an alias named "live".      
      AutoPublishAlias: live
```

この`AutoPublishAlias`はデプロイ対象の関数が変更されたときに検出し、テンプレート上で命名したエイリアスを使用してデプロイするようにフレームワークに指示します。この指定は必須です。
やってみるとわかるのですが、`sam deploy`するとlambdaはバージョン管理されます。`$LATEST`とは別にエイリアスをつけられます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/e27486be-e6b5-fb09-6bc6-c2f64e1a5c44.png)

エイリアスとは、lambdaのバージョン番号に別名を付したものです。

#### DeploymentPreference Type

```yml:チュートリアルのtemplate.yml
      DeploymentPreference:
# Specifies the deployment configuration      
          Type: Linear10PercentEvery1Minute
```

ここでは新しいバージョンのlambdaへトラフィックを流す割合を定義しています。
`Linear10PercentEvery1Minute`は1minのトラッフィクのうち10％を次バージョンへ回す設定です。

クラスメソッドの実際の実験がわかりやすいです。
https://dev.classmethod.jp/server-side/serverless/understanding-lambda-deploy-with-codedeploy-using-aws-sam/

また、このデプロイのタイプの設定はカナリアリリースなども選択可能であり同じく上記リンクに紹介があります。

#### DeploymentPreference Hooks

```yml:チュートリアルのtemplate.yml
# Specifies Lambda functions for deployment lifecycle hooks
          Hooks:
            PreTraffic: !Ref beforeAllowTraffic
            PostTraffic: !Ref afterAllowTraffic
```

`Hooks`はCodeDeployでEC2へのデプロイ時の設定にもあった通り、各イベントの前後の適切なタイミングで指定したプログラムを実行するための設定です。lmabdaへのデプロイではtrafic allowの前後2つのタイミングを選択できます。公式には以下のように説明があります。(`beforeAllowTraffic`と`afterAllowTraffic`はCloudFrontでの指定方法であり、SAMではそれぞれ`PreTraffic`と`PostTraffic`に対応しております。わかりにくい！)

> AWS Lambda フックは、ライフサイクルイベントの名前の後の新しい行に文字列で指定された 1 つの Lambda 関数です。各フックはデプロイごとに 1 回実行されます。以下は、AppSpec ファイルに使用できるフックの説明です。

> BeforeAllowTraffic – これを使用して、トラフィックがデプロイされた Lambda 関数のバージョンに移行する前にタスクを実行します。

> AfterAllowTraffic – これを使用して、トラフィックがデプロイされた Lambda 関数のバージョンに移行した後でタスクを実行します。

用途としては`PreTraffic`の段階で単体テストし、成功すればデプロイ、 `PostTraffic`で結合テストし、失敗すればロールバックさせるなど一連の流れを構築できそうです。

また、この`Ref!`という記法はCloudFrontの組み込み関数で、以下のように読み換えられます。

```yml
# パラメータ参照
!Ref {パラメーター名}
# リソースの返り値を取得
!Ref {リソース名}
```

同じく、 `!Sub`という表現も次に出てきます。
これは文字列とパラメータを組み合わせた値を作成する際に使用します。

参考） https://qiita.com/ryurock/items/766154e0afb8fdb629e2

#### 関数ポリシー

```yml:チュートリアルのtemplate.yml
# Grants this function permission to call codedeploy:PutLifecycleEventHookExecutionStatus        
          Statement: 
          - Effect: "Allow"
            Action: 
              - "codedeploy:PutLifecycleEventHookExecutionStatus"
            Resource:
              !Sub 'arn:aws:codedeploy:${AWS::Region}:${AWS::AccountId}:deploymentgroup:${ServerlessDeploymentApplication}/*'
        - Version: "2012-10-17"
```

デプロイ前後のテスト用関数から対象の関数をinvokeするにはアクセス許可をする必要があります。（正直lambdaFullアクセスを与えてしまってもいいと思ってます。）

#### lambda環境変数の指定

```yml:チュートリアルのtemplate.yml
      Environment:
        Variables:
          NewVersion: !Ref myDateTimeFunction.Version
```