nato243 weblog.n-jitter brand iconweblog.n-jitter
テクノロジー

AWS Lambda x DockerでNodeJSサーバーを動かす

2024.09.09
NodeDockerAWS
AWS Lambda x DockerでNodeJSサーバーを動かす アイキャッチ

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、おすすめです。