基于 DRBD + NFS + Keepalived 的高可用共享存储实战指南
核心摘要:在基于虚拟 IP(VIP)漂移的传统高可用架构中,若底层存储采用rsync + lsyncd等文件级同步,客户端常因文件系统 Inode 与文件句柄(File Handle)不一致而遭遇严重的ESTALE (Stale file handle)错误。本文详解这一失效机理,并基于 DRBD 块级别镜像 + NFS(固定统一 fsid)+ Keepalived 状态联动 构建真正的生产级零报错无缝漂移高可用存储解决方案。
一、 问题根因与架构选型分析
1.1 文件级同步为何会导致 ESTALE 报错?
在多 Web 节点挂载 NFS 共享存储的架构中,当主存储故障引发 VIP 漂移至备机时,客户端常发生连接假死或读取报错:
Stale file handle (ESTALE, Error 116)
底层机理剖析
NFS 文件句柄与 Inode 强绑定:
- NFS 客户端访问文件并非每次都通过完整路径寻找,而是在初次打开时通过 RPC 协议获取服务端分配的文件句柄(File Handle)。
- 文件句柄在服务端内核通常由
文件系统设备号 + 文件系统内部 Inode 编号 + 生成计数器计算生成。
文件级同步(rsync / lsyncd)的致命缺陷:
- 即使主备两端文件的目录路径、名称、大小和内容完全一样,只要是在备端操作系统上由不同进程分别创建的,两端的 Inode 编号与文件系统 UUID 必然不同。
- VIP 发生漂移后,客户端继续携带在原主机上缓存的旧句柄向 VIP 发送 I/O 请求,备机内核查验发现该句柄对应的 Inode 在本地文件系统不存在或不匹配,判定为过期作废,直接返回
ESTALE。
核心结论:凡是底层基于“文件级复制”的多节点存储方案,从原理上均无法实现真正的客户端无感 NFS VIP 漂移。
1.2 解决方案对比与技术选型
| 维度 | 方案一:文件同步 + 客户端探测强制重挂 | 方案二:DRBD 块级镜像 + Keepalived 联动(推荐) |
|---|---|---|
| 底层实现 | rsync + lsyncd 定期/增量文件复制 | DRBD 内核模块进行块设备底层扇区级镜像复制 |
| Inode/UUID 一致性 | 不一致(两套独立文件系统) | 严格绝对一致(同一套文件系统的实时镜像) |
| 切换时客户端体验 | 发生卡顿与报错,需依赖定时脚本探测并强制 umount -lf 重挂 | 完全无感,I/O 仅出现短暂网络重试等待,绝不报 ESTALE |
| 架构侵入性 | 需侵入所有客户端配置守护进程或定时任务 | 客户端无需任何特殊脚本,只作为标准 NFS Client |
| 数据同步时延 | 秒级(取决于 lsyncd 触发间隔) | 毫秒级(Protocol C 同步网络确认落盘) |
| 方案定位 | 临时应急/过渡兜底手段 | 企业级标准高可用存储工业方案 |
二、 DRBD 核心运行机制
2.1 架构分层与数据流向
DRBD(Distributed Replicated Block Device)运行在 Linux 内核层,工作在文件系统与本地磁盘驱动之间:
[ 用户态应用 / NFS Server / 挂载点 ]
↓ (标准 VFS 读写系统调用)
[ 文件系统层: XFS / ext4 ]
↓
[ DRBD 虚拟块设备: /dev/drbd0 ]
↙ ↘
[ 本地物理磁盘驱动 ] [ DRBD 网络协议栈 (TCP 7788) ]
(/dev/sdb) ↓ (网络实时传输)
[ 远端主机 DRBD 驱动 ]
↓
[ 远端物理磁盘 (/dev/sdb) ]
2.2 主备角色与操作边界约束
Primary(主节点):
- 拥有完整的读写权限。
- 只有处于 Primary 状态的节点,操作系统才允许格式化与挂载文件系统(如
mount /dev/drbd0 /mnt)。
Secondary(备节点):
- 仅作为只读镜像副本,接收并写入网络同步过来的数据块。
- 严禁挂载文件系统(在非集群文件系统下挂载备端会导致文件系统元数据冲突与严重损坏)。
2.3 三大同步协议(Replication Protocols)
| 协议类型 | 复制机制 | 性能特征 | 适用场景 |
|---|---|---|---|
| Protocol A (异步) | 本地写入磁盘且数据进入本地网络发送队列即返回成功 | 性能最高,网络开销小 | 跨广域网(WAN)远距离容灾,允许丢少量包 |
| Protocol B (半同步) | 本地落盘且网络包到达对端内存缓冲区后返回成功 | 性能与安全适中 | 跨机房组网,主机关机能防丢,但断电仍有风险 |
| Protocol C (完全同步) | 本地磁盘与对端磁盘均确认写入落盘后才返回成功 | 保证零数据丢失,要求局域网质量高 | 高可用存储集群的标准首选协议(本方案采用) |
2.4 底层状态指标速查表
在实时监控状态(/proc/drbd 或 drbdadm status)时,核心参数对应含义如下:
| 参数 | 全称 | 正常健康期望值 | 含义说明 |
|---|---|---|---|
cs | Connection State | Connected | 网络连接状态,表示主备节点间 7788 端口已建立通信 |
ro | Roles | Primary/Secondary | 角色状态,前者为本地节点,后者为对端节点 |
ds | Disk State | UpToDate/UpToDate | 磁盘同步状态,两端均为最新数据,说明已完全对齐 |
ns / nr | Network Send / Receive | 累计计数值 | 节点通过网络发送 / 接收到的数据块数量(KB) |
dw / dr | Disk Write / Read | 写入时持续递增 | 磁盘实际写入 / 读取数据块计数 |
oos | Out of Sync | 0 | 两端未对齐的扇区数据量(为 0 表示无任何滞后) |
三、 集群环境与规划
3.1 节点环境分配
| 节点类型 | 主机名 | 业务/通信 IP | 虚拟 IP (VIP) | 裸盘分区 | DRBD 资源与设备 | 挂载点目录 |
|---|---|---|---|---|---|---|
| 主节点 | nfs | 172.16.1.31 | 172.16.1.35 | /dev/sdb (空闲裸盘) | nfs_data (/dev/drbd0) | /code/wordpress/wp-content/uploads |
| 备节点 | backup | 172.16.1.41 | 172.16.1.35 | /dev/sdb (空闲裸盘) | nfs_data (/dev/drbd0) | /code/wordpress/wp-content/uploads |
| 客户端 | web01/web02 | 172.16.1.7/8 | 访问 VIP | - | - | 远程挂载主备导出的 NFS 路径 |
四、 阶段一:基础环境准备(主备两台均执行)
4.1 安装软件包与内核模块配置
在主节点 nfs 和备节点 backup 分别执行:
# 1. 安装 DRBD 9.0 用户态管理工具与内核模块
yum -y install drbd90-utils kmod-drbd90
# 2. 手动加载内核模块
modprobe drbd
lsmod | grep -i drbd
# 3. 配置开机自动加载模块
echo "drbd" > /etc/modules-load.d/drbd.conf
systemctl enable systemd-modules-load.service
systemctl restart systemd-modules-load.service
# 4. 验证服务状态与模块可用性
systemctl status systemd-modules-load.service --no-pager
4.2 配置主机名与 Hosts 本地解析
DRBD 资源文件中的 on <hostname> 严格匹配系统的真实主机名(uname -n)。
# 1. 确认本机主机名
uname -n
# 2. 编辑 /etc/hosts(主备两台保持完全一致)
cat << 'EOF' >> /etc/hosts
172.16.1.31 nfs
172.16.1.41 backup
EOF
# 3. 清理待使用的裸盘元数据与签名(防止残留旧文件系统信息)
wipefs -a /dev/sdb
五、 阶段二:DRBD 资源配置与初次全量同步
5.1 编写资源定义文件
在主节点和备节点同时创建 /etc/drbd.d/nfs_data.res:
resource nfs_data {
protocol C;
startup {
wfc-timeout 30;
degr-wfc-timeout 15;
}
net {
csums-alg sha1;
# 脑裂(Split-Brain)自动恢复策略
after-sb-0pri discard-younger-primary;
after-sb-1pri discard-secondary;
after-sb-2pri call-pri-lost-after-sb;
}
on nfs {
device /dev/drbd0;
disk /dev/sdb;
address 172.16.1.31:7788;
meta-disk internal;
}
on backup {
device /dev/drbd0;
disk /dev/sdb;
address 172.16.1.41:7788;
meta-disk internal;
}
}
配置项重点说明:
protocol C;:完全同步复制,保障数据安全。meta-disk internal;:将 DRBD 的元数据存放在物理分区的尾部,无需额外划分离线磁盘。after-sb-*:配置脑裂场景下的自动协商规则,避免意外中断后两端彻底失锁。
5.2 初始化元数据与启用资源
主备两台机器分别执行:
# 1. 初始化资源元数据(首次创建时执行)
drbdadm create-md nfs_data
# 2. 启用 DRBD 资源
drbdadm up nfs_data
# 3. 检查当前资源初始状态
drbdadm status nfs_data
此时状态通常显示为 role:Secondary/Secondary,disk:Inconsistent/Inconsistent,表示通信通道已连通,但初始数据尚未对齐。
5.3 指定数据源并执行初次全量同步
由于是首次构建镜像,必须指定以哪台机器的数据为基准。
在 主节点(nfs) 单独执行:
# 强制设为主节点,触发全量初始化同步到备机
drbdadm primary --force nfs_data
在两台节点上观察同步进度:
# 方式 1:通过 drbdadm 命令查看
drbdadm status nfs_data
# 方式 2:高频实时查看内核 /proc 接口(推荐)
watch -n 1 'cat /proc/drbd'
同步过程中的典型输出示例:
0: cs:SyncSource ro:Primary/Secondary ds:UpToDate/Inconsistent C r-----
ns:0 nr:0 dw:0 dr:16636928 al:8 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:4333916
[==============>.....] sync'ed: 79.4% (4232/20476)M
finish: 0:01:47 speed: 40,496 (37,136) K/sec
同步完成后的健康状态:
0: cs:Connected ro:Primary/Secondary ds:UpToDate/UpToDate C r-----
当 ds:UpToDate/UpToDate 且 oos:0 时,证明全量同步彻底完成。
六、 阶段三:文件系统挂载与主备数据一致性核验
6.1 格式化与挂载测试(主节点)
安全红线警告:
格式化与挂载时严禁操作底层裸盘/dev/sdb,必须且只能操作虚拟块设备/dev/drbd0!
在 主节点(nfs) 执行:
# 1. 格式化 DRBD 设备为 XFS
mkfs.xfs -f /dev/drbd0
# 2. 创建本地挂载目录并挂载
mkdir -p /code/wordpress/wp-content/uploads
mount /dev/drbd0 /code/wordpress/wp-content/uploads
# 3. 验证挂载信息
df -hT /code/wordpress/wp-content/uploads
# 4. 写入基线测试数据与大文件
echo "drbd sync test: $(date)" > /code/wordpress/wp-content/uploads/test.txt
dd if=/dev/zero of=/code/wordpress/wp-content/uploads/bigfile.img bs=1M count=50 conv=fdatasync
# 5. 核对写入与 DRBD 计数器变化
cat /proc/drbd
在 /proc/drbd 中可观察到 dw(Disk Write)数值显著增加,表明本地写入已同步向对端落盘。
6.2 手动角色切换验证(离线数据校验)
为验证备机能否完整读出最新数据,进行一次标准的手动切换演练:
步骤 1:主节点(nfs)卸载并降级
# 卸载文件系统
umount /code/wordpress/wp-content/uploads
# 将 DRBD 降级为 Secondary
drbdadm secondary nfs_data
# 检查状态:确认两端均为 Secondary
drbdadm status nfs_data
步骤 2:备节点(backup)升主并挂载
# 备机提升为 Primary
drbdadm primary nfs_data
# 创建挂载点并挂载
mkdir -p /code/wordpress/wp-content/uploads
mount /dev/drbd0 /code/wordpress/wp-content/uploads
# 验证数据完整性
cat /code/wordpress/wp-content/uploads/test.txt
ls -lh /code/wordpress/wp-content/uploads/
确认之前在主节点写入的文本与 50MB 镜像文件完全一致且内容无损。
步骤 3:切回主节点正常工作角色
# 1. backup 节点卸载并降级
umount /code/wordpress/wp-content/uploads
drbdadm secondary nfs_data
# 2. nfs 节点重新升主并挂载
drbdadm primary nfs_data
mount /dev/drbd0 /code/wordpress/wp-content/uploads
七、 阶段四:NFS 服务配置与 Keepalived 联动脚本
7.1 NFS 导出配置与关键 fsid 设置
为了彻底消除 VIP 漂移后的 ESTALE 报错,主备两台机器在导出相同目录时,必须显式指定相同的 fsid 属性。
在主节点 nfs 和备节点 backup 的 /etc/exports 中追加写入:
/code/wordpress/wp-content/uploads *(rw,sync,all_squash,anonuid=666,anongid=666,fsid=10)
核心原则:
fsid=10必须两台一模一样:NFS 协议依赖fsid识别文件系统实例。固定该参数后,VIP 漂移至备机时,客户端 RPC 请求仍能识别为同一文件系统句柄。- 严禁将 NFS 设为开机自启(
systemctl disable nfs-server):备节点在 DRBD 未升主且底层分区未挂载时,绝不可提前启动 NFS,其启停必须完全由 Keepalived 状态机托管。
7.2 编写 Keepalived 联动升主降级脚本
在 主备两台机器 上创建脚本 /etc/keepalived/nfs_notify.sh:
#!/bin/bash
# -------------------------------------------------------------
# Keepalived 状态漂移通知脚本 (DRBD + NFS 联动管理)
# -------------------------------------------------------------
TYPE=$1
NAME=$2
STATE=$3
MOUNT_DIR="/code/wordpress/wp-content/uploads"
RESOURCE="nfs_data"
DRBD_DEV="/dev/drbd0"
case $STATE in
"MASTER")
# 1. 尝试提升 DRBD 为 Primary
drbdadm primary $RESOURCE
if [ $? -ne 0 ]; then
logger -t keepalived-drbd "警告: DRBD 常规升主失败,尝试强制提升 primary --force"
drbdadm primary --force $RESOURCE
fi
# 2. 检查并挂载文件系统
mkdir -p $MOUNT_DIR
mountpoint -q $MOUNT_DIR || mount $DRBD_DEV $MOUNT_DIR
# 3. 启动 NFS 服务并生效导出规则
systemctl start nfs-server
exportfs -arv
logger -t keepalived-drbd "成功提升为 MASTER: DRBD 挂载完成,NFS 服务已启动"
exit 0
;;
"BACKUP"|"FAULT")
# 1. 立即清除 NFS 导出并停止服务
exportfs -uav
systemctl stop nfs-server
# 2. 卸载磁盘(强制 + 延迟卸载,防止有残留句柄阻塞降级)
if mountpoint -q $MOUNT_DIR; then
umount -lf $MOUNT_DIR
fi
# 3. 将 DRBD 降级为 Secondary
drbdadm secondary $RESOURCE
logger -t keepalived-drbd "已降级为 $STATE: NFS 已停止,DRBD 已卸载并降级"
exit 0
;;
*)
logger -t keepalived-drbd "接收到未知状态: $STATE"
exit 1
;;
esac
赋予执行权限:
chmod +x /etc/keepalived/nfs_notify.sh7.3 配置 Keepalived
编辑两台机器的 /etc/keepalived/keepalived.conf:
主节点(nfs)配置
# ! Configuration File for keepalived (Master)
vrrp_instance VI_NFS {
state MASTER
interface ens36 # 替换为实际业务网卡名称
virtual_router_id 51
priority 100 # 主节点优先级高于备节点
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
172.16.1.35/24 dev ens36
}
# 状态切换钩子脚本
notify /etc/keepalived/nfs_notify.sh
}
备节点(backup)配置
# ! Configuration File for keepalived (Backup)
vrrp_instance VI_NFS {
state BACKUP
interface ens36 # 替换为实际业务网卡名称
virtual_router_id 51
priority 90 # 备节点优先级低于主节点
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
172.16.1.35/24 dev ens36
}
# 状态切换钩子脚本
notify /etc/keepalived/nfs_notify.sh
}
八、 阶段五:集群集成验证与高可用故障演练
8.1 集群服务启动与初始健康检查
# 1. 主节点启动 Keepalived
systemctl restart keepalived
# 2. 备节点启动 Keepalived
systemctl restart keepalived
# 3. 在主节点确认资源是否已自动拉起
ip a show dev ens36 # 查看 VIP 是否绑定成功
df -hT /code/wordpress/wp-content/uploads # 查看 DRBD 是否自动挂载
systemctl status nfs-server # 查看 NFS 服务是否正常运行
drbdadm status nfs_data # 确认主节点角色为 Primary
# 4. 在备节点确认状态保持待命
ip a show dev ens36 # 此时无 VIP
mountpoint -q /code/wordpress/wp-content/uploads || echo "未挂载(正常)"
drbdadm status nfs_data # 备节点角色保持 Secondary
8.2 Web 客户端挂载与写入验证
在客户端机器(如 web01 / web02)执行挂载:
# 1. 创建本地挂载目录
mkdir -p /var/www/html/uploads
# 2. 挂载到高可用 VIP
mount -t nfs -o vers=4,proto=tcp,hard,intr 172.16.1.35:/code/wordpress/wp-content/uploads /code/wordpress/wp-content/uploads
# 3. 验证访问
ls -lh /code/wordpress/wp-content/uploads
echo "client write test: $(date)" >> /code/wordpress/wp-content/uploads/test.txt
8.3 模拟主机宕机与无感漂移演练
演练步骤
持续 I/O 监听:
在客户端开启长循环写入并读取状态:while true; do date >> /var/www/html/uploads/test.txt; sleep 1; done切断主节点:
在主节点直接停止 Keepalived(或直接关机):systemctl stop keepalived观察备节点接管行为:
- 查看备节点系统日志:
journalctl -u keepalived -n 50 --no-pager - 备机 VIP 自动绑定:
ip a show dev ens36 - DRBD 自动升主:
drbdadm status nfs_data变为Primary - 挂载点与服务自动启动:
df -h出现挂载,systemctl status nfs-server正常
- 查看备节点系统日志:
客户端验证现象:
- 客户端长循环在漂移的 2\~3 秒内出现短暂挂起重试(TCP 自动重连)。
- VIP 漂移完成后,写入立即恢复继续,绝对不报
Stale file handle错误,实现了真正的生产级高可用无缝切换。
九、 生产运维避坑与高频问题指南
9.1 脑裂(Split-Brain)处理方案
若双机因网络中断同时升为 Primary 并分别产生写入,DRBD 会断开连接以保护数据一致性(状态显示为 StandAlone)。
手动仲裁解决流程:
确定丢弃哪端数据(以备端丢弃为例):
在备节点放弃自身修改,转为同步接收者:drbdadm disconnect nfs_data drbdadm secondary nfs_data drbdadm connect --discard-my-data nfs_data在保留数据的主节点重新连接:
drbdadm disconnect nfs_data drbdadm connect nfs_data
9.2 卸载失败 device is busy 的应急规避
在 Keepalived 降级时,如果有孤儿进程正在读取挂载点,普通 umount 会报错退出。通知脚本中必须采用 umount -lf $MOUNT_DIR(Lazy 延迟卸载结合 Force 强制脱钩),确保 DRBD 降级操作不被阻塞。
9.3 物理设备与逻辑设备操作禁忌
- 永远不要在物理盘
/dev/sdb上执行mkfs或直接mount,任何绕过/dev/drbd0虚拟层的写入都会破坏同步位图,直接导致元数据错乱和数据丢失。 - 如果备节点开机自启了 NFS 服务,会因找不到底层存储而引发 RPC 注册混乱,因此主备两端的
nfs-server必须完全交由 Keepalived 的notify脚本按需启停。
底层机制决定协议表现:
lsyncd + rsync只是文件级别复制,备机inode完全变了,NFS 协议天生无法漂移。- DRBD 是块级镜像(Block-level replication),扇区、文件系统超级块和
inode完全对齐,这是无缝热备漂移的物理基石。
fsid的决定性作用:- 即使底层用了 DRBD,Linux NFS 默认仍会根据不同宿主机的内核设备号在内存中动态计算句柄。
- 显式指定完全相同的
fsid,强行锁死了客户端识别服务端文件系统的“身份证”,这才彻底消除漂移时的Stale file handle。
客户端抗抖动防护:
- 客户端采用
hard,intr以及vers=3,nolock挂载,能在 Keepalived 切换的 2~3 秒静默重试,避免 systemd 或用户态进程报 I/O 错误或触发异常自动卸载。
- 客户端采用
评论