Удаление старых файлов pacemaker raw sig. Установка и базовая настройка Pacemaker и CLVM

19.03.2019

В этой статье я опишу установку и настройку Active/Active кластера на основе Pacemaker, Corosync 2.x и CLVM с использованием разделяемого хранилища. Покажу, как приспособить этот кластер для работы с контейнерами LXC и Docker. Опишу команды для работы с кластером. И припомню те грабли, в которые вляпался, что, надеюсь, облегчит судьбу следующим проходимцам.

В качестве серверных дистрибутивов буду использовать CentOS 7 + epel и актуальные версии пакетов в них. Основной инструмент для работы с Pacemaker-ом будет PCS (pacemaker/corosync configuration system).

Подготовка серверов

Я использовал конфигурацию из двух узлов, но их количество можно увеличивать по мере надобности. Сервера имеют общее разделяемое хранилище, подключённое по SAS. Если такого под рукой не найдётся, то можно использовать хранилище подключаемое FC или iSCSI. Потребуются два тома, один для общих нужд, другой для Docker-а. Можно один том разбить на два раздела.
Устанавливаем CentOS 7, epel репозиторий и настраиваем сеть. Использование bonding-а для сетевых интерфейсов и multipath для SAS желательны. Для работы с различными vlan-ми настраиваем соответствующие мосты br0.VID, к которым потом будем привязывать контейнера LXC. Подробности описывать не буду – всё стандартно.

Lelik13a 20 июля 2015 в 13:33

HA-Cluster на основе Pacemaker под контейнерную виртуализацию LXC и Docker

  • Виртуализация

В этой статье я опишу установку и настройку Active/Active кластера на основе Pacemaker, Corosync 2.x и CLVM с использованием разделяемого хранилища. Покажу, как приспособить этот кластер для работы с контейнерами LXC и Docker. Опишу команды для работы с кластером. И припомню те грабли, в которые вляпался, что, надеюсь, облегчит судьбу следующим проходимцам.

В качестве серверных дистрибутивов буду использовать CentOS 7 + epel и актуальные версии пакетов в них. Основной инструмент для работы с Pacemaker-ом будет PCS (pacemaker/corosync configuration system).

Подготовка серверов

Я использовал конфигурацию из двух узлов, но их количество можно увеличивать по мере надобности. Сервера имеют общее разделяемое хранилище, подключённое по SAS. Если такого под рукой не найдётся, то можно использовать хранилище подключаемое FC или iSCSI. Потребуются два тома, один для общих нужд, другой для Docker-а. Можно один том разбить на два раздела.
Устанавливаем CentOS 7, epel репозиторий и настраиваем сеть. Использование bonding-а для сетевых интерфейсов и multipath для SAS желательны. Для работы с различными vlan-ми настраиваем соответствующие мосты br0.VID, к которым потом будем привязывать контейнера LXC. Подробности описывать не буду – всё стандартно.

Для работы LXC и Docker нужно отключить штатный firewald.
# systemctl stop firewalld.service # systemctl disable firewalld.service # setenforce Permissive Так же переводим selinux в режим permissive , для облегчения настройки и смелых экспериментов. Позже, когда отладим, переключим назад, может быть.

Сразу же внесём необходимые адреса и имена в /etc/hosts на всех узлах:
#nodes, vlan 10 10.1.0.1 cluster-1 10.1.0.2 cluster-2 #nodes ipmi, vlan 314 10.1.15.1 ipmi-1 10.1.15.2 ipmi-2 #docker, vlan 12 10.1.2.10 docker 10.1.2.11 dregistry

Для работы будет необходим механизм STONITH («Shoot The Other Node In The Head»), в качестве которого используем ipmi. Настраиваем с помощью ipmitool :
# ipmitool shell impitool> user set name 2 admin impitool> user set password 2 "очень секретный пароль" # < privilege level> ) impitool> user priv 2 4 1 Заводим пользователя admin (id=2) и даём ему права администратора (livel=4) на канал связанный с сетевым интерфейсом (channel=1).

Сеть для ipmi желательно вынести в отдельный vlan, во первых это позволит её изолировать, во вторых не будет проблем со связностью, если IPMI BMC (baseboard management controller) разделяет сетевой интерфейс с сервером.
impitool> lan set 1 ipsrc static impitool> lan set 1 ipaddr 10.1.15.1 impitool> lan set 1 netmask 255.255.255.0 impitool> lan set 1 defgw ipaddr 10.1.15.254 impitool> lan set 1 vlan id 314 # настройка доступа: impitool> lan set 1 access on impitool> lan set 1 auth ADMIN MD5 ipmitool> channel setaccess 1 2 callin=on ipmi=on link=on privilege=4 На других узлах аналогично, только IP разные.

Проверь связность можно так:
# ipmitool -I lan -U admin -P "очень секретный пароль" -H 10.1.15.1 bmc info

Установка и базовая настройка Pacemaker и CLVM

Если вы не знаете что такое Pacemaker, то желательно сначала почитать про него. Хорошо и по русски про Pacemaker .
На всех узлах устанавливаем пакеты из epel репозитория:
# yum install pacemaker pcs resource-agents fence-agents-all
На всех узлах устанавливаем пароль для администратора кластера hacluster. Под этим пользователем работает PCS , а так же доступен веб-интерфейс управления.
echo CHANGEME | passwd --stdin hacluster

Дальше операции выполняются на одном узле.
Настраиваем аутентификацию:
# pcs cluster auth cluster-1 cluster-2 -u hacluster -p CHANGEME --force
Создаём и запускаем кластер “Cluster” из двух узлов:
# pcs cluster setup --force --name Cluster cluster-1 cluster-2 # pcs cluster start --all
Смотрим результат:
# pcs cluster status Cluster Status: Last updated: Wed Jul 8 14:16:32 2015 Last change: Wed Jul 8 10:01:20 2015 Stack: corosync Current DC: cluster-1 (1) - partition with quorum Version: 1.1.12-a14efad 2 Nodes configured 17 Resources configured (всё ещё впереди) PCSD Status: cluster-1: Online cluster-2: Online
Есть один нюанс, если в переменных окружения задан https_proxy, то pcs может врать о статусе узлов, видимо пытаясь использовать проксю.

Запускаем и прописываем в автозагрузку демон pcsd :
# systemctl start pcsd # systemctl enable pcsd
После этого доступен веб-интерфейс управления кластером по адресу "https://ip_узла:2224"

Интерфейс позволит посмотреть состояние кластера, добавить или изменить его параметры. Мелочь, а приятно.

Так как узла у нас всего два, то кворума у нас не будет, потому нужно эту политику отключить:
# pcs property set no-quorum-policy=ignore
Для авто-запуска узлов кластера достаточно добавить в автозагрузку pacemaker :
# systemctl enable pacemaker

Для работы CLVM и GFS2 необходим DLM (Distributed Lock Manager). И CLVM и DLM в RHEL7 (CentOS 7) как самостоятельные демоны отсутствуют и являются ресурсами кластера. При этом для работы DLM требуется STONITH, иначе соответствующий ресурс кластера не запустится. Настраиваем:
# pcs property set stonith-enabled=true # pcs stonith create cluster-1.stonith fence_ipmilan ipaddr="ipmi-1" passwd="пароль на ipmi" login="admin" action="reboot" method="cycle" pcmk_host_list=cluster-1 pcmk_host_check=static-list stonith-timeout=10s op monitor interval=10s # pcs stonith create cluster-2.stonith fence_ipmilan ipaddr="ipmi-2" passwd="пароль на ipmi" login="admin" action="reboot" method="cycle" pcmk_host_list=cluster-2 pcmk_host_check=static-list stonith-timeout=10s op monitor interval=10s # pcs constraint location cluster-1.stonith avoids cluster-1=INFINITY # pcs constraint location cluster-2.stonith avoids cluster-2=INFINITY
Почему так, . Если вкратце, то заводим два stonith ресурса, каждый отвечающий за свой узел и запрещаем им работать на узле, который они должны пристреливать.

Настроим дополнительные глобальные параметры.
Для экспериментов, полезно настроить миграцию ресурсов после первого сбоя:
# pcs resource defaults migration-threshold=1
Чтобы ресурс, мигрировавший на другой узел в результате сбоя, не возвращался назад, по восстановлении узла выставим:
# pcs resource defaults resource-stickiness=100 Где «100» это некий вес, на основе которого Pacemaker рассчитывает поведение ресурса.

Чтобы в разгар смелых экспериментов узлы не перестреляли друг друга, рекомендую явно задать политику на отказ ресурса:
# pcs resource op defaults on-fail=restart Иначе на самом интересном месте сработает stonith, который по умолчанию на отказ команды «stop».

Устанавливаем CLVM на каждом узле:
# yum install lvm2 lvm2-cluster
Настраиваем LVM для работы в кластере на каждом узле:
# lvmconf --enable-cluster
Заводим ресурсы dlm и clvmd в кластере:
# pcs resource create dlm ocf:pacemaker:controld op monitor interval=30s on-fail=fence clone interleave=true ordered=true # pcs resource create clvmd ocf:heartbeat:clvm op monitor interval=30s on-fail=fence clone interleave=true ordered=true Это критичные ресурсы для нашего кластера, потому для ситуации сбоя явно задаём политику stonith (on-fail=fence ). Ресурс должен быть запущен на всех узлах кластера, потому он объявляется клонированным (clone ). Запускаем ресурсы по очереди, а не параллельно (ordered=true ). Если ресурс зависит от других clone-ресурсов, то не ждём запуска всех образцов ресурса на всех узлах, а довольствуемся локальным (interleave=true ). Обратите внимание на последние два параметра, они могут существенно отразится на работе кластера в целом, а clone-ресурсы у нас ещё будут.

Задаём порядок запуска ресурсов, при котором clvmd запускается только после dlm . В командах используется уже служебное имя *-clone , которое обозначает ресурс на конкретном узле:
# pcs constraint order start dlm-clone then clvmd-clone # pcs constraint colocation add clvmd-clone with dlm-clone Так же обязываем запускать ресурс clvmd-clone вместе с dlm-clone на одном узле. В нашем случае двух узлов это кажется излишним, но в общем случае *-clone ресурсов может быть меньше, чем количество узлов и тогда совместное расположение становится критичным.

Сразу же по созданию ресурсов они запускаются на всех узлах, и если всё хорошо, то можем приступать к созданию разделяемых логических томов и файловых систем. clvmd проследит за целостностью метаданных и оповестит об изменениях все узлы, потому операцию проделываем на одном узле.
Инициализируем разделы для использования в LVM:
# pvcreate /dev/mapper/mpatha1 # pvcreate /dev/mapper/mpatha2
Вообще, работа с кластерным LVM почти не отличается от работы с обычным, с той лишь разницей, что если группа томов (VG) помечена как кластерная, то её метаданные контролирует clvmd. Создаём группы томов:
# vgcreate --clustered y shared_vg /dev/mapper/mpatha1 # vgcreate --clustered y shared_vg-ex /dev/mapper/mpatha2
Общая настройка кластера завершена, дальше будем его наполнять ресурсами. Ресурс с точки зрения Pacemaker-а, это любая служба, процесс, ip-адрес работой которого можно управлять скриптами. Сами ресурс-скрипты похожи на init-скрипты и так же выполняют набор функций start, stop, monitor и тп. Основной принцип, которому мы будем следовать – данные необходимые ресурсу, работающему на одном узле, помещаем в логический раздел группы shared_vg и любую файловую систему по желанию; данные, которые необходимы на обоих узлах одновременно, помещаем на GFS2. За целостностью данных в первом случае проследит Pacemaker, который контролирует количество и расположение запущенных ресурсов, включая используемые файловые системы. Во втором случае внутренние механизмы GFS2. Группа shared_vg-ex будет полностью отдана под логический раздел для Docker-а. Дело в том, что Docker создаёт разреженный том (thin provisioned), который может быть активен только в эксклюзивном режиме на одном узле. А поместить этот том в отдельную группу удобно для дальнейшей работы и настройки.

Работа с LXC в кластере

Работать будем используя утилиты lxc-*, которые входят в пакет lxc . Ставим:
# yum install lxc lxc-templates
Настаиваем параметры по умолчанию для будущих контейнеров:
# cat /etc/lxc/default.conf lxc.start.auto = 0 lxc.network.type = veth lxc.network.link = br0.10 lxc.network.flags = up # memory and swap lxc.cgroup.memory.limit_in_bytes = 256M lxc.cgroup.memory.memsw.limit_in_bytes = 256M Тип сети будет veth – внутри контейнера интерфейс eth0, вне контейнера он будет прицеплен к мосту br0.10 . Из ограничений у нас в ходу только по памяти, их и укажем. При желании можно прописать любые, поддерживаемые ядром, по принципу lxc.cgroup.state-object-name=value . Так же их можно менять на лету с помощью lxc-cgroup . На файловой системе эти параметры представлены по пути /sys/fs/cgroup/TYPE/lxc/CT-NAME/object-name . Важный момент по поводу ограничений: параметр memory.limit_in_bytes должен быть указан перед memory.memsw.limit_in_bytes . А так же, второй параметр - сумма памяти и свопа, и должен быть больше или равен первому. Иначе машина запустится без ограничений по памяти.
Размещением, запуском и остановкой контейнеров будет управлять Pacemaker, потому автозагрузку контейнеров выключаем явно.

Каждый LXC контейнер будет жить в своём логическом томе группы shared_vg. Настроим имя VG по умолчанию:
# cat /etc/lxc/lxc.conf lxc.bdev.lvm.vg = shared_vg
Такое размещение позволит запускать контейнер на любом узле кластера. Файлы конфигурации контейнеров тоже должны быть общие, по этому создадим общую файловую систему и настроим её использование на всех узлах:
# lvcreate -L 500M -n lxc_ct shared_vg # mkfs.gfs2 -p lock_dlm -j 2 -t Cluster:lxc_ct /dev/shared_vg/lxc_ct Выбираем протокол блокировок lock_dlm , так как хранилище общее. Заводим два журнала, по количеству узлов (-j2 ). Настраиваем имя в таблице блокировок, где Cluster - имя нашего кластера.

# pcs resource create fs-lxc_ct Filesystem fstype=gfs2 device=/dev/shared_vg/lxc_ct directory=/var/lib/lxc clone ordered=true interleave=true # pcs constraint order start clvmd-clone then fs-lxc_ct-clone Заводим очередной clone-ресурс, типа Filesystem , поля device и directory обязательны и описывают что примонтировать и куда. И указываем порядок запуска ресурса, так как без clvmd файловая система не примонтируется. После этого, на всех узлах директория, где LXC хранит настройки контейнеров, примонтируется.

Создадим первый контейнер:
# lxc-create -n lxc-racktables -t oracle -B lvm --fssize 2G --fstype ext4 --vgname shared_vg -- -R 6.6 Здесь lxc-racktables - имя контейнера, oracle – используемый шаблон. -B определяет тип используемого хранилища и параметры. lxc-create создаст LVM раздел и развернёт туда базовую систему, согласно шаблону. После "--" указываются параметры шаблона, в моём случае - версия.
На момент написания статьи шаблон из пакета для centos на lvm не работал, но меня устроил и oracle.
Если нужно развернуть систему на базе deb пакетов, то сначала необходимо поставить утилиту debootstrap . Подготовленная система сначала разворачивается в /var/cache/lxc/ и при каждом последующем запуске lxc-create обновляет пакеты системы до текущих версий. Для себя удобно собрать собственный шаблон, со всеми нужными предустановками. Стандартные шаблоны находятся тут: /usr/share/lxc/templates .
Так же можно использовать специальный шаблон "download ", который скачивает из репозитория уже подготовленные архивы систем.

Контейнер готов. Управлять контейнерами можно утилитами lxc-*. Запускаем в фоне, смотрим его состояние, останавливаем:
# lxc-start -n lxc-racktables -d # lxc-info -n lxc-racktables Name: lxc-racktables State: RUNNING PID: 9364 CPU use: 0.04 seconds BlkIO use: 0 bytes Memory use: 1.19 MiB KMem use: 0 bytes Link: vethS7U8J1 TX bytes: 90 bytes RX bytes: 90 bytes Total bytes: 180 bytes # lxc-stop -n lxc-racktables
Настраиваем дополнительные параметры контейнера либо в его консоли с помощью lxc-console , либо примонтировав куда-нибудь lvm раздел контейнера.

Теперь можно отдать управление Pacemaker-у. Но сначала возьмём свежий ресурс-файл управления с GitHub -а:
# wget -O /usr/lib/ocf/resource.d/heartbeat/lxc https://raw.githubusercontent.com/ClusterLabs/resource-agents/master/heartbeat/lxc # chmod +x /usr/lib/ocf/resource.d/heartbeat/lxc
Директория /usr/lib/ocf/resource.d/ содержит файлы управления ресурсами в иерархии provider/type. Посмотреть весь список ресурсов можно командой pcs resource list . Посмотреть описание определённого ресурса - pcs resource describe .

Пример:

# pcs resource describe ocf:heartbeat:lxc ocf:heartbeat:lxc - Manages LXC containers Allows LXC containers to be managed by the cluster. If the container is running "init" it will also perform an orderly shutdown. It is "assumed" that the "init" system will do an orderly shudown if presented with a "kill -PWR" signal. On a "sysvinit" this would require the container to have an inittab file containing "p0::powerfail:/sbin/init 0" I have absolutly no idea how this is done with "upstart" or "systemd", YMMV if your container is using one of them. Resource options: container (required): The unique name for this "Container Instance" e.g. "test1". config (required): Absolute path to the file holding the specific configuration for this container e.g. "/etc/lxc/test1/config". log: Absolute path to the container log file use_screen: Provides the option of capturing the "root console" from the container and showing it on a separate screen. To see the screen output run "screen -r {container name}" The default value is set to "false", change to "true" to activate this option


Итак, добавляем в наш кластер новый ресурс:
# pcs resource create lxc-racktables lxc container=lxc-racktables config=/var/lib/lxc/lxc-racktables/config # pcs constraint order start fs-lxc_ct-clone then lxc-racktables И указываем порядок запуска.
Ресурс сразу же запустится и его состояние можно узнать командой pcs status . Если запуск не удался, то там же будет и возможная причина. Команда pcs resource debug-start <resource id> позволит сделать запуск ресурса с выводом на экран результата:
# pcs resource debug-start lxc-racktables Operation start for lxc-racktables (ocf:heartbeat:lxc) returned 0 > stderr: DEBUG: State of lxc-racktables: State: STOPPED > stderr: INFO: Starting lxc-racktables > stderr: DEBUG: State of lxc-racktables: State: RUNNING > stderr: DEBUG: lxc-racktables start: 0
Но с ней нужно быть осторожнее, она игнорирует настройки кластера по размещению ресурса и запускает его на текущем узле. И если ресурс уже запущен на другом узле, то могут быть неожиданности. Модификатор "--full" выдаст много дополнительной информации.
Хоть управляет контейнером Pacemaker, но с ним по прежнему можно работать всеми lxc-* утилитами, конечно только на узле, на котором он в данный момент работает и с оглядкой на Pacemaker.

Получившийся контейнер-ресурс можно перенести на другой узел, выполнив:
# pcs resource move <resource id> В таком случае, контейнер корректно завершит работу на одном узле, и запустится на другом.

К сожалению, у LXC нет приличного инструмента живой миграции, но когда появится, то можно будет настроить и миграцию. Для этого нужно будет создать ещё один общий раздел на GFS2, куда будут помещаться дампы и модифицировать ресурс-скрипт lxc, чтобы он отрабатывал функции migrate_to и migrate_from.
Проект CRIU я смотрел, но добиться работы на CentOS 7 не сумел.

Переносим контейнер OpenVZ в LXC

Создаём новый логический раздел и переносим туда данные с контейнера OpenVZ (выключенного):
# lvcreate -L 2G -n lxc-openvz shared_vg # mkfs.ext4 /dev/shared_vg/lxc-openvz # mount /dev/shared_vg/lxc-openvz /mnt/lxc-openvz # rsync -avh --numeric-ids -e "ssh" openvz:/vz/private// /mnt/lxc-openvz/
Создаём файл конфигурации для нового контейнера, копируя и меняя содержимое lxc-racktables:
# mkdir /var/lib/lxc/lxc-openvz # cp /var/lib/lxc/lxc-racktables/config /var/lib/lxc/lxc-openvz/
В файле конфигурации необходимо изменить поля:
lxc.rootfs = /dev/shared_vg/lxc-openvz lxc.utsname = openvz #lxc.network.hwaddr
При необходимости, настроить ограничения и нужный сетевой мост. Так же в контейнере нужно изменить настройки сети, переписав их на интерфейс eth0 и исправить файл etc/sysconfig/network .

В принципе, после этого контейнер уже можно запускать, но для лучшей совместимости с LXC необходимо содержимое доработать. Как пример, я использовал шаблон для создания контейнера centos (/usr/share/lxc/templates/lxc-centos), а именно содержание функций configure_centos и configure_centos_init с небольшой доработкой. Особое внимание обратите на создание скрипта etc/init/power-status-changed.conf , без него контейнер не сможет корректно завершать работу. Либо inittab контейнера должен содержать правило вида: "p0::powerfail:/sbin/init 0" (зависит от дистрибутива).

/etc/init/power-status-changed.conf

# power-status-changed - shutdown on SIGPWR
#
start on power-status-changed

Exec /sbin/shutdown -h now «SIGPWR received»


Если кому-то лень разбираться самому (а зря), то можете воспользоваться моим (на свой страх и риск). MAC адрес контейнера лучше зафиксировать в файле конфигурации.
У перенесённых контейнеров возможны проблемы с консолью - её нельзя получить средствами lxc-console . Скриптом я эту проблему решаю используя agetty (alternative Linux getty), который входил в переносимые контейнера. И настройками init , который запускает процессы вида:
/sbin/agetty -8 38400 /dev/console /sbin/agetty -8 38400 /dev/tty1 Рецепт и скрипты /etc/init/ были позаимствованы из созданного чистого контейнера и переделаны под agetty .

/etc/init/start-ttys.conf

#
# This service starts the configured number of gettys.
#

# please create a file start-ttys.override and put your changes there.

Env ACTIVE_CONSOLES=/dev/tty
env X_TTY=/dev/tty1
task
script
. /etc/sysconfig/init
for tty in $(echo $ACTIVE_CONSOLES); do
[ "$RUNLEVEL" = «5» -a "$tty" = "$X_TTY" ] && continue
initctl start tty TTY=$tty
done
end script


/etc/init/console.conf

# console - getty
#
# This service maintains a getty on the console from the point the system is
# started until it is shut down again.

Start on stopped rc RUNLEVEL=
stop on runlevel [!2345]
env container

Respawn
#exec /sbin/mingetty --nohangup --noclear /dev/console
exec /sbin/agetty -8 38400 /dev/console


/etc/init/tty.conf

# tty - getty
#
# This service maintains a getty on the specified device.
#
# Do not edit this file directly. If you want to change the behaviour,
# please create a file tty.override and put your changes there.

Stop on runlevel

Respawn
instance $TTY
#exec /sbin/mingetty --nohangup $TTY
exec /sbin/agetty -8 38400 $TTY
usage "tty TTY=/dev/ttyX - where X is console id"

Я пробовал использовать mingetty в перенесённом контейнере с CentOS 6.6, но он отказался работать с ошибкой в логах:
# /sbin/mingetty --nohangup /dev/console console: no controlling tty: Operation not permitted

С LXC можно работать через libvrt , используя драйвер lxc:/// , но этот способ опасен и RedHat грозится удалить его поддержку из дистрибутива.
Для управлением через libvrt в Pacemaker-е существует ресурс-скрипт ocf:heartbeat:VirtualDomain , который может управлять любой VM, в зависимости от драйвера. Возможности включают в себя и живую миграцию для KVM. Думаю, использование Pacemaker-а для управления KVM, будет аналогичным, но мне это не было нужным.

Работа с Docker-ом в кластере

Настройка Pacemaker-а для работы с Docker-ом похожа на настройку LXC, но есть и конструктивные отличия.
Для начала поставим Docker, так как он входит в дистрибутив RHEL/CentOS 7, то проблем не возникнет.
# yum install docker
Научим Docker работать с LVM. Для этого создаём файл /etc/sysconfig/docker-storage-setup с содержимым:
VG=shared_vg-ex Где указываем, в какой группе томов Docker-у создавать свой пул. Тут же можно задать дополнительные параметры (man docker-storage-setup).

Запускаем docker-storage-setup:
# docker-storage-setup Rounding up size to full physical extent 716.00 MiB Logical volume "docker-poolmeta" created. Logical volume "docker-pool" created. WARNING: Converting logical volume shared_vg-ex/docker-pool and shared_vg-ex/docker-poolmeta to pools data and metadata volumes. THIS WILL DESTROY CONTENT OF LOGICAL VOLUME (filesystem etc.) Converted shared_vg-ex/docker-pool to thin pool. Logical volume "docker-pool" changed. # lvs | grep docker-pool docker-pool shared_vg-ex twi-aot--- 17.98g 14.41 2.54
Docker использует разреженный том (thin provisioned), это накладывает ограничение на использование в кластере. Такой том не может быть активным на нескольких узлах одновременно. Настроим LVM так, чтобы тома в группе shared_vg-ex не активировалась автоматически. Для этого необходимо явно указать группы (или тома по отдельности) которые будут активироваться автоматически в файле /etc/lvm/lvm.conf (на всех узлах):
auto_activation_volume_list = [ "shared_vg" ]
Передадим управление этим томом Pacemaker-у:
# pcs resource create lvm-docker-pool LVM volgrpname=shared_vg-ex exclusive=yes # pcs constraint order start clvmd-clone then lvm-docker-pool # pcs constraint colocation add lvm-docker-pool with clvmd-clone Теперь тома группы shared_vg-ex будут активироваться на том узле, где запущен ресурс lvm-docker-pool .

Docker будет использовать для NAT-а контейнеров из внешней сети выделенный IP. Зафиксируем его в конфигурации:
# cat /etc/sysconfig/docker-network DOCKER_NETWORK_OPTIONS="--ip=10.1.2.10 -fixed-cidr=172.17.0.0/16" Отдельный мост настраивать не будем, пусть использует по умолчанию docker0 , только зафиксируем сеть для контейнеров. Я пробовал указывать мне удобную сеть для контейнеров, но наткнулся на какие-то не внятные ошибки. Гугл подсказал, что не я один, по этому удовлетворился просто зафиксировав сеть, которую Docker сам себе выбрал. Docker так же не останавливает мост при своём выключении и не убирает ip-адрес, но так как мост не соединён ни с одним физическим интерфейсом, то это не проблема. В других конфигурациях это надо учитывать.

Чтобы Docker ходил в интернет через прокси-сервер, настроим ему переменные окружения. Для этого создаём директорию и файл /etc/systemd/system/docker.service.d/http-proxy.conf с содержанием:
Environment="http_proxy=http://ip_proxy:port" "https_proxy=http://ip_proxy:port" "NO_PROXY=localhost,127.0.0.0/8,dregistry"

Базовая настройка завершена, будем наполнять кластер соответствующими ресурсами. Так как за Docker будет отвечать набор ресурсов, то удобно их собрать в группу. Все ресурсы группы запускаются на одном узле и запускаются последовательно, согласно порядку в группе. Но нужно учитывать, что при сбое одного из ресурсов группы, вся группа соберётся мигрировать на другой узел. А так же, при выключении какого-либо ресурса группы, все последующие ресурсы тоже выключатся. Первым ресурсом группы будет созданный LVM том:
# pcs resource group add docker lvm-docker-pool
Создаём ресурс - IP адрес, выданный Docker-у:
# pcs resource create dockerIP IPaddr2 --group docker --after lvm-docker-pool ip=10.1.2.10 cidr_netmask=24 nic=br0.12
Кроме LVM тома Docker так же использует файловую систему для хранения своих данных. По этому нужно завести ещё один раздел под управлением Pacemaker-а. Так как эти данные нужны только работающему Dokcer-у, то и ресурс будет обычным.
# lvcreate -L 500M -n docker-db shared_vg # mkfs.xfs /dev/shared_vg/docker-db # pcs resource create fs-docker-db Filesystem fstype=xfs device=/dev/shared_vg/docker-db directory=/var/lib/docker --group docker --after dockerIP
Теперь можно добавлять сам Docker-демон:
# pcs resource create dockerd systemd:docker --group docker --after fs-docker-db
После успешного запуска ресурсов группы, на узле, где поселился Docker смотрим его статус и убеждаемся, что всё хорошо.

docker info:

# docker info Containers: 5 Images: 42 Storage Driver: devicemapper Pool Name: shared_vg--ex-docker--pool Pool Blocksize: 524.3 kB Backing Filesystem: xfs Data file: Metadata file: Data Space Used: 2.781 GB Data Space Total: 19.3 GB Data Space Available: 16.52 GB Metadata Space Used: 852 kB Metadata Space Total: 33.55 MB Metadata Space Available: 32.7 MB Udev Sync Supported: true Library Version: 1.02.93-RHEL7 (2015-01-28) Execution Driver: native-0.2 Kernel Version: 3.10.0-229.7.2.el7.x86_64 Operating System: CentOS Linux 7 (Core) CPUs: 4 Total Memory: 3.703 GiB Name: cluster-2

Можно уже работать с Docker-ом в обычном порядке. Но для полноты картины, заведём и поселим в наш кластер Docker-реестр. Для реестра будем использовать отдельный IP и имя 10.1.2.11 (dregistry), а файловое хранилище образов вынесем в отдельный раздел.
# lvcreate -L 10G -n docker-registry shared_vg # mkfs.ext4 /dev/shared_vg/docker-registry # mkdir /mnt/docker-registry # pcs resource create docker-registry Filesystem fstype=ext4 device=/dev/shared_vg/docker-registry directory=/mnt/docker-registry --group=docker –after=dockerd # pcs resource create registryIP IPaddr2 --group docker --after docker-registry ip=10.1.2.11 cidr_netmask=24 nic=br0.12
Создаём контейнер-реестр на узле, где запущен Docker:
# docker create -p 10.1.2.11:80:5000 -e REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/var/lib/registry -v /mnt/docker-registry:/var/lib/registry -h dregistry --name=dregistry registry:2 Задаём проброс порта в контейнер (10.1.2.11:80 → 5000). Подключаемую директорию /mnt/docker-registry . Имя хоста и имя контейнера.
Вывод docker ps -a покажет созданный контейнер, готовый к запуску.

Отдадим управление им Pacemaker-у. Для начала скачиваем свежий ресурс-скрипт:
# wget -O /usr/lib/ocf/resource.d/heartbeat/docker https://raw.githubusercontent.com/ClusterLabs/resource-agents/master/heartbeat/docker # chmod +x /usr/lib/ocf/resource.d/heartbeat/docker
Важно следить за идентичностью ресурс-скриптов на всех узлах, а то могут быть неожиданности. Ресурс-скрипт «docker» сам умеет создавать требуемые контейнера с заданными параметрами, скачивая их из реестра. По этому можно просто использовать постоянно запущенные Docker-а на узлах кластера с общим реестром и личными хранилищами. А Pacemaker-ом только контролировать отдельные контейнера, но это не так интересно, да и избыточно. Я и с одним то Docker-ом ещё не придумал, что делать.
Итак, передадим управление готовым контейнером Pacemaker-у.
# pcs resource create dregistry docker reuse=true image="docker.io/registry:2" --group docker --after registryIP reuse=true – важный параметр, иначе контейнер будет удалён после остановки. image необходимо указать полные координаты контейнера, включая реестр и тэг. Ресурс-скрипт подхватит готовый контейнер по имени dregistry и запустит.
Пропишем наш локальный реестр в конфигурацию Docker-а на узлах кластера (/etc/sysconfig/docker).
ADD_REGISTRY="--add-registry dregistry" INSECURE_REGISTRY="--insecure-registry dregistry" HTTPS нам не нужен, потому отключаем для локального реестра.

После этого перезапускаем службу Docker-а systemctl restart docker на узле, где он живёт или pcs resource restart dockerd на любом узле кластера. И можем пользоваться возможностями нашего личного реестра по адресу 10.1.2.11 (dregistry).

Теперь на примере Docker-контейнеров я покажу, как работать с шаблонами в Pacemaker-е. К сожалению, возможности утилиты pcs тут сильно ограничены. Шаблоны как таковые она не умеет вообще, а для constraint позволяет создать некоторые объединения, но работать с ними через pcs не удобно. Благо, на помощь приходит возможность править конфигурацию кластера на прямую в xml файле:
# pcs cluster cib > /tmp/cluster.xml # правим, что нужно # pcs cluster cib-push /tmp/cluster.xml

Docker-ресурсы должны отвечать следующим требованиям:

  • находится на одном узле с ресурсами группы docker
  • запускаться после всех ресурсов группы docker
  • контейнера должны быть не зависимы от статуса друг друга
Конечно, этого можно добиться навешав зависимости на каждый контейнер с помощью pcs constraint , но конфигурация получается громоздкой и плохо читаемой.

Для начала создадим три подопытных контейнера, в качестве которых я использовал контейнер с Nginx-ом. Контейнер предварительно скачивается и выкладывается в локальный реестр:
# docker pull nginx:latest # docker push nginx:latest # pcs resource create doc-test3 docker reuse=false image="dregistry/nginx:latest" --disabled # pcs resource create doc-test2 docker reuse=true image="dregistry/nginx:latest" --disabled # pcs resource create doc-test docker reuse=true image="dregistry/nginx:latest" --disabled Ресурсы создаются выключенными, иначе они попробуют запустится и тут как повезёт с узлом.

В свежевыгруженный xml добавляем объединение ресурсов. Определяющее совместное расположение (секция):
Здесь заводится общее объединение (colocation set , id="docker-col" ) с требованием совместного проживания на одном узле ( score="INFINITY" ). Первое объединение ресурсов ( id="docker-col-0" ) со свойствами:

  • одновременный запуск ресурсов ( sequential="false" )
  • не зависимость ресурсов от статуса друг друга ( require-all="false" )
  • роль ресурсов ( role="Started" )
Тэг resource_ref ссылается на существующие ресурсы кластера, которые нужно включить в это объединение. Параметр role="Started" критичен.
И второе объединение ресурсов ( id="docker-col-1" ), куда входят все ресурсы группы docker .
Мне не совсем понятна логика работы параметра role в этой конструкции, но оно должно быть так (проверено экспериментами).

Ordering set , определяющий порядок запуска ресурсов:
Здесь прозрачнее, сначала запускаем ресурсы группы docker , затем скопом и не зависимо все контейнера.

После загрузки изменённой конфигурации, контейнера можно безбоязненно включать/выключать и удалять. Это не повлияет на работу других ресурсов. Но требование совместного расположение заставит переместится на другой узел кластера все связанные ресурсы, в случае переноса какого-либо ресурса. В выводе pcs эти настройки выглядят следующим образом:
# pcs constraint --full | grep -i set Resource Sets: set docker (id:order_doc-0) set doc-test doc-test2 doc-test3 sequential=false require-all=false (id:order_doc-1) (id:order_doc) Resource Sets: set doc-test doc-test2 doc-test3 role=Started sequential=false require-all=false (id:docker-col-0) set docker (id:docker-col-1) setoptions score=INFINITY (id:docker-col)

Работа с шаблонами ресурсов осуществляется сходным образом. Создадим шаблон для LXC контейнера (в секции):

В котором пропишем основные параметры ресурса. И перепишем настройки ресурса на использование этого шаблона:

После обновления конфигурации вывод свойств ресурса стал совсем короток:
# pcs resource show lxc-racktables Resource: lxc-racktables (template=lxc-template) Attributes: container=lxc-racktables config=/var/lib/lxc/lxc-racktables/config
Правда создавать новые ресурсы средствами pcs с использованием шаблонов пока не получится.
Осталось зафиксировать порядок запуска контейнеров LXC аналогичным объединением, как с Docker-ом.

Для управления Pacemaker-ом можно поставить пакет crmsh из репозитория

, Децентрализованные сети


Хочу рассказать вам еще об одном способе балансировки нагрузки. Про Pacemaker и IPaddr (ресурс-агент) и настройке его для Active/Passive кластера сказано уже и так достаточно много, но информации по организации полноценного Active/Active кластера, используя этот модуль я нашел крайне мало. Постараюсь исправить эту ситуацию.


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

  • Отсутсвие внешнего балансировщика - На всех нодах в кластере настраивается один общий виртуальный IP-адрес. Все запросы отправляются на него. Ноды отвечают на запросы на этот адрес случайно и по договоренности между ссобой.
  • Высокая доступность - Если одна нода падает ее обязаности подхватывает другая.
  • Простота настройки - Настройка осуществляется всего в 3-5 команд.

Вводные данные

Давайте посмотрим на картинку в начале статьи, мы увидим следующие устройства:

  • Gateway - IP: 192.168.100.1
  • HostA - IP: 192.168.100.101
  • HostB - IP: 192.168.100.102
  • HostC - IP: 192.168.100.103

Клиенты будут обращаться на внешний адрес нашего шлюза, тот будет перенаправлять все запросы на виртуальный IP 192.168.100.100, который будет настроен на всех трех нодах нашего кластера.

Подготовка

Для начала нам нужно убедиться в том, что все наши ноды могут обращаться друг к другу по single hostname, для надежности лучше сразу добавить адреса нод в /etc/hosts:


192.168.100.101 hostA 192.168.100.102 hostB 192.168.100.103 hostC

Установим все необходимые пакеты:


yum install pcs pacemaker corosync #CentOS, RHEL apt-get install pcs pacemaker corosync #Ubuntu, Debian

При установке pcs создает пользователя hacluster , давайте зададим ему пароль:


echo CHANGEME | passwd --stdin hacluster

pcs cluster auth HostA HostB HostC -u hacluster -p CHANGEME --force

Создаём и запускаем кластер “Cluster” из трех узлов:


pcs cluster setup --force --name Cluster hostA hostB hostC pcs cluster start --all

Смотрим результат:


pcs cluster status

Вывод

Cluster Status: Last updated: Thu Jan 19 12:11:49 2017 Last change: Tue Jan 17 21:19:05 2017 by hacluster via crmd on hostA Stack: corosync Current DC: hostA (version 1.1.14-70404b0) - partition with quorum 3 nodes and 0 resources configured Online: [ hostA hostB hostC ] PCSD Status: hostA: Online hostB: Online hostC: Online


Некоторые шаги были позаимствованы из статьи Lelik13a , спасибо ему за это.


В нашем конкретном случае ни кворум ни stonith нашему кластеру не требуется, так что смело отключаем и то и другое:


pcs property set no-quorum-policy=ignore pcs property set stonith-enabled=false

В дальнейшем, если у вас появятся ресурсы для которых это необходимо, вы можете обратиться к статье Silvar .

Пара слов о MAC-адресах

Прежде чем мы приступим, нам нужно понимать что на всех наших нодах будет настроен одинаковый IP и одинаковый mac-адрес, по запросу на который они будут поочередно давать ответы.


Проблема в том, что каждый коммутатор работает таким образом, что во время работы он составляет свою таблицу коммутации, в которой каждый mac-адрес связывается с определенным физическим портом. Таблица коммутации составляется автоматически, и служит для разгрузки сети от "ненужных" L2-пакетов.


Так вот, если mac-адрес есть в таблице коммутации, то пакеты будут отправляться только в один порт за которым и закреплен этот самый mac-адрес.


К сожалению, нам это не подходит и нам необходимо удостовериться в том, что бы все наши хосты в кластере одновременно "видели" все эти пакеты. В противном случае эта схема работать не будет.


Для начала нам нужно удостовериться в том, что используемый нами mac-адрес является multicast -адресом. То есть находится в диапазоне 01:00:5E:00:00:00 - 01:00:5E:7F:FF:FF . Получив пакет для такого адреса наш коммутатор будет передавать его во все остальные порты, кроме порта источника. Кроме того, некоторые управляемые коммутаторы позволяют настроить и определить несколько портов для конткретного MAC-адреса.


Также вам вероятно придется отключить функцию Dynamic ARP Inspection , если она поддерживается вашим коммутатором, так как она может стать причиной блокировки arp-ответов от ваших хостов.

Настройка IPaddr-ресурса

Вот мы и добрались до самого интересного.


На данный момент существует две версии IPaddr с поддержкой клонирования:

    IPaddr2 (ocf:heartbeat:IPaddr2) - Стандартный ресурс-агент для создания и работы виртуального IP-адреса. Как правило устанавливается вместе со стандартным пакетом resource-agents .

  • IPaddr3 (ocf:percona:IPaddr3) - Улучшенная версия IPaddr2 от Percona.
    В эту версию включены исправления ориентированные на работу именно в режиме clone.
    Требуется отдельная установка.

Для установки IPaadr3 выполните эти команды на каждом хосте:


curl --create-dirs -o /usr/lib/ocf/resource.d/percona/IPaddr3 https://raw.githubusercontent.com/percona/percona-pacemaker-agents/master/agents/IPaddr3 chmod u+x /usr/lib/ocf/resource.d/percona/IPaddr

Создадим ресурс для нашего виртуального IP-адреса:


pcs resource create ClusterIP ocf:percona:IPaddr3 params ip="192.168.100.100" cidr_netmask="24" nic="eth0" clusterip_hash="sourceip-sourceport" op monitor interval="10s"

clusterip_hash - здесь нужно указать желаемый тип распределения запросов.
Всего может быть три варианта:

  • sourceip - распределение только по IP-адресу источника, это гарантирует что все запросы от одного источника всегда будут попадать на один и тот же хост.
  • sourceip-sourceport - распределение по IP-адресу источника и исходящему порту. Каждое новое соединение будет попадать на новый хост. Оптимальный вариант.
  • sourceip-sourceport-destport - распределение по IP-адресу источника исходящему порту и порту назначения. Обеспечивает наилучшее распределение, актуально если у вас работает несколько сервисов на разных портах.

Для IPaddr2 обязательно нужно указать параметр mac=01:00:5E:XX:XX:XX с mac-адресом из multicast-диапазона. IPaddr3 устанавливает его автоматически.


Теперь склонируем наш ресурс:


pcs resource clone ClusterIP meta clone-max=3 clone-node-max=3 globally-unique=true

Это действие создаст следующее правило в iptables:



Как вы можете заметить, здесь используется модуль CLUSTERIP .


Работает он следующим образом:


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


Подробнее об этом написанно в этой