今天咱们不整那些虚的,直接聊一个实战味很浓的话题:怎么用 GlusterFS 和 NFS-Ganesha 搭一套能自动故障转移的共享存储服务。很多朋友在搞文件共享的时候,要么用简单的 NFS,要么用 Samba,但真要到了生产环境,单点故障、数据一致性、并发性能这些事就会冒出来。我当年第一次搭存储集群的时候,也是踩了不少坑,后来把 GlusterFS 和 NFS-Ganesha 这对组合摸熟了,才觉得心里踏实。这篇文章就当是我跟你坐在马路牙子上唠嗑,把原理、步骤、还有那些坑都掰开揉碎了讲清楚。
一、先聊聊为什么要搞这套东西
咱得先明白,共享存储不是“把文件放到服务器上”这么简单。你搞一个 NFS 服务器,客户端挂载上去,是能用了。但假如这台 NFS 服务器半夜宕机,所有客户端就全瞎了,数据就暂时摸不着了。这要是放在生产环境,老板能把你骂到怀疑人生。所以,高可用是必须的。GlusterFS 是个分布式文件系统,它能把多台机器的磁盘汇成一个大的存储池,而且它本身是支持冗余的,比如把文件复制两份或者三份,防止磁盘坏了丢数据。可 GlusterFS 自带的原生协议(叫 FUSE)不是所有客户端都认,尤其是老牌 Linux 系统或者一些想要标准 NFS 协议的场景。这时候 NFS-Ganesha 就登场了,它能把 GlusterFS 的卷“翻译”成标准的 NFS 协议,让任何支持 NFS 的客户端都能直接挂载使用。更妙的是,NFS-Ganesha 自己有集群能力,可以搭配资源代理做故障转移,咱再配上 keepalived 漂移虚拟 IP,就能实现客户端无感知的自动切换。
二、认识一下两个主角
2.1 GlusterFS 是啥
GlusterFS 说白了就是一个分布式的存储系统,但跟那些特别复杂的对象存储不一样,它更像一个“大仓库”。你把多台服务器的磁盘目录交给它管理,它就帮你把这些目录合并成一个名叫“卷”的逻辑单元。你可以对这个卷设置类型,比如副本模式(Replica)就是每份数据存几份,这样坏一台机器数据还在。很多人喜欢它是因为它安装简单、没有锁死机制,也不强制依赖某个中心节点,用起来挺自由的。
2.2 NFS-Ganesha 又是啥
NFS-Ganesha 是一个用户态的 NFS 服务器实现。注意,是“用户态”,意味着它不用改内核,也不容易把系统搞崩。它官方就支持接入 GlusterFS 作为后端存储。最牛的在于它支持 NFS v3/v4 各种版本,还支持高可用集群,比如用 Pacemaker 或者 keepalived 来管理。咱们这篇就选用 keepalived 来做虚拟 IP 的漂移,因为 keepalived 的配置简单直白,更容易理解整个故障转移的原理。
三、整体架构长啥样
咱们设想有这么一个小集群:两台机器(比如 host1 和 host2)都装了 GlusterFS,这两台机器组成一个信任池。GlusterFS 卷设置为副本模式,每个文件在两个机器上各留一份。然后我们在 host1 和 host2 上分别装好 NFS-Ganesha,把同一个 GlusterFS 卷导出成 NFS 共享。客户端访问的 IP 是一个虚拟 IP(VIP),平时 VIP 漂移在 host1 上,NFS 请求都打到 host1。如果 host1 挂掉了,keepalived 会把 VIP 飞到 host2 上,同时 host2 上的 NFS-Ganesha 立刻接管服务。由于 GlusterFS 在两个机器上都有数据,所以 host2 能直接提供完整的读写能力。对于客户端来说,VIP 没变,只是底层服务器悄悄换了,整个切换时间很短,基本无感知。
四、动手搭建前需要准备什么
4.1 环境清单
咱要用两台 CentOS 7 或者 8 系统的服务器,最好配置差不多。每台机器至少两块磁盘,一块装系统,另一块给 GlusterFS 存数据。由于是基础示例,咱就把独立的数据盘目录分别设为 /data/gluster。另外需要每个机器的主机名和静态 IP,比如:
- host1:192.168.1.11
- host2:192.168.1.12
- VIP:192.168.1.100
当然了,如果是虚拟机,那就更简单了,克隆两台就行,注意 MAC 地址要不一样。
4.2 网络和存储规划
咱把 GlusterFS 的通信端口和管理端口都放防火墙通过,比如 24007、24008 以及卷相关的 49152 到 49156 端口。NFS 需要开放 2049。另外,时间同步非常重要,不然集群容易出幺蛾子。这里咱用 chrony 或者 ntpdate 都行,但重点是不管用哪个,必须让两台机器时间一致。
五、开始搭建 GlusterFS 集群
5.1 安装软件
咱们使用 yum 仓库来装,先把依赖搞明白。在 host1 和 host2 上都执行一样的操作。技术栈:Shell 命令行。下面代码块里就是完整的安装和初始化流程,注释写的很清楚。
# 在 host1 和 host2 上都要执行:配置云镜像仓库,然后安装软件
# 用 yum 安装 centos-release-gluster 这个包,它会引入 GlusterFS 的软件源
yum install -y centos-release-gluster
# 更新仓库缓存(如果网慢就多等一会)
yum makecache
# 安装 GlusterFS 的服务端和管理命令,同时安装后端的存储驱动程序
yum install -y glusterfs-server glusterfs-fuse
# 启动 glusterd 服务,并设为开机自启
systemctl start glusterd
systemctl enable glusterd
# 看看服务状态,如果出现 active (running) 就说明成功
systemctl status glusterd
5.2 配置可信池
现在把 host2 加入到 host1 的信任池里。在 host1 上执行下面的命令。注意,只需要在一台机器上执行就行,另一台会被自动同步。这一段是集群互相“认识”的过程,就像两个人加微信一样,加上之后就能互相通信了。
# 在 host1 上执行:把 host2 添加为可信节点
# 如果提示连接成功,你会看到类似 "Added" 的字样
gluster peer probe host2
# 查看当前池子的状态,能看到 host1 和 host2 都是 ACTIVE
gluster pool list
如果你用的是 hostname 而不是 IP,请确保 /etc/hosts 里有两台机器的解析记录,不然会跟 ping 不通一样。
5.3 创建存储卷
创建卷之前,先在两台机器上把数据目录建好,并且把权限设置一下。数据目录建议最好放在独立分区上,我们这里为了演示,放在根目录下的指定路径。
# 在 host1 和 host2 上分别执行
mkdir -p /data/gluster
# 确保这个目录可以被 gluster 进程读写
chmod 755 /data/gluster
接着在 host1 上创建一个名为 gv0 的副本卷。卷的类型是 replica 2,意思就是每份数据存两副本。创建命令里需要列出两个 brick,即组成副本的两份数据。
# 在 host1 上执行,创建名为 gv0 的副本卷
gluster volume create gv0 replica 2 \
host1:/data/gluster \
host2:/data/gluster
# 确认创建结果
gluster volume info gv0
# 启动这个卷
gluster volume start gv0
# 查看卷状态,里面会显示 bricks 都 online 了
gluster volume status gv0
这里有个细节,如果 host1 上已经有数据目录,最好先清空再创建卷,否则可能报错。还有,卷类型是副本,所以一台机器挂掉后,另一台还有完整的数据,这是高可用的基础。
六、部署 NFS-Ganesha 服务
咱的目标是用 NFS-Ganesha 替代默认的 NFS 服务器,因为它跟 GlusterFS 的集成更紧密,还支持集群故障转移。所以咱要先把系统自带的 NFS 相关服务关掉,避免端口冲突。
6.1 安装和配置
同样在 host1 和 host2 上安装 NFS-Ganesha。安装包名字叫做 nfs-ganesha-glusterfs,里面包含了连接 GlusterFS 的插件。
# 在 host1 和 host2 上分别执行
yum install -y nfs-ganesha-glusterfs
# 为了防止系统自带的 nfs-server 抢占 2049 端口,我们禁用并停止它们
systemctl stop nfs-server
systemctl disable nfs-server
# 然后启动 ganesha 服务(先别急着启动,咱还要改配置文件,这里先记一下命令)
# systemctl enable nfs-ganesha
Ganesha 的配置文件存放位置是 /etc/ganesha/ganesha.conf。这个文件可以写的很灵活,但核心是要定义一个导出项,并且指定文件系统类型为 GLUSTER。我们用一个 here-doc 一次性写入完整配置。注意参数 FSAL 是 File System Abstraction Layer 的缩写,就是让 Ganesha 能跟不同后端存储对接的“翻译官”。
# 在 host1 上编辑配置文件之前,先把原配置备份一下
cp /etc/ganesha/ganesha.conf /etc/ganesha/ganesha.conf.bak
# 使用 cat 写入新配置(下面的内容会覆盖原文件)
cat > /etc/ganesha/ganesha.conf <<'EOF'
# 日志配置
LOG {
# 日志文件路径,这里放到了 /var/log/ganesha.log
path = /var/log/ganesha.log;
# 日志级别,DEBUG 最详细,生产环境建议 INFO
level = INFO;
}
# 这个就是导出配置,相当于传统 /etc/exports 里的条目
EXPORT {
# 导出名称,可以随意写,但不能重复
Export_Id = 1;
# 挂载路径,客户端看到的共享名
Path = "/gv0";
# 伪路径,客户端挂载后就能看到这个目录
Pseudo = "/gv0";
# 协议,支持 NFSv3 和 NFSv4
Protocols = 4, 3;
# 断开连接超时,设成 60 秒
Transports = TCP;
# 访问权限配置
Access_Type = RW;
Squash = No_Root_Squash; # 这样客户端 root 就不会被压成 nobody 了,测试方便
# 后端存储类型
FSAL {
Name = GLUSTER;
# 后端的卷名称
hostname = "192.168.1.11"; # 这个地址要用创建卷时的主机名或IP
volume = "gv0";
# 如果 GlusterFS 是非默认端口,还要指定端口,但我们用默认
}
}
EOF
注意,上面配置里的 hostname 字段是给 Ganesha 找 GlusterFS 卷用的,在正常情况下,应该填创建卷时使用的 hostname,比如 host1。但为了更稳定,建议填 IP 或者确保 DNS 可解析。另外注意,在 host2 上也要写同样的配置,只是 hostname 字段可以改成 host2 自己,也可以保持不变,因为 Ganesha 通过这个地址去访问 GlusterFS 的管理接口,只要两台机器都能访问即可。
配置好以后,启动服务:
# 在 host1 和 host2 上分别执行
systemctl start nfs-ganesha
systemctl enable nfs-ganesha
# 确认状态
systemctl status nfs-ganesha
如果启动失败,立刻查看日志 /var/log/ganesha.log,看看是不是 GlusterFS 没连上,或者是权限问题。通常第一遍都会出点小岔子,别慌,看日志对症下药。
6.2 导出 GlusterFS 卷
实际上,配置文件里直接用 Path 和 Pseudo 定义了导出,所以启动服务之后,就相当于已经导出了。但我们要验证一下 Ganesha 到底有没有把卷“看”到。可以通过 Ganesha 的管理协议查询,但更简单的方法是在本地直接尝试挂载看看。
# 在 host1 本机挂载验证(临时操作,挂载到 /mnt/test)
mkdir -p /mnt/test
mount -t nfs 192.168.1.11:/gv0 /mnt/test
# 如果挂载成功,查看里面内容
ls /mnt/test
# 创建一个测试文件
echo "hello ganesha" > /mnt/test/hello.txt
# 卸载
umount /mnt/test
如果在 host1 上能挂载成功,说明导出没有问题。接下来,我们要让客户端通过 VIP 访问,而不是直接访问 host1 或者 host2。
6.3 启动服务并验证
别忘了,host2 上也要配置同样的 Ganesha 配置并启动。因为万一 VIP 飘过去,host2 也得能提供服务。启动完成后,可以在 host2 本地也挂载一次,确认文件能正常读写。这里要特别注意,因为 GlusterFS 是复制卷,在 host1 上创建的文件在 host2 上也是可见的。所以你可以先在 host1 上写个文件,然后从 host2 挂载,应该能看到同样的文件。这就验证了数据一致性。
七、高可用与故障转移设计
现在核心服务都起来了,可光有 Ganesha 还不够。因为客户端默认只知道一个 IP,如果 host1 挂了,客户端不会自动切换到 host2。所以咱们需要一套“浮动 IP”机制。这里就用 keepalived 来实现 VIP 漂移。
7.1 使用 keepalived 做 VIP 漂移
keepalived 通常被用来做负载均衡的高可用,但它也可以单独做虚拟 IP 管理。原理就是两台机器上都跑 keepalived,它们通过 VRRP 协议互发心跳。正常情况下 VIP 绑在 master 上,如果 master 心跳丢失,backup 就会接管 VIP,并绑定到自己的网卡上。咱们这里设置 host1 为 master,host2 为 backup。安装和配置都很简单。
# 在 host1 和 host2 上分别安装 keepalived
yum install -y keepalived
配置 keepalived 文件,不要直接改默认的那个,先备份。注意配置里的接口名,比如 eth0,你要用 ip addr 查看自己的网卡名字。下面这段是 master 的配置。
# 在 host1 上执行,备份原配置
cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak
# 写入 master 配置
cat > /etc/keepalived/keepalived.conf <<'EOF'
# keepalived 的全局定义
global_defs {
# 防止出现奇怪的问题,建议每个 host 有唯一标识
router_id nfs_ha_1
}
# 定义监控脚本,用来检查 NFS-Ganesha 进程是否活着
vrrp_script check_ganesha {
# 检查进程的命令,如果进程死掉则返回非零值,触发 VIP 漂移
script "/usr/bin/killall -0 nfs-ganesha"
# 每 2 秒检查一次
interval 2
# 如果连续 3 次失败,就认为服务挂了
fall 3
# 如果恢复,连续 1 次成功就算回来
rise 1
}
# VRRP 实例定义
vrrp_instance VI_NFS {
# 当前机器角色:MASTER 或者 BACKUP
state MASTER
# 绑定在网卡上,根据你的环境修改
interface eth0
# VRRP 组播地址不变,同组实例 ID 必须一致
virtual_router_id 51
# 优先级,MASTER 要高于 BACKUP
priority 100
# 广告发送间隔,单位秒
advert_int 1
# 认证信息,两台机器必须一样
authentication {
auth_type PASS
# 密码最多 8 位,这里用 12345678
auth_pass 12345678
}
# 虚拟 IP 地址,可以写多个,但咱们就用一个
virtual_ipaddress {
192.168.1.100/24
}
# 调用上面定义的脚本来监控服务
track_script {
check_ganesha
}
}
EOF
在 host2 上,配置几乎一样,只有两处不同:state 要写成 BACKUP,priority 要比 master 低,比如写成 90。其他包括虚拟路由 ID、密码、接口、VIP 都要保持一致。我们来写一下 host2 的配置文件。
# 在 host2 上执行
cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak
cat > /etc/keepalived/keepalived.conf <<'EOF'
# keepalived 的全局定义
global_defs {
router_id nfs_ha_2
}
# 监控脚本和 master 相同,不再赘述
vrrp_script check_ganesha {
script "/usr/bin/killall -0 nfs-ganesha"
interval 2
fall 3
rise 1
}
vrrp_instance VI_NFS {
state BACKUP # 注意这里是 BACKUP
interface eth0
virtual_router_id 51
priority 90 # 注意这里是 90,低于 master 的 100
advert_int 1
authentication {
auth_type PASS
auth_pass 12345678
}
virtual_ipaddress {
192.168.1.100/24
}
track_script {
check_ganesha
}
}
EOF
配置好以后,在两台机器上启动 keepalived:
# 在 host1 和 host2 上分别执行
systemctl start keepalived
systemctl enable keepalived
启动之后,验证 VIP 状态。在 host1 上执行 ip addr show,应该能看到 192.168.1.100 在 eth0 上。在 host2 上,暂时不应该看到这个 IP。
7.2 故障转移流程
keepalived 的监控脚本 killall -0 nfs-ganesha 会检查进程是否存在,注意如果进程不存在,该命令会返回退出码 1,keepalived 就会认为服务异常,于是降低自身优先级,从而让 backup 接管 VIP。但这里有个小坑:NFS-Ganesha 进程可能没死,只是卡住了,或者端口没监听,单纯检查进程不够保险。所以更严谨的做法是写一个脚本,既检查进程又检查端口。比如用 ss -lnt | grep 2049 来检查 TCP 2049 是否在监听。我们优化一下监控脚本:
# 创建一个检查脚本,放在 /etc/keepalived/check_ganesha.sh
# 注意脚本内容也需要在两个节点上一致
cat > /etc/keepalived/check_ganesha.sh <<'EOF'
#!/bin/bash
# 检查 nfs-ganesha 进程是否存活
if ! /usr/bin/killall -0 nfs-ganesha; then
exit 1
fi
# 检查 TCP 2049 端口是否在监听
if ! ss -lnt | grep -q ':2049'; then
exit 1
fi
exit 0
EOF
# 添加执行权限
chmod +x /etc/keepalived/check_ganesha.sh
然后修改 keepalived.conf 里的脚本路径,把 script 改成 /etc/keepalived/check_ganesha.sh。改完之后重启 keepalived。这样,只有进程跟端口都正常才算健康。
VIP 漂移的流程是这样的:正常情况下,host1 是 master,持有 VIP,同时 host1 上的 NFS-Ganesha 处理所有客户端请求。当 host1 的 Ganesha 进程死掉或者整台服务器宕机,keepalived 检测失败,在两秒内的间隔连续三次失败后,就会进入 FAULT 状态。此时 host1 释放 VIP,host2 收到 host1 的 VRRP 包超时后,自动提升为 master,在自己网卡上绑定 VIP。host2 上的 NFS-Ganesha 服务因为核心里有 GlusterFS 的完整副本,所以能立刻接替读写任务。整个过程对客户端是透明的,因为它们连的 IP 一直是 192.168.1.100。
八、实测一下故障切换
理论讲完了,咱们实际操作一把。先把客户端挂载到 VIP 上,然后模拟故障。客户端可以是任何一台别的机器,只要网络通就行。技术栈依然是 Shell。
# 在客户端机器上执行,创建挂载点并挂载
mkdir -p /mnt/shared
mount -t nfs 192.168.1.100:/gv0 /mnt/shared
# 创建一个文件,记录当前时间
echo "test fault transfer - $(date)" > /mnt/shared/fault_test.txt
# 持续 ping VIP,观察是否丢包
ping 192.168.1.100
然后我们模拟 host1 直接宕机。在真实环境就是关机;在虚拟机里也可以执行 hostname 那个机器上的关机命令。比如在 host1 上执行:
# 在 host1 上执行,直接断开电源(物理机)或者关机
# 虚拟机可以 poweroff
poweroff
此时,客户端 ping 会丢几个包(大概 2-5 个,因为 VRRP 检测需要时间)。等几秒后,ping 恢复,然后查看刚才写的文件还在不在。
# 在客户端重新执行(如果 ping 恢复后,尝试读取文件)
cat /mnt/shared/fault_test.txt
只要文件还能读,并且还能继续写新文件,就说明故障转移成功了。如果一切正常,你会在 host2 上看到 VIP:
# 在 host2 上执行,查看 VIP 是否已经漂移过来
ip addr show eth0
可以看到 192.168.1.100 已经出现。这就代表 host2 成功接管了服务。
注意,如果是有状态的操作,比如正在写文件的时候切换,可能会让写操作失败,这是不可避免的。但 NFS 的客户端会有重试机制,对于大部分场景,切换过去之后继续操作没问题。
九、这套方案的应用场景和优缺点
9.1 适合用在哪
这套方案最适合中小企业或开发测试环境,比如内部文档共享、代码库的共享目录、日志收集的落盘区域。如果你的业务对存储可靠性有要求,又不想花大钱上商业存储,那 GlusterFS + NFS-Ganesha 是很好的替代品。它也能跑在虚拟化环境中,为几台虚拟机提供共享存储,用于结合其他软件实现虚机热迁移。
9.2 优点有哪些
第一,成本低,全是开源软件,只要有两台普通的 x86 服务器就能玩。第二,横向扩展方便,往集群里加一个新节点就能扩充容量。第三,避免单点故障,有了复制卷和 VIP,一台机器挂掉不影响服务。第四,标准NFS协议,兼容性好,无论是 Linux、Windows(装个 NFS 客户端)还是虚拟机中的内核 NFS 驱动都能直接挂载。第五,配置相对简单,比起 Ceph 那套要容易理解得多。
9.3 缺点和坑
凡事都有两面。这个方案的缺点也不少。第一,副本模式的空间利用率低,比如副本数是2,那么实际可用空间只有总量的一半。第二,GlusterFS 在高并发下性能并不算特别出色,尤其是小文件读写,会出现明显的时延。第三,NFS-Ganesha 本身虽然支持高可用,但需要搭配 keepalived 才能实现自动切换,而 keepalived 只能基于 IP 和端口做判断,无法感知更细微的文件系统卡慢问题。第四,脑裂风险:如果两台机器之间的网络抖动,keepalived 可能把 VIP 在两个节点上各绑一次,导致服务互相打架,所以建议设置严格的网络隔离和仲裁机制,或者用更专业的集群软件管。第五,故障转移过程中,客户端可能短暂丢包,对强一致有要求的业务可能接受不了。
十、注意事项和优化建议
先说说最关键的几点。第一,时间同步必须做,不然 VRRP 和 GlusterFS 的口碑都会出问题。建议在两台机器上配置 chrony 对准一个时间服务器。第二,防火墙别乱开,但该开的必须开。比如 keepalived 需要 VRRP 协议,所以你不能把 VRRP 的 IP 协议号 112 给堵死。如果防火墙默认拒绝,那两台机器根本无法选举 VIP。最简单的办法是先关闭防火墙测试,确认后再配置精准放行。第三,GlusterFS 卷的配置参数可以调一调,比如 performance.read-ahead 等,能适当提升性能,但要根据负载测试。第四,Ganesha 的日志默认可能会写到 /var/log/ganesha.log,日志轮转要设置好,否则磁盘会被日志塞满。第五,写脚本要多注意 Ganesha 的回收机制,NFS 有“lease”的概念,切换后旧的客户端连接需要重新建立,所以如果使用四层负载均衡,最好要开启连接持久化。
对于优化方向,可以考虑把 keepalived 换成更重量级的 Pacemaker + Corosync,它能对资源做到更多更细的监控,还能处理文件系统一致性检查。但相应的复杂度也高。如果你只是想要一个高可用的 NFS 分享,那上面这套轻量方案足够日常使用。另外,如果存储量特别大,建议把 GlusterFS 改成分布式复制卷(Distributed Replica),这样既能扩展容量又不丢冗余。还有,客户端的挂载参数也要优化,比如加上 hard、intr、timeo=50、retrans=2,这样在网络抖动或切换的时候,NFS 客户端会重试而不是挂死。
十一、总结
这套基于 GlusterFS 和 NFS-Ganesha 的共享存储方案,就像搭了一套“自动换轮胎”的系统。平时正常跑,一旦轮胎爆了,备胎自己换上,驾驶的人几乎感觉不到。我们从 GlusterFS 集群的创建开始,到 NFS-Ganesha 的导出配置,再到 keepalived 的 VIP 漂移,一步一步把它搭起来了。实践下来,你会发现高可用并不是玄学,只要把核心的组件之间关系梳理清楚,故障转移是可以稳定复现的。最后还是要提醒一句:技术在不断演进,比如 NFS-Ganesha 官方推荐的 HA 配置可能是 Pacemaker,但咱今天这个版本,胜在简单明了,适合入门也好维护。希望你能在自己的环境里动手试一试,把原理彻底吃透,以后再遇到存储高可用的需求,就不会只喊“分布式”却不知道该怎么落地方案了。
评论
围绕“基于GlusterFS的NFS-Ganesha导出共享存储:高可用服务搭建与故障转移设计”参与讨论