🛖

Lambda Web Adapter でExpressアプリをサーバーレス化してみた

に公開

実際に手を動かして学ぶAWS Lambda + Express

はじめに

AWS Lambda Web Adapter(LWA)を使って、ExpressアプリをほぼそのままLambdaで動かしてみました。従来のサーバーレス開発では、Lambda固有の書き方を覚える必要がありましたが、LWAを使えば既存のWebアプリケーションをほとんど変更せずにサーバーレス化できます。

この記事では、新規でExpressアプリを作成し、Lambda Web Adapterを使ってAWS Lambdaにデプロイするまでの手順を詳しく解説します。

Lambda Web Adapterとは

Lambda Web Adapterは、AWS公式が提供するOSSで、VM やコンテナ用に実装されたウェブアプリを、ほとんどそのまま Lambda でも動かせるようにするツールです。

くわしくはこちら

https://aws.amazon.com/jp/builders-flash/202301/lambda-web-adapter/

従来の課題:

  • Lambda固有のハンドラー関数の実装が必要
  • フレームワークごとに個別のアダプターが必要
  • Lambda専用のコードはローカルやECSで再利用できない

LWAの利点:

  • たった1行の追加でLambda対応
  • フレームワーク非依存
  • 同じコンテナイメージでローカル・ECS・Lambda全てで動作

プロジェクトの作成

まず、新しいNode.jsプロジェクトを作成します。

mkdir lambda-express-hello
cd lambda-express-hello
npm init -y

package.jsonの設定

{
  "name": "lambda-express-hello",
  "version": "1.0.0",
  "description": "Express app with Lambda Web Adapter",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "nodemon": "^3.0.2"
  },
  "engines": {
    "node": ">=18"
  }
}

依存関係をインストール:

npm install

Expressアプリケーションの実装

server.jsファイルを作成します。

const express = require('express');
const app = express();

// ポート番号はLWAのデフォルト(8080)を使用
const port = process.env.PORT || 8080;

// JSONパーサーを有効化
app.use(express.json());

// ヘルスチェック(LWAが使用)
app.get('/', (req, res) => {
  res.status(200).json({
    message: 'Hello World from Lambda Web Adapter!',
    timestamp: new Date().toISOString(),
    environment: process.env.NODE_ENV || 'development'
  });
});

// 複数のエンドポイント例
app.get('/api/hello', (req, res) => {
  const name = req.query.name || 'World';
  res.json({
    message: `Hello, ${name}!`,
    from: 'Express on Lambda'
  });
});

app.get('/api/info', (req, res) => {
  res.json({
    service: 'Lambda Express API',
    version: '1.0.0',
    node_version: process.version,
    aws_region: process.env.AWS_REGION || 'N/A',
    lambda_function_name: process.env.AWS_LAMBDA_FUNCTION_NAME || 'local'
  });
});

app.post('/api/echo', (req, res) => {
  res.json({
    message: 'Echo endpoint',
    received_data: req.body,
    headers: req.headers
  });
});

// 404ハンドラー
app.use('*', (req, res) => {
  res.status(404).json({
    error: 'Not Found',
    path: req.originalUrl,
    method: req.method
  });
});

// エラーハンドラー
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({
    error: 'Internal Server Error',
    message: err.message
  });
});

app.listen(port, () => {
  console.log(`Express server running on port ${port}`);
  console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
});

ローカルでのテスト

npm start

以下のエンドポイントでテスト:

Docker化(Lambda Web Adapter対応)

ここが最重要ポイント! LWAの導入はDockerfileたった1行追加するだけです。

FROM node:22-alpine

# Lambda Web Adapterをインストール(この1行だけ追加!)
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.0 /lambda-adapter /opt/extensions/lambda-adapter

# 作業ディレクトリを設定
WORKDIR /usr/src/app

# パッケージファイルをコピーして依存関係をインストール
COPY package*.json ./
RUN npm ci --omit=dev && \
    npm cache clean --force

# アプリケーションコードをコピー
COPY server.js ./

# ポート8080を公開(LWAのデフォルト)
EXPOSE 8080

# 本番環境に設定
ENV NODE_ENV=production

# アプリケーションを開始
CMD ["node", "server.js"]

.dockerignoreも作成:

node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.DS_Store

Dockerでのローカルテスト

# イメージをビルド
docker build -t lambda-express-hello .

# コンテナを実行
docker run -p 8080:8080 lambda-express-hello

同じエンドポイントでテストして、Docker環境でも正常に動作することを確認します。

AWSへのデプロイ

1. ECRリポジトリの作成

AWSコンソールでAmazon ECRを開き:

  1. 「リポジトリを作成」をクリック
  2. リポジトリ名:lambda-express-hello
  3. 「リポジトリを作成」をクリック

2. ECRへのイメージプッシュ

# AWS CLIでECRにログイン
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin YOUR_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com

# イメージをビルド
docker build -t lambda-express-hello .

# タグ付け
docker tag lambda-express-hello:latest YOUR_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-express-hello:latest

# プッシュ
docker push YOUR_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-express-hello:latest

よくあるトラブル:プロキシエラー

Docker Desktopでプロキシが設定されている場合、以下のエラーが発生することがあります:

proxyconnect tcp: dial tcp 192.168.65.1:3128: i/o timeout

解決方法:

  1. Docker Desktopの設定を開く
  2. 「Resources」→「Proxies」
  3. 「Bypass proxy settings for these hosts & domains」に*.amazonaws.comを追加
  4. または一時的にプロキシを無効化してプッシュを実行

3. Lambda関数の作成

AWSコンソールでAWS Lambdaを開き:

  1. 「関数の作成」をクリック
  2. 「コンテナイメージ」を選択
  3. 関数名:lambda-express-hello
  4. コンテナイメージURI:先ほどプッシュしたECRイメージのURI
  5. アーキテクチャ:x86_64
  6. 「関数の作成」をクリック

4. Lambda設定の調整

基本設定:

  • タイムアウト:30秒(デフォルトの3秒では不足の可能性)
  • メモリ:512MB(必要に応じて調整)

環境変数(必要に応じて):

  • AWS_LWA_PORT:8080
  • NODE_ENV:production

5. Function URLの設定

  1. Lambda関数の「設定」タブ
  2. 左メニューの「関数 URL」
  3. 「関数 URL を作成」をクリック
  4. 認証タイプ:NONE(パブリックアクセス)
  5. CORS設定(必要に応じて設定)
  6. 「保存」をクリック

テストと動作確認

Function URLが生成されたら、以下のエンドポイントをテスト:

  • https://YOUR_FUNCTION_URL.lambda-url.ap-northeast-1.on.aws/
  • https://YOUR_FUNCTION_URL.lambda-url.ap-northeast-1.on.aws/api/hello?name=Lambda
  • https://YOUR_FUNCTION_URL.lambda-url.ap-northeast-1.on.aws/api/info

結果

Lambda Web Adapterを使用することで:

✅ 成功したポイント:

  • Dockerfileへの1行追加だけでLambda対応完了
  • 同じコードでローカル・Docker・Lambdaで動作
  • 既存のExpress知識をそのまま活用
  • コールドスタートも許容範囲内の速度

📊 パフォーマンス:

  • 初回実行(コールドスタート):約2-3秒
  • 2回目以降(ウォームスタート):約100-300ms
  • メモリ使用量:約50-80MB

監視とトラブルシューティング

CloudWatch Logsでの確認項目:

  • アプリケーションの起動ログ
  • Lambda Web Adapterの動作ログ
  • リクエスト処理のログ

よくある問題と解決法:

  1. タイムアウトエラー → Lambda関数のタイムアウトを延長
  2. メモリ不足 → メモリ割り当てを増加
  3. ポート設定エラーAWS_LWA_PORT環境変数を確認

まとめ

Lambda Web Adapterは、既存のWebアプリケーションをサーバーレス化する際の強力なツールです。特に:

こんな場面におすすめ:

  • 既存のExpressアプリをサーバーレス化したい
  • Lambda特有の書き方を覚える時間がない
  • 同じコードベースを複数の環境で動かしたい
  • プロトタイプを素早くサーバーレス化したい

注意すべき点:

  • コールドスタート時間はネイティブLambdaより長い
  • メモリ使用量はやや多め
  • 大量の同時リクエストには従来のコンテナの方が適している場合もある

Lambda Web Adapterにより、サーバーレスへの移行ハードルが大幅に下がりました。ぜひお手持ちのアプリケーションで試してみてください!

Discussion