本番環境用にDockerイメージを最適化する方法
著者はCode.orgを選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
実稼働環境では、 Docker を使用すると、コンテナー内でアプリケーションを簡単に作成、デプロイ、および実行できます。 コンテナーを使用すると、開発者はアプリケーションとそのすべてのコアの必需品と依存関係を1つのパッケージにまとめて、Dockerイメージに変換して複製することができます。 Dockerイメージは、Dockerfilesから構築されます。 Dockerfileは、イメージがどのように表示されるか、イメージが持つ基本オペレーティングシステム、およびイメージ内で実行されるコマンドを定義するファイルです。
大きなDockerイメージは、クラスターとクラウドプロバイダー間でイメージを構築して送信するのにかかる時間を長くする可能性があります。 たとえば、開発者の1人がビルドをトリガーするたびにプッシュするギガバイトサイズのイメージがある場合、ネットワーク上で作成するスループットはCI / CDプロセス中に加算され、アプリケーションの動作が遅くなり、最終的にリソースが消費されます。 。 このため、本番環境に適したDockerイメージには、必要最低限のものだけをインストールする必要があります。
Dockerイメージのサイズを小さくして、本番環境に最適化する方法はいくつかあります。 まず、これらのイメージは通常、アプリケーションを実行するためのビルドツールを必要としないため、追加する必要はまったくありません。 マルチステージビルドプロセスを使用すると、中間イメージを使用してコードをコンパイルおよびビルドし、依存関係をインストールし、すべてを可能な限り最小のサイズにパッケージ化してから、アプリケーションの最終バージョンをにコピーできます。ビルドツールなしの空のイメージ。 さらに、 AlpineLinuxのような小さなベースの画像を使用できます。 Alpineは、アプリケーションを実行するために必要なものだけを備えているため、本番環境に適したLinuxディストリビューションです。
このチュートリアルでは、いくつかの簡単な手順でDockerイメージを最適化し、Dockerイメージをより小さく、より速く、より本番環境に適したものにします。 サンプルGoAPI のイメージを、Ubuntuおよび言語固有のイメージから始めて、Alpineディストリビューションに移り、いくつかの異なるDockerコンテナーでビルドします。 また、マルチステージビルドを使用して、制作用にイメージを最適化します。 このチュートリアルの最終目標は、デフォルトのUbuntuイメージと最適化された対応するイメージの使用のサイズの違いを示し、マルチステージビルドの利点を示すことです。 このチュートリアルを読んだ後、これらの手法を独自のプロジェクトとCI/CDパイプラインに適用できるようになります。
注:このチュートリアルでは、例としてGoで記述されたAPIを使用します。 このシンプルなAPIにより、Dockerイメージを使用してGoマイクロサービスを最適化する方法を明確に理解できます。 このチュートリアルではGoAPIを使用していますが、このプロセスはほとんどすべてのプログラミング言語に適用できます。
前提条件
始める前に、次のものが必要になります。
sudo
権限を持つ非rootユーザーアカウントを持つUbuntu18.04サーバー。 ガイダンスについては、 Ubuntu18.04を使用した初期サーバーセットアップのチュートリアルに従ってください。 このチュートリアルはUbuntu18.04でテストされていますが、どのLinuxディストリビューションでも多くの手順を実行できます。- サーバーにDockerがインストールされています。 インストール手順については、 Ubuntu18.04にDockerをインストールして使用する方法のステップ1と2に従ってください。
ステップ1—サンプルGoAPIをダウンロードする
Dockerイメージを最適化する前に、Dockerイメージのビルド元となるサンプルAPIを最初にダウンロードする必要があります。 シンプルなGoAPIを使用すると、Dockerコンテナ内でアプリケーションを構築して実行するためのすべての主要な手順を紹介します。 このチュートリアルでは、 C++やJavaのようなコンパイル型言語であるため、Goを使用しますが、それらとは異なり、フットプリントが非常に小さくなっています。
サーバーで、サンプルのGoAPIのクローンを作成することから始めます。
git clone https://github.com/do-community/mux-go-api.git
プロジェクトのクローンを作成すると、サーバー上にmux-go-api
という名前のディレクトリが作成されます。 cd
を使用してこのディレクトリに移動します。
cd mux-go-api
これがプロジェクトのホームディレクトリになります。 このディレクトリからDockerイメージをビルドします。 内部には、Goで記述されたAPIのソースコードがapi.go
ファイルにあります。 このAPIは最小限であり、エンドポイントはわずかですが、このチュートリアルの目的で本番環境に対応したAPIをシミュレートするのに適しています。
サンプルのGoAPIをダウンロードしたので、ベースのUbuntu Dockerイメージを構築する準備が整いました。これに対して、後で最適化されたDockerイメージを比較できます。
ステップ2—ベースUbuntuイメージを構築する
最初のDockerイメージでは、ベースのUbuntuイメージから始めたときにどのように見えるかを確認すると便利です。 これにより、Ubuntuサーバーですでに実行しているソフトウェアと同様の環境にサンプルAPIがパッケージ化されます。 イメージ内に、アプリケーションの実行に必要なさまざまなパッケージとモジュールをインストールします。 ただし、このプロセスでは、ビルド時間とDockerfileのコードの可読性に影響を与えるかなり重いUbuntuイメージが作成されることがわかります。
まず、DockerにUbuntuイメージを作成し、Goをインストールして、サンプルAPIを実行するように指示するDockerfileを作成します。 クローンリポジトリのディレクトリにDockerfileを作成してください。 ホームディレクトリにクローンを作成した場合は、$HOME/mux-go-api
になります。
Dockerfile.ubuntu
という名前の新しいファイルを作成します。 nano
またはお気に入りのテキストエディタで開きます。
nano ~/mux-go-api/Dockerfile.ubuntu
このDockerfileでは、Ubuntuイメージを定義し、Golangをインストールします。 次に、必要な依存関係のインストールとバイナリのビルドに進みます。 Dockerfile.ubuntu
に次の内容を追加します。
〜/ mux-go-api / Dockerfile.ubuntu
FROM ubuntu:18.04 RUN apt-get update -y \ && apt-get install -y git gcc make golang-1.10 ENV GOROOT /usr/lib/go-1.10 ENV PATH $GOROOT/bin:$PATH ENV GOPATH /root/go ENV APIPATH /root/go/src/api WORKDIR $APIPATH COPY . . RUN \ go get -d -v \ && go install -v \ && go build EXPOSE 3000 CMD ["./api"]
FROM
コマンドは、上から順に、イメージの基本オペレーティングシステムを指定します。 次に、RUN
コマンドは、イメージの作成中にGo言語をインストールします。 ENV
は、Goコンパイラが正しく動作するために必要な特定の環境変数を設定します。 WORKDIR
は、コードをコピーするディレクトリを指定し、COPY
コマンドは、Dockerfile.ubuntu
があるディレクトリからコードを取得し、イメージにコピーします。 最後のRUN
コマンドは、ソースコードがAPIをコンパイルして実行するために必要なGo依存関係をインストールします。
注: &&
演算子を使用してRUN
コマンドを文字列化することは、Dockerfileを最適化する上で重要です。これは、すべてのRUN
コマンドが新しいレイヤーを作成するためです。新しいレイヤーは、最終的な画像のサイズを大きくします。
ファイルを保存して終了します。 これで、build
コマンドを実行して、作成したDockerfileからDockerイメージを作成できます。
docker build -f Dockerfile.ubuntu -t ubuntu .
build
コマンドは、Dockerfileからイメージを構築します。 -f
フラグは、Dockerfile.ubuntu
ファイルからビルドすることを指定し、-t
はタグを表します。つまり、ubuntu
という名前でタグ付けします。 。 最後のドットは、Dockerfile.ubuntu
が配置されている現在のコンテキストを表します。
しばらく時間がかかりますので、お気軽にご利用ください。 ビルドが完了すると、APIを実行するためのUbuntuイメージの準備が整います。 ただし、画像の最終的なサイズは理想的ではない場合があります。 このAPIの数百MBを超えるものは、非常に大きな画像と見なされます。
次のコマンドを実行して、すべてのDockerイメージを一覧表示し、Ubuntuイメージのサイズを見つけます。
docker images
作成した画像を示す出力が表示されます。
OutputREPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest 61b2096f6871 33 seconds ago 636MB . . .
出力で強調表示されているように、この画像のサイズは、基本的なGolangAPIの636MB であり、マシンごとにわずかに異なる場合があります。 複数のビルドにわたって、この大きなサイズは展開時間とネットワークスループットに大きく影響します。
このセクションでは、ステップ1でクローン化したAPIを実行するために必要なすべてのGoツールと依存関係を使用してUbuntuイメージを構築しました。 次のセクションでは、事前にビルドされた言語固有のDockerイメージを使用して、Dockerfileを簡素化し、ビルドプロセスを合理化します。
ステップ3—言語固有のベースイメージを構築する
ビルド済みイメージは、状況固有のツールを含めるためにユーザーが変更した通常のベースイメージです。 その後、ユーザーはこれらのイメージを Docker Hub イメージリポジトリにプッシュできるため、他のユーザーは独自のDockerファイルを作成する代わりに共有イメージを使用できます。 これは本番環境での一般的なプロセスであり、ほとんどすべてのユースケースでDockerHubにさまざまなビルド済みイメージを見つけることができます。 このステップでは、コンパイラと依存関係がすでにインストールされているGo固有のイメージを使用してサンプルAPIをビルドします。
アプリをビルドして実行するために必要なツールがすでに含まれているビルド済みのベースイメージを使用すると、ビルド時間を大幅に短縮できます。 必要なすべてのツールがプリインストールされているベースから開始しているため、これらをDockerfileに追加することをスキップして、見た目をすっきりさせ、最終的にビルド時間を短縮できます。
先に進み、別のDockerfileを作成して、Dockerfile.golang
という名前を付けます。 テキストエディタで開きます。
nano ~/mux-go-api/Dockerfile.golang
このファイルには、Go固有の依存関係、ツール、およびコンパイラがすべてプリインストールされているため、前のファイルよりも大幅に簡潔になります。
ここで、次の行を追加します。
〜/ mux-go-api / Dockerfile.golang
FROM golang:1.10 WORKDIR /go/src/api COPY . . RUN \ go get -d -v \ && go install -v \ && go build EXPOSE 3000 CMD ["./api"]
上から順に、FROM
ステートメントがgolang:1.10
になっていることがわかります。 これは、Dockerが必要なすべてのGoツールがすでにインストールされているDockerHubからビルド済みのGoイメージをフェッチすることを意味します。
ここで、もう一度、Dockerイメージを次のコマンドでビルドします。
docker build -f Dockerfile.golang -t golang .
次のコマンドを使用して、画像の最終的なサイズを確認します。
docker images
これにより、次のような出力が得られます。
OutputREPOSITORY TAG IMAGE ID CREATED SIZE golang latest eaee5f524da2 40 seconds ago 744MB . . .
Dockerfile自体はより効率的で、ビルド時間は短くなりますが、実際には合計イメージサイズが大きくなります。 ビルド済みのGolangイメージは約744MBで、かなりの量です。
これは、Dockerイメージを構築するための推奨される方法です。 これは、コミュニティが指定された言語(この場合はGo)で使用する標準として承認したベースイメージを提供します。 ただし、イメージを本番環境で使用できるようにするには、実行中のアプリケーションが必要としない部分を切り取る必要があります。
ニーズがよくわからない場合は、これらの重い画像を使用しても問題ないことに注意してください。 使い捨てのコンテナとしてだけでなく、他のイメージを構築するためのベースとしてもお気軽にご利用ください。 ネットワークを介した画像の送信について考える必要がない開発またはテストの目的では、重い画像を使用することはまったく問題ありません。 ただし、展開を最適化する場合は、イメージをできるだけ小さくするために最善を尽くす必要があります。
言語固有のイメージをテストしたので、次のステップに進むことができます。このステップでは、軽量のAlpine Linuxディストリビューションをベースイメージとして使用して、Dockerイメージを軽量化します。
ステップ4—ベースアルパインイメージの構築
Dockerイメージを最適化する最も簡単な手順の1つは、小さいベースイメージを使用することです。 Alpine は、セキュリティとリソース効率のために設計された軽量Linuxディストリビューションです。 Alpine Dockerイメージは、 musllibcとBusyBoxを使用してコンパクトな状態を維持し、コンテナーでの実行に必要なのは8MB以下です。 サイズが小さいのは、バイナリパッケージが間引かれ分割されているためです。これにより、インストールするものをより細かく制御できるため、環境を可能な限り小さく効率的に保つことができます。
アルパインイメージを作成するプロセスは、ステップ2でUbuntuイメージを作成した方法と似ています。 まず、Dockerfile.alpine
という名前の新しいファイルを作成します。
nano ~/mux-go-api/Dockerfile.alpine
次に、このスニペットを追加します。
〜/ mux-go-api / Dockerfile.alpine
FROM alpine:3.8 RUN apk add --no-cache \ ca-certificates \ git \ gcc \ musl-dev \ openssl \ go ENV GOPATH /go ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH ENV APIPATH $GOPATH/src/api RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" "$APIPATH" && chmod -R 777 "$GOPATH" WORKDIR $APIPATH COPY . . RUN \ go get -d -v \ && go install -v \ && go build EXPOSE 3000 CMD ["./api"]
ここでは、apk add
コマンドを追加して、Alpineのパッケージマネージャーを使用してGoと必要なすべてのライブラリをインストールします。 Ubuntuイメージと同様に、環境変数も設定する必要があります。
先に進み、イメージを作成します。
docker build -f Dockerfile.alpine -t alpine .
もう一度、画像サイズを確認します。
docker images
次のような出力が表示されます。
OutputREPOSITORY TAG IMAGE ID CREATED SIZE alpine latest ee35a601158d 30 seconds ago 426MB . . .
サイズは約426MBに縮小されました。
アルパインベース画像のサイズが小さいため、最終的な画像サイズは小さくなりましたが、さらに小さくするためにできることがいくつかあります。
次に、Go用に事前に作成されたAlpineイメージを使用してみてください。 これにより、Dockerfileが短くなり、最終的なイメージのサイズも小さくなります。 Go用にビルド済みのAlpineイメージは、ソースからコンパイルされたGoを使用してビルドされるため、そのフットプリントは大幅に小さくなります。
Dockerfile.golang-alpine
という名前の新しいファイルを作成することから始めます。
nano ~/mux-go-api/Dockerfile.golang-alpine
次の内容をファイルに追加します。
〜/ mux-go-api / Dockerfile.golang-alpine
FROM golang:1.10-alpine3.8 RUN apk add --no-cache --update git WORKDIR /go/src/api COPY . . RUN go get -d -v \ && go install -v \ && go build EXPOSE 3000 CMD ["./api"]
Dockerfile.golang-alpine
とDockerfile.alpine
の唯一の違いは、FROM
コマンドと最初のRUN
コマンドです。 現在、FROM
コマンドは[X36X]タグ付きのgolang
イメージを指定し、RUN
にはGitをインストールするコマンドしかありません。 Dockerfile.golang-alpine
の下部にある2番目のRUN
コマンドでgo get
コマンドを機能させるには、Gitが必要です。
次のコマンドを使用してイメージをビルドします。
docker build -f Dockerfile.golang-alpine -t golang-alpine .
画像のリストを取得します。
docker images
次の出力が表示されます。
OutputREPOSITORY TAG IMAGE ID CREATED SIZE golang-alpine latest 97103a8b912b 49 seconds ago 288MB
これで、画像サイズは約288MBになりました。
サイズを大幅に縮小できたとしても、画像を制作できるようにするためにできる最後のことが1つあります。 これは、マルチステージビルドと呼ばれます。 マルチステージビルドを使用することで、1つのイメージを使用してアプリケーションをビルドし、別のより軽いイメージを使用して、コンパイルされたアプリケーションを本番用にパッケージ化できます。このプロセスは次のステップで実行します。
ステップ5—マルチステージビルドでビルドツールを除外する
理想的には、本番環境で実行するイメージには、本番アプリケーションを実行するために冗長なビルドツールや依存関係がインストールされていない必要があります。 マルチステージビルドを使用して、これらを最終的なDockerイメージから削除できます。 これは、バイナリ、つまりコンパイルされたGoアプリケーションを中間コンテナーでビルドし、それを不要な依存関係のない空のコンテナーにコピーすることで機能します。
Dockerfile.multistage
という別のファイルを作成することから始めます。
nano ~/mux-go-api/Dockerfile.multistage
ここで追加するものはおなじみです。 Dockerfile.golang-alpine
とまったく同じコードを追加することから始めます。 ただし、今回は、最初の画像からバイナリをコピーする2番目の画像も追加します。
〜/ mux-go-api / Dockerfile.multistage
FROM golang:1.10-alpine3.8 AS multistage RUN apk add --no-cache --update git WORKDIR /go/src/api COPY . . RUN go get -d -v \ && go install -v \ && go build ## FROM alpine:3.8 COPY --from=multistage /go/bin/api /go/bin/ EXPOSE 3000 CMD ["/go/bin/api"]
ファイルを保存して閉じます。 ここに2つのFROM
コマンドがあります。 1つ目は、FROM
コマンドにAS multistage
が追加されていることを除いて、Dockerfile.golang-alpine
と同じです。 これにより、multistage
という名前が付けられ、Dockerfile.multistage
ファイルの下部で参照されます。 2番目のFROM
コマンドでは、ベースのalpine
イメージとCOPY
を、コンパイルされたGoアプリケーションのmultistage
イメージから取得します。 このプロセスにより、最終的な画像のサイズがさらに縮小され、制作の準備が整います。
次のコマンドでビルドを実行します。
docker build -f Dockerfile.multistage -t prod .
多段階ビルドを使用した後、今すぐイメージサイズを確認してください。
docker images
1つだけではなく、2つの新しい画像が見つかります。
OutputREPOSITORY TAG IMAGE ID CREATED SIZE prod latest 82fc005abc40 38 seconds ago 11.3MB <none> <none> d7855c8f8280 38 seconds ago 294MB . . .
<none>
イメージは、FROM golang:1.10-alpine3.8 AS multistage
コマンドで作成されたmultistage
イメージです。 これは、Goアプリケーションのビルドとコンパイルに使用される仲介者にすぎませんが、このコンテキストでのprod
イメージは、コンパイルされたGoアプリケーションのみを含む最終イメージです。
最初の744MBから、画像サイズを約11.3MBに縮小しました。 このような小さなイメージを追跡し、ネットワーク経由で本番サーバーに送信することは、700MBを超えるイメージよりもはるかに簡単であり、長期的にはかなりのリソースを節約できます。
結論
このチュートリアルでは、コードをコンパイルおよびビルドするために、さまざまなベースDockerイメージと中間イメージを使用して本番環境用にDockerイメージを最適化しました。 このようにして、サンプルAPIを可能な限り最小のサイズにパッケージ化しました。 これらの手法を使用して、DockerアプリケーションおよびCI/CDパイプラインのビルドとデプロイの速度を向上させることができます。
Dockerを使用したアプリケーションの構築について詳しく知りたい場合は、Dockerを使用してNode.jsアプリケーションを構築する方法のチュートリアルをご覧ください。 コンテナの最適化に関するより概念的な情報については、Kubernetes用に最適化されたコンテナの構築を参照してください。