0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Nest+Prisma を Lambda で動かす

Posted at

Nest+Prisma を Lambda で動かす

既に作っていたNestJSのアプリケーションをAWS Lambdaで動かすことにしました。
備忘録としてNestJS + Prisma + Lambda + Terraform での構築方法もまとめます。

結論

詰まったところ

  • Prisma のエンジンのパスが Lambda 上で見つからない
  • 容量が大きすぎて Lambda にデプロイできない

解決方法

  • Prisma のエンジンは Lambda ようにビルドされたものを使う
  • Prisma のエンジンは環境変数で指定する
  • バンドルファイルを作成して Lambda にデプロイする

目次

概要

以下の図は、今回構築するアーキテクチャの概要です。

image.png

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のインストール

公式: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.tf
provider "aws" {
  region = "ap-northeast-1"
}

IAMロールの設定

iam.tf
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関数の設定

lambda.tf
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の設定

api_gateway.tf
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対応
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?