Dockerfileでの複数のRUNと単一のチェーンRUNの違いは何ですか?


132

Dockerfile.1複数実行RUN

FROM busybox
RUN echo This is the A > a
RUN echo This is the B > b
RUN echo This is the C > c

Dockerfile.2 それらに参加します:

FROM busybox
RUN echo This is the A > a &&\
    echo This is the B > b &&\
    echo This is the C > c

それぞれRUNが1つのレイヤーを作成するので、レイヤーが少ないほど良い、つまり優れているといつも思っていDockerfile.2ました。

これは明らかにRUN、前のものRUN(つまりyum install nano && yum clean all)によって追加されたものを削除するときに当てはまりますが、すべてRUNが何かを追加する場合は、考慮すべきいくつかの点があります。

  1. レイヤーは前のレイヤーの上に差分を追加するだけなので、後のレイヤーが前のレイヤーで追加されたものを削除しない場合、両方の方法の間にディスク領域を節約する利点はあまりありません...

  2. レイヤーはDocker Hubから並行してプルされるためDockerfile.1、おそらく少し大きくなりますが、理論的にはダウンロードが速くなります。

  3. 4番目の文(つまりecho This is the D > d)を追加してローカルで再Dockerfile.1構築する場合、キャッシュにより高速に構築さDockerfile.2れますが、4つのコマンドすべてを再度実行する必要があります。

それで、質問:Dockerfileを実行するためのより良い方法はどれですか?


1
状況や画像の使用方法(サイズ、ダウンロード速度、ビルド速度の最適化)に依存するため、一般的に回答することはできません
Henry

回答:


99

可能な場合は、ファイルを作成するコマンドとそれらの同じファイルを削除するコマンドを常に1行にマージしますRUN。これは、各行RUNが画像にレイヤーを追加するためです。出力は、文字通り、docker diff作成した一時コンテナーで表示できるファイルシステムの変更です。別のレイヤーで作成されたファイルを削除した場合、ユニオンファイルシステムはファイルシステムの変更を新しいレイヤーに登録するだけで、ファイルは以前のレイヤーに存在し、ネットワーク経由で出荷され、ディスクに保存されます。したがって、ソースコードをダウンロードして抽出し、バイナリにコンパイルして、最後にtgzファイルとソースファイルを削除した場合、画像サイズを小さくするために、これをすべて1つのレイヤーで実行する必要があります。

次に、他の画像での再利用の可能性と予想されるキャッシュの使用状況に基づいて、レイヤーを個別に分割します。4つのイメージがあり、すべて同じベースイメージ(debianなど)である場合、それらのイメージのほとんどに共通のユーティリティのコレクションを最初の実行コマンドにプルして、他のイメージがキャッシュから恩恵を受けるようにすることができます。

Dockerfileでの順序は、イメージキャッシュの再利用を検討するときに重要です。非常にまれに、おそらくベースイメージが更新されたときにのみ更新されるコンポーネントを調べ、それらをDockerfileの上位に配置します。Dockerfileの終わりに向かって、ホスト固有のUIDを持つユーザーの追加やフォルダーの作成、権限の変更など、すばやく実行され頻繁に変更される可能性のあるコマンドをすべて含めます。コンテナーに、アクティブに開発されている解釈済みコード(JavaScriptなど)が含まれている場合、そのビルドはできるだけ遅く追加されるため、再構築ではその単一の変更のみが実行されます。

これらの変更の各グループでは、レイヤーを最小限に抑えるためにできる限り統合します。したがって、4つの異なるソースコードフォルダーがある場合、それらは1つのフォルダー内に配置されるため、1つのコマンドで追加できます。パッケージマネージャーのオーバーヘッド(更新とクリーンアップ)の量を最小限に抑えるために、apt-getなどのパッケージインストールは、可能な場合は単一のRUNにマージされます。


マルチステージビルドの更新:

マルチステージビルドの最終ではないステージでのイメージサイズの縮小については、それほど心配する必要はありません。これらのステージにタグが付けられておらず、他のノードに出荷されていない場合、各コマンドを個別のRUN行に分割することにより、キャッシュの再利用の可能性を最大化できます。

ただし、ステージ間でコピーするのはファイルだけであり、環境変数の設定、エントリポイント、コマンドなどのイメージメタデータの残りの部分ではないため、これはレイヤを押しつぶすための完全なソリューションではありません。また、Linuxディストリビューションにパッケージをインストールすると、ライブラリやその他の依存関係がファイルシステム全体に散らばって、すべての依存関係のコピーが困難になる場合があります。

このため、CI / CDサーバーでバイナリをビルドする代わりにマルチステージビルドを使用しているので、CI / CDサーバーにはツールを実行するだけdocker buildでよく、jdk、nodejs、go、およびインストールされている他のコンパイルツール。


30

ベストプラクティスに記載された公式の回答(公式の画像はこれらに準拠している必要があります)

レイヤー数を最小限に

Dockerfileの可読性(および長期的な保守性)と、使用するレイヤー数を最小限にすることのバランスを見つける必要があります。使用するレイヤーの数については、戦略的かつ慎重に行ってください。

ドッキングウィンドウ1.10以来COPYADDおよびRUNステートメントは、あなたのイメージに新しいレイヤーを追加します。これらのステートメントを使用するときは注意してください。コマンドを1つのRUNステートメントに結合してみてください。読みやすくするために必要な場合にのみ、これを区切ります。

詳細:https : //docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#/minimize-the-number-of-layers

アップデート:dockerのマルチステージ> 17.05

マルチステージビルドFROMでは、Dockerfileで複数のステートメントを使用できます。各FROMステートメントはステージであり、独自の基本イメージを持つことができます。最終ステージでは、alpineなどの最小限のベースイメージを使用し、前のステージからビルドアーティファクトをコピーして、ランタイム要件をインストールします。このステージの最終結果はあなたのイメージです。したがって、ここで、前述のようにレイヤーについて心配します。

いつものように、Dockerにはマルチステージビルドに関する優れたドキュメントがあります。ここに簡単な抜粋があります:

マルチステージビルドでは、Dockerfileで複数のFROMステートメントを使用します。各FROM命令は異なるベースを使用でき、それぞれがビルドの新しいステージを開始します。あるステージから別のステージにアーティファクトを選択的にコピーして、最終的なイメージに不要なものをすべて残すことができます。

これに関する素晴らしいブログ投稿は、https//blog.alexellis.io/mutli-stage-docker-builds/にあります。

あなたのポイントに答えるには:

  1. はい、レイヤーは差分のようなものです。変更がまったくない場合は、レイヤーが追加されているとは思いません。問題は、いったんレイヤー2に何かをインストールまたはダウンロードすると、レイヤー3でそれを削除できないことです。したがって、レイヤーに何かを書き込んだ後は、それを削除しても画像サイズを小さくすることはできません。

  2. レイヤーを並行してプルできるため、潜在的に高速になりますが、ファイルを削除している場合でも、各レイヤーは間違いなく画像サイズを増やします。

  3. はい、Dockerファイルを更新する場合はキャッシュが役立ちます。しかし、それは一方向に機能します。レイヤーが10個あり、レイヤー#6を変更した場合でも、レイヤー#6-#10からすべてを再構築する必要があります。したがって、ビルドプロセスを高速化することはそれほど頻繁ではありませんが、イメージのサイズを不必要に大きくすることが保証されています。


この回答を更新するよう通知してくれた@Mohanに感謝します。


1
これは現在古くなっています-以下の回答を参照してください。
Mohan

1
@Mohan、リマインダーをありがとう!ユーザーを支援するために投稿を更新しました。
Menzo Wijmenga 2017

19

上記の回答は古くなっているようです。ドキュメントのメモ:

Docker 17.05より前、さらにはDocker 1.10より前では、イメージのレイヤー数を最小限に抑えることが重要でした。次の改善により、この必要性が緩和されました。

[...]

Docker 17.05以降では、マルチステージビルドのサポートが追加され、必要なアーティファクトのみを最終的なイメージにコピーできるようになりました。これにより、最終的なイメージのサイズを増やすことなく、中間ビルドステージにツールとデバッグ情報を含めることができます。

https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minimize-the-number-of-layers

そして

この例では、Bash &&演算子を使用して2つのRUNコマンドを人工的に一緒に圧縮し、画像に追加のレイヤーを作成しないようにしています。これは障害が発生しやすく、保守が困難です。

https://docs.docker.com/engine/userguide/eng-image/multistage-build/

ベストプラクティスは、マルチステージビルドを使用してDockerfilesを読みやすくするように変更されたようです。


マルチステージビルドはバランスを保つための優れたオプションのようですが、この問題に対する実際の修正は、docker image build --squashオプションが実験的でない場合に行われます。
Yajo

2
@矢城-私はsquash実験的な過去を取得することについて懐疑的です。多くの仕掛けがあり、マルチステージビルドの前にのみ意味があります。マルチステージビルドでは、非常に簡単な最終ステージを最適化するだけで済みます。
Menzo Wijmenga 2017

1
@八条それをさらに詳しく言うと、最終段階のレイヤーだけが最終的な画像のサイズに違いをもたらします。したがって、すべてのビルダーグビンを初期段階に置き、最終段階でパッケージをインストールして、初期段階のファイル間でコピーするだけの場合、すべてが美しく機能し、スカッシュは必要ありません。
モハン

3

画像レイヤーにどのように含めるかによって異なります。

重要なポイントは、できるだけ多くのレイヤーを共有することです。

悪い例:

Dockerfile.1

RUN yum install big-package && yum install package1

Dockerfile.2

RUN yum install big-package && yum install package2

良い例え:

Dockerfile.1

RUN yum install big-package
RUN yum install package1

Dockerfile.2

RUN yum install big-package
RUN yum install package2

もう1つの提案は、削除が追加/インストールアクションと同じレイヤーで発生する場合にのみ、それほど有用ではないことです。


これら2つは実際にRUN yum install big-packagefromキャッシュを共有しますか?
Yajo

はい、同じベースから始めれば、同じレイヤーを共有します。
OndraŽižka18年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.