Наверное каждый, кто хоть раз озадачивался поиском высокопроизводительного software-defiined хранилища рано или поздно слышал про DRBD, а может даже и имел дело с ним.
Правда на пике популярности Ceph и GlusterFS, которые работают в принципе неплохо, а главное сразу и из коробки, все просто немного подзабыли про него. Тем более что предыдущая версия не поддерживала репликацию более чем на два узла, и из-за чего часто встречались проблемы со split-brain, что явно не добавило ему популярности.
Решение и правда не новое, но вполне конкурентоспособное. При относительно небольших затратах на CPU и RAM, DRBD предоставляет реально быструю и безопасную синхронизацию на уровне блочного устройства. За все это время LINBIT — разработчики DRBD не стоят на месте и постоянно дорабатывают его. Начиная с версии DRBD9 перестает быть просто сетевым зеркалом и становится чем-то бОльшим.
Во первых, идея создания одного распределенного блочного устройства для нескольких серверов отошла на задний план, и теперь LINBIT старается предоставить инструменты оркестрации и управления множеством drbd-устройств в кластере, которые создаются поверх LVM и ZFS-разделов.
Например DRBD9 поддерживает до 32 реплик, RDMA, diskless-ноды, а новые инструменты оркестрации позволяют использовать снапшоты, online-миграцию и много чего другого.
Несмотря на то что DRBD9 имеет инструменты интеграции с Proxmox, Kubernetes, OpenStack и OpenNebula, на данный момент они находится в некотором переходном режиме, когда новые инструменты еще не везде поддерживаются, а старые уже очень скоро будут объявлены как deprecated. Речь идет о DRBDmanage и Linstor.
Я воспользуюсь этим моментом что бы не сильно вдаваться в подробности каждого из них, но более подробно рассмотреть настройку и принципы работы с самим DRBD9. Вам все равно придется c этим разрбраться, хотя бы потому что отказоустойчивая настройка контроллера Linstor, подразумевает его установку на одно их таких устройств.
В данной статье я бы хотел рассказать вам о DRBD9 и возможности его использования в Proxmox без сторонних плагинов.
DRBDmanage и Linstor
Во первых стоит еще раз упомянуть про DRBDmanage, который очень неплохо интегрируется в Proxmox. LINBIT предоставляет готовый плагин DRBDmanage для Proxmox который позволяет использовать все его функции прямо из интерфейса Proxmox.
Выглядит это и правда потрясающе, но к сожалению имеет некоторые минусы.
- Во первых захаркоженные названия томов, LVM-группа или ZFS-пул обязательно должен иметь название
drbdpool
. - Невозможность использования более одного пула на ноду
- В силу специфики решения сontroller volume может находиться только на обычном LVM и никак иначе
- Переодические глюки dbus, который тесно используется DRBDmanage для взаимодействия с нодами.
В результате чего, LINBIT было принято решение заменить всю сложную логику DRBDmanage на простое приложение, которое связывается с нодами используя обычное tcp-соединение и работает безо всякой там магии. Так появился Linstor.
Linstor и правда работает очень хорошо. К сожалению разработчики выбрали java как основной язык для написания Linstor-server, но пусть это не пугает вас, так как Linstor сам по себе занимается только распределением конфигов DRBD и нарезкой LVM/ZFS разделов на нодах.
Оба решения бесплатны и распространяются под свободной лицензией GPL3
Прочитать про каждое из них и о настройке вышеупомянутого плагина для Proxmox можно на официальной wiki Proxmox
Отказоустойчивый NFS-сервер
К сожалению на момент написания статьи Linstor имеет готовую интеграцию только с Kubernetes. Но в конце года ожидаются драйверы и для остальных систем Proxmox, OpenNebula, OpenStack.
Но пока готового решения нет, а старое нам так или иначе не нравится. Попробуем использовать DRBD9 по старинке для организации NFS-доступа на общий раздел.
Тем не менее данное решение так-же окажется не без плюсов, ведь NFS-сервер позволит вам организовать конкурентный доступ к файловой системе хранилища с нескольких серверов без необходимости использования сложных кластерных файловых систем с DLM, таких как OCFS и GFS2.
При этом вы получите возможность переключать роли Primary/Secondary нод просто мигрируя контейнер с NFS-сервером в интерфейсе Proxmox.
Вы так же сможете хранить любые файлы внутри этой файловой системы, так же как виртуальные диски и бэкапы.
В случае если вы используете Kubernetes вы сможете организовать ReadWriteMany доступ для ваших PersistentVolumes.
Proxmox и LXC-контейнеры
Теперь вопрос: почему Proxmox?
В принципе для построения подобной схемы мы могли бы использовать и Kubernetes так и обычную схему с кластер-менеджером. Но Proxmox предоставляет готовый, очень многофункциональный и в тоже время простой и интуитивно понятный интерфес практически для всего что нужно. Он из коробки умеет кластеризацию и поддерживает механизм fencing основанный на softdog. А при использовании LXC-контейнеров позволяет добиться минимальных таймаутов при переключении.
Полученное решение не будет иметь единой точки отказа.
По сути мы будем использовать Proxmox преимущественно как cluster-manager, где мы сможем рассматривать отдельный LXC-контейнер как сервис запущенный в классическом HA-кластере, лишь с тем отличием, что с контейнером в комплекте идет так же и его корневая система. То есть у вас нет необходимости устанавливать несколько экземаляров сервиса на каждом сервере отдельно, вы можете сделать это только один раз внутри контейнера.
Если вы когда-нибудь работали с cluster-manager software и обеспечением HA для приложений вы поймете что я имею ввиду.
Общая схема
Наше решение будет напоминать стандартную схему репликации какой-нибудь базы данных.
- У нас есть три ноды
- На каждой ноде распределенное drbd-устройство.
- На устройстве обычная файловая система (ext4)
- Только один сервер может быть мастером
- На мастере запущен NFS-сервер в LXC-контейнере.
- Все ноды обращаются к устройству строго через NFS
- При необходимости мастер может переехать на другую ноду, вместе с NFS-сервером
DRBD9 имеет одну очень крутую функцию которая сильно все упрощает:
drbd-устройство автоматически становиться Primary в тот момент когда оно монтируется на какой-нибудь ноде. В случае если устройство помечено как Primary, любая попытка смонтировать его на другой ноде приведет к ошибке доступа. Таким образом обеспечивается блокировка и гарантированная защита от одновременного доступа к устройству.
Почему это все сильно упрощает? Потому что при запуске контейрнера Proxmox автоматически монтирует это устройство и оно становится Primary на этой ноде, а при остановке контейнера наоборот размонтирует и устройство становится опять Secondary.
Таким образом нам больше не нужно беспокоиться о переключении Primary/Secondary устройств, Proxmox будет делать это автоматически, УРА!
Настройка DRBD
Ну хорошо, с идеей разобрались теперь перейдем к реализации.
По умолчанию в комплекте с ядром Linux поставляется модуль восьмой версии drbd, к сожалению он нам не подходит и нам необходимо установить модуль девятой версии.
Подключим репозиторий LINBIT и установим все необходимое:
wget -O- https://packages.linbit.com/package-signing-pubkey.asc | apt-key add -
echo "deb http://packages.linbit.com/proxmox/ proxmox-5 drbd-9.0" > /etc/apt/sources.list.d/linbit.list
apt-get update && apt-get -y install pve-headers drbd-dkms drbd-utils drbdtop
pve-headers
— заголовки ядра необходимые для сборки модуляdrbd-dkms
— модуль ядра в формате DKMSdrbd-utils
— основные утилиты для управления DRBDdrbdtop
— интерактивный инструмент, как top только для DRBD
После установки модуля проверим, все ли в порядке с ним:
# modprobe drbd
# cat /proc/drbd
version: 9.0.14-1 (api:2/proto:86-113)
Если вы увидите в выводе команды восьмую версию значит что-то пошло не так и загружен in-tree модуль ядра. Проверьте dkms status
что-бы разобраться в чем причина.
Каждая нода у нас будет иметь одно и тоже drbd-устройство запущенное поверх обычных разделов. Для начала нам нужно подготовить этот раздел под drbd на каждой ноде.
В качестве такого раздела может выступать любое блочное устройство, это может быть lvm, zvol, mdadm раздел, раздел диска или весь диск целиком. В этой статье я буду использовать отдельный nvme диск с разделом под drbd: /dev/nvme1n1p1
Стоит заметить, что имена устройств имеют свойство иногда меняться, так что лучше сразу взять за привычку использовать постоянный симлинк на устройство.
Найти такой симлинк для /dev/nvme1n1p1
можно таким образом:
# find /dev/disk/ -lname '*/nvme1n1p1'
/dev/disk/by-partuuid/847b9713-8c00-48a1-8dff-f84c328b9da2
/dev/disk/by-path/pci-0000:0e:00.0-nvme-1-part1
/dev/disk/by-id/nvme-eui.0000000001000000e4d25c33da9f4d01-part1
/dev/disk/by-id/nvme-INTEL_SSDPEKKA010T7_BTPY703505FB1P0H-part1
Опишем наш ресурс на всех трех нодах:
# cat /etc/drbd.d/nfs1.res
resource nfs1 {
meta-disk internal;
device /dev/drbd100;
protocol C;
net {
after-sb-0pri discard-zero-changes;
after-sb-1pri discard-secondary;
after-sb-2pri disconnect;
}
on pve1 {
address 192.168.2.11:7000;
disk /dev/disk/by-partuuid/95e7eabb-436e-4585-94ea-961ceac936f7;
node-id 0;
}
on pve2 {
address 192.168.2.12:7000;
disk /dev/disk/by-partuuid/aa7490c0-fe1a-4b1f-ba3f-0ddee07dfee3;
node-id 1;
}
on pve3 {
address 192.168.2.13:7000;
disk /dev/disk/by-partuuid/847b9713-8c00-48a1-8dff-f84c328b9da2;
node-id 2;
}
connection-mesh {
hosts pve1 pve2 pve3;
}
}
Желательно для синхронизации drbd использовать отдельную сеть.
Теперь создадим метаданные для drbd и запустим его:
# drbdadm create-md nfs1
initializing activity log
initializing bitmap (320 KB) to all zero
Writing meta data...
New drbd meta data block successfully created.
success
# drbdadm up nfs1
Повторим эти действия на всех трех нодах и проверим состояние:
# drbdadm status
nfs1 role:Secondary
disk:Inconsistent
pve2 role:Secondary
peer-disk:Inconsistent
pve3 role:Secondary
peer-disk:Inconsistent
Сейчас наш диск Inconsistent на всех трех нодах, это потому, что drbd не знает какой диск должен быть взят в качестве оригинала. Мы должны пометить один из них как Primary, что бы его состояние синхронизировалось на остальные ноды:
drbdadm primary --force nfs1
drbdadm secondary nfs1
Сразу после этого начнется синхронизация:
# drbdadm status
nfs1 role:Secondary
disk:UpToDate
pve2 role:Secondary
replication:SyncSource peer-disk:Inconsistent done:26.66
pve3 role:Secondary
replication:SyncSource peer-disk:Inconsistent done:14.20
Нам не обязательно дожидаться ее окончания и мы можем параллельно выполнять дальнейшие шаги. Их можно выполнять на любой ноде, вне зависимости от ее текущего состояния локального диска в DRBD. Все запросы будут автоматически перенаправлены на устройство с UpToDate состоянием.
Стоит не забыть активировать автозапуск drbd-сервиса на нодах:
systemctl enable drbd.service
Настройка LXC-контейнера
Опустим часть настройки кластера Proxmox из трех нод, эта часть хорошо описана в официальной wiki
Как я говорил раньше наш NFS-сервер будет работать в LXC-контейнере. Сам контейнер мы будем держать на устройстве /dev/drbd100
, которое мы только что создали.
Сначала нам нужно создать файловую систему на нем:
mkfs -t ext4 -O mmp -E mmp_update_interval=5 /dev/drbd100
Proxmox по умолчанию включает multimount protection на уровне файловой системы, в принципе мы можем обойтись и без нее, т.к. DRBD по умолчанию имеет собственную защиту, он просто запретит второй Primary для устройства, но осторожность нам не повредит.
Теперь скачаем шаблон Ubuntu:
# wget http://download.proxmox.com/images/system/ubuntu-16.04-standard_16.04-1_amd64.tar.gz -P /var/lib/vz/template/cache/
И создадим из него наш контейнер:
pct create 100 local:vztmpl/ubuntu-16.04-standard_16.04-1_amd64.tar.gz --hostname=nfs1 --net0=name=eth0,bridge=vmbr0,gw=192.168.1.1,ip=192.168.1.10/24 --rootfs=volume=/dev/drbd100,shared=1
В данной команде мы указываем что корневая система нашего контейнера будет находиться на устройстве /dev/drbd100
и добавим параметр shared=1
что бы разрешить миграцию контейнера между нодами.
Если что-то пошло не так, вы всегда можете поправить это через интерфейс Proxmox или в конфиге контейнера /etc/pve/lxc/100.conf
Proxmox распакует шаблон и подготовит корневую систему контейнера для нас. После этого мы можем запустить наш контейнер:
pct start 1140
Настройка NFS-сервера.
По умолчанию Proxmox не разрешает запуск NFS-сервера в контейнере, но есть несколько способов разрешить это.
Один из них просто добавить lxc.apparmor.profile: unconfined
в конфиг нашего контейнера /etc/pve/lxc/100.conf
.
Или мы можем разрешить NFS для всех контейнеров на постоянной основе, для этого нужно обновить стандартный шаблон для LXC на всех нодах, добавьте в /etc/apparmor.d/lxc/lxc-default-cgns
следующие строки:
mount fstype=nfs,
mount fstype=nfs4,
mount fstype=nfsd,
mount fstype=rpc_pipefs,
После изменений перезапустите контейнер:
pct shutdown 100
pct start 100
Теперь давайте залогинимся в него:
pct exec 100 bash
Установим обновления и NFS-сервер:
apt-get update
apt-get -y upgrade
apt-get -y install nfs-kernel-server
Создадим экспорт:
echo '/data *(rw,no_root_squash,no_subtree_check)' >> /etc/exports
mkdir /data
exportfs -a
Настройка HA
На момент написания статьи proxmox HA-manager имеет баг, который не позволяет HA-контейнеру успешно завершить свою работу, в результате чего, не до конца убитые kernel-space процессы nfs-сервера не дают drbd-устройству уйти в Secondary. В случае если вы уже столкнулись с такой ситуацией не стоит паниковать и просто выполните killall -9 nfsd
на ноде, где был запущен контейнер и тогда drbd-устройство должно "отпустить" и оно перейдет в Secondary.
Что бы этот баг поправить, выполните следующие команды на всех нодах:
sed -i 's/forceStop => 1,/forceStop => 0,/' /usr/share/perl5/PVE/HA/Resources/PVECT.pm
systemctl restart pve-ha-lrm.service
Теперь мы можем перейти к кофигурации HA-manager. Создадим отдельную HA-группу для нашего устройства:
ha-manager groupadd nfs1 --nodes pve1,pve2,pve3 --nofailback=1 --restricted=1
Наш ресурс будет работать только на нодах указанных для этой группы. Добавим наш контейнер в эту группу:
ha-manager add ct:100 --group=nfs1 --max_relocate=3 --max_restart=3
На этом все. Просто, не правда ли?
Полученную nfs-шару можно сразу-же подключить в Proxmox, для хранения и запуска других виртуальных машин и контейнеров.
Рекомендации и тюнинг
DRBD
Как я отметил выше, всегда желательно использовать отдельную сеть под репликацию. Крайне желательно использовать 10-гигабитные сетевые адаптеры, в противном случае у вас все упрется в скорость портов.
Если репликация вам кажется достаточно медленной попробуйте потюнить некоторые параметры для DRBD. Вот конфиг, который на мой взгляж является оптимальными для моей 10G-сети:
# cat /etc/drbd.d/global_common.conf
global {
usage-count yes;
udev-always-use-vnr;
}
common {
handlers {
}
startup {
}
options {
}
disk {
c-fill-target 10M;
c-max-rate 720M;
c-plan-ahead 10;
c-min-rate 20M;
}
net {
}
}
Подробнее про каждый параметр вы можете получить информацию из официальной документации DRBD
NFS-сервер
Для ускорения работы NFS-сервера возможно поможет увеличение общего числа запускаемых экземпляров NFS-сервера. По умолчанию — 8, лично мне помогло увеличение этого числа до 64.
Что бы этого добиться обновите параметр RPCNFSDCOUNT=64
в /etc/default/nfs-kernel-server
.
И перезапустите демоны:
systemctl restart nfs-utils
systemctl restart nfs-server
NFSv3 vs NFSv4
Знаете в чем отличие между NFSv3 и NFSv4?
- NFSv3 — это stateless протокол как правило он лучше переносит сбои и быстрее восстанавливается.
- NFSv4 — это stateful протокол, он работает быстрее и может быть привязан к определенным tcp-портам, но из-за наличия состояния он более чувствителен к сбоям. В нем так же есть возможность использование аутентификации с помощью Kerberos и куча других интересных функций.
Тем не менее когда вы выполняете showmount -e nfs_server
используется протокол NFSv3. Proxmox тоже использует NFSv3. NFSv3 так же обычно используется для организации загрузки машин по сети.
В общем, если у вас нет особых причин использовать NFSv4, старайтесь использовать NFSv3 так как он менее болезненно переживает любые сбои из-за отсутствия состояния как такового.
Примонтировать шару используя NFSv3 можно указав параметр -o vers=3
для команды mount:
mount -o vers=3 nfs_server:/share /mnt
При желании можно отключить NFSv4 для сервера вообще, для этого добавьте опцию --no-nfs-version 4
в переменную RPCNFSDCOUNT
и перезапустите сервер, пример:
RPCNFSDCOUNT="64 --no-nfs-version 4"
iSCSI и LVM
Подобным образом внутри контейнера может быть настроен и обычный tgt-демон, iSCSI будет выдавать значительно бОльшую производительность для операций ввода-вывода, а контейнер будет работать более гладко ввиду того tgt-сервер работает полностью в пространстве пользователя.
Как правило экспортируемый LUN нарезается на много кусочков с помощью LVM. Однако есть несколько ньюансов которые стоит учесть, например: каким образом обеспечены блокировоки LVM для совместного использования экспортированной группы на нескольких хостах.
Пожалуй эти и другие ньюансы я опишу в следующей статье.
amarao
Модель DRBD с _двумя_ узлами в кластере с самого начала полна шизофрении и split-brain'а. Как бы с ним не боролись, без кворума никак. А какой кворум у DRBD? Ну… Вот такой вот кворум, из двух, всегда друг с другом согласных узлов.
Magister7
Под небольшой нагрузкой и с определенной осторожностью — вполне себе работал DRBR 8 с Dual Primary.
Но да — конструкция ненадежная была.
amarao
Разумеется, она работает, когда всё хорошо.
Все кластерные системы надо смотреть не в том, как они работают (happy path), а как они обрабатывают ошибки (sad path). У drbd такой sad path, что он, скорее, bad path по всем признакам.
UPD: И дело не только в primary/primary. Даже модель primary/secondary с автопромоутингом secondary на primary — всё равно шизофренична.
Pacemaker в такой конфигурации не работает даже со STONITH, потому что из-за сетевого сбоя «плохая» нода может прибить хорошую, и нет никакого арбитра понять кто прав, а кто баг.
kvaps Автор
Если мне не изменяет память, для STONITH было «замечательное» решение с рандомным таймаутами на убийство второй ноды для решения этой проблемы :)
amarao
Рандомный таймаут не решает проблемы, от слова «совсем».
Если у нас происходит partitioning, то для двух нод не существует метода понять, какая половинка «в сети». node1 думает, что она в сети, node2 думает, что она в сети. Не важно кто кого первым прибъёт, т.к. каждая из них может ошибаться, есть 50% шанс, что умрёт не та нода.
Чтобы такого не было, нужен кворум. А кворум требует большинства. Большинство для двух нод — это две ноды, т.е. неудобно. Чтобы было удобно, нужна третья нода — арбитр.