はじめに
こんにちは、@y-428です。
今回はMySQLの標準ストレージエンジンであるInno DBを利用して、フェイルオーバ可能な可用性の高いDBサーバの構築手順についてまとめてみました。
間違いがあれば都度ご指摘いただけますと幸いです。
また、併せてこちらのドキュメントもご参照ください。
では早速行きましょう。
執筆の背景
以前Proxmox上のCentOS Stream 10でMySQL InnoDB Clusterを構築しました。
今回はその構成をDockerで簡単に再現できるか気になり、検証も込めて執筆に至りました。
VM環境では当たり前にできることがDockerではうまくいかないケースが何箇所かあったので、ハマりポイントも含めてまとめます。
DockerでMySQL 8.4 InnoDB Cluster + MySQL Routerを構築してWordPressと繋ぐ
構成
3台のDBコンテナでInnoDB Clusterを組み、MySQL Routerを経由してWordPressから接続します。
[WordPress] → router:6446 → [MySQL Router]
↓
┌───────────────┼───────────────┐
[db01] [db02] [db03]
PRIMARY SECONDARY SECONDARY
(R/W) (R/O) (R/O)
| コンテナ名 | 役割 | ホスト側ポート |
|---|---|---|
| db01 | MySQL 8.4 / PRIMARY | 13306 |
| db02 | MySQL 8.4 / SECONDARY | 13307 |
| db03 | MySQL 8.4 / SECONDARY | 13308 |
| mysql-router | MySQL Router 8.4 | 6446(RW) / 6447(RO) |
| wordpress | WordPress latest | 8080 |
クラスター構築を先に完了させてからWordPressを起動する必要があるため、DBクラスターとWordPress側でComposeファイルを分けています。
ディレクトリ構成
db_server_test/
├── db/
│ ├── Dockerfile
│ ├── docker-compose.yml
│ └── conf/
│ ├── db01.cnf
│ ├── db02.cnf
│ └── db03.cnf
└── wp/
├── docker-compose.yml
└── router/
├── Dockerfile
└── entrypoint.sh
Step 1: DBコンテナの準備
Dockerfile
mysql:8.4 はOracle Linuxベースだったので、パッケージマネージャーは microdnf を使ってます。
イメージには既にMySQLの公式リポジトリが含まれているため、リポジトリの追加設定なしで mysql-shell をインストールできます。
FROM mysql:8.4
RUN microdnf install -y mysql-shell && microdnf clean all
docker-compose.yml
networks:
innodb-net:
name: innodb-net
driver: bridge
services:
db01:
build: .
container_name: db01
hostname: db01
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_ROOT_HOST: "%"
volumes:
- ./conf/db01.cnf:/etc/mysql/conf.d/cluster.cnf:ro
- db01_data:/var/lib/mysql
networks:
- innodb-net
ports:
- "13306:3306"
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-prootpassword"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
db02:
build: .
container_name: db02
hostname: db02
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_ROOT_HOST: "%"
volumes:
- ./conf/db02.cnf:/etc/mysql/conf.d/cluster.cnf:ro
- db02_data:/var/lib/mysql
networks:
- innodb-net
ports:
- "13307:3306"
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-prootpassword"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
db03:
build: .
container_name: db03
hostname: db03
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_ROOT_HOST: "%"
volumes:
- ./conf/db03.cnf:/etc/mysql/conf.d/cluster.cnf:ro
- db03_data:/var/lib/mysql
networks:
- innodb-net
ports:
- "13308:3306"
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-prootpassword"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
volumes:
db01_data:
db02_data:
db03_data:
各DBでMYSQL_ROOT_HOST: "%" を設定しておかないと、rootユーザーがlocalhost経由でしか接続できず、後のMySQL Shellによるコンテナ間操作で失敗します。
conf/db01.cnf / db02.cnf / db03.cnf
最低限 server_id(各ノードで一意の値)と report_host(コンテナのホスト名)を設定してます。
GTIDやbinlogなどの設定は、後で実行するdba.configureInstance() が自動で書き込んでくれます。便利ですね。
conf/db01.cnf
[mysqld]
server_id = 1
report_host = db01
conf/db02.cnf
[mysqld]
server_id = 2
report_host = db02
conf/db03.cnf
[mysqld]
server_id = 3
report_host = db03
report_host はコンテナ名と一致させる必要があります。
未設定の場合、コンテナIDがホスト名として使われてしまうため、ノードからの名前解決に失敗してしまいます。
Step 2: DBコンテナを起動する
cd db_server_test/db
docker compose up -d --build
全コンテナのhealthcheckが通るまで1〜2分待ちましょう。
起動確認のために以下のコマンドを実行します。
docker compose ps
全台 (healthy) になってることを確認したら次に進みましょう。
Step 3: InnoDB Clusterを構築する
各インスタンスをクラスター用に設定する
db01のMySQL Shellから3台分を順番に設定していきます。
docker exec -it db01 mysqlsh --js "root:rootpassword@db01:3306"
Note: 自分がMySQL Shellのパスワード入力でタイプミスが起きまくったのでURIに直接含めてますが、普通に対話入力で大丈夫です。
MySQL Shell内で実行:
dba.configureInstance('root:rootpassword@db01:3306')
途中でいくつか確認が出ますが、すべて y で進めます。
最後の「MySQLを再起動するか」という項目で y を入力しますが、以下のエラーが出ます。
これはDocker環境では想定内の挙動です。
DockerではSQLの RESTART コマンドが使えないため、設定ファイルへの書き込みは完了していても再起動だけ失敗してしまいます。
ここまで完了したら、MySQL Shellを抜けてコンテナを手動再起動していきます。
# mysqlshを抜ける
\quit
# コンテナ再起動
docker restart db01
db02・db03も同様に実施します(db01のmysqlshで行ってください)。
docker exec -it db01 mysqlsh --js "root:rootpassword@db01:3306"
追記
「リカバリ方法(Clone または Incremental)」を聞かれた場合(基本聞かれます)は、今回はCloneを選択してもらえれば大丈夫です。
db02
dba.configureInstance('root:rootpassword@db02:3306')
docker restart db02
db03
dba.configureInstance('root:rootpassword@db03:3306')
docker restart db03
クラスターを作成してメンバーを追加
全台の再起動が完了したら、db01のMySQL Shellでクラスターを作成します。
docker exec -it db01 mysqlsh --js "root:rootpassword@db01:3306"
var cluster = dba.createCluster('wpCluster')
cluster.addInstance('root:rootpassword@db02:3306')
cluster.addInstance('root:rootpassword@db03:3306')
cluster.status()で以下のような出力が得られれば成功です。

"can tolerate up to ONE failure." = 1台が落ちてもクラスターが継続できる状態です。
WordPress用のDBとユーザーを作成する
docker exec -it db01 mysql -u root -p rootpassword
CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8mb4;
CREATE USER 'wp_user'@'%' IDENTIFIED BY 'wp_password';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wp_user'@'%';
FLUSH PRIVILEGES;
Step 4: WordPress + MySQL Routerを起動する
router/Dockerfile
wpコンテナにmysql-router をインストールします。
FROM mysql:8.4
RUN microdnf install -y mysql-router && \
microdnf clean all && \
useradd -r -s /sbin/nologin mysqlrouter 2>/dev/null || true
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 6446 6447 6448 6449
ENTRYPOINT ["/entrypoint.sh"]
router/entrypoint.sh
#!/bin/bash
set -e
CONF_DIR="/etc/mysqlrouter"
CLUSTER_HOST="${CLUSTER_HOST:-db01}"
CLUSTER_PORT="${CLUSTER_PORT:-3306}"
echo "[router] Bootstrapping against ${CLUSTER_HOST}:${CLUSTER_PORT} ..."
until mysqlrouter \
--bootstrap "root:${MYSQL_ROOT_PASSWORD}@${CLUSTER_HOST}:${CLUSTER_PORT}" \
--user=root \
--directory="${CONF_DIR}" \
--force; do
echo "[router] Bootstrap failed, retrying in 10s..."
sleep 10
done
echo "[router] Bootstrap complete."
exec mysqlrouter -c "${CONF_DIR}/mysqlrouter.conf"
このスクリプトのポイントは後述のハマりポイントで解説します。
docker-compose.yml
networks:
innodb-net:
external: true
name: innodb-net
services:
router:
build: ./router
container_name: mysql-router
environment:
MYSQL_ROOT_PASSWORD: rootpassword
CLUSTER_HOST: db01
CLUSTER_PORT: "3306"
networks:
- innodb-net
ports:
- "6446:6446"
- "6447:6447"
restart: unless-stopped
wordpress:
image: wordpress:latest
container_name: wordpress
environment:
WORDPRESS_DB_HOST: router:6446
WORDPRESS_DB_USER: wp_user
WORDPRESS_DB_PASSWORD: wp_password
WORDPRESS_DB_NAME: wordpress
volumes:
- wp_data:/var/www/html
networks:
- innodb-net
ports:
- "8080:80"
depends_on:
- router
restart: unless-stopped
volumes:
wp_data:
innodb-net を external: true にすることで、DBコンテナが作成したネットワークをWordPress側でも使い回します。
WordPressの接続先はDBサーバーのIPではなく router:6446(MySQL Router)にします。
起動
cd db_server_test/wp
docker compose up -d --build
docker logs mysql-routerでBootstrapの完了ログが確認できます。
[router] Bootstrapping against db01:3306 ...
# Bootstrapping MySQL Router 8.4.x instance at '/etc/mysqlrouter'...
...
# MySQL Router configured for the InnoDB Cluster 'wpCluster'
[router] Bootstrap complete.
http://localhost:8080 にアクセスしてWordPressのインストール画面が表示されれば完成です。
ハマったポイント
1. mysql:8.4はOracle Linuxベース(apt-getが使えない)
mysql:8.4 の公式イメージはDebianではなくOracle Linuxベースに変わっており、apt-get を使うとビルドエラーになるので microdnf を使いました。
# NG
RUN apt-get install -y mysql-shell
# OK
RUN microdnf install -y mysql-shell
2. dba.configureInstance() の再起動はDockerでは使えない
dba.configureInstance() は設定完了後にMySQLを再起動しようとしますが、DockerではPID 1のプロセスがsupervisorではないため RESTART SQLコマンドが失敗します。
ERROR: mysqld is not managed by supervisor process.
設定ファイルへの書き込み自体は完了しているので、docker restart で手動再起動すれば問題ありません。
3. RouterのBootstrapはファイル存在チェックをしてはいけない
mysqlrouter.conf の有無でBootstrapをスキップする実装を最初に書いたのですが、うまく動きませんでした。
原因は、Oracle Linux版の mysql-router RPMパッケージがインストール時にデフォルトの mysqlrouter.conf を /etc/mysqlrouter/ に配置するためです。
ファイルがあると判定されるとBootstrapがスキップされ、ルーティング設定のない空のconfでRouterが起動してしまいました。
# NG: デフォルトファイルが存在するのでBootstrapがスキップされる
if [ ! -f "/etc/mysqlrouter/mysqlrouter.conf" ]; then
mysqlrouter --bootstrap ...
fi
# OK: 毎回Bootstrapを実行(--force で上書き)
mysqlrouter --bootstrap ... --force
4. rootでBootstrapするには --user=root が必要
rootユーザーでBootstrapを実行するには --user=root の明示が必要でした。
省略するとエラーになります。
Error: You are bootstrapping as a superuser.
Please use --user=username option.
Use --user=root if this really should be the superuser.
まとめ
DockerでもMySQL InnoDB Cluster + MySQL RouterによるHA構成は動作することを確認できました。
VM環境との主な違いは以下の3点です。
| 項目 | VM環境(CentOS) | Docker |
|---|---|---|
| パッケージマネージャー | dnf |
microdnf |
| MySQLの再起動 |
systemctl restart mysqld または自動 |
docker restart で手動 |
| Router Bootstrapのデフォルトconf | なし | RPMがテンプレートを配置するため注意 |
次のステップとして、db01を停止してdb02がPRIMARYに昇格するフェイルオーバーの検証もやってみると理解が深まります。
おわりに
最後までご拝読ありがとうございました。
いかがだったでしょうか。
かなり詰め込んだので、何度か見返していただけるとありがたいです。
もし間違い等気づきましたらご指摘ください!
ありがとうございました!
