2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Docker × Go × MySQL 環境構築してみた

Last updated at Posted at 2023-05-25

はじめに

最近、色んな企業のサービスを見ていて、Go言語が使われている事が多くどんなものかと勉強しています。
まずは環境構築してみたので下記へ手順をまとめておきます。

目次

DockerでGoを起動する
GoでWebページを作成して、「Hello, World!」を出力する
DockerでMySQLを起動する
GoとMySQLを接続する
GoでCRUD処理を実装
Dockerでphpmyadminを起動する
参考文献

DockerでGoを起動する

まずはDockerでGoを起動させます。
1. 作業ディレクトリ直下へ以下ファイルを作成します。
 ・Dockerfile
 ・docker-compose.yml
 ・main.go

Dockerfile
FROM golang:1.17.1-alpine3.13 as builder

WORKDIR /go/src
docker-compose.yml
version: '3'
services:
  go:
    build:
      context: .
      dockerfile: Dockerfile
    command: /bin/sh -c "go run main.go"
    stdin_open: true
    tty: true
    volumes:
      - .:/app
main.go
package main

import "fmt"

func main() {
  fmt.Println("Hello, World!")
}

2. ターミナルを開いて、docker-compose up を実行します。
Hello, World!と出ればGoの起動は成功です!
スクリーンショット 2023-05-24 22.24.47.png
・Dockerfile
Goのコンテナを作成します。
WORKDIR /go/srcの指定により以降全ての動作は/go/src以下で行われます。

・docker-compose.yml
Dockerfileで作成したコンテナの起動設定を記載します。
Dockerfileに記載されたコンテナ起動後、go run main.goのコマンドを叩いてmain.goを起動します。

・main.go
Goの処理を書くところです。

GoでWebページを作成して、「Hello, World!」を出力する

次にGoでWebページを作っていきます。フレームワークはGinを使います。
1. 以下の通り、各ファイルの作成・更新を行います。
 ・DockerfileへGinインストールを追記
 ・docker-compose.ymlへportsの記述を追記
 ・main.goの内容を変更
 ・templates/index.htmlを作成

Dockerfile
FROM golang:1.17.1-alpine3.13 as builder

WORKDIR /go/src

RUN go get github.com/gin-gonic/gin
docker-compose.yml
version: '3'
services:
  go:
    build:
      context: .
      dockerfile: Dockerfile
    command: /bin/sh -c "go run main.go"
    stdin_open: true
    tty: true
    volumes:
      - .:/app
    ports:
      - 8080:8080
main.go
package main

import (
  "github.com/gin-gonic/gin"
)

func main() {
  router := gin.Default()
  router.LoadHTMLGlob("templates/*.html")

  router.GET("/", func(ctx *gin.Context){
    ctx.HTML(200, "index.html", gin.H{})
  })

  router.Run()
}
templates/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Sample App</title>
</head>
<body>
  <h1>Hello World!!!</h1>
</body>
</html>

2. 次にdocker-compose buildしたい所ですが、main.goでBrokenImportエラーが出ています。
スクリーンショット 2023-05-24 22.52.24.png
というかそもそもgo.modがなかったので作成していきます。
go.modの作成、go getによるモジュールとそのバージョン追記
go mod init github.com/「importpath」
あまり意識してなかったですが、 go mod init github.com/の後は「importpath」の使用が推奨のようです。(詳しくはこちら
go get
使用するモジュールとそのバージョンをgo.modファイルに追記するコマンド

BrokenImportエラーの原因、go.workの作成
今回はgo work initコマンドでgo.workを作成後、workspaceを示すuseを手入力しました。自分も理解がまだ曖昧なので別途記事を投稿しようと思います。(詳しくはこちら

3. docker-compose builddocker-compose up -dの順に行いコンテナを立ち上げます。
up実行後にdocker psしてみましたが、コンテナがうまく起動していないようです。
スクリーンショット 2023-05-25 0.24.54.png
何が原因???
ymlのvolumesとDockerfileのWORKDIRのパスが異なるのが原因みたいです。
スクリーンショット 2023-05-25 1.22.12.png

再度試した所、問題なくコンテナが立ち上がりました!!!
スクリーンショット 2023-05-25 1.29.59.png
http://localhost:8080へアクセスしてみると...
ご覧の通り、Webページが表示されました👏
スクリーンショット 2023-05-25 1.37.01.png

DockerでMySQLを起動する

GoでのWebページ作成ができました。次はサービスづくりにどうしても必要なDBへの接続、MySQLのコンテナを立ち上げていきます。

1. docker-compose.ymlへ以下を追記します。
 ・dbコンテナの記述
 ・volumesの記述

docker-compose.yml
version: '3'
services:
  go:
    build:
      context: .
      dockerfile: Dockerfile
    command: /bin/sh -c "go run main.go"
    stdin_open: true
    tty: true
    volumes:
      - .:/go/src
    ports:
      - 8080:8080

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: go_database
      MYSQL_USER: go_test
      MYSQL_PASSWORD: password
      TZ: 'Asia/Tokyo'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
      - db-data:/var/lib/mysql
      - ./db/my.cnf:/etc/mysql/conf.d/my.cnf
    ports:
      - 3306:3306

volumes:
  db-data:
    driver: local
my.cnf
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_bin

default-time-zone = SYSTEM
log_timestamps = SYSTEM

default-authentication-plugin = mysql_native_password

[mysql]
default-character-set = utf8mb4

[client]
default-character-set = utf8mb4

2. docker-compose up -dを叩くと、MySQLのコンテナが立ち上がリます。
スクリーンショット 2023-05-25 22.32.37.png
スクリーンショット 2023-05-25 22.34.21.png

GoとMySQLを接続する

次はGoとMySQLを繋いでいきましょう。
接続にはsqlドライバーとGORMというフレームワークを使います。

1. 以下を行います。
 ・DockerfileにsqlドライバーとGORMのインストールを追記
 ・docker-compose.ymlへ依存関係の記述を追記
 ・main.goへDB接続の処理を追記

Dockerfile
FROM golang:1.17.1-alpine3.13 as builder

WORKDIR /go/src

RUN go get github.com/gin-gonic/gin
RUN go get github.com/go-sql-driver/mysql
RUN go get github.com/jinzhu/gorm
docker-compose.yml
version: '3'
services:
  go:
    build:
      context: .
      dockerfile: Dockerfile
    command: /bin/sh -c "go run main.go"
    stdin_open: true
    tty: true
    volumes:
      - .:/go/src
    ports:
      - 8080:8080
    depends_on:
      - "db"

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: go_database
      MYSQL_USER: go_test
      MYSQL_PASSWORD: password
      TZ: 'Asia/Tokyo'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
      - db-data:/var/lib/mysql
      - ./db/my.cnf:/etc/mysql/conf.d/my.cnf
    ports:
      - 3306:3306

volumes:
  db-data:
    driver: local
main.go
package main

import (
  "fmt"
  "time"

  "github.com/gin-gonic/gin"
  "github.com/jinzhu/gorm"
  _ "github.com/go-sql-driver/mysql"
)

func main() {
  db := sqlConnect()
  defer db.Close()

  router := gin.Default()
  router.LoadHTMLGlob("templates/*.html")

  router.GET("/", func(ctx *gin.Context){
    ctx.HTML(200, "index.html", gin.H{})
  })

  router.Run()
}

func sqlConnect() (database *gorm.DB) {
  DBMS := "mysql"
  USER := "go_test"
  PASS := "password"
  PROTOCOL := "tcp(db:3306)"
  DBNAME := "go_database"

  CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME + "?charset=utf8&parseTime=true&loc=Asia%2FTokyo"

  count := 0
  db, err := gorm.Open(DBMS, CONNECT)
  if err != nil {
    for {
      if err == nil {
        fmt.Println("")
        break
      }
      fmt.Print(".")
      time.Sleep(time.Second)
      count++
      if count > 180 {
        fmt.Println("")
        fmt.Println("DB接続失敗")
        panic(err)
      }
      db, err = gorm.Open(DBMS, CONNECT)
    }
  }
  fmt.Println("DB接続成功")

  return db
}

2. docker compose upを行い、コンソールに「DB接続成功」と出たら成功です。
残念ながらエラーのようです(o^^o)
スクリーンショット 2023-05-25 23.10.47.png
go.modへバイナリ(ライブラリ)のインストールが必要とのこと。
なぜかDockerfileのRUNコマンドが実行されてない模様...。今回は以下を手動で実行します。
go get github.com/go-sql-driver/mysql
go get github.com/jinzhu/gorm

再度 docker compose upした所、成功しました🎉
スクリーンショット 2023-05-25 23.16.39.png

GoでCRUD処理を実装

いよいよラストです!GoでCRUD処理を実装していきます。

1. main.goとindex.htmlを以下の通り変更します。
 main.go
  ・Userの定義を作成
  ・Userテーブルを作成
  ・post処理(Userデータの登録・削除)の実装
 index.html
  ・ユーザー登録・削除機能の実装

main.go
package main

import (
	"fmt"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
)

type User struct {
	gorm.Model
	Name       string
	Department string
	Email      string
	Phone      string
}

func main() {
	db := sqlConnect()
	db.AutoMigrate(&User{})
	defer db.Close()

	router := gin.Default()
	router.LoadHTMLGlob("templates/*.html")

	router.GET("/", func(ctx *gin.Context) {
		db := sqlConnect()
		var users []User
		db.Order("created_at asc").Find(&users)
		defer db.Close()

		ctx.HTML(200, "index.html", gin.H{
			"users": users,
		})
	})

	router.POST("/new", func(ctx *gin.Context) {
		db := sqlConnect()
		name := ctx.PostForm("name")
		department := ctx.PostForm("department")
		email := ctx.PostForm("email")
		phone := ctx.PostForm("phone")
		fmt.Println("create user " + name + " with email " + email)
		db.Create(&User{Name: name, Department: department, Email: email, Phone: phone})
		defer db.Close()

		ctx.Redirect(302, "/")
	})

	router.POST("/delete/:id", func(ctx *gin.Context) {
		db := sqlConnect()
		n := ctx.Param("id")
		id, err := strconv.Atoi(n)
		if err != nil {
			panic("id is not a number")
		}
		var user User
		db.First(&user, id)
		db.Delete(&user)
		defer db.Close()

		ctx.Redirect(302, "/")
	})

	router.Run()
}

func sqlConnect() (database *gorm.DB) {
	DBMS := "mysql"
	USER := "go_test"
	PASS := "password"
	PROTOCOL := "tcp(db:3306)"
	DBNAME := "go_database"

	CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME + "?charset=utf8&parseTime=true&loc=Asia%2FTokyo"

	count := 0
	db, err := gorm.Open(DBMS, CONNECT)
	if err != nil {
		for {
			if err == nil {
				fmt.Println("")
				break
			}
			fmt.Print(".")
			time.Sleep(time.Second)
			count++
			if count > 180 {
				fmt.Println("")
				panic(err)
			}
			db, err = gorm.Open(DBMS, CONNECT)
		}
	}

	return db
}
templates/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>My App</title>
  <style>
    label {
      width: 200px;
      float:left;
      margin: 0 50px 25px 0;
    }
    p {
      margin-bottom: 5px;
    }
    input {
      height: 20px;
      margin-bottom: 10px;
    }
    tr {
      width: 50%;
    }
    th,td{
      border:1px #333 solid;
    }
    table{
      margin-top: 5px;
      width: 100%;
      border-collapse: collapse;
    }
    #tdSample {
      margin-left: 5px;
    }
    button {
      width: 75px;
      height: 30px;
      color: #FFF;
      background-color: #3CB371;
      border: 0;
      border-radius: 3px;
      cursor: pointer;
      font-weight: bold;
    }

  </style>
</head>
<body>
  <h2>ユーザー一覧</h2>
  <form method="post" action="/new" style="margin: 10px 50px 10px 50px;">
    <div style="width: 100%;">
      <label>
        <p>氏名</p>
        <input type="text" name="name" size="30" placeholder="入力してください">
      </label>
      <label>
        <p>所属部署</p>
        <input type="text" name="department" size="30" placeholder="入力してください">
      </label>
      <label>
        <p>メールアドレス</p>
        <input type="text" name="email" size="30" placeholder="入力してください">
      </label>
      <label>
        <p>電話番号</p>
        <input type="text" name="phone" size="30" placeholder="入力してください">
      </label>
      <div style="padding-top: 43px;">
        <button type="submit">登録</button>
      </div>
    </div>
  </form>


    <table style="width: 90%;margin: 10px 50px 10px 50px;">
      <tr>
        <th style="width: 50px;"></th>
        <th>氏名</th>
        <th>所属部署</th>
        <th>メールアドレス</th>
        <th>電話番号</th>
      </tr>
      {{ range .users }}
        <tr>
          <td>
            <form method="post" action="/delete/{{.ID}}">
              <div align="center">
                <button type="submit">削除</button>
              </div>
            </form>
          </td>
          <td><div id="tdSample">{{ .Name }}</div></td>
          <td><div id="tdSample">{{ .Department }}</div></td>
          <td><div id="tdSample">{{ .Email }}</div></td>
          <td><div id="tdSample">{{ .Phone }}</div></td>
        </tr>
      {{ end }}
    </table>
</body>
</html>

2. docker compose upを行い、http://localhost:8080へアクセスします。
ユーザー登録フォームが表示され、ユーザーを登録すると一覧へ登録情報が表示されます。
スクリーンショット 2023-05-30 22.56.29.png

Dockerでphpmyadminを起動する

先程、ラストと言いましたがついでなのでdb管理ツールであるphpmyadminの立ち上げまで行って終わりとします。

1. docker-compose.ymlへphpmyadminのコンテナ設定を追記します。

docker-compose.yml
  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOST=db # mysqlのサービス名を指定
      - PMA_USER=go_test # phpmyadminへログインするユーザ
      - PMA_PASSWORD=password # phpmyadminにログインするユーザのパスワード
    links:
      - mysql
    ports:
      - 4040:80
    volumes:
      - ./phpmyadmin/sessions:/sessions

2. docker compose up -dを行い、http://localhost:4040 へアクセスします。
ユーザー一覧画面で登録したユーザー情報がusersテーブルで見れるかと思います。
スクリーンショット 2023-05-31 1.37.19.png

参考文献

2
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?