Lambda FunctionをCLIからデプロイ – TypeScript, Nodejs, Prisma, RDS Proxyの使用

Amazon

はじめに

参考

ハマりポイント

  • Lambda関数からRDS Proxyに接続するには、LambdaをVPC内に設置する必要がある
  • Lambda関数のサブネット、セキュリティグループをRDS Proxyと合わせる必要がある
  • VPC内のLambda関数からSecrets Managerに接続するにはVPCエンドポイントかインターネットアクセスが必要になる
  • PrismaとProstgreSQLのテーブル名やENUMタイプ名は大文字・小文字や複数形まで名前をあわせる必要がある
  • Lambda関数のpermissionを設定する実行ロールには、EC2, CloudWatch, SecretsManagerの権限を付与し、Trust relationshipでは、LambdaとRDSサービスのassumeRoleの設定をする
  • Lambdaのタイムアウトとメモリがデフォルトでは3秒と128MBと小さいので、適切に増やす
  • PostgreSQLでは、datasouce urlでschemaまで指定する
  • PostgreSQLのTimestampカラムには、JavaScriptのDateをISO 8601形式に変換してから設定する

チュートリアル

アプリ作成

npm init

npm install -D @types/aws-lambda esbuild

index.ts作成

import { Context, APIGatewayProxyResult, APIGatewayEvent } from 'aws-lambda';

export const handler = async (event: APIGatewayEvent, context: Context): Promise<APIGatewayProxyResult> => {
  console.log(`Event: ${JSON.stringify(event, null, 2)}`);
  console.log(`Context: ${JSON.stringify(context, null, 2)}`);
  return {
      statusCode: 200,
      body: JSON.stringify({
          message: 'hello world',
      }),
   };
};

package.jsonの編集

{
  "name": "lambda-api",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "prebuild": "rm -rf dist && mkdir -p dist/node_modules",
    "build": "esbuild index.ts --bundle --minify --sourcemap --platform=node --target=es2020 --outfile=dist/index.js",
    "postbuild": "cp -r node_modules/.prisma dist/node_modules/.prisma && cd dist && zip -r index.zip *"
  },
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "@types/aws-lambda": "^8.10.143",
    "esbuild": "^0.23.0"
  }
}

ビルド

npm run build

実行ロールの作成

Lambda関数の作成

aws lambda create-function \
--function-name hello-world \
--runtime "nodejs20.x" \
--role 実行ロールのARN \
--zip-file "fileb://dist/index.zip" \
--handler index.handler

開発

prismaコマンドのインストール

npm install -g prisma

パッケージインストール

npm install @prisma/client
npm install aws-sdk

schema.prisma

generator client {
  provider = "prisma-client-js"
  binaryTargets = ["native", "rhel-openssl-3.0.x"]
}

datasource db {
  provider = "postgresql"
  url      = env("DATASOURCE_URL")
}

enum meeting_type {
  google
  zoom
}

enum meeting_state {
  init
  done
  canceled
}

model meetings {
  id  Int  @id @default(autoincrement())
  order_id  Int
  meeting_type  meeting_type
  created_at  DateTime
  updated_at  DateTime
  state  meeting_state
  schedule  DateTime
  link  String?
}

index.ts

import { Context, APIGatewayProxyResult, APIGatewayEvent } from 'aws-lambda';
import { PrismaClient, meeting_state, meeting_type } from '@prisma/client'

const AWS = require('aws-sdk');
const secretsManager = new AWS.SecretsManager();

export const handler = async (event: APIGatewayEvent, context: Context): Promise<APIGatewayProxyResult> => {

    const secretName = process.env.SECRET_NAME_DB_CRED;

    const secret = await secretsManager.getSecretValue({ SecretId: secretName as string }).promise();

    const dbCredentials = JSON.parse(secret.SecretString ?? '{}');
    const databaseUrl = `postgresql://${dbCredentials.username}:${dbCredentials.password}@${process.env.DATASOURCE_URL}:5432/${process.env.DB_NAME}?schema=orders`;
  
    console.log(databaseUrl);

    const prisma = new PrismaClient({
      datasources: {
        db: {
          url: databaseUrl,
        },
      },
    });  

    // console.log(prisma)
    const date = new Date();
    const isoString = date.toISOString();    
    
    const user = await prisma.meetings.create({
        data: {
          order_id: 1,
          meeting_type: meeting_type.google,
          state: meeting_state.init,
          schedule: isoString,
          link: 'aaa',
          created_at: isoString,
          updated_at: isoString
        },
      })

  return {
      statusCode: 200,
      body: JSON.stringify({
          user: user
      }),
   };
};

ビルド&デプロイ

package.json

  "scripts": {
    "prebuild": "rm -rf dist && mkdir dist && mkdir dist/node_modules",
    "build": "esbuild index.ts --bundle --minify --sourcemap --platform=node --target=es2020 --outfile=dist/index.js",
    "postbuild": "cp -r node_modules/.prisma dist/node_modules/.prisma && cd dist && zip -r index.zip *"
  },

node_modules/.prisma/client, node_modules/@prisma/clientコード生成

prisma generate

ビルド

npm run build

Lambdaコードの更新

aws lambda update-function-code \
--function-name hello-world \
--zip-file "fileb://dist/index.zip"

Lambdaの環境変数

  • DATASOURCE_URL
  • DB_NAME
  • SECRET_NAME_DB_CRED

Lambdaの実行ロール

  • Permission Policy
    • EC2
    • CloudWatch
    • SecretsManager
  • Trust relationship
    • LambdaサービスのassumeRole
    • RDSサービスのassumeRole

関連記事

カテゴリー

アーカイブ

Lang »