😭
Drizzle StudioをAWS Lambdaで動かす試みの失敗記録
2024-07-09追記
いつのまにか、Drizzle StudioのChrome拡張機能とnpmパッケージが提供されていた。
npmの方を見ているとそのうちDockerに載せて動かせるようになりそうなので期待してる。有料でもいい
モチベーション
- 以前からLambda Web Adapterという技術が気になっており、試しておきたいと思っていた
- 業務でDBを操作するダッシュボード系ツールを作る機会が増え、REST API/UIの実装をその都度、行うことにしんどさを感じ始めた
→ Drizzle Studioをサーバ上で動かせれば、楽できるのでは?試してみよう
まとめ(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ツールの類似製品
ローカル環境で動かせるようにする
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
スキーマを定義する
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 /lambda-adapter /opt/extensions/lambda-adapter
EXPOSE 3000
WORKDIR /app
COPY /build/package.json ./
COPY /build/drizzle.config.js ./
COPY /build/schema ./schema
COPY /build/node_modules ./node_modules
# Express.jsをリバースプロキシとして利用する(0.0.0.0:3000 -> 127.0.0.1:5173
COPY /build/proxy.js ./
ENV PATH /app/node_modules/.bin:$PATH
ENTRYPOINT ["node"]
CMD ["proxy.js"]
Docker内のDrizzle Studioにリクエストが通らない問題を解決する
- Express.jsとhttp-proxy-middlewareをインストールする
npm i express http-proxy-middleware
- proxy.jsを作成
touch proxy.js
- 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");
});
- 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. 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