image


Наверное каждый, кто хоть раз озадачивался поиском высокопроизводительного 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 — модуль ядра в формате DKMS
  • drbd-utils — основные утилиты для управления DRBD
  • drbdtop — интерактивный инструмент, как 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 для совместного использования экспортированной группы на нескольких хостах.


Пожалуй эти и другие ньюансы я опишу в следующей статье.

Комментарии (5)


  1. amarao
    19.07.2018 12:26

    Модель DRBD с _двумя_ узлами в кластере с самого начала полна шизофрении и split-brain'а. Как бы с ним не боролись, без кворума никак. А какой кворум у DRBD? Ну… Вот такой вот кворум, из двух, всегда друг с другом согласных узлов.


    1. Magister7
      19.07.2018 12:55

      Под небольшой нагрузкой и с определенной осторожностью — вполне себе работал DRBR 8 с Dual Primary.
      Но да — конструкция ненадежная была.


      1. amarao
        19.07.2018 13:05
        +1

        Разумеется, она работает, когда всё хорошо.

        Все кластерные системы надо смотреть не в том, как они работают (happy path), а как они обрабатывают ошибки (sad path). У drbd такой sad path, что он, скорее, bad path по всем признакам.

        UPD: И дело не только в primary/primary. Даже модель primary/secondary с автопромоутингом secondary на primary — всё равно шизофренична.

        Pacemaker в такой конфигурации не работает даже со STONITH, потому что из-за сетевого сбоя «плохая» нода может прибить хорошую, и нет никакого арбитра понять кто прав, а кто баг.


        1. kvaps Автор
          19.07.2018 13:19

          Если мне не изменяет память, для STONITH было «замечательное» решение с рандомным таймаутами на убийство второй ноды для решения этой проблемы :)


          1. amarao
            19.07.2018 13:41
            +1

            Рандомный таймаут не решает проблемы, от слова «совсем».

            Если у нас происходит partitioning, то для двух нод не существует метода понять, какая половинка «в сети». node1 думает, что она в сети, node2 думает, что она в сети. Не важно кто кого первым прибъёт, т.к. каждая из них может ошибаться, есть 50% шанс, что умрёт не та нода.

            Чтобы такого не было, нужен кворум. А кворум требует большинства. Большинство для двух нод — это две ноды, т.е. неудобно. Чтобы было удобно, нужна третья нода — арбитр.