Metonymical Deflection

ゆるく日々のコト・たまにITインフラ

CentOS7 + SPDKでNVMe-oF target構築

CentOS7上でSPDKをビルドして、NVMe-oF(NVMe over Fabric) targetを構築しました。
ただ、Mellanox製NICが1つしか入手できなかったことと、HPの不具合(CUSTOMER ADVISORY)にヒット*1したため、1台のサーバ筐体内でパケットキャプチャ可能な構成までは組めていません。このため、Mellanox製NICをもう1個追加で入手した後に更新したいと思います。

1.環境

1-1.筐体
筐体                             : ProLiant DL360e Gen8
System ROM                       : P73 01/22/2018
NIC                              : Mellanox ConnectX-3pro MCX311A-XCCT
SSD                   : Samsung SSD 250GB 970 EVO M.2 Type2280 PCIe3.0×4 NVMe1.3
Adapter                          : GLOTRENDS M.2 PCIe NVMe or PCIe AHCI SSD to PCIe 3.0 x4 Adapter Card
OS                               : CentOS7.5(1804)
Kernel                           : 3.10.0-862.el7.x86_64
Installed Environment Groups     : Minimal Install
SPDK                         : v18.07-pre
DPDK                             : v18.02.0
1-2.全体の流れ

過去記事「CentOS7 + SPDKでiSCSI target構築」とほぼ一緒です。*2
事前準備
SPDKのビルド
SPDKのnvmf.conf設定

2.事前準備

2-1.セキュリティ設定の無効化

firewalldとSELinuxを無効化します。

systemctl disable firewalld

vi /etc/selinux/config
で開いて
SELINUX=disabled
にして保存。
2-2.grubの設定

grubにHugePageの設定追加

vi /etc/sysconfig/grub

GRUB_CMDLINE_LINUX=の行末に追加
default_hugepagesz=1G hugepagesz=1G hugepages=8

保存後、grubに反映
grub2-mkconfig -o /etc/grub2.cfg
2-3.NICIPアドレス設定

nmcli devなどでMellanox製NICのデバイス名を確認の上、IPアドレスを設定してください。今回構築するNVMe-oF targetはRoCEv2で動作するため、NICにはIPアドレスが必要となります。

nmcli connection add type ethernet autoconnect yes con-name ens1 ifname ens1
nmcli connection modify ens1 ipv4.method manual ipv4.addresses 192.168.11.200/24
nmcli connection up ens1
2-4.numactl-develのインストール

dpdkがnumactl-develやopenssl-devel を必要とするようなのでインストール。

yum -y install numactl-devel openssl-devel 

reboot

再起動後にHugePageを確認。
# cat /proc/meminfo | grep ^Huge
HugePages_Total:       8
HugePages_Free:        8
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:    1048576 kB

3.SPDKのビルド

3-1.gitインストールからspdkのビルドまで

以下を流し込んでください。

yum -y install git && \
cd /usr/src && \
git clone https://github.com/spdk/spdk && \
cd spdk && \
git submodule update --init && \
scripts/pkgdep.sh && \
./configure --with-rdma --enable-debug && \
make

gitインストール
/usr/srcにcd
spdkのソースをgitから取得
/usr/src/spdkにcd*3
アップデート
必要なパッケージのインストール
makeファイル生成
ビルド

iSCSI Targetと異なる点
--with-rdmaは必須となります。
また、nvmf_tgtを起動した際に「-L」オプションでDebugログが見れるようになるため、--enable-debugを追加しています。正常起動しているのか?固まっているのか?を見極めるためにも、あった方がいいかなと思います。

3-2.unittest.shの実行

ビルドが完了したら、最後に各モジュールのテストを実施してみてください。

./test/unit/unittest.sh

 出力結果省略

=====================
All unit tests passed
=====================
WARN: lcov not installed or SPDK built without coverage!
WARN: neither valgrind nor ASAN is enabled!

多数の出力結果が表示されますが、最後に「All unit tests passed」と表示されればOKです。
Warringが2件表示されてますが、気にせず先に進んでください。

4.spdkのnvmf.confファイル設定

4-1.設定ファイルの作成
# cd /usr/src/spdk/app/nvmf_tgt
# vi nvmf.conf

[Global]
  ReactorMask 0x0F

[Nvmf]
  MaxQueuesPerSession 4
  MaxQueueDepth 1024
  AcceptorPollRate 10000

[Nvme]
  TransportId "trtype:PCIe traddr:0000:03:00.0" Nvme0
  RetryCount 4
  TimeoutUsec 1
  ActionOnTimeout None
  AdminPollRate 100000
  HotplugEnable No

[Subsystem1]
  NQN nqn.2016-06.io.spdk:cnode1
  Listen RDMA 192.168.11.200:4420
  AllowAnyHost Yes
  Host nqn.2016-06.io.spdk:init
  SN SPDK00000000000001
  Namespace Nvme0n1 1

ReactorMaskの掛け方は最後に詳細を記載したいと思います。
わからなければ、コメントアウトしても問題ありません。

TransportIdの確認方法
私の場合、Samsung SSD 250GB 970を使用したため、lspciで以下のようにgrepしました。

# lspci |grep Samsung
03:00.0 Non-Volatile memory controller: Samsung Electronics Co Ltd Device a808

SSDのbsf(bus slot function)番号が判明したら、頭にDomain番号「0000:」を付けてください。
ListenしているUDPポート番号が4420になっていますが、RoCEv2の正式なポート番号は4791のようです。
サンプルconfがspdk/etc/spdk/nvmf.conf.inにあるので、これをコピーしてnvmf.confを作成してもよいと思います。

4-2.setup.shの起動

カーネル上での制御からSPDK(正確にはDPDK)上での制御に移行します。
ますはステータス確認から。

# scripts/setup.sh status
Hugepages
node     hugesize     free /  total
node0   1048576kB        8 /      8
node1   1048576kB        8 /      8
NVMe devices
BDF		Numa Node	Driver name		Device name
0000:03:00.0	0		nvme			nvme0
I/OAT DMA
BDF		Numa Node	Driver Name
0000:00:04.0	0		ioatdma
0000:20:04.0	1		ioatdma
0000:00:04.1	0		ioatdma
0000:20:04.1	1		ioatdma
0000:00:04.2	0		ioatdma
0000:20:04.2	1		ioatdma
0000:00:04.3	0		ioatdma
0000:20:04.3	1		ioatdma
0000:00:04.4	0		ioatdma
0000:20:04.4	1		ioatdma
0000:00:04.5	0		ioatdma
0000:20:04.5	1		ioatdma
0000:00:04.6	0		ioatdma
0000:20:04.6	1		ioatdma
0000:00:04.7	0		ioatdma
0000:20:04.7	1		ioatdma
virtio
BDF		Numa Node	Driver Name		Device Name

現在はカーネル上のnvmeやioatdmaドライバで制御していることがわかります。

オプション無しでsetup.shを実行すると、uio_pci_genericに移行します。*4

# scripts/setup.sh
0000:03:00.0 (144d a808): nvme -> uio_pci_generic
0000:00:04.0 (8086 3c20): ioatdma -> uio_pci_generic
0000:20:04.0 (8086 3c20): ioatdma -> uio_pci_generic
0000:00:04.1 (8086 3c21): ioatdma -> uio_pci_generic
0000:20:04.1 (8086 3c21): ioatdma -> uio_pci_generic
0000:00:04.2 (8086 3c22): ioatdma -> uio_pci_generic
0000:20:04.2 (8086 3c22): ioatdma -> uio_pci_generic
0000:00:04.3 (8086 3c23): ioatdma -> uio_pci_generic
0000:20:04.3 (8086 3c23): ioatdma -> uio_pci_generic
0000:00:04.4 (8086 3c24): ioatdma -> uio_pci_generic
0000:20:04.4 (8086 3c24): ioatdma -> uio_pci_generic
0000:00:04.5 (8086 3c25): ioatdma -> uio_pci_generic
0000:20:04.5 (8086 3c25): ioatdma -> uio_pci_generic
0000:00:04.6 (8086 3c26): ioatdma -> uio_pci_generic
0000:20:04.6 (8086 3c26): ioatdma -> uio_pci_generic
0000:00:04.7 (8086 3c27): ioatdma -> uio_pci_generic
0000:20:04.7 (8086 3c27): ioatdma -> uio_pci_generic

以下のようになっていれば、OKです。

# scripts/setup.sh status
Hugepages
node     hugesize     free /  total
node0   1048576kB        0 /      1
node1   1048576kB        0 /      1
NVMe devices
BDF             Numa Node       Driver name             Device name
0000:03:00.0    0               uio_pci_generic         -
I/OAT DMA
BDF             Numa Node       Driver Name
0000:00:04.0    0               uio_pci_generic
0000:20:04.0    1               uio_pci_generic
0000:00:04.1    0               uio_pci_generic
0000:20:04.1    1               uio_pci_generic
0000:00:04.2    0               uio_pci_generic
0000:20:04.2    1               uio_pci_generic
0000:00:04.3    0               uio_pci_generic
0000:20:04.3    1               uio_pci_generic
0000:00:04.4    0               uio_pci_generic
0000:20:04.4    1               uio_pci_generic
0000:00:04.5    0               uio_pci_generic
0000:20:04.5    1               uio_pci_generic
0000:00:04.6    0               uio_pci_generic
0000:20:04.6    1               uio_pci_generic
0000:00:04.7    0               uio_pci_generic
0000:20:04.7    1               uio_pci_generic
virtio
BDF             Numa Node       Driver Name             Device Name
4-3.Targetプログラムの実行

/usr/src/spdkのパス上で以下を実行。

app/nvmf_tgt/nvmf_tgt -c app/nvmf_tgt/nvmf.conf

以下、出力結果。
# app/nvmf_tgt/nvmf_tgt -c app/nvmf_tgt/nvmf.conf
Starting SPDK v18.07-pre / DPDK 18.02.0 initialization...
[ DPDK EAL parameters: nvmf -c 0x0F --file-prefix=spdk_pid23820 ]
EAL: Detected 8 lcore(s)
EAL: Multi-process socket /var/run/.spdk_pid23820_unix
EAL: Probing VFIO support...
app.c: 530:spdk_app_start: *NOTICE*: Total cores available: 4
reactor.c: 718:spdk_reactors_init: *NOTICE*: Occupied cpu socket mask is 0x1
reactor.c: 492:_spdk_reactor_run: *NOTICE*: Reactor started on core 1 on socket 0
reactor.c: 492:_spdk_reactor_run: *NOTICE*: Reactor started on core 2 on socket 0
reactor.c: 492:_spdk_reactor_run: *NOTICE*: Reactor started on core 3 on socket 0
reactor.c: 492:_spdk_reactor_run: *NOTICE*: Reactor started on core 0 on socket 0
EAL: PCI device 0000:03:00.0 on NUMA socket 0
EAL:   probe driver: 144d:a808 spdk_nvme
rdma.c:1454:spdk_nvmf_rdma_create: *ERROR*: rdma_create_event_channel() failed, No such device
Segmentation fault (core dumped)

初回起動時に赤文字のエラーを吐く場合は、一度サーバを再起動してみてください。

nvmf_tgtはフォアグラウンドで稼働してしまうため、上記出力(赤文字以外の出力)でプロンプトが停止したように見えますが、nvmf targetは正常に起動できています。
iscsi targetとは異なり、-b(バックグラウンド)オプションはないようです。

また、Debugオプションを使用したい場合、まずは-Lに指定可能な引数を確認するため、-hオプションでnvmf_tgtを起動してあげてください。

# app/nvmf_tgt/nvmf_tgt -h
app/nvmf_tgt/nvmf_tgt [options]
options:
 -c config  config file (default /usr/local/etc/nvmf/nvmf.conf)
 -d         disable coredump file enabling
 -e mask    tracepoint group mask for spdk trace buffers (default 0x0)
 -g         force creating just one hugetlbfs file
 -h         show this usage
 -i shared memory ID (optional)
 -m mask    core mask for DPDK
 -n channel number of memory channels used for DPDK
 -p core    master (primary) core for DPDK
 -q         disable notice level logging to stderr
 -r         RPC listen address (default /var/tmp/spdk.sock)
 -s size    memory size in MB for DPDK (default: all hugepage memory)
 -u         disable PCI access.
 -w         wait for RPCs to initialize subsystems
 -B addr    pci addr to blacklist
 -R         unlink huge files after initialization
 -W addr    pci addr to whitelist (-B and -W cannot be used at the same time)
 -L flag    enable debug log flag (all, aio, bdev, bdev_malloc, bdev_null, bdev_nvme, blob, blob_rw, copy_ioat, gpt_parse, ioat, log, lvol, lvolrpc, nbd, nvme, nvmf, rdma, reactor, rpc, vbdev_gpt, vbdev_lvol, vbdev_passthru, vbdev_split, virtio, virtio_blk, virtio_dev, virtio_pci, virtio_user)

実際にDebugオプションを付けて起動した場合は、こんな感じです。

# app/nvmf_tgt/nvmf_tgt -L nvme -c app/nvmf_tgt/nvmf.conf
4-4.Initiatorからのアクセス

今回は一台の筐体内でTargetとInitiatorを動作させるため、参考程度までに。

Initiatorのインストール

yum -y install nvme-cli

modprobe実施

modprobe nvme-rdma

Discover実施

# nvme discover -t rdma -a 192.168.11.200 -s 4420

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

Connect実施
iSCSIのときとは違い、Successなどは出力されません。

# nvme connect -t rdma -n nqn.2016-06.io.spdk:cnode1 -a 192.168.11.200 -s 4420

List確認

# nvme list
Node             SN                   Model                                    Namespace Usage                      Format           FW Rev
---------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- --------
/dev/nvme0n1     SPDK00000000000001   SPDK bdev Controller                     1         250.06  GB / 250.06  GB    512   B +  0 B   18.07

bdevとして認識されていることを確認*5

# lsblk
NAME            MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda               8:0    0 136.8G  0 disk
tqsda1            8:1    0     1G  0 part /boot
mqsda2            8:2    0 135.7G  0 part
  tqcentos-root 253:0    0 131.7G  0 lvm  /
  mqcentos-swap 253:1    0     4G  0 lvm  [SWAP]
sr0              11:0    1  1024M  0 rom
nvme0n1         259:0    0 232.9G  0 disk

ログアウト

# nvme disconnect -n "nqn.2016-06.io.spdk:cnode1"
NQN:nqn.2016-06.io.spdk:cnode1 disconnected 1 controller(s)

あとは、iSCSI Targetと同様に動作しますので、Initiator側でmkfsするなり、fioで速度測定するなりご自由にどうぞ。

以上です。

5.補足:ReactorMaskについて

ReactorMaskとは、PMD(Poll Mode Driver)で専有させるCPUコアを明示的に指定するためのものです。NVMe-oF targetの場合、SSDが管理されているNUMA socket*6と同じNUMA socket上のCPUコアを明示的に指定することでパフォーマンスが向上します。このため、ReactorMaskでPMDが専有するCPUコアを明示的に指定することが重要となってきます。

それでは、CPUコアを明示的に指定する方法を具体例を交えて解説します。

lscpuの結果

# lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                8
On-line CPU(s) list:   0-7
Thread(s) per core:    1
Core(s) per socket:    4
Socket(s):             2
NUMA node(s):          2
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 45
Model name:            Intel(R) Xeon(R) CPU E5-2407 0 @ 2.20GHz
Stepping:              7
CPU MHz:               1200.000
CPU max MHz:           2200.0000
CPU min MHz:           1200.0000
BogoMIPS:              4389.14
Virtualization:        VT-x
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              10240K
NUMA node0 CPU(s):     0-3
NUMA node1 CPU(s):     4-7
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx lahf_lm epb tpr_shadow vnmi flexpriority ept vpid xsaveopt ibpb ibrs stibp dtherm arat pln pts spec_ctrl intel_stibp

今回使用したサーバ(DL360e)の場合、2ソケット、Hyperスレッド無し、4コアなので、トータル8コアあります。
さらに、NUMA socketの割り当ては以下のようになっています。
 NUMA socket(node)0:0-3コア
 NUMA socket(node)1:4-7コア

また、nvmf_tgtの実行結果から、SSDがNUMA socket(node)0で動作していることがわかります。

# app/nvmf_tgt/nvmf_tgt -c app/nvmf_tgt/nvmf.conf
Starting SPDK v18.07-pre / DPDK 18.02.0 initialization...
[ DPDK EAL parameters: nvmf -c 0x0E --file-prefix=spdk_pid17014 ]
EAL: Detected 8 lcore(s)
EAL: Multi-process socket /var/run/.spdk_pid17014_unix
EAL: Probing VFIO support...
app.c: 530:spdk_app_start: *NOTICE*: Total cores available: 3
reactor.c: 718:spdk_reactors_init: *NOTICE*: Occupied cpu socket mask is 0x1
reactor.c: 492:_spdk_reactor_run: *NOTICE*: Reactor started on core 2 on socket 0
reactor.c: 492:_spdk_reactor_run: *NOTICE*: Reactor started on core 3 on socket 0
reactor.c: 492:_spdk_reactor_run: *NOTICE*: Reactor started on core 1 on socket 0
EAL: PCI device 0000:03:00.0 on NUMA socket 0
EAL:   probe driver: 144d:a808 spdk_nvme

上記結果より、PMDに専有させたいコアは「NUMA socket(node)0:0-3」となります。

このときのReactorMaskの指定方法は、

[Global]
  ReactorMask 0x0F

となります。

CPUコアの並び順は以下のようになっています。

7654 3210

lscpuの結果より、
 7654がNUMA socket(node)1
 3210がNUMA socket(node)0
となります。

ReactorMaskのうち、0x0Fを2進数に変換すると、以下のようになります。

16進数
0x0F
2進数
0000 1111

CPUコアの並びと2進数に変換したReactorMaskを合わせると

7654 3210
0000 1111

となり、ReactorMaskのうち「1」が立っているCPUがPMDに専有されます。
考え方としては、SubnetMaskと同様ですね。

今回の場合、あまり意味はないですが、例えば、奇数番号のコアだけを使用したい場合は、

CPUコア
7654 3210
2進数
1010 1010
16進数
0xAA

となります。

6.最後に

以下のサイトを参考にさせて頂きました。
SPDK: NVMe over Fabrics Target
https://www.starwindsoftware.com/blog/intel-spdk-nvme-of-target-performance-tuning-part-2-preparing-testing-environment

何かしらの不具合に遭遇する予感はしていましたが、やはりといった感じでした。次回はもう一枚NICを準備して挑みたいと思います。

*1:HPE Support document - HPE Support Center

*2:metonymical.hatenablog.com

*3:ここのパスが基点となります。

*4:先にも書いたHPのアドバイザリにヒットしたため、今回はuio_pci_genericで行っています。GRUB_CMDLINE_LINUX=の行末に、intel_iommu=on iommu=pt pci=realloc も追記すると、uioの代わりにvfio-pciで稼働するようになります。また、SR-IOVのVFも作れるため、仮想マシンをNVMe-oFのInitiatorとして稼働させ、パケットキャプチャできるじゃん!と思いましたが、HP不具合のため、あえなく断念。。

*5:一台の筐体でやっているため、あまり雰囲気は出ませんが。。。setup.shの実行直後にlsblkを実行すると、nvmeはカーネルの制御から解放されているためlsblkでは見えなくなります。その後、Initiator側からConnectして、lsblkを実行すると、nvme0n1が見えるようになります。

*6:NUMA socketとかNUMA nodeと言ったりします。厳密には異なりますが、今回は同じ用語だと考えてください。