Homelab - Linux挂载TrueNAS中的iSCSI
基本概念
NAS:(Network Attached Storage,网络附加存储) 使用的是基于文件的通信协议,例如NFS或SMB/CIFS,计算机请求访问的是抽象文件的一段内容,而非对磁盘进行的块设备操作。
SAN:(storage area network,存储区域网络)是一种连接外接存储设备和服务器的架构。该架构的特点是,连接到服务器的存储设备,将被操作系统视为直接连接的存储设备。
NAS和SAN都是为了实现存储共享。不同的是NAS以文件为基础,客户端可以操作远程共享出的文件或文件夹。而SAN以更底层的磁盘块为基础,客户端可以像使用本地磁盘一样使用SAN共享出的磁盘块,可以在上面创建独立的文件系统。
SCSI:(Small Computer System Interface,小型计算机系统接口)是一种用于计算机及其周边设备之间(硬盘、软驱、光驱、打印机、扫描仪等)的通信协议。
iSCSI(Internet Small Computer System Interface,基于Internet的SCSI)又称为IP-SAN,利用了TCP/IP的port 860 和 3260 作为沟通的渠道。透过两部计算机之间利用iSCSI的协议来交换SCSI命令,让计算机可以透过Internet把SAN模拟成为本地的储存设备。
TrueNAS:一个操作系统,专门用于提供各种形式的存储共享服务(包括NAS和SAN)。家用场景主要有两个版本:TrueNAS Core基于FreeBSD,TrueNAS Scale基于Linux。当然也有企业版
为什么要这么做
如果你有一个Homelab,Nas系统肯定是必不可少的,因为肯定要有一个数据中心吧。既然是数据中心,我希望所有的数据都保存到Nas系统中,当我创建Redis服务的时候想到了这点,所以就想研究下能不能把Redis的持久化数据保存到Nas系统中。因为是用于服务器,使用基于文件的共享可能会有一些限制。为避免出现奇奇怪怪的问题,所以决定使用基于块的共享iSCSI,毕竟可以像本地磁盘一样进行操作。
环境介绍
Target(iscsi服务端):TrueNas Core 13.0
Initiator(iscsi客户端):Debian12
TrueNas创建iSCSI共享
首先在存储池中创建一个Zvol,将作为块设备通过iSCSI共享出去
配置iSCSI
服务器全局配置:Target Global Configuration标签页面,保持默认就行,可以忽略下图中我的配置
账号密码配置:客户端连接时的用户名密码。点击Authorized Access标签页,点击添加按钮
群组ID:自定义一个数字
用户:远程连接用户名
秘密,确认:远程连接密码
门户配置:点击Portals标签页,点击添加按钮
描述:自定义描述信息
验证方法:CHAP
验证组:上一步配置的群组ID
IP地址:0.0.0.0
客户端组配置:Initiators Groups标签页,点击添加
勾选允许所有启动器,并保存
共享块配置:选择Extends标签页,点击添加按钮
名称:自定义一个名称
设备:选择之前创建的Zvol
LUN RPM:磁盘转速,根据自己的磁盘类型选择
配置目标:客户端要连接的target
目标名称:自定义名称
门户组ID:Portals中配置的ID
身份验证方法:CHAP
启动器组ID:Initiators Groups添加之后生成的ID,默认第一个为1
身份验证组号:Authorized Access中添加的组ID
关联目标:Associated Targets,将目标和磁盘块进行关联
目标:目标中配置的目标名称
区块:Extends中配置的名称
到此服务端的配置就完成了,配置的过程中,有些配置项是有依赖的,所以需要按照上述步骤顺序进行。
Linux连接iSCSI
连接到iSCSI,并挂载磁盘
安装客户端工具:
apt install open-iscsi
修改配置文件 /etc/iscsi/iscsid.conf,之后才能执行下面的命令
discovery.sendtargets.auth.authmethod = CHAP discovery.sendtargets.auth.username = jdoe # 在TrueNAS中配置的用户名 discovery.sendtargets.auth.password = YourSecurePwd1 # 在TrueNAS中配置的密码 node.session.auth.authmethod = CHAP node.session.auth.username = jdoe # 在TrueNAS中配置的用户名 node.session.auth.password = YourSecurePwd1 # 在TrueNAS中配置的密码 node.startup = automatic node.session.timeo.replacement_timeout = -1 # 当连接出现故障会一直重试
发现iscsi target
iscsiadm --mode discovery --type sendtargets --portal truenas.home # truenas.home,你自己truenas的ip地址或域名,因为我局域网配置了小型dns服务器,所以我使用了自定义的域名
执行结果如下
连接iscsi
iscsiadm --mode node --targetname "iqn.2005-10.org.freenas.ctl:redis" --portal "truenas.home:3260" --login # iqn.2005-10.org.freenas.ctl:redis 上一步显示的结果,后面部分
验证连接
iscsiadm --mode session --print=1
最后三行显示如下,说明连接成功了
开机自动连接
以上操作完之后,开机就会自动连接了,无需额外操作
挂载
# 查看磁盘,正常情况下会多出/dev/sdb fdisk -l # 直接格式化磁盘(不用分区,更加方便扩容) mkfs.ext4 /dev/sdb # 查看uuid blkid #修改/etc/fstab,添加下面一行(替换成你自己的uuid) UUID=2009e83c-73a0-4774-a55c-5cf02fa50e37 /data ext4 _netdev 0 0 # 最后挂载 mkdir /data systemctl daemon-reload mount -a
安装redis,将redis的持久化数据保存到/data
这一步主要是安装redis,修改redis的配置文件将数据保存到/data。redis可以换成任意服务。不是文章重点,就不多说了。
修改redis的systemd单元,让redis在iscsid之后启动
# 编辑:/lib/systemd/system/redis-server.service
# 修改After字段为:
After=network.target open-iscsi.service iscsid.service
还有更重要的事
经过上面的操作,Linux成功挂载了iSCSI磁盘,并且作为了redis服务的数据盘,可以正常使用了。实现了我们的初衷:可以将所有的数据保存到TrueNAS系统中。但还远远没有结束。
扩容
TrueNAS中,Zvol卷只能扩容,不能缩容,所以最开始要分配尽量小的空间
TrueNAS中,编辑Zvol,直接更改容量就行
在linux中:
关闭redis服务(就是写/data的那个服务,保证磁盘在扩容过程中没有被占用)
卸载/data:
umount /data
扩容操作:
e2fsck -f /dev/sdb resize2fs /dev/sdb
重新挂载:
mount -a
最后启动redis服务
结束,就是如此简单
重中之重:故障恢复
这一部分才是最难的,也是这篇文章最有价值的地方。上面的所有东西,只要玩过Homelab都应该能摸索出来,或者可以在网上搜索的到。但是下面要说的,在网上找了很久都没找到。
现在我们只是搭建了一个Redis服务(每个服务都是一个单独的虚拟机),在未来我肯定要部署很多服务,开很多虚拟机,每个虚拟机都使用iSCSI。在家庭环境中,我们的数据中心(TrueNAS系统)肯定会有升级、重启的时候。那么问题来了!1:如何保证TrueNAS不可用的时候,不影响其他依赖的虚拟机;2:如何保证TrueNAS恢复的时候,依赖的虚拟机也一起恢复。
经过不厌其烦的测试,发现iscsi客户端是可以做到故障自动恢复的,由于上面的配置文件中,我们修改了一行node.session.timeo.replacement_timeout = -1
,所以检测到故障后会一直重试,当恢复的时候会自动连接。但是!如果在故障期间对iscsi磁盘进行了操作,iscsi连接恢复后,磁盘会变得只读,重新挂载会报错:写保护
所以要保证在iscsi连接故障期间,不能对磁盘进行任何操作。好在依赖iscsi的不是整个虚拟机,只是具体的服务(比如Redis),所以我们要解决的问题是:检测到iscsi不可用时停止具体的依赖服务(Redis)。检测到iscsi可用了,再启动具体的依赖服务。
所以问题的关键变成了关键的问题:如何在Linux中及时检测到iSCSI的连接状态。
咋一听好像挺难的,需要很深入的了解Linux和iSCSI,也确实挺难的,我也不会。所以想到一种简单的方法:监听日志。
在Linux上的服务出现故障肯定会有日志打印出来,而维护iSCSI连接的服务是iscsid.service
,所以我们需要实时监听iscsid.service的日志,而在使用了systemd的Linux中,实时监听日志也变得非常简单:journalctl -u iscsid.service -f

在上图中,红色划线部分(不是红色字体)分别是iSCSI不可用时打印的第一条日志,和iSCSI恢复时打印的第一条日志。所以只需要写个脚本实时监听日志,在出现上述字符串时分别关闭/启动redis服务。
脚本如下:/root/monitor_iscsid.sh
#!/bin/bash
# 定义消息模式
ERROR_PATTERN="Kernel reported iSCSI connection 1:0 error"
RECOVERY_PATTERN="connection1:0 is operational after recovery"
# 针对不同的服务,只需修改这个变量
depend_server="redis-server"
# 初始状态:所依赖的服务正在运行
depend_running=true
# 函数:停止服务
stop_depend() {
if $depend_running; then
echo "$(date): 检测到iscsid错误,正在停止${depend_server}服务..."
systemctl stop ${depend_server}.service
depend_running=false
echo "$(date): ${depend_server}服务已停止."
echo ""
fi
}
# 函数:启动depend服务
start_depend() {
if ! $depend_running; then
echo "$(date): iscsid已恢复,正在启动${depend_server}服务..."
systemctl start ${depend_server}.service
depend_running=true
echo "$(date): ${depend_server}服务已启动."
echo ""
fi
}
# 实时监控iscsid的日志
echo "$(date): 开始监控iscsid的日志..."
journalctl -u iscsid -f | while IFS= read -r line; do
if echo "$line" | grep -q "$ERROR_PATTERN"; then
stop_depend
elif echo "$line" | grep -q "$RECOVERY_PATTERN"; then
start_depend
fi
done
加上可执行权限:chmod +x monitor_iscsid.sh
脚本使用systemd进行管理:/etc/systemd/system/monitor-iscsid.service
[Unit]
Description=Monitor iscsid and manage depend service
After=network.target iscsid.service
Requires=iscsid.service
[Service]
Type=simple
ExecStart=/root/monitor_iscsid.sh
Restart=on-failure
User=root
Group=root
StandardOutput=append:/root/monitor-iscsid.log
StandardError=append:/root/monitor-iscsid.err
WorkingDirectory=/root/
[Install]
WantedBy=multi-user.target
启动服务:
systemctl daemon-reload
systemctl enable monitor-iscsid.service --now
问题优化
在之前的操作中,我们在全局配置文件/etc/iscsi/iscsid.conf
中修改了配置node.startup = automatic
,当执行iscsiadm --mode discovery --type sendtargets --portal truenas.home
之后如果发现了多个target,所有发现的target都会保存到/etc/iscsi/nodes/
文件夹中。重启后会自动连接这个文件夹下的所有target。
我们并不需要自动连接所有的target
如果在TrueNAS端删除了某个target,linux的target文件夹中并不会自动删除。就会出现如下问题:
当Linux关机时报错,并且一直无法关机
open-iscsi.service服务启动失败,因为会去自动连接一个不存在的target。
为解决这个问题,需要修改全局配置文件:node.startup = manual
,然后重新执行:iscsiadm --mode discovery --type sendtargets --portal truenas.home
,然后修改单独的target配置,操作步骤如下:
# 1. 修改/etc/iscsi/iscsid.conf
node.startup = manual
# 2. 重新发现target,执行:
iscsiadm --mode discovery --type sendtargets --portal truenas.home
# 3. 修改具体的target配置文件(这里假如target为mysql):/etc/iscsi/nodes/iqn.2005-***.freenas.ctl:mysql/truenas.home,3260
node.startup = automatic
总结
通过监听日志的方式来检测故障,是我能想到的最简单的办法了,具体的可靠性还要经过验证。不确定是否可能出现这样一种情况:iscsi出现了故障,但是没有及时检测到,而对iscsi磁盘进行了操作。也就是可能出现对故障的检测并不及时的情况。