はじめに
開発マシンを Intel Mac から M2 Mac に切り替えたら、色々問題が起きた。これまで、サンプルを元に適当にやっていたが、OpenSSL 1.1.1 のサポートが9月11日に終了するので、OpenSSL 3.0 対応を含めて解消しつつ、きちんと Docker と向き合うことにした。
最終的にやりたいことは、ローカルで build した image を AWS ECR に push し、それを AWS ECS に deploy したい。
結論だけ > TAKT-R-D / nextjs-prisma3-busterslim-openssl3
対応方針
対応方針は、Prisma のバージョンによって変わってくる。
本当は、Prisma のバージョンを上げるのが良いと思うが、対応・検証に時間がかかるし、色々考えているうちに、一昨日 5.0.0 がリリースされて絶望したので、いったん Prisma の Upgrade は先延ばしにする一次対応とする。
まず、SSL 3.0.x に対応した Prisma の binaryTarges を確認する。
node のバージョンも色々だが、ここでは説明簡略のため 18.16.1 とする。
Prisma のバージョン >= 4.10.0 の場合
base となる node イメージは、node:18.16.1-alipine3.17
とし、schema.prisma
の binaryTargets は、linux-musl-openssl-3.0.x
, linux-musl-arm64-openssl-3.0.x
を追加する。native
とそれ以外はいらない。
このケースは、たぶん特筆することはないと思われるので、今回はやらない。時間があったらやってみる。
Prisma のバージョン < 4.10.0 の場合
この場合、alpine が使えない。イメージはどれでも良いが、なるべく安定していて、軽量にしたいので、Debian の現LTS(2024年6月30日まで)の Buster (Debian 10) の slim な node:18.16.1-buster-slim
を使うことにする。
binaryTargets は、M2 Mac 用に linux-arm64-openssl-3.0.x
(Linux (most distributions except Alpine), ARM64), ECS 用に debian-openssl-3.0.x
(Linux (Debian), x86_64) を入れる。
ECS に Deploy するには、docker image を build する際に platform=linux/amd64
というオプションをつける必要があるが、このオプションをつけると、build する環境(Intel Mac or M2 Mac)に関わらず、実際に使われるイメージは、x86_64 になるので、debian-openssl-3.0.x
が必要になる。
また、buster-slim には OpenSSL がインストールされておらず、apt でインストールできる OpenSSL のバージョンは、現時点では、1.1.1n-0+deb10u3
であり(こちら)、3.0 は apt ではインストールできないため、ソースコードを取ってきて、make install する必要がある。そのうち apt-get install できるようになると思うので、以下を読む前に、できるかできないかは確認して欲しい。なぜなら悶絶するくらい build に時間がかかるので。
Debian 10 buster-slim に OpenSSL3.0 をインストールする
まず、インストールするバージョンの検討であるが、OpenSSLのページを見ると、3.0 系は 3.0.9、3.1 系は 3.1.1 が最新となっている。ちょっと悩ましいところだが、今回は FIPS Validated な最新 3.0.8 を入れることにする。どれでも手順は一緒だと思うので、入れたいものを選んで貰えればよい。
とりあえず、OpenSSL3.0 をインストールする Dockerfile を作る。
FROM node:18.16.1-buster-slim AS base
FROM base AS deps
# 何も入ってないので、必要なものを入れる
RUN apt-get update && apt-get install wget build-essential zlib1g-dev make -y
# source を取ってくる。予めローカルにダウンロードしておいて、COPY でも良い
WORKDIR /usr/local/src
RUN wget https://www.openssl.org/source/openssl-3.0.8.tar.gz
RUN tar -zxf /usr/local/src/openssl-3.0.8.tar.gz
WORKDIR /usr/local/src/openssl-3.0.8
RUN ./config --prefix=/usr/local/ssl --openssldir=/usr/local/ssl shared zlib
RUN make && make install
# build
FROM base AS builder
COPY --from=deps /usr/local/ssl /usr/local/ssl
RUN echo "/usr/local/ssl/lib\n/usr/local/ssl/lib64" > /etc/ld.so.conf.d/openssl-3.0.8.conf && ldconfig
ENV PATH="$PATH:/usr/local/ssl/bin"
ここで注意点としては、最後から2行目の
RUN echo "/usr/local/ssl/lib\n/usr/local/ssl/lib64" > ...
make install した際に、ARM64 では、/usr/local/ssl/lib
に、x86_64 では /usr/local/ssl/lib64
とパスが変わるため、2行書いている。
/usr/local/ssl/lib
/usr/local/ssl/lib64
ldconfig
した際にないものは無視されるが、エラーにはならないので、これで。Dockerfile を分けるべきな気もするが横着した。気になる人は、分けてください。
PATH は、ENV
としているが、
RUN echo "$PATH:/usr/local/ssl/bin" > /etc/environment && . /etc/environment
などでも良いはず。もっと良い方法もありそうだが、私の Debian 力ではいまはこれが精一杯。
なお、make
, make install
はめちゃくちゃ時間がかかるので、実運用では、/usr/local/ssl
ディレクトリはローカルから COPY
が良いのかも知れないが、本旨からはそれるので、後々考えることとする。
ここまでできれば、あとつまるところはないはず。
runner にて addgroup
か groupadd
かなどあるが、たいした問題ではない。
成果物
というわけで、サンプルを元に、image を alpine から buster-slim に変更し、OpenSSL3.0.8 に対応した Next.js + Prisma が動く Dockerfile と schema.prisma は以下のようになる。PORT はお好きに。サンプルは 3000 だ。
イメージサイズは、まだまだチューニングの余地がありそうだが、まぁ良いか…というレベルで、アプリを載せて、alpine の 2 倍、slim じゃない buster の 1/3 程度で、ECR 上で 130 MB くらいだったので、暇があったら検討するかも案件。
なお、じゃあこれを Production 用に使って良いか?というと、それはまた別の問題で、以下の Dockerfile のサンプルが、御社のセキュリティ要件を満たしているかは検証が必要だ。
以下のようになると書いたが、ふと我に返って、github に置いた。
ローカル用 build
$ docker build ./ -t {your_container_name}
ECS 用 build
$ docker build --platform linux/amd64 ./ -t {your_container_name}
builder の yarn build
でこける場合、prisma generate
が通ってて、ローカルで build できるなら、yarn build
できるので、再度 docker build しましょう。なんでこけることがあるのかは、まだ分かってない。
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "debian-openssl-3.0.x", "linux-arm64-openssl-3.0.x"]
}
...
なお、next.js の docker で、
ENV NEXT_TELEMETRY_DISABLED 1
は、匿名情報収集を許可しないという設定で、yarn build 中に
Attention: Next.js now collects completely anonymous telemetry regarding usage.
と、完全に匿名だから大丈夫的なメッセージが出てくるが、情報提供して良いかはアプリケーションのオーナーが決めるべきことだし、Next.js の発展にどうしても貢献したい!という情熱をお持ちでない限り、DISABLED にしておくのが無難だ。
おまけ
Intel Mac から M2(/M1) Mac に切り替えた際に詰まる点は、上記以外にもいくつかあった。
node, yarn のバージョン
ローカルの yarn
のバージョンがいつから 4.0.0-rc.42
だったか記憶にないが、alpine でも buster-slim でも、yarn
のバージョンは、1.22.19
である。4と1はだいぶ違うので、--fronzen-lockfile
する際にうまく行かない。
というわけで、Project ごとに node, yarn のバージョンは定期的に見直す前提で、固定した方が間違いがない。Intel Mac では node のバージョン管理には、nodebrew を利用していたが、volta に切り替えた。
インストール方法は各自ググっていただくとして、プロジェクトごとに、
$ volta pin node@18.16.1
$ volat pin yarn@1.22.19
などとすると、バージョンを固定できる。package.json に、
{
...,
"volta": {
"node": "18.16.1",
"yarn": "1.22.19"
}
}
こんなものが追記される。
もちろん、volta を利用していなければ、無視される設定ではあるが、便利である。
実行 platform
ECS に Deploy する場合、platform=linux/amd64 にしないといけない。docker build の場合は前述の通りで、docker compose の場合は、以下のようにする
version: '3'
services:
app:
build:
context: ./
dockerfile: Dockerfile
image: app_local
platform: linux/amd64
ports:
- '8080:8080'
インストールできない package もある。
bycrypt
など、M1/2 Mac ではインストールできない package もある。そのうちできるようになるかも知れないが対応が必要だ。ググると無理矢理インストールする方法などが出てはくるが、今回は、代替 package を使うことにした。bycryptjs
。大した使い方はしていなかったので、きちんと違いを読み込んではいない。
ChatGPT に聞くと…
動かなそうだし、試していないが、割りと的を得た回答だった。今どきは、色々ググって調べるよりも、ここからスタートするのも良いかも知れない。
(おしまい)