TL;DR
Docker Composeとかでコンテナー化のMySQLサーバーに正常に接続できたらアプリを起動するという依存関係を実現したいのなら、下記のようなhealthcheck
とdepends_on
構文を使ってください。
app:
container_name: app
build:
context: .
dockerfile: Dockerfile
ports:
- '8080:8080'
depends_on:
mysql:
condition: service_healthy
mysql:
container_name: mysql
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: password
healthcheck:
test: "mysqladmin ping -h 127.0.0.1 -u root -p$$MYSQL_ROOT_PASSWORD"
timeout: 5s
interval: 5s
retries: 10
一時的に立ち上げられたMySQLサーバーによる惨事
いろんな記事は上記のtest
文が["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
と書いてあるが、それは実に大問題になる。
MySQLコンテナーは起動する時にまず一時的なMySQLサービスを立ち上げて初期化プロセスをするので、もしこのタイミングに上記のテストコマンドが実行されたら、ヘルスチェックが成功になってしまう。でも本当なサービスがまだ起動してないので、MySQLに依存するコンテナーの起動は当然に失敗になるわけ。
一時的なMySQLサーバーと正式なMySQLサーバーにどんな違いがあるか
一時的なサーバーはローカルのsocket connectionしかサポートしていない。
正式的なサーバーはnetwork interfaceもlistenしている。
MySQLのクライエントは指定されるホストがlocalhostであれば自動的にsocket connectionで接続するので、mysqladmin ping -h localhost
だと、成功しても一時的なサーバーに接続した可能性もあるので、ヘルスチェックの標準として頼れない。
ドルが足りない環境変数
もしMySQLコンテナーにRootパスワードを指定したら、mysqladmin ping
にはパスワードも必要になる。
test: "mysqladmin ping -h 127.0.0.1 -u root -p$MYSQL_ROOT_PASSWORD"
でいいじゃない?
もし単純にシェルで実行すれば、確かにこれで問題なし。でもdocker compose
のYAMLであれば、$MYSQL_ROOT_PASSWORD
を$$MYSQL_ROOT_PASSWORD
を変更しなければならない。
test: "mysqladmin ping -h 127.0.0.1 -u root -p$$MYSQL_ROOT_PASSWORD"
原因はこの前に記事で書いたことがある。
テスト文のフォーマット
ちなみに、ここには["CMD", "command", "argument"]
のようなフォーマットじゃだめそう。なぜなら、CMDはEXECでコマンドを実行するので、コマンド内の環境変数は普通の文字列として扱ってしまう。["CMD", "mysqladmin" ,"ping", "-h", "127.0.0.1", "-p$$MYSQL_ROOT_PASSWORD"]
がmysqladmin ping -h 127.0.0.1 -p'$MYSQL_ROOT_PASSWORD'
になる。つまりここで指定されたRootパスワードが$MYSQL_ROOT_PASSWORD
そのままであるわけ。
[]
のようなリストフォーマットにこだわれば、CMD-SHELL
を使ってください。
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -u root -p$$MYSQL_ROOT_PASSWORD"]
ドキュメントによると、
test must be either a string or a list. If it's a list, the first item must be either NONE, CMD or CMD-SHELL. If it's a string, it's equivalent to specifying CMD-SHELL followed by that string.