之前,我们一起搭建了一个极简 MySQL 主从复制拓扑(基于 Binlog 行复制),这是一个不安全的复制拓扑,优点是性能高。生产环境中核心数据库系统的首要任务是安全、稳定,其次才是性能,这叫做关键任务型(Mission-Critical)应用程序。这也是 Oracle 、DB2、MS SQL Server 三家曾经为什么能近乎垄断全球数据库生产环境市场的原因。
笔者最初是因为测试 Mycat2 而搭建了一个使用 SSL 加密的复制拓扑,没有采用 Mycat2 建议的非安全的复制拓扑。在调试成功后,又搭建了非安全的复制拓扑。
下面来一起学习一下如何搭建一个安全的MySQL 主从复制拓扑吧!
先决条件
- 源(或主)主服务器开启 SSL 加密连接。
- (建议)在副本(或从)服务器配置复制通道时必须使用 SSL 连接。
- 配置复制源时必须设置
SOURCE_SSL=1
才能使用SOURCE_SSL_CA
、SOURCE_SSL_CAPATH
(不建议使用) 、SOURCE_SSL_CERT
、SOURCE_SSL_KEY
这些参数。 - 非安全的复制拓扑的先决条件要求。
搭建
配置源、副本服务器选项文件
即配置主、从服务器选项文件。
Source 服务器 /etc/my.cnf
配置如下:
[mysqld@mycat]
log_timestamps=SYSTEM
port=3307
socket=/var/lib/mysql/mycat/mysql.sock
tmpdir=/tmp
datadir=/var/lib/mysql/mycat
log_bin=bin-mycat.log
lower_case_table_names=1
log-error=/var/log/mysqld-mycat.log
slow-query-log-file=/var/log/mysqld_mycat-slow.log
pid-file=/var/run/mysqld/mysqld-mycat.pid
innodb_buffer_pool_size=128M
innodb_redo_log_capacity=100M
innodb_flush_log_at_trx_commit=1
sync_binlog=1
#replication for mycat
server_id=1
##SSL authority
ssl_ca=ca.pem
ssl_cert=server-cert.pem
ssl_key=server-key.pem
#require_secure_transport=ON
Replica 服务器 /etc/my.cnf
配置如下:
[mysqld@mycat]
log_timestamps=SYSTEM
port=3307
socket=/var/lib/mysql/mycat/mysql.sock
tmpdir=/tmp
datadir=/var/lib/mysql/mycat
log_bin=bin-mycat.log
lower_case_table_names=1
log-error=/var/log/mysqld-mycat.log
slow-query-log-file=/var/log/mysqld_mycat-slow.log
pid-file=/var/run/mysqld/mysqld-mycat.pid
innodb_buffer_pool_size=128M
innodb_redo_log_capacity=100M
innodb_flush_log_at_trx_commit=1
sync_binlog=1
#replication for mycat
server_id=2
配置完成后分别启动两个 MySQL 实例。
验证启用了 SSL 连接
使用 mysql 连接到 Source 服务器验证:
mysql> \s
出现 SSL: Cipher in use is ECDHE-RSA-AES128-GCM-SHA256
即证明使用了 SSL 加密连接。
虽然已经证明使用了 SSL 加密连接,但因为默认的 --ssl-mode=PREFERRED
,没有进行证书验证,无法防止中间人攻击(Man-in-the-middle Attacks)。所以,下面我们再使用 --ssl-mode=VERIFY_CA
验证一下。
先复制 Source 服务器上的 datadir
数据目录中的 ca.pem
、client-cert.pem
、client-key.pem
到 Replica 服务器上。
[root@ic-source mycat]$ scp ca.pem client-*.pem replica1:/usr/local/etc/cert_source/
ca.pem 100% 1147 506.2KB/s 00:00
client-cert.pem 100% 1192 1.1MB/s 00:00
client-key.pem 100% 1704 1.5MB/s 00:00
注意,要修改 CA 文件的属主为 mysql
操作系统用户,否则在配置复制源时会报错。
[root@ic-replica1 lib]# chown -R mysql:mysql /usr/local/etc/cert_source/
[root@ic-replica1 lib]# ll /usr/local/etc/cert_source/
总用量 12
-rw-r--r-- 1 mysql mysql 1147 5月 1 18:52 ca.pem
-rw-r--r-- 1 mysql mysql 1192 5月 1 18:52 client-cert.pem
-rw------- 1 mysql mysql 1704 5月 1 18:52 client-key.pem
然后在 Replcia 服务器上强制使用证书认证连接到 Source 服务器上的 MySQL 实例。
注意
ssl-*
这类 SSL 选项的值是指路径,需要使用绝对路径或相对于当前命令行所在目录$PWD
的相对目录。因而,使用 mysql 客户端连接到 MySQL 服务器时需要cd CA文件所在目录
,或使用绝对路径。
例如,我的当前目录是 /var/lib
,SSL 参数使用绝对路径,执行:
[root@ic-replica1 lib]# pwd
/var/lib
[root@ic-replica1 lib]# mysql -uroot -p -P3307 -hsource --ssl-mode=verify_ca --ssl-ca=/usr/local/etc/cert_source/ca.pem --ssl-cert=/usr/local/etc/cert_source/client-cert.pem --ssl-key=/usr/local/etc/cert_source/client-key.pem -e '\s'
Enter password:
--------------
mysql Ver 8.0.31 for Linux on x86_64 (MySQL Community Server - GPL)
Connection id: 22938
Current database:
Current user: root@replica1
SSL: Cipher in use is ECDHE-RSA-AES128-GCM-SHA256
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 8.0.31 MySQL Community Server - GPL
Protocol version: 10
Connection: source via TCP/IP
Server characterset: utf8mb4
Db characterset: utf8mb4
Client characterset: utf8mb4
Conn. characterset: utf8mb4
TCP port: 3307
Binary data as: Hexadecimal
Uptime: 3 days 17 hours 30 min 17 sec
Threads: 6 Questions: 17832 Slow queries: 3 Opens: 429 Flush tables: 3 Open tables: 338 Queries per second avg: 0.055
--------------
无报错即验证成功。
如果不指定证书,会报错:
[root@ic-replica1 lib]# mysql -uroot -p -P3307 -hsource --ssl-mode=verify_ca -e '\s'
Enter password:
ERROR 2026 (HY000): SSL connection error: CA certificate is required if ssl-mode is VERIFY_CA or VERIFY_IDENTITY
创建复制用户
此处我额外创建了一个角色 role_repl
以复用。
mysql> create role `role_repl`;
Query OK, 0 rows affected (0.01 sec)
GRANT REPLICATION SLAVE ON *.* TO `role_repl` WITH GRANT OPTION;
mysql> create user repl1 identified by 'Repl?111' default role `role_repl`;
Query OK, 0 rows affected (0.00 sec)
mysql> show grants for repl1 using `role_repl`;
+-----------------------------------------------------------------+
| Grants for repl1@% |
+-----------------------------------------------------------------+
| GRANT REPLICATION SLAVE ON *.* TO `repl1`@`%` WITH GRANT OPTION |
| GRANT `role_repl`@`%` TO `repl1`@`%` |
+-----------------------------------------------------------------+
2 rows in set (0.00 sec)
获取复制源服务器的二进制日志坐标
即获取主服务器的二进制日志坐标。
mysql> SHOW MASTER STATUS\G
*************************** 1. row ***************************
File: bin-mycat.000009
Position: 4029
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set:
1 row in set (0.01 sec)
配置复制源
在从服务器上配置复制源为主服务器。
mysql> change replication source to
-> source_host='source',
-> source_port=3307,
-> source_user='repl1',
-> source_password='Repl?111',
-> source_log_file='bin-mycat.000009',
-> source_log_pos=4029,
-> source_ssl=1,
-> source_ssl_ca='/usr/local/etc/cert_source/ca.pem',
-> source_ssl_cert='/usr/local/etc/cert_source/client-cert.pem',
-> source_ssl_key='/usr/local/etc/cert_source/client-key.pem';
Query OK, 0 rows affected, 2 warnings (0.00 sec)
开启复制
在从服务器上开启复制。
mysql> start replica;
Query OK, 0 rows affected (0.01 sec)
mysql> show replica status\G
*************************** 1. row ***************************
Replica_IO_State: Waiting for source to send event
Source_Host: source
Source_User: repl1
Source_Port: 3307
Connect_Retry: 60
Source_Log_File: bin-mycat.000009
Read_Source_Log_Pos: 4029
Relay_Log_File: ic-replica1-relay-bin.000002
Relay_Log_Pos: 326
Relay_Source_Log_File: bin-mycat.000009
Replica_IO_Running: Yes
Replica_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_Source_Log_Pos: 4029
Relay_Log_Space: 542
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Source_SSL_Allowed: Yes
Source_SSL_CA_File: /usr/local/etc/cert_source/ca.pem
Source_SSL_CA_Path:
Source_SSL_Cert: /usr/local/etc/cert_source/client-cert.pem
Source_SSL_Cipher:
Source_SSL_Key: /usr/local/etc/cert_source/client-key.pem
Seconds_Behind_Source: 0
Source_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Source_Server_Id: 1
Source_UUID: fefc2e34-d95f-11ed-9382-000c298d6cb9
Source_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Replica_SQL_Running_State: Replica has read all relay log; waiting for more updates
Source_Retry_Count: 86400
Source_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Source_SSL_Crl:
Source_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Source_TLS_Version:
Source_public_key_path:
Get_Source_public_key: 0
Network_Namespace:
1 row in set (0.00 sec)
结果如下图所示,没有报错即搭建成功。
验证
在主服务器上查看有哪些副本服务器、建库、建表:
mysql> show replicas;
+-----------+------+------+-----------+--------------------------------------+
| Server_Id | Host | Port | Source_Id | Replica_UUID |
+-----------+------+------+-----------+--------------------------------------+
| 2 | | 3307 | 1 | 72e8f54a-d965-11ed-8c0b-000c298656b1 |
+-----------+------+------+-----------+--------------------------------------+
1 row in set (0.00 sec)
mysql> create database mycat;
Query OK, 1 row affected (0.00 sec)
mysql> use mycat;
Database changed
mysql> create table m1(id int primary key);
Query OK, 0 rows affected (0.06 sec)
mysql> insert into m1 values (1),(2),(3);
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
在从服务器上验证是否同步成功:
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mycat |
| mysql |
| performance_schema |
| sys |
| testdb |
+--------------------+
6 rows in set (0.01 sec)
mysql> use mycat;
Database changed
mysql> table m1;
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
+----+
3 rows in set (0.00 sec)
可以看到已经同步成功了。
进阶
上面我们并没有限制复制用户强制使用 SSL 加密连接。在副本上要求加密连接并不能确保源需要来自副本的加密连接。如果要确保源只接受使用加密连接连接的副本,应使用 REQUIRE SSL
。例如:
先在 Replica 服务器上关闭复制:
mysql> stop replica;
Query OK, 0 rows affected, 1 warning (0.00 sec)
再在 Source 服务器上创建用户
mysql> ALTER USER repl1 REQUIRE SSL;
Query OK, 0 rows affected (0.00 sec)
mysql> show master status\G
*************************** 1. row ***************************
File: bin-mycat.000011
Position: 1352
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set:
1 row in set (0.00 sec)
在 Replica 服务器上重新配置复制源的日志起始位置,并启动,查看有无报错:
mysql> change replication source to source_log_pos=1352;
Query OK, 0 rows affected (0.04 sec)
mysql> start replica;
Query OK, 0 rows affected (0.04 sec)
mysql> show replica status\G
*************************** 1. row ***************************
Replica_IO_State: Waiting for source to send event
Source_Host: source
Source_User: repl1
Source_Port: 3307
Connect_Retry: 60
Source_Log_File: bin-mycat.000011
Read_Source_Log_Pos: 1352
Relay_Log_File: ic-replica1-relay-bin.000002
Relay_Log_Pos: 326
Relay_Source_Log_File: bin-mycat.000011
Replica_IO_Running: Yes
Replica_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_Source_Log_Pos: 1352
Relay_Log_Space: 542
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Source_SSL_Allowed: Yes
Source_SSL_CA_File: /usr/local/etc/cert_source/ca.pem
Source_SSL_CA_Path:
Source_SSL_Cert: /usr/local/etc/cert_source/client-cert.pem
Source_SSL_Cipher:
Source_SSL_Key: /usr/local/etc/cert_source/client-key.pem
Seconds_Behind_Source: 0
Source_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Source_Server_Id: 1
Source_UUID: fefc2e34-d95f-11ed-9382-000c298d6cb9
Source_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Replica_SQL_Running_State: Replica has read all relay log; waiting for more updates
Source_Retry_Count: 86400
Source_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Source_SSL_Crl:
Source_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Source_TLS_Version:
Source_public_key_path:
Get_Source_public_key: 0
Network_Namespace:
1 row in set (0.00 sec)
可以看到没有报错。