SPDK和容器相结合的应用场景分享

科技   科技   2023-06-28 12:00   上海  


容器技术通过打包运行环境和依赖到可移植的镜像中实现轻量级的资源隔离,真正做到了一次编译,随处运行,且各个实例的初始状态均一致。这种快捷部署、扩展和一致性的特性,让容器技术在近些年来广泛流行,衍生出了多种相关的技术类型并吸引了各大应用向容器环境迁移。本文将以docker容器为例,介绍SPDK与容器相结合的3个主要应用场景:(1)“在容器中实现高性能的用户态存储应用”;(2)“为容器提供高性能的外部存储”;(3)“与IPU (Infrastructure Processing Unit,业界也称DPU-- Data Processing Unit) 结合加速容器镜像的准备和容器启动过程”。


一. 在容器中实现高性能的用户态存储应用

将SPDK的应用程序在docker环境中运行的案例在SPDK的官方网站上其实已有简单的介绍[1] 。概括起来就是将SPDK应用和相应的依赖包打包到与编译环境相匹配的镜像中运行。此处将列举在Host下编译容器镜像和运行容器实例的主要步骤。在系统中安装好docker命令工具、设置好源&代理等配置参数并使用docker pull hello-world测试网络访问正常后,即可按如下步骤来执行具体的步骤。

1) 准备编译docker容器镜像的Dockerfile (内容参见如下,此处选择fedora 35作为目标镜像)

# start with the latest Fedora 35

FROM fedora:35

# if you are behind a proxy, set that up now

ADD dnf.conf ./dnf.conf

# these are the min dependencies for the hello_world app

RUN dnf install libaio-devel -y

RUN dnf install numactl-devel -y

# set our working dir

WORKDIR /app

# add the hello_world binary

ADD hello_world hello_world

# run the app

CMD ./hello_world

2) 准备docker容器文件

在目标镜像对应的OS环境编译SPDK的用户态NVMe驱动example 程序hello_world以及用于docker中使用的配置代理的文件dnf.conf(dnf.conf文件是一个通用的文件,并无特殊的地方,其内容参考系统下/etc/dnf/dnf.conf) ,与前步中的Dockerfile放在同一个目录下。

3) 编译容器镜像

进入存放上述文件的目录执行如下命令编译相应的hello镜像。

docker build ./ -t hello:1.0

编译生成image后即可使用docker images 命令进行查询,输出可举例如下所示。

REPOSITORY    TAG       IMAGE ID       CREATED         SIZE

hello         1.0       f650b0c3ffba   5 weeks ago     452MB

4) 运行容器

编译镜像完成后即可按如下操作运行相应的容器。

A. 在系统中执行SPDK源码目录下的./scripts/setup.sh将系统下看到的NVMe设备(如bus/device/fun为d9:00.0)指定给vfio_pci或者uio_pci_generic驱动。

B. 执行如下命令启动docker实例

docker run --privileged -v /dev/hugepages:/dev/hugepages -v /dev/shm:/dev/shm hello:1.0

 

//命令执行过程的输出可参考如下。

[2022-12-10 13:40:17.888222] [ DPDK EAL parameters: [2022-12-10 13:40:17.888253] hello_world [2022-12-10 13:40:17.888263] --no-shconf [2022-12-10 13:40:17.888275] -c 0x1 [2022-12-10 13:40:17.888284] --log-level=lib.eal:6 [2022-12-10 13:40:17.888293] --log-level=lib.cryptodev:5 [2022-12-10 13:40:17.888303] --log-level=user1:6 [2022-12-10 13:40:17.888312] --iova-mode=pa [2022-12-10 13:40:17.888320] --base-virtaddr=0x200000000000 [2022-12-10 13:40:17.888330] --match-allocations [2022-12-10 13:40:17.888339] --file-prefix=spdk_pid1 [2022-12-10 13:40:17.888359] ]

EAL: No available 1048576 kB hugepages reported

TELEMETRY: No legacy callbacks, legacy socket not created

Initializing NVMe Controllers

Attaching to 0000:d9:00.0

Attached to 0000:d9:00.0

  Namespace ID: 1 size: 1600GB

Initialization complete.

INFO: using host memory buffer for IO

Hello world!


当然,也可使用docker run -itd --privileged --name=hello -v /dev/hugepages:/dev/hugepages hello:1.0 /bin/bash命令后台运行docker实例,然后通过docker exec -it hello /bash/bin登录到docker实例中手动执行“./hello_world” 。

此处仅以最简单的SPDK示例程序展示了在容器中运行SPDK应用的过程,实际中可根据需求配置具体的SPDK的应用。


二. 为容器提供提供高性能的外部存储

众所周知,给docker容器提供外部持久化存储,可通过bind主机的目录,或者创建volume的方式来实现[2] 。如果要用SPDK提供的用户态存储来给容器作外部存储,目前主要是将SPDK的bdev块设备在主机Host的通用块层呈现为对应的块设备,然后对块设备进行格式化,再以mount或者创建快捷链接的方式让docker的volume创建到该存储中。关键点在于如何将SPDK初始化的bdev块设备资源高效地在主机Host下呈现。

目前而言,将SPDK初始化的bdev块设备资源作为主机下通用块设备给容器使用的方式主要有NBD (Network Block Device)机制和通过主机NVMe驱动走TCP loopback连接local  NVMe-oF tgt的方式。新的SPDK版本(V23.05)也增加了另外的1种新的更高效的方式: Userspace Block Device Driver (ublk driver)[3] 。并且在后续的版本中,可能还会增加VDUSE (vDPA Device in Userspace) 的相应机制[5]。其中,ublk drviver结合Linux内核UBLK_DRV驱动的框架(在6.0以后的内核支持)以uring的方式来实现,同NBD一样,可以对接各种SPDK的bdev块设备类型;而VDUSE则主要是针对virtio类型的SPDK bdev块设备,同样也需要依赖相应的内核模块配合(VDUSE框架在5.15内核版本已有合入)。各种对接的方式可参见图1所示。 

图 1将SPDK的bdev块设备提供给容器使用的可能方式示意

本处将结合ublk driver的方式将SPDK的bdev块设备资源在主机Host下以通用块设备呈现的主要步骤举例说明如下。

1)升级Linux内核

6.0以上的内核版本已合入了UBLK_DRV的驱动,所以本处的例子中,需要先升OS使用的内核为6.0及以上的内核版本(如6.1.0-rc7+)并在编译内核时使能UBLK_DRV的选项。在确认升级后的内核中包含了ublk_drv.ko和ublk_cmd.h后即可以满足ublk driver的测试环境需求。

2) 安装io uring头文件和库

可以从https://github.com/axboe/liburing下载liburing的源码执行configure然后编译安装,对应liburing.h的头文件和liburing.so的库文件会被安装到系统相应的目录下。需要注意系统原先是否已装有ublk_cmd.h、liburing.h的头文件和liburing的库文件(如放在/usr/lib64目录下)避免实际运行过程中出现连接的库文件不符合预期。

3) 下载包含ublk driver 特性的SPDK版本并编译

SPDK V23. 05的版本包含了 ublk driver 特性的target支持。下载对应的版本后,可在执行configure操作时加上” --with-ublk  --with-uring=/usr”的参数进行编译。

4) 启动spdk_tgt程序并添加 ublk的设备

A. 启动spdk_tgt

./build/bin/spdk_tgt -m 0xf &

B. 通过RPC命令创建ublk target

./scripts/rpc.py ublk_create_target

C. 创建后端的bdev

./scripts/rpc.py bdev_malloc_create -b Malloc0 128 512

D. 为ublk target添加设备

./scripts/rpc.py ublk_start_disk Malloc0 0 -q 1 -d 512

以上操作后即可在Host下看到/dev/ublkb0的块设备。

5) 格式化系统下呈现的ublk 设备,并mount到/var/lib/docker/volume

mkfs -t ext4 /dev/ublkb0

mount /dev/ublkb0 /var/lib/docker/volumes

此时即可看到挂载后的/dev/ublkb0,如图2所示。

图 2将SPDK的bdev块设备以ublk的方式在linux系统下呈现并格式化挂载示意

6) 创建docker volume

docker volume create test-volume

7) 启动docker容器并指定volume的挂载

docker run -itd --privileged --name=hello1 --mount type=volume,source=test-volume,destination=/usr/share -v /dev/hugepages:/dev/hugepages hello:1.0 /bin/bash

#启动容器后可使用docker inspect hello1命令来查看具体的volume挂载信息

"Mounts": [

            {

                "Type": "bind",

                "Source": "/dev/hugepages",

                "Destination": "/dev/hugepages",

                "Mode": "",

                "RW": true,

                "Propagation": "rprivate"

            },

            {

                "Type": "volume",

                "Name": "test-volume",

                "Source": "/var/lib/docker/volumes/test-volume/_data",

                "Destination": "/usr/share",

                "Driver": "local",

                "Mode": "z",

                "RW": true,

                "Propagation": ""

            }

        ],

8) 在docker容器中创建需要保存的数据文件

通过查看对比Host下/var/lib/docker/volumes/test-volume/_data目录下的内容和使用docker exec -it hello1 /bin/bash命令登录docker容器实例后在/usr/share目录下的内容是一致的。可以使用echo “test for docker volume with storage provided by SPDK” > test 在docker的/usr/share目录新建test文件,再到Host的/var/lib/docker/volumes/test-volume/_data目录中可以看到对应的内容。

9) 关闭容器并在ublk对应的设备中查看容器中创建的数据文件

使用docker stop hello1关闭容器,然后再到Host下的/var/lib/docker/volumes/test-volume/_data目录中依然可以看到容器中生成的文件。具体可如图3所示。

图 3在docker容器中向SPDK提供的外部存储volume写入数据并进行查看

VDUSE在容器中的应用方式与ublk driver类似,主要是在系统下生成块设备的方式有所不同,对应块设备被容器所使用的过程是完全一样的。具体在系统创建生成VDUSE对应类型的块设备的步骤可等待后续SPDK版本中包含VDUSE时的帮助信息描述, 此处不再详细展开。

此外,对于Kata容器,还可以直接和SPDK的vhost进行对接,具体的配置可参见参考信息[4] ,本文也不进行具体展开。


三. 与IPU结合加速容器镜像的准备和容器启动过程

通过IPU加速容器镜像的实现主要依赖了IPU对主机提供的virtio-blk/NVMe类型的PF/VF来为容器应用提供解压后的镜像存储,将镜像的下载和构建放到IPU上执行[6]。这样可以提前准备好资源,尽可能实现镜像重用并支持热拔插。该过程的具体结构可举例如图4中所示。

图 4通过运行在IPU上的SPDK加速容器镜像构建主要框架示意

IPU对主机提供的virtio-blk/NVMe的PF/VF设备对应后端的存储主要是通过运行在IPU上的SPDK来模拟后者抽象,其对应的真实设备可以是远端的存储也可以是IPU本地下挂的物理存储设备。这种应用方式的核心操作主要是将docker image解压后的bundle构建到IPU对主机提供的virtio-blk或NVMe PF/VF对应的后端bdev块设备中。IPU上的动作,一方面是访问容器的镜像Registry下载原始镜像并进行解压,另一方面则是通过aio或者NBD的方式将镜像的bundle文件构建到对应的 SPDK  bdev中。

目前Intel提供的基于FPGA的IPU和基于ASIC的IPU均可以实现该应用场景功能的支撑。此处将以基于FPGA的BSC(Big Spring Canyon)型IPU为例,具体列举通过BSC上对Host主机提供的virtio-blk的PF/VF设备来提供容器镜像的步骤和过程。在本例子中省略了在BSC中下载容器镜像和解压的过程,直接以手动下载并解压制作的镜像bundle文件拷贝到BSC上的系统中以NBD的方式来进行镜像的构建。例子中使用的bsc_tgt涉及的源码并未完全开源(可以通过特定的方式获取),但其中涉及的构建镜像bdev以及bdev相关的操作步骤和命令对SPDK而言是通用的。其具体过程可举例如下。

1)下载容器镜像、解压制作bundle文件并拷贝到BSC上的SoC系统中

A.下载并解压容器镜像 

skopeo copy docker://busybox:latest oci:busybox:latest

umoci unpack --image busybox:latest bundle

B.制作bundle文件

dd if=/dev/zero of=image_test2.file bs=1024K count = 1024 //Generate a 1GB file.

mkfs -t ext4 -q image_test2.file

losetup /dev/loop12 /home/ image_test2.file //Find an unused loop device

mount /dev/loop12 /home/ image_file2

umoci unpack --image /home/ busybox:latest bundle

umount /home/ image_file2

losetup -d /dev/loop12

//以此种方式生成的bundle文件,可以同时适用于以aio或者NBD的方式构建到SPDK bdev中。

C.拷贝生成的bundle文件到BSC上的SoC系统中 

可以通过BSC的管理网口将文件拷贝到BSC上的系统中,如拷贝到/home目录下。

2)配置virtio-blk在IPU上的后端设备

A.配置并启动BSC SPDK APP 

具体运行命令可举例如下。bsc_tgt即为基于SPDK编程框架实现的配合BSC型IPU使用的target程序,可以通过SPDK的RPC命令进行配置和管理。

./bsc_tgt/bsc_tgt -s 5G --base-virtaddr 0x2000000000 -m 0x01

B.创建virtio-blk的PF/VF对应的后端bdev设备

#create blk-net device 

./scripts/rpc.py bsc_open_mgm_interface "0000:05:00.0"

./scripts/rpc.py virtio_blk_net_construct bndev0 "0000:05:00.4"

#create lvol bdev and export it through NBD mechanism

./scripts/rpc.py bdev_malloc_create -b Malloc0 1024 512

./scripts/rpc.py  bdev_lvol_create_lvstore Malloc0 lvol0

./scripts/rpc.py  bdev_lvol_create -l lvol0 bdev_lvol0 768

3)将镜像bundle文件构建到virtio-blk PF/VF对应的实际的后端bdev中
以NBD的方式将容器镜像的bundle文件拷到上述创建的bdev中。其中,使用SPDK bdev的snapshot和clone来实现容器镜像的复用,提高资源使用效率。

./scripts/rpc.py  nbd_start_disk lvol0/bdev_lvol0 /dev/nbd0

#format the nbd block device and copy Container image

mkfs -t ext4 /dev/nbd0 

mkdir /mnt/test_for_nbd 

mount /dev/nbd0 /mnt/test_for_nbd/

cp -r /home/bundle /mnt/test_for_nbd/

./scripts/rpc.py  nbd_stop_disk  /dev/nbd0

#create snapshot and clone from lvol bdev

./scripts/rpc.py  bdev_lvol_snapshot lvol0/bdev_lvol0 snap_bdev_lvol0

./scripts/rpc.py  bdev_lvol_clone lvol0/snap_bdev_lvol0 clon0

./scripts/rpc.py  bdev_lvol_clone lvol0/snap_bdev_lvol0 clon1

./scripts/rpc.py  bdev_lvol_clone lvol0/snap_bdev_lvol0 clon2

#Map clone bdevs to different port of blk-net device

#use port_id to identify the specific PF/VF export to host 

./scripts/rpc.py  virtio_blk_net_map_bdev bndev0 3 lvol0/clon0  //port 3 for VF

./scripts/rpc.py  virtio_blk_net_map_bdev bndev0 7 lvol0/clon1  //port 7 for VF 

./scripts/rpc.py  virtio_blk_net_map_bdev bndev0 124 lvol0/clon2 //port 124 for PF

4)在主机侧初始化vritio-blk的PF/VF并基于此启动容器(注意以下步骤在BSC IPU所接在的Host下执行)

#List virtio-blk PF provided by BSC 

lspci | grep -i virtio

#Probe virtio-blk driver and create VFs

modprobe virtio_blk 

echo 2 > /sys/bus/pci/devices/0000\:18\:00.3/sriov_numvfs

mount block devices initialized from virtio-blk PF/VF

mount /dev/vdc /mnt/test_for_runc/

#run container image

cd /mnt/test_for_runc/bundle/

runc run test

#can mount block devices initialized from PF and VFs separately and use them 

indepently


以上3种应用场景即为目前SPDK与Container相结合的主要应用举例。除此以外,SPDK还抽象出了SMA (Storage Management Agent)接口用于更好地和orchestrator软件进行对接。例如基于SMA接口,可以很方便地通过SPDK-CSI的插件和K8S (Kubernetes) 等软件进行对接,实现批量动态化地创建存储资源、连接挂载创建的存储资源并提供给VM或者容器使用,相关内容将在后续进行更新。期待SPDK在持续的发展中为容器应用的发展创造更多的价值。


参考信息:
1. https://spdk.io/doc/containers.html
2. https://blog.51cto.com/u_15127549/3609056 
3. https://docs.kernel.org/block/ublk.html
4. https://github.com/kata-containers/documentation/blob/master/use-cases/using-SPDK-vhostuser-and-kata.md#host-setup-for-vhost-user-devices
5. https://www.kernel.org/doc/html/latest/userspace-api/vduse.html
6. 利用DPU/IPU 卸载容器镜像以及文件系统的相关操作

作者:曾军 刘孝冬 边一帆 杨子夜

转载须知

DPDK与SPDK开源社区

公众号文章转载声明

推荐阅读

利用DPU/IPU 卸载容器镜像以及文件系统的相关操作

DPDK上的P4应用实例


点点“赞”“在看”,给我充点儿电吧~

DPDK与SPDK开源社区
最权威,最贴近DPDK/SPDK技术专家的社区。
 最新文章