これは、Docker Image Deep Dive and Build Best Practices Tech Talk です。
目次
- イントロ (0:05)
- ユニオンファイルシステムを理解する (1:59)
- ユニオンファイルシステムの用語 (4:52)
- イメージを手動で作成する (6:29)
- レイヤーの可視化 (8:44)
- イメージ構築のベストプラクティス (10:21)
- イメージゴール (12:40)
- ビルドキャッシュ(15:50)
- キャッシュが壊れる場所を特定する (17:47)
- マルチステージビルド (21:41)
- 最新のビルドとDockerfileの機能(24:59)
- マルチアーキテクチャ (26:57)
- Docker Desktop GUI ビルドビュー (29:44)
- 詳細情報
イントロ (0:05)
Docker イメージは、輸送用コンテナと似ています。 輸送用コンテナには、あらゆる種類のものを入れることができます。 食べ物を持つことも、家具を置くことも、建築資材を持つこともできる。 Docker イメージも同じですが、ソフトウェアを除きます。 Docker イメージに Web サーバーを含めることも、アプリケーション、データベース、メッセージ キューなど、さまざまなものを持つこともできます。 そのため、イメージは、その内部に存在する可能性のあるこれらすべての異なるタイプのソフトウェアを処理できるほど柔軟性がある必要があります。 さて、 2015年にさかのぼると、DockerはOpen Container Initiativeを作成しました。 Open Container Initiativeは、コンテナに関する一連の標準です。 現在は Linux Foundation が所有しており、3 つの仕様があります。 イメージ仕様があり、イメージの構造とマニフェストを記述しています。 OCIイメージの実行方法を定義するランタイム仕様と、コンテンツをプッシュ、プル、検出するためのAPIプロトコルを定義する配布仕様があります。 画像の詳細に入る前に、よく聞かれる質問が1つあります。それは、画像にはさまざまなアーキテクチャがある理由です。 コンテナは特定のカーネルと特定のチップセットに関連付けられたプロセスであるため、異なる OS で異なるチップセットをサポートできるようにするには、異なるバージョンが存在する必要があります。 そのため、画像の下にアーキテクチャタグが表示されます。 これで、特定のマシンにイメージをプルダウンすると、どのアーキテクチャからプルダウンしているかが自動的に認識され、正しいイメージがプルダウンされるため、これを気にせずに実行できます。 しかし、イメージを構築している場合は、そのイメージを実際に実行するアーキテクチャについて心配する必要があります。 それについては、プレゼンテーションで詳しく説明します。
ユニオンファイルシステムを理解する (1:59)
さて、それでは、イメージを構成するユニオン ファイル システムと、イメージの構造を理解しましょう。 したがって、ここではnginxイメージをプルする例を示します。 そして、ここの右側を見ると、自分で引っ張るとこんな感じになります。 ここには文字と数字の異なる行がいくつかあり、プルとプルと書かれており、プルコンプリートと書かれています。 これらに「すでに存在する」と表示される場合があります。 そこで問題となるのは、これらのものが実際に引っ張られているのは何なのか、そしてそれがどのようにしてコンテナになるのかということです。
ええと、これらのものの一つ一つがレイヤーであり、レイヤーは、互いに重なり合うファイルシステムの変更のセットです。 したがって、ここを見ると、レイヤー 1、レイヤー 2、およびマージされたレイヤーが表示されます。 各レイヤーは、ファイルを追加、削除、または削除できます。 さて、ユニオンされたファイルシステムでは、これらすべてが一緒にプルされます。 ここのレイヤー 1では、ファイル 1、ファイル 2、ファイル 3、ファイル 4を追加しています。 レイヤー 2 は、新しいバージョンのファイル 2を追加し、ここにもファイル 5 を追加します。 したがって、マージされたバージョンでは、このイメージがコンテナとして実行される場合に存在するもの、ファイル1、レイヤー2からのファイル2、ファイル3、ファイル4、およびファイル5になります。これらのレイヤーをそれぞれ取得し、それらをマージされたバージョンにまとめます。 このようにすることにはいくつかの利点がありますが、これについてさらに進むにつれて説明します。
では、削除されたファイルについてはどうでしょうか? より高度なレイヤーのファイルを削除する必要がある場合はどうすればよいですか? さて、それがどのようなものになるか見てみましょう。 ここにはレイヤー 3があり、レイヤー 1から何かを削除する必要があります。 これで、各レイヤーは、書き込まれると不変になります。 レイヤーは、実際にファイルシステムに書き込まれた後は変更できません。 したがって、レイヤー 3では、ファイル 4を取り除く必要がある場合、私が行うことは、インクが書かれた紙があり、それを上書きする必要がある場合と非常によく似ています。 ホワイトのボトルを取り出して、そのテキストを紙に覆い、その上に書きます。 それがまさに私たちがここで行っていることです。 レイヤー 3に存在するホワイトアウトファイルを作成しているため、マージされたファイルシステムでは、実行中のコンテナでファイル 4 が表示されなくなります。 さて、ここで理解すべき非常に重要な部分は、ファイル 4 がまだレイヤー 1に存在するということです。 レイヤー 1 は、実際にファイルシステムに書き込まれた時点から不変です。 したがって、マージされたバージョンで表示されないからといって、まだそこにないわけではありません。 これについては、セキュリティに関する懸念については、後ほど説明します。
ユニオンファイルシステムの用語 (4:52)
では、ここでの用語を見て、何を言っているのかを理解しましょう。 書き込まれたレイヤーは下位ディレクトリです。 私が言ったように、それらは不変です。 これらは読み取り専用です。 上位ディレクトリは書き込み可能なスペースです。 したがって、この特定のイメージを積極的にビルドしたり、コンテナで実行して実際に変更を加えたりすると、その上位ディレクトリに移動します。 そして、マージされたディレクトリは、コンテナが実際に使用しているファイルシステムの表現です。 さて、このすべての情報を見たい場合、コンテナで docker inspect を実行します。
さっそくやってみましょう。 ここでは、DockerDesktopのGUIを使用しています。 ここではバージョン 4で見ることができます。35、今は端末があります。 だから私はこのターミナルを開くつもりです。 そして、私がやろうとしていることは、この特定のコンテナでdocker inspectを行うことです。 そして、コンテナIDをコピーして、ここにドロップします。 これで、inspect は、この特定のコンテナに関するあらゆる種類の情報を提供します。 そのすべての情報が必要なわけではありません。 ここで探す部分は、グラフドライバーと呼ばれるものです。 そして、私たちはそれをここで見ることになります。 グラフ ドライバーは、実際には各レイヤーの位置を示しています。 したがって、ここでは下位ディレクトリと、それらすべての特定のディレクトリ、マージされたディレクトリ、上位ディレクトリ、作業ディレクトリを確認できます。 これらすべてがここにレイアウトされています。 これは、これらの特定のレイヤーのそれぞれが実際にファイルシステムのどこに保存されているかを実際に示しています。 これはコンテナ情報の一部です
イメージを手動で作成する (6:29)
手動で画像を作成できることをご存知ですか? docker run を実行して、新しいコンテナを開始できます。docker exec と docker cp を実行して、上位ディレクトリで実行中のコンテナに再度変更を加えることができます。そして、 docker commit を実行して、そのファイルシステムをイメージとして保存できます。 ここで、これは画像を作成する推奨される方法ではないことを非常に明確にしたいと思います。 これは、そのようなことをデバッグしている場合に 1 回限りで実行できるものです。 しかし、ほとんどの場合、Dockerfileを使用することを望みます。 Dockerfile は、実際に Docker イメージを作成するための反復可能な標準化された方法です。 Dockerfile には、その特定のイメージをビルドする方法を実際に理解するのに役立ついくつかのコマンドがあります。 私たちは、その一つ一つを見ていきます。
最初のものは FROM ステートメントです。 FROM ステートメントは、開始するベースを提供します。 基本イメージと呼ばれるもの。 右側の例では、ベースとしてFROMUbuntuを使用していることがわかります。 次に行うことは、イメージに WORKDIR (作業ディレクトリ)を設定することです。 コンテナとコンテナイメージは分離されたファイルシステムであるため、その作業ディレクトリは任意のものにすることができます。 この場合は /usr/local/app です。 /app の場合もあります。/opt の場合もあります。これもまた分離されたファイルシステムであるため、なりたいものなら何でもかまいません。 次は 、COPY コマンドと RUN コマンドです。 そして、これらはいくつでも行うことができます。 必要な作業に応じて、複数の実行または複数のコピー、または前後に実行できます。 コピーは明らかに、ホスト・ファイル・システムまたはその他の場所からコンテナ・イメージ・ファイル・システムにコピーしています。 実行は、実際にはそのイメージ ファイル システム内で物事を実行しています。 それは、コードのコンパイルやツールのインストールなど、何でもかまいません。 最後に、 CMD (コマンド)を設定します。 このコマンドは、このイメージをコンテナとして起動したときに実行されるデフォルトのコマンドになります。 そして、これをビルドするために、 docker build コマンドを使用します。 これは、イメージを構築するための標準化された方法です。
レイヤーの可視化 (8:44)
さて、次は、これらのレイヤーを実際に見るのはどうでしょうか? 画像がある場合、これらのレイヤーを表示して詳細を取得できますか? はい、これを行うにはいくつかの方法があります。 各レイヤーごとに詳細を提供する Dockerイメージ履歴 があります。 実際にGUIに移動して情報を確認できます。 また、ダイブツールのように、ファイルシステムをレイヤーごとに実際に表示する他のオープンソースツールもあります。 それでは、画像の例をいくつか見てみましょう。 Docker Desktopに戻ると、ターミナルをクリアします。 私の画像に行きます。 私のモンゴを探しに行かせてください、ここにあります。 そして、私はただdockerイメージヒストリーモンゴを行います。 そして、この特定の画像のさまざまなレイヤーのそれぞれが表示されます。 ここでも、各レイヤーを作成したコマンドが表示されます。 実際にそれを見ると、各レイヤーのサイズを確認できます。 これらのいくつかは何もないことがわかりますが、その特定のレイヤーで実際に何が起こっているかによっては、それよりも大幅に多いものもあります。 また、GUIでここにアクセスすることもできます。 これをクリックすると、実際に中に入ると、すべてのレイヤー、すべての異なるレイヤーが表示されます 26 。 そして、この特定のパッケージのすべての脆弱性を確認できます。 脆弱性が何であるか、また各脆弱性がどのレイヤーにあるかも確認できます。
イメージ構築のベストプラクティス (10:21)
さて、画像の構造とそれが何であるか、そしてなぜそうなのかについて少し理解したところで、画像のベストプラクティスについて話し始めましょう。 最初の1つは、おそらくあなたの多くがすでにこれを推測している、あなたの画像に秘密を含めないでください。 なぜなら、後で画像から削除しても、常にそこにあるからです。 画像レイヤーは、画像に書き込むとすぐに不変であるため、永遠に存在します。 決して、決して、秘密にコピーして、後でそれらの種類のものを削除することはありません。 シークレットは常に実行時にコンテナに提供したいものであり、イメージ自体にベイクされるものではありません。
次はベースイメージを使用することです。 基本イメージを使用している場合は、その基本イメージがどこから来たのかを信頼する必要があります。 そのベースイメージは、それがどこから来て誰が作成したかを知っているものであることを確認する必要があります。 これは、Dockerの信頼できるコンテンツのようなものです - Dockerの公式イメージやDocker検証済みの発行者イメージ、または管理されたイメージがこの特定の部分に役立ちます。 または、それを提供する他のベンダーもあります。 または、独自のベースイメージを作成することもできます。 それもオプションです。 しかし、どのような方法でも、そのベースイメージがどこから来ているのかを知る必要があります。
次は最新のタグです。 latest タグがデフォルトのタグです。 そして、ご存知のように、それは非常に使いやすいです。 ノードから、またはnginxから、そのようなことを言うことができます。 そして、多くの人がそうしています。 そして、私があなたに伝えようとしていることは、あなたが最新のタグを使用している場合、それがあなたのビルドを壊す日が来るということです。 しばらくはうまくいくでしょう。 そして、突然バージョンが変わります。 あなたが予想していなかった何かが違うでしょう。 そして突然、対処しなければならないエラーが発生します。 したがって、最新のタグを使用する代わりに、特定のバージョンに固定する必要があります。 このようには、それは変化せず、自分が何を持っているのか、そしてあなたが作業しているもの、あなたがその特定の画像のどのバージョンで作業しているのかを正確に知ることができます。
最後の1つは、デフォルトでは、画像にはrootユーザーがいることです。 非 root ユーザーを設定する必要があります。 user コマンドを使用すると、ユーザーを指定できます。 ここにも、ユーザーの指示に踏み込んだ非常に素晴らしいブログがあります。 それを見てみることを強くお勧めします。
イメージゴール (12:40)
さて、それでは、画像を構築する際のいくつかの目標について話しましょう。 1つ目は、可能な限り画像のサイズを縮小したいということです。 画像に余分なものを入れる必要はありません。 次に、再利用を促進するためにレイヤーを構造化したいと考えています。 レイヤーが画像をより迅速に作成し、作業を容易にするのに役立つようにしたいと考えています。 そして、アプリケーションの実行に必要なものだけを含めます。 そのため、開発で重要だった無関係なツールを本番環境に含めたくありません。
これらのことの一つ一つをどのように行うのでしょうか? まず、各レイヤーで何をコピーしているのか、そして本当に追加するつもりだったものだけを追加していることを確認するために、どのように構成できるかを考えます。 では、ここにあるこの例を見てみましょう。 私たちがやりたいのは、私たちが進むにつれてクリーンアップしたいということです。 ここには、Ubuntuの左側の例があります。 apt update を実行し、python3-pip install flask をインストールしてから、最後の 2 つのコマンドのすべてのものを削除します。 さて、これはかなり良さそうですね? ご存知のように、私たちは自分たちの仕事をしています。 私たちは、そのようなことを自分たちで後片付けするようにしています。
しかし、ここで少し考えてみましょう。 これらの線のそれぞれは、新しいレイヤーを作成します。 そのため、各レイヤーは書き込まれるとすぐに不変になります。 したがって、実際にクリーンアップする代わりに、最後の 2 つのコマンドは、その特定のイメージで削除できない特定のファイルのホワイトアウト ファイルを追加するため、実際にはこのサイズに追加されています。 それを行う代わりに、これらすべてのコマンドを 1 つのコマンドと 1 つのレイヤーとして実行できる場合、これらすべての出力がレイヤーとしてファイル システムに書き込まれるものになることを意味します。 したがって、右側の例に移動すると、ここには 1 つの run コマンドがあり、アンパサンドとスラッシュを使用してこれらすべての異なるコマンドを 1 つに結合していることがわかります。 これが意味するのは、これらすべてが一度に実行され、すべてが同時に実行され、その結果がファイルシステムに書き込まれるということです。 したがって、左側のコマンドから作成されるイメージは 460 MB になります。右側の機能的に同じ画像はわずか 157 MB です。 この 1 つのコマンドを変更するだけで 66% の変更です。 つまり、レイヤーで何をしているのかを考え、実際にイメージの各レイヤーをどのように書いているかを効率的に作成するために、クリーンアップを行うことです。
ビルドキャッシュ(15:50)
さて、次の目標は、再利用を促進するためにレイヤーを構造化することです。 そのため、各ビルドの変更レイヤーの数を減らし、イメージをプッシュおよびプルするための帯域幅を減らします。 さて、この時点でレイヤーについて少し話しましたが、なぜこのレイヤーのことをしているのかは本当に説明していません。 そして、このレイヤーのことをやっている理由の1つは、ここで説明していることです:各レイヤーは不変であり、レイヤーは再利用できるため、実際にイメージをプルしてプッシュしたり、イメージをビルドしたりするときに、そのレイヤーを毎回再構築する代わりに、そのレイヤーのキャッシュされたバージョンを使用できることを意味します。 これは、画像を構築するだけでなく、必要なレイヤーに応じて画像をプッシュおよびプルする場合にも大きなメリットです。 ビルドキャッシュは、実際にイメージをビルドするときに非常に重要になります。 そのため、ビルドキャッシュは、可能な限りレイヤーを再利用しようとします。 したがって、キャッシュ ルールでは、追加命令とコピー命令ごとに、コンテンツまたはメタデータのチェックサムが作成されるように規定されています。 チェックサムが変更されると、キャッシュは無効になります。 つまり、たとえば、ソースディレクトリをイメージにコピーしていて、そのソースディレクトリ内の何かを変更すると、チェックサムが変更され、コピーする必要があるものを変更したため、キャッシュが無効になります。 RUN コマンドが変更されると、キャッシュは無効になります。 したがって、ビルド中の任意の時点でキャッシュが無効になると、そのビルドの残りの部分、つまりその特定のビルドの残りのすべてのレイヤーに対してキャッシュが無効になります。 これが意味するのは、本当に高速なビルドが必要な場合、そのキャッシュはビルドプロセス中にできるだけ遅くまで無効にする必要があるということです。 ここで重要なのは、Javaなどの特定の技術セットに対してキャッシングを設定することが、ある方法で機能するからといって、すべての技術で同じように機能するわけではないということです。 確認する必要があるキャッシングルールは、特定のテクノロジースタックによって異なります。
キャッシュが壊れる場所を特定する (17:47)
さて、最初に見るべきことは、キャッシュがどこで壊れるのかを本当に理解しようとすることですか? これを行う 1 つの方法は、ビルドを実行して出力を確認することです。 この例では、右側の行を見ると、作業ディレクトリのキャッシュされた 2 が表示されます 4 。 これは、そのステップがキャッシュされたことを意味します。 実際には実行されていません。 これは単に、ローカルファイルシステム上のレイヤーから取得されました。 以下の copy ステートメントと run ステートメントは、その隣に cache ステートメントがないためにキャッシュされませんでした。 レイヤーを見て、どのレイヤーが実際に再利用され、どのレイヤーが再利用されなかったかを理解できます。 したがって、これはキャッシュが発生する必要がある場所を実際に理解する簡単な方法です。 それを行う別の方法については、プレゼンテーションの少し後で説明します。
さて、画像を構築する際に理解すべき重要なことの1つは、実際に定期的に変更する必要があるものについてです。 確かに、アプリケーションコードは定期的に変更されます。 しかし、アプリケーションの設定は定期的に変更する必要がありますか? たぶんそうじゃないです。 実際にアプリケーションをセットアップし、適切に構成すると、そのコードを頻繁に変更することはおそらくなくなります。 したがって、ここの左側の例を見ると、FROMノードがあり、現在のディレクトリからその特定のイメージにすべてをコピーしています。 npm installを実行しています。 私たちは港を公開しており、エントリーポイントを行うと言いました。 さて、これを見ると、ソースコードの変更など、どのような変更も、2番目のステップでキャッシュを検証することを意味します。 つまり、npm installは常に毎回実行する必要があります。 一方、右側では、npm installが必要とするいくつかのファイルを取得し、それらを最初にコピーしてからnpm installを実行し、残りのソースをイメージにコピーします。 これで、キャッシュが壊れる場所を低いポイントに移動したので、パッケージJSONとパッケージロックJSONとnpm installのコピーをキャッシュでき、特定のイメージを構築するたびにビルドを高速化できます。
では、さらに一歩踏み込んでみましょう。 私がコピーしているソースが純粋にソースであるとしましょう。 HTMLファイルでも何でもかまいません。 そして、その特定のレイヤーは他のレイヤーと相互作用することはありません。 後で別のステップなどでコンパイルするつもりはありません。 それは単にそこに入っているファイルのセットです。 –linkコマンドを使用して、基本的にこのレイヤーは他のレイヤーから独立していると言うことができます。この特定の手順が異なる場合でも、キャッシュを無効にしないでください。 これにより、実際にソースコードを変更し、そのソースコードの変更を後続のビルドにコピーできるようになりますが、キャッシュを無効にすることはできません。 そのため、キャッシュはポートの公開と最後のコマンドのために所定の位置に残ります。 したがって、これは明らかな理由でCOPYコマンドとADDコマンドでのみ機能しますが、ビルドの後のステップに含まれないものをコピーする場合は、–linkコマンドを使用してキャッシュを続行できます。
これらの最後の 1 つは、アプリケーションの実行に必要なものだけを含めることです。 ですから、本番環境で何かに遭遇した場合、その本番環境システムで悪用される可能性のある無関係なものがたくさんあることは避けたいものです。 私たちは、その運用システムをできるだけクリーンでむき出しにし、実行する必要があるアプリケーションを単純に実行できるようにしたいと考えています。 では、開発に役立つイメージを持ちながら、どのようにそれを実現していくのでしょうか?
マルチステージビルド (21:41)
さて、私たちはマルチステージビルドのこの概念を持っています。 マルチステージ ビルドを使用すると、Dockerfile 内にミニ パイプラインを作成できます。 そのため、ビルド時イメージとランタイム イメージを分離して、それ以上のことを行うことができます。 COPY –from=<stage> を実行して、前のステージからコピーできます。最後のステージは、この特定のビルドのデフォルトのターゲットになりますが、オーバーライドすることもできます。これについては、ここではすぐに説明します。 したがって、ここの下部にある例を見ると、ステージ1としてFROM<image>があります。 そして、たくさんのコピーやランを行い、物事をコンパイルするなど、さまざまなことを行っています。 その後、FROM <image2>があるため、ステージ 2 として別のベース イメージが作成されます。 次に、ステージ1から、以前に行ったすべてのコンパイルとコピーの出力を、ランタイム環境となるステージ2のディレクトリにコピーします。 したがって、出力をコピーしているだけで、本番環境で必要のない他のすべての部分をコピーしているわけではありません。
これが実際にどのように見えるかを確認するための React の例を次に示します。 この特定のことを見ていきましょう。 そこで、Nodeを使用してReactコードをビルドし、これらすべてを静的Webサーバーにコピーします。 FROM node:lts AS ビルドがあります。これが私たちのビルドステージです。 作業ディレクトリを設定します。 パッケージJSONとpackage-lock JSONをコピーします。 NPMをインストールします。 パブリックディレクトリ、ソースディレクトリにコピーします。 私たちはビルドを行います。 これらの手順をすべて実行して、実際にアプリケーションをビルドしました。 次に、FROM nginx:alpine、完全に異なるベースイメージがあります。 ホストからこの新しいステージにnginx構成をコピーします。 次に、ビルドステージの出力をnginx HTMLディレクトリにCOPY –from=buildします。 これで、実際にプロダクションイメージを実行するために必要な部分のみを持つプロダクションイメージができました。
ここまでは2つのステージがありましたが、なぜもっと多くのステージがないのでしょうか? 複数の異なるステージを持つことができます。 これは、同じDockerfile内で開発イメージと本番イメージを実際にビルドできることを意味します。 この例を見てみましょう。 FROM node:lts AS baseがあり、そこで行っているのは作業ディレクトリの設定だけです。 次に、FROM base AS dev があり、コマンドを設定します。 この時点で、右側にあるように、実際にdevのターゲットでビルドを行い、ホスト上のディレクトリのファイルバインドマウントを行い、ポートを設定し、開発イメージを持つことができます。 開発者として作業する準備ができているイメージがあり、それを使用してホストシステムに変更を加え、実行中のコンテナに反映されるのを見ることができるのは素晴らしいことです。 次に、FROM base AS ビルドを作成し、前に実行したすべてのコマンドを実行します。 そして最後に、FROM nginx:alpineを使用した製品版があります。 これにより、開発イメージと運用イメージを同じDockerfile内に持つことができるようになり、両方のバージョンのイメージで何が起こっているかを正確に確認できるようになります。
最新のビルドとDockerfileの機能(24:59)
次に、最新のビルド機能と Dockerfile 機能について説明します。 つまり、ここでの最初のものはマウントです。 そこで、画像にシークレットを含めないことについて先ほどお話ししました。 それは非常に悪い考えです。 代わりに何をすべきですか? ビルドにSSHキーなどのシークレットが必要な場合、または単に別のファイルシステムをそのビルドにバインドする必要がある場合はどうでしょうか。 そこで登場するのがマウントです。 マウントを使用すると、ビルド中にファイルシステムを変更できます。 ただし、ファイルシステムに何かを追加するわけではありません。 ビルドプロセス中に一時的にアタッチするだけです。 そのため、ホストディレクトリを作成し、ビルドにマウントすることができました。 すでにどこか別の場所にキャッシュを構築し、それを使用したいのであれば、キャッシュディレクトリを持つことができます。 私たちには秘密があるかもしれません。 SSHキーを持つことができます。 そういうことばかりです。
だから、ここの例を見て、ノードから。 パッケージと糸をコピーします。 yarn install を実行します。 しかし、npmの資格情報の秘密をマウントします。 そして、そのビルダーが使用するキャッシュにもマウントします。 これらは一時的にマウントされますが、その特定のイメージの一部にはなりません。
次は HEREDOC のサポートです。 先ほどの例で、アンパサンドとスラッシュを使用してコマンドを組み合わせました。 HEREDOCを使用すると、アンパサンドやスラッシュをすべて行うことなく、実際に複数の行をまとめることができます。 左側の例を見ると、RUNとEOT bashがあります。 そして、まとめるコマンドがたくさんあります。 そして、それらはすべて 1 行として実行されます。 右側には、インタープリターを指定して、ラインの印刷など、その中でさまざまなことを行うこともできます。 そのため、HEREDOC のサポートは柔軟性が高く、Dockerfile のクリーンアップにも役立ちます。
マルチアーキテクチャ (26:57)
これについては最初にお話ししました。 それについて今話しましょう。 イメージをビルドする場合、そのイメージは、現在実行しているアーキテクチャに合わせてビルドされることを意味します。 Mac M2 マシンからお越しです。 イメージをネイティブにビルドすると、そのイメージのARM 64 イメージがビルドされます。 たとえば、そのイメージを AMD 64 で実行する必要がある場合、そのアーキテクチャのイメージをビルドして実行できるようにする必要があります。 マルチアーキテクチャビルドは、その機能を提供します。 ここでの例を見ると、ここにはいくつかのステートメントがあります。 ビルドとして FROM –platform=$BUILDPLATFORM ノードがあります。 ここでのBUILDPLATFORMは、構築されるネイティブプラットフォームになります。 私の場合、それがARM 64 であり、ここには、実際にこれらすべての異なるものを一緒に構築しているさまざまなステートメントが見られます。 次に、FROM –platform=$TARGETPLATFORM nginx があります。 では、そのターゲットプラットフォームはどこから来ているのでしょうか? 下部を見ると、実際にはビルドコマンドを実行しているコマンドラインで指定していることがわかります。 –platform=linux/amd64,linux/arm64と表示されます。 これは、右側に見られるように、この最終ステージの2つの異なるバージョンを作成することを意味します。 ビルドは、私たちが構築しているネイティブ アーキテクチャに基づいていました。ファイナルには、この特定のイメージのAMD 64 バージョンとARM 64 バージョンの両方が含まれます。 これが、実際にマルチアーキテクチャビルドを行う方法です。 さて、重要なのは、私がネイティブアーキテクチャではないアーキテクチャのために構築している場合、それを実現するためにはエミュレートする必要があるということです。 つまり、ビルドに時間がかかり、ビルドの処理時間も長くなります。 しかし、それを回避する方法はあります。 Docker Build Cloudのようなものを使用すると、ARMとAMDの両方の 64 ビルドをクラウド環境でネイティブに実行できます。 これについては、後でもう少し詳しく説明します。
ビルド チェックは、現時点でビルダーに搭載されている新機能の 1 つです。 ビルドチェックが行っていることは、基本的に、Dockerfileで起こっている可能性のあることに関する情報を提供することです。 正しく実行されているように見えるDockerfileがある場合でも、その中に正しくないものがあり、注意する必要があるかもしれません。 これにより、ベストプラクティスとチェック、およびDockerfileで何をしているのかを知ることができます。 この内部でそれらの問題を解決する方法に関する警告とガイダンスのリストが表示されます。 これは、ビルドを行っているとき、またはデスクトップのビルドビューで確認できますが、これについては1秒でここに戻ります。
Docker Desktop GUI ビルドビュー (29:44)
そのため、最近、Docker Desktop GUI内にビルドビューを追加しました。 このビルド ビューでは、実行しているビルドに関する多くの情報が提供されます。 ビルド時間、キャッシュの使用状況、依存関係、ビルド ソース、Dockerfile、ビルド ログ、およびイメージごとの履歴に関する情報を提供します。 それを見て、それがどのようなものか見てみましょう。 これが私のDockerデスクトップGUIです。 ビルドに移動すると、さまざまなイメージすべてのビルドが表示されます。 キャッシュがすべての処理を行い、実際に画像を再生成するだけで済むため、これらのいくつかは非常に時間がかからなかったことがわかります。 これらのいくつかは長い時間であり、それらに関する情報を得ることができます。 例えば、ここスプリングペットクリニックに来ると、ビルドチェックではここにいくつかの警告があると実際に確認できます。 だから、私はそれらの特定の警告をチェックしに行くことができます。 これにより、ビルド時間に関する情報も得られます。 ですから、実際に作るのにどれくらいの時間がかかったかがわかります。 この特定の時間内にキャッシュが実際にどれだけ使用されたかを確認でき、ここでも依存関係を確認できます。 したがって、この特定の依存関係内にも、実際には複数の異なる依存関係があることがわかります。 ソースに来ると、そのDockerfileの実際のソースファイルを取得します。 そして、ここでFROMとASが異なることについての警告のいくつかを見ることができます。 繰り返しになりますが、これらは必ずしもエラーを引き起こすものではありませんが、Dockerfileを実行するときに注意したいことです。 特定のDockerfileに関するすべての情報を確認できます。 次に、ログに移動すると、この各ステップの個々のログだけでなく、上部で気付いた場合、ここをスクロールしているときは、これを処理しているときに、小さな線が移動し、ビルドがどのように進行しているかを示しています。 これを経験しているとき、私は実際にビルドプロセスのどこにいるかを見ることができます。 これは、特に長いJavaビルドです。 その中で見るのは少し難しいでしょうが、この特定のビルドを移動しているときに、実際にはその小さな線を動かしています。 その後、歴史を振り返ると、私が構築している特定のイメージについて、構築にどれくらいの時間がかかったかが実際にわかります。 どのくらいの期間が経ちましたか? これらのそれぞれにいくつのビルドステップがありましたか? また、それらのステップのうち、キャッシュされたステップはいくつありますか? だから、この特定のイメージを実際に構築しているうちに、時間が経つにつれて良くなっているのか、悪くなっているのかが見えてきます。 そして、それらの以前のビルドについてもより多くの情報を取得し、それが何であるかを確認できます。 ここで、これらのビルダーのいくつかはデフォルトのデスクトップビルダーにすぎないことに注意してください。 ここにはクラウドビルダーもあります。
これは、Docker Build Cloud の機能です。 そして、ここで私のローカルビルドがほぼ3分、2分 45 秒から3分だったことに気づきました。 そして、これが 1されました:クラウドビルダーの23 。 クラウドビルダーは、ビルドを高速化し、ネイティブ環境で直接ARMおよびAMDのビルドを実行できるようになるため、エミュレーションについて心配する必要がない 64 メリットです。
この Docker Image Deep Dive and Build Best Practices プレゼンテーションをお楽しみいただけたでしょうか。 さらに詳しい情報が必要な場合は、ビルドに関する情報がたくさんあります。 それらは私たちのビルドマニュアルと私たちのドキュメントにあります。 ぜひ足を運んでみてください。 どうもありがとうございます。
さらに詳しく
- 詳細については、 Docker ビルド マニュアル を参照してください。
- ドッカーは初めてですか? 始めましょう。
- 注目のガイドでDocker製品を深く掘り下げます。
- Docker Newsletter を購読してください。
- Docker デスクトップの最新リリースを入手します。
- 質問がありますか? Docker コミュニティがお手伝いします。