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

_著者はhttps://www.brightfunds.org/organizations/code-org[Code.org]を選択して、https://do.co/w4do-cta [Write for DOnations]プログラムの一環として寄付を受け取りました。

前書き

実稼働環境では、https://www.docker.com/ [Docker]を使用すると、コンテナ内でアプリケーションを簡単に作成、デプロイ、実行できます。 コンテナを使用すると、開発者はアプリケーションとそのすべてのコアの必要性と依存関係を単一のパッケージに収集し、Dockerイメージに変換して複製することができます。 Dockerイメージはhttps://docs.docker.com/develop/develop-images/dockerfile_best-practices/[Dockerfiles]から構築されます。 Dockerfileは、イメージの外観、イメージの基本オペレーティングシステム、イメージ内で実行するコマンドを定義するファイルです。

大きなDockerイメージは、クラスターとクラウドプロバイダー間でイメージを構築および送信するのにかかる時間を長くする可能性があります。 たとえば、開発者の1人がビルドをトリガーするたびにプッシュするギガバイトサイズのイメージがある場合、ネットワークで作成したスループットはCI / CDプロセス中に追加され、アプリケーションが遅くなり、最終的にリソースが消費されます。 このため、実稼働に適したDockerイメージには、最低限必要なもののみをインストールする必要があります。

Dockerイメージのサイズを小さくして本番用に最適化する方法はいくつかあります。 まず、これらの画像は通常、アプリケーションを実行するためのビルドツールを必要としないため、追加する必要はまったくありません。 multi-stage build processを使用することにより、中間イメージを使用してコードをコンパイルおよびビルドし、依存関係をインストールし、すべてをパッケージ化できます。可能な限り小さいサイズにしてから、ビルドツールを使用せずに、アプリケーションの最終バージョンを空のイメージにコピーします。 さらに、https://alpinelinux.org/about/ [Alpine Linux]のような小さなベースでイメージを使用できます。 Alpineは、アプリケーションを実行するために必要な最低限の要件のみを備えているため、実稼働に適したLinuxディストリビューションです。

このチュートリアルでは、いくつかの簡単な手順でDockerイメージを最適化し、より小さく、高速で、生産に適したものにします。 サンプルhttps://github.com/do-community/mux-go-api[Go API]のイメージを、Ubuntuおよび言語固有のイメージから始めて、Alpineに移動して、いくつかの異なるDockerコンテナーでビルドします。分布。 また、マルチステージビルドを使用して、生産用にイメージを最適化します。 このチュートリアルの最終目標は、デフォルトのUbuntuイメージと最適化された同等物の使用のサイズの違いを示し、マルチステージビルドの利点を示すことです。 このチュートリアルを読んだ後、これらの手法を独自のプロジェクトおよびCI / CDパイプラインに適用できるようになります。

前提条件

開始する前に、次のものが必要です。

  • `+ sudo +`特権を持つ非rootユーザーアカウントを持つUbuntu 18.04サーバー。 ガイダンスについては、https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-18-04 [Ubuntu 18.04での初期サーバー設定]チュートリアルに従ってください。 このチュートリアルはUbuntu 18.04でテストされましたが、Linuxディストリビューションで多くの手順を実行できます。

  • サーバーにインストールされたDocker。 Ubuntu 18.04でDockerをインストールして使用する方法インストール手順。

ステップ1-サンプルGo APIのダウンロード

Dockerイメージを最適化する前に、まずDockerイメージを作成するhttps://github.com/do-community/mux-go-api[sample API]をダウンロードする必要があります。 シンプルなGo APIを使用すると、Dockerコンテナー内でアプリケーションを構築および実行するためのすべての重要な手順が示されます。 このチュートリアルでは、https://en.wikipedia.org/wiki/C%2B%2B [C ++]またはhttps://www.java.com/en/[Java]のようなコンパイルされた言語であるGoを使用します。 、フットプリントが非常に小さい。

サーバーで、サンプルGo APIのクローンを作成することから始めます。

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をシミュレートするのに適しています。

サンプルGo APIをダウンロードしたので、ベースの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-

ENV GOROOT /usr/lib/go-
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依存関係をインストールします。

ファイルを保存して終了します。 これで、 `+ 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
. . .

出力で強調表示されているように、この画像のサイズは基本的なGolang APIの* 636MB *で、数値はマシンごとにわずかに異なる場合があります。 複数のビルドで、この大きなサイズは展開時間とネットワークスループットに大きく影響します。

このセクションでは、ステップ1で複製したAPIを実行するために必要なすべてのGoツールと依存関係を使用してUbuntuイメージを構築しました。 次のセクションでは、ビルド済みの言語固有のDockerイメージを使用して、Dockerfileを簡素化し、ビルドプロセスを合理化します。

手順3-言語固有のベースイメージの構築

事前に作成されたイメージは、ユーザーが状況固有のツールを含めるために変更した通常のベースイメージです。 その後、ユーザーはこれらのイメージをhttps://hub.docker.com/[Docker Hub]イメージリポジトリにプッシュして、他のユーザーが独自のDockerfilesを作成する代わりに共有イメージを使用できるようにします。 これは実稼働環境での一般的なプロセスであり、ほとんどすべてのユースケースに対応するさまざまなビルド済みイメージをDocker Hubで見つけることができます。 この手順では、コンパイラと依存関係が既にインストールされているGo固有のイメージを使用して、サンプルAPIを構築します。

アプリのビルドと実行に必要なツールがすでに含まれているビルド済みのベースイメージを使用すると、ビルド時間を大幅に短縮できます。 必要なすべてのツールが事前にインストールされているベースから開始しているため、Dockerfileへのこれらの追加をスキップして、見た目をずっときれいにし、最終的にビルド時間を短縮できます。

先に進み、別のDockerfileを作成して、 `+ Dockerfile.golang +`という名前を付けます。 テキストエディタで開きます。

nano ~/mux-go-api/Dockerfile.golang

このファイルには、Go固有の依存関係、ツール、およびコンパイラがすべてプリインストールされているため、以前のファイルよりも大幅に簡潔になります。

ここで、次の行を追加します。

〜/ mux-go-api / Dockerfile.golang

FROM golang:

WORKDIR /go/src/api
COPY . .

RUN \
   go get -d -v \
   && go install -v \
   && go build

EXPOSE 3000
CMD ["./api"]

最初から、 `+ FROM `ステートメントが ` golang:+`になっていることがわかります。 つまり、Dockerは、必要なすべてのGoツールがすでにインストールされているDocker Hubから事前に作成されたGoイメージをフェッチします。

もう一度、次のコマンドを使用してDockerイメージをビルドします。

docker build -f Dockerfile.golang -t golang .

次のコマンドで画像の最終サイズを確認します。

docker images

これにより、次のような出力が生成されます。

OutputREPOSITORY  TAG     IMAGE ID        CREATED         SIZE
golang      latest  eaee5f524da2    40 seconds ago
. . .

Dockerfile自体はより効率的で、ビルド時間は短くなりますが、実際には合計イメージサイズは増加します。 ビルド済みのGolangイメージは約* 744MB *で、かなりの量です。

これは、Dockerイメージを構築するための好ましい方法です。 指定された言語(この場合はGo)に使用する標準としてコミュニティが承認した基本イメージを提供します。 ただし、本番用にイメージを準備するには、実行中のアプリケーションに不要な部分を切り取る必要があります。

ニーズがわからない場合は、これらの重い画像を使用しても問題ないことを忘れないでください。 使い捨てコンテナとしてだけでなく、他のイメージを構築するためのベースとしても自由に使用できます。 開発やテストの目的で、ネットワーク経由で画像を送信する必要がない場合、重い画像を使用しても問題ありません。 ただし、展開を最適化する場合は、画像をできるだけ小さくするために最善を尽くす必要があります。

言語固有のイメージをテストしたので、次のステップに進むことができます。このステップでは、軽量のAlpine Linuxディストリビューションをベースイメージとして使用して、Dockerイメージを軽くします。

ステップ4-ベースの高山画像の構築

Dockerイメージを最適化する最も簡単な手順の1つは、より小さな基本イメージを使用することです。 Alpineは、セキュリティとリソース効率のために設計された軽量のLinuxディストリビューションです。 Alpine Dockerイメージは、https://www.musl-libc.org/ [musl libc]およびhttps://busybox.net/about.html[BusyBox]を使用してコンパクトな状態を維持し、コンテナーで実行するのに必要なのは8MB以下です。 。 この小さなサイズは、バイナリパッケージが間引かれて分割されるためです。これにより、インストールするものをより細かく制御でき、環境を可能な限り小さく効率的に保つことができます。

Alpineイメージを作成するプロセスは、ステップ2でUbuntuイメージを作成した方法と似ています。 最初に、 `+ Dockerfile.alpine +`という新しいファイルを作成します。

nano ~/mux-go-api/Dockerfile.alpine

次に、このスニペットを追加します。

〜/ mux-go-api / Dockerfile.alpine

FROM alpine:

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 *になりました。

Alpineベース画像のサイズが小さいため、最終的な画像サイズが小さくなりましたが、さらに小さくするためにできることがいくつかあります。

次に、Go用のビルド済みのAlpineイメージを使用してみてください。 これにより、Dockerfileが短くなり、最終的な画像のサイズも小さくなります。 Go用のビルド済みのAlpineイメージは、ソースからコンパイルされたGoでビルドされているため、フットプリントが大幅に小さくなります。

「++」という名前の新しいファイルを作成することから始めます。

nano ~/mux-go-api/Dockerfile.golang-alpine

ファイルに次の内容を追加します。

〜/ mux-go-api / Dockerfile.golang-alpine

FROM golang:

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 +`コマンドは `+`タグで `+ golang +`イメージを指定し、 `+ RUN +`にはhttps://git-scm.com/[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 *になりました。

サイズを大幅に削減できたとしても、画像の制作準備を整える最後の方法があります。 マルチステージビルドと呼ばれます。 マルチステージビルドを使用すると、1つのイメージを使用してアプリケーションをビルドし、別のより軽いイメージを使用して、コンパイル済みアプリケーションを実稼働用にパッケージ化できます。これは、次のステップで実行します。

ステップ5-マルチステージビルドでビルドツールを除外する

実稼働環境で実行するイメージには、ビルドツールをインストールしたり、実稼働アプリケーションを実行するために冗長な依存関係を持たせたりしないことが理想的です。 マルチステージビルドを使用して、最終的なDockerイメージからこれらを削除できます。 これは、バイナリ、つまりコンパイルされたGoアプリケーションを中間コンテナーに構築し、不要な依存関係のない空のコンテナーにコピーすることで機能します。

「++」という別のファイルを作成することから始めます。

nano ~/mux-go-api/Dockerfile.multistage

ここで追加する内容はおなじみのものです。 `+ Dockerfile.golang-alpine +`とまったく同じコードを追加することから始めます。 ただし、今回は、最初の画像からバイナリをコピーする2番目の画像も追加します。

〜/ mux-go-api / Dockerfile.multistage

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

##

FROM alpine:3.8
COPY  /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 」画像と、「 multistage」画像からコンパイルされたGoアプリケーション上に「+ COPY +」を取り込みます。 このプロセスにより、最終イメージのサイズがさらに削減され、本番環境で使用できるようになります。

次のコマンドでビルドを実行します。

docker build -f Dockerfile.multistage -t prod .

マルチステージビルドを使用した後、ここでイメージサイズを確認します。

docker images

1つだけではなく2つの新しい画像があります。

OutputREPOSITORY      TAG     IMAGE ID        CREATED         SIZE
prod            latest  82fc005abc40    38 seconds ago
<none>          <none>  d7855c8f8280    38 seconds ago
. . .

`+ <none> `イメージは、 ` FROM golang:1.10-alpine3.8 `コマンドで作成された ` multistage `イメージです。 これはGoアプリケーションのビルドとコンパイルに使用される中間体にすぎませんが、このコンテキストの ` prod +`イメージは、コンパイルされたGoアプリケーションのみを含む最終イメージです。

最初の* 744MB から、画像サイズを約 11.3MB *に削減しました。 このような小さな画像を追跡し、ネットワーク経由で実稼働サーバーに送信することは、700MBを超える画像を使用するよりもはるかに簡単になり、長期的には大幅なリソースを節約できます。

結論

このチュートリアルでは、異なるベースDockerイメージと中間イメージを使用してコードをコンパイルおよびビルドし、生産用にDockerイメージを最適化しました。 このようにして、サンプルAPIを可能な限り小さいサイズにパッケージ化しました。 これらの手法を使用して、DockerアプリケーションとCI / CDパイプラインの構築と展開の速度を向上させることができます。

Dockerを使用したアプリケーションの構築について詳しく知りたい場合は、https://www.digitalocean.com/community/tutorials/how-to-build-a-node-js-application-with-docker [How To DockerでNode.jsアプリケーションを構築する]チュートリアル。 コンテナの最適化に関する概念的な情報については、https://www.digitalocean.com/community/tutorials/building-optimized-containers-for-kubernetes [Kubernetes用の最適化されたコンテナの構築]を参照してください。

Related