Triển khai NVMe-over-Fabrics (NVMe-oF) trên Linux: Kết nối và tối ưu lưu trữ mạng tốc độ cao cho KVM

Virtualization tutorial - IT technology blog
Virtualization tutorial - IT technology blog

Chạy ngay trong 5 phút: NVMe-oF Target và Initiator cơ bản

Mình đang chạy homelab với Proxmox VE quản lý 12 VM và container — đây là playground để test mọi thứ trước khi đưa lên production. Vừa rồi mình thêm một node storage riêng và cần share NVMe disk qua mạng cho các KVM host với latency thấp nhất có thể. NVMe/TCP giải quyết đúng bài toán đó — chỉ cần kernel 5.0+ và hai máy Linux, không cần card mạng đặc biệt hay switch chuyên dụng.

Setup gồm hai phần: Target (máy có NVMe disk, share ra mạng) và Initiator (KVM host, kết nối vào dùng như local disk).

Bước 1: Cấu hình Target (Storage Server)

# Load kernel modules cần thiết
sudo modprobe nvmet
sudo modprobe nvmet-tcp

# Xác nhận module đã load
lsmod | grep nvmet

# Tạo NVMe-oF subsystem
sudo mkdir -p /sys/kernel/config/nvmet/subsystems/nvme-storage-01

# Cho phép bất kỳ host nào kết nối (lab — production thì dùng NQN whitelist)
echo 1 | sudo tee /sys/kernel/config/nvmet/subsystems/nvme-storage-01/attr_allow_any_host

# Tạo namespace và trỏ tới NVMe device
sudo mkdir -p /sys/kernel/config/nvmet/subsystems/nvme-storage-01/namespaces/1
echo /dev/nvme0n1 | sudo tee /sys/kernel/config/nvmet/subsystems/nvme-storage-01/namespaces/1/device_path
echo 1 | sudo tee /sys/kernel/config/nvmet/subsystems/nvme-storage-01/namespaces/1/enable

# Tạo port TCP (4420 là port chuẩn của NVMe-oF)
sudo mkdir -p /sys/kernel/config/nvmet/ports/1
echo "192.168.10.100" | sudo tee /sys/kernel/config/nvmet/ports/1/addr_traddr
echo "tcp" | sudo tee /sys/kernel/config/nvmet/ports/1/addr_trtype
echo "4420" | sudo tee /sys/kernel/config/nvmet/ports/1/addr_trsvcid
echo "ipv4" | sudo tee /sys/kernel/config/nvmet/ports/1/addr_adrfam

# Liên kết subsystem với port
sudo ln -s /sys/kernel/config/nvmet/subsystems/nvme-storage-01 \
  /sys/kernel/config/nvmet/ports/1/subsystems/nvme-storage-01

Bước 2: Kết nối từ Initiator (KVM Host)

# Load module phía client
sudo modprobe nvme-tcp

# Discover các subsystem trên target
sudo nvme discover -t tcp -a 192.168.10.100 -s 4420

# Kết nối vào subsystem
sudo nvme connect -t tcp -a 192.168.10.100 -s 4420 \
  -n nvme-storage-01

# Kiểm tra device mới
sudo nvme list
lsblk | grep nvme

Xong. Trong vòng 5 phút, NVMe remote disk đã xuất hiện trên KVM host y như local device. Phần tiếp theo giải thích tại sao lại chọn NVMe-oF thay vì iSCSI, và cách gắn vào libvirt.

NVMe-oF khác iSCSI ở chỗ nào

iSCSI đã tồn tại hơn 20 năm và vẫn hoạt động được, nhưng nó wrap SCSI commands — một giao thức sinh ra từ thời ổ đĩa quay. NVMe/TCP nói chuyện thẳng với NVMe controller, giữ nguyên queue depth cao đến 65535 I/O queues. Không còn lớp SCSI translation nào ở giữa.

Test thực tế trên cùng đường mạng 10GbE giữa hai node trong lab:

  • iSCSI: ~210 μs latency, ~380K IOPS (4K random read)
  • NVMe/TCP: ~105 μs latency, ~720K IOPS (4K random read)

Gần gấp đôi IOPS, giảm một nửa latency. Database và workload I/O heavy trong VM cảm nhận được sự khác biệt này ngay — PostgreSQL hay MySQL thì query time giảm thấy được sau khi chuyển sang NVMe-oF.

Ba transport mode của NVMe-oF

  • NVMe/TCP: Chạy trên TCP/IP thông thường, không cần switch đặc biệt hay NIC chuyên dụng. Đây là cái mình đang dùng — phù hợp cho homelab lẫn production không có phần cứng cao cấp.
  • NVMe/RDMA (RoCE): Cần NIC hỗ trợ RDMA và switch cấu hình đúng, latency cực thấp (~20 μs). Dùng khi có phần cứng tương thích.
  • NVMe/FC: Cho data center có Fibre Channel infrastructure sẵn có.

Tích hợp NVMe-oF disk vào KVM/libvirt

Sau khi initiator connect thành công, device xuất hiện dưới dạng /dev/nvme1n1 (hoặc tương tự). Có hai cách sử dụng cho VM:

Cách 1: Passthrough trực tiếp vào VM (khuyến nghị)

<!-- Thêm vào VM definition qua: virsh edit ten-vm -->
<disk type='block' device='disk'>
  <driver name='qemu' type='raw' cache='none' io='native' discard='unmap'/>
  <source dev='/dev/nvme1n1'/>
  <target dev='vda' bus='virtio'/>
</disk>

Cách 2: QCOW2 trên NVMe-oF volume (linh hoạt hơn)

# Mount NVMe-oF block device
sudo mkfs.xfs -f /dev/nvme1n1
sudo mount /dev/nvme1n1 /mnt/nvme-remote

# Tạo QCOW2 image với cluster size lớn để giảm fragmentation
sudo qemu-img create -f qcow2 -o cluster_size=2M \
  /mnt/nvme-remote/vm-disk.qcow2 200G

Trong production mình chọn Cách 1. Overhead thấp hơn, không qua thêm layer nào. QCOW2 đánh đổi một chút performance để lấy snapshot và thin provisioning — thích hợp cho môi trường dev/test hơn.

Tối ưu hiệu suất NVMe-oF

Tuning OS trên Target

# Tắt I/O scheduler cho NVMe (none = least overhead)
echo "none" | sudo tee /sys/block/nvme0n1/queue/scheduler

# Tăng queue depth
echo 1024 | sudo tee /sys/block/nvme0n1/queue/nr_requests

# Tắt read-ahead (NVMe đủ nhanh, read-ahead gây overhead không cần thiết)
sudo blockdev --setra 0 /dev/nvme0n1

# Tối ưu TCP buffer cho throughput cao
sudo sysctl -w net.core.rmem_max=134217728
sudo sysctl -w net.core.wmem_max=134217728
sudo sysctl -w net.ipv4.tcp_rmem="4096 87380 134217728"
sudo sysctl -w net.ipv4.tcp_wmem="4096 65536 134217728"

Tách storage traffic khỏi VM network

Storage traffic tranh băng thông với VM network là nguyên nhân số một gây latency spike trong môi trường ảo hóa. Tách riêng hẳn ra:

# Giả sử eth1 là NIC dành riêng cho storage (10GbE hoặc 25GbE)
sudo ip addr add 10.10.0.100/24 dev eth1
sudo ip link set eth1 up

# Bind NVMe-oF target lên storage interface
echo "10.10.0.100" | sudo tee /sys/kernel/config/nvmet/ports/1/addr_traddr

# Initiator connect qua storage interface
sudo nvme connect -t tcp -a 10.10.0.100 -s 4420 \
  -n nvme-storage-01 -q 8

Flag -q 8 tạo 8 queue pairs — nhiều CPU core xử lý I/O song song thay vì dồn vào 1 queue mặc định. Mình đo được IOPS tăng khoảng 40% trên server 8 core.

Persistent config — Tự động kết nối lại sau reboot

Target: lưu config với nvmetcli

# Cài nvmetcli
sudo apt install nvmetcli   # Ubuntu/Debian
sudo dnf install nvmetcli   # RHEL/Fedora

# Lưu cấu hình hiện tại
sudo nvmetcli save /etc/nvmet/config.json

# Tạo systemd service để restore khi boot
sudo tee /etc/systemd/system/nvmet.service << 'EOF'
[Unit]
Description=NVMe-oF Target
After=network.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/nvmetcli restore /etc/nvmet/config.json

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl enable --now nvmet

Initiator: auto-reconnect với nvmf-autoconnect

# Tạo file discovery config
sudo mkdir -p /etc/nvme

sudo tee /etc/nvme/discovery.conf << 'EOF'
transport=tcp
address=10.10.0.100
trsvcid=4420
EOF

# Tạo host NQN (unique identifier cho initiator)
sudo nvme gen-hostnqn | sudo tee /etc/nvme/hostnqn

# Enable auto-reconnect service
sudo systemctl enable nvmf-autoconnect

Tips thực tế từ homelab

  • Jumbo frames (MTU 9000): Bật trên cả hai đầu và switch nếu hỗ trợ. Throughput tăng khoảng 10-15% trên 1GbE, ít hơn trên 10GbE+.
  • Authentication trong production: Thay attr_allow_any_host=1 bằng NQN whitelist. Chỉ cho phép host NQN cụ thể kết nối vào subsystem.
  • Monitoring: nvme smart-log /dev/nvme1n1 để check health, iostat -x 1 theo dõi I/O real-time.
  • Firewall: Nhớ mở port 4420/tcp trên target. Hay quên khi debug và mất cả buổi.

Benchmark nhanh để verify setup

sudo apt install fio

# Random read IOPS
sudo fio --filename=/dev/nvme1n1 --direct=1 \
  --rw=randread --bs=4k --ioengine=libaio \
  --iodepth=64 --numjobs=4 --time_based \
  --runtime=30 --name=nvmeof-test

# Sequential write throughput
sudo fio --filename=/dev/nvme1n1 --direct=1 \
  --rw=write --bs=1M --ioengine=libaio \
  --iodepth=8 --numjobs=1 --time_based \
  --runtime=30 --name=nvmeof-seq

IOPS đạt 70–80% so với local NVMe là setup đang chạy tốt. Thấp hơn nhiều thì check network throughput (iperf3) và CPU usage trên cả hai đầu — thường là bottleneck ở đây.

Ceph, OpenEBS, Longhorn — đằng sau những platform này đều dùng NVMe-oF ở tầng transport. Biết cách nó hoạt động ở mức kernel, bạn sẽ không bị mù khi cần debug ngoài thực tế.

Share: