NVMeとConnectX-4で始めるNVMe over RDMA (RoCEv2)入門

目次

概要

近年、NVMe over Fabric (=NVMeoF) という言葉をよく聞くようになりました。
この Fabric 自体は NVMe のプロトコルをネットワーク上に流すことでリモートホスト上からターゲットホストの NVMe Disk に直接IO操作を行うものです。
同じような目的を持ったプロトコルとしては iSCSI などがあげられます。
しかし、SCSI 自体が近年目覚ましく進化したフラッシュストレージ (=SSD) の性能に追いつけていないのが現状です。
この問題に対処するため、より効率的なプロトコルとして新たに NVMe プロトコルが策定されました。
NVMe over Fabric は効率的な NVMe プロトコルを利用することにより iSCSI よりも高速にIOを裁くことができるメリットがあり、注目されています。

NVMe over Fabric は通信に使用するプロトコルとして Infiniband や Ethernet 、さらには FibreChannel を選択することができます。
加えて、RDMA(=Remote Direct Memory Access) などの技術を用いることでより高速・低遅延を実現しています。

RDMA 自体は Infiniband の技術です。
しかし、RDMA を Ethernet 上で利用するための技術として、RoCE (RDMA over Converged Ethernet) があります。
この RoCE は v1 と v2 が存在し、RoCEv1 は Link Layer のプロトコルであるため異なるネットワークセグメント間において接続することができませんでした。
しかし RoCEv2 では Internet Layer のプロトコルとなったため、ルーティングが可能となり、異なるネットワークセグメント間においても使用することができるようになりました。

まとめると、低遅延でかつより高速にリモートホスト上の NVMe にアクセスするための技術として、NVMe over RDMA があり、これを Ethernet 上で利用するために RoCEv2を用いる必要があるということです。

また、RoCE 自体は途中経路上のスイッチにも設定が必要です。
このため、RDMA を用いずに TCP で直接接続するプロトコルとして、NVMe over TCP が存在します。
しかし、Mellanox (NVIDIA) の ConnectX-4 以降においてはスイッチの設定を不要とする Zero Touch RoCE が使用でき、より簡単に環境構築が可能となりました。

今回はそんな ConnectX-4 を用いながら、実際に NVMe over RDMA (RoCEv2) な環境を構築し、軽くパフォーマンスなどを見ていきます。
(NVMe over TCPについては後日検証します。)

環境

今回の検証に使用した環境です。
マシンは2台用意し、それぞれをターゲット(Server)側、Initiator(Client)側としています。

ターゲット側

詳細
Machine HPE ProLiant DL380 Gen10
CPU Intel Xeon Silver 4208 x2
Memory DDR4 ECC RDIMM 128GB (16GB x8 2400MHz)
NIC Mellanox ConnectX-4 100Gb(Ethernet) MCX415A-CCAT
NVMe Intel DC P4600 1.6TB U.2 x2
OS Ubuntu Server 22.04 LTS

Initiator側

詳細
Machine Fujitsu Primergy RX2540 M4
CPU Intel Xeon Gold 6148 x1
Memory DDR4 ECC RDIMM 128GB (32GB x4 2666MHz)
NIC Mellanox ConnectX-4 100Gb(Ethernet) MCX415A-CCAT
OS Ubuntu Server 22.04 LTS

Mellanoxドライバのインストール

事前に、RDMAに対応した Mellanox ドライバである OFED をインストールしておく必要があります。
ドライバは公式サイトからダウンロードできます。

# tar -xzvf MLNX_OFED_LINUX-5.6-2.0.9.0-ubuntu22.04-x86_64.tgz
# cd MLNX_OFED_LINUX-5.6-2.0.9.0-ubuntu22.04-x86_64
# ./mlnxofedinstall --with-nvmf
# /etc/init.d/openibd restart

インストール完了後、念のために initramfs を更新しておきます。
しなかった場合、この後に行うカーネルモジュールの読み込みでエラーになる場合があります。

# update-initramfs -u
# reboot

RoCEv2サポートの確認

念のため、公式サイトを参考にNICがRoCEv2に対応していることを確認しておきます。
ネットワークインタフェース名を確認しておきます。

# cat /sys/class/infiniband/mlx5_0/ports/1/gid_attrs/ndevs/0
ens5np0
# cat /sys/class/infiniband/mlx5_0/ports/1/gid_attrs/ndevs/1
ens5np0

今回は 0 と 1 が該当するものでした。
続いて、確認した情報をもとにサポートしているかどうかを確認します。

# cat /sys/class/infiniband/mlx5_0/ports/1/gid_attrs/types/0
IB/RoCE v1
# cat /sys/class/infiniband/mlx5_0/ports/1/gid_attrs/types/1
RoCE v2

RoCE v2をサポートしていることが確認できました。

カーネルモジュールのロード

ターゲット側、Initiator側の両方でカーネルモジュールを読み込んでおきます。

# modprobe mlx5_core
# lsmod | grep mlx
mlx5_ib               434176  0
ib_uverbs             135168  6 rdma_ucm,mlx5_ib
ib_core               417792  8 rdma_cm,ib_ipoib,iw_cm,ib_umad,rdma_ucm,ib_uverbs,mlx5_ib,ib_cm
mlx5_core            1888256  1 mlx5_ib
mlxdevm               172032  1 mlx5_core
mlxfw                  32768  1 mlx5_core
psample                20480  1 mlx5_core
tls                   106496  2 bonding,mlx5_core
mlx_compat             69632  11 rdma_cm,ib_ipoib,mlxdevm,iw_cm,ib_umad,ib_core,rdma_ucm,ib_uverbs,mlx5_ib,ib_cm,mlx5_core
pci_hyperv_intf        16384  1 mlx5_core
# modprobe nvmet
# modprobe nvmet-rdma
# modprobe nvme-rdma
# lsmod | grep nvme
nvme_rdma              40960  0
nvme_fabrics           24576  1 nvme_rdma
nvmet_rdma             57344  0
nvmet                 135168  1 nvmet_rdma
rdma_cm               122880  3 nvme_rdma,nvmet_rdma,rdma_ucm
ib_core               417792  10 rdma_cm,ib_ipoib,nvme_rdma,nvmet_rdma,iw_cm,ib_umad,rdma_ucm,ib_uverbs,mlx5_ib,ib_cm
nvme                   49152  2 nvmet,nvmet_rdma
nvme_core             122880  4 nvmet,nvme,nvme_rdma,nvme_fabrics
mlx_compat             69632  17 rdma_cm,ib_ipoib,mlxdevm,nvmet,nvme,nvme_rdma,nvmet_rdma,iw_cm,nvme_core,nvme_fabrics,ib_umad,ib_core,rdma_ucm,ib_uverbs,mlx5_ib,ib_cm,mlx5_core

ネットワーク設定

この段階でネットワーク設定を行っておきます。
手順については省略します。
なお、今回は次のような構成にしています。

ターゲット側 10.0.0.1/24 ---------- 10.0.0.2/24 Initiator側

nvme-cliのインストール

ターゲット側とInitiator側の両方に nvme-cli をインストールしておきます。

# apt install nvme-cli

ターゲット側の設定

今回は、ターゲット側のソフトウェアとして SPDK (=Storage Performance Development Kit) を用います。
git から clone してビルドします。

[email protected]:~# git clone https://github.com/spdk/spdk
[email protected]:~# cd spdk/
[email protected]:~/spdk# git submodule update --init
[email protected]:~/spdk# scripts/pkgdep.sh --rdma
[email protected]:~/spdk# ./configure --with-rdma --enable-debug
[email protected]:~/spdk# make

テストを実施しておきます。
All unit tests passed の表示を確認できればOKです。

[email protected]:~/spdk# ./test/unit/unittest.sh
<省略>
=====================
All unit tests passed
=====================
WARN: lcov not installed or SPDK built without coverage!
WARN: neither valgrind nor ASAN is enabled!

テストをパスしたことが確認出来たら、NVMeドライバの変更を行います。
変更には、SPDK 内の setup.sh を用います。
先に現在の状態を確認します。

[email protected]:~/spdk# scripts/setup.sh status
Hugepages
node     hugesize     free /  total
node0   1048576kB        0 /      0
node0      2048kB        0 /      0
node1   1048576kB        0 /      0
node1      2048kB        0 /      0

Type     BDF             Vendor Device NUMA    Driver           Device     Block devices
I/OAT    0000:00:04.0    8086   2021   0       ioatdma          -          -
I/OAT    0000:00:04.1    8086   2021   0       ioatdma          -          -
I/OAT    0000:00:04.2    8086   2021   0       ioatdma          -          -
I/OAT    0000:00:04.3    8086   2021   0       ioatdma          -          -
I/OAT    0000:00:04.4    8086   2021   0       ioatdma          -          -
I/OAT    0000:00:04.5    8086   2021   0       ioatdma          -          -
I/OAT    0000:00:04.6    8086   2021   0       ioatdma          -          -
I/OAT    0000:00:04.7    8086   2021   0       ioatdma          -          -
NVMe     0000:39:00.0    8086   0a54   0       nvme             nvme0      nvme0n1
NVMe     0000:3a:00.0    8086   0a54   0       nvme             nvme1      nvme1n1
I/OAT    0000:80:04.0    8086   2021   1       ioatdma          -          -
I/OAT    0000:80:04.1    8086   2021   1       ioatdma          -          -
I/OAT    0000:80:04.2    8086   2021   1       ioatdma          -          -
I/OAT    0000:80:04.3    8086   2021   1       ioatdma          -          -
I/OAT    0000:80:04.4    8086   2021   1       ioatdma          -          -
I/OAT    0000:80:04.5    8086   2021   1       ioatdma          -          -
I/OAT    0000:80:04.6    8086   2021   1       ioatdma          -          -
I/OAT    0000:80:04.7    8086   2021   1       ioatdma          -          -

現在はカーネル上の ioatdma や nvme で制御が行われていることがわかります。
続いて、念のため NVMe をフォーマットして初期化しておきます。
検証環境では、以前ブロックデバイスとして使用していたディスクを使用したためか、この後の操作で Active devices: [email protected], so not binding PCI dev といったような表示となりドライバの変更を行うことが出来ませんでした。
フォーマットは次のコマンドで実施できます。
(使用するすべてのNVMeに対して実施しました。)

[email protected]:~# nvme format /dev/nvme0n1

先ほどの setup.sh をオプションなしで実行することで uio_pci_generic に変更します。

[email protected]:~/spdk# scripts/setup.sh
0000:39:00.0 (8086 0a54): nvme -> uio_pci_generic
0000:3a:00.0 (8086 0a54): nvme -> uio_pci_generic
0000:00:04.7 (8086 2021): ioatdma -> uio_pci_generic
0000:00:04.6 (8086 2021): ioatdma -> uio_pci_generic
0000:00:04.5 (8086 2021): ioatdma -> uio_pci_generic
0000:00:04.4 (8086 2021): ioatdma -> uio_pci_generic
0000:00:04.3 (8086 2021): ioatdma -> uio_pci_generic
0000:00:04.2 (8086 2021): ioatdma -> uio_pci_generic
0000:00:04.1 (8086 2021): ioatdma -> uio_pci_generic
0000:00:04.0 (8086 2021): ioatdma -> uio_pci_generic
0000:80:04.7 (8086 2021): ioatdma -> uio_pci_generic
0000:80:04.6 (8086 2021): ioatdma -> uio_pci_generic
0000:80:04.5 (8086 2021): ioatdma -> uio_pci_generic
0000:80:04.4 (8086 2021): ioatdma -> uio_pci_generic
0000:80:04.3 (8086 2021): ioatdma -> uio_pci_generic
0000:80:04.2 (8086 2021): ioatdma -> uio_pci_generic
0000:80:04.1 (8086 2021): ioatdma -> uio_pci_generic
0000:80:04.0 (8086 2021): ioatdma -> uio_pci_generic

変更が行われたか確認しておきます。
uio_pci_generic になっていればOKです。

[email protected]:~/spdk# scripts/setup.sh status
Hugepages
node     hugesize     free /  total
node0   1048576kB        0 /      0
node0      2048kB     1024 /   1024
node1   1048576kB        0 /      0
node1      2048kB        0 /      0

Type     BDF             Vendor Device NUMA    Driver           Device     Block devices
I/OAT    0000:00:04.0    8086   2021   0       uio_pci_generic  -          -
I/OAT    0000:00:04.1    8086   2021   0       uio_pci_generic  -          -
I/OAT    0000:00:04.2    8086   2021   0       uio_pci_generic  -          -
I/OAT    0000:00:04.3    8086   2021   0       uio_pci_generic  -          -
I/OAT    0000:00:04.4    8086   2021   0       uio_pci_generic  -          -
I/OAT    0000:00:04.5    8086   2021   0       uio_pci_generic  -          -
I/OAT    0000:00:04.6    8086   2021   0       uio_pci_generic  -          -
I/OAT    0000:00:04.7    8086   2021   0       uio_pci_generic  -          -
NVMe     0000:39:00.0    8086   0a54   0       uio_pci_generic  -          -
NVMe     0000:3a:00.0    8086   0a54   0       uio_pci_generic  -          -
I/OAT    0000:80:04.0    8086   2021   1       uio_pci_generic  -          -
I/OAT    0000:80:04.1    8086   2021   1       uio_pci_generic  -          -
I/OAT    0000:80:04.2    8086   2021   1       uio_pci_generic  -          -
I/OAT    0000:80:04.3    8086   2021   1       uio_pci_generic  -          -
I/OAT    0000:80:04.4    8086   2021   1       uio_pci_generic  -          -
I/OAT    0000:80:04.5    8086   2021   1       uio_pci_generic  -          -
I/OAT    0000:80:04.6    8086   2021   1       uio_pci_generic  -          -
I/OAT    0000:80:04.7    8086   2021   1       uio_pci_generic  -          -

続いて、ターゲットプログラムを起動します。
なお、この後の作業では Shell が最低2つ必要になりますので、事前に用意しておくことをおすすめします。

[email protected]:~/spdk# ./build/bin/nvmf_tgt

起動したら、もう一方の Shell で設定を投入していきます。
まず初めに、RDMA の設定を投入します。
次のコマンドは、I/O ユニットサイズを 8192byte 、最大 I/O ユニットサイズを 131072 、カプセル内のデータサイズを 8192byte としています。

[email protected]:~/spdk# scripts/rpc.py nvmf_create_transport -t RDMA -u 8192 -i 131072 -c 8192

続いて、ブロックデバイスの設定を行います。
SPDK では Malloc を使用して RAM ディスクを作成して試すこともできます。
ここでは、両方の手順について記述しておきます。

Malloc を使用する場合

[email protected]:~/spdk# scripts/rpc.py bdev_malloc_create -b Malloc0 512 512
Malloc0
[email protected]:~/spdk# scripts/rpc.py nvmf_create_subsystem nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 -d SPDK_Controller1
[email protected]:~/spdk# scripts/rpc.py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 Malloc0
[email protected]:~/spdk# scripts/rpc.py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a 10.0.0.1 -s 4420

NVMe を使用する場合
bdev_nvme_attach_controller 実施時の -a オプションはPCIeのIDを指定します。
このIDは setup.sh status で確認でき、今回の例では 0000:39:00.0 または 0000:3a:00.0 です。

[email protected]:~/spdk# scripts/rpc.py bdev_nvme_attach_controller -b NVMe0 -a 0000:39:00.0 -t pcie
[email protected]:~/spdk# scripts/rpc.py bdev_nvme_get_controllers
[
  {
    "name": "nvme0",
    "ctrlrs": [
      {
        "state": "enabled",
        "trid": {
          "trtype": "PCIe",
          "traddr": "39:00.0"
        },
        "cntlid": 0,
        "host": {
          "nqn": "nqn.2014-08.org.nvmexpress:uuid:e00e3bdd-93c6-4f3b-83be-0f9965b91e97",
          "addr": "",
          "svcid": ""
        }
      }
    ]
  }
]
[email protected]:~/spdk# scripts/rpc.py nvmf_create_subsystem nqn.2016-06.io.spdk:cnode1 -a -s SPDK00000000000001 -d SPDK_Controller1
<ここで名前を確認しておく>
[email protected]:~/spdk# scripts/rpc.py bdev_get_bdevs | grep "name"
<確認した名前を使用>
[email protected]:~/spdk# scripts/rpc.py nvmf_subsystem_add_ns nqn.2016-06.io.spdk:cnode1 NVMe0n1
[email protected]:~/spdk# scripts/rpc.py nvmf_subsystem_add_listener nqn.2016-06.io.spdk:cnode1 -t rdma -a 10.0.0.1 -s 4420

双方において、listen するアドレスとして 10.0.0.1 を使用し、ポート番号は 4420 を使用していますが、それぞれを環境に合わせて変更してください。
(なお、本来であればポート番号はRoCEv2用にIANAで予約された 4791 を使用するべきだと思います。)

ここからの手順は NVMe を使用した場合の例です。手順は同様ですが、表示が若干異なる場合があります。

ここまでの設定は nvmf_tgt を停止すると初期化されてしまうため、エクスポートしておきます。

[email protected]:~/spdk# scripts/rpc.py save_config > setting.json

エクスポートした設定は nvmf_tgt を起動する際に読み込ませることができます。

[email protected]:~/spdk# ./build/bin/nvmf_tgt -c setting.json

Initiator側の設定

Initiator 側で Discover を実施して検出できるか確認します。

[email protected]:~# nvme discover -t rdma -a 10.0.0.1 -s 4420

Discovery Log Number of Records 1, Generation counter 1
=====Discovery Log Entry 0======
trtype:  rdma
adrfam:  ipv4
subtype: nvme subsystem
treq:    not required
portid:  0
trsvcid: 4420
subnqn:  nqn.2016-06.io.spdk:cnode1
traddr:  10.0.0.1
rdma_prtype: not specified
rdma_qptype: connected
rdma_cms:    rdma-cm
rdma_pkey: 0x0000

無事検出出来たので、接続します。

[email protected]:~# nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode1" -a 10.0.0.1 -s 4420

認識しているか確認します。

[email protected]:~# nvme list
Node                  SN                   Model                                    Namespace Usage                      Format           FW Rev
--------------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- --------
/dev/nvme0n1          SPDK00000000000001   SPDK_Controller1                         1           1.60  TB /   1.60  TB    512   B +  0 B   22.09

無事認識していることが確認できました。

fioを用いてベンチマーク

Initiator 側から NVMe に IO 負荷をかけてベンチマークを実施します。
ベンチマークには fio を用います。

[email protected]:~# apt install fio

シーケンシャルリードでベンチマークをまわしてみます。

[email protected]:~# fio --name=seqread --rw=read --filename=/dev/nvme0n1 --direct=1 --ioengine=libaio --bs=32m --numjobs=2 --size=10G --group_reporting
seqread: (g=0): rw=read, bs=(R) 32.0MiB-32.0MiB, (W) 32.0MiB-32.0MiB, (T) 32.0MiB-32.0MiB, ioengine=libaio, iodepth=1
...
fio-3.28
Starting 2 processes
Jobs: 2 (f=2): [R(2)][100.0%][r=3200MiB/s][r=100 IOPS][eta 00m:00s]
seqread: (groupid=0, jobs=2): err= 0: pid=3997: Sun Jun 26 07:40:59 2022
  read: IOPS=100, BW=3222MiB/s (3378MB/s)(20.0GiB/6357msec)
    slat (usec): min=7776, max=17896, avg=10373.59, stdev=1942.05
    clat (usec): min=4888, max=11913, avg=9478.44, stdev=1901.61
     lat (usec): min=17439, max=22960, avg=19853.26, stdev=319.36
    clat percentiles (usec):
     |  1.00th=[ 5342],  5.00th=[ 6456], 10.00th=[ 7635], 20.00th=[ 7898],
     | 30.00th=[ 8029], 40.00th=[ 8160], 50.00th=[ 8586], 60.00th=[11076],
     | 70.00th=[11338], 80.00th=[11469], 90.00th=[11600], 95.00th=[11731],
     | 99.00th=[11863], 99.50th=[11863], 99.90th=[11863], 99.95th=[11863],
     | 99.99th=[11863]
   bw (  MiB/s): min= 3200, max= 3328, per=99.99%, avg=3221.33, stdev=24.36, samples=24
   iops        : min=  100, max=  104, avg=100.67, stdev= 0.76, samples=24
  lat (msec)   : 10=53.75%, 20=46.25%
  cpu          : usr=0.19%, sys=14.35%, ctx=10189, majf=0, minf=16408
  IO depths    : 1=100.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0%
     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     issued rwts: total=640,0,0,0 short=0,0,0,0 dropped=0,0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=1

Run status group 0 (all jobs):
   READ: bw=3222MiB/s (3378MB/s), 3222MiB/s-3222MiB/s (3378MB/s-3378MB/s), io=20.0GiB (21.5GB), run=6357-6357msec

Disk stats (read/write):
  nvme0n1: ios=0/0, merge=0/0, ticks=0/0, in_queue=0, util=0.00%

3.3GB/s となっており、これは使用したNVMeの公称値とほぼ同様です。

続いて、シーケンシャルライトを試します。

[email protected]:~# fio --name=seqwrite --rw=write --filename=/dev/nvme0n1 --direct=1 --ioengine=libaio --bs=32m --numjobs=2 --size=10G --group_reporting
seqwrite: (g=0): rw=write, bs=(R) 32.0MiB-32.0MiB, (W) 32.0MiB-32.0MiB, (T) 32.0MiB-32.0MiB, ioengine=libaio, iodepth=1
...
fio-3.28
Starting 2 processes
Jobs: 2 (f=2): [W(2)][100.0%][w=1345MiB/s][w=42 IOPS][eta 00m:00s]
seqwrite: (groupid=0, jobs=2): err= 0: pid=4369: Sun Jun 26 08:39:02 2022
  write: IOPS=40, BW=1303MiB/s (1366MB/s)(20.0GiB/15720msec); 0 zone resets
    slat (usec): min=10077, max=40126, avg=24915.36, stdev=5671.09
    clat (usec): min=10707, max=43728, avg=24162.18, stdev=5742.35
     lat (usec): min=30520, max=65408, avg=49078.53, stdev=3970.34
    clat percentiles (usec):
     |  1.00th=[12125],  5.00th=[13960], 10.00th=[16909], 20.00th=[19268],
     | 30.00th=[20579], 40.00th=[22414], 50.00th=[23987], 60.00th=[26084],
     | 70.00th=[27919], 80.00th=[29754], 90.00th=[31327], 95.00th=[32637],
     | 99.00th=[35914], 99.50th=[37487], 99.90th=[43779], 99.95th=[43779],
     | 99.99th=[43779]
   bw (  MiB/s): min= 1152, max= 1408, per=99.99%, avg=1302.71, stdev=33.82, samples=62
   iops        : min=   36, max=   44, avg=40.71, stdev= 1.06, samples=62
  lat (msec)   : 20=26.56%, 50=73.44%
  cpu          : usr=3.30%, sys=3.68%, ctx=10582, majf=0, minf=19
  IO depths    : 1=100.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0%
     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     issued rwts: total=0,640,0,0 short=0,0,0,0 dropped=0,0,0,0
     latency   : target=0, window=0, percentile=100.00%, depth=1

Run status group 0 (all jobs):
  WRITE: bw=1303MiB/s (1366MB/s), 1303MiB/s-1303MiB/s (1366MB/s-1366MB/s), io=20.0GiB (21.5GB), run=15720-15720msec

Disk stats (read/write):
  nvme0n1: ios=0/0, merge=0/0, ticks=0/0, in_queue=0, util=0.00%

こちらも 1.3GB/s となっており公称値と同等です。

EthernetSwitchを挟む

NVMeをもう1台分設定したうえで、経路上に Ethernet Switch を挟んでみます。
使用する Switch は Azure/SONiC を搭載したホワイトボックススイッチです。

ブロックデバイスの設定と同様の手順をターゲット側に実施し、NVMeの設定を追加しておきます。
追加後の Initiator 側からの Discover 結果は次のようになります。

[email protected]:~# nvme discover -t rdma -a 10.0.0.1 -s 4420

Discovery Log Number of Records 2, Generation counter 2
=====Discovery Log Entry 0======
trtype:  rdma
adrfam:  ipv4
subtype: nvme subsystem
treq:    not required
portid:  0
trsvcid: 4420
subnqn:  nqn.2016-06.io.spdk:cnode1
traddr:  10.0.0.1
rdma_prtype: not specified
rdma_qptype: connected
rdma_cms:    rdma-cm
rdma_pkey: 0x0000
=====Discovery Log Entry 1======
trtype:  rdma
adrfam:  ipv4
subtype: nvme subsystem
treq:    not required
portid:  0
trsvcid: 4420
subnqn:  nqn.2016-06.io.spdk:cnode2
traddr:  10.0.0.1
rdma_prtype: not specified
rdma_qptype: connected
rdma_cms:    rdma-cm
rdma_pkey: 0x0000

2つの NVMe を認識していることがわかります。
本来であれば Switch 側に QoS 等の設定が必要なはずですが、今回使用した ConnectX-4 の Zero Touch RoCEv2 によって Switch 側の設定なしに機能しているようです。

複数のInitiatorからIO負荷をかける

複数の Intiator でそれぞれ1台ずつ NVMe を接続した状態で同時に IO 負荷をかけてみます。
結果は次のようになりました。

Initiator 1 : Run status group 0 (all jobs):
   READ: bw=3221MiB/s (3377MB/s), 3221MiB/s-3221MiB/s (3377MB/s-3377MB/s), io=200GiB (215GB), run=63589-63589msec
Initiator 2 : Run status group 0 (all jobs):
   READ: bw=3223MiB/s (3380MB/s), 3223MiB/s-3223MiB/s (3380MB/s-3380MB/s), io=200GiB (215GB), run=63538-63538msec

それぞれのディスクで 3.3GB/s を確認できます。
この程度では特に問題なく動作するようです。
この時、 Switch 側のトラフィック量は次のようになっていました。

[email protected]:~$ show interfaces counters
      IFACE    STATE       RX_OK        RX_BPS    RX_UTIL    RX_ERR    RX_DRP    RX_OVR       TX_OK        TX_BPS    TX_UTIL    TX_ERR    TX_DRP    TX_OVR
-----------  -------  ----------  ------------  ---------  --------  --------  --------  ----------  ------------  ---------  --------  --------  --------
  Ethernet0        U  51,631,461  6616.61 MB/s     52.93%         0         2         0   4,696,866    13.35 MB/s      0.11%         0         0         0
  Ethernet4        D           0      0.00 B/s      0.00%         0         0         0           0      0.00 B/s      0.00%         0         0         0
  Ethernet8        D           0      0.00 B/s      0.00%         0         0         0           0      0.00 B/s      0.00%         0         0         0
 Ethernet12        D           0      0.00 B/s      0.00%         0         0         0           0      0.00 B/s      0.00%         0         0         0
 Ethernet16        U   2,788,576  6529.03 KB/s      0.05%         0         0         0  31,156,784  3302.67 MB/s     26.42%         0         0         0
 Ethernet20        D           0      0.00 B/s      0.00%         0         0         0           0      0.00 B/s      0.00%         0         0         0
 Ethernet24        D           0      0.00 B/s      0.00%         0         0         0           0      0.00 B/s      0.00%         0         0         0
 Ethernet28        D           0      0.00 B/s      0.00%         0         0         0           0      0.00 B/s      0.00%         0         0         0
 Ethernet32        U   1,907,040  6825.01 KB/s      0.05%         0         0         0  20,460,390  3315.02 MB/s     26.52%         0         0         0
 Ethernet36        D           0      0.00 B/s      0.00%         0         0         0           0      0.00 B/s      0.00%         0         0         0
 Ethernet40        D           0      0.00 B/s      0.00%         0         0         0           0      0.00 B/s      0.00%         0         0         0
 Ethernet44        D           0      0.00 B/s      0.00%         0         0         0           0      0.00 B/s      0.00%         0         0         0

Ethernet0 がターゲット側のインタフェースであり、 6.6GB/s を観測しており、先ほどのベンチマーク結果と一致しています。

まとめ

今回は、SPDK を利用して NVMe over RDMA ターゲットを構築するとともに、Initiator から接続を行って検証を行うとともに、簡単なパフォーマンス測定を行いました。
検証に使用した環境では十分に性能が発揮できているようです。
後日、NVMe over TCP の検証も行う予定なので、その際にベンチマーク結果などを確認できればと思います。

About

インフラエンジニア
主に作業ログ

About Me

Archives