А все ли врут? Продолжаем издеваться над NVME


А пока мои коллеги пытаются разобраться с проблемами серверных NVME Raid массивов, я решил посмотреть на проблему с другого ракурса. Ведь NVME — это не только жёсткий диск, но и три-четыре протокола быстропередаваемых данных.

Для многих из нас nvme означает, что мы купили новый компьютер или ультрабук. Жёсткий диск, подключённый напрямую к шине PCIE, позволяет существенно снизить задержки передачи данных и ускорить любую систему. NVME — это ключ к загрузке любой системы за 3 секунды.

Но, на самом деле сам по себе NVME — это не стандарт для жёстких дисков. NVME расшифровывается как NVM Express. NVM, в свою очередь, означает Non-volatile memory, И в первую очередь — это спецификация протокола, который позволяет производить эффективный доступ к данным, хранящимся в энергонезависимой памяти.

А как мы хорошо знаем, протоколы можно запускать на разных носителях. В этой статье мы будем издеваться над моим лэптопом с Ubuntu Linux 21 на борту, подключая его жёсткий диск к разным серверам. Вы можете посетовать, что всё это игрушки, но хороший администратор со свитчем, позволяющим поддерживать скорости более 10 гигабит в секунду, должен взять это на заметку. Вы можете получить удалённый доступ к вашим nvme жёстким дискам через tcp/ip без уловок и мошенства.

Поехали.

▍ Краткое введение

Итак, давайте начнём с небольшого погружения в инструкции. В этой публикации от 2013 года вы можете почитать о том, что такое протокол NVME, и узнать, зачем он создавался. Этот документ вполне реально прочитать. В нём всего пять страниц текста. Остальные мануалы на nvmexpress.org не настолько щадящие. Там вам придётся прорываться через 500 страничные талмуды. Так что можете окунуться, если вас не смущает английский язык.

Для тех, кому лень читать, вот основные идеи в этом протоколе:

  • Поддержка 64 тысяч очередей для ввода-вывода команд, где каждая очередь поддерживает до 64 тысяч инструкций.
  • Вся информация, необходимая для прочтения блока в 4 килобайте содержится в самой 64-х битной команде. Это означает, что случайное чтение маленьких блоков может происходить очень быстро.
  • Поддержка пространств имён.
  • Поддержка SR-IOV (Single Root I/O Virtualization) — технологии, которая позволяет виртуализировать устройства для ввода-вывода информации.

Подобные моменты очень важны для современных систем, где мы практически не видим жизни без виртуализации.

Итак, давайте попробуем воспользоваться протоколом NVME. В интернете существует один мануал, который рассказывает, как подключать эти диски. И к сожалению, это просто копи-паст одного и того же документа. Помимо пары ошибок, он просто не содержит в себе сколь-либо действенного описания того, что и как надо делать для того, чтобы подключить NVME диск через сеть.

Посему начнём.

▍ Подготовка системы

Для начала вам нужно будет установить свежее ядро для вашего линукса. Debian 11 и Ubuntu 21.10 поддерживают nvme “из коробки”. Возможно, у вас возникнут проблемы, если вы работаете с CentOS.

Если вдруг у вас в руках Gentoo или вы заходите собрать ядро сами, то включите следующие флаги при сборке ядра:

CONFIG_NVME_TCP=m
CONFIG_NVME_TARGET_TCP=m

Далее, нам нужно будет установить nvme-cli для того, чтобы работать с самими дисками. По этой ссылке вы сможете раскопать более подробную инструкцию о том, как пользоваться этой программой. nvmexpress.org/open-source-nvme-management-utility-nvme-command-line-interface-nvme-cli

apt install nvme-cli

Теперь можно загружать модули ядра. Для клиента нам нужно будет подключать модуль nvme. На моём лаптопе он постоянно загружен, так как он работает с nvme. Для того чтобы “расшарить” nvme диск вам понадобятся два других модуля.

modprobe nvmet
modprobe nvmet-tcp

▍ Сетевой стек

nvmet означает nmve-transport. Это драйвер транспортного протокола. Драйвер существует для нескольких типов протоколов передачи информации. Loop для передачи данных на одном хосте и tcp и fabrics для сетевой передачи данных. Fabrics предназначен для работы с fibre channel системами и не является особо доступным для малого бизнеса. Но к счастью для нас, у нас в руках есть TCP.

Хочу оговориться, что на самом деле, сам протокол не использует TCP/IP сокеты для передачи данных. Посмотрите на знаменитую картинку, слитую с сайта Oracle. Если соединение будет происходить через стэк TCP/IP, то передача данных будет осуществляться контроллером напрямую. Фактически, порт, к которому вы будете подключать устройство, будет отдан на растерзание драйверу, и после установки соединения вам не нужно будет беспокоиться о том, что что-то может остановить 6 гигабит в секунду. Вы полностью обходите Socket API для передачи информации.

Хорошо, загрузили драйверы.

▍ Подсистемы (subsystems)

Идём дальше. Конфигурацию можно производить из nvme-cli или путём создания и изменения файлов в /sys/kernel/config/. Туда и отправимся.

Для начала нам нужно будет создать подсистему (subsystem). В документации подсистема описана как набор информации о том, какой контроллер, порт и пространства имён нужно использовать для установки соединения. (It’s a set of all information of what controller, port, namespace and device to use for a connection).

Посему заходим в

cd /sys/kernel/config/nvmet/subsystems

И создаём там папку с названием нашей подсистемы. Название может быть произвольным, но учтите, что вам придётся его использовать как на клиенте, так и на сервере. Так что не нужно писать что-то очень сложное.

mkdir test

После захода в эту директорию мы можем осмотреться. На данном этапе нас интересует один фаил — attr_allow_any_host.

cat attr_allow_any_host
#0

По умолчанию никто не сможет подключится к вашей подсистеме, если они не находятся в списке allowed_hosts (см директорию в папке). Упростив задачу, вы сможете просто разрешить подключения с любого хоста, сделав следующее.

echo -n 1 > attr_allow_any_host 

(Учтите, система очень придирчива к чтению конфигурации и ненужные /n могут натворить проблем. Поэтому придётся не забывать флаг -n во всех echo командах).

▍ Пространства имён (namespaces)

После этого забираемся в папку namespaces и начинаем создавать пространства имён.

mkdir 1

Это создаст пространство имён #1.

В отличие от подсистем, пространства имён называются по номерам. Заходим и осматриваемся

 ls -lah
total 0
-rw-r--r-- 1 root root 4.0K Jan  3 09:31 ana_grpid
-rw-r--r-- 1 root root 4.0K Jan  3 09:31 buffered_io
-rw-r--r-- 1 root root 4.0K Jan  3 09:31 device_nguid
-rw-r--r-- 1 root root 4.0K Jan  2 17:36 device_path
-rw-r--r-- 1 root root 4.0K Jan  3 09:31 device_uuid
-rw-r--r-- 1 root root 4.0K Jan  2 17:36 enable
--w------- 1 root root 4.0K Jan  3 09:31 revalidate_size

Здесь мы можем произвести определённые настройки конкретного пространства имён.

Давайте отвлечёмся на секунду и посмотрим на жёсткий диск на моём лаптопе.

ls /dev/nvme* -lah
crw------- 1 root root 238, 0 Jan  2 17:31 /dev/nvme0
brw-rw---- 1 root disk 259, 0 Jan  2 17:31 /dev/nvme0n1
brw-rw---- 1 root disk 259, 1 Jan  2 17:31 /dev/nvme0n1p1
brw-rw---- 1 root disk 259, 2 Jan  2 17:31 /dev/nvme0n1p2
brw-rw---- 1 root disk 259, 3 Jan  2 17:31 /dev/nvme0n1p3
brw-rw---- 1 root disk 259, 4 Jan  2 17:31 /dev/nvme0n1p4
brw-rw---- 1 root disk 259, 5 Jan  2 17:31 /dev/nvme0n1p5
brw-rw---- 1 root disk 259, 6 Jan  2 17:31 /dev/nvme0n1p6

Никогда не замечали, что в NVME диски называются совсем не так, как привычные нам block устройства, sda, sdb и так далее. Уж слишком много циферок в этих nvme.

Первая цифра означает номер устройства. Диска. Это как раз и есть subsystem.

Вторая цифра (n) это namespace, в данном случае, на моём лаптопе все диски идут под одним пространством имён.

Третья цифра (p) обозначает уже привычные нам partitions, разделы.

Ок, разобрались с номенклатурой, давайте вернёмся к настройкам нашего namespace. Итак, в данном случае вы можете указать, какие диски будут являться частью вашего пространства имён, путём записи их в device_path

echo -n /dev/nvme0 > device_path

Теперь можно попробовать включить это пространство имён. Делаем

echo -n 1 > enabled

После этого у вас либо всё включится, либо ничего не включится. Узнать об это можно будет только после прочёса.

dmesg | grep nvmet

Там же вы можете прочитать настоящие сообщения об ошибках, если таковые имеются.

[  845.255544] nvmet: creating controller 1 for subsystem c413bb88-69e7-4d38-8d4c-081bce31ca47 for NQN nqn.2014-08.org.nvmexpress:uuid:d6d1a339-34d4-4ae0-be29-4375c2eeaf2c.
[  912.892151] nvmet: adding nsid 2 to subsystem c413bb88-69e7-4d38-8d4c-081bce31ca47
[  950.873917] nvmet: creating controller 1 for subsystem c413bb88-69e7-4d38-8d4c-081bce31ca47 for NQN nqn.2014-08.org.nvmexpress:uuid:d6d1a339-34d4-4ae0-be29-4375c2eeaf2c.
[ 1323.410291] nvmet: adding nsid 3 to subsystem c413bb88-69e7-4d38-8d4c-081bce31ca47

В данном случае я насоздавал 3 разных пространства имён. (Для названия подсистемы я использовал guid, так что не обращайте внимания на этот длинный c4… Это просто часть имени подсистемы.

▍ Порты (Ports)

Всё выглядит неплохо. Теперь мы можем сконфигурировать порт и подключить нашу подсистему к этому порту. Возвращаемся назад в /sys/kernel/config/nvmet/ и идём в ports.

Как обычно, создаём новый порт, путём создания директории. Все порты нумеруются начиная с единицы, так что присваивать им имена не получится.

mkdir 1

После этого — осматриваемся

# ls -lah
total 0
-rw-r--r-- 1 root root 4.0K Jan  2 17:38 addr_adrfam
-rw-r--r-- 1 root root 4.0K Jan  2 17:37 addr_traddr
-rw-r--r-- 1 root root 4.0K Jan  3 10:07 addr_treq
-rw-r--r-- 1 root root 4.0K Jan  2 17:37 addr_trsvcid
-rw-r--r-- 1 root root 4.0K Jan  2 17:41 addr_trtype
drwxr-xr-x 3 root root    0 Jan  2 17:36 ana_groups
-rw-r--r-- 1 root root 4.0K Jan  3 10:07 param_inline_data_size
-rw-r--r-- 1 root root 4.0K Jan  3 10:07 param_pi_enable
drwxr-xr-x 2 root root    0 Jan  2 17:36 referrals
drwxr-xr-x 2 root root    0 Jan  2 17:45 subsystems

Конфигурация такая же, как и в подсистемах. С помощью echo выгружаем следующие параметры в файлы:

addr_taddr — адрес, на который надо биндиться.
addr_trsvcid — порт, к которому присоединяемся. Обычно это 4420.
addr_trtype — тип протокола. В нашем случае это будет tcp
addr_adrfam — тип адреса — ipv4

Так, теперь у нас есть настроенный порт, пора подключать нашу подсистему к хосту. Для этого в настройках порта заходим в папку subsystems и создаём ссылку на подсистему, которую мы создали ранее.

ln -s ../../../subsystems/test .

(Что самое приятное, система не даст вам натворить тут бед. Вы не сможете создать неправильную ссылку и запороть таким образом жёсткий диск. Убедитесь, что вы понимаете синтаксис команды ln и создаёте ссылку в правильной директории.)

В моём случае получается следующее:

# ls -lah
lrwxrwxrwx 1 root root 0 Jan  2 17:45 c413bb88-69e7-4d38-8d4c-081bce31ca47 -> ../../../../nvmet/subsystems/c413bb88-69e7-4d38-8d4c-081bce31ca47

Ок, хорошо. Осталось проверить, что всё запустилось. При этом в dmesg вы увидите что-то вроде:

[  570.105916] nvmet_tcp: enabling port 1 (10.10.1.42:4420)

Отлично! Всё работает. Если не работает, то вы увидите причину ошибки, идите и исправляйте файлы конфигурации. Они обычно очевидные. Не тот тип протокола или неправильный адрес.

▍ Клиент

(Не забудьте сделать modprobe nvme и modprobe nvme-tcp на клиенте, или положите их в /etc/modules)

Осталось подключиться к жёсткому диску на клиенте. На клиенте, кстати, у меня запущен Debian 11, в котором я ничего не перенастраивал, и единственное, что я сделал, была установка nvme-cli

Запускаем команду

nvme connect -t tcp -n test -a 10.10.1.42 -s 4420

И проверяем наши жёсткие диски в системе.

# nvme list
Node             SN                   Model                                    Namespace Usage                      Format           FW Rev  
---------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- --------
/dev/nvme0n1     76b9b4aeef600ece     Linux                                    1           521.00  GB /   521.00  GB    512   B +  0 B   5.13.0-2

Ок, всё работает. Клиент видит диск. Делаем

ls /dev/nvme* -lah
crw------- 1 root root 238, 0 Jan  2 17:31 /dev/nvme0
brw-rw---- 1 root disk 259, 0 Jan  2 17:31 /dev/nvme0n1
brw-rw---- 1 root disk 259, 1 Jan  2 17:31 /dev/nvme0n1p1
brw-rw---- 1 root disk 259, 2 Jan  2 17:31 /dev/nvme0n1p2
brw-rw---- 1 root disk 259, 3 Jan  2 17:31 /dev/nvme0n1p3
brw-rw---- 1 root disk 259, 4 Jan  2 17:31 /dev/nvme0n1p4
brw-rw---- 1 root disk 259, 5 Jan  2 17:31 /dev/nvme0n1p5
brw-rw---- 1 root disk 259, 6 Jan  2 17:31 /dev/nvme0n1p6

И — о чудо! Весь жёсткий диск просто “перекочевал” на клиент.

▍ Аккуратнее!

И вот тут я совершил свою первую ошибку. Шестой раздел моего ноутбука был примонтирован как корневая файловая система моего линукса. Я решил рискнуть и примонтировать его как файловую систему на клиенте.

И знаете что? Мне это удалось!

Без проблем. Просто mount отлично сработал и я получил доступ к файловой системе.

Я проверил скорость работы диска, и у меня вышло реальных 20 мегабайт в секунду. Неплохо, учитывая то, что всё это делается по Wi-Fi.

После этого мой лэптоп начал шалить. Естественно, всё было весело, до тех пор, пока я не посмотрел на корневую систему с моего устройства и не выяснил, что она была заботливо перемонтирована с флагом RO.

Попытка отмонтировать эту систему на удалённом хосте не увенчалась успехом. Device busy, и всё. Попытка перемонтировать систему на локальном хосте была неудачной, и пришлось перезагрузиться. Ну как, пришлось начать перезагрузку, ведь закончить её не удалось, потому что система отказалась грузиться.

После небольшого количества fsck мне удалось всё поднять. Я решил больше не издеваться над самой файловой системой моего основного диска.

Вывод был следующим:
Использование утилит для работы с дисками, таких как fdiks, gparted и lvm — вполне возможно на двух хостах сразу. Вы можете создать новый раздел на удалённом хосте или вообще записать на него новую таблицу разделов. Это вполне законно. Главное, не пытаться монтировать эти разделы.

Монтирование ext4 разделов на двух хостах одновременно создаёт большое количество проблем.

Для того чтобы монтировать систему на нескольких хостах одновременно, вам будет нужна какая-нибудь кластерная файловая система.

Хорошо, разобравшись с тем, что делать можно и чего нельзя, я взялся за дополнительное тестирование.

И вот тут я открыл для себя странную возможность NVMET. Я подумал, что играться с единственным жёстким диском в системе на основном компьютере — это не кайф. Под рукой ничего подходящего не было, так что я взял 32х гигабайтную флешку, разрезал её на десяток разделов и начал эксперименты.

С точки зрения самой системы, это не запрещено — вы можете использовать NVME для доступа к любым устройствам хранения данных. Проблема в том, что NVME заточен для работы с NVM устройствами, а старые добрые крутящиеся жёсткие диски таковыми не являются. Это как если бы вы использовали танкер для перевозки одного чемодана. В принципе вы правы.

Я вернулся в настройки своего пространства имён и подключил различные разделы на своей флешке, как пространства имён в настройках NVME… И всё заработало!

Так как я не пытался монтировать файловые системы на двух хостах, то ничего ужасного не происходило.

Вот пример того, что получилось сделать:

На “сервере”:

lsblk
sdb                 8:16   1  29.3G  0 disk
├─sdb1              8:17   1   1.9G  0 part
├─sdb2              8:18   1   1.9G  0 part
├─sdb3              8:19   1   1.9G  0 part
├─sdb4              8:20   1   1.9G  0 part
├─sdb5              8:21   1   1.9G  0 part
└─sdb6              8:22   1   1.8G  0 part

У нас есть большой диск с разделами по 2 гигабайта.

Создаём namespace в настройках nvme и в device_path указываем следующее:

# cat device_path
/dev/sdb1

После чего монтируем этот диск по сети и смотрим, что примонтировалось на “клиенте”

lsblk
nvme0n1         259:6    0   1.9G  0 disk
nvme0n2         259:21   0   1.9G  0 disk
nvme0n3         259:23   0   1.9G  0 disk 

Наша обыкновенная глупая флешка, подключённая по usb 2.0 представлена как NVME диск на удалённом хосте. Причём она представляет собою три разных диска, так как я создал 3 разных пространства имён.

После этого давайте поупражняемся немного с LVM и создадим новый том на одном из этих дисков на “клиенте”

nvme0n1         259:6    0   1.9G  0 disk
└─egdisk1-lvol0 253:0    0   1.9G  0 lvm  
nvme0n2         259:21   0   1.9G  0 disk
nvme0n3         259:23   0   1.9G  0 disk

А теперь, внимание, без перемонтирования или чего-либо ещё, идём и смотрим на флешку с точки зрения сервера:

sdb                 8:16   1  29.3G  0 disk
├─sdb1              8:17   1   1.9G  0 part
│ └─egdisk1-lvol0 253:0    0   1.9G  0 lvm  
├─sdb2              8:18   1   1.9G  0 part
├─sdb3              8:19   1   1.9G  0 part
├─sdb4              8:20   1   1.9G  0 part
├─sdb5              8:21   1   1.9G  0 part
└─sdb6              8:22   1   1.8G  0 part

Бум! ОС распознаёт разделы! Всё работает более чем правильно.

Но погодите! Это ещё не всё.

При работе с подсистемой, каждый раз, когда я вносил изменения в namespaces, я отключал удалённый диск и выключал порт, чтобы сделать всё безопасно.

Но, как оказалось на практике, делать этого не нужно.

Вы можете пойти в настройки подсистемы и создать новые пространства имён прямо на ходу. При этом “клиент” автоматически “увидит” изменения в настройках диска и добавит тома в /dev/nvme*.

Интересно, подумал я. NVME предоставляет огромное количество инструментов для работы с удалёнными жёсткими дисками. Ввиду всего этого, мне захотелось собрать больше реальных данных.

Посему я закупил четыре серверных NVME на 512 гигабайт (CL SN720, которые, судя по документации, позиционируются как серверные NVME диски.), десять SAS SSDs, и 10Gbps сетевые адаптеры. Всё это готовится к работе, и в скором времени я выложу отчёт о производительности. Задача — измерение производительности дисковых устройств при работе с TCP/IP. Ждите новой статьи через неделю.

Адаптеры с адаптерами на адаптере.

Диски в адаптере. Внимание! Не используйте в настоящих серверах! Для этого есть форм-факторы получше.

 

Источник

Читайте также

Меню