Docker Compose で依存先のコンテナが「ちゃんと」起動しているかチェックするひとつの方法
背景
Docker Compose を使って複数のコンテナをまとめる場合、「あるコンテナが起動してから別のコンテナを起動する」、といったようにコンテナを決まった順番に起動させたい場合がよくあると思います。
- DBコンテナが起動してからAPサーバを起動したい
- モックサーバが起動してからAPIサーバを起動したい などなど
そんな場合によく使われるのが Docker Compose のdepends_on
ですが、実はこのオプションも独りではそこまで万能でもありません。
公式の記述を引用します。
version: "3.9"
services:
web:
build: .
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres
depends_on では、 web を開始する前に db と redis の「準備」が整うのを待ちません。単に、順番通り開始するだけです。
というわけで、素のdepends_on
で指定できるのは単にコンテナの「起動順」のみであり、依存先のコンテナで必要なサービスが起動しているかまではチェックしてくれていないことがわかります。
上記で挙げた「APサーバとDBサーバ」のような構成の場合、おおむねDBサーバが必要とされるのはAPサーバがリクエストを受けるタイミングなので、起動時にはそこまで問題とならないことが多いですが、起動直後から別の依存先のサービスを参照するようなアプリケーションの場合、起動に失敗してしまうこともあります。
ひとつの攻略例
ここでは、依存先のサービスがpostgresql
である場合を例にとり、postgresqlがちゃんと起動してから他のコンテナを起動する例をご紹介します。
うまくいかない例
services:
postgres:
image: postgres:14
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: ferretdb
ports:
- 5432:5432
volumes:
- postgres-bg-ferretdb:/var/lib/postgresql/data
networks:
- app-network
ferretdb:
image: ghcr.io/ferretdb/ferretdb
ports:
- 27017:27017
environment:
FERRETDB_POSTGRESQL_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/ferretdb
networks:
- app-network
depends_on:
- postgres # postgres に依存している
volumes:
postgres-bg-ferretdb:
networks:
app-network:
postgresql に依存するコンテナとしては「FerretDB」を使ってみました。
FerretDB は バックエンドに postgresql のようなRDBMSを用いていて、起動時にバックエンドのDBへ接続するため、今回の場合は postgresql のサービスが起動していないと、起動に失敗します。
この状態で、docker compose up -d
してみます。
ステータスだけだとうまく起動しているようにも見えますので、docker compose logs -f ferretdb
として、ferretdb
の状態を確認してみます。
ferretdb-ferretdb-1 | 2023-05-11T08:46:33.313+0900 INFO erretdb/main.go:231 Starting FerretDB v1.0.0... {"version": "v1.0.0", "commit":
--- 途中省略 ---
ferretdb-ferretdb-1 | 2023-05-11T08:46:34.248+0900 INFO pgdb v4@v4.18.1/conn.go:354 Dialing PostgreSQL server {"host": "postgres"}
ferretdb-ferretdb-1 | 2023-05-11T08:46:34.250+0900 ERROR pgdb v4@v4.18.1/conn.go:354 connect failed {"err": "failed to connect to `host=postgres user=postgres database=ferretdb`: dial error (dial tcp 172.20.0.3:5432: connect: connection refused)"}
ferretdb-ferretdb-1 | github.com/jackc/pgx/v4.(*Conn).log
ferretdb-ferretdb-1 | /cache/gomodcache/github.com/jackc/pgx/v4@v4.18.1/conn.go:354
ferretdb-ferretdb-1 | github.com/jackc/pgx/v4.connect
ferretdb-ferretdb-1 | /cache/gomodcache/github.com/jackc/pgx/v4@v4.18.1/conn.go:225
ferretdb-ferretdb-1 | github.com/jackc/pgx/v4.ConnectConfig
ferretdb-ferretdb-1 | /cache/gomodcache/github.com/jackc/pgx/v4@v4.18.1/conn.go:113
ferretdb-ferretdb-1 | github.com/jackc/pgx/v4/pgxpool.ConnectConfig.func1
ferretdb-ferretdb-1 | /cache/gomodcache/github.com/jackc/pgx/v4@v4.18.1/pgxpool/pool.go:232
ferretdb-ferretdb-1 | github.com/jackc/puddle.(*Pool).constructResourceValue
ferretdb-ferretdb-1 | /cache/gomodcache/github.com/jackc/puddle@v1.3.0/pool.go:558
ferretdb-ferretdb-1 | github.com/jackc/puddle.(*Pool).Acquire.func1
ferretdb-ferretdb-1 | /cache/gomodcache/github.com/jackc/puddle@v1.3.0/pool.go:317
ferretdb-ferretdb-1 | 2023-05-11T08:46:34.250+0900 WARN pg/pg.go:136 DBPool: authentication failed {"username": "", "error": "pgdb.NewPool: failed to connect to `host=postgres user=postgres database=ferretdb`: dial error (dial tcp 172.20.0.3:5432: connect: connection refused)"}
--- 途中省略 ---
ferretdb-ferretdb-1 | 2023-05-11T08:46:34.250+0900 ERROR // 172.20.0.5:46074 -> 172.20.0.4:27017 clientconn/conn.go:556 Response message:
ferretdb-ferretdb-1 | {
ferretdb-ferretdb-1 | "Checksum": 0,
ferretdb-ferretdb-1 | "FlagBits": 0,
ferretdb-ferretdb-1 | "Sections": [
ferretdb-ferretdb-1 | {
ferretdb-ferretdb-1 | "Document": {
ferretdb-ferretdb-1 | "$k": [
ferretdb-ferretdb-1 | "ok",
ferretdb-ferretdb-1 | "errmsg",
ferretdb-ferretdb-1 | "code",
ferretdb-ferretdb-1 | "codeName"
ferretdb-ferretdb-1 | ],
ferretdb-ferretdb-1 | "ok": {
ferretdb-ferretdb-1 | "$f": 0
ferretdb-ferretdb-1 | },
ferretdb-ferretdb-1 | "errmsg": "[msg_listdatabases.go:34 pg.(*Handler).MsgListDatabases] [pg.go:137 pg.(*Handler).DBPool] pgdb.NewPool: failed to connect to `host=postgres user=postgres database=ferretdb`: dial error (dial tcp 172.20.0.3:5432: connect: connection refused)",
ferretdb-ferretdb-1 | "code": 1,
ferretdb-ferretdb-1 | "codeName": "InternalError"
ferretdb-ferretdb-1 | },
ferretdb-ferretdb-1 | "Kind": 0
ferretdb-ferretdb-1 | }
ferretdb-ferretdb-1 | ]
ferretdb-ferretdb-1 | }
ferretdb-ferretdb-1 |
ferretdb-ferretdb-1 |
ferretdb-ferretdb-1 |
ferretdb-ferretdb-1 | github.com/FerretDB/FerretDB/internal/clientconn.(*conn).logResponse
ferretdb-ferretdb-1 | /src/internal/clientconn/conn.go:556
ferretdb-ferretdb-1 | github.com/FerretDB/FerretDB/internal/clientconn.(*conn).run
ferretdb-ferretdb-1 | /src/internal/clientconn/conn.go:293
ferretdb-ferretdb-1 | github.com/FerretDB/FerretDB/internal/clientconn.acceptLoop.func1
ferretdb-ferretdb-1 | /src/internal/clientconn/listener.go:307
ferretdb-ferretdb-1 | 2023-05-11T08:46:34.250+0900 WARN listener clientconn/listener.go:311 Connection stopped {"conn": "172.20.0.5:46074 -> 172.20.0.4:27017", "error": "fatal error"}
ferretdb-ferretdb-1 | 2023-05-11T08:46:35.258+0900 INFO listener clientconn/listener.go:305 Connection started {"conn": "172.20.0.5:46078 -> 172.20.0.4:27017"}
ferretdb-ferretdb-1 | 2023-05-11T08:46:35.395+0900 INFO listener clientconn/listener.go:305 Connection started {"conn": "172.20.0.2:37696 -> 172.20.0.4:27017"}
ferretdb-ferretdb-1 | 2023-05-11T08:46:35.504+0900 INFO listener clientconn/listener.go:305 Connection started {"conn": "172.20.0.2:37704 -> 172.20.0.4:27017"}
dial error (dial tcp 172.20.0.3:5432: connect: connection refused)
が出力されており、postgresqlへの接続に失敗していることがわかります。[1]
その後、リトライによりConnection started
となって正常に起動できている様子も見えますが、なんとなくきもちわるいのでどうにかしたいところです。
うまくいくようにしてみる
services:
postgres:
image: postgres:14
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: ferretdb
ports:
- 5432:5432
volumes:
- postgres-data:/var/lib/postgresql/data
+ healthcheck: # コンテナの起動チェック
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
+ interval: 5s
+ retries: 3
networks:
- app-network
ferretdb:
image: ghcr.io/ferretdb/ferretdb
ports:
- 27017:27017
environment:
FERRETDB_POSTGRESQL_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/ferretdb
networks:
- app-network
depends_on:
- - postgres
+ postgres:
+ condition: service_healthy # postgres がちゃんと起動したら start
volumes:
postgres-data:
networks:
app-network:
一度docker compose down
したあと、同様にdocker compose up -d
してみます。
up
コマンド実行直後の状態です。postgres
コンテナがWaiting
となり、ferretdb
コンテナはCreated
の状態で待っている様子がわかります。
数秒待つと、postgres
コンテナの状態がHealthy
となり、ferretdb
もStarted
となりました。
ferretdb-ferretdb-1 | 2023-05-11T09:12:36.833+0900 INFO ferretdb/main.go:231 Starting FerretDB v1.0.0... {"version": "v1.0.0", "commit": "6734769da718c9b1b182f9c4ab61fb5e9fa37bd6", "branch": "unknown", "dirty": true, "package": "docker", "debugBuild": false, "buildEnvironment": {"-buildmode":"exe","CGO_ENABLED":"0","GOAMD64":"v1","GOARCH":"amd64","GOOS":"linux","buildtags":"ferretdb_tigris","compiler":"gc","vcs":"git","vcs.time":"2023-04-03T17:19:58Z"}, "uuid": "299725a4-0eed-4013-a7cb-d3c4a8c84662"}
ferretdb-ferretdb-1 | 2023-05-11T09:12:36.834+0900 INFO telemetry telemetry/reporter.go:145 The telemetry state is undecided; the first report will be sent in 1h0m0s. Read more about FerretDB telemetry and how to opt out at https://beacon.ferretdb.io.
ferretdb-ferretdb-1 | 2023-05-11T09:12:36.835+0900 INFO listener clientconn/listener.go:95 Listening on TCP [::]:27017 ...
ferretdb-ferretdb-1 | 2023-05-11T09:12:36.836+0900 INFO listener clientconn/listener.go:183 Waiting for all connections to stop...
ferretdb-ferretdb-1 | 2023-05-11T09:12:36.836+0900 INFO debug debug/debug.go:95 Starting debug server on http://[::]:8080/
ferretdb-ferretdb-1 | 2023-05-11T09:12:45.859+0900 INFO listener clientconn/listener.go:305 Connection started {"conn": "172.20.0.2:53430 -> 172.20.0.4:27017"}
ferretdb-ferretdb-1 | 2023-05-11T09:12:45.957+0900 INFO listener clientconn/listener.go:305 Connection started {"conn": "172.20.0.2:53442 -> 172.20.0.4:27017"}
起動時のエラーも出なくなり、正常に構成されたことがわかります!
仕組みについておさらい
依存される側
healthcheck
オプションを追加し、自身のヘルスチェックを行うコマンドを記述します。
今回のケースでは postgresql の機能であるpg_isready
コマンドが正常に終了した状態をhealty
とするようにしました。interval
とretries
はよしなに指定します。
依存する側
depends_on
にcondition
オプションを追加し、service_healthy
を指定すると、コンテナは依存先コンテナのヘルスチェックが完了するまで、Waiting
状態で待機するようになります。
まとめ
以上です。
Docker Compose で複数のコンテナを構成するにあたり、あるコンテナが別のコンテナに強く依存している場合の起動順のコントロール方法についてご紹介しました。
依存されるコンテナやサービスによってヘルスチェックの方法は様々あると思いますので、ぜひご参考いただいて活用いただければ幸いです。
ではまた!
参考記事
-
実はタイミング次第でうまくいってしまう場合もあります。 ↩︎
Discussion