AWS Lambda Node.js 18ではデフォルトのAWS SDKがv3になっている(そしてv2を使い続ける方法)

AWS LambdaのランタイムでNode.js 18を選ぶとAWS SDK for JavaScriptのバージョンが3になっています。 (LambdaのランタイムとしてNode.js 18が利用できるようになったのは2022年11月なのでこの記事は今更過ぎますね。)

Lambdaではaws-sdkモジュールが内部に含まれているため、node_modulesに含めることなく、AWS SDKを利用することができます。 Node.js 16まではこのAWS SDKのバージョンが2となっていましたが、Node.js 18ではバージョン3となっています。 下に公式ブログからの情報を載せておきます。

Node.js 18.x runtime now available in AWS Lambda | AWS Compute Blog

Up until Node.js 16, Lambda’s Node.js runtimes have included theAWS SDK for JavaScript version 2. This has since been superseded by theAWS SDK for JavaScript version 3, which wasreleased in December 2020. With this release, Lambda has upgraded the version of the AWS SDK for JavaScript included with the runtime from v2 to v3.

以前からAWS SDK for JavaScriptはv2ではなく、v3を使いましょうね、と言われていたのでここで大きく変えてきたという感じでしょうか。 さらに、開発者ガイドからLambdaのNode.jsランタイムの環境を確認してみます。 Lambdaにデフォルトで含まれているSDKはNode.js 18からv3になっています。

Building Lambda functions with Node.js - AWS Lambda

v2を使い続けるには

さて、いきなりv3に変えるのは結構コードが違うこともあり、難しいのでv2を使い続けたいと思っていました。 方法があるようで、どうやらnode_modulesAWS SDK v2を含めていればv2を利用できるようです(下がその根拠です。先述の公式ブログに記載されていました。)。

If your existing Lambda functions are using the included SDK v2, then you must update your function code to use the SDK v3 when upgrading to the Node.js 18 runtime. This is the recommended approach when upgrading existing functions to Node.js 18. Alternatively, you can use the Node.js 18 runtime without updating your existing code if you deploy the SDK v2 together with your function code.

訳すと、

既存のLambda関数が内部に含まれているSDK v2を利用している場合、Node.js 18ランタイムにアップグレードするとき、SDK v3を利用するようにLambda関数のコードを更新しなければなりません。これは既存の関数をNode.js 18をアップグレードするときに推奨されるアプローチです。代わりに、関数のコードとともにSDK v2をデプロイすることで既存のコードを更新することなく、Node.js 18ランタイムを利用することができます。

v2を使い続ける方法を試してみる

Lambda Node.js 18ランタイムで、デプロイするzipファイル(のnode_modules以下)にaws-sdk v2を含めた場合、含めなかった場合にどうなるのかを実際に試してみました。

コードはGitHubにあります。 nodejs-module-labo/aws-sdk/aws-sdk-version · GitHub 動かすにはgit cloneし、npm installしてください。

AWSでLambda関数を作成しておきます。 ランタイムは当然Node.js 18とします。 このLambdaを実行するIAMロールにはIAMポリシーでLambdaのReadOnlyの権限を与えておきます。

aws-sdk v3を使ってみる

まずは、Lambda Node.js 18ランタイムの環境に含まれているaws-sdk v3を使ってみましょう。

コードを用意します。 先のGitHubにあるリポジトリnpm run zip:v3を実行します。 aws-sdk-version-v3.zipというファイルが作成されます。 このzipファイルにはaws-sdkは含まれていません

また、Lambdaで実行されるコードは以下となります(リポジトリ内のindex-v3.jsです)。 aws-sdk v3を使うコードです。

const { LambdaClient, ListFunctionsCommand } = require("@aws-sdk/client-lambda");

exports.handler = async (event) => {
    const client = new LambdaClient({ region: 'ap-northeast-1', });

    const params = {};
    const command = new ListFunctionsCommand(params);

    const data = await client.send(command);
    console.log(data);

    return;
};

Lambdaにzipファイルをアップロードすることでデプロイします。 実行してみると、成功します。

Lambda Node.js 18ランタイムの環境にはaws-sdk v3が含まれているということがわかりました。

aws-sdk v2を使うがzipファイルに含めない

次にLambda Node.js 18ランタイムには含まれていないaws-sdk v2を使ってみます。

コードを用意します。先のGitHubにあるリポジトリnpm run zip:v2-notを実行します。 aws-sdk-version-v2-notincluded.zipというファイルが作成されます。 このzipファイルにはaws-sdkは含まれていません

Lambdaで実行されるコードは以下となります(リポジトリ内のindex-v2.jsです)。 aws-sdk v2を使うコードです。

const AWS = require('aws-sdk');

exports.handler = async (event) => {
    const lambda = new AWS.Lambda({
        region: 'ap-northeast-1',
    });

    const params = {};
    lambda.listFunctions(params, function (err, data) {
        if (err) {
            console.log(err);
            return;
        }
        console.log(data);
    });
};

これをデプロイして実行すると、エラーが発生します。 aws-sdkモジュールが見つからない、という内容です。

{
  "errorType": "Runtime.ImportModuleError",
  "errorMessage": "Error: Cannot find module 'aws-sdk'\nRequire stack:\n- /var/task/index.js\n- /var/runtime/index.mjs",
  "trace": [
    "Runtime.ImportModuleError: Error: Cannot find module 'aws-sdk'",
    "Require stack:",
    "- /var/task/index.js",
    "- /var/runtime/index.mjs",
    "    at _loadUserApp (file:///var/runtime/index.mjs:996:17)",
    "    at async UserFunction.js.module.exports.load (file:///var/runtime/index.mjs:1031:21)",
    "    at async start (file:///var/runtime/index.mjs:1194:23)",
    "    at async file:///var/runtime/index.mjs:1200:1"
  ]
}

なるほど、公式情報のとおり、Lambda Node.js 18ランタイムの環境にはaws-sdk v2は含まれておらず、そのままでは動かなくなるのでaws-sdk v3に移行する必要があります。

aws-sdk v2をzipファイルに含める

さて次に解決策であるaws-sdk v2をzipファイルに含めた場合を確認してみます。

npm run zip:v2を実行するとaws-sdk-version-v2.zipというファイルが作成されます。 このzipファイルにはaws-sdk v2が含まれています。 Lambdaで実行されるコードは1つ前とおなじです(リポジトリ内のindex-v2.js)。

デプロイして実行すると、実行に成功します。 こちらも情報通り、node_modulesaws-sdk v2を含めることでソースコードを変更することなく、動作させられることが確認できました。

(おまけ)aws-sdk v2をzipファイルに含めた状態でaws-sdk v3を使う

npm run zip:v3-allを実行すると、aws-sdk-version-v3a-all.zipというファイルが作成されます。 ここではaws-sdk v2が存在する状態で、ソースコードとしてはv3を使ってみるというものになります。 競合とか変な動作をするかと思いましたが、問題なく動作しました。

まとめ

結果をまとめます。 Lambda Node.js 18ランタイムの環境で、

  • aws-sdk v3を使うとき、aws-sdkv2とv3をzipファイルに含めない -> 動作する!
  • aws-sdk v2を使うとき、aws-sdk v2をzipファイルに含めない  -> エラー
  • aws-sdk v2を使うとき、aws-sdk v2をzipファイルに含める   -> 動作する!
  • aws-sdk v3を使うとき、aws-sdk v2をzipファイルに含める    -> 動作する!

なので、Node.js 18でもaws-sdk v2を使い続けるには、aws-sdk v2をデプロイするzipファイルに含めることが必要です。 これまでよりもデプロイするzipファイルのサイズが大きくなる可能性があることに注意しましょう。

また、2020年にaws-sdk v3はリリースされています。 これを機会に利用するaws-sdkのバージョン切り替えをおこなうべきなのでしょう。