docker-compose upでMySQLが起動するまで待つ方法(2種類紹介)


docker-compose upで立ち上がらないGoのコンテナがありまして

docker-compose up -dでコンテナを立ち上げる際、
なぜかWeb側のコンテナが立ち上がらないという現象が発生していました。

docker-compose up -d!

docker-compose up -d
Creating network "api_network" with the default driver
Creating redis ... done
Creating mysql ... done
Creating api ... done

しかし調べてみると、、、

docker-compose ps
Name      Command         State         Ports              
--------------------------------------------------------------------------------------------------
mysql     .sh mysql ...   Up       0.0.0.0:3306->3306/tcp, 33060/tcp
redis     .sh redis ...   Up       0.0.0.0:6380->6380/tcp          
api       app/build/api   Exit 2

webのコンテナがExitとなっており、立ち上がっていません..

docker-compose logs api
api    | panic: dial tcp 172.28.0.3:3306: connect: connection refused 

なぜだーーー

これは割とあるあるな話のようで、
MySQLコンテナのMySQL実行完了が遅く、起動する前に接続しにいこうとしてしまい、
コケてしまうようです。

ま、もう1回docker-compose upすれば済む話なのですが、
1発でなんとかせよ! というミッションがありまして、達成に向けて試行錯誤してみる事に。

今回はシェルスクリプトを使う方法と、Goのコードから起動確認する方法を試してみました。
それぞれご紹介します!

問題解決に到るまで、5名以上の方からアドバイスを頂けまして、
そのおかげで達成できました! 本当にありがとうございます!!

シェルスクリプトを使ってpingでMySQLを確認して繋ぎにいく

下記の記事を参考にさせてもらいました ↓

http://docs.docker.jp/compose/startup-order.html
https://qiita.com/shiena/items/47437f4f7874bf70d664

以下のようなシェルスクリプトを書きます

start.sh
# !/bin/.sh
# MySQLサーバーが起動するまでループで待機する

until mysqladmin ping -h mysql --silent; do
  echo 'waiting for mysqld to be connectable...'
  sleep 2
done

echo " go app is started!"
exec app/build/api

start.shをwebのコンテナ内に入れます。今回はmntに入れました。
docker-compose.ymlを以下のように書き換えます。

docker-compose.yml
volumes:
      - ./api/scripts:/mnt
    entrypoint: mnt/start.sh

entrypointを.shにして、ループ処理をさせてから
1番最後のexecでコンテナを立ち上げるようにします。

ただし、mysqladmin pingを実行する為には、
mysql-clientをインストールさせる必要があります。
Dockerfileにそれ用の記述を書き加えます。

Dockerfile

RUN apk update \
&& apk add mysql-client

時は来た!
docker-compose up -d!

docker-compose ps
Name      Command         State         Ports              
--------------------------------------------------------------------------------------------------
mysql     .sh mysql ...   Up       0.0.0.0:3306->3306/tcp, 33060/tcp
redis     .sh redis ...   Up       0.0.0.0:6380->6380/tcp          
api       ./api/scripts:start.sh   Up       0.0.0.0:8080->8080/tcp

やった! 成功です!

GoのコードでMySQLの立ち上がりを確認してから接続するようにする

下記の記事を参考にさせてもらいました ↓

https://qiita.com/Bmouthf/items/d3cfdbee74caeda77e3f
https://kleinblog.net/docker-golang-mysql-min/

上の方法で上手くいったのですが、その為だけにmysql-clientをインストールさせるのは
ちょっと... という事で、GoのコードでMySQLコンテナの立ち上がりを待つようにします!

mysqlへの接続はそれ用の関数を呼び出して繋いでいたのですが、
少し書き加えてMySQLの立ち上がりを確認してから接続させるようにします。

↓ mysqlの関数を呼び出して接続させている

main.go

db := mysql.ConnectDB()
    db.LogMode(true)
    defer db.Close()

importにgorm、log、timeを追加


import (
"github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
"log"
"time"
)

以下のように書き足しました。
※本当にMySQLが起動していない時、タイムアウトするような機能を追加しました

var DB *gorm.DB
var err error
var count = 0
main.go
func connect(path string) (*gorm.DB, error) {
    db, err := gorm.Open("mysql", path)
    if err != nil {
        log.Println("Not ready. Retry connecting...")
        time.Sleep(time.Second)
        count++
        log.Println(count)
        if count > 30 {
            panic(err)
        }
        return connect(path)
    }
    log.Println("Successfully")
    return db, nil
}

30秒経過しても立ち上がらないようであれば、
panicで終了させます。

docker-compose up -d!

docker-compose ps
Name      Command         State         Ports              
--------------------------------------------------------------------------------------------------
mysql     .sh mysql ...   Up       0.0.0.0:3306->3306/tcp, 33060/tcp
redis     .sh redis ...   Up       0.0.0.0:6380->6380/tcp          
api       app/build/api   Up       0.0.0.0:8080->8080/tcp

よっしゃー!

※追記※

上記の記述で動作しますが、親切な方のアドバイスの元、改良する事にしました。
この書き方ですと、関数の外部に定義した変数の値と中のCOUNTが
依存関係になってしまいます。

サイドエフェクト(副作用)が混じる恐れがあり、
極力なくした方が良いとの事で、以下のようにしました。

↓ サイドエフェクトについて
https://ja.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0)


import (
    "fmt"
    "os"
    "strings"
    "time"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)
var DB *gorm.DB
var err error
// カウントの変数は削除
main.go
// openの第2引数をカウント、uintにする
func open(path string, count uint) (*gorm.DB, error) {
    db, err := gorm.Open("mysql", path)
    if err != nil {
        if count == 0 {
            return nil, fmt.Errorf("Retry count over")
        }
        time.Sleep(time.Second)
// カウントダウンさせるようにする
        count--
        return open(path, count)
    }
    return db, nil
}

// MySQLへの接続
func ConnectDB() *gorm.DB {
// openの引数に30のカウントを指定
db, err := open(path, 30)
    if err != nil {
        panic(err)
    }

これで依存関係がなくなりました。

今回は、Docker + Go + MySQLコンテナの組み合わせでした。
同じような現象に悩まれてる方が何らかの参考になりましたら幸いです!

著者自身、DockerもGoも全然分かっていない未熟者でして不備もあるかと思います。
批評、マサカリ大歓迎です!
何かありましたら、ぜひご意見アドバイス等等下さいませ〜!!