Kubernetes 测试镜像 volume/iscsi 全解析:iSCSI Target 容器的构建、运行与 E2E 集成机制
本篇文章以 Kubernetes 仓库中 test/images/volume/iscsi/README.md 为主线,系统讲解这套用于存储 E2E 测试的 iSCSI Target(iSCSI 服务器端)容器镜像:它如何在容器内借助宿主内核与 targetcli 暴露一个真实的 iSCSI 目标,供 kubernetes.io/iscsi 内置卷插件挂载验证。读完你将掌握该镜像目录内每个文件的作用、create_block.sh 生成测试块设备的原理、run_iscsi_target.sh 配置 LIO target 的完整命令,以及它被 Kubernetes 存储 E2E 框架调度消费的完整调用链。
一、容器定位:为 iSCSI 卷插件测试提供"服务器端"
Kubernetes 的存储测试需要一个能随时拉起、可重复销毁的存储服务器。在 test/e2e/storage/drivers/in_tree.go 中,iSCSI 被实现为一个 in-tree 测试驱动(kubernetes.io/iscsi):测试先在集群里启动一个 iSCSI Target 容器作为"卷提供者",再让挂载该卷的客户端 Pod 去访问它。
本镜像扮演的正是这个角色——iSCSI target 端(即服务器端)。它的特点是把 Linux 内核自带的 LIO target 能力封装进容器:容器本身并不常驻 daemon,而是启动后通过 targetcli 在宿主内核中注册一个 IQN 和对应 LUN,等待 initiator(测试客户端)通过 3260 端口连接。整个仓库目录位于 test/images/volume/iscsi/,与同级的 NFS 测试容器(test/images/volume/nfs/)并列,共同构成 test/images/volume 下最基础的两种共享存储测试服务器。
test/images/volume/
├── OWNERS
├── iscsi/ # 本文主题:iSCSI target 测试容器
└── nfs/ # NFS server 测试容器
二、README 声明的两条硬性运行前提
原 README 开门见山地给出了容器运行的两个关键约束,这两点是理解整个镜像设计的基础:
- 必须挂载宿主机的
/lib/modules:容器需要向宿主机内核插入 iSCSI 相关的内核模块(如iscsi_target_mod、target_core_mod、target_core_file等,由 LIO 使用)。这要求宿主机预先安装好这些模块(原文:This assumes that these modules are installed on the host!)。 - 必须以
docker --privileged(特权模式)运行:因为容器要在宿主内核中创建 target 设备、配置内核路径,普通受限容器没有这些权限。
这两点与 E2E 框架中该镜像的调度方式是呼应的:见下文第七节,服务端 Pod 需要绑定 /lib/modules、/sys/kernel 等宿主目录并运行在 hostNetwork 上。
三、目录文件清单与职责
镜像目录下共 8 个条目,各自职责如下:
| 文件 | 作用 |
|---|---|
| Dockerfile | 镜像构建描述:安装 targetcli、拷贝入口脚本与预置块设备 |
| BASEIMAGE | 多架构基础镜像映射,当前为 Fedora 42 各架构变体 |
| VERSION | 镜像版本号,当前为 2.7 |
| create_block.sh | 构建期脚本(须以 root 运行):生成 block.tar.gz 测试块设备 |
| block.tar.gz | 已生成的约 120MB ext2 块设备文件压缩包,直接 ADD 进镜像 |
| run_iscsi_target.sh | 容器 ENTRYPOINT:用 targetcli 配置并启动 iSCSI target |
| README.md | 使用说明(本文主体) |
| BASEIMAGE / VERSION 配套的构建约定 | 供镜像发布流水线读取 |
其中 BASEIMAGE 与 VERSION 被 K8s 的镜像发布体系(PromoterE2eRegistry)读取——详见 test/utils/image/manifest.go,其中把该镜像注册为 volume/iscsi,版本 2.7:
// VolumeISCSIServer image
VolumeISCSIServer
...
configs[VolumeISCSIServer] = Config{list.PromoterE2eRegistry, "volume/iscsi", "2.7"}
镜像由此以 VolumeISCSIServer 这一常量名被 E2E 测试统一引用(见下文第七节),而无需在测试里硬编码镜像地址。
四、block.tar.gz:预置的 ext2 测试块设备
README 指出:block.tar.gz 是一个由 create_block.sh(须以 root 运行)生成的、很小的 ext2 文件系统。阅读 create_block.sh 可看到它的完整生成流程:
MNTDIR="$(mktemp -d)"
# Create 120MB device with ext2
# (volume_io tests need at least 100MB)
dd if=/dev/zero of=block seek=120 count=1 bs=1M
mkfs.ext2 block
# Add index.html to it
mount -o loop block "$MNTDIR"
echo "Hello from iscsi" > "$MNTDIR/index.html"
umount "$MNTDIR"
tar cfz block.tar.gz block
几个值得注意的实现细节:
- 大小 120MB 是有讲究的:脚本注释明确说明
volume_io tests need at least 100MB,即运行 volume I/O 类测试需要至少 100MB 的空间预算,因此这里用dd的seek方式快速创建一个稀疏的 120MB 文件,再格式化为 ext2。 - 写入标识文件:文件系统内唯一的文件是
index.html,内容为Hello from iscsi。这并非随意为之——E2E 测试客户端挂载该卷后,需要校验卷内容(如读取该文件并断言内容),README 之外的测试逻辑依赖这一约定。 - 为什么必须 root 执行:脚本使用了
mount -o loop(loop 挂载)与mkfs.ext2,这两步都需要特权。脚本还通过trap cleanup TERM EXIT保证中途失败时自动卸载并清理临时文件与block文件。
Dockerfile 中 ADD block.tar.gz / 会把解压后的 /block(一个裸的 ext2 块文件)放进镜像根目录,作为后续 target 暴露的"磁盘"来源。
五、Dockerfile 与基础镜像构建
test/images/volume/iscsi/Dockerfile 非常精简,完整内容如下:
ARG BASEIMAGE
FROM $BASEIMAGE
RUN yum install -y targetcli && yum clean all
ADD run_iscsi_target.sh /usr/local/bin/
ADD block.tar.gz /
ENTRYPOINT ["/usr/local/bin/run_iscsi_target.sh"]
要点解读:
ARG BASEIMAGE允许构建时注入多架构基础镜像。对应 BASEIMAGE 文件给出了四个平台各自的 Fedora 42 镜像:
Kubernetes 官方 e2e 镜像普遍采用这种"按架构映射基础镜像"的方式做多架构发布。linux/amd64=fedora:42 linux/arm64=arm64v8/fedora:42 linux/ppc64le=ppc64le/fedora:42 linux/s390x=s390x/fedora:42- 唯一运行时依赖是
targetcli:镜像内并不安装 iscsid 之类的传统用户态 target 守护进程,而是使用 Linux 内核 LIO(Linux-IO Target)的用户态管理工具targetcli直接在内核侧配置 target。 - ENTRYPOINT 不接收 shell:容器启动时直接执行
/usr/local/bin/run_iscsi_target.sh,该脚本会把命令行第一个参数当作 IQN 使用(见下节)。
六、入口脚本:run_iscsi_target.sh 的 target 配置原理
run_iscsi_target.sh 是整个容器的核心。脚本注释明确说明它不运行任何 daemon,只是在内核中配置 iSCSI target,且可以在同一节点上多次运行,每次运行都会创建自己独立的 IQN 与 LUN——这正是 E2E 测试并行创建多个隔离卷服务器的前提。
6.1 启动:注册 IQN 与 LUN
脚本把第一个参数作为 IQN(iSCSI Qualified Name)传入 start 函数:
IQN=$1
function start()
{
# Create new IQN (iSCSI Qualified Name)
targetcli /iscsi create "$IQN"
# Run it in demo mode, i.e. no authentication
targetcli /iscsi/"$IQN"/tpg1 set attribute authentication=0 demo_mode_write_protect=0 generate_node_acls=1 cache_dynamic_acls=1
# Create unique "block volume" (i.e. flat file) on the *host*.
cp /block /srv/iscsi/"$IQN"
# Make the block volume available through our IQN as LUN 0
targetcli /backstores/fileio create block-"$IQN" /srv/iscsi/"$IQN"
targetcli /iscsi/"$IQN"/tpg1/luns create /backstores/fileio/block-"$IQN"
echo "iscsi target started"
}
逐步拆解:
targetcli /iscsi create "$IQN"—— 创建 IQN 对应的 target portal group。- 一组属性配置让测试无需鉴权即可直连:
authentication=0:关闭 CHAP 认证(测试环境下最省事);demo_mode_write_protect=0:关闭 demo 模式的写保护,允许客户端写入;generate_node_acls=1/cache_dynamic_acls=1:动态为任意发起端生成并缓存访问控制列表,使任何 initiator 都能直接访问而无需预先手工加 ACL。
- 块设备文件必须放在宿主机而非容器内:脚本将镜像内预置的
/block拷贝为/srv/iscsi/$IQN。注释解释了原因——若块文件存在于容器文件系统内,内核会因某种原因无法同时为多个容器提供多个 LUN;因此/srv/iscsi必须是从宿主 bind-mount 进来的目录(/srv/iscsi must be bind-mount from the host)。 - 用 fileio backstore 把该文件注册为块存储
block-$IQN,再把它作为 LUN 0 关联到目标 tpg1 上。
配置成功后打印 iscsi target started——这个字符串正是 E2E 框架等待的服务就绪信号(见第七节 ServerReadyMessage)。
6.2 停止:信号驱动的清理逻辑
function stop()
{
echo "stopping iscsi target"
# Remove IQN
targetcli /iscsi/"$IQN"/tpg1/luns/ delete 0
targetcli /iscsi delete "$IQN"
# Remove block device mapping
targetcli /backstores/fileio delete block-"$IQN"
/bin/rm -f /srv/iscsi/"$IQN"
echo "iscsi target stopped"
exit 0
}
trap stop TERM
start
while true; do
sleep 1
done
脚本主体是一个"注册 TERM 信号陷阱 → 执行 start → 空转保活"的结构:启动完成后进入 while true; sleep 1 的无限循环让容器保持存活;当 Pod 被删除/终止时收到 TERM 信号,stop 按逆序清理 LUN、IQN、fileio backstore 与宿主机上的块文件,做到服务端退出后内核与宿主机不留残留状态。
七、E2E 框架如何调度该镜像
要理解这套镜像的价值,需要看它在真实测试链路里的调用点。核心入口在 test/e2e/storage/drivers/in_tree.go。
7.1 驱动与卷源
// iSCSI
// The iscsiadm utility and iscsi target kernel modules must be installed on all nodes.
type iSCSIDriver struct {
driverInfo storageframework.DriverInfo
}
InitISCSIDriver() 把驱动能力注册为:in-tree 插件名 kubernetes.io/iscsi,标记 feature.Volumes,支持默认 fsType 与 ext4,并声明 CapPersistence/CapFsGroup/CapBlock/CapExec/CapMultiPODs/CapTopology/CapMultiplePVsSameID 等一系列能力——其中 CapBlock 对应块设备模式测试。驱动测试文件类型时生成如下卷源:
volSource := v1.VolumeSource{
ISCSI: &v1.ISCSIVolumeSource{
TargetPortal: "127.0.0.1:3260",
IQN: iv.iqn,
Lun: 0,
ReadOnly: readOnly,
},
}
注意 TargetPortal 固定写为 127.0.0.1:3260、LUN 恒为 0:因为测试客户端被强制调度到与 target 服务端相同的节点上(见下节),通过回环地址即可访问 hostNetwork 上监听的 3260 端口,这与镜像脚本把块设备暴露为 LUN 0 的设计完全咬合。
7.2 服务端 Pod 的创建参数
newISCSIServer(同一文件第 361 行起)按集群内唯一命名空间生成 IQN:
// Generate cluster-wide unique IQN
iqn = fmt.Sprintf(iSCSIIQNTemplate, namespace)
其中模板常量定义为:
// Template for iSCSI IQN.
iSCSIIQNTemplate = "iqn.2003-01.io.k8s:e2e.%s"
即形如 iqn.2003-01.io.k8s:e2e.<namespace> 的全局唯一 IQN,正好对应脚本首个参数。随后构造服务端配置:
config = e2evolume.TestConfig{
Namespace: namespace,
Prefix: "iscsi",
ServerImage: imageutils.GetE2EImage(imageutils.VolumeISCSIServer),
ServerArgs: []string{iqn},
ServerVolumes: map[string]string{
// iSCSI container needs to insert modules from the host
"/lib/modules": "/lib/modules",
// iSCSI container needs to configure kernel
"/sys/kernel": "/sys/kernel",
// iSCSI source "block devices" must be available on the host
"/srv/iscsi": "/srv/iscsi",
// targetcli uses dbus
"/run/dbus": "/run/dbus",
},
ServerReadyMessage: "iscsi target started",
ServerHostNetwork: true,
}
这里把 README 中"挂载 /lib/modules"的约束落实为 /lib/modules、/sys/kernel(内核配置接口)、/srv/iscsi(块文件存放目录)、/run/dbus(targetcli 依赖 dbus)四个宿主目录的 bind-mount,并指定服务就绪消息为脚本打印的 iscsi target started。容器参数正是脚本入口需要的 IQN。
随后框架通过 test/e2e/framework/volume/fixtures.go 中的 CreateStorageServer 启动该 Pod 并取其 Pod IP:
func CreateStorageServer(ctx context.Context, cs clientset.Interface, config TestConfig) (pod *v1.Pod, ip string) {
pod = startVolumeServer(ctx, cs, config)
...
ip = pod.Status.PodIP
...
return pod, ip
}
7.3 客户端与服务端的同节点绑定
由于 iSCSI 走内核 target 且测试环境通常不开防火墙,E2E 把客户端限制在与服务端同一节点上:
// Make sure the client runs on the same node as server so we don't need to open any firewalls.
config.ClientNodeSelection = e2epod.NodeSelection{Name: pod.Spec.NodeName}
这就解释了为什么卷源中 target portal 可以直接使用 127.0.0.1:3260 回环地址——客户端与服务端同机,走宿主回环即可命中 target。同时 ServerHostNetwork: true 保证服务端 Pod 直接监听宿主的 3260 端口。
八、使用与二次构建要点汇总
综合以上源码分析,可归纳出这套 iSCSI 测试镜像的完整使用与构建流程:
- 构建期(离线一次性完成):以 root 运行 create_block.sh 生成
block.tar.gz(内含 120MB ext2 文件与index.html,内容为Hello from iscsi);随后按 Dockerfile 构建镜像:安装targetcli、放入入口脚本与块设备压缩包。 - 运行期(每个测试用例动态执行):把镜像作为 privileged 容器启动,传入唯一 IQN 作为第一个参数,并 bind-mount
/lib/modules、/sys/kernel、/srv/iscsi(及 dbus 目录)到容器;容器启动即通过targetcli在宿主内核注册 LUN 0 并打印iscsi target started。 - 对测试客户端:在宿主已安装
iscsiadm与 target 内核模块的前提下,客户端通过kubernetes.io/iscsi卷插件指向127.0.0.1:3260+ 该 IQN + LUN 0 即可挂载由index.html标识内容的 ext2 卷。 - 清理期:Pod 收到
TERM信号后执行stop,逆序删除 LUN、IQN、fileio backstore 与宿主块文件;由于每次运行使用独立 IQN,同一节点可并行运行多个彼此隔离的 target 实例。
若要在自己的集群中复现这一套"容器化内核态存储服务器"方案,只需遵循两条原则:特权模式 + 宿主目录透传。这也是 Linux 内核 LIO 无法像用户态 NFS 那样被完全容器化的根本原因——target 配置本质上是内核对象,容器只是配置内核的工具载体。
九、小结
本文从 test/images/volume/iscsi/README.md 出发,把这条仅有十几行的简短说明还原成了一个完整的工程方案:一个以 Fedora 42 为基础、以 targetcli 驱动 Linux 内核 LIO、以预置 120MB ext2 块设备作为卷后端、以 IQN 作为隔离边界的 iSCSI target 容器。它体现了 Kubernetes 测试基础设施中一个常见设计模式——用容器化、参数化的"存储服务器"镜像,为 in-tree 卷插件提供隔离、可重复、可并发的真实存储后端。对希望理解 Kubernetes 存储测试架构、或自行搭建 iSCSI 目标做验证的开发者,这个目录都是值得精读的范本。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00