【docker-compose】depends_onとサービス名解決にまつわるエトセトラ

# 概要
docker-compose の `depends_on` における「依存関係」とは一体どのようなものなのか？
依存関係を明記しないとサービス開始に失敗する？
書かなきゃダメ？　書かなくても良い？

# 本文

## ことの発端

[Teratail | docker-compose.ymlでdepends_onを使わないで依存関係が生まれている理由を知りたい。](https://teratail.com/questions/270644)
PHPを動かすサービス(以下app)とNginxを動かすサービス(以下web)がある。webは拡張子がphpであるリクエストを受けるとapp側に処理を投げる(fastCGI)よう設定する。システムを俯瞰して見ると、webはappに依存している。
ところが質問内のdocker-compose.ymlではappとwebの間に依存関係を指定していない(`depends_on`の記述がない)。また、この構成は`dokcer-compose up`すると正しく動作する。どうやって両者の依存関係をコントロールしているのか？

## `depends_on`の挙動

[公式ドキュメント](http://docs.docker.jp/compose/toc.html)を読み漁りつつ回答したが、認識が間違っている個所もありつつだったのでもう一度整理してみる。

### `depends_on`詳細

[depends_onのリファレンス(http://docs.docker.jp/compose/compose-file.html#depends-on)にはこのような記載がある。

> サービス間の依存関係を指定したら、２つの効果があります。
> ・`docker-compose up` を実行したら、依存関係のある順番に従ってサービスを起動します。
  ・`docker-compose up` サービス（の名称） を実行したら、自動的に サービス の依存関係を処理します。

つまり、サービスの起動順序のみをコントロールしている。システム全体としての依存関係を見ているわけではない。
裏を返すと、サービスの起動順序のみで依存関係をコントロールするように、とも読める。

なお、この日本語版ドキュメントのバージョンは17.06で、現時点での最新バージョンは19.03。英語版の最新ドキュメントでは効果が1つ増えている。

> docker-compose stop stops services in dependency order.

起動時とは逆順にサービスが停止するようになったようだ。

### サービスの起動とは

`docker compose up`すると、以下の順に処理が行われる。

2. ネットワークの作成
1. build：dockerイメージの作成。
2. create：コンテナの作成。
3. start：作成したコンテナの起動。
4. 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`で始まるリクエスト情報を追いかけているだけであるが)

1. imageの取得
2. コンテナのcreate
3. createしたコンテナにattach
4. attachしたコンテナにjsonで情報を取得
4. ネットワークにconnect
5. コンテナstart

この場合、4.の段階でネットワークにサービス名の情報が追加される。
5.から6.までは多少時間かかる。

#### コンテナが既にcreate済みの場合

1. すでにcreateされているコンテナ情報の取得
2. コンテナにattach
4. attachしたコンテナにjsonで情報を取得
3. コンテナ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接続](https://www.nginx.com/resources/wiki/start/topics/examples/phpfcgi/#connecting-nginx-to-php-fpm)の場合、php側がListenしている.sockファイルを指定する。nginx側から見ると.sockファイル自体は別のコンテナに存在するので直接アクセスすることができないが、compose内で共有ボリュームを設定し両コンテナに同じボリュームをマウントすることで.sockファイルの共有を図っている。

それで、TCP接続と同じようにnginx起動時に.sockファイルが存在しないとエラーになってしまうのではと考えるが、実際はそうならない。unixソケットの場合は起動時に.sockの存在確認をしていないようだ。クライアントからhttpリクエストがあって初めて.sockに接続を試みる。なので（最終的にちゃんと両サービスが起動していれば）起動順を気にしなくても良い。
このことを、

- docker-composeの機能面から考えると、起動順に制限がないので`depends_on`を記載する必要はない
- システム構成面から考えると、起動順に制限がなくても、システムとしての依存性があるから`depends_on`は記載すべき

どちらが正しいかと言えば、なんとも悩ましいところ。現段階ではどっちでも良いのかな。