LambdaバージョンをAWS CDKで構築する際にハマったエラー

## 概要

Lambda functionとそのバージョンの発行をAWS CDKにて構築する際に「Lambdaコード更新と発行されるバージョン番号の関係」を正しく理解できず、エラーに悩みました。

そこで、AWS CDKでLambda functionとそのバージョンを発行する場合の挙動について検証しました。

本記事では、これらの検証結果とその解説を記述しています。

## サンプルコード

以下GitHubにて公開しています。

[YoshinoriSatoh/o-aws-lambda-version-cdk](https://github.com/YoshinoriSatoh/o-aws-lambda-version-cdk)

## Lambda Version

Lambda versionについては、以下参考記事でも説明されています。

[https://qiita.com/quotto/items/4c364074edc69cb67d70](https://qiita.com/quotto/items/4c364074edc69cb67d70)

また、公式ドキュメントは以下です。

[https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-versions.html](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-versions.html)

## 要点

先に要点を記述します。  

1. Lambda functionには、常に最新バージョン=$LATEST が存在する
2. Lambda functionコードを更新すると、$LATEST も合わせて更新される
3. 新規バージョンを発行すると、その内容は $LATEST のものとなる
4. 新規バージョンは、"$LATEST" と "最後に発行されたバージョン" との間でコード差分がある場合のみ発行できる
5. バージョン番号は 1 から始まり、新規バージョン発行時にインクリメントされていく

上記を図示すると以下のイメージです。

![lambda version (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103461/62286501-d71a-6f4a-a8c2-170e13620d98.png)

## AWSコンソールから操作時の検証

理解のし易さのため、先にAWSコンソールから手動でLambda functionを作成し、バージョン発行する際の挙動を見てみます。

手動で作成直後のLambda function です。

最新バージョンである "$LATEST" だけがバージョンとして存在しています。

画像では作成してからスクショを撮るまでに時間が空いてしまったため、$LATESTの最終更新日時は2時間前となっています。

![ 2020-01-31 21.10.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103461/6460653f-bb75-f749-fd8c-a249516ab4ef.png)


関数コードを変更して「保存」します。

    exports.handler = async (event) => {
        // TODO implement
        const response = {
            statusCode: 200,
            body: JSON.stringify('Change 1'), <-- 変更
        };
        return response;
    };

$LATESTの更新時刻が更新されました。

<img width="303" alt=" 2020-01-31 21.31.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103461/b7df90c3-cf91-741b-0a93-b93ad6d02535.png">

バージョンを発行してみます。

<img width="832" alt=" 2020-01-31 21.35.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103461/2d3b0165-db2c-96f6-9230-b4a6bac02ce5.png">

バージョン１が発行されました。

<img width="306" alt=" 2020-01-31 21.35.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103461/90257d31-b819-cd15-b63d-25f7cdb902c7.png">


コードを変更せず、さらに新しいバージョンを発行しようとすると怒られます。

「"$LATEST" と "最後に発行されたバージョン"との間でコード差分がない」ためです。

<img width="828" alt=" 2020-01-31 21.42.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103461/f378ed28-77b3-ec35-3187-29a2d7d51f7e.png">
この状態で、コードを変更して「保存」してみます。

$LATESTの更新時刻が更新されています。

<img width="304" alt=" 2020-01-31 21.46.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103461/e2264432-6509-759b-a0ec-aa45622f6895.png">

これで、「"$LATEST" と "最後に発行されたバージョン"との間でコード差分がある」状態になりました。

新しいバージョンを発行できます。

<img width="302" alt=" 2020-01-31 21.49.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103461/a98dd666-f49a-a1e3-80f0-1b7e643b2f2f.png">

Lambda function versionの挙動は、概ね以上のようになります。  

AWSコンソールからやってみると、全く難しい概念ではないということがわかると思います。

## CDKによる構築

CDKを使用する場合でも、上記までのことを正しく理解していれば迷うことなく構築できるはずです。

### CDKコードとその挙動

関数コードに変更がある度にバージョンを発行するCDKコードとその挙動を紹介します。

    export class OAwsLambdaVersionCdkStack extends cdk.Stack {
      constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
    
        const codePath = path.join(__dirname, 'functions/api');
        const fn = new lambda.Function(this, 'CdkLambdaFunction', {
          functionName: `cdk-lambda-function`,
          runtime: lambda.Runtime.NODEJS_12_X,
          handler: 'index.handler',
          code: lambda.Code.fromAsset(codePath),
        });
        fn.addVersion(sha256(`${codePath}/index.js`)); // <-- この行で新バージョンを発行
      }
    }

上記のCDKコードをデプロイすると、以下のように、Lambda functionとそのバージョン１が作成されます。

<img width="563" alt=" 2020-01-31 22.37.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103461/f434d8cf-d764-ceba-4b02-04d495f3fa8f.png">


2回目以降のデプロイでは、

「lambda functionコードの内容に変更があった場合にのみ、新しいバージョンを発行する」という動作になります。

関数コードを変更して再度デプロイしてみます。

<img width="559" alt=" 2020-01-31 22.43.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103461/e28ee8bf-8364-8179-5272-acafb6541c43.png">


関数コードの変更により$LATESTが更新され、更に$LATESTが"最後に発行されたバージョン"であるバージョン1と差分がある状態になったため、新しいバージョンが発行されています。

### バージョン発行部分の解説

さて、lambda functionのバージョンを発行する部分ですが、以下のような記述をしています。

    fn.addVersion(sha256(`${codePath}/index.js`)); // <-- この行で新バージョンを発行

[API Reference](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html#add-wbr-versionname-codesha256-description-provisionedexecutions-asyncinvokeconfig)からaddVersionの仕様を確認すると、第一引数(必須)には一意の識別子を指定する必要があります。

<img width="572" alt=" 2020-01-31 23.00.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103461/8b4d0eaa-6f5d-36e3-6c14-0939580a2ecb.png">


「バージョン番号を指定」ではなく、「バージョンに紐づく一意の識別子」を指定です。

上記では「関数コードのSHA256ハッシュ値」を指定することで、関数コードの内容に応じて異なり、かつ関数コードに紐づく識別子を生成しています。

この指定によって「関数コードを変更する度に新バージョンを発行する」という挙動となります。

### バージョン識別子を個別に指定する場合に発生する可能性があるエラー

必ずしも関数コードの変更に応じてバージョンを発行したい場合だけではないと思います。

しかし、addVersionに指定する識別子を個別に設定する場合、気をつけていないと、以下２パターンのエラーが起こり得ます。

- 「"$LATEST" と "最後に発行されたバージョン"との間でコード差分がない」のに新バージョンを発行しようとしてエラーになる
- 「"$LATEST" と "最後に発行されたバージョン"との間でコード差分がある」のに既に発行済みのバージョンに紐づく識別子を指定してしまいエラーになる

先ほどの行を以下のように変更した場合について考えてみます。

    fn.addVersion('1'); // <-- バージョン１を発行したい

 これは、最初のデプロイでは問題なくバージョン1が発行されます。

<img width="646" alt=" 2020-01-31 23.20.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103461/875a5b2e-af17-c438-5d41-030431477118.png">

この状態で、関数コードが変更されていないのに、バージョン識別子をうっかり更新してしまった場合、

    fn.addVersion('2'); // <-- コードが変更されていないのにバージョン2にしてしまった

以下のようなエラーになります。

    1/3 | 2:29:21 PM | CREATE_FAILED        | AWS::Lambda::Version  | CdkLambdaFunction/Version2 (CdkLambdaFunctionVersion3FF20C5FC) A version for this Lambda function exists ( 1 ). Modify the function to create a new version.
            new Version (/home/vsonline/workspace/o-aws-lambda-version-cdk/node_modules/@aws-cdk/aws-lambda/lib/lambda-version.ts:137:21)

また、関数コードが変更された場合に、過去のバージョンを指定してしまっても、同じようなエラーになります。

自分でバージョン識別子を個別に指定する場合は、このような「＄LATESTとの食い違い」に注意する必要がありそうです。

## まとめ

Lambda functionとバージョン発行の挙動を検証し、AWS CDKにて構築する際にエラーとなる場合について解説しました。

関数コードの変更による、$LATESTと新バージョン発行の挙動について、正しく理解できていれば、CDKデプロイ時のエラーに悩むこともないはずです。

今回のCDKコードはサンプル的なものなので、バージョンの扱いにはより良い方法もあるかと思いますが、参考になれば幸いです。