0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MariaDBのレプリケーション構成をk3dでクイック構築

0
Posted at

はじめに

ここでは、
Kubernetes上でStatefulなデータベースがどのように動作するかを確認するための環境を
クイックに構築します。


背景・課題

ローカル環境でデータベースの冗長構成や障害挙動を検証しようとすると、
仮想マシンを複数台用意する必要があり、環境準備のコストが高くなりがちです。

また Docker Compose でも複数コンテナは起動できますが、
Compose は単一ホスト上のプロセス管理ツールであり、
Kubernetes のような「クラスタスケジューリング」は行われません。

そのため、複数仮想マシンとDocker Composeでは次のような挙動は再現が困難です。

  • Pod再配置時のDB接続挙動(DNS名の再解決)
  • 永続ボリューム再接続時のデータ整合性
  • ノード障害を想定した再起動・再スケジューリング
  • StatefulSet上のデータベース運用確認

2. 目的

冒頭で記載した背景から、本番を想定した検証を行うには、以下が必要になります。

  • DNS名でのDB接続
  • Pod再生成時のデータ維持
  • StatefulSet
  • Headless Service

これらを一番簡単に試せるのが k3d です。

本記事では、軽量Kubernetesである k3d を利用し、ローカルPC上にKubernetesクラスタを作成したうえで、
MariaDBのPrimary / Replicaレプリケーション構成を構築します。

  • Kubernetes StatefulSet 上での MariaDB レプリケーション構築
  • Primary/Replica 同期確認
  • Pod再作成時のデータ維持
  • Replica への読み取りアクセス

3. やったこと(構成と構築)

3.1 システム構成図

前提として、以下の注意点があります。

  1. :warning: 自動フェイルオーバー機能は実装しない方式になります。
    • (自動フェイルオーバーを実装する場合は、Orchestratorが必要になります。)
    • 自動フェイルオーバー機能はなく、マスターデータベースからスレーブデータベースへの同期を行うのみとします。
  2. :warning: k3dの設定になりますが、k3dボリュームマウントで、StatefuleSetで作成されたPodから固有のPersistentVolumeが設定できません。
    • k3dのドキュメントを確認してもそういった記載はないのですが、どうもDockerの仕様とk3dとStatefulSetとがかち合っている感じで調査中ですが、
    • 今回はローカルボリュームとして構築します。

また、コンテナイメージは軽量化のため、AlpineLinuxを使用します。

imagemysql/Dockerfileの内容
FROM alpine:3.23

# MySQLと必要最小限パッケージ
RUN apk add --no-cache mysql mysql-client bash

COPY ./entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

COPY ./conf/my.cnf /etc/my.cnf
COPY ./conf/my.cnf /etc/my.cnf.d/mariadb-server.cnf

WORKDIR /

EXPOSE 3306
ENTRYPOINT ["/entrypoint.sh"]
CMD ["mysqld", "--user=mysql", "--datadir=/var/lib/mysql"]
imagemysql/entrypoint.shの内容
#!/bin/sh
set -e

echo "[entrypoint] starting mysql entrypoint"

# =========================
# Pod index 取得
# =========================
ORDINAL=$(echo "${HOSTNAME}" | awk -F'-' '{print $NF}')
SERVER_ID=$((ORDINAL + 1))
OFFSET=$((ORDINAL + 1))

if [ "${ORDINAL}" = "0" ]; then
  READONLY=0
else
  READONLY=1
fi

echo "[entrypoint] ordinal=${ORDINAL} server-id=${SERVER_ID} read-only=${READONLY}"

# =========================
# node.cnf 生成(書き込み可能領域)
# =========================
cat > /etc/mysql/conf.d/node.cnf <<EOF
[mysqld]
server-id=${SERVER_ID}
auto-increment-offset=${OFFSET}
read-only=${READONLY}
EOF

# Master/Slave判定(Master:ORDINAL=0)
if [ "${ORDINAL}" = "0" ]; then
  echo "log-bin=mysql-bin" >> /etc/mysql/conf.d/node.cnf
  echo "binlog-format=ROW" >> /etc/mysql/conf.d/node.cnf
else
  echo "relay-log=relay-bin" >> /etc/mysql/conf.d/node.cnf
fi

# =========================
# 初期化(初回のみ)
# =========================
if [ ! -d "/var/lib/mysql/mysql" ]; then
  echo "[entrypoint] initializing mysql datadir"
  mysql_install_db --user=mysql --datadir=/var/lib/mysql
fi

# =========================
# 権限調整
# =========================
chown -R mysql:mysql /var/lib/mysql
mkdir -p /run/mysqld
chown -R mysql:mysql /run/mysqld

# =========================
# 起動(これだけ)
# =========================
echo "[entrypoint] starting mysqld"
exec mysqld --user=mysql

imagemysql/conf/my.cnfの内容
[client]
port            = 3306
socket          = /var/run/mysqld/mysqld.sock

[server]

[mysqld]
user            = mysql
pid-file        = /var/run/mysqld/mysqld.pid
socket          = /var/run/mysqld/mysqld.sock
port            = 3306
max_connections = 10
innodb_buffer_pool_size = 34M
innodb_thread_concurrency = 8
innodb_io_capacity = 100
innodb_io_capacity_max = 1000
innodb_read_io_threads = 2
innodb_write_io_threads = 2
bind-address = 0.0.0.0
max_allowed_packet = 256M

# Replication settings (will be added dynamically by entrypoint.sh)
# server-id will be set based on pod name
# log-bin=mysql-bin
# binlog-format=ROW
# relay-log=relay-bin

[galera]

[embedded]

[mariadb]

[mariadb-10.5]

install_statefulset_mysql.sh

install_statefulset_mysql.shの内容
#!/bin/bash

# スクリプトが失敗した場合、即座に終了するように設定
# set -e

# 現在のディレクトリを変数に設定
hostexexecution_path=$(pwd)

# 管理者権限に昇格
sudo su - <<'EOF'
  mkdir -pvm 777 /tmp/datamnt/data
  ln -sf /tmp/datamnt /datamnt
  mkdir -pvm 777 /home/kubeuser/podmultinode
EOF

# configmysqlディレクトリは不要になったため、コピー削除
sudo rsync -avz imagemysql /home/kubeuser/podmultinode/imagemysql
sudo rsync -avz podmysql.yml /home/kubeuser/podmultinode/podmysql.yml

# 必要なパッケージのインストール
echo "hogehoge"
sudo apt-get update
sudo apt-get install -y cron cpulimit rsync

# cronサービスの起動
sudo service cron start

registry_directory_path="registry"

# chmod -v 755 ${hostexexecution_path}/*.sh
tags_select="v5.8.3"
image_k3s="rancher/k3s:v1.34.3-k3s1"
echo "k3s image: ${image_k3s} is selected. and k3d version: ${tags_select} is selected."

# install k3d
curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | TAG=${tags_select} bash

k3d node list
chmod 755 ${hostexexecution_path}/*.sh
mkdir -pv ~/.k3d_t_kyn
mkdir -pv ~/.k3d_t_kyn/k3s_etc

# レジストリ設定ファイルのコピー
cp -pv ${registry_directory_path}/.k3d_t_kyn/t_kyn_config.tmplate ~/.k3d_t_kyn/t_kyn_config.tmplate 2>/dev/null || true
cp -pv ${registry_directory_path}/.k3d_t_kyn/k3s_etc/registries.yaml ~/.k3d_t_kyn/k3s_etc/registries.yaml
cp -pv ${registry_directory_path}/registry-config.json ~/ 2>/dev/null || true
cat ~/registry-config.json 2>/dev/null || true

# Dockerレジストリの作成
docker volume create local_registry
docker volume ls | grep local_registry

# レジストリコンテナの起動(既に起動している場合はスキップ)
if ! docker ps | grep -q tkynregistry.local; then
    docker container run -d --name tkynregistry.local \
        -v local_registry:/var/lib/registry \
        --restart always \
        -p 5000:5000 \
        registry:2
fi

# container_network_list and k3d cluster list confirm
docker network ls
kubectl config get-contexts 2>/dev/null || true

# k3dボリュームディレクトリの作成
mkdir -pvm 777 /tmp/k3d-vols/server-0 /tmp/k3d-vols/agent /tmp/k3d-vols/registry /datamnt/var /datamnt/data

# k3dクラスタの作成(既に存在する場合はスキップ)
if ! k3d cluster list | grep -q "k3s-default"; then
    k3d cluster create \
        --agents 2 \
        --port "8080:30000@loadbalancer" \
        --wait \
        --image ${image_k3s} \
        --registry-config ~/.k3d_t_kyn/k3s_etc/registries.yaml \
        --volume /datamnt/data:/datamnt/data \
        --volume /tmp/k3d-vols/server-0:/var/lib/rancher/k3s/storage
fi

# レジストリをk3dネットワークに接続
docker network connect k3d-k3s-default tkynregistry.local 2>/dev/null || true

# コンテナレジストリへの起動確認
curl -kv http://tkynregistry.local:5000/v2/_catalog

# ホストへの登録をします
entry_count=$(cat /etc/hosts | grep tkynregistry.local | grep 127.0.0.1 | wc -l)
if [ "$entry_count" -ge 1 ]; then
    echo "tkynregistry.local already exists in /etc/hosts. Skipping..."
else
    echo '127.0.0.1       tkynregistry.local' | sudo tee -a /etc/hosts
    echo "Added tkynregistry.local to /etc/hosts."
    cat /etc/hosts | grep tkynregistry.local
fi

curl -kv http://tkynregistry.local:5000/v2/_catalog

# Dockerイメージのビルドとプッシュ
cd imagemysql
docker build -t imagemysql:v1.0.0 .
docker tag imagemysql:v1.0.0 tkynregistry.local:5000/imagemysql:v1.0.0
docker push tkynregistry.local:5000/imagemysql:v1.0.0
cd ..
pwd

# generation of database mysql
mkdir -pvm 777 /datamnt/data/mysqlpv{1,2,3}

# deploy pod mysql
_mysqlYamlFilename="podmysql.yml"

echo "Applying MySQL StatefulSet..."
kubectl apply -f ${_mysqlYamlFilename}

echo "Waiting for pods to be ready..."
kubectl wait --for=condition=ready pod -l app=mysql --timeout=300s

echo "Deployment complete!"
kubectl get all
kubectl get pvc
実行結果
k3d_taist_statefulset_test $ ./install_statefulset_mysql.sh
mkdir: created directory '/tmp/datamnt'
mkdir: created directory '/tmp/datamnt/data'
mkdir: created directory '/home/kubeuser'
mkdir: created directory '/home/kubeuser/podmultinode'
sending incremental file list
created directory /home/kubeuser/podmultinode/imagemysql
imagemysql/
imagemysql/Dockerfile
imagemysql/entrypoint.sh
imagemysql/entrypoint.sh_tmp20260206
imagemysql/conf/
imagemysql/conf/my.cnf

sent 3,127 bytes  received 169 bytes  6,592.00 bytes/sec
total size is 6,359  speedup is 1.93
sending incremental file list
podmysql.yml

sent 2,024 bytes  received 35 bytes  4,118.00 bytes/sec
total size is 5,915  speedup is 2.87
hogehoge
0% [Working]            Get:1 https://repo.anaconda.com/pkgs/misc/debrepo/conda stable InRelease [3961 B]
!!!!!長いんで略します!!!!!
Applying MySQL StatefulSet...
service/mysql-headless created
configmap/mysql-config created
statefulset.apps/mysql created
Waiting for pods to be ready...
pod/mysql-0 condition met
Deployment complete!
NAME          READY   STATUS    RESTARTS   AGE
pod/mysql-0   1/1     Running   0          36s
pod/mysql-1   0/1     Pending   0          0s

NAME                     TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
service/kubernetes       ClusterIP   10.43.0.1    <none>        443/TCP    99s
service/mysql-headless   ClusterIP   None         <none>        3306/TCP   36s

NAME                     READY   AGE
statefulset.apps/mysql   1/3     36s
NAME                 STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
mysql-data-mysql-0   Bound     pvc-f4279a73-8e44-4b18-a5ef-e18210734276   1Gi        RWO            local-path     <unset>                 36s
mysql-data-mysql-1   Pending                                                                        local-path     <unset>                 0s
k3d_taist_statefulset_test $ echo $?
0
k3d_taist_statefulset_test $ kubectl wait --for=condition=ready pod -l app=mysql --timeout=300sk3d_taist_statefulset_test $ kubectl wait --for=condition=ready pod -l app=mysql --timeout=300s
pod/mysql-0 condition met
pod/mysql-1 condition met
pod/mysql-2 condition met
k3d_taist_statefulset_test $ kubectl get podskubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
mysql-0   1/1     Running   0          98s
mysql-1   1/1     Running   0          62s
mysql-2   1/1     Running   0          31s
k3d_taist_statefulset_test $ 

3.2 設定ファイル

mariadbのymlの設定

マニフェストファイルは、以下の通りになっております。

podmysql.yml
---
# =========================
# Headless Service for StatefulSet
# =========================
apiVersion: v1
kind: Service
metadata:
  name: mysql-headless
spec:
  clusterIP: None
  selector:
    app: mysql
  ports:
    - name: mysql
      port: 3306
      targetPort: 3306

---
# =========================
# ConfigMap (my.cnf + server-id 雛形)
# =========================
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  my.cnf: |
    [client]
    port = 3306
    socket = /var/run/mysqld/mysqld.sock

    [mysqld]
    user = mysql
    pid-file = /var/run/mysqld/mysqld.pid
    socket = /var/run/mysqld/mysqld.sock
    port = 3306
    bind-address = 0.0.0.0

    max_connections = 10
    max_allowed_packet = 256M

    innodb_buffer_pool_size = 34M
    innodb_thread_concurrency = 8
    innodb_io_capacity = 100
    innodb_io_capacity_max = 1000
    innodb_read_io_threads = 2
    innodb_write_io_threads = 2

    !includedir /etc/mysql/conf.d

  server-id.cnf: |
    [mysqld]
    # generated by initContainer

  log-bin.cnf: |
    [mysqld]
    # generated by initContainer
---
# =========================
# StatefulSet
# =========================
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-headless
  replicas: 3
  selector:
    matchLabels:
      app: mysql

  template:
    metadata:
      labels:
        app: mysql
    spec:
      securityContext:
        fsGroup: 999

      # -------------------------
      # initContainer
      # -------------------------
      initContainers:
      - name: init-server-id
        image: busybox:1.36
        command:
          - sh
          - -c
          - |
            ORDINAL=${POD_NAME##*-}
            SERVER_ID=$((ORDINAL + 1))

            cat > /conf/server-id.cnf <<EOF
            [mysqld]
            server-id=${SERVER_ID}
            EOF

            if [ "$ORDINAL" -eq 0 ]; then
            # Master: binlog 有効
            cat > /conf/log-bin.cnf <<EOF
            [mysqld]
            log_bin = mysql-bin
            binlog_format = ROW
            sync_binlog = 1
            EOF
            else
            # Slave: binlog 無効
            cat > /conf/log-bin.cnf <<EOF
            [mysqld]
            # log_bin OFF for slave
            EOF
            fi

            # # slave setting for replicas
            # if [ "$ORDINAL" -ne 0 ]; then
            #   echo "Initializing replica"

            # # not mysql-0.mysql-headless --> localhost
            #   mysql -h localhost \
            #     -uroot -p${MYSQL_ROOT_PASSWORD} <<EOF
            #   STOP SLAVE;
            #   CHANGE MASTER TO
            #     MASTER_HOST='mysql-0.mysql-headless',
            #     MASTER_USER='repl',
            #     MASTER_PASSWORD='replpass',
            #     MASTER_AUTO_POSITION=1;
            #   START SLAVE;
            #   EOF
            # fi

            chown -R 999:999 /conf
        env:
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
        volumeMounts:
          - name: mysql-node-conf
            mountPath: /conf

      # -------------------------
      # main container
      # -------------------------
      containers:
      - name: mysql
        image: tkynregistry.local:5000/imagemysql:v1.0.0
        imagePullPolicy: IfNotPresent

        ports:
          - containerPort: 3306

        env:
          - name: MYSQL_ROOT_PASSWORD
            value: "examplepassword"

        resources:
          limits:
            cpu: "0.4"
            memory: "512Mi"
          requests:
            cpu: "0.2"
            memory: "256Mi"

        volumeMounts:
          - name: mysql-data
            mountPath: /var/lib/mysql

          - name: mysql-config
            mountPath: /etc/mysql/my.cnf
            subPath: my.cnf
            readOnly: true

          - name: mysql-node-conf
            mountPath: /etc/mysql/conf.d

          - name: mysql-run
            mountPath: /run/mysqld

        livenessProbe:
          exec:
            command: ["mysqladmin", "ping", "-h", "127.0.0.1"]
          initialDelaySeconds: 30
          periodSeconds: 10

        readinessProbe:
          exec:
            command: ["mysqladmin", "ping", "-h", "127.0.0.1"]
          initialDelaySeconds: 10
          periodSeconds: 5

        # # postStart: set replica
        # lifecycle:
        #   postStart:
        #     exec:
        #       command:
        #         - sh
        #         - -c
        #         - |
        #           (
        #             ORDINAL=${HOSTNAME##*-}

        #             echo "[postStart] waiting mysql..."
        #             for i in $(seq 1 60); do
        #               mysqladmin ping -uroot -p${MYSQL_ROOT_PASSWORD} --silent && break
        #               sleep 2
        #             done

        #             if [ "$ORDINAL" != "0" ]; then
        #               mysql -uroot -p${MYSQL_ROOT_PASSWORD} <<EOF
        #               STOP SLAVE;
        #               CHANGE MASTER TO
        #                 MASTER_HOST='mysql-0.mysql-headless',
        #                 MASTER_USER='repl',
        #                 MASTER_PASSWORD='replpass',
        #                 MASTER_AUTO_POSITION=1;
        #               START SLAVE;
        #               EOF
        #             fi
        #           ) >/proc/1/fd/1 2>&1 &

      volumes:
        - name: mysql-config
          configMap:
            name: mysql-config

        - name: mysql-node-conf
          emptyDir: {}

        - name: mysql-run
          emptyDir: {}

  # -------------------------
  # PVC (dynamic provisioning)
  # -------------------------
  volumeClaimTemplates:
  - metadata:
      name: mysql-data
    spec:
      accessModes:
        - ReadWriteOnce
      storageClassName: local-path
      resources:
        requests:
          storage: 1Gi

レプリケーション設定

初回セットアップ用のスクリプトになります。
マスタデータベースにあるbin_logを参照し、
スレーブ側のデータベースにbin_logのindexを更新することにより、
スレーブ設定を自動反映します。
このまま実行することで反映できます。

initial_setup_of_replication_on_pods.sh
#!/bin/bash

################################################################################
# MySQL Replication Initial Setup Script
################################################################################
# Purpose: 
#   - Setup master-slave replication AFTER initial bulk data loading
#   - This script should be run AFTER loading 1000 tables x 100000 records
#
# Workflow:
#   1. Verify all pods are ready
#   2. Create replication user on master (mysql-0)
#   3. Get master binlog file and position
#   4. Configure slave nodes (mysql-1, mysql-2) to replicate from master
#   5. Start replication
#   6. Verify replication status
################################################################################

set -e

# Configuration
DB_USER="root"
DB_PASS="examplepassword"
REPL_USER="repl"
REPL_PASS="replpass"
MASTER_POD="mysql-0"
SLAVE_PODS=("mysql-1" "mysql-2")
NAMESPACE="default"

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

################################################################################
# Helper Functions
################################################################################

print_header() {
    echo ""
    echo "========================================="
    echo -e "${BLUE}$1${NC}"
    echo "========================================="
}

print_success() {
    echo -e "${GREEN}$1${NC}"
}

print_warning() {
    echo -e "${YELLOW}⚠️  $1${NC}"
}

print_error() {
    echo -e "${RED}$1${NC}"
}

print_info() {
    echo -e "${BLUE}ℹ️  $1${NC}"
}

# Execute MySQL command on a pod
exec_mysql() {
    local pod=$1
    local query=$2
    kubectl exec -it ${pod} -n ${NAMESPACE} -- mysql -u ${DB_USER} -p${DB_PASS} -e "${query}" 2>/dev/null
}

# Execute MySQL command and suppress warnings
exec_mysql_silent() {
    local pod=$1
    local query=$2
    kubectl exec ${pod} -n ${NAMESPACE} -- mysql -u ${DB_USER} -p${DB_PASS} -e "${query}" 2>/dev/null | grep -v "Deprecated" | grep -v "Warning"
}

################################################################################
# Step 1: Verify Pod Status
################################################################################

print_header "Step 1: Verifying Pod Status"

for pod in ${MASTER_POD} ${SLAVE_PODS[@]}; do
    print_info "Checking ${pod}..."
    
    # Check if pod is running
    if ! kubectl get pod ${pod} -n ${NAMESPACE} &>/dev/null; then
        print_error "Pod ${pod} does not exist"
        exit 1
    fi
    
    # Check if pod is ready
    if ! kubectl wait --for=condition=ready pod/${pod} -n ${NAMESPACE} --timeout=10s &>/dev/null; then
        print_error "Pod ${pod} is not ready"
        exit 1
    fi
    
    # Check MySQL connection
    if ! exec_mysql ${pod} "SELECT 1;" &>/dev/null; then
        print_error "Cannot connect to MySQL on ${pod}"
        exit 1
    fi
    
    print_success "${pod} is ready and MySQL is accessible"
done

################################################################################
# Step 2: Check if Binary Logging/Relay Logging is Enabled
################################################################################

print_header "Step 2: Verifying Logging Settings"

# Master: log_binチェック
print_info "Checking binary logging on ${MASTER_POD}..."
log_bin=$(exec_mysql_silent ${MASTER_POD} "SHOW VARIABLES LIKE 'log_bin';" | grep log_bin | awk '{print $2}')
if [ "$log_bin" = "ON" ]; then
    print_success "${MASTER_POD}: Binary logging is ENABLED"
else
    print_error "${MASTER_POD}: Binary logging is DISABLED"
    print_error "Please rebuild the Docker image with binary logging enabled (Master側)"
    exit 1
fi

# Slave: relay-logチェック(log_binは不要)
for slave in ${SLAVE_PODS[@]}; do
    print_info "Checking relay logging on ${slave}..."
    relay_log=$(exec_mysql_silent ${slave} "SHOW VARIABLES LIKE 'relay_log';" | grep relay_log | awk '{print $2}')
    if [ -n "$relay_log" ]; then
        print_success "${slave}: Relay logging is ENABLED (relay_log: $relay_log)"
    else
        print_warning "${slave}: Relay logging is NOT explicitly set (relay_log: $relay_log)"
        # relay-logは自動生成される場合もあるため、エラーにはしない
    fi
done

################################################################################
# Step 3: Stop Any Existing Replication on Slaves
################################################################################

print_header "Step 3: Stopping Existing Replication (if any)"

for slave in ${SLAVE_PODS[@]}; do
    print_info "Stopping replication on ${slave}..."
    exec_mysql ${slave} "STOP SLAVE;" 2>/dev/null || true
    print_success "${slave}: Replication stopped"
done

sleep 2

################################################################################
# Step 4: Create Replication User on Master
################################################################################

print_header "Step 4: Creating Replication User on Master"

print_info "Creating replication user '${REPL_USER}' on ${MASTER_POD}..."

# Drop user if exists and create new one
exec_mysql ${MASTER_POD} "DROP USER IF EXISTS '${REPL_USER}'@'%';"
exec_mysql ${MASTER_POD} "CREATE USER '${REPL_USER}'@'%' IDENTIFIED BY '${REPL_PASS}';"
exec_mysql ${MASTER_POD} "GRANT REPLICATION SLAVE ON *.* TO '${REPL_USER}'@'%';"
exec_mysql ${MASTER_POD} "FLUSH PRIVILEGES;"

print_success "Replication user created successfully"

sleep 2

################################################################################
# Step 5: Get Master Status
################################################################################

print_header "Step 5: Getting Master Binlog Position"

print_info "Fetching master status from ${MASTER_POD}..."

# Get master status
MASTER_STATUS=$(kubectl exec ${MASTER_POD} -n ${NAMESPACE} -- mysql -u ${DB_USER} -p${DB_PASS} -e "SHOW MASTER STATUS\G" 2>/dev/null | grep -v "Deprecated" | grep -v "Warning")

MASTER_LOG_FILE=$(echo "$MASTER_STATUS" | grep "File:" | awk '{print $2}')
MASTER_LOG_POS=$(echo "$MASTER_STATUS" | grep "Position:" | awk '{print $2}')

if [ -z "$MASTER_LOG_FILE" ] || [ -z "$MASTER_LOG_POS" ]; then
    print_error "Failed to get master binlog file or position"
    echo "$MASTER_STATUS"
    exit 1
fi

print_success "Master binlog file: ${MASTER_LOG_FILE}"
print_success "Master binlog position: ${MASTER_LOG_POS}"

################################################################################
# Step 6: Configure Slaves
################################################################################

print_header "Step 6: Configuring Slave Nodes"

for slave in ${SLAVE_PODS[@]}; do
    print_info "Configuring ${slave}..."
    
    # Reset slave configuration
    exec_mysql ${slave} "RESET SLAVE ALL;" 2>/dev/null || true
    
    # Configure master connection
    exec_mysql ${slave} "CHANGE MASTER TO 
        MASTER_HOST='${MASTER_POD}.mysql-headless',
        MASTER_USER='${REPL_USER}',
        MASTER_PASSWORD='${REPL_PASS}',
        MASTER_LOG_FILE='${MASTER_LOG_FILE}',
        MASTER_LOG_POS=${MASTER_LOG_POS};"
    
    print_success "${slave}: Master connection configured"
done

sleep 2

################################################################################
# Step 7: Start Replication
################################################################################

print_header "Step 7: Starting Replication"

for slave in ${SLAVE_PODS[@]}; do
    print_info "Starting replication on ${slave}..."
    exec_mysql ${slave} "START SLAVE;"
    print_success "${slave}: Replication started"
done

sleep 3

################################################################################
# Step 8: Verify Replication Status
################################################################################

print_header "Step 8: Verifying Replication Status"

ALL_SLAVES_OK=true

for slave in ${SLAVE_PODS[@]}; do
    print_info "Checking replication status on ${slave}..."
    
    SLAVE_STATUS=$(kubectl exec ${slave} -n ${NAMESPACE} -- mysql -u ${DB_USER} -p${DB_PASS} -e "SHOW SLAVE STATUS\G" 2>/dev/null | grep -v "Deprecated" | grep -v "Warning")
    
    SLAVE_IO_RUNNING=$(echo "$SLAVE_STATUS" | grep "Slave_IO_Running:" | awk '{print $2}')
    SLAVE_SQL_RUNNING=$(echo "$SLAVE_STATUS" | grep "Slave_SQL_Running:" | awk '{print $2}')
    SECONDS_BEHIND=$(echo "$SLAVE_STATUS" | grep "Seconds_Behind_Master:" | awk '{print $2}')
    LAST_ERROR=$(echo "$SLAVE_STATUS" | grep "Last_Error:" | cut -d: -f2- | xargs)
    
    echo ""
    echo "  Pod: ${slave}"
    echo "  ├─ Slave_IO_Running:  ${SLAVE_IO_RUNNING}"
    echo "  ├─ Slave_SQL_Running: ${SLAVE_SQL_RUNNING}"
    echo "  ├─ Seconds_Behind_Master: ${SECONDS_BEHIND}"
    
    if [ -n "$LAST_ERROR" ]; then
        echo "  └─ Last_Error: ${LAST_ERROR}"
    fi
    
    if [ "$SLAVE_IO_RUNNING" = "Yes" ] && [ "$SLAVE_SQL_RUNNING" = "Yes" ]; then
        print_success "${slave}: Replication is working correctly"
    else
        print_error "${slave}: Replication is NOT working correctly"
        ALL_SLAVES_OK=false
        
        if [ -n "$LAST_ERROR" ]; then
            print_error "Error: ${LAST_ERROR}"
        fi
    fi
done

################################################################################
# Step 9: Final Summary
################################################################################

print_header "Step 9: Replication Setup Summary"

echo ""
echo "Master Node:"
echo "  └─ ${MASTER_POD}"
echo ""
echo "Slave Nodes:"
for slave in ${SLAVE_PODS[@]}; do
    echo "  ├─ ${slave}"
done
echo ""
echo "Replication User:"
echo "  ├─ Username: ${REPL_USER}"
echo "  └─ Password: ${REPL_PASS}"
echo ""
echo "Master Binlog:"
echo "  ├─ File: ${MASTER_LOG_FILE}"
echo "  └─ Position: ${MASTER_LOG_POS}"
echo ""

if [ "$ALL_SLAVES_OK" = true ]; then
    print_header "✅ Replication Setup Completed Successfully!"
    echo ""
    print_info "Next steps:"
    echo "  1. Verify data synchronization with: ./check_replication.sh"
    echo "  2. Test replication with: ./test_replication.sh"
    echo "  3. Monitor replication lag regularly"
    echo ""
    exit 0
else
    print_header "❌ Replication Setup Failed"
    echo ""
    print_error "Some slaves are not replicating correctly"
    print_info "Run the following command to check details:"
    echo "  kubectl exec -it mysql-1 -- mysql -u root -pexamplepassword -e 'SHOW SLAVE STATUS\G'"
    echo ""
    exit 1
fi

動作確認

テストデータ用にテーブルとデータを挿入

以下の通りテストデータを作成し、マスターのほうのみ追加します。

k8s_generate_test_data.sh
#!/bin/bash

# Kubernetes Pod内でテストデータを生成するスクリプト

POD_NAME=${1:-"mysql-0"}
NAMESPACE=${2:-"default"}
DB_USER="root"
DB_PASS="examplepassword"
DB_NAME="t_kyn_usrdb"

echo "==================================="
echo "K8s Pod内でテストデータ生成"
echo "==================================="
echo "Pod: $POD_NAME"
echo "Namespace: $NAMESPACE"
echo "==================================="

# SQLファイルをPodにコピー
echo "SQLファイルをPodにコピー中..."
kubectl cp create_test_data.sql ${NAMESPACE}/${POD_NAME}:/tmp/create_test_data.sql

# データベース作成
echo "データベース作成中..."
kubectl exec -it ${POD_NAME} -n ${NAMESPACE} -- mysql -u ${DB_USER} -p${DB_PASS} -e "CREATE DATABASE IF NOT EXISTS ${DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

# SQLファイル実行
echo "テストデータ生成中..."
kubectl exec -it ${POD_NAME} -n ${NAMESPACE} -- mysql -u ${DB_USER} -p${DB_PASS} ${DB_NAME} -e "source /tmp/create_test_data.sql"

# 結果確認
echo "==================================="
echo "✅ 完了しました"
echo "==================================="
kubectl exec -it ${POD_NAME} -n ${NAMESPACE} -- mysql -u ${DB_USER} -p${DB_PASS} ${DB_NAME} -e "SELECT COUNT(*) as total_records FROM test_table;"

確認用スクリプト

データベースがマスタ、スレーブで同じ行数出ているか確認します。

check_replication.sh
#!/bin/bash

DB_USER="root"
DB_PASS="examplepassword"
DB_NAME="t_kyn_usrdb"

echo "========================================="
echo "MySQL Replication Status Check"
echo "========================================="

# マスターのステータス確認
echo ""
echo "=== MASTER (mysql-0) Status ==="
kubectl exec -it mysql-0 -- mysql -u ${DB_USER} -p${DB_PASS} -e "SHOW MASTER STATUS\G"

# スレーブのステータス確認
echo ""
echo "=== SLAVE (mysql-1) Status ==="
kubectl exec -it mysql-1 -- mysql -u ${DB_USER} -p${DB_PASS} -e "SHOW SLAVE STATUS\G" | grep -E "Slave_IO_Running|Slave_SQL_Running|Seconds_Behind_Master|Last_Error"

echo ""
echo "=== SLAVE (mysql-2) Status ==="
kubectl exec -it mysql-2 -- mysql -u ${DB_USER} -p${DB_PASS} -e "SHOW SLAVE STATUS\G" | grep -E "Slave_IO_Running|Slave_SQL_Running|Seconds_Behind_Master|Last_Error"

# 各Podのデータベース一覧確認
echo ""
echo "========================================="
echo "Database List on Each Pod"
echo "========================================="

for pod in mysql-0 mysql-1 mysql-2; do
    echo ""
    echo "=== ${pod} ==="
    kubectl exec -it ${pod} -- mysql -u ${DB_USER} -p${DB_PASS} -e "SHOW DATABASES;" 2>/dev/null | grep -v "information_schema\|performance_schema\|mysql\|Database"
done

# テストデータの同期確認(test_tableが存在する場合)
echo ""
echo "========================================="
echo "Test Table Record Count"
echo "========================================="

for pod in mysql-0 mysql-1 mysql-2; do
    echo ""
    echo "=== ${pod} ==="
    kubectl exec -it ${pod} -- mysql -u ${DB_USER} -p${DB_PASS} ${DB_NAME} -e "SELECT COUNT(*) as record_count FROM test_table;" 2>/dev/null || echo "Table does not exist yet"
done


4. 結論・まとめ

k3d + StatefulSet を利用することで、MariaDB レプリケーション検証が可能でした。

特に以下が検証できる点が有用です。

  • Pod再生成時のデータ永続性
  • DNS名によるDB接続
  • レプリケーション追従挙動

Docker Composeでは難しい「Statefulな挙動確認」が可能になります。


今後の展望:

  • MaxScale導入による自動フェイルオーバー
  • Read/Write 分離
  • Orchestratorによる自動昇格

参考リンク

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?