Nest+Prisma を Lambda で動かす
既に作っていたNestJSのアプリケーションをAWS Lambdaで動かすことにしました。
備忘録としてNestJS + Prisma + Lambda + Terraform での構築方法もまとめます。
結論
詰まったところ
- Prisma のエンジンのパスが Lambda 上で見つからない
- 容量が大きすぎて Lambda にデプロイできない
解決方法
- Prisma のエンジンは Lambda ようにビルドされたものを使う
- Prisma のエンジンは環境変数で指定する
- バンドルファイルを作成して Lambda にデプロイする
目次
概要
以下の図は、今回構築するアーキテクチャの概要です。
AWS S3: Lambda関数で使用するNestJSのバンドルファイルを保存します。
AWS Lambda: NestJSアプリケーションがデプロイされている実行環境です。リクエストを処理し、レスポンスを返します。
AWS API Gateway: クライアントからのリクエストを受け取り、対応するLambda関数にルーティングします。
AWS IAMロール: Lambda関数に必要な権限を付与します。
クライアント(ユーザー): ブラウザやアプリケーションからHTTPリクエストを送信します。
Terraform: 上記のAWSリソースをコードで定義・管理し、インフラのプロビジョニングや更新を自動化します。
前提条件
- AWSアカウントを持っていること
- AWS CLIとTerraformがインストールされていること
ツールのインストールと設定
AWS CLIのインストール & 設定
【AWS】aws cliの設定方法
↑ こちらの記事がとてもわかりやすいです。
# AWS CLIのインストール
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# AWS CLIのインストール確認
aws --version
# AWS CLIの設定
aws configure
Terraformのインストール
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
# インストール確認 (terraform)
terraform --version
NestJSアプリケーションの準備
こちらは、既に用意されているものを使用します。
### main.tsの編集
main.tsを以下のように編集します。
```typescript
# コードの例
import serverlessExpress from '@codegenie/serverless-express';
import { INestApplication } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { Callback, Context, Handler } from 'aws-lambda';
import { AppModule } from './app.module';
let server: Handler;
// 共通のブートストラップ関数
async function bootstrap(): Promise<Handler | INestApplication> {
const app = await NestFactory.create(AppModule);
await app.init();
const expressApp = app.getHttpAdapter().getInstance();
return serverlessExpress({ app: expressApp });
}
// Lambda ハンドラー
export const handler: Handler = async (
event: any,
context: Context,
callback: Callback,
) => {
context.callbackWaitsForEmptyEventLoop = false;
server = server ?? (await bootstrap());
return server(event, context, callback);
};
Serverless Frameworkの設定
ローカル環境で動かすために、Serverless Frameworkをインストールします。
npm install serverless serverless-offline @codegenie/serverless-express
serverless.ymlの作成
プロジェクトのルートディレクトリにserverless.ymlを作成し、以下の内容を記述します。
serverless-offlineプラグインを使用して、ローカルでの動作をサポートします。
service: nestjs-lambda-example
plugins:
- serverless-offline
provider:
name: aws
runtime: nodejs22.x
region: ap-northeast-1
environment:
IS_LAMBDA: 'true'
custom:
serverless-offline:
httpPort: 3000
lambdaPort: 3002
host: localhost
debug: true
useChildProcesses: true
allowCache: true
functions:
main:
handler: dist/main.handler
timeout: 30
events:
- httpApi:
method: '*'
path: '/{proxy+}'
payloadFormatVersion: '2.0'
ローカルでの動作確認
以下のコマンドでローカルサーバーを起動し、動作を確認します。
serverless offline
ブラウザで http://localhost:3000 にアクセスし、正常に動作していることを確認します。
curl http://localhost:3000
こちらは使用している NestJS の挙動を確認します。
Terraformによるインフラ構築
Terraform の設定ファイルを作成し、以下の内容を記述します。
プロバイダーの設定
provider "aws" {
region = "ap-northeast-1"
}
IAMロールの設定
resource "aws_iam_role" "lambda_exec" {
name = "lambda_exec_role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}]
})
}
resource "aws_iam_policy_attachment" "lambda_exec_attach" {
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
roles = [aws_iam_role.lambda_exec.name]
}
Lambda関数の設定
resource "aws_lambda_function" "nestjs_lambda" {
function_name = "nestjs-function"
role = aws_iam_role.lambda_exec.arn
handler = "dist/main.handler"
runtime = "nodejs20.x"
filename = "path/to/your/nestjs/lambda.zip"
source_code_hash = filebase64sha256("path/to/your/nestjs/lambda.zip")
environment {
variables = {
NODE_ENV = "production"
IS_LAMBDA = "true"
PRISMA_QUERY_ENGINE_LIBRARY = "/var/task/node_modules/@prisma/engines/libquery_engine-rhel-openssl-1.0.x.so.node"
}
}
}
API Gatewayの設定
resource "aws_apigatewayv2_api" "nestjs_api" {
name = "nestjs-api"
protocol_type = "HTTP"
}
resource "aws_apigatewayv2_integration" "nestjs_integration" {
api_id = aws_apigatewayv2_api.nestjs_api.id
integration_type = "AWS_PROXY"
integration_uri = aws_lambda_function.nestjs_lambda.invoke_arn
payload_format_version = "2.0"
}
resource "aws_apigatewayv2_route" "nestjs_route" {
api_id = aws_apigatewayv2_api.nestjs_api.id
route_key = "$default"
target = "integrations/${aws_apigatewayv2_integration.nestjs_integration.id}"
}
resource "aws_lambda_permission" "api_gw" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.nestjs_lambda.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.nestjs_api.execution_arn}/*/*"
}
Terraformの実行
Terraformの初期化、プラン作成、適用を行います。
terraform init
terraform plan
terraform apply
デプロイと動作確認
Lambda関数へのデプロイ
NestJSアプリケーションをビルドし、Lambda関数にデプロイします。
package.jsonに以下のスクリプトを追加します。
// package.json
"scripts": {
"build": "nest build",
"start": "nest start",
"esbuild": "node esbuild.config.js",
"zip": "zip -r lambda.zip build"
},
esbuild.config.jsを作成し、以下の内容を記述します。
const { build } = require('esbuild');
const { copy } = require('esbuild-plugin-copy');
const { esbuildDecorators } = require('esbuild-decorators');
build({
entryPoints: ['src/main.ts'], // エントリーポイントを指定
bundle: true,
platform: 'node',
target: 'node20',
outfile: 'build/main.js',
external: ['aws-sdk',
'class-transformer',
'class-validator',
'@nestjs/websockets',
'@nestjs/microservices',
'@nestjs/websockets/socket-module',
'@nestjs/microservices/microservices-module'
],
plugins: [
copy({
// prismのエンジンをコピー
assets: [
{
from: './node_modules/.prisma/**',
to: './node_modules/.prisma', // ビルド先にコピー
},
{
from: './node_modules/@prisma/**',
to: './node_modules/@prisma', // ビルド先にコピー
},
],
verbose: true,
}),
esbuildDecorators(),
],
}).catch(() => process.exit(1));
npm run esbuild
npm run build
zip -r lambda.zip .
lambda.zipファイルをTerraform設定ファイルで指定したパスに配置します。
動作確認
API Gatewayのエンドポイントにリクエストを送信し、正常にレスポンスが返ってくることを確認します。
# コードをコピーする
curl https://{api_id}.execute-api.ap-northeast-1.amazonaws.com/
Prismaの対応
Prismaを使用している場合、Lambda環境でのPrismaエンジンのパスを指定する必要があります。環境変数に以下を追加します。
environment {
variables = {
PRISMA_QUERY_ENGINE_LIBRARY = "/var/task/node_modules/@prisma/engines/libquery_engine-rhel-openssl-1.0.x.so.node"
}
}
まとめ
以上で、NestJSアプリケーションをAWS Lambda上で動作させ、Terraformを用いてインフラを構築・管理する方法を記載しました。
当初、Prismaのエンジンのパスが見つからないという問題に頭を抱えました。
いろんな解決方法がありそうでしたが、私がうまく行ったのは、環境変数に値をいれることでした。
今回の実装のついでに、Terraform でのAWSリソースの作成について知見を深めることもできたので良かったと思います。
参考資料
- NestJS公式ドキュメント - サーバーレス
- Terraform公式ドキュメント
- AWS Lambda - Prisma対応