gin + mysql + dockerで環境を構築した際に遭遇したconnection refusedエラーと対応について記載する。
問題の概要
docker-compose.ymlでdepends_onを使い、mysql → goの順に起動するよう制御しているつもりが、
mysqlの起動を待たずにgoが起動してしまい、connection refusedで落ちる。
TL;DR
docker-composeのconditonとhealthcheckを使うことでmysqlの起動を待ってからgoを起動させることに成功。
下記のようにapp/depends_onのcondition
とdbのhealthcheck
を追記した。
services:
go:
depends_on:
- db
db:
(通常通りの設定)
services:
go:
depends_on:
db:
condition: service_healthy
db:
healthcheck:
test: mysqladmin ping -h 127.0.0.1 -u$$MYSQL_USER -p$$MYSQL_PASSWORD
問題が発生した時の状況
環境
Mac M1
OS Monterey
Docker 20.10.22
構成
root
├ .env
├ docker-compose.yml
├ Dockerfile
├ go.mod
├ go.sum
└ main.go
main.go
func main() {
config := mysql.Config{
User: os.Getenv("MYSQL_USER"),
Passwd: os.Getenv("MYSQL_PASSWORD"),
Net: "tcp",
Addr: os.Getenv("MYSQL_HOST") + ":3306",
DBName: os.Getenv("MYSQL_DATABASE"),
AllowNativePasswords: true,
}
var err error
db, err = sql.Open("mysql", config.FormatDSN())
if err != nil {
log.Fatal(err)
}
pingErr := db.Ping()
if pingErr != nil {
log.Fatal(pingErr)
}
fmt.Println("Connected!")
router := gin.Default()
router.Run()
}
発生したエラー
docker-compopse up時に発生。
途中db側の起動が終わる前にgoのログ(connection refused)が吐かれている。
デバッグしてみるとわかるが、main.goのdb.Ping()での疎通確認で発生している。
Attaching to golang-docker-app-1, golang-docker-db-1
db-1 | 2023-02-04 19:52:47+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.32-1.el8 started.
(...まずはdb側が起動している様子のログ...)
app-1 | 2023/02/04 19:52:55 dial tcp 172.21.0.2:3306: connect: connection refused
app-1 | exit status 1
app-1 exited with code 1
(...以降引き続きdb側のログ...)
原因
詳細はわかっていないが、どうやらMacだとdepends_onで「起動した」と判定するタイミングが早いようで、
mysqlの準備が整う前に「起動したよ」と判断してしまう様子。
ちなみにこれはIntel Macでも発生。
上記の通りconditionとhealthcheckを利用することで解決。
ちなみにlinuxだと発生しなかった。
Tips:override.yml
Macのためだけにdocker-compose.ymlにhealthcheckつけるの嫌だよねという意見もありそう。
そんな時はdocker-compose.override.ymlを使うという手がある。
docker-compose.override.ymlは、その名の通りdocker-compose.ymlの設定を上書き変更するための設定ファイル。
docker起動時にdocker-compose.ymlと一緒に読み込まれる。
使い方としては、
- 基本の設定はdocker-comopse.ymlに記述
- 上書き・追記したい部分だけをdocker-compose.override.ymlに記述して、同じディレクトリに置いておく。
- docker-compose.override.ymlをgitignoreに入れておく
という感じかなと。
今回の問題に当てはめてみた場合はこちら。
services:
go:
depends_on:
- db
db:
(通常通りの設定)
services:
go:
depends_on:
db:
condition: service_healthy
db:
healthcheck:
test: mysqladmin ping -h 127.0.0.1 -u$$MYSQL_USER -p$$MYSQL_PASSWORD
詳しくはこちら