一、整套流程先看一遍
在 docker 里做 MySQL 主从,其实和两台真机做没什么区别:两台容器各挂各的目录、各写各的配置文件,主库开 binlog、建一个专门用来复制的用户,从库指过去、开同步。
难的地方不在 MySQL,而在网络:两个容器默认不在一个网络里,IP 是动态的,写死了下次重启就找不着了(网络的原理放在最后一页)。整套流程是这个样子的:

主库容器的创建和挂载上一篇已经写过了(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 项目,一条命令拉起来。