Node.js のコンテナイメージを作成するときに気をつけていること
自分が Node.js を用いたアプリケーションのコンテナイメージを作成するときに気をつけていることをメモとして記載します。
Docker and Node.js Best Practices
node の Docker Image のリポジトリには Best Practices というドキュメントが用意されています。
基本的には、これに従って Dockerfile を書いています。
Handling Kernel Signals
上記の Best Practices にも記載がありますが、Node.js は PID 1 で動作するように設計されていないようで、そのままコンテナイメージで動かそうとすると SIGINT
等のシグナルを上手く扱ってくれません。
そのため tini 等を PID 1 で動かし、その子プロセスで Node.js を動かします。
docker
コマンドには --init
フラグがあるため、これを使うと tini を導入しなくても動作しますが Kubernetes 等のサポートが無い環境もあるため、コンテナイメージを作成するときにあらかじめ tini を導入するようにしています。
FROM node:18 as builder
# Add Tini
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static /tini-static
RUN chmod +x /tini-static
# 略
ENTRYPOINT ["/tini-static", "--"]
distroless
ベースイメージは Node.js のランタイムが入ったイメージが用意されている distroless を採用することが多いです。
shell や package manager も入っていないイメージになっているのでサイズが小さいです。
また、自分がセキュリティや脆弱性周りに詳しくないので distroless 側での対応に頼って、運用コストを下げたいという意図もあります。
FROM gcr.io/distroless/nodejs18-debian11:latest
COPY /tini-static /tini-static
ENTRYPOINT ["/tini-static", "--", "/nodejs/bin/node"]
distroless ではデフォルトで node
が ENTRYPOINT
に設定されているため、上記のように tini
から distroless 内の node
を呼び出すような ENTRYPOINT
に変更しています。
distroless は Bazel でイメージを作成しており node
の場所を探すのに少々苦労したのですが、この辺り を見て設定しています。
まとめ
ここまで記載したことをまとめると以下のような Dockerfile をベースに要件に応じて作り変えています。
Multi-stage builds を使い、オフィシャルの node イメージでビルドを行い distroless をベースイメージとしてアプリケーションのコンテナイメージを作成しています。
FROM node:18 as builder
# Add Tini
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static /tini-static
RUN chmod +x /tini-static
# アプリケーションに必要なビルド等
FROM gcr.io/distroless/nodejs18-debian11:latest
COPY /tini-static /tini-static
ENTRYPOINT ["/tini-static", "--", "/nodejs/bin/node"]
# CMD ["server.js"]
Discussion