最近两年,公司在搞私有云ipv6双网改造,我主要承担了mysql ipv6适配测试,现在基本已经改造完成,mysql双栈运行也很稳定,现在把IPv6改造中的一下经验和成果分享出来,欢迎大家借鉴交流。
操作系统开启ipv6支持
mysql ipv6改造之前,必须在操作系统开启ipv6功能,下面是centos7开启ipv6的详细步骤。
第1步:
检查是否启动ipv6,步骤1如果没有出现inet6则表示未启用ipv6,按照2~4步骤检查。
ifconfig|grep -i inet6
第2步:
检查 vi /etc/sysctl.conf 是否出现以下配置,
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
有则改为
net.ipv6.conf.all.disable_ipv6 = 0
net.ipv6.conf.default.disable_ipv6 = 0
net.ipv6.conf.lo.disable_ipv6 = 0
sysctl -p 执行生效
第3步:
vi /etc/sysconfig/network
NETWORKING_IPV6=no
改为
NETWORKING_IPV6=yes
重启网络network 服务
第4步:
vi /etc/default/grub
GRUB_CMDLINE_LINUX="rd.lvm.lv=rhel/swap crashkernel=auto rd.lvm.lv=rhel/root ipv6.disable=1"
删除ipv6.disable=1字段
grub2-mkconfig -o /boot/grub2/grub.cfg
#重启机器使/etc/default/grub配置生效
第5步:
根据用户给的清单配置IPv6 地址及网关地址
cp /etc/sysconfig/network-scripts/ifcfg-eth0 /opt/ifcfg-eth0.bk
vi /etc/sysconfig/network-scripts/ifcfg-eth0
IPV6INIT=yes
IPV6_FAILURE_FATAL=no
IPV6ADDR=/
IPV6_DEFAULTGW=
重启network 服务,知会用户重启network服务会导致网络短暂中断。
验证
执行下面命令,检查ipv6是否可以正常使用。
ping6 ::1
mysql数据库ipv6支持官方说明
实例支持
修改my.cnf配置文件,修改bind-address参数,配置为 :: 可以同时支持IPv4和IPv6的TCP/IP的连接。
[mysqld]
bind_address = ::
注意:bind_address是只读参数,只能重启实例生效。
驱动支持
驱动支持5.1、8.0,使用MySQL Connector 8.0驱动, 对IPv6有了更好的支持。
https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-url-format.html
https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-jdbc-url-format.html
jdbc:mysql://[2001:db8:1:0:20c:29ff:fe96:8b55]:3306/databaseName
jdbc:mysql://2001:db8:1:0:20c:29ff:fe96:8b55:3306/databaseName
jdbc:mysql://[2001:db8:1:0:20c:29ff:fe96:8b55]/databaseName
IPv6在MySQL中的测试场景和结论
大部分功能支持ipv6,如创建mysql ipv6账号,使用ipv6 tcp登录,DQL操作,主从复制同步等。但mysql cluster的相关组件均不支持ipv6。
先看测试结果
名称 | 测试内容 | 结论 |
---|---|---|
mysql客户端使用IPv6访问数据库 | TCP/IP连接、socket连接 | 支持 |
用户管理 | 创建、赋权、删除IPv6用户 | 支持 |
binlog复制 | DDL、DML、DCL数据同步(传统、GTID) | 支持 |
复制特性 | 异步/半同步复制、并行复制、复制线程启停等 | 支持 |
mysql shell | mysqlshell接管mgr集群 | 不支持 |
mysql router | IPv6连接router、router初始化 | 不支持 |
以下是测试过程和输出日志
<1>使用IPv6创建账号,远程登录测试
#先检查是否开启IPv6支持
root@localhost: 14:44: [(none)]> select @@bind_address;
+----------------+
| @@bind_address |
+----------------+
| :: |
+----------------+
1 row in set (0.00 sec)
#创建账号
CREATE USER 'ipv6test3'@'2406:440:a00::38' IDENTIFIED BY '123456';
GRANT ALL PRIVILEGES ON *.* TO 'ipv6test3'@'2406:440:a00::38' IDENTIFIED BY '123456';
#登录验证
mysql -h 2406:440:a00::38 -uipv6test3 -p'123456' -P 3307
#查看状态
mysql> status
Current user: ipv6test@2406:440:a00::38
Connection: 2406:440:a00::38 via TCP/IP
<2>常见DQL操作测试
#创建ipv6用户,验证通过
create user ipv6test2@'::1' identified by '123456';
Query OK, 0 rows affected (0.01 sec)
#删除ipv6用户,验证通过
root@localhost: 15:04: [(none)]> drop user ipv6test2@'::1';
Query OK, 0 rows affected (0.01 sec)
#赋权ipv6用户,验证通过
grant insert,update,delete,select on *.* to ipv6test2@'::1';
Query OK, 0 rows affected (0.01 sec)
<3>使用IPv6搭建主从复制,测试binlog数据同步
配置主从复制
IPv6示例
主库: 2406:440:a00::9f8
从库: 2406:440:a00::9d7
#主库:创建复制账号
CREATE USER 'ipv6repl'@'2406:440:a00::9d7' IDENTIFIED BY 'Crcrepl@2020#DBA';
grant Replication client,Replication slave on *.* to 'ipv6repl'@'2406:440:a00::9d7';
#从库:执行change master
change master to master_host='2406:440:a00::9f8',
master_port=3307,
master_user='ipv6repl',
master_password='Crcrepl@2020#DBA',
master_auto_position=1;
#从库:启动主从
start slave;
#从库:查看复制状态
show slave status\G;
root@localhost: 16:43: [(none)]> show slave status\G;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 2406:440:a00::9f8
Master_User: ipv6repl
Master_Port: 3307
Connect_Retry: 60
Master_Log_File: mysql-bin.000002
Read_Master_Log_Pos: 655
Relay_Log_File: relay.000002
Relay_Log_Pos: 414
Relay_Master_Log_File: mysql-bin.000002
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: 655
Relay_Log_Space: 611
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: 12211527
Master_UUID: 11993726-8105-11ed-9c1f-fa163ec0080c
Master_Info_File: mysql.slave_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: 11993726-8105-11ed-9c1f-fa163ec0080c:1-2
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.00 sec)
binlog同步测试
#主库:执行测试语句
create database ipv6repldb;
use ipv6repldb;
create table ip6(id int,name varchar(20),primary key(id));
insert into ip6(name) values('maomao','peter','heihe');
#从库:验证数据是否同步过来
root@localhost: 16:46: [ipv6repldb]> select * from ip6;
+----+--------+
| id | name |
+----+--------+
| 1 | maomao |
| 2 | mhah |
+----+--------+
2 rows in set (0.00 sec)
<4>mysqlrouter初始化报错
另外,ipv6通过router访问数据库也报错(下图中,6446是router端口,3307是数据库端口)
<5>mysqlshell接管mgr报错
IPv4/v6双栈访问数据库面临的困难
1、开源高可用方案中,目前还没有虚拟ipv6 vip访问的案例。
虽然mysql已经支持ipv6,但是很多mysql高可用方案中,一般通过虚拟vip实现故障漂移。那么高可用架构主库出现failover主库切换时,就需要一个虚拟ipv6漂移寻主,这个功能就需要DBA自行实现了。另外在官方的mgr cluster方案中,router不支持ipv6访问(本人亲测)。
2、ipv6改造中,无法使用ipv6单栈网络运行。
在实际的生产环境ipv6改造中发现,所有的系统几乎无法单栈网络运行,比如在TMS运输系统中,它的上下游很多系统都有数据交互。如果将TMS系统的应用全部改为ipv6单栈访问,就会导致上下游很多系统也要进行ipv6改造,这是不现实的。另外一些开源组件、第三方接口本身无法支持ipv6。
3、mysql双栈运行时,两个VIP需要具备原子性。
双栈网络改造中(即ipv4、ipv6两个独立的网络),如果mysql使用了高可用方案,就需要配置两个虚拟vip(一个ipv4,另一个ipv6),两个vip同时运行。当主库切换时,我们要保证两个vip同时在旧主下线,新主上线,保证两个vip漂移的原子性。另外,遇到网卡异常导致其中一个vip异常掉线时,需要及时发现并处理。
4、双栈网络之间彼此独立,彼此无法感知。
如果ipv4和ipv6拥有独立的网卡时,其中一个网卡故障导致通信中断时,另外一条网络链路可能是正常的,这种场景类似于脑裂,对于mysql 高可用 ipv6双栈改造带来了困难。
mysql ipv6双栈改造方案
先看一下我们的mysql高可用方案:mha(双节点)+ VIP
该方案由开源 MHA 组件(在原生的基础上做了修改)和自研 HA_Monitor 服务组成。另外在数据同步中,使用到了 GTID 复制、并行复制和半同步复制。
自研HA_Monitor服务的主要功能:
<1>守护MHA Manager 服务,实时探测manager服务可用性,确保其总是运行在slave节点上。如果发生主库切换,将在新的slave节点拉起;
<2>检查实例可用性及数据同步线程,以及从库只读状态等,发现并自愈故障,如果遇到处理不了的,则采集指标数据并上报监控平台,触发一级告警。整个过程无需DBA干预。
<3>实时检查主从状态,保证ipv4 vip总是运行在主库上,类似于mysqld_safe的功能。我在mha双栈vip开发时,部分功能在HA_Monitor中实现。
以下是ipv6设计思路:
1、双vip(ipv4、ipv6)漂移在mha原生的主库切换程序中实现。
主要涉及下面两个脚本,一个是在线切换脚本,另一个是故障切换脚本:
mha/scripts/master_ip_online_change_vip
mha/scripts/master_ip_failover_vip
当主库发生切换时,就会调用上面脚本,完成vip漂移,所以需要在这2个脚本中新增ipv6的挂载和卸载。脚本用到了两个变量,ifdev是ipv6网卡名称,vip6为ipv6虚拟ip地址。其中这两个变量通过配置文件app.conf带入,下面是部分代码展示:
vim ./mha/scripts/master_ip_online_change_vip
# 自定义VIP绑定
my $ssh_start_vip6 = "/sbin/ifconfig $ifdev inet6 add $vip6";
my $ssh_stop_vip6 = "/sbin/ifconfig $ifdev inet6 del $vip6";
# 定义VIP方法
sub start_vip() {
`ssh $new_master_ssh_user\@$new_master_host \" $ssh_start_vip \"`;
`ssh $new_master_ssh_user\@$new_master_host \" $ssh_start_vip6 \"`;
}
sub stop_vip() {
return 0 unless ($orig_master_ssh_user);
`ssh $orig_master_ssh_user\@$orig_master_host \" $ssh_stop_vip \"`;
`ssh $orig_master_ssh_user\@$orig_master_host \" $ssh_stop_vip6 \"`;
}
vim ./mha/scripts/master_ip_failover_vip
# 定义VIP方法
sub start_vip() {
`ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`;
`ssh $ssh_user\@$new_master_host \" $ssh_start_vip6 \"`;
}
sub stop_vip() {
return 0 unless ($ssh_user);
`ssh $ssh_user\@$orig_master_host \" $ssh_stop_vip \"`;
`ssh $ssh_user\@$orig_master_host \" $ssh_stop_vip6 \"`;
}
配置文件示例,在原来的基础上,新增:–ifdev6=xxx --vip6=xxx。
#自动故障切换master脚本
master_ip_failover_script=/data/mha/scripts/master_ip_failover_vip --ifdev=eth0 --ifdev6=eth1 --vip=10.133.xxx.xxx --vip6=2406:440:600::9503/120
#手动切换master脚本
master_ip_online_change_script=/data/mha/scripts/master_ip_online_change_vip --ifdev=eth0 --ifdev6=eth1 --vip=10.133.xxx.xxx --vip6=2406:440:600::9503/120
2、在HA_Monitor.py中新增双栈 VIP 可用性检测,保证其原子性。
以ipv4 vip为主,ipv6 vip为辅,在服务中实现以下功能:
- 双栈运行过程中,如果ipv4 vip在线,ipv6 vip异常掉线是,HA_Monitor在拥有ipv4的节点自动挂载ipv6,整个检查流程与ipv4 vip的检测流程完全独立。
- 双栈运行过程中,如果ipv6 vip在线,ipv4 vip异常掉线时,HA_Monitor立刻在主库卸载ipv6 vip,整个检查流程与ipv4 vip的检测流程完全独立。
- 扩展,上面检测依赖于ipv4 vip的可用性,这部分逻辑原来就有,原来的ipv4 vip是否正常运行主要通过检查主机网络、网关通信以及数据库集群状态等。
下面分享部分代码段
def is_local_vip6(self):
"""
这个函数用于确认IPv6是否在本地
:param vip6:参数设置中的VIP6值
:return: 0 vip6在本地 1 vip6不在本地
"""
# 获取含有IPv6的网卡信息
info = psutil.net_if_addrs()
netcard_info = []
for k, v in info.items():
for item in v:
# 获取含有ipv6的网卡信息
if item[0] in [2, 10] and not item[1] == '127.0.0.1' and not item[1] == '::1':
netcard_info.append((item[1], k))
global ipaddr
# 获得网卡信息形如 [('192.168.99.101':'eth0')]
ipaddr = netcard_info
for i in ipaddr:
# 截取配置文件中的vip参数,只保留/前面的部分,示例:'2406:440:600::206/128'截取为substring: '2406:440:600::206'
start = self.vip6.find('/')
substring = self.vip6[:start]
if substring == i[0]: # 如果vip和其中一个网卡IP相等说明在本地
return 1 # 代表在本地
return 0 # 代表没在本地
卸载与挂载ipv6 vip
def start_vip6(self):
"""
挂载vip6,先判断哪个是主库,然后进行挂载,如果是双主,判断主从是否正常,然后挂载到ip主机位较小的server上。
"""
mount_vip6 = '/sbin/ifconfig ' + self.netcard6 + ' inet6 add ' + self.vip6 + ' > /dev/null 2>&1'
if not subprocess.call(mount_vip6, shell=True) == 0:
Logger('check vip6').get_logger().error("mount vip6 failed,please check os env.")
def stop_vip6(self):
"""
挂载vip6,先判断哪个是主库,然后进行挂载,如果是双主,判断主从是否正常,然后挂载到ip主机位较小的server上。
"""
umount_vip6 = '/sbin/ifconfig ' + self.netcard6 + ' inet6 del ' + self.vip6 + ' > /dev/null 2>&1'
if not subprocess.call(umount_vip6, shell=True) == 0:
Logger('check vip6').get_logger().error("下线vip6异常,请检查主机环境!")
调用上面方法的逻辑在大循环中实现
""" 从库vip管理:
1、如果从库出现VIP,则执行方法stop_vip()
2、如果从库不存在VIP,则保持现状
"""
db_status_for_vip = db_client.exec_sql(show_slave_status)
# 再次查看复制关系(避免中途发生主库切换),如果没有发生变化,则执行下面操作,反之,进行进入下一轮检查。
if db_status_for_vip:
if mgt_mon.is_local_vip() == 1:
Logger('check vip').get_logger().error("current host is slave,but vip was found,must stop_vip(%s) now!" % self.vip)
mgt_mon.stop_vip()
elif mgt_mon.is_local_vip() == 0:
Logger('check vip').get_logger().info("current host is slave, ipv4 vip(%s) check pass." % self.vip)
# vip6实时监控. ipv4 vip不存在,但是ipv6 vip在线时,必须卸载vip6。
if mgt_mon.is_local_vip6() == 1:
# 当主库ipv4 vip存在,ipv6 vip不存在时,立刻挂载ipv6 vip。
Logger('check vip6').get_logger().error("fun:is_vip6_local:slave hit vip6, exec func stop vip6(%s) now! " % self.vip6)
mgt_mon.stop_vip6()
if mgt_mon.is_local_vip6() == 0:
Logger('check vip6').get_logger().info("current host is slave, ipv6 vip(%s) check pass." % self.vip6)
else:
Logger('check manager').get_logger().warning('database status is changed,exit func vip_check().')
......
代码实现方式很多,但是整体思路不变,首先要保证ipv4 vip的可用性,然后再次基础上,实时探测并保证ipv6是否挂载和卸载。