💭

NestJSをLambda上に乗せたい話

2022/10/05に公開

NestJSをLambdaに載せたい

いくつかの批判的意見はありつつもメリットが上回りそうかなと思っているので簡単にメリデメと動かし方について整理しておきます。最悪ECSへの移植することになるかもしれませんが、NestJSで作っておけば比較的容易に済むかと。

概要

先達はいるのか

いっぱいいます。一見奇抜なアイデアにも見えますが、一定の有効性は保証できそうです。

想定されるメリット

想定されるメリットを整理しておきます。このあたりが挙げられるでしょう。

  • Lambda関数が一つになるので管理やデプロイが手軽
  • GETやPOSTなどのHTTPメソッドの分岐をLambda関数内ではなく、フレームワークのルーティング機能を活用して実現できる(コードの見通しが良くなる(かも)
  • Express用のプラグインが使える(エコシステムが活かせる)

想定されるデメリット

また、デメリットや批判としては以下が挙げられますが、一応の反論もできそうです。

  • Hackyな構成ではあるし、ECSに載せるほうが良いのでは?同時実行数の問題もある。
    -> Lambdaであればゼロスケール できるのでコストメリットがある
    -> フレームワーク使って実装するので、ECSへの移行も比較的容易に可能なはずなので、最悪それで対応できる。
  • 関数ごとにAWS上でポリシー設定が出来ないのはイマイチでは?
     -> Guardなどでロール別認可処理を加えれば、要件次第だが一定カバーは可能かと。

NestJS公式はなんと言っている?

推奨も否定もしていなそうだが、動かし方やコールドスタート対策などは書いている
https://docs.nestjs.com/faq/serverless#serverless

動かし方

エッセンスだけメモしておきます。

必要なライブラリ

本番でも必要なものは以下になります。

  • @vendia/serverless-express
  • aws-lambda

また、開発時に便利なので以下を入れとくと良いかと(AWS SAM使っても別に変わらないのでそこは好みで)

  • serverless
  • serverless-offline: ローカルでのテスト用

実装や設定

公式に乗っ取ればそのまま動かせるので基本的にはそれを見てください。一応多少端折りつつ紹介だけはしておきます。

serverless.yamlに以下の設定の追加が必要となります(一部略)。ポイントはmethodANY, path{proxy+}を指定して、あらゆるリクエストを受け入れられるようにするところです。

...
plugins:
  - serverless-offline

functions:
  XXXX:
    handler: dist/main.handler
    events:
      - http:
        method: ANY
        path: /
      - http:
        method: ANY
        path: '{proxy+}'

以下は完全に公式ドキュメントからのコピペですが、以下のようにmain.tshandler関数を追加する必要があります(上のdist/main.handelrに該当するものですね)。

import { NestFactory } from '@nestjs/core';
import serverlessExpress from '@vendia/serverless-express';
import { Callback, Context, Handler } from 'aws-lambda';
import { AppModule } from './app.module';

let server: Handler;

async function bootstrap(): Promise<Handler> {
  const app = await NestFactory.create(AppModule);
  await app.init();

  const expressApp = app.getHttpAdapter().getInstance();
  return serverlessExpress({ app: expressApp });
}

export const handler: Handler = async (
  event: any,
  context: Context,
  callback: Callback,
) => {
  server = server ?? (await bootstrap());
  return server(event, context, callback);
};

tsconfig.jsonに以下の追加が必要です。
(参考: TypeScriptのesModuleinteropフラグを設定してCommonJSモジュールを実行可能とする

"esModuleInterop": true

あとはビルドして $npx serverless offlineとすればlocalhost:3000/devのエンドポイントで挙動が確認できます($ sls deployでsandbox環境へデプロイしてしまってもいいかと)

結論

とても有効なはず。

Discussion