Go+MySQLをDockerで立ち上げようとしたら下のようなエラーが出ました。
failed to initialize database, got error dial tcp 127.0.0.1:3306: connect: connection refused
これはdatabaseの初期化がうまくいかず、コネクションができなかったというエラーです。
解決方法
dsnを確認する
dsnは、Data Source Name
の略称で、データベースの識別子のことです。
例えばMySQLだと以下のようなdsnになります。
[user]:[password]@tcp([IPアドレス]:3306)/[database_name]?charset=utf8mb4&parseTime=True
まず上手くいかなかった例を紹介します。
私は、ymlファイルにMySQLの設定を下のように記述していました。
services:
server:
build:
context: .
dockerfile: ./docker/go/Dockerfile
tty: true
depends_on:
- db
ports:
- 8080:8080
db:
image: mysql:8.0
container_name: db
restart: always
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: db
MYSQL_USER: user
MYSQL_PASSWORD: password
TZ: Asia/Tokyo
LANG: ja_JP.UTF-8
ports:
- 3306:3306
volumes:
- db-volume:/var/lib/mysql
volumes:
db-volume:
この場合、dsnは下のようになると思っていましたが、そうではありませんでした。
user:password@tcp(127.0.0.1:3306)/db?charset=utf8mb4&parseTime=True
このdsnがうまくいかなかった理由は、dockerで作成されたネットワークでは、mysqlのコンテナは必ずしも127.0.0.1
にはならず、動的にIPアドレスが変更されていってしまうからでした。
実際には下のようにすると、接続できました。
user:password@tcp(db:3306)/db?charset=utf8mb4&parseTime=True
docker composeを使用すると、プロジェクト毎にブリッジネットワークが自動作成され、同一ymlファイルに書かれているサービス同士はコンテナ名でIPアドレス解決ができるようになります。
もし、同一ymlファイルに書かれていない場合にコンテナ名解決をしたい場合は、docker network create
でネットワークを定義する必要があります。
↓この記事で同じymlに書いていないコンテナ同士の通信の仕方を書いています。
なので、実際にGoでは下のような実装でdb接続しました。
func ConnectDB() (*gorm.DB, error) {
var db *gorm.DB
var err error
dsn := "user:password@tcp(db:3306)/db?charset=utf8mb4&parseTime=True"
return db, nil
}
リトライ処理をつける
ymlファイルで、dbのコンテナを依存関係として明示していても、起動した後にサーバーアプリケーションのコンテナが立ち上がるので、dbが接続できるようになる前に接続動作が行われ、アプリケーションが接続できない場合もあります。
その場合は下のようなリトライ接続ができる仕組みを作成します。
func ConnectDB() (*gorm.DB, error) {
var db *gorm.DB
var err error
dsn := "user:password@tcp(db:3306)/db?charset=utf8mb4&parseTime=True"
count := 5
for count > 1 {
if db, err = gorm.Open(mysql.Open(dsn)); err != nil {
time.Sleep(2 * time.Second)
count--
log.Printf("retry... count:%v\n", count)
continue
}
break
}
return db, nil
}
こうすることで5回まで接続動作をリトライしてくれます。