| 主机 | 角色 | 管理 IP | iSCSI Portal 1 | iSCSI Portal 2 |
|---|---|---|---|---|
| storage-a | iSCSI Target | 10.123.6.128/24 | 10.10.40.250/16 | 192.168.200.250/24 |
| hanode1 | Pacemaker Node 1 | 10.123.6.222/24 | 10.10.40.241/16 | 192.168.200.241/24 |
| hanode2 | Pacemaker Node 2 | 10.123.6.170/24 | 10.10.40.242/16 | 192.168.200.242/24 |
统一凭据: root / redhat123 (实验环境,VM 集群)
VIP 资源 (HA): 10.10.40.100/16 (在管理网卡 ens34 上)
| 分区 | 设备 | 容量 | 用途 |
|---|---|---|---|
| 1 | /dev/sda1 | 20 GB | SBD 设备 (二节点共享, fence) |
| 2 | /dev/sda2 | 40 GB | share1 (备用) |
| 3 | /dev/sda3 | 60 GB | share2 (HA web 服务) → 9.2 web_fs 挂到 /var/www/html(对齐 httpd 默认 DocumentRoot) |
| 4 | /dev/sda4 | 60 GB | share3 (备用) |
/dev/sda,系统盘 = /dev/sdb)。
网卡命名也以实际 VM 状态为准 (ens33/ens34 等)。
前置: storage-a 上 /dev/sda 是新加的 200G 空盘。
parted -s /dev/sda mklabel gpt
parted -s /dev/sda mkpart primary xfs 0% 10% # 20G
parted -s /dev/sda mkpart primary xfs 10% 30% # 40G
parted -s /dev/sda mkpart primary xfs 30% 60% # 60G
parted -s /dev/sda mkpart primary xfs 60% 90% # 60G
# 留 10% free 给未来扩容
partprobe /dev/sda
lsblk /dev/sda
parted -s /dev/sda print
输出:
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 200G 0 disk
├─sda1 8:1 0 20G 0 part
├─sda2 8:2 0 40G 0 part
├─sda3 8:3 0 60G 0 part
└─sda4 8:4 0 60G 0 part
Number Start End Size File system Name Flags
1 1049kB 21.5GB 21.5GB primary
2 21.5GB 64.4GB 42.9GB primary
3 64.4GB 129GB 64.4GB primary
4 129GB 193GB 64.4GB primary
# 1. 启动 target 服务
systemctl enable --now target
# 2. 创建 4 个 block backstore
targetcli /backstores/block create sda1_block dev=/dev/sda1
targetcli /backstores/block create sda2_block dev=/dev/sda2
targetcli /backstores/block create sda3_block dev=/dev/sda3
targetcli /backstores/block create sda4_block dev=/dev/sda4
# 3. 建 target IQN
targetcli /iscsi create iqn.2026-08.com.example:storage-a.target0
TARGET=iqn.2026-08.com.example:storage-a.target0
# 4. 配 target 属性
targetcli /iscsi/$TARGET/tpg1 set attribute generate_node_acls=1
targetcli /iscsi/$TARGET/tpg1 set attribute demo_mode_write_protect=0
# 5. 配双 portal (先删默认 0.0.0.0)
targetcli /iscsi/$TARGET/tpg1/portals delete 0.0.0.0 ip_port=3260
targetcli /iscsi/$TARGET/tpg1/portals create 10.10.40.250 ip_port=3260
targetcli /iscsi/$TARGET/tpg1/portals create 192.168.200.250 ip_port=3260
# 6. 在 hanode1 + hanode2 启动 iscsid 后拿到 IQN,加 ACL
INI1=$(ssh root@10.10.40.241 'cat /etc/iscsi/initiatorname.iscsi' | grep InitiatorName | cut -d= -f2)
INI2=$(ssh root@10.10.40.242 'cat /etc/iscsi/initiatorname.iscsi' | grep InitiatorName | cut -d= -f2)
targetcli /iscsi/$TARGET/tpg1/acls create $INI1
targetcli /iscsi/$TARGET/tpg1/acls create $INI2
# 7. 创建 4 LUN
targetcli /iscsi/$TARGET/tpg1/luns create /backstores/block/sda1_block lun=01
targetcli /iscsi/$TARGET/tpg1/luns create /backstores/block/sda2_block lun=02
targetcli /iscsi/$TARGET/tpg1/luns create /backstores/block/sda3_block lun=03
targetcli /iscsi/$TARGET/tpg1/luns create /backstores/block/sda4_block lun=04
# 8. 持久化
targetcli saveconfig
# 9. 验证
ss -tnlp | grep 3260
输出:
LISTEN 0 256 10.10.40.250:3260 0.0.0.0:*
LISTEN 0 256 192.168.200.250:3260 0.0.0.0:*
在 hanode1 和 hanode2 都执行 (脚本相同):
systemctl enable --now iscsid
iscsiadm -m discovery -t sendtargets -p 10.10.40.250:3260
iscsiadm -m discovery -t sendtargets -p 192.168.200.250:3260
iscsiadm -m node -l
iscsiadm -m session
# 验证 (应见 2 条 session,8 个 sd 设备)
lsblk
输出 (hanode1):
tcp: [1] 10.10.40.250:3260,1 iqn.2026-08.com.example:storage-a.target0 (non-flash)
tcp: [2] 192.168.200.250:3260,1 iqn.2026-08.com.example:storage-a.target0 (non-flash)
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sdb 8:16 0 60G 0 disk ← LUN04 path1
sdc 8:32 0 60G 0 disk ← LUN04 path2
sdd 8:48 0 60G 0 disk ← LUN03 path1
sde 8:64 0 60G 0 disk ← LUN03 path2
sdg 8:96 0 40G 0 disk ← LUN02 path1
sdh 8:112 0 20G 0 disk ← LUN01 path1
sdi 8:128 0 20G 0 disk ← LUN01 path2
/dev/mapper/share2 这个名字永远不变。UUID= 的更好方案:
UUID=: mkfs 后变(业务上 mkfs 是稀有的,但仍要管)user_friendly_names no(让 alias 生效,不然 mpath{a,b,c} 覆盖)
cat > /etc/multipath.conf << 'EOF'
defaults {
user_friendly_names no # ★ 必须 no,让 multipaths { alias } 生效
find_multipaths yes
polling_interval 5
path_selector "service-time 0"
path_grouping_policy multibus
path_checker tur
failback immediate
no_path_retry 5
checker_timeout 5
fast_io_fail_tmo 5
}
blacklist {
devnode "^sda[0-9]*" # 排除本地系统盘
}
# ★ multipath alias:把 WWID 绑到固定别名
# 两节点跑同样这段,share2 在两边都是 /dev/mapper/share2
# WWID 怎么拿: multipath -ll 第一行的 (3600...)
multipaths {
multipath {
wwid "360014052959d472cc0f436fbf4ef5cb2"
alias sbd # 20G, 留给 SBD fence
}
multipath {
wwid "36001405f7875d00fa7d4531a2294e5b2"
alias share1 # 40G, 备用
}
multipath {
wwid "36001405d194710902554d799a2b7fbf9"
alias share2 # 60G, HA web 服务用
}
multipath {
wwid "360014054486a9d228904c179e03e0e30"
alias share3 # 60G, 备用
}
}
EOF
systemctl enable --now multipathd
multipathd reconfigure
# 验证:应见 /dev/mapper/sbd / share1 / share2 / share3
ls -la /dev/mapper/sbd /dev/mapper/share1 /dev/mapper/share2 /dev/mapper/share3
multipath -ll
输出(实测 2026-08-13):
lrwxrwxrwx 1 root root 7 Aug 13 17:16 sbd -> ../dm-0
lrwxrwxrwx 1 root root 7 Aug 13 17:16 share1 -> ../dm-1
lrwxrwxrwx 1 root root 7 Aug 13 17:16 share2 -> ../dm-2
lrwxrwxrwx 1 root root 7 Aug 13 17:16 share3 -> ../dm-3
share2 (36001405d194710902554d799a2b7fbf9) dm-2 LIO-ORG,sdb3_block
size=60G features='1 queue_if_no_path' hwhandler='1 alua' wp=rw
`-+- policy='service-time 0' prio=50 status=active
|- 33:0:0:3 sdg 8:96 active ready running
`- 34:0:0:3 sdf 8:80 active ready running
★ WWID 怎么拿? 第一行 Stage 4 跑完后 multipath -ll,每个 mpath 设备的第一行就是 WWID((36001405xxx) 那个长 hex 串)。把 4 个 WWID 复制到这个 conf 里。
注意: user_friendly_names yes 会覆盖 alias(走 mpath{a,b,c}),必须 no。
⚠️ 2026-08-13 reboot 后 mpathX 名字会变(为什么 Stage 5 不能 hardcode mpath 名字):
mpatha=20G(SBD), mpathe=60G(share2), mpathf=60G(share3), mpathg=40G(share1)★ 解决方案(也在 Stage 4): 用 multipaths { multipath { wwid X alias sbd/share1/share2/share3 } } 锁死设备名,从此 /dev/mapper/share2 永久不变。
✅ 4 个 mpath 设备,各 2 active path。mpatha (20G) 留给 SBD,不格式化(/dev/mapper/sbd 永远叫这个)。
/dev/mapper/sbd — 永远叫这个/dev/mapper/share1 — 永远叫这个/dev/mapper/share2 — 永远叫这个/dev/mapper/share3 — 永远叫这个# /tmp/stage5-fixed.sh (推送脚本,推荐版本)
# 因为 multipath alias 锁死了设备名,直接用就行
set -e
# 1. 格式化 3 个 share(用 alias 名,稳定)
mkfs -t xfs -f -L share1 /dev/mapper/share1
mkfs -t xfs -f -L share2 /dev/mapper/share2
mkfs -t xfs -f -L share3 /dev/mapper/share3
# /dev/mapper/sbd 不格式化,留给 SBD
# 2. 建挂载点
mkdir -p /share1 /share2 /share3
# 3. 挂载
mount /dev/mapper/share1 /share1
mount /dev/mapper/share2 /share2
mount /dev/mapper/share3 /share3
# 4. fstab 写 UUID(reboot 自动挂)
M1=$(blkid -s UUID -o value /dev/mapper/share1)
M2=$(blkid -s UUID -o value /dev/mapper/share2)
M3=$(blkid -s UUID -o value /dev/mapper/share3)
cat >> /etc/fstab <<EOF
# iSCSI multipath shares (Stage 4 alias 锁死设备名;UUID 来自 mkfs)
UUID=$M1 /share1 xfs _netdev,defaults 0 0
UUID=$M2 /share2 xfs _netdev,defaults 0 0
UUID=$M3 /share3 xfs _netdev,defaults 0 0
EOF
# 5. 验证
df -hT /share1 /share2 /share3
echo "test_share1" > /share1/test.txt
echo "test_share3" > /share3/test.txt
ls -la /share1/test.txt /share3/test.txt
# ★ 在 hanode2 跑同样脚本 ★
bash /tmp/stage5-fixed.sh
# 验证:
df -hT /share1 /share2 /share3
# 应见 share1/2/3 都挂上
# ★ 在 hanode2 跑(手动,跳过 share2 mount) ★
mkfs -t xfs -f -L share1 /dev/mapper/share1
mkfs -t xfs -f -L share2 /dev/mapper/share2
mkfs -t xfs -f -L share3 /dev/mapper/share3
mkdir -p /share1 /share3
mount /dev/mapper/share1 /share1
mount /dev/mapper/share3 /share3
# share2 不挂,留给 Pacemaker group 控制 (Stage 8)
# 验证:share1 + share3 挂上,share2 没挂
df -hT /share1 /share3
echo "test_share1" > /share1/test.txt && echo OK
echo "test_share3" > /share3/test.txt && echo OK
# ★ 两节点都跑 ★
dnf install -y --nogpgcheck pcs pacemaker pacemaker-cli pacemaker-cluster-libs \
pacemaker-remote corosync corosynclib resource-agents \
sbd fence-agents-sbd httpd
# ★ 两节点都跑(用同一密码)★
systemctl enable --now pcsd
echo "hacluster:Redh@t2026!" | chpasswd
# ★ 两节点都跑 ★
grep -E "hanode1|hanode2" /etc/hosts || {
echo "10.10.40.241 hanode1" >> /etc/hosts
echo "10.10.40.242 hanode2" >> /etc/hosts
}
# ★ 在 hanode1 跑 ★
pcs host auth hanode1 hanode2 -u hacluster -p "Redh@t2026!"
pcs cluster setup ha_cluster hanode1 hanode2 --start
pcs cluster enable --all
pcs status
输出:
Cluster name: ha_cluster
* Stack: corosync (Pacemaker is running)
* Current DC: hanode1 (version 3.0.1-5.el10_2.1-ff52770) - partition with quorum
* 2 nodes configured
modprobe softdog
ls -la /dev/watchdog* # /dev/watchdog + /dev/watchdog0 都应存在
# ★ 在 hanode1 上(用 Stage 4 multipath alias,稳定设备名)
sbd -d /dev/mapper/sbd create
sbd -d /dev/mapper/sbd dump
# 在 hanode2 上验证(也用 alias)
sbd -d /dev/mapper/sbd dump
# UUID 必须跟 hanode1 一致!
# ★ 在 hanode1 跑 ★(或任一节点都行)
# ★ 用 alias 设备名 /dev/mapper/sbd (Stage 4 锁死的名字)
pcs stonith create sbd_fence fence_sbd devices=/dev/mapper/sbd
# 注意: 不要加 meta provides=unfencing (fence_sbd 不支持 unfencing mode)
pcs status
验证输出:
* sbd_fence (stonith:fence_sbd): Started hanode1
share2 在 Stage 5 已经格式化 xfs(Label=share2),这一节只是放一个测试 html 文件进去。不需要再格式化(share2 是干净状态)。
说明:web_fs 配的是 directory=/var/www/html(跟 httpd 默认 DocumentRoot 对齐),所以 html 直接放 share2 的 /html/ 子目录。我们 mount 到任意临时位置(不要 mount 到 /var/www,会被 web_fs 之后覆盖)。
# ★ 在 hanode1 跑 ★(一次性初始化)
# 集群还没有启 web_group,所以手动 mount + 写文件 + umount
# ★ 因为 Stage 4 的 multipath alias,/dev/mapper/share2 是稳定的设备名
mount /dev/mapper/share2 /mnt
mkdir -p /mnt/html
cat > /mnt/html/index.html <<EOF
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>cluster HA</title></head>
<body><h1>你好,这里是 cluster HA</h1></body>
</html>
EOF
umount /mnt
echo "html 内容已写好(share2/html/index.html)"
说明: 9.2 创建 web_group 后,Pacemaker 会自动 mount share2 到 /var/www/html(因为 web_fs 配的是 directory=/var/www/html),你访问 VIP 10.10.40.100 就能看到这个页面。
3 个资源(vip + fs + server)放一个 group,保证同时启动/迁移。
★ 关键:用 multipath alias 锁死的设备名(Stage 4)
web_fs device="/dev/mapper/share2" — Stage 4 的 multipath alias 让这个文件名永久稳定web_fs directory="/var/www/html" — 跟 httpd 默认 DocumentRoot 对齐,不用改 httpd.conf# ★ 在 hanode1 跑 ★(或任一节点都行)
# 1. web_vip(10.10.40.100 这个 VIP 在 10.10.40.0/16 网段里)
pcs resource create web_vip ocf:heartbeat:IPaddr2 \
ip=10.10.40.100 cidr_netmask=16 nic=ens34 \
op monitor interval=10s
# 2. web_fs(multipath alias 锁死的 share2 设备)
pcs resource create web_fs ocf:heartbeat:Filesystem \
device="/dev/mapper/share2" fstype=xfs \
directory="/var/www/html" options="_netdev" \
op monitor interval=20s
# 3. web_server(httpd)
pcs resource create web_server ocf:heartbeat:apache \
configfile=/etc/httpd/conf/httpd.conf \
op monitor interval=30s
# 4. 把 3 个资源加入 group (启动顺序 vip → fs → server,保证 fs 挂好才启 httpd)
pcs resource group add web_group web_vip web_fs web_server
# 5. 清失败历史(如果之前尝试有错)
pcs resource cleanup web_fs
实测确认 mount /dev/mapper/share2(multipath alias)永久稳定(2026-08-13):
# alias 锁死设备名 — /dev/mapper/share2 是 LUN03 的固定别名
# 多路径重建后 /dev/mapper/mpathX 会变,但 /dev/mapper/share2 永远不变
mkdir -p /mnt_share2_test
mount /dev/mapper/share2 /mnt_share2_test
df -hT /mnt_share2_test
# 输出:
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/mapper/share2 xfs 60G 1.2G 59G 2% /mnt_share2_test
umount /mnt_share2_test
为什么 alias 比 UUID 更好?
UUID=: 每次 mkfs 后变,运维要记 UUID9.2 用 device="/dev/mapper/share2" 就是这个原因 — 不需要 UUID,直接用 alias 名。
pcs status
输出:
Cluster name: ha_cluster
* Stack: corosync (Pacemaker is running)
* Current DC: hanode1
Full List of Resources:
* sbd_fence (stonith:fence_sbd): Started hanode1
* Resource Group: web_group:
* web_vip (ocf:heartbeat:IPaddr2): Started hanode2
* web_fs (ocf:heartbeat:Filesystem): Started hanode2
* web_server (ocf:heartbeat:apache): Started hanode2
用 VIP 访问 web 服务:
curl -s http://10.10.40.100/
输出:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>cluster HA</title>
</head>
<body>
<h1>你好,这里是 cluster HA</h1>
</body>
</html>
# ★ 在 hanode1 跑 ★
pcs node standby hanode2
sleep 8
pcs status
输出:
Node List:
* Node hanode2: standby
* Online: [ hanode1 ]
Full List of Resources:
* sbd_fence (stonith:fence_sbd): Started hanode1
* Resource Group: web_group:
* web_vip Started hanode1
* web_fs Started hanode1
* web_server Started hanode1
VIP 仍能访问:
curl -s http://10.10.40.100/ | grep h1
<h1>你好,这里是 cluster HA</h1>
pcs node unstandby hanode2
sleep 5
pcs status
# 看 fence_sbd agent 元数据
fence_sbd --action=metadata | head -20
# 模拟 pacemaker 调用 (不真 fence,只是 status)
# ★ 用 alias 设备名 /dev/mapper/sbd (Stage 4 锁死)
fence_sbd --devices=/dev/mapper/sbd --plug=hanode1 --action=status --verbose
# cluster 视角的 SBD 状态
pcs stonith sbd status
# hanode1: YES | YES | YES
# hanode2: YES | YES | YES
# 1. 在 hanode2 上 kill corosync (模拟故障,★ 此命令在 hanode2 上 ★)
ssh hanode2 'systemctl kill corosync'
# 2. 等待 ~3 分钟,观察
# - hanode1 会被 SBD 触发 reboot
# - watch log 看到 cluster status 断掉
| 阶段 | 现象 | 含义 |
|---|---|---|
| T+0 | kill hanode2 corosync | 触发模拟故障 |
| T+30s | hanode1 上 pcs status 仍正常 | corosync partition, hanode1 仍 quorum |
| T+3min | hanode1 系统被 reboot | SBD fence 真的工作, watchdog timeout 触发 |
| boot 后 | uptime 报 "up 1 min", ssh 报 "System is booting up" | 确认 reboot 来自 SBD 触发 |
# 1. 两节点重启后,/dev/watchdog 可能不存在(softdog 没自动加载)
# 解决: modprobe softdog + 加 /etc/modules-load.d/softdog.conf 持久化
# 2. 启动 cluster
pcs cluster start --all
# 3. 清 SBD slot 残留 message + 清 cluster 失败历史
pcs stonith history cleanup
pcs resource cleanup
# 4. 验证
pcs status
# 应见 2 nodes Online,所有 resource Started
症状: 节点被 SBD fence reboot 后再次启动,watchdog 设备不存在,sbd daemon exit code 1。
修复:
# 一次性持久化
echo "softdog" > /etc/modules-load.d/softdog.conf
modprobe softdog
症状: cluster 重启后所有节点 UNCLEAN,resource 不启动。
修复: pcs stonith history cleanup + pcs resource cleanup。
症状: 看到节点 reboot 而非只是 fencing 流程,应用层中断。
说明: SBD 触发 watchdog 是为了 绝对保证节点不再写共享盘。这是设计行为,生产环境会用 BMC/IPMI fence 替代(更优雅)。VM 实验环境接受 reboot 是正常的。
症状: 在 hanode1 上 echo c > /proc/sysrq-trigger 触发 kernel panic + kdump reboot,hanode2 在同一时刻也 reboot。
真实根因(2026-08-10 实战):
/var/crash/127.0.0.1-2026-08-10-15:21:35/vmcore 捕获)pcs stonith history: 0 events),/var/crash/ 目录空(没有 panic)关键诊断点:
# 验证 SBD 没触发 fence
pcs stonith history # 应见 "0 events found"
ls /var/crash/ # 只有触发了 panic 的节点会生成 vmcore
ls -la /var/crash/127.0.0.1-* # 看时间戳确认谁真的 panic 了
教训: SysRq-c 这类 暴力测试在 VM 实验环境 + shared iSCSI 上可能产生 不可预期的连带 reboot。生产环境务必 BMC fence 代替 watchdog 直接 reboot,避免双机连环掉。
症状: 三台 VM 都 reboot 后,hanode1+2 上 iSCSI 设备"消失了"(lsblk 只看到 1MB mpatha,4 个 share LUN 都不见)。
根因(2 层):
第 1 层 — storage-a 端 VM 磁盘枚举变了:
重启前: /dev/sda = 200G 空盘 /dev/sdb = 80G 系统盘
重启后: /dev/sda = 80G 系统盘 /dev/sdb = 200G 空盘
↑ VM 磁盘枚举变了!
targetcli 的 backstore 之前指向 /dev/sda1-4
↓ 现在 /dev/sda1 是 1MB 系统盘杂凑
↓ backstore fail
↓ LUN 自动 marked failed
↓ ACL mapping 还显示 lun1-4,但实际只有 1MB 的 lun1 能用
第 2 层 — hanode 端 iscsiadm 默认不 rescan LUN:
reboot 后 iSCSI sessions 在(2 条)
但 lsblk 只看到 sdb/sdc(都是 1MB = mpatha)
因为 storage-a 上 lun2/3/4 失效了,iSCSI 看不到
修复(完整脚本,一行跑完):
# === 在 storage-a 上 ===
TARGET=iqn.2026-08.com.example:storage-a.target0
targetcli /iscsi/$TARGET/tpg1/luns delete lun=1 lun=2 lun=3 lun=4
targetcli /backstores/block delete sda1_block sda2_block sda3_block sda4_block
targetcli /backstores/block create sdb1_block dev=/dev/sdb1
targetcli /backstores/block create sdb2_block dev=/dev/sdb2
targetcli /backstores/block create sdb3_block dev=/dev/sdb3
targetcli /backstores/block create sdb4_block dev=/dev/sdb4
targetcli /iscsi/$TARGET/tpg1/luns create /backstores/block/sdb1_block lun=01
targetcli /iscsi/$TARGET/tpg1/luns create /backstores/block/sdb2_block lun=02
targetcli /iscsi/$TARGET/tpg1/luns create /backstores/block/sdb3_block lun=03
targetcli /iscsi/$TARGET/tpg1/luns create /backstores/block/sdb4_block lun=04
targetcli saveconfig
# === 在 hanode1 + hanode2 上 ===
iscsiadm -m discovery -t sendtargets -p 10.10.40.250:3260
iscsiadm -m discovery -t sendtargets -p 192.168.200.250:3260
iscsiadm -m node -l
for host in /sys/class/scsi_host/host*/scan; do echo "- - -" > $host; done
sleep 3
multipathd reconfigure
multipath -ll # 验证看到 4 个 mpath
教训 — 这次最重要的发现:
/dev/sdaN),改用:/dev/disk/by-id/wwn-*(基于 LUN WWN)/dev/disk/by-path/*(基于物理路径)iscsiadm -m node -l + echo "- - -" > /sys/class/scsi_host/host*/scan/usr/local/bin/iscsi-rescan.sh,reboot 后跑一次就恢复。症状: 用固定设备名(如 mpathb)写脚本时,reboot 后 mpath 名字变了(从 mpathb 变成 mpathe/f/g),脚本报 No such file or directory。
原因:
mpathN 名字是按发现顺序 + WWID 哈希分配修复: 脚本里 用 UUID 或按 size 找设备,不依赖 mpath 名:
# 按 size 找 share1(40G):
SHARE1=$(for m in /dev/mapper/mpath*; do
sz=$(blockdev --getsize64 "$m")
# 40G ≈ 42949672960 bytes
if [ "$sz" -ge 42949672960 ] && [ "$sz" -lt 50000000000 ]; then
echo "$m"; break
fi
done)
# 写 fstab 时也用 UUID,不写 /dev/mapper/mpathX:
M1=$(blkid -s UUID -o value "$SHARE1")
echo "UUID=$M1 /share1 xfs _netdev,defaults 0 0" >> /etc/fstab
教训: 配置 iSCSI 时,fstab、multipath.conf、mount 命令都永远用 UUID 或 LABEL,不写裸设备名。/dev/mapper/mpathN 是 unstable 名字。/dev/disk/by-uuid/* 才是 stable。
08:59:14.000 corosync on hanode2: KNET link host:1 down
08:59:15.000 corosync on hanode2: Token timeout 2250ms
08:59:19.897 corosync: hanode1 left via cluster exit
08:59:20.905 schedulerd: "hanode1 will be fenced: peer is no longer part of the cluster"
08:59:20.906 schedulerd: "Fence (reboot) hanode1 'peer is no longer part of the cluster'"
08:59:20.911 fenced: "Requesting peer fencing (reboot) targeting hanode1"
08:59:21.232 fenced: "Couldn't find anyone to fence (reboot) hanode1 using any device"
08:59:21.232 fenced: "No fence device" (sbd_fence 在 hanode1,但 hanode1 失联)
08:59:58 hanode2 kernel boot (reboot)
| # | 组件 | 判断"失败"? | 做什么 |
|---|---|---|---|
| 1 | corosync | ✅ 是 | 节点间发 token,token timeout = 节点失联(2.25s) |
| 2 | pacemaker-schedulerd | ✅ 是 | 看到 corosync "lost" → 决策"Fence (reboot)"(5s grace) |
| 3 | pacemaker-fenced | ❌ 否 | 调 STONITH 资源("Couldn't find anyone") |
| 4 | sbd_fence (STONITH 资源) | ❌ 否 | 在共享盘写"reset" message |
| 5 | SBD daemon on each node | ✅ 执行 | 读共享盘 → 触发本地 watchdog 跳 |
不是 Pacemaker 主动命令(它执行失败)。是 SBD 自身的 watchdog 机制:hanode1 的 SBD 看到共享盘断写 → 跳;hanode2 的 SBD 误判共享盘变化 → 也跳。SBD 假设每个节点独立判断,但 2Node + shared storage 写盘动作被两个 daemon 同时读到,所以 30s grace period 没用。
SBD_TIMEOUT_ACTION=off 关闭 SBD 自带 reboot 路径(只 sync 不 reset),但 SBD 退化为 heartbeat-onlystonith-watchdog-timeout=30 让 Pacemaker 走自己的 grace periodfence_vmware_soap 走 ESXi API


function pacemaker_evaluate_node(node):
# 第 1 步: 我自己健康吗?(本地检查)
if not my_processes_alive():
return "I'm sick, don't fence" # 怕自己误判
# 第 2 步: corosync 说对方 status 是什么?
cs_status = corosync.get_node_status(node)
if cs_status == "alive":
return "对方活着" # 没事
# 第 3 步: 我有 quorum 吗?
if not has_quorum():
return "我没 quorum, 自己也可能已隔离" # 不 fence
# 第 4 步: 5s grace 过了吗?
if grace_period_not_elapsed(node, 5):
return "5s 内可能恢复,等"
return "Fence (reboot) {node}" # 4 步全满足 + grace 过了
| 阶段 | 理论预期 | 实测结果 | 符合? |
|---|---|---|---|
| 第 1 步:本地健康 | hanode2 进程都在 | corosync/pacemaker/sbd/multipathd/iscsid 全 active | ✅ |
| 第 2 步:corosync token 收不到 | 5s 内触发 "Token timeout" | T+5s 触发 | ✅ |
| 第 3 步:quorum 在 | 2 中 1,仍是 majority | nodes=1, votes=1, retained | ✅ |
| 第 4 步:5s grace | grace 过了仍 unclean → fence | T+6.905s 决策 fence | ✅ |
| Pacemaker fence 命令执行 | sbd_fence 收到命令写共享盘 | "No fence device" 失败 | ❌(没找到 STONITH) |
| 真正让 hanode1 reboot | 应通过 Pacemaker 命令 | SBD 自己 watchdog 跳 | ⚠️ 设计降级 |
| hanode2 行为 | 不 fence 对方(本机无关) | 也 reboot(SBD 误判) | ❌(VM + 共享盘特殊问题) |
症状: 两节点分别跑 sbd create 会产生不同 UUID,后续 SBD 服务起不来。
修复: 只在 1 个节点 create 一次,另一个节点 dump 验证 UUID 一致。
症状: systemctl start sbd 报 "Operation refused"。
修复: EL10 上 sbd.service 是 pacemaker/corosync 的 requires dep,只能通过 pacemaker/corosync 自动 pull up,不能手动启停。
症状: 加 meta provides=unfencing 后 "unfencing failed"。
修复: fence_sbd 不实现 unfencing 协议,必须不加这个 meta。
症状: web_vip 跑到 hanode2, web_fs 跑到 hanode1, 互不约束 → web 服务不通。
修复: 3 个资源加入同一 pcs resource group add web_group,保证同时在同一节点启动。
症状: share2 mount 时报 "fsconfig system call failed: Structure needs cleaning"。
根因: 早先手动 mount/umount 太急,metadata 写盘没 sync 完整。
修复: 重新 mkfs -t xfs -f (本次数据可丢) 或者 xfs_repair。
症状: pcs cluster setup ha_cluster hanode1 hanode2 报 "Could not resolve host"。
修复: 在两节点 /etc/hosts 加 10.10.40.241 hanode1 和 10.10.40.242 hanode2。
EL10 + pcs 0.12 中,部分 pcs resource 子命令(pcs stonith update ...)在某些场景下更新 conf 后需要重启 corosync 才能生效。资源创建后如有问题,先看 pcs status --full 找具体失败 action,再 pcs resource cleanup <name> 清失败历史。
在每台 hanode 上跑一次:
# /usr/local/bin/iscsi-rescan.sh
#!/bin/bash
# 发现 + login + 触发 SCSI rescan + multipath 重配置
set -e
# 1. 双 portal 发现(每次只 1 次,因为 sendtargets 重复跑会重复 login)
iscsiadm -m discovery -t sendtargets -p 10.10.40.250:3260
iscsiadm -m discovery -t sendtargets -p 192.168.200.250:3260
# 2. 登录所有 nodes
iscsiadm -m node -l
# 3. 触发 kernel LUN rescan
for host in /sys/class/scsi_host/host*/scan; do
echo "- - -" > $host
done
sleep 3
# 4. multipathd 重读 conf + 重新扫描
multipathd reconfigure
# 5. 验证 4 个 mpath 都在
multipath -ll
echo "✓ iSCSI rescan complete"
用法: chmod +x /usr/local/bin/iscsi-rescan.sh && bash /usr/local/bin/iscsi-rescan.sh
在 storage-a 上跑(VM 磁盘枚举变了,targetcli backstore 失效的恢复):
# /usr/local/bin/storage-restore.sh
#!/bin/bash
set -e
TARGET=iqn.2026-08.com.example:storage-a.target0
# 1. 删所有旧 backstore + LUN(此时它们指向失效的 /dev/sdaN)
for lun in 1 2 3 4; do
targetcli /iscsi/$TARGET/tpg1/luns delete lun=$lun 2>/dev/null || true
done
for n in 1 2 3 4; do
targetcli /backstores/block delete sda${n}_block 2>/dev/null || true
done
# 2. 重建(用 /dev/sdb1-4,因为 reboot 后空盘变 sdb)
for n in 1 2 3 4; do
targetcli /backstores/block create sdb${n}_block dev=/dev/sdb$n
done
# 3. 重建 LUN + ACL
for n in 1 2 3 4; do
targetcli /iscsi/$TARGET/tpg1/luns create /backstores/block/sdb${n}_block lun=0$n
done
# 4. 持久化
targetcli saveconfig
# 5. 验证
ss -tnlp | grep 3260
targetcli ls /iscsi
echo "✓ storage restored"
关键: /dev/sdbN 不是固定的(VM reboot 还会变),更稳的写法是用 /dev/disk/by-id/ 或 LVM。改进版:
# 用 disk by-id 找 200G 空盘(基于 WWN,稳定)
DISK=$(ls -l /dev/disk/by-id/wwn-* 2>/dev/null | grep -v part | awk "{print \$NF}" | head -1)
echo "Target disk: $DISK"
# 1. 先 storage-a 跑 SOP-2(storage-restore.sh)
# 2. 再所有 hanode 跑 SOP-1(iscsi-rescan.sh)
# 3. 验证 mount -a 自动挂载所有 share
mount -a
df -hT | grep share
| 编号 | 文件 / 配置 | 位置 |
|---|---|---|
| 1 | 主配置文档 (本文档) | /srv/samba/share/SEDP/cluster-ha-deploy-2026-08-10.html |
| 2 | stage5 hanode1 脚本(旧版,设备名硬编码) | /tmp/stage5-hanode1.sh (不要用,见 #3) |
| 3 | stage5 推荐版(用 multipath alias,简化) | /tmp/stage5-fixed.sh |
| 4 | fix-share2 脚本 | /tmp/fix-share2.sh |
| 5 | reboot 后 iSCSI 恢复 SOP-1 (hanode 端) | /usr/local/bin/iscsi-rescan.sh |
| 6 | reboot 后 iSCSI 恢复 SOP-2 (storage-a 端) | /usr/local/bin/storage-restore.sh |
| 7 | /etc/multipath.conf | 两节点相同 (4 个 mpath + blacklist sda) |
| 8 | /etc/iscsi/initiatorname.iscsi | 两节点不同 (系统自动生成) |
| 9 | /etc/target/saveconfig.json | storage-a (4 LUN 配置) |
| 10 | Pacemaker CIB | 两节点同步 (web_group + sbd_fence) |