iSCSI Target 配置:用 targetcli 发布块设备

使用 targetcli 创建 iSCSI Target、LUN 和访问控制,并给出持久化与验证步骤。

技术文章约 19 分钟138 次阅读

#这篇文章解决什么问题

本文要解决一个具体问题:如何把一台 Linux 服务器上的块设备(例如一块空闲的 SATA/NVMe 盘)发布为网络存储,让其他机器通过 iSCSI 协议挂载和读写。它背后最基础的机制是:Linux 内核里住着一个名为 LIO(Linux IO Target)的 target 子系统,它把 SCSI 协议的"服务端"完整实现进了内核态;而 targetcli 这个命令行工具,本质上是操作一棵名为 configfs 的内核对象树的外壳。理解了"内核对象树"这条主线,targetcli 那些看似绕口的树状路径就不再有门槛:backstores(盘在哪)→ iSCSI Target(门面身份)→ LUN(窗口)→ ACL(谁可以进),每一步都对应内核里真实存在的一个对象。

#原理层:它为什么是这样工作的

#iSCSI 的本质:把 SCSI 命令装进 TCP/IP

存储世界里有两个角色:initiator(发起端)发出 SCSI 命令,例如服务器上的 HBA 卡或软件 initiator(open-iscsi);target(目标端)接收并执行命令,把命令翻译成对本地磁盘的实际读写。SCSI 命令的载体是 CDB(Command Descriptor Block,命令描述块),典型的如 READ(10)、WRITE(10)、INQUIRY。iSCSI 做的事情只有一件:把这些 CDB 封装进 TCP/IP 数据包,在网络上传输(协议规范为 RFC 3720,后更新为 RFC 7143)。

因此 iSCSI 是"块级"共享:客户端拿到的是一块"看起来像本地磁盘"的 SCSI 盘,分区、文件系统、缓存策略全部由客户端自己决定。这与 NFS/CIFS 的"文件级"共享有本质区别——后者共享的是目录树和文件语义,前者共享的是原始扇区。这一区别决定了 iSCSI 的典型用途:虚拟化平台的共享存储、数据库的块设备存储、需要把整块磁盘"迁移"到另一台服务器继续使用的场景。也正因为是块级共享,多个客户端同时写同一块盘时不存在文件系统层面的锁,必须由上层(集群文件系统或应用)保证一致性,这一点在后面"边界与坑"里还会展开。

一次 iSCSI 会话(session)的建立要经过 login 阶段:先是 SecurityNegotiation(安全协商,CHAP 认证就在这里发生),然后是 LoginOperationalNegotiation(协商最大连接数、首播/每次读写的长度等运行参数),最后进入 FullFeaturePhase(全功能阶段,才开始真正传输数据)。协议允许一个会话里包含多个 TCP 连接,这为多路径(multipath)留下了空间。理解这条登录链路,对后面定位"能发现但登录失败"的问题很有帮助。

#LIO:在内核里实现 target 服务端

Linux 上实现 iSCSI target 曾经有多种方案:早期常见的 scsi-target-utils(stgt)是一个用户态守护进程,把存储协议栈放到用户态实现;SCST 走的是另一条内核补丁路线,但长期游离在主线之外。这些方案要么性能受限于用户态与内核态之间的切换和拷贝,要么维护困难、难以进主线。

LIO 的路线不同:把 target 实现直接放进内核。它最初由 Datera(当时名为 Rising Tide Systems)开发并推进上游,target core(模块名 target_core_mod)从 Linux 2.6.38 进入主线,iSCSI fabric(模块名 iscsi_target_mod)从 Linux 3.1 进入主线;RHEL 7 起,LIO + targetcli 取代用户态 stgt,成为 RHEL 系官方维护的 iSCSI target 方案。

LIO 内部可以分成三层:

  • target core:通用的 target 引擎,负责管理逻辑单元(LU)、命令队列、ACL 授权,以及把命令调度到不同的 backstore;
  • backstore 驱动:负责"命令最终落在什么介质上",block(块设备)、fileio(文件)、ramdisk(内存)都是这一层的实现;
  • fabric 驱动:负责"命令从哪个协议进来",iscsi_target_mod 是 iSCSI 的 fabric,tcm_loop 提供本地回环 fabric。

一次完整的读操作,数据路径大致是:

客户端网卡 → TCP/IP 栈 → iSCSI PDU 解析(内核 iscsi_target_mod)
→ CDB 交给 target core → 调度到 block backstore
→ 内核块层请求(bio)→ 磁盘 → 响应沿原路返回

全程不出内核。这就是 LIO 与用户态 target 的根本差异:省掉了用户态与内核态之间的多层切换与拷贝,也避免了用户态进程成为单点瓶颈。生产环境选择 LIO 而不是跑一个用户态 daemon,理由正在于此。

#configfs:一棵可以被 mkdir 的内核对象树

targetcli 的配置是一棵树,这棵树不是 UI 的噱头,而是内核对象树的直接投影。Linux 里有两个"以文件系统形式管理内核对象"的机制,容易混淆:

  • sysfs 挂载在 /sys,反映内核已经存在的对象,用户态主要是读取属性;
  • configfs 挂载在 /sys/kernel/config(现代发行版通常由 systemd 自动挂载),特殊之处在于:用户态可以 mkdir 创建对象、rmdir 删除对象、向属性文件写入配置,内核会实时响应并真正创建或销毁对应的内核对象。

LIO 把整棵配置树暴露在 /sys/kernel/config/target 之下。你可以亲手验证,configfs 挂载后,目录结构就摆在那里:

mount | grep configfs
ls /sys/kernel/config/target
ls /sys/kernel/config/target/iscsi

targetcli 是构建在 rtslib 之上的一个交互式 shell:你输入的 create、set、ls、saveconfig,本质上分别对应 mkdir、写属性文件、读目录、把整棵树导出成 JSON。这解释了 targetcli 的三种使用形态——交互式 shell、heredoc 批处理、单条子命令(如直接执行 targetcli saveconfig)——它们操作的是同一棵 configfs 树。也解释了为什么每次改动都"立即生效":因为你改的就是内核对象本身,不存在"保存后才生效"的两段式。

#发布一块盘的因果链:backstore → target → TPG → LUN → ACL

把一块裸盘发布成可用的 iSCSI 存储,实际上是在内核里依次创建五类对象,每一步都建立在前一步之上:

  1. backstore:把块设备包装成 target core 可以管理的存储对象。这一步决定"底层是什么介质、命令如何落盘"。block backstore 直接走内核块层,性能最接近本地盘,生产环境用它;fileio 用普通文件模拟,适合测试,但多一层文件系统开销;ramdisk 用内存当盘,重启即丢,只适合开发验证。
  2. target:用 IQN 给这台 SCSI 设备一个全局唯一身份,相当于门面。targetcli 创建 target 时会自动带出一个默认的 tpg1 和监听 0.0.0.0:3260 的 portal。
  3. TPG(Target Portal Group,目标门户组):把监听地址(portal)、LUN、ACL 组织成一个可寻址单元。简单场景只有一个 TPG、一个 portal,可以暂时把 TPG 理解为"该 target 对外提供服务的入口集合"。
  4. LUN(Logical Unit Number,逻辑单元号):把 backstore 对象挂到 TPG 上,成为"这台设备上的第几号磁盘抽屉"。SCSI 世界里每个 target 可以挂多个 LUN,客户端用"target + LUN 号"来寻址,第一个 LUN 从 0 开始编号。
  5. ACL(Access Control List,访问控制列表):告诉 LIO"哪个 initiator 可以登录"。LIO 默认拒绝一切连接(generate_node_acls 默认关闭),所以没有显式 ACL 的客户端连不进来——这是安全默认,不是故障。

原稿里那句口诀——backstores(盘)→ iSCSI Target(门面)→ LUN(窗口)→ ACL(谁可以进)——就是这条因果链的浓缩:先有介质,再有身份,再开窗口,最后放行。

#IQN 与 TPG:两个必须先理解的名词

IQN(iSCSI Qualified Name)是 iSCSI 节点的全局唯一标识,格式为:

iqn.<年份>-<月份>.<反向域名>:<标识>

例如 iqn.2026-08.com.example:storage01,含义是"2026 年 08 月注册、域名 example.com 的倒序形式、标识为 storage01"。命名必须规范,因为客户端、监控、多路径配置、排障日志全靠这个字符串区分存储;随便起名后期会非常痛苦。

TPG 的细节到生产环境会遇到:一个 target 可以同时监听多个地址(例如双网卡、多 IP),这一组 portal 合起来就是一个 TPG;客户端如果记录了同一 target 的多个 portal,可以视为同一设备的多个入口,这是多路径的基础。简单场景只有一个 TPG,但了解它存在,才能在看到配置树里默认生成的 tpg1 时知道那是什么。

#操作层:完整可复现的配置过程

#环境与前置条件

  • 发行版:本文命令以 dnf 系发行版(Fedora、RHEL 8 及更新版本)为例。RHEL 6 时代还存在过基于用户态 stgt 的方案,RHEL 7 之后 LIO + targetcli 是官方路线。Debian/Ubuntu 对应的软件包名为 targetcli-fb,命令用法一致,但服务名与持久化细节以各自发行版文档为准。
  • 内核:需要包含 LIO。主线内核 2.6.38 之后的发行版默认都有,现代发行版直接可用;动手前可以用 modinfo 确认 iSCSI fabric 内核模块存在(执行 modinfo 不需要先安装 targetcli,查的是内核模块元数据)。
  • 权限:全部操作需要 root,下面命令统一用 sudo 表示。
  • 块设备:准备一块空闲盘。发布前确认它没有被本地文件系统挂载(lsblk 的 MOUNTPOINT 列为空)。如果机器上只有系统盘,也可以用空闲分区做实验,但生产发布建议用独立整盘。
  • 客户端:验证环节需要另一台装 initiator 软件的机器,dnf 系对应包名 iscsi-initiator-utils(open-iscsi),它提供 iscsiadm 命令和 /etc/iscsi/initiatorname.iscsi 配置文件。

#安装 targetcli 并确认内核与设备

modinfo iscsi_target_mod | head -n 3
sudo dnf install -y targetcli
sudo lsblk -o NAME,SIZE,TYPE,MOUNTPOINT

lsblk 输出示例:

NAME   SIZE TYPE MOUNTPOINT
sda  119.2G disk /
sdb     20G disk

本例中 /dev/sda 是系统盘(挂在 / 上),/dev/sdb 是一块 20G 的空闲盘,就是准备发布的设备。发布前再用 mount 确认一遍没有本地挂载点,避免误发布在用盘。

#第一步:创建 block backstore

sudo targetcli <<'EOF'
backstores/block create name=disk01 dev=/dev/sdb
EOF

语法说明:backstores/block 是要进入的目录,create 是子命令,name 是对象名(会出现在 configfs 树里),dev 是块设备路径。执行后用 ls 查看树,应该能看到 block 存储对象 disk01 已经出现。

原稿反复强调的警告在这里:targetcli 会把 /dev/sdb 直接交给 LIO,客户端一旦登录并写入,盘上的原有数据会被覆盖式使用。发布前务必确认这就是你打算发布的那块盘,不要拿着系统盘练手。

#第二步:创建 target 与 LUN

sudo targetcli <<'EOF'
/iscsi create iqn.2026-08.com.example:storage01
/iscsi/iqn.2026-08.com.example:storage01/tpg1/luns create /backstores/block/disk01
EOF

逐句看因果:

  • /iscsi create 创建 fabric 对象。执行后内核里出现一个 iSCSI target,targetcli 自动带上 tpg1 和默认 portal 0.0.0.0:3260(即监听所有网卡的 3260 端口)。此时还没有任何 LUN,相当于一栋空楼。
  • tpg1/luns create 把 disk01 挂成 LUN。targetcli 自动分配最小的可用 LUN 号,第一个就是 LUN 0。客户端将来访问的就是这个"target + LUN 0"组合。

查看当前配置树:

sudo targetcli ls

预期输出(不同 targetcli 版本格式略有差异,但结构一致):

o- / ............................................................. [...]
  o- backstores ..................................................... [...]
  | o- block ...................................... [Storage Objects: 1]
  | | o- disk01 ........................... [/dev/sdb (20.0GiB) write-thru activated]
  | o- fileio ..................................... [Storage Objects: 0]
  | o- ramdisk .................................... [Storage Objects: 0]
  o- iscsi ......................................................... [...]
    o- iqn.2026-08.com.example:storage01 ........................... [...]
      o- tpg1 ................................................ [enabled, no-auth]
        o- acls ...................................................... [...]
        o- luns ...................................................... [...]
          o- lun0 ........................ [block/disk01 (/dev/sdb) (default_tg_pt_gp)]
        o- portals .................................................. [...]
          o- 0.0.0.0:3260 .............................................. [OK]

注意两个细节:luns 下已经出现 lun0 并指向 block/disk01;portals 下出现 0.0.0.0:3260。括号里的 write-thru 表示该 backstore 处于直写模式(LIO 默认不开写缓存,避免断电一致性风险),activated 表示对象已激活。此时 tpg1 标注还是 no-auth,下一步配 ACL。

#第三步:配置 ACL 与双向 CHAP

先在客户端机器上取它的 initiator IQN(即"我是谁"):

cat /etc/iscsi/initiatorname.iscsi

输出形如:

InitiatorName=iqn.2026-08.com.client:web01

然后在 target 侧添加 ACL 并配置双向 CHAP(Challenge-Handshake Authentication Protocol,质询握手认证协议):

sudo targetcli <<'EOF'
/iscsi/iqn.2026-08.com.example:storage01/tpg1/acls create iqn.2026-08.com.client:web01
/iscsi/iqn.2026-08.com.example:storage01/tpg1/acls/iqn.2026-08.com.client:web01 set auth userid=storage_user password=strong-pass-123
/iscsi/iqn.2026-08.com.example:storage01/tpg1/acls/iqn.2026-08.com.client:web01 set auth mutual_userid=storage_user mutual_password=strong-pass-123
EOF

CHAP 的机制值得说清楚:

  • 单向 CHAP:登录时 initiator 向 target 证明"我知道密码",防止未授权者伪装成合法客户端。
  • 双向 CHAP(mutual):除此之外,target 还要向 initiator 证明自己,防止中间人冒充 target 骗取数据。set auth 配 userid/password 是单向部分,配 mutual_userid/mutual_password 才把双向补全。
  • RFC 3720 对 CHAP 密钥有硬性约束:至少 12 字节;若超过 16 字节,只有前 16 字节参与计算。也就是说密码设得再长,超出的部分不会生效,还容易让排障变得迷惑。
  • 原稿经验:双向 CHAP 的密码要单独生成一组,别和系统或其他服务的密码混用。

这里还隐藏着一个客户端侧的必做步骤:target 要求 CHAP,客户端必须在 /etc/iscsi/iscsid.conf 里声明同样的凭据,否则登录会报 Authentication failure。由于本配置启用了双向 CHAP,客户端需要同时配置两个方向的凭据(username/password 对应 target 侧 userid/password,username_in/password_in 对应 mutual 部分):

node.session.auth.authmethod = CHAP
node.session.auth.username = storage_user
node.session.auth.password = strong-pass-123
node.session.auth.username_in = storage_user
node.session.auth.password_in = strong-pass-123

注意这份配置文件在客户端上,与 target 侧的 saveconfig.json 是两回事,改完需要重启 iscsid 服务生效。

#第四步:持久化配置

关键因果:targetcli 的所有改动都实时写进了 configfs——也就是内核内存里的对象树。/sys 和 /sys/kernel/config 都不持久,重启后这棵树就没了。所以必须做两件事:把树导出成文件,并让开机时自动重建。

sudo targetcli saveconfig
sudo systemctl enable --now target
sudo systemctl is-active target

逐条解释:

  • saveconfig:把当前 configfs 树序列化成 JSON,写入 /etc/target/saveconfig.json(跨发行版路径有差异,RHEL/Fedora 系是这个路径)。这是"此刻状态的快照",它本身不会自动发生,每次改完配置都要再执行一次。
  • systemctl enable --now target:把 target 服务设为开机自启并立即启动。target 服务启动时会执行 restore(回放),把 saveconfig.json 里的对象逐一重建回 configfs 树。
  • systemctl is-active target:确认服务处于 active 状态。

有两个常见误解需要纠正:saveconfig 不是自动的,改完不存,重启即丢;enable 只是让服务开机跑,不会替你 saveconfig。正确顺序永远是"先 saveconfig 存快照,再让服务去恢复快照"。

#完整命令链一览

把从安装到持久化的全部命令汇总成一份可直接执行的序列(dnf 系发行版、以 root 或 sudo 执行),便于整体复现:

# 1. 安装与确认
sudo dnf install -y targetcli
sudo lsblk -o NAME,SIZE,TYPE,MOUNTPOINT

# 2. 创建 block backstore(确认 /dev/sdb 是目标盘)
sudo targetcli <<'EOF'
backstores/block create name=disk01 dev=/dev/sdb
EOF

# 3. 创建 target 与 LUN
sudo targetcli <<'EOF'
/iscsi create iqn.2026-08.com.example:storage01
/iscsi/iqn.2026-08.com.example:storage01/tpg1/luns create /backstores/block/disk01
EOF

# 4. ACL 与双向 CHAP(把 iqn.2026-08.com.client:web01 换成客户端真实 IQN)
sudo targetcli <<'EOF'
/iscsi/iqn.2026-08.com.example:storage01/tpg1/acls create iqn.2026-08.com.client:web01
/iscsi/iqn.2026-08.com.example:storage01/tpg1/acls/iqn.2026-08.com.client:web01 set auth userid=storage_user password=strong-pass-123
/iscsi/iqn.2026-08.com.example:storage01/tpg1/acls/iqn.2026-08.com.client:web01 set auth mutual_userid=storage_user mutual_password=strong-pass-123
EOF

# 5. 持久化并启用服务
sudo targetcli saveconfig
sudo systemctl enable --now target
sudo systemctl is-active target

# 6. (可选)防火墙放行 iSCSI 默认端口
sudo firewall-cmd --add-service=iscsi-target --permanent
sudo firewall-cmd --reload

到这里 target 侧就绪:/dev/sdb 已经以 LUN 0 的身份出现在 iqn.2026-08.com.example:storage01 的 tpg1 下,只等被授权的 initiator 来登录。

#验证层:怎么确认我做对了

#Target 侧复查

sudo targetcli ls
sudo systemctl status target

检查要点:

  • luns 下存在 lun0,且指向 block/disk01(显示 backing store 与设备名);
  • acls 下存在客户端的 initiator IQN,认证属性已设置(用 set 后 ls 能看到 userid 与 mutual_userid);
  • portals 下监听地址为 0.0.0.0:3260 或你指定的 IP:端口,状态为 OK;
  • systemctl status target 显示 active (running);
  • 如有疑惑,可再看内核日志:journalctl -u target 或 dmesg,登录事件通常会在 target 侧留下记录。

#客户端验证

客户端需要先安装 initiator 软件并启动守护进程(dnf 系):

sudo dnf install -y iscsi-initiator-utils
sudo systemctl enable --now iscsid

然后执行发现与登录:

sudo iscsiadm -m discovery -t sendtargets -p <target-ip>
sudo iscsiadm -m node -T iqn.2026-08.com.example:storage01 -p <target-ip> --login
lsblk -S
sudo iscsiadm -m session

预期结果与判定标准:

  • discovery 返回一条形如 192.168.10.20:3260,1 iqn.2026-08.com.example:storage01 的记录,说明网络与 portal 层面通了。注意:discovery 只负责"找到 target",真正的身份校验发生在 login 阶段,所以能发现不代表能登录。
  • --login 成功、没有 Authentication failure 报错,说明 ACL 与双向 CHAP 都通过了。
  • lsblk -S 出现一块新的 SCSI 盘(例如 sdc),它对应远端的 LUN 0;更稳的标识看 /dev/disk/by-path 下形如 ip-:3260-iscsi--lun-0 的链接。
  • iscsiadm -m session 列出已建立的会话,包含 TCP 连接与 target IQN。

原稿的判定标准依然成立:登录成功说明 ACL、CHAP、portal 全部打通——因为 login 是 iSCSI 鉴权的关口,initiator 必须落在某条 ACL 上、CHAP 密钥必须双方一致、portal 必须能被 TCP 访问,任何一个环节不对都会在这里失败。登录后,这块新盘就是普通块设备,可以分区、mkfs、挂载;生产上通常再交给多路径或集群文件系统管理。

#边界与坑:常见失败模式与解决路径

#数据被覆盖:发布前必须确认盘

targetcli 把 /dev/sdb 直接交给 LIO 后,客户端的写入会直接作用在这块盘上;如果盘上原有数据,等于把写权限暴露给了网络。发布前用 lsblk 核对设备号、用 mount 确认没有本地挂载点。生产上建议使用专门的裸盘,不要图省事导出带有其他用途的分区,更不要导出系统盘。

#配置变更先备份、改动无撤销

原稿经验:改 ACL 或 LUN 之前,先 saveconfig 留一份当前配置;改完再存一次。targetcli 没有"撤销"按钮——每条 create/delete/set 都实时生效,回滚只能靠配置文件。具体回滚路径:saveconfig 产出的 JSON 是当前树的快照,改坏了就恢复旧 JSON 再让 target 服务重建树,或在 targetcli 里手动删除出错的对象。至少做到每次变更前留一份带时间戳的备份:

sudo cp /etc/target/saveconfig.json /etc/target/saveconfig.json.bak.$(date +%Y%m%d-%H%M%S)
sudo targetcli saveconfig

另外注意:saveconfig.json 里包含 ACL 的明文口令,文件权限要保护好(root 所有、owner 可读写即可),不要把它提交进版本库或随意拷贝。

#CHAP 的密码长度与单向/双向不一致

前面提过 RFC 3720 对 CHAP 密钥的要求:至少 12 字节,超过 16 字节只取前 16 字节。所以配密码时 12 到 16 个字符最合适。另一个高频坑是单双向不一致:target 侧只配了单向 CHAP(userid/password),客户端却只填了 username_in/password_in,登录同样会失败;反之亦然。两端对不上时,报错几乎都是 Authentication failure。原稿建议单独生成一组密码、不复用系统密码,这里再补一句:应定期轮换,并纳入密码管理流程。

#换盘时 backstore 的 name 不要变

原稿经验:生产换盘(例如把 /dev/sdb 换成 /dev/sdc)时,block backstore 的 name 保持不变,只改 dev。原因是客户端看到的设备标识由 target、LUN 结构和 backstore 对象共同决定;一旦 name 变了,客户端 /dev/disk/by-path 下的路径与挂载表全部要跟着改。替换流程:先 saveconfig 留快照、记录客户端挂载点,然后 create 同名 backstore 指向新盘、把 LUN 重新挂一遍,最后在客户端确认设备路径不变再更新挂载信息。

#多客户端共享同一 LUN 的一致性

本文配置的是"一台 target、若干客户端各挂各的 LUN"场景。如果多个客户端同时挂载并写入同一个 LUN,块级共享没有文件系统锁,数据一致性完全取决于上层:要么使用集群文件系统(如 GFS2、OCFS2),要么由应用层自己保证互斥。误用场景是单机写、多机同时挂,会导致文件系统损坏。这类需求应该先想清楚共享语义,而不是靠 iSCSI 硬扛。

#排障路径:从哪一层下手

按网络、鉴权、存储的顺序排查:

  • discovery 超时或提示 No portals found:多半是网络或防火墙问题——3260 端口未放行、portal 只监听了某张网卡而客户端走了另一张网卡、目标 IP 不可达。先用 ping 和 telnet 3260 验证连通性。
  • discovery 成功但 login 报 Authentication failure:CHAP 用户名或密码两端不一致、密码超过 16 字节被截断、客户端漏配 mutual CHAP 字段。
  • login 后会话被重置(connection reset):检查 target 侧 dmesg 与 journalctl -u target,常见原因是 backstore 指向的设备异常(例如设备被拔出或离线)。
  • 配置树里 LUN 显示 error:backstore 指向的设备不存在或不可访问,用 lsblk 核对设备号,必要时重建 backstore。

#什么时候不该用它

  • 只是给几台机器共享文件:NFS 更简单,语义也更适合文件级并发访问。
  • 追求极低时延或超高吞吐:NVMe-oF(内核 nvmet)或 RDMA 体系是更现代的方案,iSCSI 的 TCP 路径在极致场景下有天花板。
  • 单机自用:直接用本地磁盘即可,iSCSI 不会让访问变快,只会多一层网络开销和排障面。

#收尾

从机制到命令,本文的主线是:先想清楚"内核里要出现哪些对象、它们之间依赖什么",再动手敲 targetcli。每次配置都可以用这条因果链自检:backstore 是介质、target 是身份、TPG 是地址、LUN 是窗口、ACL 是门禁;saveconfig 把树落盘,target 服务负责开机重建。下一步建议:给 target 规划独立网段或专网、对 3260 端口与会话数量做监控告警、定期演练"换盘 + 恢复 saveconfig"的流程。记住,iSCSI 的坑大多不在命令本身,而在"没想清楚树里每个对象是谁、重启后还在不在"——想清楚了,targetcli 只是把想法落进内核的一双手。

相关阅读

Related

留言讨论

0 条

还没有留言,来说点什么吧。

iSCSI Target 配置:用 targetcli 发布块设备 · LXH·BLOG