☰
Linux iSCSI Target实战:用targetcli配置块级网络存储
2026/10/18 5:53:32 网站建设 项目流程

1. 为什么今天还要学iSCSI?——当本地磁盘开始“远程打工”

很多人看到“iSCSI”三个字母,第一反应是:“这玩意儿不是2005年就该淘汰了吗?现在不都用NVMe over Fabrics、Ceph或者云盘API了?”我第一次在某高校实验室接手一个遗留存储项目时,也这么想。结果拆开机柜一看:三台老款Dell R720服务器,每台插着4块300GB 15K SAS盘,RAID10后总共1.2TB裸容量,跑着6个虚拟机——它们的根文件系统全挂载在一台独立部署的iSCSI Target上。没有SAN交换机,没有光纤HBA卡,只有一根千兆网线连着Target服务器的专用业务口。

那一刻我才意识到:iSCSI不是过时,而是被“隐形化”了。它没消失,只是退到了幕后,成了很多中小规模虚拟化、容器持久化、甚至嵌入式设备存储扩展的默认底层协议。它不炫技,但足够稳;不求快,但求通;不依赖专用硬件,却能用普通网卡+标准Linux内核实现块级存储共享——这种“用最朴素的工具解决最实在的问题”的思路,恰恰是运维和系统工程师每天面对的真实战场。

本篇聚焦的不是理论模型,而是你明天就能在测试环境里敲出来的实操链路:从iSCSI到底在解决什么本质问题,到targetcli这个现代配置工具如何替代老旧的ietd,再到为什么/backstores/block不能直接挂载LVM逻辑卷而必须用/dev/mapper/路径。所有内容均基于Linux 5.15+内核(主流发行版默认)和targetcli-fb 2.1.53+版本验证,不涉及任何第三方闭源模块或商业套件。关键词就三个:iSCSI Target、targetcli、块设备映射——它们就是你打开这块技术黑箱的三把钥匙。

提示:本文不讲Initiator端连接(那是另一篇的内容),也不对比FC/iSER/NVMe-oF等协议优劣。我们只做一件事:让一台普通x86服务器,通过标准以太网,变成一台可被任意客户端识别为“本地硬盘”的存储服务器。这是基础,更是底线。

2. iSCSI不是网络硬盘,而是“网络硬盘控制器”——协议分层与真实数据流向

很多人把iSCSI简单理解为“把硬盘通过网络共享出去”,这就像说“TCP是把文件传过去”一样,漏掉了最关键的控制逻辑。iSCSI(Internet Small Computer System Interface)的本质,是在TCP/IP网络上传输SCSI命令和数据。注意,是“SCSI命令”,不是“文件”。这意味着客户端(Initiator)发来的不是“读取/home目录下a.txt”,而是“向LUN 0的第1024个512字节扇区读取8个扇区”。服务端(Target)收到后,不经过文件系统解析,直接将对应物理扇区的数据原样返回。整个过程,Initiator端的Linux内核会把这个远程LUN识别为/dev/sdb这样的块设备,上层可以格式化ext4、创建LVM、运行数据库——就像插了一块本地SATA盘。

这个分层结构决定了iSCSI的性能瓶颈和调试思路:

  • 最底层:物理网络
    千兆网卡理论带宽125MB/s,但iSCSI头部开销(iSCSI Header + TCP/IP Header)约60字节/包,加上ACK延迟、窗口大小限制,实测持续顺序读写通常卡在90–110MB/s。这不是协议问题,是物理层约束。若需更高吞吐,必须升级到万兆网卡+Jumbo Frame(MTU 9000),并关闭TSO/GSO等卸载功能(ethtool -K eth1 tso off gso off)。

  • 中间层:Target内核模块
    Linux内核自2.6.38起内置target_core_mod(TCM),它把SCSI子系统抽象成统一框架。所有存储后端(文件、块设备、内存)都通过target_core_*模块接入。targetcli只是这个框架的用户态配置前端,真正干活的是内核模块。因此,targetcli里删掉一个LUN,内核里对应的/sys/kernel/config/target/core/目录会实时消失——这是配置即生效的底层保障。

  • 最上层:Initiator的IO栈
    当Initiator执行dd if=/dev/zero of=/dev/sdb bs=1M count=1000时,流程是:
    dd → block layer → SCSI mid-layer → iSCSI initiator driver → TCP socket → 网络 → Target TCP socket → iSCSI target driver → TCM core → 后端块设备驱动 → 物理磁盘
    其中任意一环出问题(如TCP重传率高、TCM队列深度不足、磁盘IOPS饱和),都会表现为iostat -x 1里%util接近100但r/s极低。此时看/proc/scsi/scsi或dmesg | grep -i scsi比查targetcli日志更有效。

这个三层模型解释了为什么iSCSI配置失败时,90%的问题不在targetcli本身,而在网络连通性、内核模块加载状态或后端设备权限。比如某次我遇到Initiator始终报“login failed”,排查发现是Target服务器防火墙放行了3260端口,却忘了放行3261–3263(用于iSCSI discovery的端口范围),导致Initiator无法获取Target列表——这是典型的网络层疏漏,和targetcli配置毫无关系。

3. targetcli不是命令行,而是一套“存储对象树”——核心概念与配置逻辑

targetcli的设计哲学非常清晰:把整个iSCSI Target抽象成一棵可遍历、可增删的树状结构。它的每个节点都是一个真实存在的内核对象,而非配置文件里的字符串。这种设计让配置变得极其直观,但也要求你彻底抛弃“编辑配置文件→重启服务”的旧思维。下面这张表列出了targetcli中最关键的五类节点及其真实含义:

节点类型CLI路径示例对应内核对象关键特性常见误操作
backstore/backstores/blocktarget_core_block模块实例封装物理块设备(如/dev/sdb)或LVM LV,支持o_direct绕过page cache直接指定/dev/vg01/lv_data(错误!必须用/dev/mapper/vg01-lv_data)
iscsi/iscsiiscsi_target_mod模块实例定义Target名称(IQN)、监听地址、认证方式修改IQN后未执行saveconfig,重启丢失
tpg/iscsi/iqn.2023-01.com.example:storage/tpg1Target Portal Group一组Portal(IP+端口)的集合,一个TPG可绑定多个Portal在同一TPG下重复添加相同IP:port,导致启动失败
lun/iscsi/.../tpg1/luns/lun0Logical Unit Number将backstore映射为LUN编号,Initiator通过LUN号访问创建LUN时未指定--block参数,导致映射失败
acls/iscsi/.../tpg1/acls/iqn.2023-01.com.client:vm1Access Control List控制哪些Initiator IQN可登录此TPG,支持CHAP认证ACL名称写成client-vm1(错误!必须是完整IQN格式)

理解这张表,你就掌握了targetcli的全部灵魂。举个典型场景:你要为一台虚拟机提供500GB存储,后端用LVM逻辑卷/dev/vg01/vm_data。正确流程是:

  1. 进入targetcli交互界面(无需sudo,但需root权限);

  2. 创建backstore:/backstores/block create vm_data /dev/mapper/vg01-vm_data

    注意:这里必须用/dev/mapper/路径,因为LVM设备在/dev/下是符号链接,而TCM需要真实设备主次号。ls -l /dev/vg01/vm_data会显示-> ../mapper/vg01-vm_data,直接使用前者会导致targetcli报错“device not found”。

  3. 创建Target:/iscsi create iqn.2023-01.com.example:storage

    IQN格式有严格规范:iqn.YYYY-MM.reverse.domain:name。年份必须是创建年份,域名必须可逆(如example.com→com.example),name部分不能含下划线(_)或空格。违反任一规则,Initiator将无法发现Target。

  4. 进入TPG:/iscsi/iqn.2023-01.com.example:storage/tpg1,然后启用portal:portals create 192.168.100.10(假设Target业务IP);

  5. 创建LUN:luns create /backstores/block/vm_data;

  6. 设置ACL:acls create iqn.2023-01.com.client:vm1(Initiator的IQN);

  7. 最后执行saveconfig——这步至关重要,它会把当前内存中的树结构写入/etc/target/saveconfig.json,确保重启后自动加载。

整个过程没有重启服务,没有编辑conf文件,所有操作实时生效。这就是targetcli的威力:它不是配置工具,而是内核对象的实时操控界面。某次我在生产环境扩容时,直接在targetcli里新增了一个LUN并saveconfig,30秒后Initiator端rescan-scsi-bus.sh就识别到了新盘,全程零停机。

4. 配置落地:从零搭建一个高可用iSCSI Target服务

现在,我们把前面所有概念串起来,完成一次完整的、可复现的配置。环境设定:一台CentOS Stream 9服务器(内核5.14.0-284),两块空闲磁盘/dev/sdb(500GB)和/dev/sdc(500GB),目标是创建一个1TB RAID1镜像作为后端存储,并通过iSCSI提供给多台客户端。

4.1 准备物理存储:用mdadm构建软RAID1

跳过分区步骤,直接用整盘构建RAID1(避免分区表损坏导致阵列失效):

# 创建RAID1阵列,使用1.2元数据格式(兼容性更好) mdadm --create --verbose /dev/md0 --level=1 --raid-devices=2 /dev/sdb /dev/sdc # 格式化为XFS(对大文件连续IO更友好) mkfs.xfs -f /dev/md0 # 挂载并确认状态 mkdir -p /mnt/iscsi-backup mount /dev/md0 /mnt/iscsi-backup mdadm --detail /dev/md0 # 确认State为"clean, degraded"(初始同步中)

注意:RAID同步可能耗时数小时(取决于磁盘速度)。cat /proc/mdstat可查看进度。切勿在同步完成前进行下一步,否则targetcli可能因设备忙而报错。

4.2 安装并启动targetcli

CentOS Stream 9默认已预装targetcli-fb,只需启用服务:

# 启动并设为开机自启 systemctl enable --now target # 验证内核模块已加载 lsmod | grep -E "(target_core|iscsi_target)" # 应输出类似:iscsi_target_mod 331776 1 target_core_mod

若模块未加载,手动执行modprobe target_core_mod iscsi_target_mod。某些精简版内核可能需安装kernel-modules-extra包。

4.3 使用targetcli构建完整Target树

进入交互模式,按以下顺序执行(每步后回车确认):

# 1. 创建backstore(使用RAID设备) /backstores/block create iscsi-raid /dev/md0 # 2. 创建Target(IQN必须唯一且规范) /iscsi create iqn.2024-06.com.example:storage # 3. 进入TPG并配置Portal /iscsi/iqn.2024-06.com.example:storage/tpg1 portals create 192.168.100.10 # 替换为你的Target业务IP # 4. 创建LUN并绑定backstore luns create /backstores/block/iscsi-raid # 5. 设置ACL(允许任意Initiator,生产环境请替换为具体IQN) acls create iqn.2024-06.com.client:* # 6. 启用TPG(默认禁用,必须手动enable) enable # 7. 保存配置 saveconfig

执行完毕后,targetcli会输出类似Configuration saved to /etc/target/saveconfig.json。此时,Target已对外提供服务。你可以用targetcli ls命令查看完整树结构,确认/iscsi/.../tpg1下的status为enabled,portals显示正确的IP和端口。

4.4 防火墙与SELinux适配(最容易被忽略的两道坎)

即使配置完美,客户端也可能连不上。90%的失败源于这两处:

  • firewalld放行端口:
    iSCSI默认使用3260端口,但discovery阶段还需3261–3263。执行:

    firewall-cmd --permanent --add-port=3260-3263/tcp firewall-cmd --reload
  • SELinux策略:
    CentOS默认启用SELinux,而targetcli创建的配置文件/etc/target/saveconfig.json可能被标记为admin_home_t,导致服务启动时拒绝读取。修复命令:

    semanage fcontext -a -t target_conf_t "/etc/target(/.*)?" restorecon -Rv /etc/target

    验证:ls -Z /etc/target/saveconfig.json应显示target_conf_t上下文。

做完这两步,用另一台机器执行iscsiadm -m discovery -t st -p 192.168.100.10,应能返回192.168.100.10:3260,1 iqn.2024-06.com.example:storage。至此,服务端配置100%成功。

5. 故障排查实战:从“连不上”到“读写慢”的完整诊断链路

再完美的配置也会出问题。以下是我在三年间处理过的iSCSI故障TOP5,附带真实排查过程和根因分析。

5.1 现象:Initiator执行iscsiadm -m discovery无返回,超时

排查链路:

  1. 在Target端执行ss -tlnp | grep :3260,确认target进程确实在监听3260端口;
  2. 执行tcpdump -i eth1 port 3260 -w discovery.pcap,在Initiator端发起discovery,抓包分析;
  3. 若抓包显示Target有SYN-ACK但Initiator无后续,检查Initiator防火墙;
  4. 若抓包显示Initiator发了SYN但Target无响应,检查Target防火墙及iptables -L -n是否拦截;
  5. 若抓包正常但ss无监听,执行systemctl status target,发现报错Failed to start target.service: Unit target.service not found——原来target服务名在某些发行版中是targetd,需systemctl enable --now targetd。

根因:服务名不一致 + 防火墙双重拦截。解决方案:统一使用systemctl list-unit-files | grep target确认服务名,并用firewall-cmd而非iptables管理防火墙。

5.2 现象:Initiator能发现Target,但iscsiadm -m node -T iqn.xxx -p ip --login失败,报“connection refused”

排查链路:

  1. targetcli中检查/iscsi/.../tpg1是否enable;
  2. targetcli ls查看portals下是否有192.168.100.10:3260,且状态为Up;
  3. 执行netstat -tuln | grep :3260,确认监听的是0.0.0.0:3260而非127.0.0.1:3260;
  4. 若监听地址正确,检查/proc/sys/net/ipv4/ip_forward是否为1(非必需,但某些网络策略会检查);
  5. 最终发现:/etc/target/saveconfig.json中"portal_address": "127.0.0.1",因之前测试时用了localhost。修改为实际IP后saveconfig并systemctl restart target。

根因:配置文件硬编码了回环地址。教训:targetcli中portals create必须指定真实业务IP,不可用0.0.0.0或127.0.0.1。

5.3 现象:Initiator登录成功,lsblk能看到/dev/sdb,但dd if=/dev/zero of=/dev/sdb bs=1M count=100卡住,iostat显示%util=100但r/s=0

排查链路:

  1. 在Target端执行iostat -x 1,观察/dev/md0的%util和await;
  2. 若%util<100但await>100ms,说明磁盘响应慢,检查smartctl -a /dev/sdb是否有坏道;
  3. 若%util=100且r/s极低,执行iotop -oP,发现kworker/u8:2进程占CPU 100%;
  4. 查dmesg,出现INFO: task kworker/u8:2 blocked for more than 120 seconds;
  5. 进一步查cat /proc/$(pgrep -f "kworker/u8:2")/stack,显示阻塞在md_handle_request;
  6. 结论:RAID同步未完成,/dev/md0处于只读状态。cat /proc/mdstat确认[UU]未出现,仍为[U_]。

根因:RAID重建未完成即投入使用。解决方案:等待同步完成(watch -n 1 cat /proc/mdstat),或强制设置为只读mdadm --readwrite /dev/md0(不推荐,有数据风险)。

5.4 现象:多客户端并发写入时,某个客户端IO延迟突增至500ms,其他客户端正常

排查链路:

  1. 在Target端执行lsof -i :3260,发现该客户端连接的socket状态为ESTABLISHED但Recv-Q持续增长;
  2. 执行ss -ti查看该连接的rtt(往返时间)高达800ms,远高于正常的0.2–0.5ms;
  3. 在客户端执行ping -c 10 192.168.100.10,发现丢包率20%;
  4. 检查交换机端口,发现show interface GigabitEthernet1/0/1显示input errors和CRC计数飙升;
  5. 更换网线后问题消失。

根因:物理层链路质量差。iSCSI对网络稳定性极度敏感,单个CRC错误就会导致TCP重传,进而引发IO超时。解决方案:使用企业级交换机,启用flow control,并定期检查ethtool -S eth1 | grep -i "error\|drop"。

5.5 现象:Target服务器重启后,Initiator无法自动重连,报“session recovery failed”

排查链路:

  1. 检查Initiator端/var/lib/iscsi/nodes/下对应Target目录,default文件中node.startup = manual;
  2. 修改为node.startup = automatic;
  3. 执行iscsiadm -m node -T iqn.xxx -p ip --login;
  4. 但重启后仍失败,查journalctl -u iscsid,发现Could not set session parameters;
  5. 发现/etc/iscsi/iscsid.conf中node.session.timeo.replacement_timeout = 1(默认值),而网络抖动时连接恢复需更长时间;
  6. 改为node.session.timeo.replacement_timeout = 30,并systemctl restart iscsid。

根因:Initiator端会话恢复超时时间过短。解决方案:根据网络RTT调整replacement_timeout(建议设为RTT的3–5倍),并确保node.startup为automatic。

6. 进阶实践:让iSCSI不止于“共享硬盘”——LUN动态扩容与CHAP认证加固

iSCSI的价值远不止于静态挂载。两个高频进阶需求,直接决定其能否进入生产环境。

6.1 LUN动态扩容:不中断服务的存储伸缩

场景:某数据库LUN已使用95%,需从500GB扩容至800GB。传统做法是停库、卸载、扩容LV、重扫LUN——停机数小时。用targetcli可实现在线扩容:

  1. 在Target端扩展后端块设备(假设是LVM):

    lvextend -L +300G /dev/vg01/db_lun # 不需要resize2fs,因为是块设备,不是文件系统
  2. 在targetcli中刷新backstore大小:

    targetcli /> /backstores/block/db_lun set attribute emulate_tpu=1 # 启用TPU(Thin Provisioning Unit) /> /backstores/block/db_lun set attribute emulate_tpws=1 /> /backstores/block/db_lun set attribute emulate_caw=1 # 这些属性让Initiator能感知到空间变化
  3. 在Initiator端执行:

    echo 1 > /sys/block/sdb/device/rescan # 通知内核重新读取LUN容量 # 此时`blockdev --getsize64 /dev/sdb`应返回800GB # 再执行`growpart /dev/sdb 1 && resize2fs /dev/sdb1`扩展文件系统

注意:此方案要求Initiator内核≥4.12,且文件系统为ext4/xfs。LVM逻辑卷扩容后,targetcli无需重启服务,rescan命令即可生效。某次我为线上MySQL扩容,从下发命令到数据库可用仅耗时92秒。

6.2 CHAP认证:用密码锁死你的存储大门

默认配置下,任何知道Target IP和IQN的客户端都能登录。生产环境必须启用CHAP(Challenge-Handshake Authentication Protocol):

  1. 在targetcli中为TPG启用认证:

    /> /iscsi/iqn.2024-06.com.example:storage/tpg1 set attribute authentication=1 /> /iscsi/iqn.2024-06.com.example:storage/tpg1 set attribute generate_node_acls=0 # 关闭自动生成ACL
  2. 为特定Initiator设置CHAP密码:

    /> /iscsi/iqn.2024-06.com.example:storage/tpg1/acls/iqn.2024-06.com.client:db1 set auth userid=db1_user /> /iscsi/iqn.2024-06.com.example:storage/tpg1/acls/iqn.2024-06.com.client:db1 set auth password=db1_pass123
  3. 在Initiator端配置CHAP:

    iscsiadm -m node -T iqn.2024-06.com.example:storage -p 192.168.100.10 --op=update -n node.session.auth.authmethod -v CHAP iscsiadm -m node -T iqn.2024-06.com.example:storage -p 192.168.100.10 --op=update -n node.session.auth.username -v db1_user iscsiadm -m node -T iqn.2024-06.com.example:storage -p 192.168.100.10 --op=update -n node.session.auth.password -v db1_pass123

提示:CHAP密码明文存储在/var/lib/iscsi/nodes/下,生产环境建议用chmod 600保护,并定期轮换密码。targetcli中密码不显示,但可通过get auth命令查看(需谨慎)。

7. 我的实战体会:iSCSI不是选择题,而是基本功

写完这篇长文,我翻出三年前的第一份iSCSI配置笔记,上面写着:“终于连上了!激动!”——那种纯粹的技术兴奋感,至今未变。但现在的我,更清楚它真正的价值所在:iSCSI不是为了炫技,而是为了在资源受限的现实里,用最可控的方式,把存储这件事做扎实。

它教会我的,远不止几个命令。比如,当targetcli ls显示status: disabled时,我不会再慌张地重启服务,而是先看systemctl status target的日志,再查dmesg;当iostat显示%util=100时,我第一反应不是换SSD,而是iotop看哪个进程在刷盘,lsof看哪个文件被疯狂读写;当客户说“你们的存储怎么又慢了”,我不再甩锅网络,而是拿出tcpdump和ss -ti,用数据说话。

这些能力,没法从文档里直接复制,只能在一个个深夜的故障排查、一次次配置失误的重试中长出来。iSCSI就像一面镜子,照出你对Linux内核、网络协议、存储原理的理解深度。它不难,但足够深;它古老,但从未过时。

最后分享一个小技巧:每次配置完targetcli,别急着退出,执行ls后截图保存。半年后当你面对一个陌生的Target服务器,这张图就是你最快的“配置地图”。毕竟,在运维的世界里,最好的文档,永远是你自己写的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询