【k8s】kubernetesでMySQL + Goのアプリをデプロイする

注意

この記事で用いているソースコードの大半はchatGPT(GPT-4)を用いて生成されています。あくまでサンプルと思って読んでください。

MySQLをデプロイ

以下のyamlをdeploy.yamlという名前で保存します。基本的にはkindに合わせてファイルを分けたりするのですが、面倒なのでふっ飛ばします。

やってることは主に以下の通りです。

  • MySQLの初期データを設定
  • MySQLのパスワードを設定
  • ClusterIPを用いてクラスタ内にMySQLを3306で公開
apiVersion: v1
kind: ConfigMap
metadata:
  name: db-init-configmap
data:
  init.sql: |
    CREATE DATABASE IF NOT EXISTS app;
    USE app;
    CREATE TABLE IF NOT EXISTS users (
      id INT AUTO_INCREMENT PRIMARY KEY,
      name VARCHAR(255) NOT NULL,
      email VARCHAR(255) NOT NULL UNIQUE,
      created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
    INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
    INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
    INSERT INTO users (name, email) VALUES ('Charlie', 'charlie@example.com');
---
apiVersion: v1
kind: Secret
metadata:
  name: sql-secret
type: Opaque
data:
  # パスワードをbase64でエンコードした値
  password: cGFzc3dvcmQ=
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-deployment
  labels:
    app: mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: 'mysql:8'
          volumeMounts:
            - name: sql-init-config
              mountPath: /docker-entrypoint-initdb.d
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: sql-secret
                  key: password
          ports:
            - containerPort: 3306
      volumes:
        - name: sql-init-config
          configMap:
            name: db-init-configmap
            items:
              - key: init.sql
                path: init.sql
---
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  type: ClusterIP
  selector:
    app: mysql
  ports:
    - port: 3306
      targetPort: 3306
      protocol: TCP

以下コマンドを叩いて起動します

> kubectl apply -f deploy.yaml

# configmap/db-init-configmap created
# secret/sql-secret created
# deployment.apps/mysql-deployment created
# service/mysql created

> kubectl get svc

# NAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
# mysql             ClusterIP   10.97.17.57      <none>        3306/TCP         0m

> kubectl get pods

# NAME                                  READY   STATUS    RESTARTS   AGE
# mysql-deployment-5c7dbf6db5-hpfvh     1/1     Running   0          0m

Goのアプリをデプロイ

イメージの作成

まずはコードを用意します。(以下コードはすべてGPTが生成したものです)

package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    _ "github.com/go-sql-driver/mysql"
)

type User struct {
    ID        int    `json:"id"`
    Name      string `json:"name"`
    Email     string `json:"email"`
    CreatedAt string `json:"created_at"`
}

func getUsers(db *sql.DB) ([]User, error) {
    rows, err := db.Query("SELECT id, name, email, created_at FROM users")
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    var users []User
    for rows.Next() {
        var u User
        if err := rows.Scan(&u.ID, &u.Name, &u.Email, &u.CreatedAt); err != nil {
            return nil, err
        }
        users = append(users, u)
    }
    return users, nil
}

func handler(db *sql.DB) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        users, err := getUsers(db)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(users)
    }
}

func main() {
    db, err := sql.Open("mysql", "root:password@tcp(mysql:3306)/app")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    http.HandleFunc("/users", handler(db))
    fmt.Println("Server is running on port 8080...")
    log.Fatal(http.ListenAndServe(":8080", nil))
}
  • main関数の以下の部分について補足します。先ほどMySQLをクラスタ内に公開しました。このときサービス名をmysql、公開ポートを3306にしていたので接続設定はmysql:3306になります。mysqlという名前で名前解決してMySQLのPodにたどり着けます
db, err := sql.Open("mysql", "root:password@tcp(mysql:3306)/app")

以下コマンドを実行して初期化します

go mod init xxx
go mod tidy

次にDockerfileを作ってイメージを作成します。ビルドに使っているイメージのバージョンに注意してください

FROM golang:1.18 AS builder

WORKDIR /app/

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

COPY . .
RUN go build -trimpath -ldflags "-w -s" -o app main.go
# ---------------------------------------------------
FROM debian:bullseye-slim as deploy

RUN apt-get update

COPY --from=builder /app/app .

CMD ["./app"]

ビルドしてpushします。

docker build . -t abc/xxx # 自分のアカウントに合わせてください
docker image push abc/xxx:latest # docker.io/abc/xxx という感じになります

pushしたイメージからPodを作成する

最後にpushしたイメージをもとにyamlを書きます。imageの部分は環境に合わせて自分で書いてください。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: appname-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: appname
  template:
    metadata:
      labels:
        app: appname
    spec:
      containers:
      - name: appname
        image: # ここを環境に合わせる。例通りだとdocker.io/abc/xxxになる
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: appname-service
spec:
  type: NodePort
  ports:
  - port: 8080
    targetPort: 8080
    protocol: TCP
  selector:
    app: appname

デプロイしてNodePortを調べる。

> kubectl apply -f goapp.yaml
deployment.apps/appname-deployment configured
service/appname-service unchanged

> kubectl get pods
NAME                                  READY   STATUS    RESTARTS   AGE
appname-deployment-57cd49c8c8-f9vrf   1/1     Running   0          32m
appname-deployment-57cd49c8c8-jgrwg   1/1     Running   0          32m
mysql-deployment-5c7dbf6db5-hpfvh     1/1     Running   0          38m

> kubectl get svc 
NAME              TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
appname-service   NodePort    10.105.150.252   <none>        8080:32340/TCP   35m
kubernetes        ClusterIP   10.96.0.1        <none>        443/TCP          27h
mysql             ClusterIP   10.97.17.57      <none>        3306/TCP         43m

http://localhost:32340/usersにアクセスして以下の情報が表示されたら成功

[{"id":1,"name":"Alice","email":"alice@example.com","created_at":"2024-03-27 15:12:53"},{"id":2,"name":"Bob","email":"bob@example.com","created_at":"2024-03-27 15:12:53"},{"id":3,"name":"Charlie","email":"charlie@example.com","created_at":"2024-03-27 15:12:53"}]

おしまい