概要
docker-compose の depends_on
における「依存関係」とは一体どのようなものなのか?
依存関係を明記しないとサービス開始に失敗する?
書かなきゃダメ? 書かなくても良い?
本文
ことの発端
Teratail | docker-compose.ymlでdepends_onを使わないで依存関係が生まれている理由を知りたい。
PHPを動かすサービス(以下app)とNginxを動かすサービス(以下web)がある。webは拡張子がphpであるリクエストを受けるとapp側に処理を投げる(fastCGI)よう設定する。システムを俯瞰して見ると、webはappに依存している。
ところが質問内のdocker-compose.ymlではappとwebの間に依存関係を指定していない(depends_on
の記述がない)。また、この構成はdokcer-compose up
すると正しく動作する。どうやって両者の依存関係をコントロールしているのか?
depends_on
の挙動
公式ドキュメントを読み漁りつつ回答したが、認識が間違っている個所もありつつだったのでもう一度整理してみる。
depends_on
詳細
[depends_onのリファレンス(http://docs.docker.jp/compose/compose-file.html#depends-on)にはこのような記載がある。
サービス間の依存関係を指定したら、2つの効果があります。
・docker-compose up
を実行したら、依存関係のある順番に従ってサービスを起動します。
・docker-compose up
サービス(の名称) を実行したら、自動的に サービス の依存関係を処理します。
つまり、サービスの起動順序のみをコントロールしている。システム全体としての依存関係を見ているわけではない。
裏を返すと、サービスの起動順序のみで依存関係をコントロールするように、とも読める。
なお、この日本語版ドキュメントのバージョンは17.06で、現時点での最新バージョンは19.03。英語版の最新ドキュメントでは効果が1つ増えている。
docker-compose stop stops services in dependency order.
起動時とは逆順にサービスが停止するようになったようだ。
サービスの起動とは
docker compose up
すると、以下の順に処理が行われる。
- ネットワークの作成
- build:dockerイメージの作成。
- create:コンテナの作成。
- start:作成したコンテナの起動。
- attach:起動したコンテナの標準入力、標準エラー出力をターミナルに接続。
1,2,3はすでに作成されている場合はスキップされる。
depend_on
が指定されているとcreate
とstart
の順番がコントロールされる。
依存関係にあるサービスが先にstart
まで完了しないと元のサービスのcreateが始まらないようになっている。
ドキュメントで言う「サービスの起動」は、このstart
のことを指している。
dokcer-composeのサービス名解決方法
docker-composeで記載されたサービス名は、/etc/hosts
に書き込まれるわけではなく、up
するときに作成されるネットワークの仮想ルータが担っている。
仮想ルータにはDNSが走っていて、nslookup
等でサービス名で名前解決を依頼すると対象サービスが動いているコンテナのIPアドレスが返ってくる。
複数のcomposeを立ち上げている状態で、他のcomposeとサービス名がかぶっていてもcompose専用のネットワーク内で名前解決できるので他のcomposeのIPアドレスが返ってきたりすることはない。
仮想ルータDNSにサービス名が登録されるタイミング
では、up
中のどのタイミングで名前解決ができるようになっているのだろうか。
docker-compose --log-level DEBUG up
コマンドでデバッグログを出しつつ、いろいろなタイミングでdocker network inspect [ネットワーク名]
を実行する少々雑な方法で調査した。
docker network inspect
は対象のネットワークの詳細を出力することができる。その中には接続されているコンテナのホスト名、IPアドレス等の情報が含まれる。
コンテナがcreateされていない場合
ログを見る限り以下のような順序でコンテナが起動する。
(http://localhost:None
で始まるリクエスト情報を追いかけているだけであるが)
- imageの取得
- コンテナのcreate
- createしたコンテナにattach
- attachしたコンテナにjsonで情報を取得
- ネットワークにconnect
- コンテナstart
この場合、4.の段階でネットワークにサービス名の情報が追加される。
5.から6.までは多少時間かかる。
コンテナが既にcreate済みの場合
- すでにcreateされているコンテナ情報の取得
- コンテナにattach
- attachしたコンテナにjsonで情報を取得
- コンテナstart
この場合は3.の段階までにはネットワークにサービス名の情報が追加されている。もしかして2.の段階で追加されているのかもしれないが、2.と3.の間が一瞬なので調査できない。
3.から4.の間は多少時間がかかる。
複数のコンテナを同時に起動する場合
ほとんどの場合、どのコンテナもネットワークconnectやjson情報取得まで完了したら若干間をおいてそれぞれのコンテナがstartされる。内部でなんらかの待ち合わせをしているかどうかは定かではない。
各サービスがコンテナ名の名前解決を必要とするタイミング
次に、docker-composeに含まれるサービスが他のサービス名の解決を必要とするタイミングを考える。
本件のようにnginxとphp間でTCPによるfpm接続を考える。
- php側は
php:9000
でnginx側の接続をListenする。php側としては他サービスが勝手に接続してくるだけなので名前解決は必要ない。 - nginx側は
default.conf
にて*.php
のリクエストをphp:9000
に投げる設定をしている。nginx起動時に対象のサービス、つまりホスト名phpがネットワーク上に存在するかどうかDNSに確認している。
DNSからサービス名の情報が得られなかった場合、起動失敗となりエラー終了する。
つまり、nginx側はサービスがstartしてからシステムとして準備完了となるまでにサービス名の解決を必要としている。
nginx側でdepends_on
にphpを指定している場合、phpコンテナがstartしてからnginxコンテナがcreateもしくはstartするのが保証されているので、名前解決によるエラーは発生しないものと考えられる。
しかしdepends_on
を指定していない場合、双方のコンテナが同時にcreateもしくはstartする。コンテナのstartタイミングをそろえてくれているわけではなさそうなので状況によってはphp側がネットワークにサービス名を登録するまでにnginx側が先にstartしてしまうかもしれない。
ただ、私が調査で2つのコンテナを数十回up
とdown
を繰り返してみたが、サービスを同時に起動して名前解決が間に合わないパターンは一度もなかった。
まとめ
あるサービスが起動時に他サービスの名前解決を必要とする場合、数個のサービスを起動する場合であれば実質的に依存関係を明記する必要はない。しかし大量のサービスを一度に起動する場合は依存関係を明記しておいた方が安全である。それを踏まえると、常にdepends_on
を書いておくに越したことはない。
だた、そこまで依存関係をガチガチに考えなくても気軽に複数のサービスを扱えるのがdocker-composeの良いところかもしれない。
余談
そもそもはunixソケットによるfpm設定でなぜdepends_on
が必要ないかという話だった。
unixドメインソケットによるfpm接続の場合、php側がListenしている.sockファイルを指定する。nginx側から見ると.sockファイル自体は別のコンテナに存在するので直接アクセスすることができないが、compose内で共有ボリュームを設定し両コンテナに同じボリュームをマウントすることで.sockファイルの共有を図っている。
それで、TCP接続と同じようにnginx起動時に.sockファイルが存在しないとエラーになってしまうのではと考えるが、実際はそうならない。unixソケットの場合は起動時に.sockの存在確認をしていないようだ。クライアントからhttpリクエストがあって初めて.sockに接続を試みる。なので(最終的にちゃんと両サービスが起動していれば)起動順を気にしなくても良い。
このことを、
- docker-composeの機能面から考えると、起動順に制限がないので
depends_on
を記載する必要はない - システム構成面から考えると、起動順に制限がなくても、システムとしての依存性があるから
depends_on
は記載すべき
どちらが正しいかと言えば、なんとも悩ましいところ。現段階ではどっちでも良いのかな。