使用容器进行 MariaDB 复制

在这篇博客中,我们将演示如何使用二进制日志记录(一种创建二进制日志文件和包含所有数据库更改(数据和结构)记录的索引的方法)将运行在 Docker 容器中的 MariaDB 数据库(我们称之为 primary)复制到一个或多个运行在 Docker 容器中的 MariaDB 服务器(我们称之为 replicas)。您可以在此处找到复制工作原理的概述,并在此处找到如何设置复制。您可以在这个 GitHub 脚本中找到一个示例。

配置并启动主服务器 (primary)

在启动主容器之前,我们首先需要对其进行配置。

主服务器 (primary) 的选项文件

在我的当前目录下,我将创建一个名为 config-files 的目录,并在其内部创建一个名为 primarycnf 的目录,该目录应包含文件 primary-1.cnf

$ mkdir -p config-files/primarycnf
$ touch config-files/primarycnf/primary-1.cnf

将以下内容写入 primary-1.cnf

[mariadb]
log-bin                         # enable binary logging
server_id=3000                  # used to uniquely identify the server
log-basename=my-mariadb         # used to be independent of hostname changes 
                                # (otherwise name is <datadir>/mysql-bin)
#binlog-format=MIXED            #default 

现在让我们检查实际的配置文件:为了进行复制,首先需要激活二进制日志。这由log-bin 系统变量控制,该变量将创建文件并存储在数据目录路径 (datadir) 中。由于配置了log-basename 服务器变量,二进制日志将存储在名为 my-mariadb-bin.<extension number> 的文件中,其中前缀 -bin 添加到 log-basename 值之后,并在服务器启动、日志刷新或达到最大二进制日志大小 (max_binlog_size) 时创建(并递增)扩展号。此外,此选项将创建一个二进制日志索引 my-mariadb-bin.index

在复制架构中,每个服务器必须唯一表示,我们为此使用server_id 系统变量。

如果您愿意,可以更改二进制日志格式。默认是混合类型 (mixed type),它提供了基于行 (ROW-based) 和基于语句 (STATEMENT-based) 的最佳组合。安全的语句通过基于语句的复制进行复制,从而节省空间,而不安全的语句则通过基于行的复制进行复制,确保主服务器和副本之间的一致性。

主容器启动的初始化脚本

我们可以在容器启动期间执行自定义 SQL 语句,为此我们将创建一个名为 primaryinit 的文件夹,并在其中创建一个文件 primaryinit.sql

$ mkdir primaryinit
$ touch primaryinit/primaryinit.sql

primaryinit.sql 的内容

CREATE USER 'repluser'@'%' IDENTIFIED BY 'replsecret';
GRANT REPLICATION SLAVE ON *.* TO 'repluser'@'%';
CREATE DATABASE primary_db;

上述 SQL 将创建一个具有特定复制权限的自定义用户,我们的副本将使用该用户连接到主服务器。我们还将创建一个示例数据库 primary_db 作为测试数据库。

启动主容器

让我们编写一个将启动运行主 MariaDB 实例的容器的命令。

下面我们使用了几个卷

  1. 配置:上面创建的 primarycnf 目录将挂载到 /etc/mysql/conf.d 目录(此目录中所有 *.cnf 文件将包含在全局选项文件中。有关如何实现,请参阅容器内的 /etc/mysql/mariadb.cnf)。
  2. 初始数据:通过将 primaryinit 目录挂载到 /docker-entrypoint-initdb.d 目录,在容器启动期间运行的初始化脚本将传入。
  3. 数据目录(可选):主服务器的数据目录将在主机上创建 log-files-primary/ 目录,对应于容器内的 /var/lib/mysql。如果在主机上不存在,它将在容器启动期间创建。

此外,我们添加了 -w 选项作为工作目录,以防您需要登录到容器中检查二进制日志,这正是我们稍后要做的事情。

作为环境变量,MARIADB_ROOT_PASSWORD 用于设置 root 用户的密码,MYSQL_INITDB_SKIP_TZINFO 环境变量用于跳过日志中的警告。所有这些都基于最新的 mariadb 镜像并在后台运行。

$ docker run -d --rm --name mariadb-primary \
-v $PWD/config-files/primarycnf:/etc/mysql/conf.d:z \
-v $PWD/primaryinit:/docker-entrypoint-initdb.d:z \
-v $PWD/log-files-primary:/var/lib/mysql \
-w /var/lib/mysql \
-e MARIADB_ROOT_PASSWORD=secret \
-e MYSQL_INITDB_SKIP_TZINFO=Y \
mariadb:latest

检查主服务器日志(查看入口点以及副本是否已启用)

$ docker logs mariadb-primary
...
022-04-05 15:16:06+00:00 [Note] [Entrypoint]: Temporary server started.
2022-04-05 15:16:06+00:00 [Note] [Entrypoint]: Securing system users (equivalent to running mysql_secure_installation)

2022-04-05 15:16:06+00:00 [Note] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/primaryinit.sql
2022-04-05 15:16:06+00:00 [Note] [Entrypoint]: Stopping temporary server
...
2022-04-05 15:16:07 0 [Note] mariadbd: ready for connections.
Version: '10.7.3-MariaDB-1:10.7.3+maria~focal-log'  socket: '/run/mysqld/mysqld.sock'  port: 3306  mariadb.org binary distribution

创建了 2 个二进制日志和一个索引。

# Log into the data directory and check for binary logs with log-basename
$ docker exec -it mariadb-primary bash
root@e591a4955306:/var/lib/mysql# ls |grep my-mariadb
my-mariadb-bin.000001
my-mariadb-bin.000002
my-mariadb-bin.index

在容器上执行 SQL 语句并检查 master statusbinary logs

$ docker exec -it mariadb-primary mariadb -uroot -psecret -e "select version()"
+-----------------------------------------+
| version()                               |
+-----------------------------------------+
| 10.7.3-MariaDB-1:10.7.3+maria~focal-log |
+-----------------------------------------+
$ docker exec -it mariadb-primary mariadb -uroot -psecret -e "show databases like 'primary%'"
+---------------------+
| Database (primary%) |
+---------------------+
| primary_db          |
+---------------------+

# Check master status
$ docker exec -it mariadb-primary mariadb -uroot -psecret -e "show master status"
+-----------------------+----------+--------------+------------------+
| File                  | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+-----------------------+----------+--------------+------------------+
| my-mariadb-bin.000002 |      347 |              |                  |
+-----------------------+----------+--------------+------------------+

# Show binary logs
$ docker exec mariadb-primary mariadb -uroot -psecret -e "show binary logs\G;"
*************************** 1. row ***************************
 Log_name: my-mariadb-bin.000001
File_size: 824
*************************** 2. row ***************************
 Log_name: my-mariadb-bin.000002
File_size: 347

由于我们正在运行 MariaDB 10.7,我们可以使用实用程序 mariadb-binlog(之前是 mysqlbinlog)来检查二进制日志(请注意,由于容器的工作目录是数据目录,因此无需设置二进制日志的路径)。您可以阅读更多关于mariadb-binlog选项列表的信息。

# To check the binary log use mariadb-binlog utility
$ docker exec -it mariadb-primary mariadb-binlog my-mariadb-bin.000001

# Specify exact position of the binlog
$ docker exec -it mariadb-primary mariadb-binlog --start-position=702 --stop-position=703 --base64-output=never -d primary_db my-mariadb-bin.000001

现在我们已经完成了主服务器的设置!

配置并启动副本 (replica)

副本 (replica) 的选项文件

与主服务器一样,我们需要为副本(或多个副本)配置选项文件。在 config-files 目录中,创建 secondary-1 目录,其中包含文件 secondary-1.cnf

$ mkdir -p config-files/secondary-1
$ touch config-files/secondary-1/secondary-1.cnf

secondary-1.cnf 的内容

[mariadb]
server_id=3001                  # used to uniquely identify the server
log-basename=my-mariadb         # used to be independent of hostname changes 
                                # (otherwise name is <datadir>/mysql-bin)
replicate_do_db=primary_db      # replicate only this DB
#binlog-format=MIXED            #default 

一切都与我们的主服务器配置相同,只是我们使用了不同的 server_id,并且通过replicate_do_db 系统变量指向我们想要复制的特定数据库(这是可选步骤 - 如果不设置,所有数据库都将被复制)。

副本容器启动的初始化脚本

在进行此步骤之前,需要找出主容器的 IP,因为我们使用虚拟桥接网络,其中容器只能通过其私有 IP 访问。需要创建一个用户定义的网络才能按其主机名引用容器。要查找 IP,请运行以下命令

$ docker exec mariadb-primary cat /etc/hosts
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.17.0.2	e591a4955306

注意获得的 IP 172.17.0.2

在这里,我们将创建作为副本容器入口点的脚本。创建文件夹 secondaryinit,并在其中创建一个文件 replinit.sql,该文件将包含复制命令 change master to(完整的复制命令列表可在此处找到)。请注意,我们使用主容器的 IP 作为 master_host 选项。

$ mkdir -p secondaryinit/
$ touch secondaryinit/replinit.sql

replinit.sql 的内容

CHANGE MASTER TO
  MASTER_HOST='172.17.0.2',
  MASTER_USER='repluser',
  MASTER_PASSWORD='replsecret',
  MASTER_PORT=3306,
  MASTER_CONNECT_RETRY=10;

启动副本容器

要启动副本容器,请运行

$ docker run -d --rm --name mariadb-secondary-1 \
-v $PWD/config-files/secondary-1:/etc/mysql/conf.d:z \
-v $PWD/secondaryinit:/docker-entrypoint-initdb.d:z \
-v $PWD/log-files-secondary-1:/var/lib/mysql \
-w /var/lib/mysql \
-e MARIADB_ROOT_PASSWORD=secret \
-e MYSQL_INITDB_SKIP_TZINFO=Y \
mariadb:latest

检查卷并与主服务器的卷进行比较(相似,对吗?)。观察在副本上创建的日志

$ docker logs mariadb-secondary-1
...
2022-04-05 15:34:20+00:00 [Note] [Entrypoint]: Temporary server started.
2022-04-05 15:34:20+00:00 [Note] [Entrypoint]: Securing system users (equivalent to running mysql_secure_installation)
2022-04-05 15:34:21+00:00 [Note] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/replinit.sql
2022-04-05 15:34:21 5 [Note] Master connection name: ''  Master_info_file: 'master.info'  Relay_info_file: 'relay-log.info'
2022-04-05 15:34:21 5 [Note] 'CHANGE MASTER TO executed'. Previous state master_host='', master_port='3306', master_log_file='', master_log_pos='4'. New state master_host='172.17.0.2', master_port='3306', master_log_file='', master_log_pos='4'.
2022-04-05 15:34:21+00:00 [Note] [Entrypoint]: Stopping temporary server
...
2022-04-05 15:34:22 4 [Note] Slave I/O thread: Start asynchronous replication to master 'repluser@172.17.0.2:3306' in log '' at position 4
2022-04-05 15:34:22 5 [Note] Slave SQL thread initialized, starting replication in log 'FIRST' at position 0, relay log './my-mariadb-relay-bin.000001' position: 4
2022-04-05 15:34:22 0 [Note] mariadbd: ready for connections.
Version: '10.7.3-MariaDB-1:10.7.3+maria~focal'  socket: '/run/mysqld/mysqld.sock'  port: 3306  mariadb.org binary distribution
2022-04-05 15:34:22 4 [Note] Slave I/O thread: connected to master 'repluser@172.17.0.2:3306',replication started in log 'FIRST' at position 4

因此,只需从日志中,我们就可以了解复制的状态。我们看到副本已连接到主服务器。我们也可以从主服务器的日志中进行验证

$ docker logs mariadb-primary
...
2022-04-05 15:34:22 7 [Note] Start binlog_dump to slave_server(3001), pos(, 4), using_gtid(0), gtid('')

要确认复制确实正常工作,让我们检查 primary_db 数据库是否存在以及二进制日志

# Check the database
$ docker exec -it mariadb-secondary-1 mariadb -uroot -psecret -e 'show databases like "primary%"'
+---------------------+
| Database (primary%) |
+---------------------+
| primary_db          |
+---------------------+

也可以运行命令 show slave status 并获得以下结果

$ docker exec -it mariadb-secondary-1 mariadb -uroot -psecret -e 'show slave status\G'
*************************** 1. row ***************************
                Slave_IO_State: Waiting for master to send event
                   Master_Host: 172.17.0.2
                   Master_User: repluser
                   Master_Port: 3306
                 Connect_Retry: 10
               Master_Log_File: my-mariadb-bin.000002
           Read_Master_Log_Pos: 347
                Relay_Log_File: my-mariadb-relay-bin.000004
                 Relay_Log_Pos: 651
         Relay_Master_Log_File: my-mariadb-bin.000002
              Slave_IO_Running: Yes
             Slave_SQL_Running: Yes
               Replicate_Do_DB: primary_db
                          [...]
           Exec_Master_Log_Pos: 347
                          [...]
       Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
                          [...]

在这种情况下,副本上没有二进制日志,但如果需要,服务器可以同时是主服务器和副本。如果想在副本上有二进制日志,以便当前副本可以作为其他副本的主服务器,我们应该在我们的中间“主服务器”(secondary-1.cnf)的选项文件中设置 log_bin 选项。如果想将源主服务器的更新保存在副本二进制日志中,我们应该为此配置log_slave_updates 系统选项。

测试主服务器/副本

让我们在主服务器的数据库中插入一些数据(MariaDB 基金会团队成员的名字),并检查 master status(注意我们的 binlog position 是 347)。

# Insert data
$ docker exec -it mariadb-primary mariadb -uroot -psecret -e 'USE primary_db; INSERT INTO primary_db.primary_tbl values  ("Anna"), ("Andreia"), ("Kaj"), ("Monty"), ("Ian"), ("Vicentiu"), ("Daniel"), ("Faustin"),("Vlad"),("Anel");'

# Check status 
$ docker exec -it mariadb-primary mariadb -uroot -psecret -e "show master status"
+-----------------------+----------+--------------+------------------+
| File                  | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+-----------------------+----------+--------------+------------------+
| my-mariadb-bin.000002 |      817 |              |                  |
+-----------------------+----------+--------------+------------------+

检查副本上的数据

$ docker exec -it mariadb-secondary-1 mariadb -uroot -psecret -e 'select * from primary_db.primary_tbl;'
+----------+
| name     |
+----------+
| Anna     |
| Andreia  |
| Kaj      |
| Monty    |
| Ian      |
| Vicentiu |
| Daniel   |
| Faustin  |
| Vlad     |
| Anel     |
+----------+

您可以在 GitHub 上找到复制示例

就是这样。如果您添加多个副本,同样适用。实际上,您可以尝试将其作为一项家庭作业 🙂(提示:您可以在 GitHub 上的脚本中找到它)。

结论与未来工作

这篇博客展示了如何使用容器在本地通过二进制日志记录执行主服务器/副本复制。在后面的一些博客中,我们将展示如何使用全局事务 ID (GTID)。

欢迎在 Zulip 上交流讨论。

阅读更多