CloudFormationでlambdaのソースコードを更新する方法

# 背景
CloudFormation(以降、cfnと記載)を使用して、lambdaソースコードの更新を行いたい時があると思います。
cfnでは、テンプレートファイルに変更があれば更新してくれますが、lambdaのソースコードをS3にアップロードしている場合、lambdaのS3BuketやS3Keyは変化しないので、テンプレートファイルには変更が起きず、結果的にcfnでの更新ができません。
```
Resources:
  layer:
    Type: AWS::Lambda::LayerVersion
    Properties:
      Description: 'pacakge deploy test layer'
      LayerName: 'PacakgeDeployTestLambdaLayer'
      Content:
            S3Bucket: Hoge
            S3Key: Layer.zip // Layerの内容を更新してもここの値は一定

  Lambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: 'PacakgeDeployTestLambda'
      Description: 'pacakge deploy test lambda'
      Handler: handler.index
      Role: !Ref LambdaIamRoleArn
      Runtime: python3.10
      MemorySize: 128
      PackageType: Zip
      Architectures: 
        - x86_64
      Layers: 
        - !Ref layer
      Code:
          S3Bucket: Hoge
          S3Key: Source.zip // Sourceの内容を更新してもここの値は一定

```
今回はその対応策をまとめたいと思います。
# 結論
<b>aws cloudformation package</b>を使用しよう
## aws cloudformation packageコマンドとは?
cfnのテンプレートファイルで記載したファイルやディレクトリを自動でS3にアップロードしてくれて、アップロードしたS3URIをテンプレートファイルに自動で記載してくれるコマンドです。
以下のサイトが参考になるかもです。
- [AWS 更新リファレンス](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html)
- [CloudFormationでAWS Lambdaを作成・更新する際のベストプラクティス](https://qiita.com/kihoair/items/fe204fc8582946646d07)
## 具体例
例えば、以下のようなフォルダ構成を考えます。
LambdaのソースコードディレクトリやLayerディレクトリのS3BucketやS3Keyがcfnのテンプレートファイルに自動で更新されたら目的達成です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1952885/7ba702a8-1a4d-1044-ca6f-a6d36617866b.png)
目標を達成するために以下の手順を実行します
1. cfnテンプレートファイルの修正
2. aws cloudformation packageの実行
3. 生成されたテンプレートファイルの確認
4. 手順2で自動生成されたテンプレートファイルを使用してDeploy
### 1. cfnテンプレートファイルの修正
S3の値を指定いた部分を修正します
```
Resources:
  layer:
    Type: AWS::Lambda::LayerVersion
    Properties:
      Description: 'pacakge deploy test layer'
      LayerName: 'PacakgeDeployTestLambdaLayer'
      Content: ./layer // templeteファイルから見た相対パス

  Lambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: 'PacakgeDeployTestLambda'
      Description: 'pacakge deploy test lambda'
      Handler: handler.index
      Role: !Ref LambdaIamRoleArn
      Runtime: python3.10
      MemorySize: 128
      PackageType: Zip
      Architectures: 
        - x86_64
      Layers: 
        - !Ref layer
      Code: ./source // templeteファイルから見た相対パス
```
### 2. aws cloudformation packageの実行
今回はCloudShellを使用してコマンドをたたきます
そのため、テンプレートファイルやSourceディレクトリなどを1つのフォルダにまとめてZip化し、CloudShellにアップロードします。(この際、SourceディレクトリやLayerディレクトリを先にZip化する必要はありません)
CloudShellのアクションから「アップロード」を選択し、Zip化したファイル一式をアップロードします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1952885/f6a45512-c63a-5268-7e8f-ae85f5d640bd.png)
Unzipしてディレクトリに移動します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1952885/a41eac99-a0fc-f7dc-0ba2-852d8ae215e3.png)
ここで aws cloudformation packageコマンドをたたきます
```
aws cloudformation package --template-file 'package.yaml' --s3-bucket 'cfn-test-main' --s3-prefix lambda --region 'XXXXX-1' --output-template-file 'Lambda.yaml'
```
#### 各引数の意味
```
--template-file　→　S3の値を書き換えたいcfnのテンプレートファイルのパス
--s3-bucket　→　SourceやLayerなどをアップロードするS3バケット名
--s3-prefix →　S3にアップロードしたいディレクトリ.指定がなければ、S3バケット直下にアップロードされる
--region　→　バケットのリージョン?なくてもいい
--output-template-file　→　S3の値を書き換えたテンプレートファイルの名前.指定がなければ標準出力に出される
```
すると、Lambda.yamlというファイルが生成されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1952885/0c6328df-bd6b-b5e4-17ca-f6b425328f12.png)
内容を確認するために、CloudShellのアクションから「ファイルダウンロード」を選択します。
ファイルパスは以下だと思います。※Pacakgeとかは各自で異なります
```
/home/cloudshell-user/Package/lambda.yaml
```
### 3. 生成されたテンプレートファイルの確認
以下の内容のように、ContentやCodeの部分の値が変更されています。
```
Resources:
  layer:
    Type: AWS::Lambda::LayerVersion
    Properties:
      Description: pacakge deploy test layer
      LayerName: PacakgeDeployTestLambdaLayer
      Content:
        S3Bucket: cfn-test-main　// --s3-bucketで指定した値
        S3Key: lambda/f36168fedef3585c635ae45f998a6706 // 自動で割り当てられる乱数
  Lambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: PacakgeDeployTestLambda
      Description: pacakge deploy test lambda
      Handler: handler.index
      Role:
        Ref: LambdaIamRoleArn
      Runtime: python3.10
      MemorySize: 128
      PackageType: Zip
      Architectures:
      - x86_64
      Layers:
      - Ref: layer
      Code:
        S3Bucket: cfn-test-main // --s3-bucketで指定した値
        S3Key: lambda/391f85c3f6f07f7a979cbc243fea3f83 // 自動で割り当てられる乱数
```
S3バケットを確認すると指定したバケットのlambdaフォルダ内に自動で割り当てられた乱数のファイルが存在していることが分かります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1952885/62ed21dd-58c3-684a-6f56-7c4ec0ca83a2.png)
ここまでで、コマンドを使用したテンプレートファイルの生成とS3へのアップロードが確認できました。
テンプレートファイルには、lambdaのソースコード置き場の値が変更されているので、cfnを使用してlambdaが自動で更新されることができると思います。
### 4. 手順2で自動生成されたテンプレートファイルを使用してDeploy
コマンドで生成されたテンプレートファイルでlmabdaを更新してみます。
今回はテンプレートファイルをS3に置いて、AWSマネジメントコンソールでcloudformationサービスをポチポチしながらdeployしてみたい思います。
aws cloudformation packageコマンドで作成されたテンプレートファイルをS3に配置します
AWSマネジメントコンソールでcloudformationからstackを立ち上げます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1952885/efbaa4ff-c7f3-e021-a34d-eee5ff131160.png)
自動でアップロードされたsoruceコードがアップロードされていることが確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1952885/2694d227-acd6-8f07-94ac-1babc1b304c2.png)

# まとめ
今回はaws cloudformation packageコマンドを使用して、cloudformationでLambda, Lambda Layerを更新する方法を紹介しました。
今回はAWSマネジメントコンソールで更新処理をしましたが、aws cloudformation deployコマンドを使用すれば手作業をより減らして対応できるかもですね。