SAMのAutoPublishAliasでバージョンがうまく発行されないときの対処法




# はじめに
みなさま，こんにちは．
[株式会社オプティマインド](http://www.optimind.tech)にて，GISエンジニアをしている@tkmbnです．


# 概要
SAMを用いたLambdaのデプロイ時にAutoPublishAliasを利用すると，うまくバージョンが発行されないときがありました．
対処法をいろいろと探っていたところ，良さそうな対処法を見つけたので，ご紹介いたします．



# なぜ書いたか
SAMを用いて，Lambdaをデプロイしたが，以下のパターンのときに，うまくバージョンが発行されませんでした．
- Layerの中身のみ変更
- Lambdaの環境変数のみの変更

手動で，バージョンを発行し，エイリアスに設定をすれば，やりたいことはできるんですが，
CanaryDeployが適用できないのが課題でした．


# 本題
## 現状の環境
- SAM テンプレートの内容
    - API Gateway
    - Lambda （複数）
        - 複数Lambda内で共通で利用するものを集めたLayer
        - 環境変数（CloudFormationのデプロイ時にParameter Storeから取得）
            - `{{resolve:ssm:<parameter name>}}` と，動的に取得するようにしています．
            - Lambdaの実行時に取得するようにしてもよいのですが，Parameter Storeの変更時からLambdaに適用されるまでの時間差がバラバラになってしまうことを防ぐため，再デプロイする形をとっています．
- デプロイパイプライン
    - CodePipelineにて設定済み
    - CodeBuild → CloudFormation(Create Changeset → Execute Changeset)
   

## 解決までの道のり
### 試行錯誤1 ： template.yaml内に `Version`と`Alias`を設定

temaplte.yamlに以下の内容を追加してみる．（Functionというリソースが定義されている前提です．）

``` diff_yaml
+  LambdaVersion:
+    Type: AWS::Lambda::Version
+    Properties:
+      FunctionName: !Ref Function
+  LambdaAlias:
+    Type: AWS::Lambda::Alias
+      Properties:
+        FunctionName: !GetAtt Function.Arn
+         FunctionVersion: $LATEST
+         Name: test
```
この状態で，パイプラインを回すと．．．

```
arn:aws:lambda:<Region>:<AccountId>:function:<FunctionName>:<Version> already exists in stack arn:aws:cloudformation:<Region>:<AccountId>:stack/<StackName>/xxxxxxxxxxxxxxxxxx
```

すでにそのバージョンは存在しているよ！という旨のエラーが出てしまい，デプロイが失敗してしまいました．
この方法では不可能ということがわかりました．

### 試行錯誤2 ： buildspec.yamlを変更
[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-autopublishcodesha256)に以下のような記述がありました．
>AutoPublishCodeSha256
新しい Lambda バージョンを公開するべきかどうかを決定するために使用される文字列値と、CodeUri の値です。
>
>このプロパティは、DeploymentPreference オブジェクトが段階的なデプロイメント (サーバーレスアプリケーションの段階的なデプロイ に説明があります) 向けに設定されている、AutoPublishAlias プロパティが設定されており、デプロイメント間で変更されない、および CodeUri プロパティが設定されており、デプロイメント間で変更されないという特徴が AWS SAM テンプレートにある場合に発生する問題に対処します。
>
>このシナリオは、Amazon Simple Storage Service (Amazon S3) の場所に保存されているデプロイパッケージが更新済みの Lambda 関数コードを使用する新しいデプロイパッケージに置き換えられたが、CodeUri プロパティがそのまま変更されていない (新しいデプロイパッケージが新しい Amazon S3 の場所にアップロードされ、CodeUri が新しい場所に変更される代わりに) という場合に発生する可能性があります。
>
>このシナリオで段階的なデプロイを正常にトリガーするには、AutoPublishCodeSha256 に一意の値を提供する必要があります。

changeset生成時，S3上のcode_uriをもとに変化があったかどうかを判定しているようです．
CloudFormationにおいて，changesetを生成する際，ビルド時にアップロードしたS3からBuild Artifactをダウンロードしていますが，CodeBuildは頭が良いので，変更がない部分はもともとのものを再利用して，アップロードがされません．
なので，ビルド時のアップロード先を強制的に変更させてあげれば，CloudFormation側は，全く違うコードだと認識するはずです．

`buildspec.yaml`
``` diff_yaml
version: 0.2

phases:
  pre_build:
    commands:
      - sam build
  build:
    commands:
-     - sam package --s3-bucket $S3_BUCKET --output-template-file 
+     - sam package --s3-bucket $S3_BUCKET --s3-prefix ${CODEBUILD_BUILD_NUMBER}  --output-template-file packaged-template.yml
artifacts:
  type: zip
  files:
    - packaged-template.yml
+ s3-prefix: ${CODEBUILD_BUILD_NUMBER}

```

ちょっとやりすぎなのかもしれないですが，sam package時に`--s3-prefix`を指定し，artifactsブロックでも`s3-prefix`をつけることで，`CodeUri`がビルド時に必ず変化するようにしています．
`${CODEBUILD_BUILD_NUMBER}`は，CodeBuildが動作する際に，毎回インクリメントし，環境変数として読み出せるものです．

この設定で，parameter storeの値のみを変更して再びパイプラインを回した結果が，以下の通りになります．

CodeBuildのコンソールログを見てみます．
```diff_shell
~~~~

[Container] 2022/09/02 00:52:52 Entering phase BUILD
[Container] 2022/09/02 00:52:52 Running command sam package --s3-bucket $S3_BUCKET --s3-prefix ${CODEBUILD_BUILD_NUMBER}  --output-template-file packaged-template.yml

Uploading to 18/986dbea6d705bae9847d4aa950c86971  262144 / 57420023  (0.46%)
~~~~
Uploading to 18/253e0fb54662100c02bb3437891a7b10  7717 / 7717  (100.00%)
# アップロード先がちゃんと<BUILD NUMBER/hash>となっている．

~~~~

[Container] 2022/09/02 00:53:27 Phase complete: BUILD State: SUCCEEDED
```

続いて，CodeBuildがアップロードした先のS3バケットも確認します．
![Screen Shot 2022-09-02 at 10.34.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285596/530e11e9-2a1f-72de-dc03-e655d496c746.png)

こちらでも，CodeBuildのビルド番号ごでフォルダが作成されていることが確認できました．


最終的な結果としては，
Lambdaにて，無事新しいバージョンが作成され，CanaryDeployも実行されました．
もちろん，Layerの中身のみの変更でも同様の結果が得られました，


# まとめ
AutoPublishAliasを利用していて，環境変数やLayerの変更でうまくデプロイされない問題の解決策をご紹介させていただきました．
- buildspec.yamlに以下を追加
    - sam package に `--s3-prefix ${CODEBUILD_BUILD_NUMBER}`
    - artifactsブロックに `s3-prefix: ${CODEBUILD_BUILD_NUMBER}`
  
これにより，CodeBuildがアップロードする先をビルドごとに必ず変化させることができ，まったく違うコードだと認識させることができました！

困っている方の手助けになれば，幸いです．
最後まで読んでいただきありがとうございした．
なにか疑問点，間違っている点，等ございましたら，コメントお願いいたします．


# 最後に宣伝

株式会社オプティマインドでは、一緒に働く仲間を大募集中です。
カジュアル面談も大歓迎ですので、気軽にお声がけください。

【エンジニア領域の募集職種】
●[ソフトウェアエンジニア](https://hrmos.co/pages/optimind/jobs/0002012)
●[QAエンジニア](https://hrmos.co/pages/optimind/jobs/0002011)
●[Androidアプリエンジニア](https://hrmos.co/pages/optimind/jobs/0002008)
●[組合せ最適化アルゴリズムエンジニア](https://hrmos.co/pages/optimind/jobs/0002013)
●[経路探索アルゴリズムエンジニア](https://hrmos.co/pages/optimind/jobs/0002014)
●[バックエンドエンジニア](https://hrmos.co/pages/optimind/jobs/0002015)
●[インフラエンジニア](https://hrmos.co/pages/optimind/jobs/0002016)
●[UXUIデザイナー](https://hrmos.co/pages/optimind/jobs/0002010)

【ビジネス領域の募集職種】
●[セールスコンサルタント](https://hrmos.co/pages/optimind/jobs/0002001)
●[オペレーションコンサルタント](https://hrmos.co/pages/optimind/jobs/0002027)

『オプティマインドってどんな会社？』については、[こちら](https://www.optimind.tech/recruit/)から
Wantedlyでも[こちらで募集中](https://www.wantedly.com/companies/optimind/projects)



# 参考文献

- [Qiitaで簡単に記事を書くためのフォーマット(@kenseitogari1 )](https://qiita.com/kenseitogari1/items/1dcf4328d54e9f2c087c)
- [AWS公式ドキュメント](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-autopublishcodesha256)