上个月在某个内网隔离得很彻底的现场,碰到一批让我印象深刻的机器:一边是全新的ARM架构服务器,一边是服役多年的x86_64老机器,两边要组成同一个MySQL高可用集群。应用层早就容器化了,数据库还跑在裸机上的传统主从方案,切换脚本写了十几个判断分支,每次故障演练都提心吊胆。客户就提了一个要求:能不能用一套部署方案,在两套CPU架构上同时跑起来,而且整个环境不能连外网。
最后落地下来的方案就是标题里这套:基于ARM64和x86_64混合架构,用docker-compose做离线部署,跑MySQL 8.0.43官方镜像,组成InnoDB Cluster,再挂一个MySQL Router做读写分离。整套东西用两个tar包加一个deploy.sh就能在任意一台目标机器上完成初始化。这篇文章想把当时的选型逻辑、离线准备过程、配置文件细节和踩过的坑完整捋一遍,给后面要做混合架构数据库容器化的运维朋友一个能直接参考的作业。
1. 为什么是InnoDB Cluster + docker-compose这套组合
1.1 传统主从方案的痛点和组复制方案的出现
在聊容器部署之前,先得说清楚为什么要选InnoDB Cluster而不是继续用"主从复制+VIP+切换脚本"那套老组合。传统主从的方案本身不差,但它的核心问题在于:主库宕机后谁来接管、怎么接管,完全取决于外部脚本的判断。脚本要检查数据库进程、VIP漂移、延迟情况,稍有不慎就出现脑裂——两个节点同时认为自己是主,然后应用层写入被劈成两半,数据一致性直接崩。
InnoDB Cluster底层是MySQL Group Replication(组复制),它用Paxos协议让每个节点对事务提交做多数派确认。三节点集群里任何一台挂了,剩下两台会自动选举新的主节点,不需要外部脚本介入。数据一致性、故障检测、成员管理这些事都由MySQL自己完成。MySQL Router则是专门干流量路由的,应用连接Router的读写端口和只读端口,完全感知不到后端谁是主库谁是从库,主节点切换后Router会自动把写流量切到新主上。
这套组合解决的是数据库高可用的最后一公里:高可用不是"我有一主一从",而是"应用无感知地自动完成故障切换"。
1.2 为什么离线环境还要坚持容器化
很多人觉得离线环境就应该老老实实装裸机MySQL,容器化反而多一层麻烦。这个观点有一定道理,但忽略了三个实际问题。第一,容器化以后,MySQL版本、配置、依赖被锁在镜像里,不会因为某台机器装过别的软件把lib库搞乱。第二,混合架构环境下,裸机安装要分别处理两套编译产物和依赖,而镜像只要按架构各准备一份tar包即可,运维对象从"操作系统"缩小到"容器编排文件"。第三,扩容和替换节点时,新机器只需要装好docker、load镜像、docker-compose up,数据目录挂上去就能快速加入集群,比手撸一遍MySQL安装快得多。
离线环境的难点不在"装不装容器",而在"怎么把所需的镜像和工具提前准备好"。只要能解决离线依赖,容器化的收益在线下场景反而被放大了。
1.3 为什么不直接上K8s
这个问题的答案很简单:现场只有三台数据库机器,K8s集群的etcd、kubelet、网络插件、离线镜像仓库全部要单独部署,复杂度是docker-compose的十倍以上,对维护人员的要求也高一个量级。InnoDB Cluster的三节点拓扑用docker-compose在每台机器上各起一个MySQL容器,加一个Router容器,就能完整撑起高可用和读写分离。工具服务于场景,三节点的数据库集群用K8s是给运维自己找活干。
2. 混合架构离线包的准备逻辑:AMD64与ARM64镜像怎么带进内网
2.1 先搞清楚目标节点到底是哪几种CPU架构
开始准备离线包之前,必须先摸清所有目标机器的CPU类型。我这次的现场是三台机器,两台是x86_64(uname -m输出为x86_64),一台是ARMv8(uname -m输出为aarch64)。不要想当然地以为"官方镜像支持多架构,同一个tar包就能到处load"——docker save默认只导出当前机器的平台镜像层,不会自动把多架构的所有层都打进一个tar里。
所以在联网的准备机器上,要分两次操作:在x86_64机器上拉取镜像并save,打出的包只含amd64架构;在aarch64机器上拉取镜像并save,打出的包只含arm64架构。这也是为什么文章标题里要把ARM64和X86_64并列,两个架构的包缺一个,对应架构的节点就起不来。
2.2 多架构镜像在离线环境下的两个处理思路
处理混合架构离线镜像有两种常见思路。
第一种是每个架构一个独立tar包,用TAG或文件名区分,到目标机器上按架构load对应包。优点是简单直观、不依赖额外的工具和网络;缺点是如果现场有10台机器,其中5台ARM、5台x86,你得分别准备两个包,但好在这两个包内容完全不同,按架构分发即可,不会混淆。
第二种是用docker buildx制作一个包含多架构层的manifest镜像,一个包load到任意架构机器上都能用。这个方案看起来更优雅,但有个前提:准备manifest时需要联网,而且在离线现场用docker load加载多架构manifest的行为在不同版本的docker上表现不一致,我见过几台机器load完以后镜像列表是空的。生产场景别赌这种不确定性,老老实实按架构分包。
实际操作时,我在联网的amd64机器上执行了如下操作:
# 在amd64联网机器上 docker pull mysql:8.0.43 docker pull mysql/mysql-router:8.0.43 docker save mysql:8.0.43 | gzip > mysql-8.0.43-amd64.tar.gz docker save mysql/mysql-router:8.0.43 | gzip > mysql-router-8.0.43-amd64.tar.gz在另一台aarch64联网机器上执行同样的命令,把文件名里的amd64替换成arm64即可:
# 在aarch64联网机器上 docker pull mysql:8.0.43 docker pull mysql/mysql-router:8.0.43 docker save mysql:8.0.43 | gzip > mysql-8.0.43-arm64.tar.gz docker save mysql/mysql-router:8.0.43 | gzip > mysql-router-8.0.43-arm64.tar.gz这里我特意加了gzip压缩,因为MySQL镜像加上Router镜像每个大约200MB左右,压缩后能小不少,现场U盘拷贝或者内网传都更快。load的时候直接:
ARCH=$(uname -m) case $ARCH in x86_64) IMG_ARCH=amd64 ;; aarch64) IMG_ARCH=arm64 ;; esac docker load < mysql-8.0.43-${IMG_ARCH}.tar.gz docker load < mysql-router-8.0.43-${IMG_ARCH}.tar.gz2.3 docker-compose插件的离线安装
只有docker还不够,现场机器上不一定装了docker-compose。两套体系要区分:老版本的docker-compose是一个独立二进制,安装时直接下到/usr/local/bin/docker-compose并加执行权限;新版本的docker compose(带空格)是docker的插件,要放到/usr/libexec/docker/cli-plugins/或者~/.docker/cli-plugins/目录下。我建议统一用新版的插件形式,离线准备时下载对应架构的二进制,然后分发到每台机器上。
# 现场离线安装docker-compose插件 mkdir -p /usr/libexec/docker/cli-plugins/ cp docker-compose-linux-${IMG_ARCH} /usr/libexec/docker/cli-plugins/docker-compose chmod +x /usr/libexec/docker/cli-plugins/docker-compose docker compose version这一步很容易被忽略,等到现场才发现机器上连docker compose都没有,会卡住整个流程。离线准备清单里务必带上它。
3. 部署脚本与目录结构设计:把"一键"拆开看
3.1 整体目录结构
"一键离线部署"不是把命令写在README里让运维去复制粘贴,而是真正放一个deploy.sh,执行完以后容器起来、集群建好、路由配置完成。我习惯把整个项目固定成下面这个结构:
mysql-innodb-cluster/ ├── deploy.sh ├── docker-compose.yml ├── .env ├── images/ │ ├── mysql-8.0.43-amd64.tar.gz │ ├── mysql-8.0.43-arm64.tar.gz │ ├── mysql-router-8.0.43-amd64.tar.gz │ └── mysql-router-8.0.43-arm64.tar.gz ├── conf/ │ ├── node1.cnf │ ├── node2.cnf │ └── node3.cnf └── initdb/ └── 01_create_admin.sql.env文件保存所有节点相关的环境变量,不同机器上这个文件里的主机IP不一样,其余完全一样:
MYSQL_ROOT_PASSWORD=Root@2024 MYSQL_IC_ADMIN_PASSWORD=Admin@2024 CLUSTER_NAME=tradeCluster NODE1_IP=192.168.10.11 NODE2_IP=192.168.10.12 NODE3_IP=192.168.10.13 MYSQL_PORT=3306 GR_PORT=33061 ROUTER_RW_PORT=6446 ROUTER_RO_PORT=6447conf目录里三份配置文件是每个节点专用的,区别只在server_id和group_replication_local_address。initdb目录放第一个节点首次启动时自动执行的初始化SQL,用于创建集群管理账号和业务测试账号。
3.2 三节点共享的docker-compose编排
混合架构下最容易被问到的就是:docker-compose.yml里怎么写platform字段。我的做法是不写死platform,而是靠每台机器已经load好的本地镜像来决定。
原因是:如果某台ARM机器上load的是arm64的mysql镜像,那么即便compose文件里什么都不写,docker daemon也会优先选择本机架构匹配的镜像;反之如果是amd64包,它也能正确匹配。一旦在compose里写死platform: linux/amd64,ARM机器上反而可能因为找不到对应镜像或者被迫走模拟而出现问题。所以三个服务用同一个image名字,靠"每台机器load对应架构的包"这个动作来保证架构匹配。
docker-compose.yml的关键部分如下:
services: mysql-node1: image: mysql:8.0.43 container_name: mysql-node1 hostname: node1 network_mode: host env_file: .env environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_ROOT_HOST: '%' TZ: Asia/Shanghai volumes: - ./data/node1:/var/lib/mysql - ./conf/node1.cnf:/etc/mysql/conf.d/node1.cnf:ro - ./initdb:/docker-entrypoint-initdb.d:ro restart: unless-stopped mysql-node2: image: mysql:8.0.43 container_name: mysql-node2 hostname: node2 network_mode: host env_file: .env environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_ROOT_HOST: '%' TZ: Asia/Shanghai volumes: - ./data/node2:/var/lib/mysql - ./conf/node2.cnf:/etc/mysql/conf.d/node2.cnf:ro restart: unless-stopped mysql-node3: image: mysql:8.0.43 container_name: mysql-node3 hostname: node3 network_mode: host env_file: .env environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_ROOT_HOST: '%' TZ: Asia/Shanghai volumes: - ./data/node3:/var/lib/mysql - ./conf/node3.cnf:/etc/mysql/conf.d/node3.cnf:ro restart: unless-stopped注意这里没有写ports映射,原因是用了network_mode: host,容器直接使用宿主机网络,端口就是宿主机的端口。这个选择背后的逻辑后面会专门讲。
3.3 deploy.sh的一键串联逻辑
deploy.sh做四件事:按架构加载镜像、启动容器、等待数据库就绪、初始化InnoDB Cluster并配置Router。核心逻辑如下:
#!/bin/bash set -euo pipefail source .env ARCH=$(uname -m) case $ARCH in x86_64) IMG_ARCH=amd64 ;; aarch64) IMG_ARCH=arm64 ;; *) echo "不支持的架构: $ARCH"; exit 1 ;; esac # 1. 加载对应架构镜像(幂等,已存在则跳过) if ! docker images | grep -q "mysql.*8.0.43"; then docker load < images/mysql-8.0.43-${IMG_ARCH}.tar.gz fi if ! docker images | grep -q "mysql-router.*8.0.43"; then docker load < images/mysql-router-8.0.43-${IMG_ARCH}.tar.gz fi # 2. 准备数据目录,处理mysql用户(uid=999)权限 mkdir -p data/node1 data/node2 data/node3 chown -R 999:999 data # 3. 启动三个MySQL容器 docker compose up -d # 4. 等待所有MySQL节点就绪 for ip in ${NODE1_IP} ${NODE2_IP} ${NODE3_IP}; do until mysqladmin ping -h"$ip" -P"${MYSQL_PORT}" -uroot -p"${MYSQL_ROOT_PASSWORD}" --silent; do echo "等待 $ip 的MySQL就绪..." sleep 3 done done # 5. 在node1上初始化集群,并将node2/node3加入 mysqlsh --uri root@"${NODE1_IP}":"${MYSQL_PORT}" --password="${MYSQL_ROOT_PASSWORD}" --js <<EOF dba.configureInstance('root@${NODE1_IP}:${MYSQL_PORT}', {password: '${MYSQL_ROOT_PASSWORD}', interactive: false}); var cluster = dba.createCluster('${CLUSTER_NAME}', {multiPrimary: false, force: true, interactive: false}); cluster.addInstance({uri: 'root@${NODE2_IP}:${MYSQL_PORT}', password: '${MYSQL_ROOT_PASSWORD}', interactive: false}); cluster.addInstance({uri: 'root@${NODE3_IP}:${MYSQL_PORT}', password: '${MYSQL_ROOT_PASSWORD}', interactive: false}); cluster.status(); EOF执行到这里,三节点的InnoDB Cluster就建好了。Router的离线容器化部署在下面专门展开。
4. 先解决容器网络再谈组复制:host模式的选择理由
4.1 默认bridge网络为什么跨主机连不通
docker-compose最常用的网络模式是bridge,容器会有自己的内网IP,宿主机通过端口映射暴露服务。单机部署完全没问题,但InnoDB Cluster是跨三台物理机的集群,问题立刻就来了。
Group Replication要求每个节点能直接访问其他节点的3306端口(用于数据复制连接)和33061端口(用于组内Paxos通信)。如果用默认bridge网络,docker会给每个容器分配一个类似172.17.0.x的IP,这个IP只有本机才能路由到。node1的容器无法直接访问node2的容器,因为node2的容器IP在node1宿主机上没有路由。端口映射只能解决"外部访问容器"的问题,却不能解决"容器访问容器"的问题——跨主机的容器间通信,bridge网络天生不支持。
有人可能会说,那我可以把所有端口都映射到宿主机,然后让组复制用宿主机IP通信。但这里有个致命点:group_replication_local_address必须填写本节点用于组复制的真实地址,如果填宿主机IP加映射端口,组播后的数据包会再次被NAT转发到容器,逻辑上绕一大圈还容易断。与其和NAT搏斗,不如直接用host网络。
4.2 host网络下的端口与主机名规划
host网络下,每个容器直接绑定宿主机的IP和端口,没有NAT、没有IP漂移、组复制通信完全无障碍。代价是端口直接暴露,3306、33061这两个端口必须确保宿主机上没有被占用,防火墙也要放行。
用host网络时,主机名成为一个重要问题。MySQL的Group Replication在成员识别时会用到主机名,如果三台机器的主机名都是localhost,很容易造成成员注册混乱。我习惯在每台机器上把主机名统一设置成node1、node2、node3,并写入/etc/hosts:
192.168.10.11 node1 192.168.10.12 node2 192.168.10.13 node3再检查时间同步。组复制对节点间时钟偏差有要求,偏差过大会直接拒绝事务。现场机器全部配置chrony同步到内网时间源:
systemctl enable --now chronyd chronyc sources -v4.3 三个节点的MySQL配置模板
host网络模式下,三份cnf配置文件的差别非常小,但每一份都必须精细。node1的配置如下:
[mysqld] server_id=101 gtid_mode=ON enforce_gtid_consistency=ON log_bin=mysql-bin binlog_format=ROW binlog_checksum=NONE log_replica_updates=ON relay_log_info_repository=TABLE master_info_repository=TABLE transaction_write_set_extraction=XXHASH64 group_replication_group_name=a1b2c3d4-1111-2222-3333-444455556666 group_replication_start_on_boot=OFF group_replication_local_address=192.168.10.11:33061 group_replication_group_seeds=192.168.10.11:33061,192.168.10.12:33061,192.168.10.13:33061 group_replication_ip_allowlist=192.168.10.0/24 group_replication_bootstrap_group=OFFnode2和node3只需要把server_id改成102/103,group_replication_local_address改成各自IP。我再说几个容易出问题的点:
binlog_checksum=NONE是组复制的硬性要求,漏掉这一项后面加节点时会直接报错。别图省事不写。group_replication_start_on_boot=OFF很重要,防止容器重启后单节点自动尝试bootstrap组复制,避免出现多个节点互相冲突的状态。集群的启停由mysqlsh统一管理,而不是由MySQL自己决定。group_replication_ip_allowlist填的是宿主机所在的网段,不是容器网段。host模式下就是192.168.10.0/24。group_replication_group_name必须是一个合法UUID格式的字符串,三台机器必须一致。
5. InnoDB Cluster初始化脚本:mysqlsh接管三节点
5.1 初始化实例前的配置检查
docker-compose起来以后,三台机器的MySQL都已经在运行了。第一次用mysqlsh连接node1时,先做实例配置检查。mysqlsh会在连接实例后自动检查所有组复制相关参数,如果发现参数不满足要求,会输出具体的修复建议。
执行dba.configureInstance()这一步的目的,就是让mysqlsh校验并自动修补实例配置。比如log_replica_updates没开、transaction_write_set_extraction不对这类问题,它会提示你是否允许自动修改。在离线环境下,我一般会先手动把配置改好,再执行configureInstance,免得它在自动修补过程中因为权限不足而卡住。
initdb目录里的初始化SQL会在第一个节点首次启动时自动执行,内容如下:
CREATE USER 'ic_admin'@'%' IDENTIFIED BY 'Admin@2024'; GRANT ALL PRIVILEGES ON *.* TO 'ic_admin'@'%' WITH GRANT OPTION; CREATE USER 'app_user'@'%' IDENTIFIED BY 'App@2024'; GRANT SELECT, INSERT, UPDATE, DELETE ON trade_db.* TO 'app_user'@'%'; FLUSH PRIVILEGES;创建ic_admin这个专用管理账号,是为了让mysqlsh和MySQL Router后续都通过这个账号来管理集群,而不是一直用root。用root做集群管理在审计和权限控制上都不够干净,属于能避免就避免的做法。
5.2 createCluster与addInstance的执行流
在deploy.sh的第5步里,mysqlsh一次性完成了集群创建和节点加入。这里有一个很关键的执行顺序问题:必须先等第一个节点处于ONLINE状态,再addInstance第二个节点;第二个节点也变成ONLINE以后,再add第三个。如果addInstance返回后立刻继续下一步,节点还没有完成组复制的追赶和状态同步,容易出现集群成员状态异常。
addInstance过程中,mysqlsh会要求提供该节点的root密码或ic_admin密码。脚本里通过here-doc把密码传进去,但要注意:生产环境如果担心密码出现在shell history里,可以用--uri配合环境变量,或者提前把mysqlsh的凭据存储起来。这个项目的示例里我保持直观,实际现场我会用source .env引用变量,至少避免把密码硬编码在脚本里。
初始化完成后执行cluster.status(),能看到每个实例的状态,期望输出大致是ONLINE、MODE:R/W或MODE:R/O。
5.3 在线状态下校验集群状态的关键输出
取出下面这段含义需要特别说明:
MODE: R/W表示当前实例是主节点,可以接受写流量;MODE: R/O表示当前实例是只读从节点;cluster.status().defaultReplicaSet.status如果都是ONLINE,说明组内成员通信正常。
如果第一次初始化失败,比如密码错误、网络不通或参数不满足,整个集群可能处于ERROR状态。排查时先用cluster.status()看具体报错,再针对性地处理。不要反复执行createCluster,因为重复创建会留下残留元数据。遇到这种情况,正确做法是先cluster.dropMetadata()清理元数据,或者直接把三份数据目录清空重新来过。
6. MySQL Router容器化与读写分离验证
6.1 Router的bootstrap与持久化配置
InnoDB Cluster建好后,读写分离由MySQL Router承担。Router可以理解为挂在集群前面的一个流量分发层:应用连Router的写端口,Router把SQL转给当前主节点;连读端口,Router把SQL轮询到从节点。
Router容器化部署后,有一个必须处理好的问题:配置目录的持久化。Router在bootstrap之后会在指定目录生成配置文件和状态文件,包括mysqlrouter.conf和data/state.json。如果容器重建而配置目录没挂出来,Router会丢失集群元数据,所以一定要把宿主机上的目录映射到容器里。
在node3机器上执行:
mkdir -p /opt/mysqlrouter/data docker run -d --name mysqlrouter \ --network host \ --restart unless-stopped \ -v /opt/mysqlrouter/data:/data \ mysql/mysql-router:8.0.43 \ --bootstrap ic_admin@192.168.10.11:3306 --directory /data --user=root --force这里--directory /data指定Router工作目录,--force允许覆盖已有配置。首次执行会影响集群元数据,所以在所有节点加入完成后才运行Router。
Router启动后会监听四个端口:6446(读写客户端连接)、6447(只读客户端连接)、64460(X协议读写)、64461(X协议只读)。平时应用主要用前两个。
6.2 6446与6447端口测试读写分离
用测试账号连Router验证路由效果。注意这里连的是Router所在机器的IP和Router端口,不是MySQL实例的3306端口:
# 连续5次查询写端口,server_id应该始终是主节点 for i in $(seq 1 5); do mysql -h 192.168.10.13 -P 6446 -u app_user -p'App@2024' -N -e "select @@server_id;" 2>/dev/null done # 连续5次查询只读端口,会在两个从节点之间轮询 for i in $(seq 1 5); do mysql -h 192.168.10.13 -P 6447 -u app_user -p'App@2024' -N -e "select @@server_id;" 2>/dev/null done读写端口无论查多少次都固定命中主节点,只读端口会把请求平均分配到从节点,这就是读写分离的效果。验证完毕以后,应用连数据库的地址就从3306改成Router的6446/6447即可。
6.3 故障切换演练:kill主库后的路由变化
集群建完、读写分离验证通过,还差最后一步:故障切换演练。我第一次在容器环境里做这个测试时,发现整个过程比裸机环境还顺。
假设当前主节点是node1,先在node1上执行:
# 确认主节点server_id mysql -h 192.168.10.13 -P 6446 -u app_user -p'App@2024' -N -e "select @@server_id;" # 停掉主节点容器 docker stop mysql-node1等待约5到10秒,Group Replication的故障检测机制就会把node1从成员列表里剔除,并自动在剩余两个节点中选出新主。Router则会定期向集群元数据同步拓扑,自动把写请求切到新主上。验证方式:再次通过6446端口查询server_id,会发现已经变成了node2或node3。
这时候把node1重新启动:
docker start mysql-node1由于group_replication_start_on_boot=OFF,node1不会自动重新加入组复制。需要用mysqlsh把它重新拉回集群:
mysqlsh --uri ic_admin@192.168.10.11:3306 --password='Admin@2024' --js \ -e "dba.getCluster().rejoinInstance('ic_admin@192.168.10.11:3306', {password: 'Admin@2024'})"这里我特别强调一点:不要在node1容器刚启动、MySQL还在初始化时就执行rejoin。先等MySQL完全就绪,可以用上面的mysqladmin ping判断,否则rejoin会因为连接被拒绝而失败。等它成功rejoin后,cluster.status()里node1会恢复到ONLINE状态,角色变为只读从节点。
7. 离线环境里的坑与最后几点经验
7.1 数据目录权限:最容易忽略的第一道坎
官方mysql镜像内部的mysql用户UID是999,挂载宿主机目录./data/node1:/var/lib/mysql以后,如果宿主目录权限是root所有,容器里的mysqld进程根本没有权限写数据文件。症状表现是容器起来几秒后就退出,查看日志全是Permission denied。
解决办法很简单,第一次启动前执行:
mkdir -p data/node1 data/node2 data/node3 chown -R 999:999 data这条命令必须在docker compose up -d之前执行,否则容器首次初始化失败后,docker-entrypoint脚本可能已经写了一半文件,后面即使补了权限,数据目录也处于不完整状态,又得删掉重新初始化。deploy.sh里专门放在第2步就是这个原因。
7.2 容器IP变化导致实例脱离集群
用bridge网络部署组复制时,如果容器重建后IP变了,这个节点会永久失去和集群的通信。原因很简单:Group Replication的种子地址和本地地址里写的是旧IP,而容器重启后IP已经漂移,其他节点再也找不到它。
host网络模式彻底规避了这个问题,但前提是宿主机IP不能变。如果现场机器也是动态IP(DHCP),建议先把三台机器的IP在交换机或DHCP服务器上固定,或者直接配置静态IP。节点IP是InnoDB Cluster的命根子,这一点再怎么强调都不过分。
7.3 监控、备份和后续升级
离线环境下的运维往往停留在"能把集群搭起来",但高可用集群真正的价值在于长期可维护。至少要配三件套监控:
- 用Prometheus的mysqld_exporter采集三台MySQL节点的复制状态、连接数、QPS指标;
- 用alertmanager对
group_replication成员状态非ONLINE、Router端口不可达做告警; - 数据备份建议用
mysqldump或xtrabackup定时全备加binlog增量,虽然组复制本身有容灾能力,但误删数据这种人为故障还得靠备份恢复。
升级的场景也值得提前想好。MySQL 8.0.43如果未来要升级到8.0.x更高的小版本,离线环境需要重新准备对应架构的tar包,然后逐个节点执行容器内升级。由于数据目录是挂载的,理论上替换镜像后数据还在,但组复制节点的升级顺序有讲究:通常建议从从节点开始,一个一个升级并观察复制状态,最后再升级主节点。
7.4 实际操作中的几点体会
写到最后,分享几个我这次项目里最深的体会。第一,离线环境的容错空间很小,网络不通、包没带全、版本不对,任何一个问题都可能让你在现场多耗半天。所以离线包准备阶段宁可多带一个docker-compose二进制,也别少带一张tar包。第二,混合架构环境下,凡是遇到"为什么这台机器起不来"的问题,第一反应先看uname -m,再看load的镜像架构,八成是这两个地方不匹配。第三,千万别因为在容器里就忽略了时间同步、主机名解析、防火墙这些基础环境,组复制对这三样的敏感度比裸机部署更高。
如果这台集群之后还要扩展,比如从3节点扩到5节点,方向是清晰的:准备新架构的镜像包、规划新IP、启动容器、用mysqlsh的addInstance加入集群,Router会自动感知新成员。整个流程和初始部署完全一致,这也是当初坚持用docker-compose而不是裸机安装的底气所在。