CDKでデプロイしたLambda Functionsで `Cannot find module '@smithy/service-error-classification'`が発生したので対応した話
リテールアプリ共創部@大阪の岩田です。
とあるプロジェクトでCDKでデプロイしたNode.jsのLambda Functionsで Cannot find module '@smithy/service-error-classification'というエラーが発生したので対応しました。内容的に他にもハマる人が出てきそうなのでブログで共有します。
先に結論
CDKのバージョンv2.161.0から@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackagesという設定値が追加されました。この設定値はデフォルトでtrueとなっており、特定の条件下(※後述します)でcdk deployするとesbuildの引数に --external:@smithy/*が追加され、@smithy/service-error-classificationはバンドル対象外になります。そのためaws-xray-coreを利用している場合はCannot find module '@smithy/service-error-classificationのエラーが発生することになります。
対策ですが@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackagesをfalseに設定する...のではなくbundleAwsSDKをtrueに設定するのが良いでしょう。
環境
今回利用した各種ライブラリのバージョンは以下のとおりです。
- aws-cdk-lib: 2.172.0
- aws-xray-sdk-core: 3.10.2
- aws-xray-sdk-fetch: 3.10.2
経緯など
ここからはエラーが発生するようになった経緯と調査の過程について共有します。
aws-xray-sdk-fetchを追加
対象のLambda FunctionはBFF的な処理を行うものでした。fetchを利用した外部APIの呼び出しが多いため、オブザーバビリティを担保するために aws-xray-sdk-fetchを導入して外部APIの呼び出しをトレースできるようにした方が良いという話をし、aws-xray-sdk-fetchを導入することになりました。
以下のようなイメージです。
import { captureFetchGlobal } from 'aws-xray-sdk-fetch';
const capturedFetch = captureFetchGlobal()
...略
const res = await capturedFetch('https://dev.classmethod.jp')
これでfetchがトレース可能になりました!
cdk deployすると...動かない
トレースを仕込んだのでcdk deployすると...Lambdaが動きません。以下のようなエラーログが出力されており、 @smithy/service-error-classificationが見つからないようです。
2024-12-17T03:27:58.686Z undefined ERROR Uncaught Exception {
"errorType": "Runtime.ImportModuleError",
"errorMessage": "Error: Cannot find module '@smithy/service-error-classification'\nRequire stack:\n- /var/task/index.js\n- /var/runtime/index.mjs",
"stack": [
"Runtime.ImportModuleError: Error: Cannot find module '@smithy/service-error-classification'",
"Require stack:",
"- /var/task/index.js",
"- /var/runtime/index.mjs",
" at _loadUserApp (file:///var/runtime/index.mjs:1087:17)",
" at async UserFunction.js.module.exports.load (file:///var/runtime/index.mjs:1119:21)",
" at async start (file:///var/runtime/index.mjs:1282:23)",
" at async file:///var/runtime/index.mjs:1288:1"
]
}
aws-xray-sdk-fetchはaws-xray-sdk-coreに依存しており、aws-xray-sdk-coreは@smithy/service-error-classificationに依存しているのですが、なぜ@smithy/service-error-classificationが見つからないのか...
cdk synthで生成されるアセットに @smithy/service-error-classificationが含まれていない
原因の切り分けとして aws-xray-sdk-fetch を利用している他のプロジェクトと比較したところ、cdk synthで生成されるcdk.out/asset...略/index.jsというアセットがおかしいことが分かりました。正常に動作しているプロジェクトではアセットの中身が以下のようになっていました。
// node_modules/@smithy/service-error-classification/dist-cjs/index.js
var require_dist_cjs = __commonJS({
"node_modules/@smithy/service-error-classification/dist-cjs/index.js"(exports2, module2) {
var __defProp2 = Object.defineProperty;
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
var __getOwnPropNames2 = Object.getOwnPropertyNames;
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp2(target, "name", { value, configurable: true });
var __export2 = (target, all) => {
for (var name in all)
__defProp2(target, name, { get: all[name], enumerable: true });
};
...略
@smithy/service-error-classificationの中身がバンドルされていることが分かります。一方でエラーが発生しているプロジェクトではアセット内に上記のようなコードが出力されていませんでした。何かしらの要因で @smithy/service-error-classificationがバンドル対象外となっているようです。
externalModulesは指定していないが...??
対象のLambda FunctionをデプロイするCDKコードを確認したところ、以下のような何の変哲も無い実装でした。
const restApiFunc = new aws_lambda_nodejs.NodejsFunction(
this,
'RestApiFunc',
{
architecture: aws_lambda.Architecture.ARM_64,
runtime: aws_lambda.Runtime.NODEJS_22_X,
entry: 'index.ts',
memorySize: 1769,
timeout: Duration.seconds(29),
tracing: aws_lambda.Tracing.ACTIVE,
},
);
てっきりexternalModulesで@smithy/service-error-classificationが指定されているのかと思いましたがそうでは無さそうです。なぜ...
esbuildを手動実行するとバンドルされた
原因切り分けのためにCDKを介さず手動で./node_modules/.bin/esbuild --platform=node --bundle src/index.tsを実行したところ、今度は無事に@smithy/service-error-classificationがバンドルされました。ということはCDKからesbuildを呼び出すあたりに原因がありそうです。
ログを仕込む
CDKがesbuildをどのように呼び出してるのか特定したかったので、力技で node_modules/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.js のコードにログ出力のロジックを追加しました。
// ...略
if(!Bundling.esbuildInstallation.version.startsWith(`${ESBUILD_MAJOR_VERSION}.`))throw new Error(`Expected esbuild version ${ESBUILD_MAJOR_VERSION}.x but got ${Bundling.esbuildInstallation.version}`);
const localCommand=createLocalCommand(outputDir,Bundling.esbuildInstallation,Bundling.tscInstallation);
// 追加↓
console.error(localCommand);
// 追加↑
return(0,util_1().exec)(osPlatform==="win32"?"cmd":"bash",[osPlatform==="win32"?"/c":"-c",localCommand],{env:{...process.env,...environment},stdio:["ignore",process.stderr,"inherit"],cwd,windowsVerbatimArguments:osPlatform==="win3
node_modulesの中身なのでトランスパイルされており、編集するのに気を使いました。この状態でcdk synthを実行すると以下のような出力が得られました。
npx --no-install esbuild --bundle "...略/index.ts" --target=node22 --platform=node --outfile="...略/cdk.out/bundling-temp-...略/index.js" --external:@aws-sdk/* --external:@smithy/*
esbuildのオプションに--external:@smithy/*が設定されていることが分かります。ついでに言うと--external:@aws-sdk/* も設定されていますね。
CDKのコードを確認
大体原因にあたりがついたのでCDKのコードを確認します。
この辺りでexternalに@smithy/*を指定しているようです。
cdk.FeatureFlags.of(scope).isEnabled(LAMBDA_NODEJS_SDK_V3_EXCLUDE_SMITHY_PACKAGES)でチェックしていることからcdk.jsonの@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackagesをtrueに設定すれば良さそうですね。ここを修正して再度cdk synthすると無事に@smithy/service-error-classificationもバンドルされるようになりました🎉
コミット履歴を調べると上記の設定値は以下のコミットで追加されたようです。
元になったissueは以下です。
AWS SDKはバンドル対象外になっているのに@smithy系のパッケージがバンドルされていたため、Lambda実行環境のAWS SDKとバンドルした@smithy系の互換性の問題でエラーが発生していたそうです。
そしてこのissueを見て気付いたのですが、NodejsFunctionのデフォルト設定を使うとAWS SDKはバンドルされないんですね...
Lambdaの公式ドキュメントには以下のように記載されており、Lambda実行環境に組み込まれているAWS SDKを使うことのリスクについて示唆しています。
Control the dependencies in your function's deployment package. The AWS Lambda execution environment contains a number of libraries. For the Node.js and Python runtimes, these include the AWS SDKs. To enable the latest set of features and security updates, Lambda will periodically update these libraries. These updates may introduce subtle changes to the behavior of your Lambda function. To have full control of the dependencies your function uses, package all of your dependencies with your deployment package.
ということで @smithy系のパッケージだけでなくAWS SDKについてもバンドル対象とするようにNodejsFunctionの呼び出しを修正しました。
const restApiFunc = new aws_lambda_nodejs.NodejsFunction(
this,
'RestApiFunc',
{
architecture: aws_lambda.Architecture.ARM_64,
runtime: aws_lambda.Runtime.NODEJS_22_X,
entry: 'index.ts',
bundling: {
bundleAwsSDK: true,
},
memorySize: 1769,
timeout: Duration.seconds(29),
tracing: aws_lambda.Tracing.ACTIVE,
},
);
bundleAwsSDKがtrueの場合はdefaultExternalsが[]になるようです。
なのでbundleAwsSDKにtrueを指定している場合はcdk.jsonの@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackagesはtrue/falseどっちでも問題無さそうですね。ということで記事の冒頭に記載した「特定の条件下」というのはbundleAwsSDKが未指定もしくはfalseの場合になります。
まとめ
Cannot find module '@smithy/service-error-classification'のエラーを解消した経緯についてご紹介しました。個人的にはNodejsFunctionがデフォルトでAWS SDKをバンドルしないというのが衝撃でした。これまでデプロイしてきたLambdaのAWS SDKが意図通りのバージョンになっていない可能性が高そうなので改めてチェックしてみます。
参考
- (aws-lambda-nodejs): treat
@smithyscope identically to@aws-sdkscope if excluding packages · Issue #31610 · aws/aws-cdk - fix(lambda-nodejs): remove smithy models from bundling for AWS SDK v3… · aws/aws-cdk@19ee46d
- fix(lambda-nodejs): support bundling aws-sdk as part of the bundled c… · aws/aws-cdk@2378635
- aws_lambda_nodejs: Using Lambda Provided SDK by default in NodejsFunction leads to higher cold starts · Issue #25492 · aws/aws-cdk
- Define Lambda function handler in Node.js - AWS Lambda







