ドッカーファイルのベストプラクティスの紹介ガイド

現在、GitHub には 100 万を超える Dockerfile がありますが、すべての Dockerfile が同じように作成されているわけではありません。 効率性は非常に重要であり、このブログ シリーズでは、より優れた Dockerfile の作成に役立つ Dockerfile のベスト プラクティスの 5 つの領域 (増分ビルド時間、イメージ サイズ、保守性、セキュリティ、再現性) について説明します。Dockerを使い始めたばかりの場合は、この最初のブログ投稿が最適です。シリーズの次の投稿はさらに高度になります。

重要な注意事項: 以下のヒントは、Mavenに基づくサンプルJavaプロジェクトのためにDockerfileを改善し続ける旅をたどります。 ザ 最後のドッカーファイル したがって、推奨されるDockerfileですが、すべての中間ファイルは特定のベストプラクティスを説明するためだけにあります。

増分ビルド時間

開発サイクルでは、Docker イメージをビルドし、コードを変更してから再構築するときに、キャッシュを活用することが重要です。 キャッシュは、不要なビルド ステップを再度実行しないようにするのに役立ちます。

ヒント#1:キャッシュの順序の問題

EF41db8f fe5e 4a78 940a 6a929db7929d 1

ただし、ファイルを変更したり Dockerfile 内の行を変更したりしてステップのキャッシュを無効にすると、 キャッシュの後続のステップが壊れるため、ビルド ステップの順序 (Dockerfile 命令) は重要です。キャッシュを最適化するために、変更頻度の低いステップから最も頻繁に変更されるステップにステップを並べ替えます。

ヒント#2:キャッシュバストを制限するためのより具体的なCOPY

0c1d0c4e 406c 468c b6ba b71ac68b9c84

必要なものだけをコピーします。 可能であれば、"COPY" は避けてください。 ファイルをイメージにコピーするときは、コピーする内容を具体的にしてください。 コピーされるファイルに変更を加えると、キャッシュが破損します。 上記の例では、イメージ内には事前に構築されたjarアプリケーションのみが必要なため、それをコピーするだけです。 そうすれば、無関係なファイルの変更がキャッシュに影響を与えません。

ヒント #3: apt-get update & install などのキャッシュ可能なユニットを特定する

2322A39E BD7E 4A2B 9A8F 548A97DBACB4

各 RUN 命令は、キャッシュ可能な実行単位と見なすことができます。 それらの数が多すぎると不要になる可能性がありますが、すべてのコマンドを1つのRUN命令にチェーンすると、キャッシュが簡単に破壊され、開発サイクルが損なわれる可能性があります。 パッケージマネージャーからパッケージをインストールする場合、常にインデックスを更新し、同じRUNにパッケージをインストールする必要があります:それらは一緒に1つのキャッシュ可能なユニットを形成します。 そうしないと、古いパッケージをインストールする危険があります。

画像サイズを小さくする

イメージが小さいほどデプロイが速くなり、攻撃対象領域が小さくなるため、イメージのサイズが重要になる可能性があります。

ヒント#4:不要な依存関係を削除する

A1b36f64 1a30 45bf 8fcd 4f88437c189e

不要な依存関係を削除し、デバッグ ツールをインストールしないでください。 必要に応じて、デバッグ ツールは後でいつでもインストールできます。 aptなどの特定のパッケージマネージャーは、ユーザーが指定したパッケージで推奨されているパッケージを自動的にインストールし、フットプリントを不必要に増やします。 Apt には –no-install-recommendations フラグがあり、実際には必要のない依存関係がインストールされないようにします。必要な場合は、明示的に追加します。

ヒント#5:パッケージマネージャーのキャッシュを削除する

363961a4 005e 46fc 963b f7b690be12ef

パッケージマネージャーは、最終的にイメージに含まれる可能性のある独自のキャッシュを維持します。 これに対処する1つの方法は、パッケージをインストールしたのと同じRUN命令のキャッシュを削除することです。 別のRUN命令で削除しても、画像サイズは小さくなりません。

このブログ投稿の最後に説明するマルチステージビルドなど、画像サイズを縮小する方法は他にもあります。 次の一連のベスト プラクティスでは、Dockerfile の保守性、セキュリティ、再現性を最適化する方法について説明します。

保守性

ヒント#6:可能な場合は公式画像を使用する

F336014d d2aa 4c1b a2bd e1d5d6ed0d93

公式イメージは、すべてのインストール手順が完了し、ベストプラクティスが適用されるため、メンテナンスに費やす時間を大幅に節約できます。 複数のプロジェクトがある場合、それらはまったく同じ基本イメージを使用するため、それらのレイヤーを共有できます。

ヒント#7:より具体的なタグを使用する

9D991DA9 BDB9 4108 8B36 296A5A3772AA

最新のタグは使用しないでください。 Docker Hubの公式イメージで常に利用できるという便利さがありますが、時間の経過とともに重大な変更が発生する可能性があります。 キャッシュなしで Dockerfile を再構築する時間間隔によっては、ビルドが失敗する可能性があります。

代わりに、基本イメージに対してより具体的なタグを使用します。 この場合、openjdk を使用しています。 利用可能なタグは他にもたくさんあるので、 既存のすべてのバリアントをリストしたそのイメージの Docker Hubドキュメント を確認してください。

ヒント#8:最小限のフレーバーを探す

6c486200 5198 4457 86c0 b5275e70e699

これらのタグの一部 は最小限のフレーバーを持っているため、さらに小さな画像になります。スリムなバリアントはDebianを取り除いたものに基づいていますが、アルパインバリアントはさらに小さなAlpine Linuxディストリビューションイメージに基づいています。顕著な違いは、Debian が依然として GNU libc を使用しているのに対し、alpine は musl libc を使用していることです。openjdk の場合、jre フレーバーには Java ランタイムのみが含まれ、SDK は含まれません。これにより、画像サイズも大幅に縮小されます。

再生可能性

これまでのところ、上記のDockerfilesは、jarアーティファクトがホスト上に構築されていることを前提としています。 これは、コンテナーによって提供される一貫した環境の利点が失われるため、理想的ではありません。 たとえば、Javaアプリケーションが特定のライブラリに依存している場合、アプリケーションがビルドされているコンピューターによっては、望ましくない不整合が発生する可能性があります。

ヒント#9:一貫した環境でソースからビルドする

ソース コードは、Docker イメージを構築するための信頼できる情報源です。 Dockerfile は単なる青写真です。

F393ad07 c25d 4241 a40f c6168e0ba4dd

まず、アプリケーションの構築に必要なものをすべて特定する必要があります。 この単純なJavaアプリケーションにはMavenとJDKが必要なので、JDKを含むDocker Hubの特定の最小限の公式Mavenイメージに基づいてDockerfileを作成しましょう。 さらに依存関係をインストールする必要がある場合は、RUN ステップでインストールできます。

pom.xml フォルダーと src フォルダーは、を使用してアプリ.jarアプリケーションmvn packageを生成する最後の RUN ステップで必要に応じてコピーされます。(-eフラグはエラーを表示し、-Bは非対話型別名「バッチ」モードで実行します)。

一貫性のない環境の問題を解決しましたが、コードが変更されるたびに、pom.xmlに記述されたすべての依存関係がフェッチされるという別の問題を導入しました。 したがって、次のヒント。

ヒント#10:別のステップで依存関係を取得する

41ea71ce 11c3 42a3 8d2b 05fe20901745

キャッシュ可能な実行単位の観点からもう一度考えることで、依存関係のフェッチは、ソースコードではなく、pomへの変更にのみ依存する必要がある別のキャッシュ可能な単位であると判断できます.xml。 2つのCOPYステップ間のRUNステップは、依存関係のみをフェッチするようにMavenに指示します。

一貫性のある環境でビルドすることによって導入されたもう1つの問題があります:実行時に必要のないすべてのビルド時の依存関係が含まれているため、イメージは以前よりもはるかに大きくなっています。

ヒント #11: マルチステージ ビルドを使用してビルドの依存関係を削除する (推奨 Dockerfile)

97ec1992 f0df 4c8f 82a0 e177c230e5c5

複数ステージのビルドは、複数の FROM ステートメントで認識できます。 各FROMは新しいステージを開始します。 これらは、後で参照する最初のステージ「ビルダー」に名前を付けるために使用するASキーワードで名前を付けることができます。 これには、一貫した環境でのすべてのビルド依存関係が含まれます。

第2段階は最終段階であり、最終的な画像になります。 これには、ランタイムに必要な厳密さ、この場合はAlpineに基づく最小限のJRE(Javaランタイム)が含まれます。 中間ビルダー ステージはキャッシュされますが、最終的なイメージには存在しません。 ビルドアーティファクトを最終的なイメージに取り込むには、 COPY --from=STAGE_NAMEを使用します。この場合、STAGE_NAMEビルダーです。

80c3c350 5f7e 4cf1 ab3e 89df755b3c33

マルチステージ ビルドは、ビルド時の依存関係を削除するための頼りになるソリューションです。

一貫性のない肥大化したイメージの構築から、キャッシュに対応しながら一貫した環境で最小限のイメージを構築することに移行しました。 次のブログ投稿では、マルチステージビルドの他の用途について詳しく説明します。

追加のリソース: