ARM64与x86_64混合架构下MySQL InnoDB Cluster离线容器化部署

发布时间:2026/10/19 21:57:36
ARM64与x86_64混合架构下MySQL InnoDB Cluster离线容器化部署
上个月在某个内网隔离得很彻底的现场碰到一批让我印象深刻的机器一边是全新的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_64uname -m输出为x86_64一台是ARMv8uname -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_ARCHamd64 ;; aarch64) IMG_ARCHarm64 ;; 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_PASSWORDRoot2024 MYSQL_IC_ADMIN_PASSWORDAdmin2024 CLUSTER_NAMEtradeCluster NODE1_IP192.168.10.11 NODE2_IP192.168.10.12 NODE3_IP192.168.10.13 MYSQL_PORT3306 GR_PORT33061 ROUTER_RW_PORT6446 ROUTER_RO_PORT6447conf目录里三份配置文件是每个节点专用的区别只在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/amd64ARM机器上反而可能因为找不到对应镜像或者被迫走模拟而出现问题。所以三个服务用同一个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_ARCHamd64 ;; aarch64) IMG_ARCHarm64 ;; *) 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用户(uid999)权限 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/hosts192.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_id101 gtid_modeON enforce_gtid_consistencyON log_binmysql-bin binlog_formatROW binlog_checksumNONE log_replica_updatesON relay_log_info_repositoryTABLE master_info_repositoryTABLE transaction_write_set_extractionXXHASH64 group_replication_group_namea1b2c3d4-1111-2222-3333-444455556666 group_replication_start_on_bootOFF group_replication_local_address192.168.10.11:33061 group_replication_group_seeds192.168.10.11:33061,192.168.10.12:33061,192.168.10.13:33061 group_replication_ip_allowlist192.168.10.0/24 group_replication_bootstrap_groupOFFnode2和node3只需要把server_id改成102/103group_replication_local_address改成各自IP。我再说几个容易出问题的点binlog_checksumNONE是组复制的硬性要求漏掉这一项后面加节点时会直接报错。别图省事不写。group_replication_start_on_bootOFF很重要防止容器重启后单节点自动尝试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 Admin2024; GRANT ALL PRIVILEGES ON *.* TO ic_admin% WITH GRANT OPTION; CREATE USER app_user% IDENTIFIED BY App2024; 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_admin192.168.10.11:3306 --directory /data --userroot --force这里--directory /data指定Router工作目录--force允许覆盖已有配置。首次执行会影响集群元数据所以在所有节点加入完成后才运行Router。Router启动后会监听四个端口6446读写客户端连接、6447只读客户端连接、64460X协议读写、64461X协议只读。平时应用主要用前两个。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 -pApp2024 -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 -pApp2024 -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 -pApp2024 -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_bootOFFnode1不会自动重新加入组复制。需要用mysqlsh把它重新拉回集群mysqlsh --uri ic_admin192.168.10.11:3306 --passwordAdmin2024 --js \ -e dba.getCluster().rejoinInstance(ic_admin192.168.10.11:3306, {password: Admin2024})这里我特别强调一点不要在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不能变。如果现场机器也是动态IPDHCP建议先把三台机器的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而不是裸机安装的底气所在。