iSCSI 存储架构:Target、Initiator 与 LUN
理解 iSCSI 的 Target、Initiator 和 LUN,梳理网络存储规划、认证与容量边界。
#这篇文章解决什么问题
一句话先给结论:这篇文章要讲清 iSCSI 存储架构里四个绕不开的概念——Target、Initiator、LUN 与 Portal——以及它们背后那条完整的因果链:SCSI 命令如何被封装成 iSCSI PDU 穿过 TCP/IP 网络,initiator 如何通过 discovery 与 login 与 target 建立会话,target 侧的一块块设备又如何最终变成 initiator 侧的 /dev/sdX。支撑这一切的最基础事实只有一个:iSCSI 的本质是"用 TCP 传 SCSI 命令",它对上层呈现的是"SCSI 语义的远程块设备",而不是网络文件共享。
搞清楚这条链的价值在于:生产环境里绝大多数 iSCSI 问题(发现不到、登录失败、IO 卡顿、重启丢盘)都不是命令敲错,而是对"每一层到底由谁负责"的边界认识不清。所以本文按因果链展开——先讲清协议与角色为什么这样设计,再给一套可完整复现的最小实验,然后是验证方法与分层排查,最后是边界条件和常见坑。
#原理层:为什么它是这样工作的
#SCSI 模型里已经藏着 Target、Initiator 与 LUN
iSCSI 不是凭空发明的新协议,它复用了 SCSI 架构里已经存在了几十年的角色划分。在 SCSI 体系中,通信双方从一开始就是不对称的:
- Initiator(发起端)发出命令,例如服务器上的 HBA 卡,或者软件 initiator(open-iscsi);
- Target(目标端)接收并执行命令,把命令翻译成对本地存储介质的实际读写。
命令的载体是 CDB(Command Descriptor Block,命令描述块),典型的如 INQUIRY、READ(10)、WRITE(10)。而 LUN(Logical Unit Number,逻辑单元号)是 target 内部对"一块可以被寻址的逻辑存储单元"的编号——一个 target 可以挂多个 LUN,客户端发给 target 的命令都要带上 LUN,让 target 知道这次读写的是它背后的哪一块存储。用一个不太严谨但容易记住的类比:target 是饭店后厨,LUN 是后厨开出来的一个个出菜窗口,而 initiator 是拿着菜单(CDB)点单的客人。
把这个模型从一根 SCSI 总线搬到网络上,就得到 iSCSI。协议规范最初是 RFC 3720(2004 年发布),2014 年被 RFC 7143 取代。规范做的事情只有一件:定义"SCSI 命令在 TCP/IP 上怎么打包、怎么拆包、怎么确认"。因为上层语义不变,所以对应用层来说,一块 iSCSI 盘和一块本地 SATA 盘几乎没有区别——分区、文件系统、缓存策略全部由客户端自己决定。
#iSCSI PDU:SCSI 命令在 TCP 上的"信封"
在 TCP 连接里流动的基本单位是 iSCSI PDU(Protocol Data Unit,协议数据单元)。每个 PDU 由三部分组成:
- BHS(Basic Header Segment,基本头段):固定 48 字节,首字节是操作码(opcode),后面依次携带 LUN、任务标签(Initiator Task Tag)、CmdSN/StatSN 等关键字段;
- 可选部分:扩展头段 AHS、头段校验(HeaderDigest);
- 数据段(Data Segment):真正的载荷,一段最大不超过双方协商的 MaxRecvDataSegmentLength(默认 8192 字节),校验可选(DataDigest)。
操作码决定了这个 PDU 是什么用途。下面只列几个最核心的:
| 操作码 | 方向 | 用途 |
|---|---|---|
| 0x01 | Initiator → Target | SCSI Command,携带 CDB,是一条 SCSI 命令的入口 |
| 0x05 | Initiator → Target | SCSI Data-Out,写操作的数据 |
| 0x24 | Target → Initiator | SCSI Data-In,读操作返回的数据 |
| 0x32 | Target → Initiator | SCSI Response,命令完成状态(如 0x00 Good) |
| 0x03 / 0x22 | 双向 | Login Request / Login Response,建立会话 |
| 0x04 / 0x23 | 双向 | Text Request / Text Response,SendTargets 发现用 |
以一次读操作为例,PDU 的因果链是这样的:
Initiator Target
│ SCSI Command PDU (0x01) │
│ CDB = READ(10), LUN=0, CmdSN │
├────────────────────────────────▶│ 解析 CDB,向块设备发起读
│ SCSI Data-In PDU (0x24) ×N │
│◀────────────────────────────────┤ 数据按 MaxRecvDataSegmentLength 分段回传
│ SCSI Response PDU (0x32) │
│◀────────────────────────────────┤ Status=Good,命令结束写操作类似,方向反过来:Initiator 先发 SCSI Command(带预期的写长度),再按协商好的突发长度(FirstBurstLength)连续发 Data-Out,Target 可能用 R2T 机制进一步请求剩余数据,最后回一个 SCSI Response 表示写入完成。读、写、响应都被拆成一个个独立 PDU,在同一个 TCP 连接上按序传输——TCP 本身保证了字节流的可靠与有序,iSCSI 只需要在 PDU 层面用 CmdSN(命令序号)做端到端排序、用 StatSN 做确认。头段和数据段都可以启用 CRC32C 校验(Digest),但这属于可选加固,默认不开启。
理解了这一层,一个关键推论就出来了:iSCSI 只是运输工具,它不知道也不关心磁盘里的数据结构。它传的是扇区级读写请求,不是文件。这也是后面所有"边界"问题的根源。
#Portal、IQN 与 TPG:网络上的"门牌号"
客户端要找到 target,先要解决三个层面的寻址:
- Portal(门户):target 在网络上监听的一个 TCP 端点,形式是"IP 地址:端口",默认端口 3260。一个 target 可以监听多个 IP,对应多个 portal。
- IQN(iSCSI Qualified Name,iSCSI 限定名):target 和 initiator 各自的身份名字,不是 IP。IQN 的规范格式是
iqn.<年份>-<月份>.<反向域名>:<标识>,例如iqn.2026-08.com.example:storage01。命名要规范,因为客户端、监控、排障都靠这个名字区分存储;同一台机器上的多个 initiator 必须各用各的 IQN。 - TPG(Target Portal Group,目标门户组):target 侧把若干 portal 归成一个组,用 tag 编号,targetcli 里默认就是 tpg1。TPG 的意义在于:组内的 portal 对某个 initiator 而言是"等价入口",登录时协商的会话参数对整组生效。
至此三个角色的边界已经清晰:
- Target 是"门面":它决定发布哪些块设备(LUN)、在哪个 portal 监听、允许哪些 initiator 进来(ACL);
- Initiator 是"访客":它决定连哪个 portal、用哪个 IQN 证明身份、把对方发布的 LUN 当成本地盘;
- LUN 是"窗口":Target 发布的每一个逻辑单元编号,Initiator 看到的只是"一块盘"。
Linux 两侧的常见实现是:
- Target 端:targetcli / LIO(Linux-IO Target),RHEL/Rocky 系内核自带相关模块(target_core_mod、iscsi_target_mod),用户态工具是 targetcli;
- Initiator 端:open-iscsi,安装后提供
iscsiadm命令和iscsid服务。
#Discovery 与 Login:从"找到"到"建立会话"
iSCSI 会话的建立分成两个阶段,对应 initiator 侧的两条命令。
第一阶段是 Discovery(发现)。客户端的默认做法是 SendTargets:连上 portal 后发一个 Text Request,key 为 SendTargets=All,target 把本机所有 "TargetName=..." 与 "TargetAddress=..." 列表返回,客户端把这些记录写进本地的 node 数据库(/etc/iscsi/)。这就是 iscsiadm -m discovery -t sendtargets -p <ip> 做的事。发现阶段只交换"有哪些 target、在哪",不建立正式会话。
第二阶段是 Login(登录)。客户端对选中的 target 发起 Login Request,由 login 阶段的三步协商组成:
- SecurityNegotiation(安全协商):如果配置了 CHAP,就在这里完成认证;
- LoginOperationalNegotiation(运行参数协商):协商 MaxRecvDataSegmentLength、MaxConnections、HeaderDigest 等运行参数;
- FullFeaturePhase(全功能阶段):参数敲定、登录成功,之后才开始真正的 SCSI 数据传输。
登录成功后,这条会话由 ISID(initiator 侧会话标识)、initiator IQN、target IQN、TSIH(target 侧会话句柄)共同唯一确定;会话内部的每条 TCP 连接用 CID 区分。协议允许一个会话包含多条连接,这正是后面 multipath 能横向扩展的基础。
CHAP 在这里的作用要说准确:它本身是"挑战-应答"式认证(基于 MD5),能在登录时证明双方知道同一个密码,防止不相干的机器"蹭盘";但它不加密登录之后的数据面,抓包仍然能看到明文数据流。所以生产上要么把 iSCSI 关进隔离网络,要么对更高要求启用 IPsec 或直接用 NVMe-oF/TLS。
#LUN 如何最终变成 /dev/sdX
把这条因果链走到底,看一块盘从 target 到 initiator 的完整旅程:
- Target 侧:LIO 通过 configfs 暴露配置树(/sys/kernel/config/target)。你把一块真实块设备(如 /dev/sdb)注册成 backstore(backstores/block),再把它挂到某个 target 的 tpg1 下作为 LUN(luns create)。所谓"发布 LUN",在内核里就是创建了这一串对象:backstore(盘在哪)→ iSCSI Target(门面身份)→ TPG/LUN(窗口)→ ACL(谁能进)。
- 网络上:Initiator 完成 discovery + login 后,进入 FullFeaturePhase,开始通过 PDU 收发 SCSI 命令。
- Initiator 侧:Linux 内核的 SCSI 子系统在登录后创建一个 scsi_device(一条总线上的 "sdb" 这样的节点),随后 sd 驱动为它注册一个块设备,用户空间看到的就是 /dev/sdX。
因此 /dev/sdX 不是 target 设备名的直接拷贝,而是 initiator 内核按"发现顺序"重新编号的结果。这带来一个必须记住的事实:设备名不稳定。两台机器探测顺序不同、重启后顺序变化,同一个 LUN 可能在 sdb 和 sdc 之间漂移;所以生产上永远不要拿 /dev/sdX 写 fstab,要用 /dev/disk/by-id/、UUID,或者 multipath 的 WWID。
#操作层:搭一套最小可复现环境
下面从零搭一个"一台 target + 一台 initiator"的最小闭环,把上面的机制全部落到可执行命令上。
#环境与前置条件
本文命令以 Rocky Linux 9 / RHEL 9 为例(内核 5.14+,自带 LIO;targetcli 与 iscsi-initiator-utils 来自发行版仓库);RHEL 8 系同样适用。前置条件:
- 两台 Linux 主机,均有 root 权限、已配静态 IP;
- 存储网络单独划段(示例用 192.168.10.0/24),两台机器都能 ping 通;
- Target 上有一块空闲盘(示例 /dev/sdb,100GiB),里面没有数据;
- Target 防火墙放行 TCP 3260。
拓扑如下:
业务网 192.168.1.0/24(示意,可省略)
Initiator 主机 Target 主机
IP 192.168.10.20 IP 192.168.10.10
open-iscsi / iscsid LIO / targetcli
目标:把远端 LUN 变成 /dev/sdX 目标:把本地 /dev/sdb 发布为 LUN
身份:iqn.2026-08.com.client:web01 身份:iqn.2026-08.com.example:storage01#Target 侧:发布一块盘
安装 targetcli,确认要发布的空闲盘:
sudo dnf install -y targetcli
sudo lsblk -o NAME,SIZE,TYPE,TRAN用一条 heredoc 完成"建 backstore → 建 target → 挂 LUN → 建 ACL":
sudo targetcli <<'EOF'
backstores/block create name=disk01 dev=/dev/sdb
/iscsi create iqn.2026-08.com.example:storage01
/iscsi/iqn.2026-08.com.example:storage01/tpg1/luns create /backstores/block/disk01
/iscsi/iqn.2026-08.com.example:storage01/tpg1/acls create iqn.2026-08.com.client:web01
EOF说明三点:backstore 的 dev=/dev/sdb 会把盘直接交给 LIO,盘上原有数据会被覆盖式使用;LIO 默认拒绝一切连接(génération 上 generate_node_acls 关闭),所以 ACL 必须显式创建;如果 initiator 那边还没定好 IQN,先在客户端机器上 cat /etc/iscsi/initiatorname.iscsi 读出来再填进去。
查看配置、保存并启用服务:
sudo targetcli ls
sudo targetcli saveconfig
sudo systemctl enable --now target
sudo systemctl is-active targetsaveconfig 会写入 /etc/target/saveconfig.json,target 服务重启时自动加载——targetcli 的改动默认只存在内核 configfs 里,不保存重启即丢。
#Initiator 侧:发现并登录
安装 open-iscsi,写入本机唯一 IQN:
sudo dnf install -y iscsi-initiator-utils
sudo vi /etc/iscsi/initiatorname.iscsi
# 内容形如:InitiatorName=iqn.2026-08.com.client:web01发现 target,然后登录:
sudo iscsiadm -m discovery -t sendtargets -p 192.168.10.10
# 预期输出:192.168.10.10:3260,1 iqn.2026-08.com.example:storage01
sudo iscsiadm -m node -T iqn.2026-08.com.example:storage01 -p 192.168.10.10 --login如果 target 配了 CHAP,登录前还要把认证写进 node 记录,否则每次登录都会因认证失败被拒:
sudo iscsiadm -m node -T iqn.2026-08.com.example:storage01 -p 192.168.10.10 \
--op=update --name node.session.auth.authmethod --value=CHAP
sudo iscsiadm -m node -T iqn.2026-08.com.example:storage01 -p 192.168.10.10 \
--op=update --name node.session.auth.username --value=storage_user
sudo iscsiadm -m node -T iqn.2026-08.com.example:storage01 -p 192.168.10.10 \
--op=update --name node.session.auth.password --value=strong-pass-123登录成功后立刻确认"新盘"出现:
sudo iscsiadm -m session
lsblk -S # 应该出现 /dev/sdb,TRAN 列为 iscsi
sudo dmesg | tail -20到此,最小闭环已经成立:/dev/sdb 不再是一块本地盘,而是远端 LIO 通过 iSCSI 会话暴露的 LUN。接下来它就和本地盘一样使用——可以直接 mkfs 挂载、可以分区、也可以交给 LVM。
#验证层:怎么确认我做对了
#会话与设备状态
在 initiator 侧,三条命令给出三组判定标准:
sudo iscsiadm -m session -P 3 # 会话细节:State=LOGGED_IN、TSIH、CID
lsblk -o NAME,SIZE,TYPE,TRAN # TRAN=iscsi 说明这块盘来自网络
ls -l /dev/disk/by-id/ | grep scsi # 稳定的 by-id 名字,如 scsi-36001405...iscsiadm -m session有输出、且 State 为 LOGGED_IN,说明 discovery、login、ACL、CHAP 整条链路已打通;lsblk里 TRAN 列为 iscsi,是从"内核视角"证明这块盘不是本地盘;- by-id 下的名字可复现,是后续 fstab / 监控应该用的稳定标识。
在 target 侧复查"发布状态":
sudo targetcli ls
sudo systemctl status targettargetcli ls 里应能看到 backstores/block/disk01、tpg1/luns 下的 LUN、acls 里的 initiator IQN;target 服务 active 且配置来自 saveconfig,说明重启后仍会加载。
#数据面验证:真的能读写
对空闲盘做一次带直写的写入与回读(破坏性操作,仅限实验盘):
sudo dd if=/dev/zero of=/dev/sdb bs=1M count=256 oflag=direct status=progress
sudo dd if=/dev/sdb of=/dev/null bs=1M count=256 iflag=direct status=progressoflag=direct 绕过客户端页缓存,确保 IO 真正走到网络和远端磁盘。如果不报 IO 错误、速度符合存储网络预期,说明数据面(iSCSI PDU 收发 + LIO 落盘)正常。更贴近生产的做法是建文件系统并挂载,然后 dd 写文件再 sync:
sudo mkfs.xfs /dev/sdb
sudo mkdir -p /data && sudo mount /dev/sdb /data
sudo dd if=/dev/zero of=/data/test bs=1M count=1024 conv=fsync
df -h /data#分层排查:先用最快的命令定位
iSCSI 出问题,按下面这个顺序定位,不要一上来就重启 iscsid:
# 1. 链路层
ping 192.168.10.10 && ss -tn state established '( dport = :3260 )'
# 2. 发现层
sudo iscsiadm -m discovery -t sendtargets -p 192.168.10.10
# 3. 会话层
sudo iscsiadm -m session
lsblk -S # 看有没有新的 SCSI 盘
# 4. 数据层(连接正常但 IO 失败)
dmesg | grep -i scsi
iostat -x 1 # 来自 sysstat 包每层都有对应的结论:链路通但发现不到 target,多半是防火墙或 target 的 portal 配置问题;发现得到但登录失败,看 CHAP 和 ACL;登录成功但 IO 卡,查多路径和链路质量(丢包、双工、存储网络拥塞)。逐层排查,先保底层,再查上层,这是这套阶梯命令的全部意义。
#边界与坑
#网络边界:明文、隔离与认证的局限
iSCSI 走 TCP,默认 3260 端口,默认明文传输。生产环境至少要给存储单独划 VLAN,最好单独用一张网卡甚至独立交换机;数据量大时优先走专用的存储网络,别和业务流量抢带宽。CHAP 是挑战-应答认证,能防"蹭盘",但防不了抓包重放与中间人;要求更高的场景直接上 IPsec 或 NVMe-oF/TLS。不要指望"内网就安全",存储网络是物理隔离逻辑隔离都要做的那一层。
#责任边界:容量、冗余、快照都在 Target 侧
LUN 建多大、底层用 RAID 还是裸盘、快照在哪里做——这些都在 Target 侧决定。Initiator 看到的只是"一块盘",它不知道底层是 RAID5 还是单盘。所以容灾的兜底责任在存储侧,客户端别指望它自己知道数据是否冗余;存储侧做了快照/复制,客户端层面的备份策略依然要按本地盘的强度执行。
#会话与路径:多路径冗余的是链路,不是数据
一台服务器可以同时从一个 target 的多块网卡、甚至多个 target 发起多条路径,这就是 multipath 的场景。路径多不代表数据多个副本,它只是"多条路到同一个目的地"——冗余的是链路,不是数据。数据冗余只能由存储侧(RAID、快照、复制)提供。配置层面还有一条联动:multipath 能切换路径,但若会话本身被判死(网络抖动超过 node.session.timeo.replacement_timeout,数据库场景一般设 60-120s),所有路径都会 failed,所以"会话超时 + multipath"必须配套。
#LUN 与文件系统:客户端自己的事
Initiator 端拿到 LUN 之后,fdisk/分区/mkfs 都是客户端自己的事。LUN 对客户端就是一块裸盘,有三种用法:
- 直接 mkfs 挂载:不分区的裸文件系统,迁移时整块带走,最灵活;
- 分区后使用:分区表在客户端,换机器要重新认盘;
- 拿去做 LVM 的 PV:以后扩 LUN 后可以 pvresize 在线扩容,非常顺。
个人习惯是裸 LUN + LVM:存储侧扩容 LUN → 客户端触发 SCSI rescan(向对应 /sys/class/scsi_device/<h:c:t:l>/device/rescan 写 1 让内核重新读容量)→ pvresize → lvextend → grow 文件系统(XFS 用 xfs_growfs,ext4 用 resize2fs),全程在线。这是块存储最舒服的用法,也是"LUN 对客户端是裸盘"这句话最直接的收益。
#设备名与持久化:两个最经典的坑
- /dev/sdX 会漂移:设备名按发现顺序分配,重启后可能从 sdb 变成 sdc。fstab 里直接写 /dev/sdb 是定时炸弹,要用 UUID、/dev/disk/by-id/ 或 multipath WWID;挂载参数上记得
_netdev,nofail(等网络就绪再挂、失败不阻塞开机),必要时加x-systemd.device-timeout=30。 - 会话重启不恢复:
iscsiadm -m node --login只是这一次登录。要让开机自动重登,把 node 记录的node.startup设为 automatic,并确保systemctl enable --now iscsid;重启后还丢盘,先看journalctl -u iscsid -u iscsi,常见原因是 CHAP 没写进 node 记录、或 startup 没设 automatic。
#排查纪律
链路通但发现不到 target,多半是防火墙或 portal 配置问题;发现得到但登录失败,看 CHAP 和 ACL;登录成功但 IO 卡,查多路径和链路质量。逐层排查,不要一上来就重启 iscsid——重启只是掩盖症状,前面四层阶梯命令已经能把问题定位到具体某一层。
#什么时候不该用它
最后记住一句话:iSCSI 给你的不是"网络硬盘",而是"SCSI 语义的远程块设备"。所有在本地盘上要注意的事(备份、一致性、监控),在 iSCSI 上一样都不能少,反而因为多了网络这一层,要更谨慎。
反过来,某些场景一开始就不该选 iSCSI:大量小文件、多客户端共享同一目录树的需求,文件级共享(NFS/CIFS)更合适;对延迟和并发有极致要求的,考虑 FC 或 NVMe-oF;跨公网传输又无法保证隔离的网络,先别碰明文 iSCSI。块设备语义是好东西,但它只解决"把一块盘搬到网络上"这一件事,别让它去干文件系统、分布式存储该干的活。
如果要把这条链路继续做扎实,下一步顺着三篇配套文章走:用 targetcli 发布块设备的完整细节、open-iscsi 客户端的持久化与断线恢复、multipath 多路径可用性。本文提供的是整套机制的地图,那三篇是地图上的具体路口。
相关阅读
Related留言讨论
0 条还没有留言,来说点什么吧。