18 ポイント 投稿者 xguru 2024-11-17 | 6件のコメント | WhatsAppで共有
  • Dockerコンテナイメージをビルドする際、DockerfileがMulti-Stage構成でない場合は不要なファイルが含まれる可能性が高い
  • これはイメージサイズの増加とセキュリティ脆弱性の増加につながる
  • コンテナイメージで発生しうる「不要なファイル」の主な原因を分析し、Multi-Stage Buildでこれを解決する方法を説明する

イメージサイズが大きくなる原因

  • アプリケーションはビルド時と実行時の依存関係を持つ。
  • ビルド時の依存関係は実行時より多く、セキュリティ脆弱性(CVE)も多い。
  • 同じイメージをビルドと実行の両方に使うと、不要なビルド時依存関係(コンパイラ、リンターなど)が含まれる。
  • ビルド用イメージとランタイムイメージは分離すべきだが、見落とされることが多い。

誤ったDockerfile構成の例

Goアプリケーション向けの誤った例

FROM golang:1.23  
WORKDIR /app  
COPY . .  
RUN go build -o binary  
CMD ["/app/binary"]  
  • golang:1.23イメージはコンパイル用だが、これをそのまま本番環境で使うと、Goコンパイラ全体と依存関係まで含まれてしまう。
  • イメージサイズ: 800MB以上、800件以上のセキュリティ脆弱性が存在。

Node.jsアプリケーションの誤った例

FROM node:lts-slim  
WORKDIR /app  
COPY . .  
RUN npm ci  
RUN npm run build  
ENV NODE_ENV=production  
EXPOSE 3000  
CMD ["node", "/app/.output/index.mjs"]  
  • node_modulesフォルダに、ランタイムでは不要な開発依存関係まで含まれてしまう。
  • npm ci --omit=devに変更しても解決できず、ビルド工程で必要な開発依存関係まで削除される可能性がある。

Multi-Stage Build以前のLeanイメージ作成方法

Builderパターン

  1. Dockerfile.buildでアプリケーションをビルドする:
FROM node:lts-slim  
WORKDIR /app  
COPY . .  
RUN npm ci  
RUN npm run build  
  1. ビルド済みアーティファクトをホストへコピーする:
docker cp $(docker create build:v1):/app/.output .  
  1. Dockerfile.runでランタイムイメージを生成する:
FROM node:lts-slim  
WORKDIR /app  
COPY .output .  
CMD ["node", "/app/.output/index.mjs"]  
•	問題点: 複数のDockerfile作成、ビルド順序の管理が必要、追加スクリプトも必要。  

Multi-Stage Buildの理解

  • Multi-Stage BuildはDocker内部にBuilderパターンを実装した機能である。
    • 複数のFROM命令を使って、1つのDockerfile内でビルドステージとランタイムステージを定義できる。
    • COPY --from=<stage>命令を使って、前のステージでビルドしたファイルを取り込む。

Multi-Stage Dockerfileの例(Node.js)

# Build stage  
FROM node:lts-slim AS build  
WORKDIR /app  
COPY . .  
RUN npm ci  
RUN npm run build  
  
# Runtime stage  
FROM node:lts-slim AS runtime  
WORKDIR /app  
COPY --from=build /app/.output .  
ENV NODE_ENV=production  
CMD ["node", "/app/.output/index.mjs"]  
  • COPY --from=buildでビルド済みアーティファクトを直接コピーすることで、ホストを経由せずにファイルを移動できる。

Multi-Stage Buildの実践例

Reactアプリケーション

# Build stage  
FROM node:lts-slim AS build  
WORKDIR /app  
COPY . .  
RUN npm ci  
RUN npm run build  
  
# Runtime stage  
FROM nginx:alpine  
COPY --from=build /app/build /usr/share/nginx/html  
ENTRYPOINT ["nginx", "-g", "daemon off;"]  
  • Reactアプリケーションはビルド後に静的ファイルとなり、Nginxで配信できる。

Goアプリケーション

# Build stage  
FROM golang:1.23 AS build  
WORKDIR /app  
COPY . .  
RUN go build -o binary  
  
# Runtime stage  
FROM gcr.io/distroless/static-debian12:nonroot  
COPY --from=build /app/binary /app/binary  
ENTRYPOINT ["/app/binary"]  
  • distrolessイメージを使い、最小化されたランタイム環境を提供する。

Javaアプリケーション

# Build stage  
FROM eclipse-temurin:21-jdk-jammy AS build  
WORKDIR /build  
COPY . .  
RUN ./mvnw package -DskipTests  
  
# Runtime stage  
FROM eclipse-temurin:21-jre-jammy  
COPY --from=build /build/target/app.jar /app.jar  
CMD ["java", "-jar", "/app.jar"]  
  • ビルドにはJDKを使い、ランタイムではより軽量なJREを使う。

結論

  • Multi-Stage Buildはビルド環境とランタイム環境を分離し、不要な開発依存関係によるイメージサイズ増加を防ぐ
  • これによりイメージサイズを削減し、セキュリティを強化し、ビルドプロセスを簡素化できる
  • Multi-Stage Buildは効率的なコンテナイメージを作るための標準的な方法であり、高度な機能(例: 分岐条件、ビルド中のユニットテスト)もサポートする

6件のコメント

 
savvykang 2024-11-18

Java の場合、jlink はバージョン 9 から導入されていますが、依存モジュールを jdeps で見つけて明示しなければならないなど、使い勝手がよくありません。人々がああした方法を知らなかったり JRE を探していたりするのを見ると、Java ツールの周知が足りないように思えますし、コマンド一つで JRE が生成されるように改善が必要そうです。

 
brainer 2024-11-17

ああいうふうに使ってはいるんですが、ビルド時間が長くかかるのは欠点な気がします

 
kandk 2024-11-18

ビルド時間に差はないはずです。差があるなら設定が間違っているということです!

 
brainer 2024-11-18

ああ、そうなんですね!

 
qurare 2024-11-18

戦略によっては1つのステージ全体をキャッシュすることもできるので、むしろビルド時間が短縮されることもあるんですよね!

 
brainer 2024-11-18

Dockerについてもう少し学ぶ必要がありそうですね!