2
1
お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

PythonまたはGoでDockerコンテナに立ち上げたMySQLサーバーと通信する方法

Last updated at Posted at 2024-07-05

データベースを構築するにあたって,DockerコンテナでDBもアプリも動くといいですよね?ローカルで動かす際にdockerコンテナ内で処理が完結するのでシームレスに通信することができる.
本記事ではPythonまたはGoを用いてDockerコンテナ上のMySQLサーバーと通信する方法について記載する.
スクリーンショット 2024-07-05 13.21.38.png

今回は以下の知識はある前提で話をする
SQLの文法

Dockerの基本

仮想環境とDockerコンテナの違い

ディレクトリ構造の確認

Pythonの方はこちらで
.
├── .env
├── docker-compose.yml
├── mysql
│   └── Dockerfile
└── python
    ├── Dockerfile
    ├── main.py
    └── requirements.txt
Goの方はこちらで
├── .env
├── docker-compose.yml
├── mysql
│   └── Dockerfile
└── golang
    ├── Dockerfile
    ├── go.mod
    ├── go.sum
    └── main.go

このような構造であることを前提に作成していく.

環境変数の設定

まず,.envファイルに必要な環境変数を設定する.

環境変数を設定するメリット
アプリケーションとデータベースの接続情報を安全に管理できる.

.env
MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_DATABASE=testdb
MYSQL_USER=user
MYSQL_PASSWORD=password
DB_HOST=db
DB_PORT=3306

この情報を使って,MySQLサーバーとPythonまたはGoでやりとりする.

docker環境の構築

docker-compose.ymlファイルで,MySQLサービスとアプリケーションサービスを定義する.環境変数は.envファイルから読み込む.
python-appまたはgolang-appサービスのcommand${COMMAND:-export}を定義しているのはこの後コマンドライン引数でデータを挿入importするか抽出exportするかを指定し,.envから読み出すためである.

${COMMAND:-export}は何も指定しなければデフォルトでデータを抽出exportする.

また,環境変数の情報はdockervolumeに格納しておく.

Dockerfileでは使用する言語とバージョン,依存関係のインストールをする.実行コマンドはdocker-compose.ymlに指定するので不要である.

まずmysqlディレクトリにDockerfileを作成する.ここではmysqlのバージョンのみ指定する.

FROM mysql:8.0

Pythonバージョン

ルートにdocker-compose.ymlファイルを定義する.

docker-compose.yml
version: '3'

services:
  db:
    build: ./mysql
    env_file: .env
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_DATABASE}
      - MYSQL_USER=${MYSQL_USER}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
    ports:
      - "3306:3306"

  python_app:
     build:
       context: ./python
       dockerfile: Dockerfile
     env_file: .env
     volumes:
     - ./.env:/app/.env
     command: python /app/main.py ${COMMAND:-export}
     depends_on:
       - db

pythonディレクトリでDockerfileファイルを定義する.

FROM python:3.9

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

Goバージョン

ルートにdocker-compose.ymlファイルを定義する.

docker-compose.yml
version: '3'

services:
  db:
    build: ./mysql
    env_file: .env
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_DATABASE}
      - MYSQL_USER=${MYSQL_USER}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
    ports:
      - "3306:3306"

  golang_app:
    build:
      context: ./golang
      dockerfile: Dockerfile
    env_file: .env
    volumes:
    - ./.env:/app/.env
    command: /app/main ${COMMAND:-export}
    depends_on:
      - db

golangディレクトリでDockerfileファイルを定義する.

FROM golang:1.18

WORKDIR /app

COPY go.mod .
COPY go.sum .
RUN go mod download

COPY . .

RUN go build -o main .


依存関係の定義

インストールする依存関係のリストを記載しておく.

Pythonバージョン

アプリケーションディレクトリにrequirements.txtを作成.

requirements.txt
mysql-connector-python==8.0.28
python-dotenv==0.19.2

Goバージョン

アプリケーションディレクトリにgo.modを作成.

go.mod
module example.com/yourappname

go 1.18

require (
	github.com/go-sql-driver/mysql v1.7.1
	github.com/joho/godotenv v1.5.1
)

この後以下のコマンドを実行する.

go mod tidy

DB操作用のコードを書く

詳しい説明はコードに記載しているが,概要として...行っていることは以下の通りである.

  • 環境変数からDB接続情報を取得

  • MySQLデータベースに接続. 接続失敗時は30回まで再試行

  • Userテーブルが存在しない場合, 作成

  • コマンドライン引数に基づいて操作を実行

    • import: サンプルユーザーデータをテーブルに挿入
    • export: テーブルの全データを取得し表示
  • データベース接続を適切に閉じる.

Pythonバージョン

main.py
import mysql.connector
from datetime import date
import time
import os
import sys
from dotenv import load_dotenv

load_dotenv()

def mysql_operations(command):

    # ここでDB接続情報を環境変数から取得
    db_config = {
        'host': os.getenv('DB_HOST'),
        'user': os.getenv('MYSQL_USER'),
        'password': os.getenv('MYSQL_PASSWORD'),
        'database': os.getenv('MYSQL_DATABASE'),
    }

    # 接続再試行用のカウント.30カウントしたら失敗とする.設定する理由は接続情報はあっているのに1回でつながらない場合がよくあるからである
    for _ in range(30):
        try:
            conn = mysql.connector.connect(**db_config)
            break
        except mysql.connector.Error:
            time.sleep(1)
    else:
        print("データベースへの接続に失敗しました")
        return

    cursor = conn.cursor()

    # テーブルを作成するSQLクエリ(テーブルが存在しない場合のみ実行)
    create_table_query = """
    CREATE TABLE IF NOT EXISTS User (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(255) NOT NULL,
        email VARCHAR(255) NOT NULL
    )
    """

    # ここでSQLを実行(execute)する
    cursor.execute(create_table_query)
    print("テーブルが作成されました")

    # コマンドライン引数がimportならデータ挿入.INSERT命令を実行
    if command == "import":
        insert_query = "INSERT INTO User (name, email) VALUES (%s, %s)"
        users = [
            ("John Doe", "john@example.com"),
            ("Jane Smith", "jane@example.com"),
        ]
        cursor.executemany(insert_query, users)
        conn.commit()
        print(f"{cursor.rowcount}行のデータが挿入されました")

    # コマンドライン引数がexportならデータ抽出.SELECT命令を実行
    elif command == "export":
        select_query = "SELECT * FROM User"
        cursor.execute(select_query)
        results = cursor.fetchall()

        print("ユーザーデータ:")
        for row in results:
            print(f"ID: {row[0]}, Name: {row[1]}, Email: {row[2]}")

    else:
        print(f"無効なコマンドです: {command}")

    # DBを閉じる
    cursor.close()
    conn.close()

# 実行されたらここが初めに呼び出される
if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("使用方法: python main.py [import|export]")
        sys.exit(1)
        
    # sys.argv[1]でコマンドライン引数を取得
    mysql_operations(sys.argv[1])

Goバージョン

main.go
package main

// 依存関係のインポート
import (
	"database/sql"
	"fmt"
	"log"
	"os"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"github.com/joho/godotenv"
)


func main() {
	if len(os.Args) < 2 {
		fmt.Println("使用方法: go run main.go [import|export]")
		os.Exit(1)
	}

    // sys.argv[1]でコマンドライン引数を取得
	command := os.Args[1]

	err := godotenv.Load()
	if err != nil {
		log.Fatal("Error loading .env file")
	}

    // ここでDB接続情報を環境変数から取得
	dbUser := os.Getenv("MYSQL_USER")
	dbPass := os.Getenv("MYSQL_PASSWORD")
	dbName := os.Getenv("MYSQL_DATABASE")
	dbHost := os.Getenv("DB_HOST")
	dbPort := os.Getenv("DB_PORT")

	var db *sql.DB
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbUser, dbPass, dbHost, dbPort, dbName)
 
     // 接続再試行用のカウント.30カウントしたら失敗とする.設定する理由は接続情報はあっているのに1回でつながらない場合がよくあるからである
	for i := 0; i < 30; i++ {
		db, err = sql.Open("mysql", dsn)
		if err == nil {
			err = db.Ping()
			if err == nil {
				break
			}
		}
		time.Sleep(time.Second)
	}
	if err != nil {
		log.Fatal("データベースへの接続に失敗しました:", err)
	}
	defer db.Close()

    // テーブルを作成するSQLクエリ(テーブルが存在しない場合のみ実行)ついでに実行(exec)
	_, err = db.Exec(`
    CREATE TABLE IF NOT EXISTS User (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(255) NOT NULL,
        email VARCHAR(255) NOT NULL
    )
    `)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("テーブルが作成されました")

    // コマンドライン引数がimportならデータ挿入.INSERT命令を実行
	switch command {
	case "import":
		stmt, err := db.Prepare("INSERT INTO User (name, email) VALUES (?, ?)")
		if err != nil {
			log.Fatal(err)
		}
		defer stmt.Close()

		users := []struct {
			name  string
			email string
		}{
			{"John Doe", "john@example.com"},
			{"Jane Smith", "jane@example.com"},
		}

		for _, user := range users {
			_, err = stmt.Exec(user.name, user.email)
			if err != nil {
				log.Fatal(err)
			}
		}
		fmt.Println("データが挿入されました")

    // コマンドライン引数がexportならデータ抽出.SELECT命令を実行
	case "export":
		rows, err := db.Query("SELECT * FROM User")
		if err != nil {
			log.Fatal(err)
		}
		defer rows.Close()

		fmt.Println("ユーザーデータ:")
		for rows.Next() {
			var id int
			var name, email string

			err := rows.Scan(&id, &name, &email)
			if err != nil {
				log.Fatal(err)
			}
			fmt.Printf("ID: %d, Name: %s, Email: %s\n", id, name, email)
		}

	default:
		fmt.Println("無効なコマンドです。import または export を指定してください。")
	}
}

コマンドライン引数の環境変数を追加

このコードは以下のように実行されることを前提としている.

Pythonの場合
Python main.py import
Python main.py export
Goの場合
go main.go import
go main.go export

しかしdocker-compose.ymlに直接コマンドライン引数を定義することは不適切なため,.envファイルにコマンドライン引数を定義しておく.

そのため先ほどcommand${COMMAND:-export}を使って定義した.

.envに以下の行を追加する

.env
COMMAND=import

実行時にはここの文字をimportにするか,exportにするかを指定して,データを挿入するのか抽出するのかを選択する.

実行

これで以下のコマンドを実行してコンテナを立ち上げてアプリケーションを実行する.

docker compose up

DBサーバーにアクセスしたい場合

DBサーバーに直接アクセスしたい場合は以下のコマンドを実行すること.DBサーバー内でSQLクエリを実行することができる.

docker compose up -d 
docker compose exec db mysql -u user -p password testdb
2
1
0

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
1