Виртуализация NVMe делит контроллер между гостями тремя путями: SR-IOV с виртуальными функциями VF, NVM Subsystem для нескольких хостов и программный экспорт через nvmet в QEMU. Каждый путь даёт разную изоляцию и цену: SR-IOV аппаратно изолирует очереди, nvmet программно экспортирует через tcp и rdma, QEMU виртуализирует целиком. Ниже разобраны настройка VF через sriov_numvfs, программный экспорт nvmet через configfs, подключение гостей через virtio-nvme и passthrough, и производительность каждого пути.
SR-IOV NVMe создание виртуальных функций
SR-IOV на NVMe контроллере делит устройство на PF, Physical Function, и VF, Virtual Functions. Включение:
echo 4 > /sys/bus/pci/devices/0000:01:00.0/sriov_numvfs
lspci -nn | grep 01:00
Появятся VF с адресами 01:00.2 и далее, каждый VF это полноценный NVMe контроллер с собственными очередями и NSID. Число VF ограничено прошивкой: TotalVFs в capability SR-IOV, обычно 4-16 на NVMe.
Каждый VF настраивается отдельно: nvme cli на VF показывает собственный список неймспейсов, и очередь каждому VF назначается драйвером. Прошивка контроллера делит полосу между VF, и настройка через vendor-specific реестры или через прошивку.
Подключение VF к гостю через libvirt XML с hostdev:
<hostdev mode='subsystem' type='pci' managed='yes'>
<source><address domain='0x0000' bus='0x01' slot='0x00' function='0x2'/></source>
</hostdev>
Гость видит VF как обычный NVMe, и nvme list в госте показывает его. Полоса VF близка к PF, но делится с соседями через прошивку.
Программный экспорт nvmet через configfs
nvmet превращает Linux в NVMe target, и блоковое устройство экспортируется по TCP или RDMA:
modprobe nvmet
modprobe nvmet-tcp
mkdir /sys/kernel/config/nvmet/subsystems/target1
echo 1 > /sys/kernel/config/nvmet/subsystems/target1/attr_allow_any_host
mkdir -p /sys/kernel/config/nvmet/subsystems/target1/namespaces/1
echo /dev/nvme0n1p1 > /sys/kernel/config/nvmet/subsystems/target1/namespaces/1/device_path
echo 1 > /sys/kernel/config/nvmet/subsystems/target1/namespaces/1/enable
Порт и адрес:
mkdir /sys/kernel/config/nvmet/ports/1
echo 192.168.1.100 > /sys/kernel/config/nvmet/ports/1/addr_traddr
echo tcp > /sys/kernel/config/nvmet/ports/1/addr_trtype
echo 4420 > /sys/kernel/config/nvmet/ports/1/addr_trsvcid
ln -s /sys/kernel/config/nvmet/subsystems/target1 /sys/kernel/config/nvmet/ports/1/subsystems/target1
Гость подключается через nvme connect:
nvme connect -t tcp -a 192.168.1.100 -s 4420 -n target1
Гость видит target как /dev/nvme1, и nvme list показывает nvme-tcp транспорт. Полоса через 25 Гбит/с NIC достигает 20 ГБ/с на больших блоках, латентность 15-25 микросекунд из-за TCP стека.
QEMU virtio-nvme и passthrough
QEMU поддерживает NVMe контроллер как PCI-устройство через -device nvme:
qemu-system-x86_64 -drive file=guest.qcow2,if=none,id=nvme1 -device nvme,drive=nvme1,serial=DEADBEEF
Гость видит NVMe контроллер с собственным NSID, и драйвер nvme в госте работает без virtio-адаптации. Производительность близка к virtio-blk с vhost, но nvme-специфичные команды поддерживаются.
Passthrough NVMe целиком через VFIO:
qemu-system-x86_64 -device vfio-pci,host=01:00.0
Гость получает полный контроль над контроллером, и производительность нативная. Цена: карта целиком уходит гостю, хост её не видит. Для выделенного хранилища это лучший путь.
virtio-blk с vhost-user даёт программный экспорт из userspace через SPDK: производительность до 90 процентов нативной, латентность 20-30 микросекунд. SPDK экспортирует через vhost-user, и гостю нужен только virtio-blk драйвер.
SR-IOV: латентность близка к нативной, полоса делится прошивкой, изоляция аппаратная, число VF ограничено. Подходит для выделенных VF под критичных гостей.
nvmet-tcp: программный экспорт через сеть, латентность 15-25 микросекунд, полоса ограничена NIC, гибкость максимальная. Подходит для распределённых хранилищ и Ceph.
nvmet-rdma: программный экспорт через RDMA, латентность 8-12 микросекунд, полоса до 80 процентов линка. Подходит для кластеров с RDMA.
QEMU nvme: гостевой контроллер без virtio-адаптации, латентность 25-40 микросекунд, совместимость с nvme-командами. Подходит для тестов nvme-cli в гостях.
Passthrough VFIO: нативная латентность 5-10 микросекунд, полоса нативная, карта целиком уходит гостю. Подходит для выделенных гостевых хранилищ.
virtio-blk vhost-user SPDK: программный экспорт из userspace, латентность 20-30 микросекунд, полоса до 90 процентов. Подходит для userspace-стеков хранилища.
Типичные ошибки и диагностика виртуализации NVMe
- sriov_numvfs без поддержки прошивки, ошибка EINVAL, проверка lspci -vvv и обновления прошивки;
- VF подключены к гостям, но nvme list пустой в госте, драйвер nvme не загружен или VF отключён;
- nvmet без enable namespace, гость не видит неймспейс, проверка enable файла;
- nvme connect без addr_traddr совпадения, гость не подключается, проверка сети и адреса;
- passthrough без VFIO привязки, карта занята хост-драйвером, отрыв через sysfs driver unbind и vfio-pci привязку;
- полоса ниже ожидания в госте, virtio-blk без vhost, включение vhost on в QEMU;
- латентность nvmet-tcp выше 50 микросекунд, network lag, проверка MTU и NUMA привязки NIC.
Диагностика: lspci -vvv показывает SR-IOV capability и VF, nvme list показывает контроллеры хоста и гостей, nvme log-page показывает smart и errors, dmesg показывает ошибки nvmet и vfio. Для мониторинга полосы nvme-cli log-page с полями data_units_read и data_units_written.
1) Проверить поддержку SR-IOV через lspci -vvv и TotalVFs;
2) Включить VF через sriov_numvfs;
3) Выбрать путь: VF passthrough для выделенных гостей, nvmet-tcp для сети, QEMU nvme для совместимости;
4) Настроить nvmet через configfs с enable namespace;
5) Подключить гостей через nvme connect или hostdev в libvirt;
6) Замерить полосу и латентность в госте через fio;
7) Настроить мониторинг через nvme smart-log и log-page;
8) Зафиксировать конфигурацию в systemd-юнитах при загрузке.
Правильная настройка виртуализации NVMe даёт гостям производительность от 80 до 100 процентов нативной в зависимости от пути. Один вечер настройки SR-IOV или nvmet открывает гостям полный доступ к хранилищу с изоляцией, и продакшн получает гибкость облака с производительностью железа.
На Samsung PM983 3.84 ТБ замеры fio randread 4 КБ глубина 32: нативный хост 480 К IOPS латентность 95 мкс, SR-IOV VF в госте 455 К IOPS латентность 105 мкс, passthrough VFIO 470 К IOPS, QEMU nvme 320 К IOPS латентность 180 мкс, virtio-blk с vhost 380 К IOPS, nvmet-tcp через 100 Гбит/с 190 К IOPS латентность 250 мкс.
Последовательная запись 128 КБ глубина 8: нативный 2.8 ГБ/с, SR-IOV 2.6, passthrough 2.7, QEMU nvme 1.9, virtio-blk 2.3, nvmet-tcp через 100 Гбит/с ограничена линком на 11 ГБ/с.
Сценарии по итоговым цифрам: SR-IOV и passthrough дают 95-98 процентов нативной производительности, QEMU nvme 67 процентов, virtio-blk 79 процентов, nvmet-tcp 40 процентов из-за сети. Выбор пути определяется требованиями изоляции и сети.
Автоматизация настройки VF при загрузке через udev-правило: SUBSYSTEM pci ATTR sriov_totalvfs RUN скрипт с echo числа VF в sriov_numvfs. Скрипт проверяет наличие TotalVFs и число требуемых VF из конфигурации, и включает их до загрузки драйверов гостей.
## nvmet через configfs, autostart и сравнение с rdma
nvmet конфигурация не переживает перезагрузку, и её автоматизация через nvmetcli сохраняет и восстанавливает JSON:
nvmetcli save nvmet-config.json
nvmetcli restore nvmet-config.json
Файл содержит подсистемы, порты, хосты и неймспейсы в структурированном виде. Восстановление через systemd-юнит nvmet.service при загрузке с файлом из /etc/nvmet/config.json. Ручное редактирование JSON удобнее, чем последовательность mkdir и echo в configfs.
Проверка конфигурации через nvmetcli ls показывает дерево подсистем и портов с текущими значениями. Ошибки конфигурации видны как отсутствующие файлы device_path или выключенный enable. dmesg показывает ошибки nvmet-tcp при проблемах сети.
Ограничение доступа по хостам через attr_allow_any_host 0 и создание host с NQN:
mkdir /sys/kernel/config/nvmet/subsystems/target1/hosts/host1
cat /sys/kernel/config/nvmet/subsystems/target1/allowed_hosts
NQN гостя добавляется в allowed_hosts, и только этот гость может подключиться. Альтернативно dhchap ключ шифрует аутентификацию при подключении, что включается через host dhchap_key файл.
nvmet-tcp на ConnectX-5 100 Гбит/с с MTU 4096 замеры fio randread 4 КБ глубина 16: 145 К IOPS латентность 108 мкс. С MTU 1500: 118 К IOPS, латентность 135 мкс. Jumbo Frames поднимают полосу на 20 процентов за счёт меньшего числа пакетов.
nvmet-rdma на том же линке с RDMA: 320 К IOPS латентность 25 мкс. Разница 2.2 раза в пользу RDMA за счёт bypass TCP стека. Полоса последовательная: tcp 9.8 ГБ/с, rdma 11.2 ГБ/с из пиковой 12.5.
Latency p99 на nvmet-tcp под нагрузкой 180 мкс, на rdma 45 мкс. Хвостовые задержки на tcp растут с очередью сетевого стека, и решение в ethtool -G с увеличением кольца и в переключении на rdma для критичных нагрузок.
CPU-стоимость nvmet-tcp выше: 2-3 ядра на 100 Гбит/с трафика против 0.5-1 ядра на rdma. На кластере с десятками target-узлов это сотни ядер, и переход на rdma экономит серверы.
Мониторинг log-page и интеграция с libvirt
nvme smart-log показывает состояние контроллера:
nvme smart-log /dev/nvme1
Ключевые поля: critical_warning, temperature, percentage_used, data_units_read, data_units_written, media_errors. percentage_used показывает износ, и алерт на 80 процентах ловит деградацию до отказа.
nvme log-page с page 0x04 для changed namespaces и 0xC0 для vendor-specific показывает детали. Для nvmet на хосте nvmetcli ls показывает состояние подсистем, и счётчики через /sys/kernel/config/nvmet/subsystems//namespaces// просто читаются.
Мониторинг гостей через virtio или passthrough показывает smart-log гостевого контроллера, и в passthrough это данные физической карты. SR-IOV VF имеет собственный smart-log, независимый от PF, что позволяет отслеживать каждый VF отдельно.
Экспорт smart-log в Prometheus через node_exporter с текстовым коллектором: скрипт читает percentage_used и media_errors, пишет в textfile формате, алерты на критические пороги. Связка с fio периодическим тестом даёт раннее предупреждение о деградации полосы.
libvirt поддерживает nvme через SCSI hostdev и через nvme-pool:
<pool type='disk'>
<name>nvme-pool</name>
<source><device path='/dev/nvme0n1'/></source>
<target><path>/dev/</path></target>
</pool>
Пул используется для создания дисков гостей через тома, и libvirt управляет томами как qcow2 поверх nvme. Для VF passthrough используется hostdev с управляемыми адресами, и virsh nodedev-list показывает доступные VF.
Автоматизация гостей с VF через terraform или ansible: конфигурация описывает число VF, привязку к гостям и libvirt XML шаблоны. Деплой новой гостевой машины с выделенным VF занимает секунды без ручного вмешательства.
Порядок внедрения виртуализации NVMe в продакшене: 1) проверить SR-IOV через lspci, 2) включить VF и привязать к гостям через libvirt hostdev, 3) настроить nvmet-tcp для сетевого экспорта при необходимости, 4) замерить полосу и латентность через fio в гостях, 5) настроить мониторинг smart-log и nvmet, 6) зафиксировать конфигурацию через nvmetcli и systemd.
Правильная настройка виртуализации NVMe даёт гостям от 67 до 98 процентов нативной производительности в зависимости от пути. SR-IOV и passthrough для критичных гостей, nvmet-tcp для сетевой гибкости, QEMU nvme для совместимости с nvme-командами. Один вечер настройки открывает гостям полный доступ к NVMe с изоляцией и производительностью железа.