👨🏼‍💻

NestJSとPrismaのイメージをCloudRunにデプロイしたらクラッシュしてコンテナが起動しなくなったので調べたこと

2022/11/27に公開

https://twitter.com/prisma/status/1648376590349352970

はじめに

NestJS と Prisma のイメージを CloudRun にデプロイしたら、クラッシュして起動しなくなったので、原因について調べたことまとめです。

事象

Segmentation fault になって、NestJS が起動してくれない。(Cloud Run のログ)

環境

Dockerfile
#==================================================
# Build Layer
FROM --platform=linux/amd64 node:18 as build

WORKDIR /app

COPY package.json yarn.lock ./

COPY prisma ./prisma

RUN yarn install --non-interactive --frozen-lockfile

RUN yarn prisma generate

COPY . .

RUN yarn build

#==================================================
# Package install Layer
FROM --platform=linux/amd64 node:18 as node_modules

WORKDIR /app

COPY package.json yarn.lock ./

COPY prisma ./prisma

RUN yarn install --non-interactive --frozen-lockfile --prod

RUN yarn prisma generate

#==================================================
# Run Layer
FROM --platform=linux/amd64 node:18-slim as node

WORKDIR /app

ENV NODE_ENV=production

COPY --from=build /app/dist /app/dist
COPY --from=build /app/prisma /app/prisma
COPY --from=node_modules /app/package.json /app/yarn.lock ./
COPY --from=node_modules /app/node_modules /app/node_modules

CMD ["/usr/local/bin/yarn", "start:prod"]

まずは、Segmentation fault について調べてみます。

Segmentation fault とは

  • プログラムが OS によって設定された基本的な規則に違反したときに発生するエラーです。
    • このエラーが起きたとき、OS はプロセスにシグナルを送信し、プロセスはシャットダウンします。
  • 基本的には、低レベルの問題(ポインタやメモリ管理など)なので、JavaScript を書くときは気にする必要はありませんが、Node.js で segfault が発生するパターンはいくつかあるようです。
    • Nodejs のネイティブのアドオン実装で、そのアドオン自体にバグがあるか、Node のバージョンと互換性がない場合
    • Node 内部の状態の操作で、前提の状態が崩れ、Node の組み込みネイティブ コードが間違った動作をして、セグメンテーション違反が発生する場合
    • Node.js 自体にバグがある場合

https://httptoolkit.com/blog/how-to-debug-node-segfaults/

https://github.com/ddopson/node-segfault-handler

原因

ログには、Segmentation fault となっているものの、なぜ Segmentation fault になっているかの原因がわからず、社内 Slack で困り果てていたところ、同僚に以下の Issue を教えてもらいました。

この Issue に原因とワークアラウンドが書かれてた(大感謝!!!)

https://github.com/prisma/prisma/issues/10649#issuecomment-1247961614

直接的な原因は、NodeJS 自体にバンドルされた OpenSSLシステムで使われてる OpenSSLのバージョンが異なることに起因してるようです。

Node.js 17.0.0 以降、Node.js にバンドルされてる OpenSSL のバージョンは 3 なのですが、システムが使っている OpenSSL のバージョンが 1.1.x の場合、バージョンがずれて本事象が起こるようでした。

https://github.com/nodejs/node/commit/66da32c045035cf2710a48773dc6f55f00e20c40

ワークアラウンド

回避策は以下の3つがあるようで、どちらかの Openssl のバージョンに合わせる、もしくはクエリエンジンタイプにバイナリエンジンを指定することで回避できるようです。

  1. Node のバージョンを 16.x に下げ、Node.js にバンドルされてるバージョンが 1.1.x になるようにして、Node とシステムで使われてる OpenSSL のバージョンを合わせる。
  2. システムで使われてる OpenSSL のバージョンを 3 に上げて、Node とシステムで使われてる OpenSSL のバージョンを合わせる。
  3. デフォルトの設定では、Prisma クライアントはクエリエンジンを NodeAPI として利用しますが(engineType = library)、クエリエンジンを利用する別の方法として、実行可能なバイナリーをサイドカープロセスとして実行するという方法もあり(engineType = binary)、デフォルトの設定から、engineType = binary に切り替えると、システムで使っている OpenSSL と Node で使ってる OpenSSL のバージョンが異なっていても、この問題を回避できます。

https://github.com/prisma/prisma/issues/10649#issuecomment-1249209025

クエリエンジン

クエリエンジンのタイプをバイナリーエンジン(engineType = binary)に切り替えると、Node.js に含まれてる OpenSSL とシステムで使われてる OpenSSL のバージョンが異なっても Segmentation fault のエラーが回避できるのがなぜか気になったので、クエリエンジンについてのドキュメントをあらためて読んでみた。

クエリエンジンとは

  • prisma client から db への connection が呼び出された時に、db とのコネクションを管理する
  • prisma client からの query を SQL に変換、DB に送信する
  • rust 実装で OS に依存してる

以下、ドキュメント

https://www.prisma.io/docs/concepts/components/prisma-engines/query-engine#defining-the-query-engine-type-for-prisma-client

クエリエンジンのタイプ

クエリエンジンのタイプは以下の2パターンから選択することができます。
Prisma クライアントとクエリ エンジン間の通信オーバーヘッドが削減されるため、Node-API ライブラリ アプローチがおすすめのようです。(デフォルトの設定)

  • Prisma Client にロードされる Node-API ライブラリとしてクエリエンジンを利用する方法(デフォルトの設定)
  • 独自のプロセスで実行される実行可能バイナリとしてクエリエンジンを利用する方法
generator client {
  provider   = "prisma-client-js"
  engineType = "binary"
}

https://www.prisma.io/docs/concepts/components/prisma-engines/query-engine#defining-the-query-engine-type-for-prisma-client

https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#fields-1

気になっていたところとして、engineType = binary の場合は、Node-API ではなく独自のプロセスでクエリエンジンが実行されるので、Node.js が内包してる OpenSSL が使われず、バージョンが異なっていても動くって感じっぽさそうと思ったのですが、詳細はあってるか自信ないのでもう少し調べてみます。

おわりに

エラーを調べてて初めて知ったことが多かったので勉強になった。Prisma のクエリエンジンのタイプや環境に依存があったりなどそのあたりあまり意識せず使っていたので、ドキュメントを改めて読んで色々発見があってよかった。

参考

  • Node.js OpenSSL Strategy

https://github.com/nodejs/TSC/blob/main/OpenSSL-Strategy.md

GitHubで編集を提案
株式会社モニクル

Discussion