😭

Drizzle StudioをAWS Lambdaで動かす試みの失敗記録

2023/08/12に公開

モチベーション

  • 以前からLambda Web Adapterという技術が気になっており、試しておきたいと思っていた
  • 業務でDBを操作するダッシュボード系ツールを作る機会が増え、REST API/UIの実装をその都度、行うことにしんどさを感じ始めた

Drizzle Studioをサーバ上で動かせれば、楽できるのでは?試してみよう

https://zenn.dev/mktbsh/scraps/0178910e721d2b
https://aws.amazon.com/jp/builders-flash/202301/lambda-web-adapter/?awsf.filter-name=*all
https://github.com/awslabs/aws-lambda-web-adapter

まとめ(2023/08/12時点)

  • Lambda Web Adapterを利用してLambda関数として実行するところまでは成功
  • esbuildが動かない
    • Lambda関数実行時にDrizzle ORMがesbuildを利用して諸々をビルドしている(はず)

Lambda上で動かないだけなので、EC2/ECSであれば問題なく動かせる。この辺はLambda Web Adapterのいいところだと思う。

Drizzle Studioとは

Drizzle ORMというTypeScript向けのORMを提供するdrizzle-teamが提供する、ローカル環境向けのSQL Explorer

  • Prisma Studio
  • DBeaver
  • MySQL Workbench

のようなGUIツールの類似製品

https://orm.drizzle.team/drizzle-studio/overview

ローカル環境で動かせるようにする

npmプロジェクト作成

npm init -y
package.json
{
  "name": "drizzle-studio-on-lambda",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {},
  "license": "ISC"
}

Drizzle ORMの導入(PostgreSQLを利用)

今回は手っ取り早くSupabase(PostgreSQL)でDBを構築しています。

※ Drizzle Studioを起動しようとすると、"pg" パッケージがないと怒られるので併せてインストールしておきます。

npm i drizzle-kit drizzle-orm postgres pg

https://github.com/drizzle-team/drizzle-orm/issues/842

スキーマを定義する

mkdir schema
touch schema/todos.js
schema/todos.js
import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
 
// とりあえず適当に
export const todos = pgTable("todos", {
  id: serial("id").primaryKey(),
  title: text("title").notNull(),
  createdAt: timestamp("created_at").notNull().defaultNow(),
});

drizzle.config.jsを作成する

touch drizzle.config.js
drizzle.config.js
export default {
  schema: "./schema/*",
  out: "./drizzle",
  driver: 'pg',
  dbCredentials: {
    connectionString: "postgresql://postgres:[supabase-passwd]@db.[id].supabase.co:5432/postgres",
  }
}

Drizzle Studioを起動してみる

起動して、DBのデータを参照・更新できればOK。

npx drizzle-kit studio --port 3000

Dockerで動かせるようにする

Drizzle Studioはlocalhost(127.0.0.1)をリッスンしており、そのままだとホスト(ローカルPC)からリクエストが通らないので、ひと工夫が必要。
残念ながら、studioコマンドでhostの上書きはできなかった。

dockerignoreを作成する

touch .dockerignore
node_modules

# 後述します。(AWS CDK関連のファイルを除外したいので入れています。)
cdk

Dockerfileを作成する

ARG NODE_VERSION=18

FROM node:${NODE_VERSION} as builder

ENV NODE_ENV production
WORKDIR /build
COPY package*.json ./
RUN npm ci
COPY . ./

FROM amazon/aws-lambda-nodejs:${NODE_VERSION}

# lambda-web-adapterを使うために必要
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.7.0 /lambda-adapter /opt/extensions/lambda-adapter

EXPOSE 3000
WORKDIR /app

COPY --from=builder /build/package.json ./
COPY --from=builder /build/drizzle.config.js ./
COPY --from=builder /build/schema ./schema
COPY --from=builder /build/node_modules ./node_modules

# Express.jsをリバースプロキシとして利用する(0.0.0.0:3000 -> 127.0.0.1:5173
COPY --from=builder /build/proxy.js ./

ENV PATH /app/node_modules/.bin:$PATH

ENTRYPOINT ["node"]
CMD ["proxy.js"]

Docker内のDrizzle Studioにリクエストが通らない問題を解決する

  1. Express.jsとhttp-proxy-middlewareをインストールする
npm i express http-proxy-middleware
  1. proxy.jsを作成
touch proxy.js
  1. Expressによるリバースプロキシを実装する
proxy.js
import express from "express";
import { createProxyMiddleware } from "http-proxy-middleware";

const EXPRESS_PORT = 3000;
const DRIZZLE_STUDIO_PORT = 5173;

const app = express();

app.use("/", createProxyMiddleware({
    target: `http://127.0.0.1:${DRIZZLE_STUDIO_PORT}`,
    changeOrigin: true,
}));

// hostを0.0.0.0で指定して起動する
app.listen(EXPRESS_PORT, "0.0.0.0", () => {
  console.log("Proxy server is running on http://0.0.0.0:3000");
});

  1. proxy.js内で別プロセスを使ってDrizzle Studioを起動させる

Dockerfileで CMD ["npm", "start"] を指定して動けばよかったのですが、残念ながらconcurrentlyを使ったりしても動かせなかったので、child_processを使いました。

proxy.js
import express from "express";
import { createProxyMiddleware } from "http-proxy-middleware";
+ import { exec } from 'child_process';

const EXPRESS_PORT = 3000;
const DRIZZLE_STUDIO_PORT = 5173;

+ const CMD = `drizzle-kit studio --port ${DRIZZLE_STUDIO_PORT} --config drizzle.config.js`;

+ // 別プロセスで起動する。非同期実行を行い、後続のExpressの起動には影響しないようにする
+ exec(CMD, (err, stdout, stderr) => {
+   console.log(err ? stderr : stdout)
+ });

const app = express();

app.use("/", createProxyMiddleware({
    target: `http://127.0.0.1:${DRIZZLE_STUDIO_PORT}`,
    changeOrigin: true,
}));

// hostを0.0.0.0で指定して起動する
app.listen(EXPRESS_PORT, "0.0.0.0", () => {
  console.log("Proxy server is running on http://0.0.0.0:3000");
});

AWS CDKを使ってデプロイする

  1. 前提
  • 認証情報などの設定は済んでいる想定で書いてます。

1. CDKプロジェクトの作成

mkdir cdk && cd cdk
npx aws-cdk@2 init app --language typescript
npm install @aws-cdk/aws-apigatewayv2-integrations-alpha @aws-cdk/aws-apigatewayv2-alpha

2. CDKの設定

cdk/lib/cdk-stack.ts
import * as cdk from 'aws-cdk-lib';
import { HttpApi } from '@aws-cdk/aws-apigatewayv2-alpha';
import { HttpLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations-alpha';
import { Construct } from 'constructs';

export class DrizzleStudioStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const platform = cdk.aws_ecr_assets.Platform.LINUX_AMD64;

    const handler = new cdk.aws_lambda.DockerImageFunction(this, 'Handler', {
      code: cdk.aws_lambda.DockerImageCode.fromImageAsset('../', {
        platform,
      }),
      memorySize: 256,
      timeout: cdk.Duration.seconds(30),
    });

    new HttpApi(this, 'Api', {
      apiName: 'DrizzleStudioDemo',
      defaultIntegration: new HttpLambdaIntegration("Integration", handler),
    });
  }
}

3. bin/cdk.tsを修正する

bin/cdk.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
- import { CdkStack } from '../lib/cdk-stack';
+ import { DrizzleStudioStack } from '../lib/cdk-stack';

const app = new cdk.App();
-  new DBStudio(app, 'CdkStack', {
+  new DrizzleStudioStack(app, 'DrizzleStudioStack', {
+    env: {
+      region: "ap-northeast-1"
+    }
});

4. リソースのプロビジョニングを実行

<project_root>/cdk
npm run cdk bootstrap

5. デプロイ

<project_root>/cdk
npm run cdk deploy

ここまで成功したらLambda Web Adapterを使ったデプロイは完了

動作確認

  • エラーJSONが返却される
  • CloudWatchでログを確認するとesbuild周りでエラーが発生しているっぽいところまで確認した

ソースコード

今後

  • esbuild周りのエラー原因調査
  • ローカルでDrizzle Studioを起動した時のビルド成果物がどこに配置されているのか確認
    • ビルド成果物だけ抜き出して、コンテナに入れられないか
  • Expressではなく、Honoで実装したい(容量問題がシビアなのでより軽量なFWで)

Discussion