AWS Lambda vs NodeJS
AWS Lambda 使ってますか? NodeJSホストしてますか?サーバー立ててますか?
僕はローンチ当初から使っていました。
昔の名残を見る限り、なんと8.10ランタイム時代から使っていたようですね。
そんなNodeJSランタイムも今や20。 当時とかなり言語仕様も変わっています。
AWS Lambdaのランタイムも、16以前の過去のバージョンは更新はできますが新規で古いバージョンを作ることはできません。
いくらなんでももうちょっと安定していて欲しい..みたいなことも増えてきました。
ということで最近は、仕事でもPrivateでも公式のNodeランタイムではなく、Dockerランタイムを使うことが多いです。
AWS Lambda Web Adapter
さて、現在はAWS Lambdaも進化していて、Dockerランタイムが使えます。
また、かなり簡単にアプリケーションサーバーをコードそのままにホストすることができるようになっています。awslabs/aws-lambda-web-adapter という公式モジュールの登場がそのブレイクスルーポイントでした。
このモジュールは、Docker Imageにしこむだけで使えるものです。
GoのWebフレームワークecho,ginであろうがNodeJSのexpressやhonoであろうが、あらゆる一般的なアプリケーションサーバーをそのまま動かすことができます。
もはやAWS Lambdaでサーバー立てるなら必須モジュールと言えるでしょう。
詳しい説明はLambda Web Adapter でウェブアプリを (ほぼ) そのままサーバーレス化する | builders.flash
などに詳しく書いてあります。
AWS Lambda Web Adapterの利点1: コードの書き換えがいらない
前述したように、どのようなDocker Imageにも仕込めます。
このモジュールが公開されるまでは、API Gateway等のリクエストを無理やりアプリケーションサーバー用の命令に変換するモジュールなどが利用されており、若干のハック感が漂っていました。
AWS Lambda Web Adapterなら、Lambda専用のサーバー立ち上げコードをローカル開発用サーバーとは別途で書く、というような作業がいりません。
一般的なアプリケーションサーバーの起動コードで充分なのです。
FROM public.ecr.aws/docker/library/node:20-slim
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter
ENV PORT=7000
WORKDIR "/var/task"
ADD src/package.json /var/task/package.json
ADD src/package-lock.json /var/task/package-lock.json
RUN npm install --omit=dev
ADD src/ /var/task
CMD ["node", "index.js"]
のような記述で
import express from "express";
const _PORT = 7000;
const app = express();
app.listen(_PORT, () => {
console.log(`Server started on http://localhost:${_PORT}`);
});
以上のような一般的なサーバーのコードがそのまま利用できます。
普通にポート待受のコードが書けるだけでなく、通常のAWS Lambdaではうまく動作させることのできない、
SIGTERMによるGraceful Shutdown にも対応しています。
これの何が嬉しいかというと、
AWS Lambda Web Adapterの利点2: Graceful Shutdownの対応
普通にポート待受のコードが書けるだけでなく、通常のAWS Lambdaではうまく動作させることのできない、
SIGTERMによるGraceful Shutdown にも対応できます。
これの何が嬉しいかというと、例えばRDBのコネクションを利用するようなアプリを書いたときに、
ランタイム終了時にコネクションを開放するコードも書けるということです。
本来はLambdaの終了フェーズになにかをHookさせることはできませんが、Lambda拡張機能を用いると2000-3000msの猶予が許されているらしく、aws-lambda-web-adapterのSIGTERM待受はこの機能を用いて実現されているようです。
(アプリケーションサーバー意外の用途でも似たようなことがしたければ、公式サンプル がありますが、待機時もコネクション貼り続けておきたいアプリケーションはアプリケーションサーバーくらいしかないかな?とも思います)
NodeJSを動かすサンプルDocker Image
実際によく書くDocker Imageは以下です。コピペして使えます。
FROM arm64v8/node:22.3-alpine3.19 AS build-deps
WORKDIR "/var/task"
COPY package.json package-lock.json ./
RUN npm ci
# Build stage
FROM build-deps AS build
COPY . .
RUN npm run build
# Production dependencies stage
FROM arm64v8/node:22.3-alpine3.19 AS prod-deps
WORKDIR "/var/task"
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
# Final stage
FROM arm64v8/node:22.3-alpine3.19 AS runner
WORKDIR "/var/task"
RUN apk update && apk add ca-certificates tzdata && rm -rf /var/cache/apk/*
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter
ENV PORT=7000
# Copy only necessary files from previous stages
COPY --from=prod-deps /var/task/node_modules ./node_modules
COPY --from=build /var/task/build ./build
COPY --from=build /var/task/public ./public
COPY --from=build /var/task/package.json ./package.json
COPY --from=build /var/task/package-lock.json ./package-lock.json
COPY server.js ./server.js
ENV NODE_OPTIONS=--enable-source-maps
CMD ["node", "server.js"]
npm ci --omit=dev
のあたりがポイントで、本番用のDocker Image を小さくするために、dev dependencyはinstallしないようにしています。
ということでAWS Lambda Web Adapter、おすすめです。