首页  /  云原生  /  正文

Docker 里搭 MySQL 主从复制

云原生 2026-10-02📖 18 分钟👁 —
🐳 云原生 · Docker第 5 / 9 页123456789📖 完整导航 →

一、整套流程先看一遍

在 docker 里做 MySQL 主从,其实和两台真机做没什么区别:两台容器各挂各的目录、各写各的配置文件,主库开 binlog、建一个专门用来复制的用户,从库指过去、开同步。

难的地方不在 MySQL,而在网络:两个容器默认不在一个网络里,IP 是动态的,写死了下次重启就找不着了(网络的原理放在最后一页)。整套流程是这个样子的:

MySQL 主从复制的完整步骤清单,从新建主库容器一直到主从复制测试

主库容器的创建和挂载上一篇已经写过了(log、data、conf 三个目录都挂到宿主机),这里接着往下配。


二、配主库

配置文件写在宿主机的挂载目录里,这样容器重建也不会丢:

# 2.在配置片目录
vim master.cnf

[mysqld] # 必须写配置段
## 设置server_id,同一局域网中需要唯一
server_id=101

## 指定不需要同步的数据库名称
binlog-ignore-db=mysql

## 开启二进制日志功能
log-bin=mall-mysql-bin

## 设置二进制日志使用内存大小(事务)
binlog_cache_size=1M

## 设置使用的二进制日志格式(mixed,statement,row)
binlog_format=mixed

## 二进制日志过期清理时间。默认值为0,表示不自动清理。
expire_logs_days=7

## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062

docker restart mysql-master

root@VM-0-12-ubuntu:/mydata/mysql-master/conf# docker ps
CONTAINER ID   IMAGE       COMMAND                  CREATED          STATUS         PORTS                                                    NAMES
95ca9a63102a   mysql:5.7   "docker-entrypoint.s…"   11 minutes ago   Up 4 seconds   33060/tcp, 0.0.0.0:3307->3306/tcp, [::]:3307->3306/tcp   mysql-master

# 3.进入容器、进入mysql
docker exec -it mysql-master bash
mysql -uroot -proot

# 进入容器操作
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.00 sec)

这里不用去写 my.cnf,因为镜像里的 my.cnf 里有一句 !includedir,会把 conf.d 目录下的 *.cnf 全引进来 —— 所以我把配置文件挂在 /etc/mysql/conf.d 下,起名 master.cnf 就够了。

还有个细节:静态参数改了要重启 mysql,动态参数不用,这个不必深究,配完重启一下最省心。

接着建一个专门用来同步的用户,别拿 root 去复制:

# 4.创建同步数据的用户,并开启(不能随便让人掏走了)
create user 'slave'@'%' identified by '123321';
grant replication slave,replication client on *.* to 'slave'@'%';

三、配从库

从库和主库一个套路:先起容器,再写配置。注意端口现在是 3308,server_id 要跟主库不一样:

# 5.新建从服务器
docker run -p 3308:3306 --name mysql-slave \
-v /mydata/mysql-slave/log:/var/log/mysql \
-v /mydata/mysql-slave/data:/var/lib/mysql \
-v /mydata/mysql-slave/conf:/etc/mysql/conf.d \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7

docker ps

CONTAINER ID   IMAGE       COMMAND                  CREATED          STATUS          PORTS                                                    NAMES
c38bc353ba03   mysql:5.7   "docker-entrypoint.s…"   2 seconds ago    Up 1 second     33060/tcp, 0.0.0.0:3308->3306/tcp, [::]:3308->3306/tcp   mysql-slave
95ca9a63102a   mysql:5.7   "docker-entrypoint.s…"   29 minutes ago   Up 17 minutes   33060/tcp, 0.0.0.0:3307->3306/tcp, [::]:3307->3306/tcp   mysql-master

# 6.配置my.cnf相关
cd /mydata/mysql-slave/conf
vim slave.cnf

[mysqld]
## 设置 server_id,同一局域网中需要唯一
server_id=102
## 指定不需要同步的数据库名称
binlog_ignore_db=mysql
## 开启二进制日志功能,以备 Slave 作为其它数据库实例的 Master 时使用
log-bin=small-mysql-slave1-bin
## 设置二进制日志使用内存大小(事务)
binlog_cache_size=1M
## 设置使用的二进制日志格式(mixed, statement, row)
binlog_format=mixed
## 二进制日志过期清理时间。默认值为 0,表示不自动清理。
expire_logs_days=7
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免 slave 端复制中断。
## 如:1062 错误是指一些主键重复,1032 错误是因为主从数据库数据不一致
slave_skip_errors=1062
## relay_log配置中继日志
relay_log=small-mysql-relay-bin
## log_slave_updates 表示 slave 将复制事件写进自己的二进制日志
log_slave_updates=1
## slave 设置为只读(具有 super 权限的用户除外)
read_only=1

docker restart mysql-slave
docker ps

从库的配置比主库多了三条,都是有讲究的:relay_log 是中继日志,主库的 binlog 拉过来先落在这里;log_slave_updates=1 让从库也把复制事件写进自己的 binlog(以后从库还能当别人的主库);read_only=1 把从库设成只读,防止有人往从库写数据 —— 主从数据不一致基本都是这么来的。


四、把两边接起来

先在主库上看一下 binlog 的起点,File 和 Position 两个值等下要用:

# 7.在主服务器查看同步状态(mysql环境)

show master status;

mysql> show master status;
+-----------------------+----------+--------------+------------------+-------------------+
| File                  | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-----------------------+----------+--------------+------------------+-------------------+
| mall-mysql-bin.000001 |      617 |              | mysql            |                   |
+-----------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

# 8.到从服务器配置主从复制
# 如果是docker的话就以宿主机(真正的物理服务器为标准)
docker exec -it mysql-slave mysql -uroot -proot

# 从服务器执行
change master to master_host='10.1.0.12',master_user='slave',master_password='123321',master_port=3307,master_log_file='mall-mysql-bin.000001',master_log_pos=617,master_connect_retry=30;
change master to
    master_host = '主服务器宿主机ip',          -- 主库所在机器的ip地址
    master_user = 'slave',                    -- 主库上创建的用于复制的用户名
    master_password = '123321',               -- 复制用户的密码
    master_port = 3307,                       -- 主库的mysql服务端口
    master_log_file = 'mall-mysql-bin.000001',-- 从库开始读取主库二进制日志的文件名
    master_log_pos = 617,                     -- 从库开始读取主库二进制日志的位置(偏移量)
    master_connect_retry = 30;                -- 主从连接断开后,重试连接的间隔时间(秒)
# 不需要都记住,但需要知道意思
# 由于是本机两个docker备份,需要写IP
# 不能写回环地址,由于docker内部是独立环境
# 写了回环就指向自己,不出docker了(docker和宿主机的IP不一样)!!

这些参数不用全记住,知道每个是干什么的就行。最容易写错的是 master_host —— 因为两个 mysql 都在本机,很容易顺手写个 127.0.0.1,那就指向从库自己了,容器内部的回环地址压根出不了这个容器。

后面那句注释我当时没当回事,结果就栽在这儿了:

# 9.从库查看同步状态
show slave status\G
# 没有报错,说明执行成功了

刚 CHANGE MASTER 完还没 START SLAVE 的时候,Slave_IO_Running 和 Slave_SQL_Running 都是 No,这是没启动的正常状态,不用慌。开了同步之后就不对了:

# 10.从数据库开启同步,然后看状态
start slave;

mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Connecting to master
                  Master_Host: 127.0.0.1
                  Master_User: slave
                  Master_Port: 3307
                Connect_Retry: 30
              Master_Log_File: mall-mysql-bin.000001
          Read_Master_Log_Pos: 617
               Relay_Log_File: small-mysql-relay-bin.000001
                Relay_Log_Pos: 4
        Relay_Master_Log_File: mall-mysql-bin.000001
             Slave_IO_Running: Connecting
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 617
              Relay_Log_Space: 154
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 2003
                Last_IO_Error: error connecting to master 'slave@127.0.0.1:3307' - retry-time: 30  retries: 1
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 0
                  Master_UUID: 
             Master_Info_File: /var/lib/mysql/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 260906 12:11:57
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Master_TLS_Version: 
1 row in set (0.00 sec)

报错很明确:Last_IO_Errno: 2003,error connecting to master 'slave@127.0.0.1:3307' —— 写了回环地址,Master_Host 就是 127.0.0.1,从库一直在自己身上找主库,当然连不上。


五、改错:两个容器连同一个自定义网络

正确的做法是给两个容器建一个自定义网络,然后直接用容器名互连(自定义网络的细节在最后一页):

# 这里发现IP写了回环,需要改掉
docker network create mysql-repl
docker network connect mysql-repl mysql-master
docker network connect mysql-repl mysql-slave

# 进从容器,先停掉之前的错误配置
STOP SLAVE;
RESET SLAVE;

# 重新配置

-- 容器间直连用3306,不是3307
-- 容器名,不是IP

CHANGE MASTER TO
  MASTER_HOST='mysql-master',  
  MASTER_PORT=3306,             
  MASTER_USER='slave',
  MASTER_PASSWORD='123321',
  MASTER_LOG_FILE='mall-mysql-bin.000001',
  MASTER_LOG_POS=617,
  MASTER_CONNECT_RETRY=30;

# 针对同一docker网络走容器端口不走宿主机端口

START SLAVE;
SHOW SLAVE STATUS\G

mysql> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: mysql-master
                  Master_User: slave
                  Master_Port: 3306
                Connect_Retry: 30
              Master_Log_File: mall-mysql-bin.000001
          Read_Master_Log_Pos: 617
               Relay_Log_File: small-mysql-relay-bin.000002
                Relay_Log_Pos: 325
        Relay_Master_Log_File: mall-mysql-bin.000001
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 617
              Relay_Log_Space: 538
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 101
                  Master_UUID: 0bc25f6d-a9e3-11f1-89a9-a6f68e26ef44
             Master_Info_File: /var/lib/mysql/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Master_TLS_Version: 
1 row in set (0.00 sec)

# 主库变更数据库查看从库,同步成功
# 添加库、表、字段都能同步就行

两个关键点:容器之间直连走的是容器端口 3306,不是宿主机的 3307;主机名直接写容器名 mysql-master,因为同一个自定义网络里的容器能互相解析名字,IP 变了也不影响。Slave_IO_Running 和 Slave_SQL_Running 都变成 Yes、Seconds_Behind_Master: 0,这时候去主库加库、加表、改字段,从库都能跟上,主从就搭好了。


六、小结

这一篇就一件事:MySQL 主从怎么在 docker 里搭。

主从本身不复杂,server_id 唯一、binlog 打开、主库建复制用户、从库 CHANGE MASTER TO 指过去再 START SLAVE,就这几步。真正卡人的是网络:容器里的 127.0.0.1 指的是它自己,宿主机端口和容器端口也不是一回事;想省心就用自定义网络,容器名当主机名用。

下一篇把这一堆容器写成 compose 项目,一条命令拉起来。