🔥
【Go】【Docker】DBにconnection refusedされた時の対処
検証環境
Go: 1.16.5
docker compose: 3.9
Mysql: 5.7
Gorm: 1.23.3
GoコンテナからDBに接続できなことありませんか?
環境構築の際に、以下のようにとりあえずのファイルを用意したとします。
docker-compose.yml
version: '3.9'
services:
go:
build:
context: .
dockerfile: ./deployments/dev/go/Dockerfile
ports:
- 8080:8080
tty: true
depends_on:
- mysql
mysql:
container_name: testDB
build: ./docker/mysql
ports:
- 3306:3306
volumes:
- mysql_data:/var/lib/mysql
- ./docker/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
environment:
- MYSQL_ALLOW_EMPTY_PASSWORD=1
- MYSQL_DATABASE=xxxx
- MYSQL_USER=xxxx
- MYSQL_PASSWORD=xxxx
- TZ=Asia/Tokyo
volumes:
mysql_data:
driver: local
main.go
var gormDB *gorm.DB
func main() {
_gormDB, err := gorm.Open(mysql.Open("xxx:mysql@tcp(xxx)/xxx?parseTime=true"), &gorm.Config{})
db, _ := _gormDB.DB()
defer db.Close()
...
}
Dockerfileの中身は割愛
この後、docker compose up --build
をしてみると…
次のようなエラーが出て、Goコンテナが落ちてしまうことがあると思います。
"failed to initialize database, got error dial tcp 192.168.0.2:3306: connect: connection refused"
データベースへの接続に失敗しているようですが
さぁ何が原因でしょうか?
DB初期化の前にGoがDBに接続しにいって失敗しとる
上述のエラーについて、さまざまの原因があると思います。
原因の1つとして、GoがDBに接続しにいくタイミングが早すぎて、DBの初期化が終わってないことが挙げられます。
(定量的な原因特定ではありません)
事実、docker compose up mysql
として、先DBのコンテナだけを立ち上げ、しばらく待ったの後に、docker compose up go
とすると問題なく立ち上がるのです。
問題は無くなるのですが、できればdocker compose up
と1回で立ち上げってくれるとありがたいですよね。
解決策(先人(神)): リトライ処理を書く
上述のエラーを解決できず、途方に暮れていた私に手を差し伸べてくれたのがこちらの記事。
DB接続に失敗しても、任意の回数リトライする処理を書かれています。
(コードを引用していいのかわからないので引用元参照してね)
解決策(オレ流): 先人のに少しカスタマイズ
先人様のコードで十分と言えますが、若干弊社のコーディングスタイルとマッチしていなかったので、カスタマイズしました。
main.go
package main
import (
// import something
)
var db *gorm.DB
func dbConnect(dialector gorm.Dialector, config gorm.Option, count uint) (err error) {
// countで指定した回数リトライする
for count > 1 {
if db, err = gorm.Open(dialector, config); err != nil {
time.Sleep(time.Second * 2)
count--
log.Printf("retry... count:%v\n", count)
continue
}
break
}
// エラーを返す
return err
}
func main() {
dsn := fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Asia%%2FTokyo",
os.Getenv("DB_USER"),
os.Getenv("DB_PASS"),
os.Getenv("DB_HOST"),
os.Getenv("DB_PORT"),
os.Getenv("DB_DBNAME"),
)
dialector := mysql.Open(dsn)
option := &gorm.Config{Logger: ...}
// optionを渡すことができる
if err = dbConnect(dialector, option, 100); err != nil {
log.Fatalln(err)
}
// do something
先人様との違いは以下になります。
- リトライの文を
for
で表現した - すべてのリトライに失敗した場合,エラーを返すようにした(panicにしない)
- DB接続の関数にGormのOptionを渡すようにした
まとめ
神はzennにおわしたんやなぁ〜(しみじみ)
Discussion