Distrolessを使った2021年最新のDocker環境を試した[Node.js/NestJS]
今回は NestJS を使っていますが、ビルド後のファイルと node_modules を使っているだけですので、Express やほかの FW でも同様にできると思います。
Distroless とは?
Google 製の Docker イメージです。
アプリケーションとそのランタイムの依存関係だけが含まれています。
本番環境に特化したイメージであり、ランタイムに不要なプログラム(シェルなど)を含まないのでパフォーマンスの向上・セキュリティ向上が見込めます。
また alpine ではなく Distroless を使う理由はこちらの記事が参考になるので、気になる方はこちらを参照してください。
Distroless で本番環境を作ろうとなったきっかけの記事でもあります。
結果
削減対策なし slim | マルチビルド slim | マルチビルド distroless |
---|---|---|
667.66 MB | 177.56 MB | 129.1 MB |
イメージサイズを削減しつつ、ランタイムに不要な最小限の構成になったことでセキュリティも上がった。
今回の環境バージョン
$ node -v
v14.16.0
$ yarn -v
1.22.10
NestJS で Hello World! を表示する
公式の Introduction に沿って Hello World! が表示するまで作成します。
npm i -g @nestjs/cli
nest new nestjs-docker
途中で出てくるパッケージマネージャーはお好きな方をお使いください。
今回は yarn を使っていきたいと思います。
nest new nestjs-docker 実行結果
$ nest new nestjs-docker
⚡ We will scaffold your app in a few seconds..
CREATE nestjs-docker/.eslintrc.js (631 bytes)
CREATE nestjs-docker/.prettierrc (51 bytes)
CREATE nestjs-docker/README.md (3339 bytes)
CREATE nestjs-docker/nest-cli.json (64 bytes)
CREATE nestjs-docker/package.json (1975 bytes)
CREATE nestjs-docker/tsconfig.build.json (97 bytes)
CREATE nestjs-docker/tsconfig.json (339 bytes)
CREATE nestjs-docker/src/app.controller.spec.ts (617 bytes)
CREATE nestjs-docker/src/app.controller.ts (274 bytes)
CREATE nestjs-docker/src/app.module.ts (249 bytes)
CREATE nestjs-docker/src/app.service.ts (142 bytes)
CREATE nestjs-docker/src/main.ts (208 bytes)
CREATE nestjs-docker/test/app.e2e-spec.ts (630 bytes)
CREATE nestjs-docker/test/jest-e2e.json (183 bytes)
? Which package manager would you ❤️ to use?
npm
❯ yarn
✔ Installation in progress... ☕
🚀 Successfully created project nestjs-docker
👉 Get started with the following commands:
$ cd nestjs-docker
$ yarn run start
Thanks for installing Nest 🙏
Please consider donating to our open collective
to help us maintain this package.
🍷 Donate: https://opencollective.com/nest
cd nestjs-docker
yarn run start
yarn run start 実行結果
$ yarn run start
yarn run v1.22.10
$ nest start
[Nest] 75685 - 2021/03/14 15:45:58 [NestFactory] Starting Nest application...
[Nest] 75685 - 2021/03/14 15:45:58 [InstanceLoader] AppModule dependencies initialized +65ms
[Nest] 75685 - 2021/03/14 15:45:58 [RoutesResolver] AppController {}: +4ms
[Nest] 75685 - 2021/03/14 15:45:58 [RouterExplorer] Mapped {, GET} route +2ms
[Nest] 75685 - 2021/03/14 15:45:58 [NestApplication] Nest application successfully started +1ms
http://localhost:3000 を開いて Hello World! が表示されたら完了です。
Docker 環境を作る
Dockerfile の作成
Dockerfile
と .dockerignore
のファイルを作成します。
touch Dockerfile
touch .dockerignore
Dockerfile
の内容を書き換えます。
コメントには npm を使った場合のコマンドを表示しています。
#==================================================
# Build Layer
FROM node:14-slim as build
WORKDIR /app
COPY package.json yarn.lock ./
# COPY package.json package-lock.json ./
RUN yarn install --non-interactive --frozen-lockfile
# RUN cpm ci
COPY . .
RUN yarn build
# RUN npm run build
#==================================================
# Package install Layer
FROM node:14-slim as node_modules
WORKDIR /app
COPY package.json yarn.lock ./
# COPY package.json package-lock.json ./
RUN yarn install --non-interactive --frozen-lockfile --prod
# RUN cpm ci --only=production
#==================================================
# Run Layer
FROM gcr.io/distroless/nodejs:14
WORKDIR /app
ENV NODE_ENV=production
COPY /app/dist /app/dist
COPY /app/node_modules /app/node_modules
CMD ["dist/main"]
.git/
node_modules/
dist/
イメージをビルド
作成した Dockerfile
をビルドします。
docker build --tag nestjs-docker .
実行結果
% docker build --tag nestjs-docker .
[+] Building 80.8s (17/17) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 668B
=> [internal] load .dockerignore
=> => transferring context: 78B
=> [internal] load metadata for gcr.io/distroless/nodejs:14
=> [internal] load metadata for docker.io/library/node:14-slim
=> [build 1/6] FROM docker.io/library/node:14-slim@sha256:e8a3dbe7f6d334acfe0365260626d3953073334de4c0fde00f93e8e9e19ed5d5
=> [stage-2 1/4] FROM gcr.io/distroless/nodejs:14@sha256:24601f9c48037634eb06dc24d0d288af3a1ad7f4175845fbd0085b4ac51effa5
=> [internal] load build context
=> => transferring context: 535.75kB
=> CACHED [stage-2 2/4] WORKDIR /app
=> CACHED [build 2/6] WORKDIR /app
=> [build 3/6] COPY package.json yarn.lock ./
=> [node_modules 4/4] RUN yarn install --non-interactive --frozen-lockfile --prod
=> [build 4/6] RUN yarn install --non-interactive --frozen-lockfile
=> [build 5/6] COPY . .
=> [build 6/6] RUN yarn build
=> [stage-2 3/4] COPY --from=build /app/dist /app/dist
=> [stage-2 4/4] COPY --from=node_modules /app/node_modules /app/node_modules
=> exporting to image
=> => exporting layers
=> => writing image sha256:edbed248b26d6b341b3b430016547cc190245bd8517021f74841fede11d775df
=> => naming to docker.io/library/nestjs-docker
イメージサイズは 129.1 MB
になりました。
イメージを実行
ビルドしたイメージを実行します。
docker run -d -p 3000:3000 --name nestjs-docker nestjs-docker
http://localhost:3000 を開いて Hello World! が表示されたら成功です。
削減対策をしない slim 環境
シングルステージでのビルドです。
ランタイムに不要なパッケージなど含まれるのでイメージサイズが大きいです。
Dockerfile
FROM node:14-slim
WORKDIR /app
COPY . .
RUN yarn install --non-interactive --frozen-lockfile
RUN yarn build
ENV NODE_ENV=production
CMD ["yarn", "start:prod"]
SIZE 667.66 MB
マルチステージビルドでの slim 環境
マルチステージビルドを使いランタイムに必要なプログラムのみをイメージに含めています。
yarn
や sh
など本番では使わないプログラムが含まれるため Distroless と比べると少しサイズが大きいですね。
Dockerfile
#==================================================
# Build Layer
FROM node:14-slim as build
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --non-interactive --frozen-lockfile
COPY . .
RUN yarn build
#==================================================
# Package install Layer
FROM node:14-slim as node_modules
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --non-interactive --frozen-lockfile --prod
#==================================================
# Run Layer
FROM node:14-slim
WORKDIR /app
ENV NODE_ENV=production
COPY /app/dist /app/dist
COPY /app/node_modules /app/node_modules
CMD ["node", "dist/main"]
SIZE 177.56 MB
まとめ
削減対策なし slim | マルチビルド slim | マルチビルド distroless |
---|---|---|
667.66 MB | 177.56 MB | 129.1 MB |
- 減対策なし slim と比較し
538.56 MB
のイメージサイズ削減 - マルチビルド slim と比較し
48.46 MB
のイメージサイズ削減
イメージサイズが小さくなるのはとても楽しいですね!
マルチステージビルドは使っていたのですが Distroless は知らなかったので積極的に使っていこうと思います。
Distroless にはシェルが含まれていないため、アタッチしてデバッグする際はデバッグ用のイメージ gcr.io/distroless/nodejs:debug
を使いましょう!
今回行った Distroless 以外のベストプラクティスなどは以下の記事が参考になります。
自分もすべてを対応できているわけではないですが、ちょっとずつ参考にしてイメージを進化させていきたいですね!
Discussion