本記事での言葉の定義
- 起動: 動き始めること(例: PC の電源ボタンを押すこと)
- 稼働: 働きはじめること(例: PC が利用可能になること)
はじめに
以下の Redis と MySQL を利用するアプリケーションの docker-compose を考えます。
version: "3" services: app: build: app depends_on: - redis - mysql mysql: image: mysql:5.7 ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: password redis: image: redis:6.0.5-alpine ports: - "6379:6379"
アプリーケーションが起動する前に Redis と MySQL が稼働することが期待されます。 docker-compose では、depends_on によって起動順は制御することができますが稼働順は制御することができません。
以下の Redis と MySQL へのネットワークの疎通を確認するだけのアプリケーションで実験します。
func main() { client := redis.NewClient(&redis.Options{Addr: "redis:6379"}) if err := client.Ping().Err(); err != nil { log.Fatal(err) } db, err := sql.Open("mysql", "root:password@tcp(mysql:3306)/performance_schema") defer db.Close() if err != nil { log.Fatal(err) } if err := db.Ping(); err != nil { log.Fatal(err) } log.Println("success") }
docker-compose up
すると、depends_on_app_1 exited with code 1
となりアプリケーションが異常終了します。
これは、MySQL の稼働前にアプリケーションが稼働し、MySQL に接続できないためです。
Redis はすぐに稼働しましたが、MySQL は稼働まで時間がかかりました。
対処法
アプリケーションでで稼働待ちする
アプリケーション内で再接続を行うことが最適解です。 何らかの理由でもデータベースへの接続に失敗した後に、接続を再度確立するようにアプリケーションを設計しておくことが必要です。 以下のようにループとスリープによって稼働待ちしました。 これはアプリケーションでの稼働待ちを表現したかっただけで、Goでのベストプラクティスは別にあるかもしません。
func main() { client := redis.NewClient(&redis.Options{Addr: "redis:6379"}) if err := client.Ping().Err(); err != nil { log.Fatal(err) } db, err := sql.Open("mysql", "root:password@tcp(mysql:3306)/performance_schema") if err != nil { log.Fatal(err) } defer db.Close() // 稼働待ち for { err = db.Ping() if err == nil { break } time.Sleep(3 * time.Second) } log.Println("success") }
docker-compose で稼働待ちする
それほどに厳密性を求めない場合であれば、簡単なスクリプトで稼働順序を制御することも選択肢にあります。
以上がDockerのドキュメントでも紹介されていますが、ベースイメージで Alpine Linux を使っておりash
でうまく動作しなかったため簡単な独自スクリプトを作成しました。
docker-compose.yml
では、アプリケーションを起動する前にスクリプトを挟みます。
app: build: app depends_on: - redis - mysql command: ["./wait-for-it.sh", "./main"]
以下のスクリプトでRedisとMySQLの稼働待ちをします。
最後のexec $@
で./main
を実行します。
また、厳密に稼働待ちするためにはnc
コマンドではなく、MySQLクライアントによるmysql -h"$host" -u"$user" -p"$password"
で疎通確認をすべきです。
しかし、アプリケーションのコンテナに初期からMySQLクライアントが入っていることはなくインストールする必要があります。
MySQLクライアントをあえてインストールするのは大袈裟すぎるので、nc
コマンドで代用します。
#!/bin/ash # wait-for-it.sh set -e until nc -z redis 6379; do >&2 echo "redis is unavailable - sleeping" sleep 3 done until nc -z mysql 3306; do >&2 echo "mysql is unavailable - sleeping" sleep 3 done >&2 echo "redis and mysql is up - executing command" exec $@
nc
コマンドでは、ネットワークの疎通確認だけでなく様々なことができます。
また、Alpine Linuxでさえ最初から含まれているので嬉しいです。
qiita.com
ソースコード全体は以下にあります。