@tony-yin
2018-04-25T10:19:33.000000Z
字数 12839
阅读 1430
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# 设置本节点的优先级,优先级高的为master
advert_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 BACKUP
interface eth0
priority 100
nopreempt
}
当主机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 MASTER
state BACKUP
interface eth0
priority 100
virtual_router_id 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
track_script {
chk_nfs
}
nopreempt
notify_master "/etc/keepalived/ChangeToMaster.sh"
notify_backup "/etc/keepalived/ChangeToBackup.sh"
virtual_ipaddress {
192.168.1.13/24
}
}
这些脚本都是针对我当前环境的,需要针对自己的环境和需求进行相应更改
ChangeToBackup.sh
:
#!/bin/bash
service nfs-kernel-server stop
for folder in $(ls /vol)
do
if $(mount | grep $folder -q); then
umount -f /vol/$folder
fi
done
ChangeToMaster.sh
:
#!/bin/bash
for folder in $(ls /vol)
do
if $(mount | grep $folder -q); then
umount /vol/$folder > /dev/null
fi
device=$(grep $folder /etc/fstab -w | awk '{print $1}')
mount $device /vol/$folder
done
service nfs-kernel-server start
check_nfs.sh
:
#!/bin/sh
vip=$(grep -A 1 virtual_ipaddress /etc/keepalived/keepalived.conf | grep -v virtual_ipaddress | tr -d [:blank:] | cut -d '/' -f 1)
#echo "$vip" >>/tmp/k.log
if ! /sbin/ip addr | grep -q $vip; then
exit
fi
# check nfs service
/sbin/service nfs-kernel-server status >/dev/null
if [ $? -ne 0 ]; then
# abnormal, try to restart the nfs service
/sbin/service nfs-kernel-server restart
/sbin/service nfs-kernel-server status >/dev/null
if [ $? -ne 0 ]; then
# still abnormal
for folder in $(ls /vol)
do
if $(mount | grep $folder -q); then
umount -f /vol/$folder
fi
done
# stop keepalived service
/sbin/service keepalived stop
fi
fi
配置完后,分别在三个节点上执行service keepalived restart
重启服务,然后分别在三个节点上执行ip addr
查看IP
情况,可以发现VIP
暴露在了node2
上,说明我这里node2
在keepalived.conf
里面配置priority
的值是最大的
node1
:
root@node1:/etc/keepalived# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
link/ether 00:50:56:aa:70:4e brd ff:ff:ff:ff:ff:ff
inet 192.168.1.111/24 brd 192.168.1.255 scope global eth0
valid_lft forever preferred_lft forever
node2
:
root@node2:/etc# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
link/ether 00:50:56:aa:61:26 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.112/24 brd 192.168.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet 192.168.1.13/24 scope global secondary eth0
valid_lft forever preferred_lft forever
node3
:
root@node3:~# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
link/ether 00:50:56:aa:a9:13 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.113/24 brd 192.168.1.255 scope global eth0
valid_lft forever preferred_lft forever
客户端检查VIP
对外暴露接口
[root@tony /]# showmount -e 192.168.1.13
Export 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]# ls
lost+found # 此时是没有数据的
我们可以测试一下读写,先看下读,比如我们在node2
的/vol/ec1
目录下写一个文件:
root@node2:/vol/ec1# ls
lost+found
root@node2:/vol/ec1# echo 'hello' > hello.txt
然后客户端查看/ec1
目录:
[root@tony ec1]# ls
hello.txt lost+found
[root@tony ec1]# cat hello.txt
hello
接下来测写,我们可以在客户端写一个文件,然后到服务端查看
[root@tony ec1]# echo 'i am client' > client.txt
[root@tony ec1]# ls
client.txt hello.txt lost+found
服务端查看:
root@node2:/vol/ec1# ls
client.txt hello.txt lost+found
root@node2:/vol/ec1# cat client.txt
i am client
ok,读写正常,目前为止客户端访问后端存储集群一切顺利!
分三个测试:
NFS
Keepalived
这个主要是测试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 status
nfsd running
手动停止主机node2
的keepalived
服务,发现VIP
已经在node2
上面消失不见
node2
:
root@node2:/vol/ec1# service keepalived stop
* Stopping keepalived keepalived [ OK ]
root@node2:/vol/ec1# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
link/ether 00:50:56:aa:61:26 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.112/24 brd 192.168.1.255 scope global eth0
valid_lft forever preferred_lft forever
我们可以在node1
发现了上面消失不见得VIP
,可知如今角色发生了改变,node1
已经成为了新的master
节点
node1
:
# 出现了 VIP:192.168.1.13/24
root@node1:/etc/keepalived# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
link/ether 00:50:56:aa:70:4e brd ff:ff:ff:ff:ff:ff
inet 192.168.1.111/24 brd 192.168.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet 192.168.1.13/24 scope global secondary eth0
valid_lft forever preferred_lft forever
# 查看node1的/vol/ec1目录
root@node1:/etc/keepalived# ls /vol/ec1
client.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/ec1
client.txt hello.txt lost+found
# 新的主机写
root@node1:/etc/keepalived# echo 'new service 111' > /vol/ec1/new_server.txt
root@node1:/etc/keepalived# ls /vol/ec1
client.txt hello.txt lost+found new_server.txt
# 客户端读
[root@tony ec1]# ls
client.txt hello.txt lost+found new_server.txt
[root@tony ec1]# cat new_server.txt
new service 111
# 客户端写
[root@tony ec1]# echo 'hello new server' > hello_new_server.txt
# 可以看到刚刚客户端写的文件
root@node1:/vol/ec1# ls
client.txt hello_new_server.txt hello.txt lost+found new_server.txt
ok,本测试通过~
关闭主机node1
,稍等片刻,确定完全关闭再测试
root@node1:/vol# reboot
Broadcast message from root@node1
(/dev/pts/3) at 9:16 ...
The system is going down for reboot NOW!
等待完全关闭,我们在node3
上看到了VIP
:
root@node3:~# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
link/ether 00:50:56:aa:a9:13 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.113/24 brd 192.168.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet 192.168.1.13/24 scope global secondary eth0
valid_lft forever preferred_lft forever
客户端读写测试:
# 客户端读
[root@tony ec1]# ls
client.txt hello_new_server.txt hello.txt lost+found new_server.txt
# 客户端写
[root@tony ec1]# echo 'reboot' > reboot.txt
[root@tony ec1]# ls
client.txt hello_new_server.txt hello.txt lost+found new_server.txt reboot.txt
node3
:
root@node3:~# ls /vol/ec1
client.txt hello_new_server.txt hello.txt lost+found new_server.txt reboot.txt
root@node3:~# cat /vol/ec1/reboot.txt
reboot
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
和高可用的认识又深入了一些,其实本文涉及到的技术还是比较简单的,还有很多更复杂、更牛逼的高可用方案,这里不禁要说一句,后端还是有意思呀!(相比于前端而言),起码对我来说是这样的,以后会不断精进这些技术,加油!