Open7

Nest.js + Prisma の CI / CD 構築

Hirofumi WakasugiHirofumi Wakasugi

Nest.js + Prisma 構成の CI / CD を構築するときのメモ

ポイント

  • テストをどう実行するか?
  • migration をどう実行するか?
  • Prisma クライアントを正しく生成するにはどうするか?

前提

  • CI / CD には AWS CodePipeline を利用する
  • デプロイ先は AWS Elastic Beanstalk の Docker ランタイム
Hirofumi WakasugiHirofumi Wakasugi

migration をどう実行するか?

課題

prisma migrate でマイグレーションを実行するには、ワークスペースが prisma init されている、言い換えると ./prisma ディレクトリが存在している必要がある。

しかし、一般的な Nest.js の本番構成では、ビルド後のディレクトリ (デフォルトは ./dist) と dependencies の詰まった node_modules を設置すればアプリケーションとしては動作するため、その構成にすることが普通だと思う。

これは言い換えると、本番環境にデプロイした状態で prisma migrate を実行することはできないということである。Prisma のドキュメントを見ても、マイグレーションは CI/CD で実行することが意図されている様子。

マイグレーションを CI / CD に組み込むこと自体は難しくないのだが、以下などの点で考慮すべき点は増える:

  • CI / CD プロセスからデータベースへアクセスさせる必要がある
    • CodeBuild だったので RDS アクセスは VPC 内で動かすことが可能だったが、GitHub Actions とかだと追加で ECS Runtask のような仕組みを利用しないといけない (そしてこのときまた ./prisma ディレクトリ問題がある)
  • アプリケーションのデプロイとマイグレーションの順序とタイムラグの管理
  • アプリケーションのデプロイ失敗、マイグレーション失敗時の適切なロールバック

やったこと

CodePipeline のデプロイアクションの に、マイグレーション用のアクションの CodeBuild を設置しマイグレーションを実行するようにした。

Docker マルチステージビルドで、コードベースを展開し npm install を行うことでパッケージの準備をするステージを用意しておき、マイグレーション用のアクションではそのステージをターゲットとして npx prisma migrate deploy を実行することにした。

先述の通り RDS に CodeBuild からアクセスさせる必要はあるが、それは VPC と subnet をそれ用に構成すれば OK。

残る課題としては、デプロイ成功 & マイグレーション失敗時にどうロールバックするかだが、これは Elastic Beanstalk ならバージョン戻しが楽なので、CI / CD での考慮は一旦置いといて、マイグレーション失敗を検知したら手動で戻すでカバーする。CodePipeline のステージの失敗をフックとして Lambda を実行とかはよくやるパターンなので、やろうと思えば自動化もできるとは思う。

この対応はアプリケーションの性質などに強く依存するので、構築対象のモノによって最適なやり方は変わると思う。ベストプラクティスと言えるものが何になるのかはわからない…

Hirofumi WakasugiHirofumi Wakasugi

Prisma クライアントを正しく生成するにはどうするか?

これがどういうことかというと、Prisma のクライアントは、npm install や明示的に prisma generate を実行したときに node_modules 内に作られる。

したがって、Prisma クライアントが正しく生成されているかはかなり意識する必要がある。正しく生成できていない場合、アプリケーションの起動 (Nest.js ならば node dist/main でエントリポイントを叩いたとき) で以下のようなランタイムエラーが発生する。

@prisma/client did not initialize yet. Please run "prisma generate" and try to import it again.

これが「普通の」Node アプリケーションの構成と異なるところなので、ぶっちゃけ一番きつくて、さらによくわかっていない点もある。

例えば、prisma パッケージは通常 devDependencies としてインストールすることが期待されているが、 npm install --only=production としたときに、上記のエラーが 出ることもあったし出ないこともあった

ここは現時点で原因が特定できておらず問題として残っている。一旦は prisma パッケージは devDependencies としたまま、--only=production をつけずに npm install することで回避している。

上記のように prisma ディレクトリがある状態で npm install などで Prisma クライアントを生成しておけば、実際のアプリケーション実行時には prisma ディレクトリは不要。

Hirofumi WakasugiHirofumi Wakasugi

その他のハマりどころ

dist/main がない!

これは、デフォルト構成の Nest.js で Prisma や Nest.js のドキュメントにしたがって進めたときの罠。

prisma ディレクトリには seed ファイルなど TS ファイルが混ざってきてビルド対象になるので、デフォルト構成のままビルドすると、dist 内のディレクトリレイアウトが変わってしまう。要は、dist/main.js ではなく dist/src/main.js となる。

これを回避するには、tsconfig.build.json をいじって、prisma ディレクトリを除外対象として追加しておく必要がある。

{
  "extends": "./tsconfig.json",
  "exclude": ["node_modules", "dist", "test", "**/*spec.ts", "prisma"]
}