Docker Compose の depends_on の使い方まとめ

Docker

Docker Compose の depends_on の使い方をかんたんにまとめました。

主に自分用のまとめですが、このあたりは公式ドキュメントの説明があまり充実していないので Docker Compose をよく使う方のお役に立つのではないかと思います。

前提

Compose ファイル( docker-compose.yml )のバージョン 3.8 を前提としています。

動作確認には次のバージョンを使用しました:

$ docker --version
Docker version 20.10.5, build 55c4c88

$ docker-compose --version
docker-compose version 1.29.0, build 07737305

depends_on とは

depends_on は Docker Compose の各サービスに対して設定できる項目です。 名前が示唆するとおり depends_on を使うとサービス間の依存関係を指定できます。

たとえば次のように書くと service_bservice_a に依存させることができます:

version: "3"

services:
  service_a:
    image: busybox
  service_b:
    image: busybox
    # `service_b` を `service_a` に依存させる
    depends_on:
      - service_a

このように設定すると docker-compose コマンドを実行したときに次のような制御が行われます:

  • docker-compose up: service_aservice_b の順に起動する
  • docker-compose run: ( docker-compose up と同じ)
  • docker-compose stop: serivce_bservice_a の順に停止する

depends_on の指定パターン

depends_on は次の 2 つのパターンで指定できます。

  • Short syntax (リスト形式)
  • Long syntax (オブジェクト形式)

Short syntax (リスト形式)

依存先のサービス名を記述するだけのシンプルな形式です。 たとえば次のように設定したサービスは service_a という名前のサービスに依存することになります:

depends_on:
  - service_a

Long syntax (オブジェクト形式)

依存先のサービス名に加えてそれぞれの条件を指定できる形式です。 たとえば次のように設定したサービスは service_a に依存することになります:

depends_on:
  service_a:
    condition: service_started

条件の指定には condition を使用します。 採りうる選択肢は次の 3 つです:

  1. service_started
  2. service_healthy
  3. service_completed_successfully

condition にはデフォルト値がありません。 Long syntax を使う場合は必ず condition の値を指定する必要があります。

以下説明が長くなるので先にまとめですが、それぞれ次のような挙動になります:

選択肢 説明
service_started 依存先のサービスが起動したら起動する
service_healthy 依存先のサービスが起動して、なおかつ、 healthcheck が通ったら起動する
service_completed_successfully 依存先のサービスが正常終了したら起動する

以下細かく見ていきます。

1. service_started

service_started は依存先のサービスが「起動したこと」を条件とするものです。

condition: service_started

次のように書くと、 service_bservice_a が起動した後に起動します:

version: "3"

services:
  service_a:
    image: busybox
  service_b:
    image: busybox
    depends_on:
      service_a:
        condition: service_started

実行結果:

$ docker-compose up -d

Creating network "PROJECT_default" with the default driver
Creating PROJECT_service_a_1 ... done
Creating PROJECT_service_b_1 ... done

service_started を使うときに注意すべきポイントは、 service_started は依存先のサービスが「起動」しさえすれば依存元のサービスが起動される点です。 依存先のサービスのメインとなる処理の「待ち受け状態が整う」まで待つことはしてくれません。 そのため、ウェブアプリケーションとデータベースのように、依存元のサービスが正常に動作するために依存先のサービスが正しく応答することが不可欠な場合に service_started では問題になることがあります。

たとえば次のように書くと、この挙動が問題になることが確認できます:

version: "3"

services:
  # service_a: わざと 5 秒待機した後にウェブサーバーを立ち上げる
  service_a:
    image: busybox
    command:
      - sh
      - -c
      - |
        set -ex
        sleep 5
        echo 'Hi' > /var/www/index.html
        httpd -f -p 80 -h /var/www
    ports:
      - "80:80"
  # service_b: service_a に対してリクエストをかける
  # 最初の 1 回だけリクエストが失敗してしまう
  service_b:
    image: busybox
    command:
      - sh
      - -c
      - |
        while true; do
          wget -O- -q http://service_a:80
          sleep 5
        done
    depends_on:
      service_a:
        condition: service_started

この設定で docker-compose up を実行すると次のようになります:

$ docker-compose up

Creating network "PROJECT_default" with the default driver
Creating PROJECT_service_a_1 ... done
Creating PROJECT_service_b_1 ... done
Attaching to PROJECT_service_a_1, PROJECT_service_b_1
service_b_1  | wget: can't connect to remote host (192.168.240.2): Connection refused
service_a_1  | + sleep 5
service_a_1  | + echo Hi
service_a_1  | + httpd -f -p 80 -h /var/www
service_b_1  | Hi
service_b_1  | Hi

Connection refused ということで、最初の 1 回だけ service_b から service_a への wget リクエストが失敗してしまいます。

なお、 short syntax (リスト形式)で使ったときの挙動は、 long syntax (オブジェクト形式)で service_started を指定したときと同じ挙動になります。

2. service_healthy

service_healthy は依存先のサービスが起動してなおかつ healthcheck がパスすることを条件とするものです。

condition: service_healthy

healthcheck をパスするかどうかのチェックが必要になるので、 service_healthy を使う場合は必ず依存先のサービスで healthcheck を定義する必要があります。

参考: healthcheck | Compose file version 3 reference | Docker Documentation

次のように書くと、 service_a の healthcheck がパスした後に service_b が起動するようになります:

version: "3"

services:
  service_a:
    image: busybox
    # 状態を Up に保つために httpd を使用する
    command: httpd -f
    healthcheck:
      test: exit 0
      # すぐに結果が出るようにタイミングを調整する
      interval: 1s
      timeout: 1s
      retries: 3
      start_period: 1s
  service_b:
    image: busybox
    depends_on:
      service_a:
        condition: service_healthy

実行結果:

$ docker-compose up -d

Creating network "PROJECT_default" with the default driver
Creating PROJECT_service_a_1 ... done
Creating PROJECT_service_b_1 ... done

service_started の場合と異なり、 service_healthy では次のようなときに対象のサービスは起動しません。

  • 依存先サービスの healthcheck が通らなかった場合
  • 依存先サービスの処理が終了してコンテナが停止した場合

たとえば次のように書くと、 service_a は正常に動いていますが healthcheck が通らないため service_b は起動しません:

version: "3"

services:
  service_a:
    image: busybox
    # かんたんに状態を Up に保つためのに httpd を使用する
    command: httpd -f
    # healthckeck が通らないようにあえて exit 1 を実行する
    healthcheck:
      test: exit 1
      # すぐに結果が出るようにタイミングを調整する
      interval: 1s
      timeout: 1s
      retries: 3
      start_period: 1s
  service_b:
    image: busybox
    depends_on:
      service_a:
        condition: service_healthy

この設定を使って docker-compose up -d を実行した後に docker-compose ps を実行すると、 service_a の state 判定が Up (unhealthy) となっていること、そして、 service_b が起動していないことを確認できます:

$ docker-compose ps

       Name          Command        State        Ports
------------------------------------------------------
PROJECT_service_a_1   httpd -f   Up (unhealthy)

また、次のように書いた場合は、 service_a が( sh を実行して)すぐに停止してしまうので、こちらも service_b は起動しません:

version: "3"

services:
  service_a:
    image: busybox
    healthcheck:
      test: exit 0
      # すぐに結果が出るようにタイミングを調整する
      interval: 1s
      timeout: 1s
      retries: 3
      start_period: 1s
  service_b:
    image: busybox
    depends_on:
      service_a:
        condition: service_healthy

3. service_completed_successfully

service_completed_successfully は依存先のサービスが正常に終了したことを条件とするものです。

condition: service_completed_successfully

service_completed_successfully では依存先のサービスが「正常に終了すること」を必要とするため、 service_startedservice_healthy とは少し毛色・使いどころが違います。 service_startedservice_healthy の場合依存先のサービスは「稼働し続けるもの」である必要がありますが、 service_completed_successfully の場合は「必要な処理を終えたら自動的に終了するもの」でなくてはなりません。

次のように書くと、 service_a が起動してすぐに停止した後に service_b が起動します:

version: "3"

services:
  # service_a: すぐに正常終了させる
  service_a:
    image: busybox
    command: ["sh", "-c", "exit 0"]
  service_b:
    image: busybox
    depends_on:
      service_a:
        condition: service_completed_successfully

次のように書くと、 service_a が(正常終了ではなく)異常終了するので service_b は起動しません:

version: "3"

services:
  # service_a: 異常終了させる
  service_a:
    image: busybox
    command: ["sh", "-c", "exit 1"]
  service_b:
    image: busybox
    depends_on:
      service_a:
        condition: service_completed_successfully

また、次のように書くと、 service_a が終了しないのでいくら待っても service_b は起動しません:

version: "3"

services:
  # `service_a` を終了させない
  service_a:
    image: busybox
    command: ["sh", "-c", "while true; do echo Hello; sleep 5; done"]
  service_b:
    image: busybox
    depends_on:
      service_a:
        condition: service_completed_successfully

ちなみに、この service_completed_successfully は 2021 年 2 月に仕様として追加 & 実装された比較的新しい機能です。 docker-compose ではバージョン 1.29.0 ( 2021/04/06 リリース)以降で使えるようになっています。 「 Kubernetes の Init Containers に似た機能を Docker Compose でも使いたい」という考えからリクエストがあり追加されたようです。

参考:

ということで Docker Compose の depends_on のまとめでした。 正確な仕様は compose-spec を参照してください。

参考

compose-specspec.md が最も正確なドキュメントのようです。


アバター
後藤隼人 ( ごとうはやと )

Python や PHP を使ってソフトウェア開発やウェブ制作をしています。詳しくはこちら