マルチステージビルドやDistrolessでGoアプリケーションのDockerイメージを最適化してみたよ
こんにちは、すずきです。
Node.js(Express)ベースのアプリケーションをGoに移行した際、Dockerイメージのサイズを2.8 GBから400 MBまで大幅に削減できて一旦満足したのですが、リソースの効率的な利用やデプロイの速度向上を目指す中で、さらなるイメージサイズの削減ができることがわかったので、新たな試みをしてみることにしました。
DevOps経験のあるエンジニアには既知の内容だと思いますが、初心者の方々への参考として、改善内容を共有します。
元のDockerfile
元のDockerfileでは、golang:1.21.0ベースのイメージを使用し、Goのモジュールのダウンロード、アプリケーションのビルド、実行のためのステップを含んでいます。しかし、この方法では開発環境に必要な全てのファイルがイメージに含まれるため、最終的なイメージサイズが大きくなってしまいます。
# Goの公式イメージをベースにする
FROM golang:1.21.0
# アプリケーションディレクトリを作成する
WORKDIR /app
# Goのモジュールを有効にする
ENV GO111MODULE=on
# 依存関係をコピーし、ダウンロードする
COPY go.mod .
COPY go.sum .
RUN go mod download
# アプリケーションのソースをコピーする
COPY . .
# アプリケーションをビルドする
RUN go build -o main .
# ポート待ち受け
EXPOSE 8081
# アプリケーションの起動コマンド実行
CMD ["./main"]
改良したDockerfile
改良したDockerfileでは、マルチステージビルドを導入しました。
マルチステージビルドは、一つのDockerfileで複数のビルドステージを定義し、最終的なイメージに必要なファイルだけを含める手法です。
最初のステージでアプリケーションをビルドし、次のステージでビルドした実行可能ファイルのみを軽量なalpineイメージにコピーします。これにより、最終的なイメージには必要最小限のファイルのみが含まれるため、イメージサイズが大幅に削減されます。
# Goの公式イメージをベースにする
FROM golang:1.21.0-alpine as builder
# アプリケーションディレクトリを作成する
WORKDIR /app
# Goのモジュールを有効にする
ENV GO111MODULE=on
# 依存関係をコピーし、ダウンロードする
COPY go.mod .
COPY go.sum .
RUN go mod download
# アプリケーションのソースをコピーする
COPY . .
# アプリケーションをビルドする
RUN go build -o main .
# 実行用のステージ
FROM alpine:3.19
WORKDIR /root/
# ビルドしたバイナリをコピー
COPY --from=builder /app/main .
# ポート待ち受け
EXPOSE 8081
# アプリケーションの起動コマンド実行
CMD ["./main"]
結果の比較
元のDockerfileを使用した場合、イメージサイズは約400 MBでした。しかし、マルチステージビルドを適用した後、イメージサイズはわずか10 MBにまで削減されました。なお、マルチステージビルドを行わずにベースイメージにgolang:1.21.0-alpineを使った場合は約160 MBでした。
Distrolessイメージについて
今回はデバッグのためにシェルを含むAlpineイメージを選択したのですが、Googleが提供するDistrolessイメージの使用も検討しました。Distrolessイメージはセキュリティが強化された非常に軽量な環境で、最小限のファイルしか含まれておらず、シェルや不要なパッケージが削除されています。
Distrolessイメージを使用する場合、Goで書かれたアプリケーションは、外部のC言語ライブラリに依存しないスタティックリンクされたバイナリを必要とします。これは、Distrolessが最小限の環境のため、外部ライブラリやシェルが含まれていないからです。そのため、CGO_ENABLED=0を設定してCGO(C言語とGo言語のインターフェース)を無効にし、全ての依存関係を含む独立したバイナリを生成する必要があります。
Distrolessを使った場合のDockerfileは以下のようになります。
# ビルドステージ
FROM golang:1.21.0 as builder
# アプリケーションディレクトリを設定
WORKDIR /app
# Goモジュールを有効にする
ENV GO111MODULE=on
# 依存関係をコピーし、ダウンロードする
COPY go.mod .
COPY go.sum .
RUN go mod download
# アプリケーションのソースをコピー
COPY . .
# アプリケーションをビルド
RUN CGO_ENABLED=0 go build -o main .
# 実行ステージ
FROM gcr.io/distroless/base-debian10
# ビルドしたバイナリをコピー
COPY --from=builder /app/main /
# アプリケーションの実行
CMD ["/main"]
採用情報
この記事が気に入ったらサポートをしてみませんか?