NFS双机热备探究实验
一、实验背景:
公司只有一台NFS服务器,上面存有一些编译工具,通用脚本等,现在该机器比较老旧,出现故障时恢复困难,存在单点故障不说,还会造成线上服务的不稳定,现急需对该服务器进行双机热备高可用,无论哪台挂了,可以自动切换过去。
二、初步设想:
使用两台机器,使用文件同步工具对两个机器的文件保持一致,其中一个挂掉后,将另一个机器改为同样的IP地址,使服务恢复,下面就来进行探究该方法是否可以。
三、实验环境
master: 192.168.254.135 Linux版本:CentOS 6.9
slave: 192.168.254.136 Linux版本:CentOS 6.9
client: 192.168.254.129 Linux版本:CentOS 6.9
VIP:192.168.254.100
实验目标:master和slave保存同一份存储文件,无论哪台机器down机,client1和2均不受影响。
四、实验步骤
1、yum安装nfs并配置keepalived,实现IP地址漂移。
master和slave上:
yum -y install nfs keepalived
yum -y install nfs-utils nfs-utils-lib nfs4-acl-tools
chmod 777 /global
vim /etc/exports
/global 192.168.254.0/24(rw,sync,no_root_squash)
/etc/init.d/rpcbind start
service nfs start
vi /etc/selinux/config
SELINUX=disabled
service iptables stop
chkconfig iptables off
vim /etc/keepalived/keepalived.conf
2、客户端
mount -t nfs 192.168.254.100:/global /global
但是经过试验,使用了keepalived并不能实现双机热备,IP地址可以漂移,但是nfs服务无法自动恢复,必须要umount卸载掉然后重新挂载VIP的/global目录,所以我们就来单机抓包来分析一下根本原因。为了不搞混VIP到底在哪台机器上,我们在这里不使用VIP挂载,使用更改IP地址的方式测试服务是否可以自动恢复。
步骤1:在客户端挂载192.168.254.135这台服务器的/global目录,挂载成功
步骤2:把136这台服务器的IP地址和mac地址都改成和135相同,发现ls /global目录提示Stale file handle。
步骤3:把136关机,135开机,发现global可以自动挂回来。
打开wireshark进行抓包,发现在135关机后把136的IP地址改成135,TCP连接可以连回来,但是NFS连不回来,进行报文分析如下。
客户端和正常的135服务器之间的通信如下:
1、客户端先向服务器端发送NFS V4 NULL Call,此报文中不包含什么有用信息,只包含一个版本号。而服务器端也向客户端返回一个同样的空报文。此为握手阶段。
2、紧接着,客户端向服务器端发送NFS报文之PUTROOTFH|GETATTR|GETFH操作。
在这里需要先解释一下FH文件句柄的含义:
RFC协议原文如下:The filehandle in the NFS protocol is a per-server unique identifier for a file system object. The contents of the filehandle are opaque to the client. Therefore, the server is responsible for translating the filehandle to an internal representation of the file system object.[1]
翻译过来就是:NFS协议中的文件句柄是文件系统对象的每服务器唯一标识符。文件句柄的内容对客户端是不透明的。因此,服务器负责将文件句柄转换为文件系统对象的内部表示。
通俗点讲就是文件句柄是文件系统的表示方式,并且对客户端是不透明的。
PUTROOTFH通常用作NFS请求中的第一个操作,用于为后续操作设置上下文。客户端通过使用PUTROOTFH操作,以ROOT 文件句柄启动。PUTROOTFH操作指示服务器将“当前”文件句柄设置为服务器文件系统的ROOT。一旦使用了这个PUTROOTFH操作,客户端就可以使用LOOKUP过程遍历整个服务器的文件树。
而GETATTR操作将获取当前文件句柄指定的文件系统对象的属性。
GETFH是指获取当前文件句柄,此操作返回当前文件句柄值。
简单来讲就是客户端要服务器端的/的文件句柄的值,然后服务器告诉他我的/的句柄hash值为0x62d40c52。
3、客户端反复进行GETATTR的操作,将服务器端根的属性全部获取到
4、获取完服务器的根的属性之后客户端请求PUTFH,ACCESS,GETATTR三个操作
PUTFH的意思就是我要对这个文件进行操作
ACCESS就是要访问PUTFH的文件,即服务器的/,客户端挨个问:我能不能读?我能不能查找?我能不能修改?我能不能增加?我能不能删除?
客户端请求报文详细信息:
然后服务器端就会告诉他:如下图:
你可以来读、查、删、增、改,但是我只允许你读和查,意思就是你可以来删,但是我不让你删,并不是不能删。
5、那么好,既然你能让我查我就查一查我的/etc/fstab中要挂载谁呢?我要挂载你的/global,于是客户端就先发送了查询请求到服务器端,查一查/下有没有global,如果有的话你把他的文件标识符发给我。
客户端请求报文详细信息:
其中:PUTFH是服务器的/的文件标识符,LOOKUP是要挂载的服务器的目录,请求它的GETFH,和我需要的/global的属性。
服务器响应报文详细信息:
服务器说:你给我的根的FH我这匹配,OK,查询请求可以帮你查,OK,你要的/global的文件标识符是xxx,对应的hash值为0x03168bcf,属性给你。
6、好的,既然你给我了/global的FH我就不必每次都找/了,就像有了中间人牵线之后两个人再联系就不必每次都让中间人联系了一样。直接可以找/global了
客户端发送了6次请求获取/global的相关属性。
7、两个人是联系上了,但是能否进入对方的心呢?客户端又问:我能不能读?我能不能查……服务器端当然还是很有礼貌的说:你可以读,你可以查……
客户端请求报文:
服务器端响应报文:
至此,客户端和服务器端连接建立完毕,下面进入/global,并执行ls命令。
8、ls命令执行时先获取一些属性信息,然后发送READDIR请求,
客户端请求READDIR报文:
服务器端响应READDIR请求报文:
9、新建一个文件,总共进行了这么多操作,我们一一来看。
首先还是权限检查,然后发送客户端ID报文:
里面包含了客户端的一些关键信息,
服务器端也会返回一个客户端id
然后客户端确认该id,服务器端再次确认。
接下来客户端使用OPEN操作创建文件,操作信息如下图:
里面包含了很多文件相关的信息,例如权限,文件名,FH,客户端ID等等信息。
然后服务器端响应该请求,两者之间互相发送OPEN_CONFIRM确认信息,最后关闭,发送CLOSE报文,完成一次写操作,其他过程类似,不再赘述,总之报文里面信息非常详细。
10、心跳
在之后的一段时间内没有对/global进行任何操作,他们之间仍然周期性的发送心跳包,客户端每隔60秒发送一次更新请求,服务器端来响应。
那么如果用另一台机器改成同样的IP地址来作为服务器会发生什么现象呢?
在客户端访问nfs服务器时同样会发出请求报文,请求FH为0x03168bcf的文件,但是服务器端会告诉他,我这没有这个FH的文件,报一个NFS4ERR_STALE的错误,在RFC官网上查询该错误的意思是:无效的文件句柄。参数中给出的文件句柄无效。该文件句柄引用的文件不再存在或访问它已被撤销。
到这里,对NFS的报文抓包分析即将接近尾声,证明试图通过keepalived为nfs做高可用的方案是不可行的,原因就是在nfs通信的时候不仅使用TCP连接,更重要的是使用FH文件句柄来识别是否是同一个文件系统,如果不一致的话是无法自动恢复连接的。所以对NFS的高可用应该是用其他方法。或者使用脚本不断检测NFS挂载点是否可用,如果不可用则强行卸载并重新挂载即可。
下面按照官方推荐的NFS高可用方法来做一遍实验,看是否是FH不变。即NFS+DRBD的方式来测试。
测试步骤简略写一下即可,网上很多,不再赘述。
第一步:下载repo yum源,并yun安装drbd。
rpm -Uvh http://www.elrepo.org/elrepo-release-6-8.el6.elrepo.noarch.rpm
yum -y install drbd83-utils kmod-drbd83
第二步:修改配置文件
modprobe drbd
lsmod |grep drbd
cat /etc/drbd.conf
# You can find an example in /usr/share/doc/drbd.../drbd.conf.example
include "drbd.d/global_common.conf";
include "drbd.d/*.res";
cp /etc/drbd.d/global_common.conf /etc/drbd.d/global_common.conf.bak
cp /etc/drbd.d/global_common.conf /etc/drbd.d/global_common.conf.bak
第三步:编辑主配置文件
vim /etc/drbd.d/global_common.conf
global {
usage-count yes;
}
common {
protocol C;
handlers {
}
startup {
wfc-timeout 240;
degr-wfc-timeout 240;
outdated-wfc-timeout 240;
}
disk {
on-io-error detach;
}
net {
cram-hmac-alg md5;
shared-secret "testdrbd";
}
syncer {
rate 30M;
}
}
给master和slave主机增加硬盘,然后fdisk分区得到一个设备路径,本例中为:/dev/sdb1。
然后vim /etc/drbd.d/r0.res 编辑
其中 on后面填写主机和备机的主机名,本例为master和slave,device为将来生成的磁盘设备号,写/dev/drbd0即可,disk为刚刚分区之后的磁盘设备路径,address分别写主机和备机的IP地址。
resource r0 {
on master {
device /dev/drbd0;
disk /dev/sdb1;
address 192.168.254.135:7898;
meta-disk internal;
}
on slave {
device /dev/drbd0;
disk /dev/sdb1;
address 192.168.254.136:7898;
meta-disk internal;
}
}
drbdadm up r0 //启动drbd drbdadm create-md r0 //创建drbd块设备
/etc/init.d/drbd start //启动drbd服务
ps -ef|grep drbd //查看进程
root 5174 2 0 02:25 ? 00:00:00 [drbd0_worker]
root 5193 2 0 02:25 ? 00:00:00 [drbd0_receiver]
root 5207 2 0 02:25 ? 00:00:00 [drbd0_asender]
root 5211 18667 0 02:25 pts/0 00:00:00 grep --color drbd
cat /proc/drbd //查看状态
/etc/init.d/drbd status //查看状态
drbd driver loaded OK; device status:
version: 8.3.16 (api:88/proto:86-97)
GIT-hash: a798fa7e274428a357657fb52f0ecf40192c1985 build by phil@Build64R6, 2014-11-24 14:51:37
m:res cs ro ds p mounted fstype
0:r0 StandAlone Secondary/Unknown UpToDate/DUnknown r-----
第四步:分区格式化,只在master上做 drbdadm primary --force r0 //只需要主机执行,设置自己为master
mkfs.ext4 /dev/drbd0
mkdir /global
mount /dev/drbd0 /global
再次查看主的状态,发现已经变成connected,Primary/Secondary了,意思就是已经连接,并且自己是主,挂载点是/global,类型是ext4。
查看备的状态,发现也是connected,但是自己是Secondary,对方是Primary,没有挂载点。
把primary的eth1给ifdown了,然后直接在secondary上进行主的提升(drbdadm primary --force r0),并且给mount上(mount /dev/drbd0 /global),发现在primary上测试拷入的文件已经同步过来了,并且客户端可以直接ls查看,无需重新挂载。
查看备的状态,状态是WFC,意思就是等待连接,因为主已经被我关机了,但是自己变成了Primary,对方为Unknown,现在在客户端对其写入文件,启动主节点。
之后把primary的eth1恢复后,发现没有自动恢复主从关系,经过查询,发现出现了drbd检测出现了Split-Brain 的状况,两个节点各自都standalone了。
手动恢复Split-Brain状况:
在secondary上:
drbdadm secondary r0
drbdadm -- --discard-my-data connect r0
在primary上:
drbdadm connect r0
drbdadm primary --force r0
mount /dev/drbd0 /global
参考文献:
[1] https://pike.lysator.liu.se/docs/ietf/rfc/56/rfc5661.xml