@tony-yin
2018-04-25T02:19:33.000000Z
字数 12839
阅读 1740
Ceph RBD HA

由于Cephfs很不稳定,并且性能差,很难达到用户在性能上的需求,所以Cephfs也难以应用于生产环境之上。而RBD可以说是一直非常稳定的存储接口方案,用户可以将image挂载到客户端进行访问读写,然而很多用户不愿意在本地安装Ceph客户端,所以我们常常需要自己封装一层,给客户端暴露出一个通用的接口进行访问,现在一般都是默认用NFS,所以本文就Ceph RBD如何实现高可用暴露NFS给客户端访问进行分享。
Linux Distribution:ubuntu
Ceph:Giant
Keepalived:v1.2.2
集群信息 :三节点,IP分别为192.168.1.111,192.168.1.112,192.168.1.113
建议先简单了解一些keepalived的机制再看下面的内容~
Keepalived的作用是检测集群中服务器的状态,如果有一台服务器死机,或工作出现故障,Keepalived将检测到,并将有故障的服务器从集群中剔除,当服务器工作正常后Keepalived自动将服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的服务器。
下面从网上找了几张图片,方便大家理解一下其原理和机制:



已下摘录自:http://blog.csdn.net/love_is_simple/article/details/47903527
global_defs {notification_email {sai@localhost # 报警邮件接收人的地址}notification_email_from root@localhost # 发送报警邮件发件人地址smtp_server 127.0.0.1# 发送邮件的服务器地址smtp_connect_timeout 30# 邮件超时时间(可以根据自己的需求进行设定)router_id LVS_DEVEL# 一个实例的标识地址(可以有多个实例但不能相同)}vrrp_script monitor_nginx {script “/root/scripts/monitor_nginx.sh”#根据自己的实际路径放置脚本文件interval 1# 脚本执行间隔Weight -5#脚本结果导致的优先级变更:5表示优先级加5;-5表示优先级减5}vrrp_instance VI_1 {# 虚拟路由器自己的名字state MASTER# 设置服务器模式,当前为主节点,master端interface eth0# 实例网卡,也就是提供服务的网卡,来发送vrrp通告virtual_router_id 51# 设置vrid,这里非常重要,相同的vrid为一个组,他决定,它将决定多播的MAC地址.(建议不要使用默认地址,以免发生冲突)priority 100# 设置本节点的优先级,优先级高的为masteradvert_int 1# 检查间隔,默认为1秒authentication {auth_type PASS# 认证方式,可以是pass或者AH两种认证方式auth_pass 1111# 认证密码}virtual_ipaddress {# 设置vip,虚拟ip地址(实现高可用,转移的vip地址)10.0.1.230# 此地址并不存在,当成为主节点时,此ip地址将会自动生成}script_track {monitor_nginx #跟踪这个monitor_nginx脚本;就是不断去检查这个脚本}}
首先我们要实现RBD导出NFS功能,毕竟只有先能让客户端通过NFS访问后端存储,然后才有必要谈后端存储集群的高可用方案。
我们需要在Ceph Server集群创建RBD image;然后在三个节点上都建立RBD Map关系,最终只会有一个块设备被mount,其余两个既用于占位(防止多image的情况下造成节点间块设备名称不一致),又是为了作为备机在主机发生故障时转换角色,挂载当前节点的块设备。
接着在三个节点上分别在指定目录下创建目录,本文是在/vol/目录下创建目录,比如创建目录/vol/ec,这个目录就是块设备对应的挂载目录。
如果有童鞋对rbd导出nfs过程有兴趣的话,具体请参考:使用NFS挂载RBD。
我们后端存储集群最终只会暴露出一个接口或者说是一个IP,keepalived中有VIP这种配置可以支持,所以我们需要在三个节点上配置keepalived.conf文件,然后启动keepalived所有节点会选举一个master节点并暴露虚拟IP。
然后我们在master节点上将块设备挂载到之前创建的目录/vol/ec,同步信息至/ect/exports,可以通过showmount -e vip可以发现/vol/ec已经暴露到了vip上,客户端便可以通过NFS将上一步创建的目录/vol/ec挂载到本地目录,比如/client_ec;
这时候客户端已经可以通过虚拟IP对RBD image进行读写了,但是如果这时候master节点down了咋办呢?
为了防止集群中主节点不能给client提供访问,我们需要实现高可用,也就是当主节点down了后,集群自动切换主机,并且针对RBD做自动相应挂载操作,让用户无感知访问存储后端。
我们需要配置keepalived.conf,当节点角色转为backup时,触发停止NFS并卸载暴露目录等操作;当节点角色转为master时,触发挂载RBD image并启动NFS等操作;定时检查当前NFS,一旦NFS服务停止了,尝试重启,如果重启失败,停止keepalived服务触发节点角色切换等等。
这些操作对用户来说是无感知的,我们还可以针对keepalived做相关邮件配置提醒服务器发生故障等等。
这个就不多说了,基本操作。
本位基于ubuntu,redhat派可以转换成对应的命令再操作
apt-get install libpopt-dev // 安装依赖apt-get install keepalived
这里默认在test1 pool中创建1G的image,请根据自己的场景转换大小,生产环境一般都要几十T,甚至上百T
rbd create --size 1024 -p test1 image_ec
rbd map -p test1 image_ec# 输出块设备名称: /dev/rbd0
mkfs.ext4 -i 400000 /dev/rbd0
mount -o noatime,user_xattr /dev/rbd0 /vol/ec
VIP必须是当前集群不存在的ip,通过将配置个节点上keepalived.conf,为virtual_ipaddress选项添加IP,我这边用的是192.168.1.13/24
virtual_ipaddress {192.168.1.13/24}
三个节点的角色都配置为BACKUP,并且配置nopreempt,这样就可以实现不抢占模式,当主节点down恢复后不会抢占成为主节点,对我而言哪个是主节点并不重要,频繁切换反而会造成客户端延时。我这边的对外网卡是eth0,priority是真正决定一开始初始化选举master的因素,最大值的节点是master节点,一旦切换角色,这个值并不会改变。
vrrp_instance VI_1 {state BACKUPinterface eth0priority 100nopreempt}
当主机down之后,如果没有关机,角色转换为backup后需要做卸载相关操作;而之前的备机如今成为了主机,也要做挂载等相关操作,这些需求我们可以通过配置keepalived,当角色转换时触发相关脚本,这里的配置就表示当节点角色切换为了master时则需要执行/etc/keepalived/ChangeToMaster.sh,角色切换为backup则会执行/etc/keepalived/ChangeToBackup.sh:
vrrp_instance VI_1 {notify_master "/etc/keepalived/ChangeToMaster.sh"notify_backup "/etc/keepalived/ChangeToBackup.sh"}
如果一旦NFS服务断了,我们不及时处理的话,客户端就可以明显地感知到无法读写了。所以我们需要定时不断检测NFS的状态,这个也可以通过配置track_script选项执行某个脚本并指定间隔时间:
vrrp_script chk_nfs {script "/etc/keepalived/check_nfs.sh" # 调用脚本interval 2 # 设置间隔时间为 2s}vrrp_instance VI_1 {track_script {chk_nfs # 调用上面的chk_nfs函数}}
暂时还是比较精简的,邮件什么的都没配置,keepalived还是可以做很多事情的,有兴趣的童鞋可以深入研究
global_defs {notification_email {}router_id NFS_HA_222}vrrp_script chk_nfs {script "/etc/keepalived/check_nfs.sh"interval 2}vrrp_instance VI_1 {#state MASTERstate BACKUPinterface eth0priority 100virtual_router_id 100advert_int 1authentication {auth_type PASSauth_pass 1111}track_script {chk_nfs}nopreemptnotify_master "/etc/keepalived/ChangeToMaster.sh"notify_backup "/etc/keepalived/ChangeToBackup.sh"virtual_ipaddress {192.168.1.13/24}}
这些脚本都是针对我当前环境的,需要针对自己的环境和需求进行相应更改
ChangeToBackup.sh:
#!/bin/bashservice nfs-kernel-server stopfor folder in $(ls /vol)doif $(mount | grep $folder -q); thenumount -f /vol/$folderfidone
ChangeToMaster.sh:
#!/bin/bashfor folder in $(ls /vol)doif $(mount | grep $folder -q); thenumount /vol/$folder > /dev/nullfidevice=$(grep $folder /etc/fstab -w | awk '{print $1}')mount $device /vol/$folderdoneservice nfs-kernel-server start
check_nfs.sh:
#!/bin/shvip=$(grep -A 1 virtual_ipaddress /etc/keepalived/keepalived.conf | grep -v virtual_ipaddress | tr -d [:blank:] | cut -d '/' -f 1)#echo "$vip" >>/tmp/k.logif ! /sbin/ip addr | grep -q $vip; thenexitfi# check nfs service/sbin/service nfs-kernel-server status >/dev/nullif [ $? -ne 0 ]; then# abnormal, try to restart the nfs service/sbin/service nfs-kernel-server restart/sbin/service nfs-kernel-server status >/dev/nullif [ $? -ne 0 ]; then# still abnormalfor folder in $(ls /vol)doif $(mount | grep $folder -q); thenumount -f /vol/$folderfidone# stop keepalived service/sbin/service keepalived stopfifi
配置完后,分别在三个节点上执行service keepalived restart重启服务,然后分别在三个节点上执行ip addr查看IP情况,可以发现VIP暴露在了node2上,说明我这里node2在keepalived.conf里面配置priority的值是最大的
node1:
root@node1:/etc/keepalived# ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWNlink/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft forever2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000link/ether 00:50:56:aa:70:4e brd ff:ff:ff:ff:ff:ffinet 192.168.1.111/24 brd 192.168.1.255 scope global eth0valid_lft forever preferred_lft forever
node2:
root@node2:/etc# ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWNlink/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft forever2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000link/ether 00:50:56:aa:61:26 brd ff:ff:ff:ff:ff:ffinet 192.168.1.112/24 brd 192.168.1.255 scope global eth0valid_lft forever preferred_lft foreverinet 192.168.1.13/24 scope global secondary eth0valid_lft forever preferred_lft forever
node3:
root@node3:~# ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWNlink/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft forever2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000link/ether 00:50:56:aa:a9:13 brd ff:ff:ff:ff:ff:ffinet 192.168.1.113/24 brd 192.168.1.255 scope global eth0valid_lft forever preferred_lft forever
客户端检查VIP对外暴露接口
[root@tony /]# showmount -e 192.168.1.13Export list for 192.168.1.13:/vol/ec1 *
将server端挂载块设备的目录/vol/ec1再次挂载到客户端上的ec1目录
[root@tony /]# mkdir ec1[root@tony /]# mount -o rw,hard,intr -t nfs 192.168.1.13:/vol/ec1 /ec1[root@tony /]# cd ec1[root@tony ec1]# lslost+found # 此时是没有数据的
我们可以测试一下读写,先看下读,比如我们在node2的/vol/ec1目录下写一个文件:
root@node2:/vol/ec1# lslost+foundroot@node2:/vol/ec1# echo 'hello' > hello.txt
然后客户端查看/ec1目录:
[root@tony ec1]# lshello.txt lost+found[root@tony ec1]# cat hello.txthello
接下来测写,我们可以在客户端写一个文件,然后到服务端查看
[root@tony ec1]# echo 'i am client' > client.txt[root@tony ec1]# lsclient.txt hello.txt lost+found
服务端查看:
root@node2:/vol/ec1# lsclient.txt hello.txt lost+foundroot@node2:/vol/ec1# cat client.txti am client
ok,读写正常,目前为止客户端访问后端存储集群一切顺利!
分三个测试:
NFSKeepalived这个主要是测试check_nfs.sh这个脚本是否在实时监控NFS状态,可以看到刚stop再次查看状态已经是running了,本测试通过~
root@node2:/vol/ec1# service nfs-kernel-server stop* Stopping NFS kernel daemon [ OK ]* Unexporting directories for NFS kernel daemon... [ OK ]root@node2:/vol/ec1# service nfs-kernel-server statusnfsd running
手动停止主机node2的keepalived服务,发现VIP已经在node2上面消失不见
node2:
root@node2:/vol/ec1# service keepalived stop* Stopping keepalived keepalived [ OK ]root@node2:/vol/ec1# ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWNlink/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft forever2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000link/ether 00:50:56:aa:61:26 brd ff:ff:ff:ff:ff:ffinet 192.168.1.112/24 brd 192.168.1.255 scope global eth0valid_lft forever preferred_lft forever
我们可以在node1发现了上面消失不见得VIP,可知如今角色发生了改变,node1已经成为了新的master节点
node1:
# 出现了 VIP:192.168.1.13/24root@node1:/etc/keepalived# ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWNlink/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft forever2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000link/ether 00:50:56:aa:70:4e brd ff:ff:ff:ff:ff:ffinet 192.168.1.111/24 brd 192.168.1.255 scope global eth0valid_lft forever preferred_lft foreverinet 192.168.1.13/24 scope global secondary eth0valid_lft forever preferred_lft forever# 查看node1的/vol/ec1目录root@node1:/etc/keepalived# ls /vol/ec1client.txt hello.txt lost+found# 查看mount信息root@node1:/etc/keepalived# mount/dev/sda3 on / type ext4 (rw,errors=remount-ro)/dev/sdb2 on /data/osd.0 type ext4 (rw,noatime,user_xattr)nfsd on /proc/fs/nfsd type nfsd (rw).../dev/rbd0 on /vol/ec1 type ext4 (rw)
此时我们可以再次测试一下读写:
# 新的主机读root@node1:/etc/keepalived# ls /vol/ec1client.txt hello.txt lost+found# 新的主机写root@node1:/etc/keepalived# echo 'new service 111' > /vol/ec1/new_server.txtroot@node1:/etc/keepalived# ls /vol/ec1client.txt hello.txt lost+found new_server.txt# 客户端读[root@tony ec1]# lsclient.txt hello.txt lost+found new_server.txt[root@tony ec1]# cat new_server.txtnew service 111# 客户端写[root@tony ec1]# echo 'hello new server' > hello_new_server.txt# 可以看到刚刚客户端写的文件root@node1:/vol/ec1# lsclient.txt hello_new_server.txt hello.txt lost+found new_server.txt
ok,本测试通过~
关闭主机node1,稍等片刻,确定完全关闭再测试
root@node1:/vol# rebootBroadcast message from root@node1(/dev/pts/3) at 9:16 ...The system is going down for reboot NOW!
等待完全关闭,我们在node3上看到了VIP:
root@node3:~# ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWNlink/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft forever2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000link/ether 00:50:56:aa:a9:13 brd ff:ff:ff:ff:ff:ffinet 192.168.1.113/24 brd 192.168.1.255 scope global eth0valid_lft forever preferred_lft foreverinet 192.168.1.13/24 scope global secondary eth0valid_lft forever preferred_lft forever
客户端读写测试:
# 客户端读[root@tony ec1]# lsclient.txt hello_new_server.txt hello.txt lost+found new_server.txt# 客户端写[root@tony ec1]# echo 'reboot' > reboot.txt[root@tony ec1]# lsclient.txt hello_new_server.txt hello.txt lost+found new_server.txt reboot.txt
node3:
root@node3:~# ls /vol/ec1client.txt hello_new_server.txt hello.txt lost+found new_server.txt reboot.txtroot@node3:~# cat /vol/ec1/reboot.txtreboot
ok,本测试通过~
通过上面三个测试,我们已经基本确保了keepalived会保证集群中主机发生异常时还是可以很好地对外提供服务,并且真正地做到了高可用,低延时,高可靠。
因为我实现这个RBD高可用是在我们项目中做的,我们项目中UI上可以创建共享目录,但是之前都是用的cephfs实现的,而我做的就是将cephfs方式使用RBD替代,大家应该都清楚作为POSIX文件接口的cephfs内部已经做好了很多事情,它可以将所有节点挂载的目录做到真正的共享,也就是共享目录三个目录都有,改一个其他两个都会随之而改变,而不是像我们RBD同时只会针对某一个主机访问。
而RBD替换的话必然存在很多困难和问题,在这里我就以QA问答的方式分享一下我实现过程中遇到的种种问题和别人提出的需求。
问:如何通过代码实现三个节点都做相关操作的,比如创建image和目录等等?
答:我们项目是这样实现的,将前端UI的增删改查(比如创建或删除目录等)和后端具体实现共享目录业务分离,也就是说前端只负责做这些信息的增删改查,对应的后端也只是负责这些信息的增删改查,所以用户会即时收到反馈。而真正做事的是在共享业务后端,这个和UI对应的后端做事有所区别,这边共享业务后端是做成了一个Daemon每隔几秒就会去扫描UI后端存储数据是否变化,发生了变化就会做相关操作,比如多了一个文件夹就要创建image等,删除一个文件夹也要做一系列的事情。
问:如何判断哪个节点是master?
答:这个很简单,就通过ip addr命令查找VIP就好了,不能通过配置文件中的priority来判断,因为即使角色切换,那个值也不会变化的,也就是说即使priority是最大值也有可能当前节点不是主节点,这里要注意的是不能仅仅是包含VIP,而是要精确匹配才行,比如VIP是192.168.1.12,如果此时还有个192.168.1.123,如果只是字符串包含的话,那这个也会被匹配,所以要精确匹配。
问:创建文件夹后端实现的逻辑是什么样的?
答:后端Daemon当扫描存储的目录信息相比于上一次扫描时新增的话,那么后端就会做事情了。首先我们要判断是否为主节点,如果是主节点,那么就创建image,然后做Map,接着就要format文件系统,创建目录,然后再做挂载。这时候要注意其他两个备节点也要做rbd map操作,这样做的原因,一是为了占位,比如当创建的项目多了之后,backup节点再map的时候顺序会乱掉,二是为了当主机down,备机转换为master后要找同样的块设备挂载,比如都是/dev/rbd0
问:删除文件夹后端实现的逻辑是什么样的?
答:扫描当前目录少于上一次扫描的目录,那么就针对这些目录,主节点要先umount,再umap,然后rm image,最后删除目录,对于备节点的话就umap,然后再删除目录就好
问:三个节点的Daemon可能执行的顺序不一样,不一定是主节点先执行,那么这个时候备节点将无法map,同理很有可能在删除image的时候,别的节点的image都还没unmap,这样的话image是会删除失败的,这里怎么处理节点间的冲突呢?
答:
首先是创建目录,这时候主节点我们已经做得比较好了,主要担心备节点map的时候image还没有创建,那么我们这边就要判断一下,如果image还不在指定pool中,那么就要设置当前目录情况还为上一次的目录信息,这样下一次扫描代码就会又以为有新目录了,那么该段代码就会又执行一次,此时应该成功了,反正只要成功的时候才会把当前目录信息更改为最新的目录信息。
再来说删除目录,这个和创建map不同的地方在于,我要删除image的时候,我无法知道这个image还有没有和其他节点有map关系,所以我们只有尝试去删除,这边加一个异常捕获,因为rm image报错我们不处理的话会造成代码出错,所以外面包一层异常,这样就可以和上面类似的操作了。这边要注意的是一旦发生异常,我们还必须要在map回去,否则我们无法获取pool等信息了,因为我们是通过rbd showmapped来获取相关信息的。
问:请问keepalived如何做自动化的?
答:由于keepalived也比较简单,三个配置文件相关配置信息都配一样的,我们要做的就是网卡和VIP,网卡的话我们就从项目中获取public ip,而VIP就是UI上面配置的,然后读写文件就好了。
问:一旦节点关机的话,下次开机后块设备就会没了,我们该如何做呢?
答:这个问题的确存在,所以我们要提前将对应关系存在文件中,下次开机的时候根据文件然后对应做map工作
问:多个块设备的时候,keepalived触发的脚本如何做?
答:其实这个的做法我已经暴露在上面我分享的三个脚本里面了,要做的就是遍历/vol目录下的所有目录或者所有rbd*,这边要注意的就是/vol/或者指定目录下存在的必须只有创建的共享目录。
本文通过keepalived初步实现了RBD的高可用,简单地替代了cephfs导出NFS,针对cephfs性能不行的问题,应该有很多小伙伴有这种需求,希望这篇文章能给大家带来一些思路和帮助
之后,我会尝试研究CTDB做高可用,因为keepalived由于比较简单,所以功能也就比较局限了。然后本文只有NFS,后续应该还会有smb,iscsi等等
通过本文,我对RBD和高可用的认识又深入了一些,其实本文涉及到的技术还是比较简单的,还有很多更复杂、更牛逼的高可用方案,这里不禁要说一句,后端还是有意思呀!(相比于前端而言),起码对我来说是这样的,以后会不断精进这些技术,加油!