blog

Dockerdockerfileの構文

これを1つのコマンドに変更し、ビルドのコンパイルに必要なソフトウェア、ダウンロードおよび展開されたすべてのファイル、aptキャッシュファイルなど、余分なファイルを削除する必要があります。ミラーは複数の...

Jul 27, 2020 · 4 min. read
シェア

I. 構文

FROM busybox
ENV foo /bar
WORKDIR ${foo} 
ADD . $foo 
COPY \$foo /quux
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
EXPOSE 
CMD /code/run-app
ENTRYPOINT ["top", "-b"]

コマンド

FROM

FROM [--platform=<platform>] <image> [AS <name>]

または

FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]

または

FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

RUN

RUN ["実行ファイル"、"引数1"、"引数2"]

注意: Dockerfileの全てのコマンドはレイヤを作成し、RUNも例外ではありません。各RUNの動作は、手動でImageを作成するプロセスと同じです。新しいレイヤを作成し、そのレイヤ上でコマンドを実行し、完了したらそのレイヤに加えた変更をコミットして新しいImageを作成します。

FROM debian:stretch
RUN apt-get update
RUN apt-get install -y gcc libc6-dev make wget
RUN wget -O redis.tar.gz "http://..//-....gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install

Imageはマルチレイヤーのストレージであり、各レイヤーにあるものは次のレイヤーで削除されることはなく、常にImageについてまわります。 そのため、Imageをビルドする際には、各レイヤーで本当に必要なものだけを追加し、余計なものはクリーンアップする必要があります。肥大化したImageを作らないためにも、きれいにする必要があります。

FROM debian:stretch ##dockert空のイメージ
RUN buildDeps='gcc libc6-dev make wget' \
 && apt-get update \
 && apt-get install -y $buildDeps \
 && wget -O redis.tar.gz "http://..//-....gz" \
 && mkdir -p /usr/src/redis \
 && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
 && make -C /usr/src/redis \
 && make -C /usr/src/redis install \
 && rm -rf /var/lib/apt/lists/* \
 && rm redis.tar.gz \
 && rm -r /usr/src/redis \
 && apt-get purge -y --auto-remove $buildDeps

COPY

フォーマット

COPY [--chown=<user>:<group>] < >... <コピー先パス>
COPY [--chown=<user>:<group>] ["<ソースパス1>",... "<コピー先パス>"]

RUN命令と同様に、コマンドラインに似た形式と関数呼び出しに似た形式の2種類があります。

COPYコマンドは、 <源路径> ファイル/ディレクトリをビルドコンテキストディレクトリから新しいレイヤーのイメージ内の <目标路径> 場所にコピーします <源路径> <目标路径> 。例えば

COPY package.json /usr/src/app/

<源路径> これは、Goのfilepath.Matchルールを満たすワイルドカードルールを持つワイルドカードであっても、複数にすることができます:

COPY hom* /mydir/
COPY hom?.txt /mydir/

<目标路径> コンテナ内の絶対パスでも、作業ディレクトリからの相対パスでもかまいません。コピー先のディレクトリが存在しない場合は、ファイルをコピーする前にそのディレクトリが作成されます。

また、COPYコマンドでは、ソースファイルに関するさまざまなメタデータが保持されることも重要です。例えば、読み取り、書き込み、実行権限、ファイルの変更時間などです。この機能はイメージのカスタマイズに便利です。特に、ビルド関連のファイルがGitを使って管理されている場合に便利です。また、--chown=:オプションと一緒にこのコマンドを使用すると、ファイルが属するユーザーとグループを変更できます。

COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/

ADD

ADDとCOPYは機能的には似ていますが、COPYの方がADDよりも透過的であるため、一般的にはCOPYの方が好まれます。COPYは単純にローカルファイルをコンテナにコピーすることしかサポートしていませんが、ADDにはすぐには分からない機能もあります。したがって、ADDの最適な使用例は、ADD rootfs.tar.xzのように、ローカルのtarファイルを自動的にImageに展開することです。各ファイルを一度にCOPYするのではなく、個別にCOPYすることで、各ステップのビルドキャッシュが、特定のファイルが変更されたときにのみ無効になるようにします。例

COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/

RUN 命令に COPY . /tmp/ が RUN 命令に置かれている場合、. /tmp/ ディレクトリにあるファイルが変更されると、それ以降のディレクティブのキャッシュが無効になります。イメージを可能な限り小さく保つためには、リモート URL からパッケージを取得するために ADD コマンドを使う代わりに curl や wget を使うのが最善です。例えば、以下のような使い方は避けるようにしてください:

ADD http://./..xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all

代わりに、以下の方法を使うべきです:

RUN mkdir -p /usr/src/things \
 && curl -SL http://./..xz \
 | tar -xJC /usr/src/things \
 && make -C /usr/src/things all

上記ではパイプライン操作を使用しているため、削除する中間ファイルはありません。ADDの自動抽出機能を必要としない他のファイルやディレクトリの場合は、COPYを使用してください。 ADDコマンドとCOPYの書式や性質は基本的に同じです。しかし、COPYをベースにした追加機能がいくつかあります。

例えば <源路径> URLの場合、Dockerエンジンはリンクされたファイルをダウンロードして <目标路径> そこに置こうとします。 <目标路径> ダウンロードされたファイルのパーミッションは自動的に600に設定されますが、これが望むパーミッションでない場合は、パーミッションを調整するためにRUNを追加する必要があります。また、ダウンロードされたファイルがzipアーカイブで、それを解凍する必要がある場合は、同様に解凍を行うためにRUNを追加する必要があります。ですから、RUNコマンドだけを使い、wgetやcurlを使ってダウンロードし、パーミッションを処理し、解凍し、無駄なファイルをクリーンアップする方が合理的です。したがって、この機能はあまり役に立たず、お勧めできません。

<源路径> tarファイルの場合、ADDコマンドはファイルがgzip、bzip2、またはxz形式であれば自動的に解凍します <目标路径> 。

この自動抽出機能は、公式イメージのubuntuなど、場合によっては非常に便利です:


しかし、どうしてもzipファイルを解凍せずにコピーしたい場合、ADDコマンドを使えないことがあります。

Dockerの公式Dockerfileベストプラクティス文書には、COPYは可能な限り使用すべきであると記載されています。なぜなら、COPYのセマンティクスは明確であり、単にファイルをコピーするだけだからです。一方、ADDはより複雑な機能を含んでおり、その動作は必ずしも明確ではないからです。ADDを使用する最適な場所は、前述の自動解凍のコンテキストです。

また、ADDディレクティブはImageのビルドキャッシュを無効にするため、Imageのビルドが遅くなる可能性があることにも注意してください。

したがって、COPYコマンドとADDコマンドのどちらかを選択する場合は、次の原則に従ってください。すべてのファイルコピーにはCOPYコマンドを使用し、ファイルを自動的に解凍する必要がある場合にのみADDを使用します。

このコマンドは、--chown=:オプションと一緒に使用して、ファイルが属するユーザーとグループを変更することもできます。


ADDは以下のルールを遵守します:

  • <源路径> パスはビルドコンテキスト内になければなりません。/なぜなら docker ビルドの最初のステップはコンテキストディレクトリを docker デーモンに送ることだからです。

  • <源路径>URLで<目标路径>スラッシュで終わっている場合、ファイル名はURLから推測され、ファイルは <目标路径>/にダウンロードされます。 <目标路径>例えば、ADD /はファイル/foobarを作成します。この場合、適切なファイル名が見つかるように、URLは自明でないパスを持っていなければなりません。

  • <源路径>ディレクトリの場合は、ファイルシステムのメタデータも含めて、ディレクトリの内容全体がコピーされます。

注意:ディレクトリ自体はコピーされません。

<源路径>ローカルのtarアーカイブファイルとして認識されている圧縮形式であれば、ディレクトリとして解凍されます。リモートURLからのリソースは解凍されません。ディレクトリのコピーや解凍は、同じ tar -x のように動作し、以下のように連結されます:

  • ターゲットパス上に存在するすべてのコンテンツと
  • 競合が解決されたソース・ツリーの内容は、代わりに " 2" で示されます。ファイルごとに

注:ファイル名ではなく、ファイルの内容のみがファイルとして識別されるかどうかが圧縮形式として認識されます。たとえば、空のファイルの末尾がたまたま .tar.gz である場合、圧縮ファイルとは認識されず、何らかの解凍エラーメッセージが生成される代わりに、ファイルは単に目的の場所にコピーされます。

  • <源路径>別の種類のファイルであれば、メタデータと一緒に別々にコピーされます。この場合、スラッシュ / で終わっていれば、ディレクトリとして扱われ、その内容は<源路径><目标路径>/base に書き込まれます<源路径><目标路径>。
  • <源路径>直接、またはワイルドカードを使った結果として、複数のリソースが指定された場合<源路径>、リソースは<目标路径>ディレクトリでなければならず、スラッシュ / で終わらなければなりません。
  • <目标路径>スラッシュで終わっていなければ、通常のファイルとして扱われ、その内容が<源路径>書き込まれます<源路径><目标路径>。
  • <目标路径>存在しない場合は、パス内のすべての欠落したディレクトリと一緒に作成されます。

CMDコンテナ起動コマンド

CMDコマンドはRUNコマンドと似ていますが、2つの形式があります:

  • シェル形式: CMD <命令>
  • exec Format: CMD ["executable", "parameter 1", "parameter 2"...].
  • パラメータリストの形式:CMD ["パラメータ1", "パラメータ2"...]....].ENTRYPOINT コマンドを指定した後、CMD を使用して特定のパラメータを指定します。

コンテナを紹介したときにも言いましたが、Dockerは仮想マシンではなく、コンテナはプロセスです。プロセスなので、コンテナを起動する際には、実行するプログラムとそのパラメータを指定する必要があり、コンテナのメインプロセスのデフォルトの起動コマンドを指定するためにCMDコマンドを使用します。

例えば、ubuntu イメージのデフォルト CMD は /bin/bash ですが、docker run -it ubuntu を指定すると直接 bash に移動します。ubuntu cat /etc/os-release これはデフォルトの /bin/bash コマンドをシステムのバージョン情報を出力する cat /etc/os-release で置き換えます。

コマンドの形式としては、一般的にexec形式が推奨されています。 この形式はJSON配列としてパースされるので、必ずシングルクォートではなくダブルクォートを使用してください。

シェル形式を使用する場合、実際のコマンドは NY -c 引数でラップされます。例えば

CMD echo $HOME 実際の実行では、次のように変更されます:


そのため、シェルによって解析・処理される環境変数を使用することができるのです。

CMDに関して言えば、コンテナ内でのアプリケーションのフォアグラウンド実行とバックグラウンド実行の問題に言及することが重要です。これは初心者にとってよくある混乱です。

Dockerは仮想マシンではなく、コンテナ内のアプリケーションはフォアグラウンドで実行されるべきであり、仮想マシンのようなものではなく、systemd内部の物理マシンはバックグラウンドサービスを開始するために、コンテナ内のバックグラウンドサービスの概念はありません。

初心者の中にはCMDをこう書く人もいます:


すると、実行直後にコンテナが終了してしまいます。コンテナ内でもsystemctlコマンドを使用した結果、実行できないことがわかりました。これは、フォアグラウンドとバックグラウンドの概念を理解しておらず、コンテナと仮想マシンを区別しておらず、まだ従来の仮想マシンの観点からコンテナを理解していないためです。

コンテナの場合、その起動プログラムはコンテナ・アプリケーション・プロセスであり、コンテナはメイン・プロセスのために存在し、メイン・プロセスが終了すると、コンテナは存在意義を失って終了し、他の補助プロセスは気にする必要はありません。

service nginx start で、upstart にバックグラウンドデーモンとして nginx サービスを開始させたい場合、CMD service nginx start は CMD ["sh" "-c" "service nginx start"] として解釈されます。CMD service nginx start は CMD ["sh", "-c", "service nginx start"] として解釈されるので、メインプロセスは実際には sh です。 service nginx start コマンドが終了すると sh も終了し、sh はメインプロセスとして終了し、当然コンテナを終了します。

正しい方法は、nginxの実行ファイルを直接実行し、フォアグラウンドとして実行させることです。例えば


エントリーポイント

フォーマット

  • exec format: ENTRYPOINT ["executable", "param1", "param2"].
  • シェル書式:ENTRYPOINT コマンド param1 param2

ENTRYPOINTの目的は、CMDと同様にコンテナの起動手順とそのパラメータを指定することです。 ENTRYPOINTは代わりに実行時に使用することもできますが、CMDよりも少し面倒で、docker runパラメータの--entrypointで指定する必要があります。ENTRYPOINTを指定するとCMDの意味が変わり、コマンドを直接実行するのではなく、CMDの内容がENTRYPOINTコマンドの引数として渡されます:


では、CMDがあるのに、なぜENTRYPOINTが必要なのでしょうか?この" "にはどのような利点があるのでしょうか?いくつかのシナリオを見てみましょう。

シナリオ1:イメージをコマンドのように動作させる

現在のパブリックIPを知っているイメージが必要だと仮定すると、CMDを使うことから始めることができます:


docker build -t myip . イメージのビルドで、現在のパブリックIPを照会する必要がある場合は、次のように実行します:


現在のIP:61.148.226.66 From: 東京ユニコム さて、Imageをコマンドとして使えそうですが、コマンドには必ず引数があるので、それを追加したい場合はどうすればいいのでしょうか?例えば、上のCMDを見ると、実際のコマンドはcurlなので、HTTPヘッダを表示したい場合は、-iパラメータを追加する必要があります。docker run myipに-iを追加すればいいのでしょうか?


実行可能ファイルが見つからないというエラーは、イメージ名の後に続くコマンドを実行するとCMDのデフォルトに置き換わると言っていることがわかります。つまり、ここでの-iは、元のcurl -sに追加するのではなく、元のCMDを置き換えるということです。また、-i はコマンドではないので、当然見つかりません。そのため、-i パラメータを追加したい場合は、コマンド全体を再入力する必要があります: $ docker run myip curl -s -i これは明らかに良い解決策ではなく、ENTRYPOINT を使用することで解決できます。ENTRYPOINTを再利用して、このImageを実装しましょう:


もう一度、docker run myip -i を直接使ってみてください:



ご覧の通り、今回はうまくいきました。これは、ENTRYPOINTが存在する場合、CMDの内容がENTRYPOINTの引数として渡されるためで、ここでは-iが新しいCMDなので、curlの引数として渡され、望ましい効果が得られます。

シナリオ2:実行前のアプリケーションの準備

コンテナの起動はメインプロセスの起動を意味しますが、メインプロセスを起動する前に準備作業が必要な場合もあります。たとえば、mysql ライクなデータベースでは、データベースの設定や初期化作業が必要になることがあります。

また、セキュリティ向上のため、rootユーザでのサービス起動を避け、サービス起動前にrootで必要な準備を行い、最後にサービスユーザに切り替えてサービスを起動したい場合もあるでしょう。あるいは、デバッグを容易にするために、サービス以外のコマンドをrootで実行したい場合もあるでしょう。

これらの準備はコンテナ CMD とは無関係であり、CMD に関係なく事前に前処理タスクが必要です。この場合、ENTRYPOINT で実行されるスクリプトを書くことが可能で、このスクリプトは受け取った引数をコマンドとして受け取り、スクリプトの最後で実行します。例えば公式 Image の redis ではこのようにしています:




このスクリプトの内容は、CMDの内容を判断し、redis-serverであればredisユーザーIDに切り替えてサーバーを起動し、そうでなければroot IDで実行します。例えば


ENV 環境変数の設定

フォーマット

  • ENV "key" "value"
  • ENV "key1"="value1" "key2"="value2"...

これは環境変数を設定するだけの非常に単純なディレクティブです。 RUNなどの他のディレクティブもランタイムアプリケーションも、ここで定義された環境変数を直接使うことができます。


この例では、改行を行い、スペースを含む値を二重引用符で囲む方法を示しています。

環境変数が定義されていれば、以降のコマンドでこの環境変数を使用することができます。例えば、公式のノードイメージDockerfileには次のようなコードがあります:


環境変数NODE_VERSIONはまずここで定義され、その後RUNレイヤで$NODE_VERSIONが何度か使用され、操作がカスタマイズされます。このように、将来Imageビルドをアップグレードする際には、7.2.0に更新するだけで済むので、Dockerfileビルドのメンテナンスが非常に簡単になります。


このコマンドのリストから、環境変数が多くの強力な方法で使用できることを感じていただけると思います。環境変数を使うことで、1つのDockerfileでより多くのImagesを作ることができます。

ARG構築パラメータ

フォーマット

  • ARG <参数名>[=<默认值>]

build パラメータは、環境変数を設定するという点では ENV と同じです。違いは、ARG は将来コンテナを実行するときには存在しないビルド環境の環境変数を設定するという点です。ただし、パスワードなどを保存するために ARG を使わないでください。

DockerfileのARGコマンドは、パラメータ名とそのデフォルト値を定義します。このデフォルト値は、ビルドコマンド docker build の --build-arg <参数名>= で <参数名><值> 上書きすることができます <参数名><值> 。

1.13以降では、-build-argのパラメータ名がDockerfileでARGを使って定義されていること、言い換えると、-build-argで指定されたパラメータがDockerfileで使われていることが必要です。対応するパラメータが使用されていない場合、ビルドを終了するためにエラーが報告されます。1.13から、この厳しい制限が緩和され、エラー終了の代わりに警告メッセージが表示され、ビルドが続行されるようになりました。これはCIシステムを使用して同じビルドプロセスで異なるDockerfileをビルドする場合に便利で、各Dockerfileの内容に基づいてビルドコマンドを修正する必要がなくなります。

ボリューム 匿名ボリュームの定義

フォーマット

  • ボリューム ["<路径1>", "<路径2>"...].
  • ボリューム <路径>

先ほども述べたように、コンテナの実行中はコンテナのストレージ階層に書き込み操作を行わないようにする必要があります。 データベースカテゴリに動的データを保存する必要があるアプリケーションの場合、そのデータベースファイルはボリュームに保存する必要があり、Dockerボリュームの概念については後の章でさらに紹介します。実行時にユーザが動的ファイルを保存するディレクトリをボリュームとしてマウントし忘れるのを防ぐために、あらかじめDockerfileで特定のディレクトリを匿名ボリュームとしてマウントするように指定しておけば、実行時にユーザがマウントを指定しなくても、コンテナのストレージ層に大量のデータを書き込むことなく、アプリケーションを正常に実行することができます。


ここで、/data ディレクトリは、実行時に匿名ボリュームとして自動的にマウントされ、/data への書き込みはコンテナストレージ層に記録されないため、コンテナストレージ層はステートレスになります。もちろん、実行時にこのマウント設定を上書きすることも可能です。例えば


このコマンド行では、Dockerfileで定義された匿名ボリュームのマウント設定を置き換えて、名前付きボリュームmydataを使用して/dataロケーションにマウントします。

EXPOSE露出したポート

フォーマット

  • EXPOSE [ <端口1><端口2>...]

EXPOSEディレクティブは、コンテナが実行時にサービスポートを提供するという宣言です。 これは単なる宣言であり、この宣言があるからといって、アプリケーションが実行時にこのポートのサービスをオンにするわけではありません。Dockerfile にこのような宣言を記述することには、2つの利点があります。1つは、Image ユーザがこの Image サービスのデーモンポートを理解してコンフィギュレーションマッピングを容易にすること、もう1つは、実行時にランダムポートマッピングを使用するとき、つまり docker run -P のときに、EXPOSE ポートを自動的にランダムにマッピングすることです。

EXPOSE と実行時の -p <宿主端口>: の使用を区別することは重要です <宿主端口><容器端口> 。ホストのポートをコンテナのポートにマッピングする -p は、言い換えれば、コンテナの対応するポートサービスを外部アクセスに公開するものですが、EXPOSE は単にコンテナが使用するポートを宣言するだけで、ホスト側で自動的にポートマッピングを実行するわけではありません。

WORKDIR 作業ディレクトリの指定

フォーマット

  • WORKDIR <工作目录路径>.

WORKDIRコマンドで作業ディレクトリを指定すると、各レイヤーのカレントディレクトリが指定したディレクトリに変更され、ディレクトリが存在しない場合はWORKDIRがディレクトリを作成します。

Dockerfileをシェルスクリプトのように書いてしまうことが、初心者にありがちな間違いであることが述べられています:


DockerfileをビルドしてImageを実行すると、ファイル/app/world.txtが見つからないか、その内容がhelloになっていないことに気づくでしょう。理由は簡単です。Shellでは2つの連続した行が同じプロセス環境で実行されるため、最初のコマンドで変更されたメモリ状態が2番目のコマンドに直接影響します。一方Dockerfileでは、2つのRUNコマンドは異なる環境にあり、2つの全く異なるコンテナです。Dockerfileでは、これら2つのRUNコマンドの実行環境は根本的に異なり、2つの全く異なるコンテナです。これは、Dockerfileで階層型ストレージを構築するという概念を理解していないために起こるミスです。

すべてのRUNはコンテナを起動し、コマンドを実行し、ストレージ層のファイル変更をコミットすると言われてきました。第一階層の RUN cd /app は、単にカレントプロセスの作業ディレクトリを変更するだけであり、ファイルの変更をもたらさないメモリ内の変更です。第2階層では、まったく新しいコンテナが開始されます。このコンテナは第1階層とは何の関係もなく、当然ながら前階層のビルドによるメモリ変更を継承することはできません。

したがって、以降のレイヤーの作業ディレクトリの場所を変更する必要がある場合は、WORKDIRコマンドを使用する必要があります。

USER 現在のユーザーを指定します。

フォーマット

  • ユーザー

WORKDIRが作業ディレクトリを変更し、USERがRUN、CMD、ENTRYPOINTなどのコマンドを実行する後続レイヤーのIDを変更するのに対し、USERコマンドは環境の状態を変更し、後続レイヤーに影響を与えるという点でWORKDIRと似ています。

もちろん、WORKDIRのように、USERは指定されたユーザーへの切り替えを助けるだけで、あらかじめ作成しておかなければ切り替えはできません。


rootとしてスクリプトを実行していて、実行中にIDを変更したい場合、例えば、確立されたユーザとしてサービスプロセスを実行したい場合、suやsudoは使わないでください。suやsudoは多くの設定を必要とし、TTYがない環境ではしばしばエラーになります。gosu を使うことをお勧めします。


ヘルスチェック 健康チェック

フォーマット

  • HEALTHCHECK [Options] CMD <命令>: コンテナの健全性をチェックするコマンドを設定します。
  • HEALTHCHECK NONE: ベースイメージがヘルスチェックディレクティブを持っている場合、この行を使ってヘルスチェックディレクティブをブロックします。

HEALTHCHECK ディレクティブは、Docker 1.12 で導入された新しいディレクティブで、コンテナの状態が正常かどうかを判断する方法を Docker に伝えます。

HEALTHCHECKディレクティブが追加される前は、Dockerエンジンはコンテナ内のメインプロセスが終了したかどうかによってのみ、コンテナが異常な状態にあるかどうかを判断することができました。多くの場合、これは問題ありませんが、アプリケーションがデッドロック状態やデッドループ状態になった場合、アプリケーションプロセスは終了しませんが、コンテナはサービスを提供できなくなります。1.12より前のバージョンでは、Dockerはコンテナのこの状態を検出せず、スケジュールを変更しなかったため、コンテナによってはサービスを提供できなくなったにもかかわらずリクエストを受け取る可能性がありました。

1.12以降、DockerはHEALTHCHECKコマンドを提供し、コンテナのマスタープロセスのサービスステータスがまだ正常かどうかを判断するために使用できるコマンドの行を指定することで、コンテナの実際の状態に対してより現実的な応答を提供します。

イメージで HEALTHCHECK コマンドを指定してコンテナを起動した場合、初期状態は起動中で、HEALTHCHECK コマンドが成功すると健全な状態に、一定回数連続して失敗すると不健全な状態に変化します。

HEALTHCHECK は以下のオプションをサポートしています:

  • --interval=<间隔>: 2回のヘルスチェックの間隔、デフォルトは30秒です;

  • -timeout<时长>=:ヘルスチェックコマンドが実行されるまでのタイムアウト;

  • --retries=<次数>: 指定された回数連続して失敗すると、コンテナの状態は不健康とみなされます。

CMD、ENTRYPOINTと同様に、HEALTHCHECKは1回しか表示できません。

HEALTHCHECK [option] CMDに続くコマンドは、ENTRYPOINTのようにシェル形式とexec形式でフォーマットされます。コマンドの戻り値は、ヘルス・チェックの成否を決定します: 0: 成功、1: 失敗、2: 予約済み、この値は使用しないでください。

イメージは最も単純なウェブサービスで、そのウェブサービスが正常に動作しているかどうかを判断するヘルスチェックを追加したいとします:


ヘルスチェックコマンドが3秒以上応答しない場合は失敗とみなされ、curl -fs || exit 1がヘルスチェックコマンドとして使用されます。

docker build を使ってこのイメージをビルドします:


コンテナを構築したら、コンテナを開始します:


イメージの実行後、docker container ls で初期状態を確認できます:


数秒待ってから docker コンテナ ls を再度実行すると、ヘルスステータスが :


リトライ回数を超えてヘルスチェックに失敗し続けると、ステータスが .

トラブルシューティングに役立てるため、ヘルスチェックコマンドの出力はヘルスステートに保存され、docker inspectで見ることができます。


オンビルド 他人の重荷になること

フォーマット

  • オンビルド <其它指令>.

ONBUILD は特別な命令で、RUN、COPY などの命令の後に続きます。これらの命令が実行されるのは、現在のイメージが次のレベルのイメージを構築するためのベースイメージとして使用される場合だけです。

Dockerfileの他のコマンドは全て現在のImageをカスタマイズするためにあります。

Node.jsで書かれたアプリケーションのImageを作成したいとします。ご存知のように、Node.jsはパッケージ管理にnpmを使用し、依存関係、設定、起動情報などはすべてpackage.jsonファイルに格納されます。コードを取得したら、まずnpmをインストールして必要な依存関係をすべて取得する必要があります。それからnpm startでアプリケーションを起動します。これが通常のDockerfileの書き方です:


このDockerfileをNode.jsプロジェクトのルートディレクトリに置き、Imageをビルドしたら、それを立ち上げてコンテナを起動させるだけです。しかし、同じことを行う2つ目のNode.jsプロジェクトがある場合はどうでしょうか?その場合は、Dockerfileを2番目のプロジェクトにコピーします。3つ目のプロジェクトがあったら?別のコピー?ファイルのコピーが増えれば増えるほど、バージョン管理は難しくなります。

最初のNode.jsプロジェクトが開発中で、このDockerfileにタイプミスや追加パッケージのインストールなどの問題があった場合、開発者はDockerfileを修正し、再度ビルドすれば問題は解決します。最初のプロジェクトは問題ありませんが、2番目のプロジェクトはどうでしょうか?最初のプロジェクトがDockerfileを修正したからといって、2番目のプロジェクトのDockerfileも自動的に修正されるわけではありません。

ベースイメージを作成し、そのベースイメージを各プロジェクトに使用することは可能ですか?そうすれば、ベースイメージが更新されたときに、各プロジェクトはDockerfileの変更を同期する必要がなく、リビルドはベースイメージの更新を継承します。では、その方法を見てみましょう。では、上記のDockerfileを次のように変更します:


ここで、プロジェクトに関連するビルド手順をサブプロジェクトにまとめます。このベースイメージの名前をmy-nodeとすると、各プロジェクト内のDockerfileは以下のようになります:


ベースイメージが変更された後、各プロジェクトはこのDockerfileを使用してイメージを再構築し、ベースイメージからの更新を継承します。

では、問題は解決したのでしょうか?いいえ。正確には半分だけです。もしDockerfileに微調整が必要なものがあったらどうしますか?例えば、npm installでパラメータを追加する必要があります。このRUNをベースイメージに入れるのは不可能です。./package.jsonに関わるので、ベースイメージにこのRUNを入れるのは不可能です。つまり、この方法でベースイメージを作ると、元のDockerfileの最初の4つのコマンドを変更する問題は解決しますが、最後の3つのコマンドの変更には対応できません。

ONBUILD はこの問題を解決できます。ベースイメージの Dockerfile を ONBUILD: で書き換えてみましょう。


今度は元のDockerfileに戻りますが、プロジェクト関連のディレクティブにONBUILDを追加して、ベースイメージのビルド時にこれらの3行が実行されないようにします。これで各プロジェクトのDockerfileはシンプルになります:

FROM my-node はい、たった1行です。この1行のDockerfileを使って各プロジェクトディレクトリにImageをビルドすると、ベースImageの3行のONBUILDが実行され、現在のプロジェクトのコードがImageにコピーされ、そのプロジェクトのnpm installが実行されてアプリケーションImageが生成されます。

ビルドイメージ

Dockerfileファイルがあるディレクトリで実行します:


コマンドの出力から、Image のビルドプロセスが明確に確認できます。Step2では、前述の通り、RUNコマンドでコンテナ9cdc27646c7bを起動し、要求されたコマンドを実行し、レイヤー44aa4490ce2cをコミットした後、使用したコンテナ9cdc27646c7bを削除します。 Imageのビルドにはdocker buildコマンドを使用し、最終的なImageの名前 -t nginx:v3を指定します。最終イメージの名前 -t nginx:v3 を指定します:


イメージビルドコンテキスト

注意すると、docker buildコマンドの最後にDockerfileがあるカレントディレクトリを示す「. コマンドの最後に". "がありますが、これはDockerfileがあるカレントディレクトリを示すもので、多くの初心者はこのパスがDockerfileがあるパスを指定していると思っていますが、これは不正確です。上のコマンド形式を見ると、コンテキストパスを指定していることがわかるかもしれません。ではコンテキストとは何でしょうか?

まず最初に、dockerビルドの仕組みを理解する必要があります。dockerは実行時にDockerエンジンとクライアントツールに分かれます。dockerエンジンはDocker Remote APIと呼ばれるREST API群を提供し、dockerコマンドなどのクライアントツールはこのAPI群を通じてDockerエンジンとやり取りし、様々な機能を実行します。Docker Remote APIは、Docker Remote APIと呼ばれるREST API群を提供します。そのため、Dockerの機能をローカルで実行しているように見えるかもしれませんが、実際にはすべてリモートコールを使ってサーバ側で行われています。このC/S設計により、リモートサーバ上のDockerエンジンを簡単に操作することもできます。

イメージをビルドする際、すべてのカスタマイズがRUNコマンドで行われるわけではありません。COPYコマンドやADDコマンドなど、ローカルのファイルをイメージにコピーする必要があることがよくあります。docker buildコマンドはイメージをローカルではなく、サーバー側、つまりDockerエンジンにビルドします。docker buildコマンドはローカルではなく、サーバ側、つまりDockerエンジンでImageを構築します。では、このクライアント/サーバアーキテクチャにおいて、ローカルのファイルをサーバ側で利用可能にするにはどうすればよいのでしょうか?

これはコンテキストの概念を導入するものです。ビルドの際、ユーザはイメージのコンテキストをビルドするパスを指定します。docker buildコマンドはこのパスを知ると、そのパスの下にあるすべてをパッケージ化してDockerエンジンにアップロードします。Dockerエンジンはこのコンテキストパッケージを受け取ると、イメージのビルドに必要なすべてを展開します。

Dockerfileにこのように書くと:


これは、docker buildコマンドを実行したディレクトリのpackage.jsonをコピーすることでも、Dockerfileがあるディレクトリのpackage.jsonをコピーすることでもなく、コンテキストディレクトリのpackage.jsonをコピーすることです。

したがって、COPYなどのコマンドにおけるソースファイルへのパスは相対パスとなります。初心者がよく

COPY .../package.json /appやCOPY /opt/xxxx /appは動作しません。これらのパスはコンテキストから外れており、Dockerエンジンはこれらの場所にあるファイルを取得できないからです。これらのファイルが本当に必要な場合は、コンテキストディレクトリにコピーしてください。

これで、docker build -t nginx:v3 . ..は実際にはコンテキストのディレクトリを指定しており、docker buildコマンドはそのディレクトリの内容をDockerエンジンにパッケージングしてImageの構築を支援します。 docker buildの出力を見ると、このコンテキストの送信プロセスが実際に行われています:


ビルドコンテキストを理解することは、イメージのビルドにおいて、やってはいけないミスを避けるために重要です。例えば、初心者の中にはCOPY /opt/xxxx /appが機能しないことに気づき、単純にDockerfileをハードドライブのルートディレクトリに置いてビルドしようとしますが、docker buildが実行されて数十ギガバイトのものが送信され、非常に遅くビルドに失敗しやすいことに気づきます。これはdocker buildにドライブ全体のパッケージを要求しているからで、明らかに誤用です。

一般的に、Dockerfileは空のディレクトリか、プロジェクトのルートディレクトリに置くべきです。そのディレクトリに必要なファイルがない場合は、必要なファイルのコピーを作成します。ビルド時にどうしてもDockerエンジンに渡したくないものがディレクトリにある場合は、.gitignoreと同じ構文で.dockerignoreを記述します。

では、なぜ. がDockerfileのあるディレクトリを指定していると勘違いするのでしょうか?デフォルトでは、Dockerfileを追加で指定しないと、コンテキストディレクトリにあるDockerfileという名前のファイルがDockerfileとして使われるからです。

これはデフォルトの動作で、実際にはDockerfileのファイル名はDockerfileである必要はなく、コンテキストディレクトリにある必要もありません。/Dockerfile.phpパラメータでファイルをDockerfileとして指定できます。

もちろん、Dockerfileというデフォルトのファイル名を使い、イメージビルドのコンテキストディレクトリに置くのが一般的です。

Read next

どのようにエレガントにフィルタを実装する

どのようにそれをフィルタを実装するには、ここで私は、図からわかるように、フィルタを実現するために、あなたに出されたマップ間の関係のフィルタサブクラスを置く、あなたは直接、フィルタインターフェイスを実装することができますまた、彼のサブクラスを継承することができます、Httpリクエストの場合は、直接、次のことができます

Jul 26, 2020 · 4 min read