🌟

docker-composeでgolangとMySQLを繋ぐ

2022/03/03に公開2

【環境】
MacBook Air (M1, 2020)
OS: MacOS Big Sur version11.6
Docker Desktop for Mac version4.5.0

docker-composeでgolangとMySQLのコンテナを作り、Dockerのnetworkを通じてgolangからMySQLを操作します。
MySQLコンテナの初期化処理でarticleテーブルを作成しデータを2つ登録、golangからそれらを確認するところまで進めます。

ディレクトリ構成

go_docker
├── docker-compose.yml
├── golang
│   ├── Dockerfile
│   └── src
│       ├── article
│       │   └── article.go
│       ├── go.mod
│       ├── go.sum
│       └── main.go
└── mysql
    ├── .env
    ├── Dockerfile
    └── init
        └── create_table.sh

mysqlディレクトリ内に関してはdocker-composeでMySQLを起動するで作成したものとほとんど同じですので、それらについては割愛します。

docker-compose.yml

version: '3.8'

services:
  go:
    container_name: go
    build:
      context: ./golang
      dockerfile: Dockerfile
    tty: true
    ports:
      - 8080:8080
    env_file:
      - ./mysql/.env
    depends_on:
      - db
    volumes:
      - type: bind
        source: ./golang/src
        target: /go/src
    networks:
      - golang_test_network

  db:
    container_name: db
    build:
      context: ./mysql
      dockerfile: Dockerfile
    tty: true
    platform: linux/x86_64
    ports:
      - 3306:3306
    env_file:
      - ./mysql/.env
    volumes:
      - type: volume
        source: mysql_test_volume
        target: /var/lib/mysql
      - type: bind
        source: ./mysql/init
        target: /docker-entrypoint-initdb.d
    networks:
      - golang_test_network

volumes:
  mysql_test_volume:
    name: mysql_test_volume

networks:
  golang_test_network:
    external: true
  • MySQL(dbサービス)についてはdocker-composeでMySQLを起動するとほとんど同じです。
  • ymlファイルの最下部にgolang_test_networkというDockerのnetworksを記述しました。
    各サービスのnetworksに指定することで、サービス名を使ってサービス間の通信を行うことができます。
    今回で言うとgolangでMySQLへ通信する際、hostに「db」というサービス名を指定することで通信を行うことができます。
  • goサービスのdepends_onはサービス起動の順番を設定します。
    ここで指定したサービス(db)が起動した後に自身(go)が起動します。

golang/Dockerfile

FROM golang:1.17.7-alpine
RUN apk update && apk add git
WORKDIR /go/src

CMD ["go", "run", "main.go"]

BaseImageはalpineサーバーのgolangです。gitだけ導入しておきます。
作業ディレクトリを設定し、go run main.goを実行します。

main.go

package main

import (
	"database/sql"
	"fmt"
	"go_docker/article"
	"log"
	"os"
	"time"
	
	_ "github.com/go-sql-driver/mysql"
)

func open(path string, count uint) *sql.DB {
	db, err := sql.Open("mysql", path)
	if err != nil {
		log.Fatal("open error:", err)
	}

	if err = db.Ping(); err != nil {
		time.Sleep(time.Second * 2)
		count--
		fmt.Printf("retry... count:%v\n", count)
		return open(path, count)
	}

	fmt.Println("db connected!!")
	return db
}

func connectDB() *sql.DB {
	var path string = fmt.Sprintf("%s:%s@tcp(db:3306)/%s?charset=utf8&parseTime=true",
		os.Getenv("MYSQL_USER"), os.Getenv("MYSQL_PASSWORD"),
		os.Getenv("MYSQL_DATABASE"))

	return open(path, 100)
}

func main() {
	db := connectDB()
	defer db.Close()
	article.ReadAll(db)
}

database/sql

database/sqlはgolangでデータベースに接続する時に使うパッケージです。
今回はMySQLに接続するのでドライバとして"github.com/go-sql-driver/mysql"もインポートします。パッケージの前に_(アンダースコア)を付けるとパッケージ内のinitメソッドだけが実行されます。(プログラム中では使わない。)

func connectDB() *sql.DB

mainメソッドの1行目で呼び出しています。
path変数にはdatabase/sqlで指定する接続情報です。
[user名]:[password]@tcp([host名]:[port])/database名
という形で記述します。
docker-composeのnetworks設定したので、host名はdbで接続することができます。
os.Getenv("環境変数")で環境変数を使うことができます。

func open(path string, count uint) *sql.DB

sql.Open()でデータベースと接続します。
第一引数にデータベースの種類を、第二引数に先ほど設定したpathを指定します。
db.Ping()でdbサービスへの接続確認をし、失敗したらcountを減らし、openメソッドを再起で呼びます。

article.go

package article

import (
	"database/sql"
	"fmt"
	"log"
)

type Article struct {
	id    int
	title string
	body  string
}

func ReadAll(db *sql.DB) {
	var articles []Article
	rows, err := db.Query("select * from article;")
	if err != nil {
		panic(err)
	}
	for rows.Next() {
		article := Article{}
		err = rows.Scan(&article.id, &article.title, &article.body)
		if err != nil {
			panic(err)
		}
		articles = append(articles, article)
	}
	rows.Close()

	fmt.Println(articles)
}

func ReadAll(db *sql.DB)

mainメソッド3行目から呼ばれます。
sql.databaseパッケージで複数の検索結果を取得する時(SELECT等)はQuery、単一データを取得する時はQueryRow、データを取得しない時はExec(INSERT, CREATE, UPDATE, DELETE等)を使います。
今回はarticleテーブルの全データを取得するのでQueryメソッドを使います。

やってみる

それでは実際にdocker-composeを実行してみます。

% docker-compose up -d
[+] Running 3/3
 ⠿ Volume "mysql_test_volume"  Created                                                                                                                                                                          0.0s
 ⠿ Container db                Started                                                                                                                                                                          0.3s
 ⠿ Container go                Started

goコンテナのLogを確認してみます。

go: downloading github.com/go-sql-driver/mysql v1.6.0

retry... count:99

retry... count:98

retry... count:97

retry... count:96

retry... count:95

retry... count:94

retry... count:93

retry... count:92

retry... count:91

retry... count:90

retry... count:89

retry... count:88

retry... count:87

db connected!!

[{1 記事1 記事1です。} {2 記事2 記事2です。}]

MySQLの起動に時間がかかり何度かretryしていますが、無事articleテーブルのデータを取得することができました。

参考

https://sourjp.github.io/posts/go-db/
https://qiita.com/study-toto/items/256c2d306b3c6c8f86cd

Discussion

yo-nagaseyo-nagase

最初に一秒待たせるだけでもできました!同じ状況発生してたのですが・・助かりました。