本番環境用にDockerイメージを最適化する方法

提供:Dev Guides
移動先:案内検索

著者は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イメージは、 musllibcBusyBoxを使用してコンパクトな状態を維持し、コンテナーでの実行に必要なのは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-alpineDockerfile.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用に最適化されたコンテナの構築を参照してください。