はじめに
エンタープライズ第一本部、2025 Japan AWS Jr. Champions の佐藤悠です。
最近、Node.jsのLambda LayerをTerraformを用いて追加するタイミングがあったので作業ログとして記事を作成します。
- はじめに
- Lambda Layerとは
- Node.jsのLayerの概要
- Node.jsのLayerを作成する
- 関数を作成し、Layerを用いた動作を確認する
- TerraformでLayerを作成する
- 終わりに
Lambda Layerとは
Lambda レイヤーは、補助的なコードやデータを含む .zip ファイルアーカイブです。レイヤーには通常、ライブラリの依存関係、カスタムランタイム、または設定ファイルが含まれています。
このようにLambdaで動作する主たるコードとは別に補助的な意味合いで使用するものとなります。
私が直面したユースケースでは、共通で利用できるコードとモジュールをパッケージ化する使用方法でした。
公式ドキュメントでは以下の項目にあたるでしょう。
- 複数の関数間で依存関係を共有するため。
レイヤーを作成したら、それをアカウント内の任意の数の関数に適用できます。レイヤーがない場合、個々のデプロイパッケージに同じ依存関係を含める必要があります。
確かに、毎回使用される依存関係をデプロイのたびにパッケージ化するのはスマートではありません。
今回の内容を図にすると以下のとおりです。
実際にはもっと多くのLambdaが共通部を持っていたので、Layerに切り出し、後からLayerを参照させることでデプロイプロセスが楽になりました。
※以降の内容では、説明の簡略化のために依存関係のみに着目して作業手順を構成しています。
Node.jsのLayerの概要
続いて、Node.jsをランタイムとするLayerに関する概要です。
関数にレイヤーを追加すると、Lambda はレイヤーのコンテンツをその実行環境の /opt ディレクトリに読み込みます。
/optへのパスはLambda側で通してくれるので、作成側としては適切なパスに配置したフォルダをアップロードする必要があります。
パスの指定は以下のようになります。
ランタイム | パス |
---|---|
Node.js | nodejs/node_modules |
Node.js 18 | nodejs/node18/node_modules (NODE_PATH) |
Node.js 20 | nodejs/node20/node_modules (NODE_PATH) |
Node.js 22 | nodejs/node22/node_modules (NODE_PATH) |
Node.jsのLayerを作成する
初めの概要に示したようにLambda Layerは.zipアーカイブなのでパッケージングを行う必要があります。
まずは任意のディレクトリで以下のコマンドを実行します。
mkdir -p nodejs npm install --prefix nodejs lodash
今回は後段でlodashを使用するサンプルコードを動作させたいと思うので以上のようにしています。
ここでパスを確認すると以下のようになっており、Layer作成の際のフォルダ構成を守れています。
※layer_demoフォルダで作業しています
layer_demo\nodejs\node_modules\lodash
次に以下のコマンドで作成したフォルダと配下にインストールした内容をzip化します。
zip -r layer.zip nodejs/
最後にAWS CLIコマンドを実行してLayerをAWS上に作成します。
このコマンドではLayerを「my-layer」という名前で作成し、ランタイムにNode.js22.xを指定しています。
先ほど作成したZIPファイルへのパスは適切なものに変更してください。
aws lambda publish-layer-version --layer-name my-layer --zip-file fileb://path_to/layer.zip --compatible-runtimes nodejs22.x
作成できたことをAWS マネジメントコンソールから確認します。
以下のように「my-layer」が作成できました。
関数を作成し、Layerを用いた動作を確認する
次にサンプルのLambda関数を作成してLayerの動作を確認してみます。
サンプルのアプリケーションとして、sample-apps/layer-nodejsがAWSから提供されているので、この中のindex.mjsを利用します。
import _ from "lodash" export const handler = async (event) => { var users = [ { 'user': 'Carlos', 'active': true }, { 'user': 'Gil-dong', 'active': false }, { 'user': 'Pat', 'active': false } ]; let out = _.findLastIndex(users, function(o) { return o.user == 'Pat'; }); const response = { statusCode: 200, body: JSON.stringify(out + ", " + users[out].user), }; return response; };
lodashのfindLastIndexメソッドを使い、users配列の中からuserがPatである最後の要素のインデックスを取得しています。
今回の配列では、Patは最後の要素なので、outは2になります。
関数はAWSマネジメントコンソール上から直接デプロイしました。
Layerの追加前なのでテストを実施すると、以下のようなエラーが発生します。
"errorMessage": "Cannot find package 'lodash' imported from /var/task/index.mjs"
では、先ほど作成したLayerを追加します。
以下の画像のレイヤーの追加を押下します。
続いてARNを指定することで先ほどの「my-layer」を追加します。
※ARNはLayerの画面でコピーして取得しました。
追加を押下して、「関数が正常に更新されました。」の表示を確認するとLayerの追加は完了です。
ちなみにAWS CLIでは、以下のコマンドの関数名とLayerのARNを指定することで追加可能です。
aws lambda update-function-configuration --function-name my-function --cli-binary-format raw-in-base64-out --layers "arn:aws:lambda:us-east-1:123456789012:layer:my-layer:1"
参考:Node.js Lambda 関数のレイヤーを操作する
Layerが追加できたことが確認できたので、再度関数のテストを実行します。
正常に動作することが確認でき、出力も期待通りのものが得られました。
TerraformでLayerを作成する
ここまではAWS マネジメントコンソールやAWS CLIを使用していましたが、Terraformでこれを実現する方法も記載します。
IaCでリソースを管理する際の参考になればと思います。
まず、基本的なリソース定義は以下のとおりです。
※公式ドキュメントのsampleです
resource "aws_lambda_layer_version" "example" { filename = "lambda_layer_payload.zip" layer_name = "lambda_layer_name" compatible_runtimes = ["nodejs20.x"] }
参考:Resource: aws_lambda_layer_version
同じプロジェクトの先輩が、Layerに変更があるたびにnpm installやzipコマンドを毎回実行することなくterraform applyできるコードを書いていたので紹介します。
locals { package_json = "${path.module}/nodejs/package.json" layer_dir = "${path.module}/nodejs" } resource "terraform_data" "prepare_layer" { triggers_replace = { package_json_sha = filesha256(local.package_json) } provisioner "local-exec" { interpreter = ["bash", "-c"] command = <<-EOT rm -rf "${local.layer_dir}/node_modules" npm install --prefix nodejs EOT } } data "archive_file" "layer_zip" { type = "zip" source_dir = local.layer_dir output_path = "${path.module}/layer.zip" depends_on = [terraform_data.prepare_layer] } resource "aws_lambda_layer_version" "demo_layer" { filename = data.archive_file.layer_zip.output_path layer_name = "layer-demo" compatible_runtimes = ["nodejs22.x"] }
ポイントはterraform_dataの使い方です。
package.jsonに変更があれば、それをトリガーにしてモジュールをインストールするように構成されています。
このおかげで、terraform applyをする前に手動でコマンドを実行するプロセスがなくなりますね。
毎回、npm installを実行しようとしていたので悔しかったです。(もう忘れない...)
終わりに
Layer作成の際の一助になれば、幸いです。
ここまでお読みいただきありがとうございました。
執筆:@sato.yu
レビュー:@miyazawa.hibiki
(Shodoで執筆されました)