Продолжу, первая часть тут.
Кластер
Итак, приступим к настройке софта, управляющего кластером.
У нас это будет Pacemaker + Corosync в качестве транспортного бэкенда для общения между нодами.
Corosync для большей надёжности поддерживает работу через несколько колец обмена данными.
Причём, три и более уже не тянет, хотя в доках про это нигде особо не указано, только ругается при запуске если указать более двух в конфиге.
Кольца названы так потому что общение между нодами идёт по кольцу — ноды передают данные друг другу последовательно, заодно проверяя живучесть друг друга. Работает оно по UDP, может как по мультикасту, так и по уникасту. У нас будет последний, почему — будет понятно ниже.
Кольца
Для связи между нодами я решил применить несколько параноидальную схему — внешнее кольцо через коммутаторы (тут стандартный Bonding/Etherchannel на два свича) + внутреннее кольцо, соединяющее ноды напрямую (напомню, что их три — два хранилища + свидетель).
Схема следующая:
Зелёные связи — внутреннее кольцо, чёрные — внешнее. В данной топологии ноды должны будут сохранить связность даже при полном отказе внешних устройств (шторм положил коммутаторы, админ (то бишь я) своими кривыми руками что-то напортачил… маловероятно, но всё может быть).
Но тут случился затык — как организовать свободный обмен данными между нодами по внутреннему кольцу? А ведь это именно топология кольцо, не очень характерная для Ethernet. Связность между любыми двумя нодами должна сохраняться при разрыве любого из трёх линков, образующих кольцо.
Были рассмотрены следующие варианты:
- Обычный Ethernet-мост + STP для обрыва петель. При тюнинге таймеров STP можно добиться сходимости в 5-6 секунд.
Для нас это вечность, не пойдёт.
- Относительно недавно добавленный в ядро протокол HSR. Если кратко, то он был задуман для отказоустойчивой связи в топологиях Ring и Mesh с мгновенной сходимостью. Два интерфейса объединяются в подобие моста и пакеты, с дополнительными заголовками, уходят в оба интерфейса одновременно. Приходящие пакеты, которые предназначены не нам — переправляем дальше по кольцу. Для отсекания петель используются идентификаторы из заголовка пакета (т.е. то, что уже пересылали — отбрасываем, в таком духе).
Выглядит красиво и вкусно, но реализация хромает: даже в последнем стабильном ядре 3.18 при удалении HSR-устройства это самое ядро падает (в GIT уже поправили).
Но, даже если не удалять — работает странно, я так и не смог прогнать iperf по кольцу для замера скорости (которая должна быть около 50% от номинальной) — траффик не шёл, хотя пинги бегали, глубже разбираться не стал.
В общем, тоже отметаем.
- OSPF. Учитывая, что для Corosync не важна L2-связность, это оказался самый подхдящий вариант. Время сходимости около 100мсек, что нас вполне устраивает.
Quagga
Для реализации OSPF используем Кваггу. Есть ещё проект BIRD от наших соседей из Чехии, но мне как-то привычнее Quagga. Хотя BIRD, по некоторым тестам, работает быстрее и занимает меньше памяти, но в наших реалиях это, в общем, по барабану.
Каждый линк между хостами будет отдельной /24 сетью. Да, можно использовать /30 или даже /31, но эти сети не будут никуда маршрутизоваться, так что экономить смысла особого не было.
На каждом хосте создадим dummy-интерфейс c IP-адресом /32 для анонса соседям, Corosync будет общаться именно через них. Можно было этот адрес повесить на Loopback, но отдельный интерфейс для этих целей мне показался более подходящим.
Примерные куски /etc/network/interfaces:
Storage1
# To Storage-2
auto int1
iface int1 inet static
address 192.168.160.74
netmask 255.255.255.0
# To Witness
auto ext2
iface ext2 inet static
address 192.168.161.74
netmask 255.255.255.0
# Dummy loopback
auto dummy0
iface dummy0 inet static
address 192.168.163.74
netmask 255.255.255.255
Storage2
# To Storage-1
auto int1
iface int1 inet static
address 192.168.160.75
netmask 255.255.255.0
# To Witness
auto ext2
iface ext2 inet static
address 192.168.162.75
netmask 255.255.255.0
# Dummy loopback
auto dummy0
iface dummy0 inet static
address 192.168.163.75
netmask 255.255.255.255
Witness
Именование сетевых интерфейсов (intN, extN) тут по принципу встроенный это или внешний адаптер + порядковый номер порта на нём, мне так удобнее.# To Storage-1
auto int2
iface int2 inet static
address 192.168.161.76
netmask 255.255.255.0
# To Storage-2
auto ext2
iface ext2 inet static
address 192.168.162.76
netmask 255.255.255.0
# Dummy loopback
auto dummy0
iface dummy0 inet static
address 192.168.163.76
netmask 255.255.255.255
Далее настраиваем OSPF.
/etc/quagga/ospfd.conf:
Storage1
hostname storage1
interface int1
ip ospf dead-interval minimal hello-multiplier 10
ip ospf retransmit-interval 3
interface ext2
ip ospf dead-interval minimal hello-multiplier 10
ip ospf retransmit-interval 3
router ospf
log-adjacency-changes
network 192.168.160.0/24 area 0
network 192.168.161.0/24 area 0
network 192.168.163.74/32 area 0
passive-interface dummy0
timers throttle spf 10 10 100
Storage2
hostname storage2
interface int1
ip ospf dead-interval minimal hello-multiplier 10
ip ospf retransmit-interval 3
interface ext2
ip ospf dead-interval minimal hello-multiplier 10
ip ospf retransmit-interval 3
router ospf
log-adjacency-changes
network 192.168.160.0/24 area 0
network 192.168.162.0/24 area 0
network 192.168.163.75/32 area 0
passive-interface dummy0
timers throttle spf 10 10 100
Witness
hostname witness
interface int2
ip ospf dead-interval minimal hello-multiplier 10
ip ospf retransmit-interval 3
interface ext2
ip ospf dead-interval minimal hello-multiplier 10
ip ospf retransmit-interval 3
router ospf
log-adjacency-changes
network 192.168.161.0/24 area 0
network 192.168.162.0/24 area 0
network 192.168.163.76/32 area 0
passive-interface dummy0
timers throttle spf 10 10 100
Включаем на хостах net.ipv4.ip_forward, запускаем кваггу, пинг с интервалом в 0.01с, и рвём кольцо:
root@witness:/# ping -i 0.01 -f 192.168.163.74
...
root@storage1:/# ip link set ext2 down
root@witness:/#
--- 192.168.163.74 ping statistics ---
2212 packets transmitted, 2202 received, 0% packet loss, time 26531ms
rtt min/avg/max/mdev = 0.067/0.126/0.246/0.045 ms, ipg/ewma 11.999/0.183 ms
Итого, потеряно 10 пакетов, это около 100мсек — OSPF сменил маршруты очень быстро.
Corosync
Теперь, когда сетевая подсистема готова, будем настраивать Corosync на её эксплуатацию.
Конфиг на всех хостах должен быть почти идентичен, меняется только адрес dummy0 интерфейса во внутреннем кольце:
/etc/corosync/corosync.conf
compatibility: none
totem {
version: 2
# Произвольное имя
cluster_name: storage
# Аутентификация через файл authkey, который мы раскидали по всем нодам в прошлой части. Лишней не будет.
secauth: on
# Включаем дополнительный контроль живости нод
heartbeat_failures_allowed: 3
threads: 6
# Режим отказоустойчивых колец активный - юзать сразу оба кольца. Об особенностях читать в доках.
rrp_mode: active
# Не использовать мультикаст - для нас это важно, так как городить мультикаст роутинг через OSPF нет никакого желания.
transport: udpu
# Внешнее кольцо, перечисляем адреса нод
interface {
member {
memberaddr: 10.1.195.74
}
member {
memberaddr: 10.1.195.75
}
member {
memberaddr: 10.1.195.76
}
ringnumber: 0
# Сеть адаптера, к которому будем привязываться
bindnetaddr: 10.1.195.0
# Порт для обмена сообщениями. Несмотря на название используется и в уникасте. Также резервируется (mcastport-1) для приёма.
mcastport: 6405
}
# Внутреннее кольцо
interface {
member {
memberaddr: 192.168.163.74
}
member {
memberaddr: 192.168.163.75
}
member {
memberaddr: 192.168.163.76
}
ringnumber: 1
# Этот адрес выставить соответственно адресу dummy0
bindnetaddr: 192.168.163.76
mcastport: 5405
}
}
# Дальше всё стандартно
amf {
mode: disabled
}
service {
ver: 1
name: pacemaker
}
aisexec {
user: root
group: root
}
logging {
syslog_priority: warning
fileline: off
to_stderr: yes
to_logfile: no
to_syslog: yes
syslog_facility: daemon
debug: off
timestamp: on
logger_subsys {
subsys: AMF
debug: off
tags: enter|leave|trace1|trace2|trace3|trace4|trace6
}
}
После этого запускаем Corosync и смотрим статус колец на серверах и список нод, которые объединяет Corosync:
root@storage1:/# corosync-cfgtool -s
Printing ring status.
Local node ID 1254293770
RING ID 0
id = 10.1.195.74
status = ring 0 active with no faults
RING ID 1
id = 192.168.163.74
status = ring 1 active with no faults
root@storage1:/# corosync-objctl | grep member
totem.interface.member.memberaddr=10.1.195.74
totem.interface.member.memberaddr=10.1.195.75
totem.interface.member.memberaddr=10.1.195.76
totem.interface.member.memberaddr=192.168.163.74
totem.interface.member.memberaddr=192.168.163.75
totem.interface.member.memberaddr=192.168.163.76
runtime.totem.pg.mrp.srp.members.1254293770.ip=r(0) ip(10.1.195.74) r(1) ip(192.168.163.74)
runtime.totem.pg.mrp.srp.members.1254293770.join_count=1
runtime.totem.pg.mrp.srp.members.1254293770.status=joined
runtime.totem.pg.mrp.srp.members.1271070986.ip=r(0) ip(10.1.195.75) r(1) ip(192.168.163.75)
runtime.totem.pg.mrp.srp.members.1271070986.join_count=2
runtime.totem.pg.mrp.srp.members.1271070986.status=joined
runtime.totem.pg.mrp.srp.members.1287848202.ip=r(0) ip(10.1.195.76) r(1) ip(192.168.163.76)
runtime.totem.pg.mrp.srp.members.1287848202.join_count=1
runtime.totem.pg.mrp.srp.members.1287848202.status=joined
Ага, всё работает.
Pacemaker
Теперь, когда бэкенд кластера в рабочем состоянии — можно приступить к настройке.
На каждой ноде запускаем Pacemaker, и с любой из них смотрим статус кластера:
root@storage1:/# crm status
============
Last updated: Tue Mar 24 09:39:28 2015
Last change: Mon Mar 23 11:40:13 2015 via crmd on witness
Stack: openais
Current DC: witness - partition with quorum
Version: 1.1.7-ee0730e13d124c3d58f00016c3376a1de5323cff
3 Nodes configured, 3 expected votes
0 Resources configured.
============
Online: [ storage1 storage2 witness ]
Все ноды видны, можно приступать к настройке.
Запускаем crm configure edit, откроется редактор по умолчанию (nano) и туда заносим такую вот ересь:
Конфиг кластера
node storage1
node storage2
node witness
# Описываем STONITH (Shoot The Other Node In The Head) ресурсы.
# В данном случае, если нода выпадет из кластера, остальные две посовещаются и убьют её через IPMI послав команду RESET
primitive ipmi_storage1 stonith:external/ipmi params hostname="storage1" ipaddr="10.1.1.74" userid="stonith" passwd="xxx" interface="lanplus" pcmk_host_check="static-list" pcmk_host_list="storage1"
primitive ipmi_storage2 stonith:external/ipmi params hostname="storage2" ipaddr="10.1.1.75" userid="stonith" passwd="xxx" interface="lanplus" pcmk_host_check="static-list" pcmk_host_list="storage2"
# Ресурс для управления состояниями ALUA, параметры выставить согласно своему /etc/scst.conf
primitive p_scst ocf:esos:scst params alua="true" device_group="default" local_tgt_grp="local" remote_tgt_grp="remote" m_alua_state="active" s_alua_state="nonoptimized" op monitor interval="10" role="Master" op monitor interval="20" role="Slave" op start interval="0" timeout="120" op stop interval="0" timeout="60"
# Описываем режимы Master-Slave для вышеуказанного ресурса
ms ms_scst p_scst meta master-max="1" master-node-max="1" clone-max="2" clone-node-max="1" notify="true" interleave="true" target-role="Master"
# Предпочитаем ноду storage1 для Master-режима
location prefer_ms_scst ms_scst inf: #uname eq storage1
# Не запускаем ресурс SCST на ноде witness
location dont_run ms_scst -inf: #uname eq witness
# Запрещаем ресурсам STONITH висеть на тех же нодах, которые они призваны убивать. Cамоубийство - это не наш метод!
location loc_ipmi_on_storage1 ipmi_storage1 -inf: #uname eq storage1
location loc_ipmi_on_storage2 ipmi_storage2 -inf: #uname eq storage2
property $id="cib-bootstrap-options" dc-version="1.1.7-ee0730e13d124c3d58f00016c3376a1de5323cff" cluster-infrastructure="openais" expected-quorum-votes="3" stonith-enabled="true" last-lrm-refresh="1427100013"
Сохраняем, применяем (commit).
Для STONITH в IPMI серверов нужно создать юзера с правами Administrator, иначе ресурс будет отказываться к нему подключаться. В принципе, хватило бы и Operator, но ковырять код ресурса желания не было.
Смотрим статус кластера:
root@storage1:/# crm status
============
Last updated: Wed Mar 25 15:48:29 2015
Last change: Mon Mar 23 11:40:13 2015 via crmd on witness
Stack: openais
Current DC: witness - partition with quorum
Version: 1.1.7-ee0730e13d124c3d58f00016c3376a1de5323cff
3 Nodes configured, 3 expected votes
4 Resources configured.
============
Online: [ storage1 storage2 witness ]
Master/Slave Set: ms_scst [p_scst]
Masters: [ storage1 ]
Slaves: [ storage2 ]
ipmi_storage1 (stonith:external/ipmi): Started witness
ipmi_storage2 (stonith:external/ipmi): Started storage1
Ну, вроде бы всё красиво. В принципе, уже можно подключаться с инициаторов.
Для верности мы проверим как работает STONITH:
Отключаемся от коммутаторов:
root@storage2:/# ip link set bond_hb_ext down
... мы всё еще живы.
Рвём внутреннее кольцо в одом месте
root@storage2:/# ip link set int1 down
... и всё равно кластер ещё держится.
Рвём последнюю ниточку:
root@storage2:/# ip link set ext2 down
... и через пару секунд кластер коллегиальным решением нас грохнул :) Сервер ушёл в ребут.
Небольшое замечание по работе с Master-Slave ресурсами: в Pacemaker нет команды, которая принудительно поменяет ноды, на которых в данный момент ресурс работает Master-ом и Slave-ом, местами. Можно только командой demote перевести ресурс в Slave на обеих нодах.
Решения два:
1) Редактируем конфигурацию кластера и меняем предпочитаемую ноду для Master-режима на другую, коммитим и через некоторое небольшое время кластер сам отработает по перемещению ресурса.
2) В нашем случае, так как рабочий ресурс, по сути, только один, можно просто потушить Pacemaker на Master-ноде :) Это сигнализирует второй ноде перейти в Master режим. После этого перезагрузить бывшую мастер-ноду для того чтобы владение массивами ушло к другой ноде.
В случае плановой остановки Pacemaker и Corosync STONITH отрабатывать не будет.
Финальные штрихи
- Установить на все сервера Watchdog-демон, работающий с IPMI через /dev/watchdog на тот случай, если сервер зависнет, а STONITH его по каким-то причинам убить не сможет.
/etc/watchdog.conf:
watchdog-device = /dev/watchdog admin = root interval = 1 realtime = yes priority = 1
- Выставить в /etc/sysctl.conf параметры:
Это нужно для того, чтобы ядро в любой непонятной (а OOPS и всякие NMI это скверно) ситуации резетило сервер и давало второй ноде полноценно взяться за дело. Если ядро будет ещё более или менее живое, то этот функционал должен отработать быстрее чем Watchdog и STONITH.kernel.panic = 1 kernel.panic_on_io_nmi = 1 kernel.panic_on_oops = 1 kernel.panic_on_unrecovered_nmi = 1 kernel.unknown_nmi_panic = 1
- Настроить watchquagga в /etc/quagga/debian.conf на перезапуск в случае падения каких-либо демонов Quagga:
watchquagga_enable=yes watchquagga_options=(--daemon --unresponsive-restart -i 5 -t 5 -T 5 --restart-all '/etc/init.d/quagga restart')
- Настроить NetConsole для прямого сбора логов ядра с серверов-хранилищ на Witness-ноде на случай каких-либо проблем.
Добавить в /etc/fstab:
Плюс небольшой скрипт для настройки:none /sys/kernel/config configfs defaults 0 0
netconsole.pl#!/usr/bin/perl -w use strict; use warnings; my $dir = '/sys/kernel/config/netconsole'; my %tgts = ( 'tgt1' => { 'dev_name' => 'ext2', 'local_ip' => '192.168.161.74', 'remote_ip' => '192.168.161.76', 'remote_mac' => '00:25:90:77:b8:8b', 'remote_port' => '6666' } ); foreach my $tgt (sort keys %tgts) { my $t = $tgts{$tgt}; my $tgtdir = $dir."/".$tgt; mkdir($tgtdir); foreach my $k (sort keys $t) { system("echo '".$t->{$k}."' > ".$tgtdir."/".$k); } system("echo 1 > ".$tgtdir."/enabled"); }
ESXi
После активации кластера хранилищ на инициаторах уже должны появиться наши LUNы:
Здесь мы видим (один из портов FC):
- 2 устройства, 4 путя до каждого (2 к основному хранилищу, 2 к резервному)
- Hardware Acceleration = Supported означает что хранилищами поддерживаются VAAI примитивы (SCSI-команды ATS, XCOPY, WRITE SAME), которые позволяют оффлоадить часть операций с хоста на хранилище (блокировка, клонирование, забивание нулями)
- SSD: позволит хосту использовать эти LUNы для Host Cache и прочих служб, которые хотят SSD
Для полноценного использования нескольких путей до хранилищ нам нужно две вещи:
- Установить режим Round Robin
- Настроить его на смену путей каждый 1 IO. По-умолчанию он меняет путь каждую 1000 операций ввода-вывода, что не совсем оптимально, хотя и несколько меньше напрягает CPU хоста. Есть отличная статья от EMC, где очень подробно исследуется влияние этого параметра на производительность: тыц
И если первый пункт можно сделать из vSphere Client, то второй придётся делать из консоли. Для этого активируем SSH на хостах, логинимся на каждый, и вводим:
Выставляем режим Round Robin (сразу, чтобы не ковырять GUI):
# for i in `ls /vmfs/devices/disks/ | grep "eui" | grep -v ":"`; do esxcli storage nmp device set --psp=VMW_PSP_RR --device=$i; done
Меняем количество IOPS до смены пути:
# for i in `ls /vmfs/devices/disks/ | grep "eui" | grep -v ":"`; do esxcli storage nmp psp roundrobin deviceconfig set --type=iops --iops=1 --device=$i; done
Проверяем:
# for i in `ls /vmfs/devices/disks/ | grep "eui" | grep -v ":"`; do esxcli storage nmp psp roundrobin deviceconfig get --device=$i | grep IOOperation; done
IOOperation Limit: 1
IOOperation Limit: 1
Отлично. Теперь смотрим на результаты наших манипуляций:
Это вид на хранилища через один из портов FC адаптера, через второй всё точно так же.
Отлично, имеем 2 активных и 2 резервных пути до каждого из LUNов.
Теперь создадим на каждом LUNе VMFS, положим на них по 1 виртуальной машине с Debian (диски — Thick Provision Eager Zeroed, чтобы ESXi не мухлевал со скоростью чтения неиспользованных блоков) и проведём тестирование скорости работы и процесса переключения на резервное хранилище.
На каждой ВМ установим fio и создадим файлик read.fio с параметрами теста:
[test]
blocksize=512
filename=/dev/sda
size=128G
rw=randread
direct=1
buffered=0
ioengine=libaio
iodepth=64
То есть будем делать случайное чтение блоками по 512 байт с глубиной очереди 64 пока не прочитаем 128Гбайт (такой диск у ВМ).
Смотрим:
Результаты fio при тестировании с одной ВМ:
fio random
test: (g=0): rw=randread, bs=512-512/512-512, ioengine=libaio, iodepth=64
2.0.8
Starting 1 process
Jobs: 1 (f=1): [r] [100.0% done] [77563K/0K /s] [155K/0 iops] [eta 00m:00s]
test: (groupid=0, jobs=1): err= 0: pid=3100
read : io=131072MB, bw=75026KB/s, iops=150052 , runt=1788945msec
slat (usec): min=0 , max=554 , avg= 2.92, stdev= 1.94
clat (usec): min=127 , max=1354.3K, avg=420.90, stdev=1247.77
lat (usec): min=130 , max=1354.3K, avg=424.51, stdev=1247.77
clat percentiles (usec):
| 1.00th=[ 350], 5.00th=[ 378], 10.00th=[ 386], 20.00th=[ 398],
| 30.00th=[ 406], 40.00th=[ 414], 50.00th=[ 418], 60.00th=[ 426],
| 70.00th=[ 430], 80.00th=[ 438], 90.00th=[ 450], 95.00th=[ 462],
| 99.00th=[ 494], 99.50th=[ 516], 99.90th=[ 636], 99.95th=[ 732],
| 99.99th=[ 3696]
bw (KB/s) : min= 606, max=77976, per=100.00%, avg=75175.70, stdev=3104.46
lat (usec) : 250=0.02%, 500=99.19%, 750=0.75%, 1000=0.03%
lat (msec) : 2=0.01%, 4=0.01%, 10=0.01%, 20=0.01%, 250=0.01%
lat (msec) : 500=0.01%, 750=0.01%, 1000=0.01%, 2000=0.01%
cpu : usr=62.25%, sys=37.18%, ctx=58816, majf=0, minf=14
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued : total=r=268435456/w=0/d=0, short=r=0/w=0/d=0
Run status group 0 (all jobs):
READ: io=131072MB, aggrb=75026KB/s, minb=75026KB/s, maxb=75026KB/s, mint=1788945msec, maxt=1788945msec
Disk stats (read/write):
sda: ios=268419759/40, merge=0/2, ticks=62791530/0, in_queue=62785360, util=100.00%
fio sequental
test: (g=0): rw=read, bs=1M-1M/1M-1M, ioengine=libaio, iodepth=64
2.0.8
Starting 1 process
Jobs: 1 (f=1): [R] [100.0% done] [1572M/0K /s] [1572 /0 iops] [eta 00m:00s]
test: (groupid=0, jobs=1): err= 0: pid=3280
read : io=131072MB, bw=1378.6MB/s, iops=1378 , runt= 95078msec
slat (usec): min=36 , max=2945 , avg=80.13, stdev=16.73
clat (msec): min=11 , max=1495 , avg=46.33, stdev=29.87
lat (msec): min=11 , max=1495 , avg=46.42, stdev=29.87
clat percentiles (msec):
| 1.00th=[ 35], 5.00th=[ 38], 10.00th=[ 39], 20.00th=[ 40],
| 30.00th=[ 42], 40.00th=[ 43], 50.00th=[ 43], 60.00th=[ 44],
| 70.00th=[ 52], 80.00th=[ 56], 90.00th=[ 57], 95.00th=[ 57],
| 99.00th=[ 59], 99.50th=[ 62], 99.90th=[ 70], 99.95th=[ 529],
| 99.99th=[ 1483]
bw (MB/s) : min= 69, max= 1628, per=100.00%, avg=1420.43, stdev=219.51
lat (msec) : 20=0.04%, 50=68.57%, 100=31.33%, 750=0.02%, 2000=0.05%
cpu : usr=0.57%, sys=13.40%, ctx=16171, majf=0, minf=550
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued : total=r=131072/w=0/d=0, short=r=0/w=0/d=0
Run status group 0 (all jobs):
READ: io=131072MB, aggrb=1378.6MB/s, minb=1378.6MB/s, maxb=1378.6MB/s, mint=95078msec, maxt=95078msec
Disk stats (read/write):
sda: ios=261725/14, merge=0/1, ticks=11798940/350, in_queue=11800870, util=99.95%
Основные моменты:
- >300k IOPS суммарно. Довольно неплохо, учитывая что при тестировании бэкенд был зашифрован
- Линейная скорость плавает в пределах 1300-1580 Мбайт/с (что близко к пределу 2х8Gbit FC), тут уже упирается в скорость шифрования
- Random Latency у 99.9% запросов не превышает 0.7мс
- Если тест на одной из ВМ остановить, то на оставшейся будут все те же 150к IOPS. Это, похоже, предел для двухпортовой FC-карты на ESXi. Хотя это несколько странно, нужно будет заняться тюнингом
- При тесте нагрузка на CPU хранилища около 60%, так что запас ещё есть
А может бахнем? Обязательно бахнем
Теперь мы проверим как система отреагирует на отключение Master-ноды.
Плановое: останавливаем на Master-ноде Pacemaker. Практически мгновенно кластер переключает вторую ноду в Master-режим:
[285401.041046] scst: Changed ALUA state of default/local into active
[285401.086053] scst: Changed ALUA state of default/remote into nonoptimized
А на первой последовательно отключает SCST и выгружает все связанные с ним модули из ядра:
dmesg
[286491.713124] scst: Changed ALUA state of default/local into nonoptimized
[286491.757573] scst: Changed ALUA state of default/remote into active
[286491.794939] qla2x00t: Unloading QLogic Fibre Channel HBA Driver target mode addon driver
[286491.795022] qla2x00t(0): session for loop_id 132 deleted
[286491.795061] qla2x00t(0): session for loop_id 131 deleted
[286491.795096] qla2x00t(0): session for loop_id 130 deleted
[286491.795172] qla2xxx 0000:02:00.0: Performing ISP abort - ha= ffff880854e28550.
[286492.428672] qla2xxx 0000:02:00.0: LIP reset occured (f7f7).
[286492.488757] qla2xxx 0000:02:00.0: LOOP UP detected (8 Gbps).
[286493.810720] scst: Waiting for 4 active commands to complete... This might take few minutes for disks or few hours for tapes, if you use long executed commands, like REWIND or FORMAT. In case, if you have a hung user space device (i.e. made using scst_user module) not responding to any commands, if might take virtually forever until the corresponding user space program recovers and starts responding or gets killed.
[286493.810924] scst: All active commands completed
[286493.810997] scst: Target 21:00:00:24:ff:54:09:80 for template qla2x00t unregistered successfully
[286493.811072] qla2x00t(1): session for loop_id 0 deleted
[286493.811111] qla2x00t(1): session for loop_id 1 deleted
[286493.811146] qla2x00t(1): session for loop_id 2 deleted
[286493.811182] qla2x00t(1): Unable to send command to SCST, sending BUSY status
[286493.811226] qla2x00t(1): Unable to send command to SCST, sending BUSY status
[286493.811266] qla2x00t(1): Unable to send command to SCST, sending BUSY status
[286493.811305] qla2x00t(1): Unable to send command to SCST, sending BUSY status
[286493.811345] qla2x00t(1): Unable to send command to SCST, sending BUSY status
[286493.811384] qla2x00t(1): Unable to send command to SCST, sending BUSY status
[286493.811424] qla2x00t(1): Unable to send command to SCST, sending BUSY status
[286493.811463] qla2x00t(1): Unable to send command to SCST, sending BUSY status
[286493.811502] qla2x00t(1): Unable to send command to SCST, sending BUSY status
[286493.811541] qla2x00t(1): Unable to send command to SCST, sending BUSY status
[286493.811672] qla2xxx 0000:02:00.1: Performing ISP abort - ha= ffff880854e08550.
[286494.441653] qla2xxx 0000:02:00.1: LIP reset occured (f7f7).
[286494.481727] qla2xxx 0000:02:00.1: LOOP UP detected (8 Gbps).
[286495.833746] scst: Target 21:00:00:24:ff:54:09:81 for template qla2x00t unregistered successfully
[286495.833828] qla2x00t(2): session for loop_id 132 deleted
[286495.833866] qla2x00t(2): session for loop_id 131 deleted
[286495.833902] qla2x00t(2): session for loop_id 130 deleted
[286495.833991] qla2xxx 0000:03:00.0: Performing ISP abort - ha= ffff88084f310550.
[286496.474662] qla2xxx 0000:03:00.0: LIP reset occured (f7f7).
[286496.534750] qla2xxx 0000:03:00.0: LOOP UP detected (8 Gbps).
[286497.856734] scst: Target 21:00:00:24:ff:54:09:32 for template qla2x00t unregistered successfully
[286497.856815] qla2x00t(3): session for loop_id 0 deleted
[286497.856852] qla2x00t(3): session for loop_id 1 deleted
[286497.856888] qla2x00t(3): session for loop_id 130 deleted
[286497.856926] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.856970] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.857009] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.857048] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.857087] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.857127] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.857166] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.857205] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.857244] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.857284] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.857323] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.857362] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.857401] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.857440] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.857480] qla2x00t(3): Unable to send command to SCST, sending BUSY status
[286497.857594] qla2xxx 0000:03:00.1: Performing ISP abort - ha= ffff88084dfc0550.
[286498.487642] qla2xxx 0000:03:00.1: LIP reset occured (f7f7).
[286498.547731] qla2xxx 0000:03:00.1: LOOP UP detected (8 Gbps).
[286499.889733] scst: Target 21:00:00:24:ff:54:09:33 for template qla2x00t unregistered successfully
[286499.889799] scst: Target template qla2x00t unregistered successfully
[286499.890642] dev_vdisk: Detached virtual device SSD-RAID6-1 ("/dev/disk/by-id/scsi-3600605b008b4be401c91ac4abce21c9b")
[286499.890718] scst: Detached from virtual device SSD-RAID6-1 (id 1)
[286499.890756] dev_vdisk: Virtual device SSD-RAID6-1 unregistered
[286499.890798] dev_vdisk: Detached virtual device SSD-RAID6-2 ("/dev/disk/by-id/scsi-3600605b008b4be401c91ac53bd668eda")
[286499.890869] scst: Detached from virtual device SSD-RAID6-2 (id 2)
[286499.890906] dev_vdisk: Virtual device SSD-RAID6-2 unregistered
[286499.890945] scst: Device handler "vdisk_nullio" unloaded
[286499.890981] scst: Device handler "vdisk_blockio" unloaded
[286499.891017] scst: Device handler "vdisk_fileio" unloaded
[286499.891052] scst: Device handler "vcdrom" unloaded
[286499.891754] scst: Task management thread PID 5162 finished
[286499.891801] scst: Management thread PID 5163 finished
[286499.891847] scst: Init thread PID 5161 finished
[286499.899867] scst: Detached from scsi0, channel 0, id 20, lun 0, type 13
[286499.899911] scst: Detached from scsi0, channel 0, id 36, lun 0, type 13
[286499.899951] scst: Detached from scsi0, channel 0, id 37, lun 0, type 13
[286499.899992] scst: Detached from scsi0, channel 0, id 38, lun 0, type 13
[286499.900031] scst: Detached from scsi0, channel 0, id 39, lun 0, type 13
[286499.900071] scst: Detached from scsi0, channel 0, id 40, lun 0, type 13
[286499.900110] scst: Detached from scsi0, channel 0, id 41, lun 0, type 13
[286499.900150] scst: Detached from scsi0, channel 0, id 42, lun 0, type 13
[286499.900189] scst: Detached from scsi0, channel 0, id 59, lun 0, type 13
[286499.900228] scst: Detached from scsi0, channel 0, id 60, lun 0, type 13
[286499.900268] scst: Detached from scsi0, channel 2, id 0, lun 0, type 0
[286499.900307] scst: Detached from scsi0, channel 2, id 1, lun 0, type 0
[286499.900346] scst: Detached from scsi1, channel 0, id 0, lun 0, type 0
[286499.900385] scst: Detached from scsi2, channel 0, id 0, lun 0, type 0
[286499.900595] scst: Exiting SCST sysfs hierarchy...
[286502.914203] scst: User interface thread PID 5153 finished
[286502.914248] scst: Exiting SCST sysfs hierarchy done
[286502.914458] scst: SCST unloaded
На виртуальных машинах IO замирает примерно секунд на 10-15, похоже ESXi тыкается какое-то время по старым путям и только после некоего таймаута переключается на новые. IOPS на каждой ВМ падает со 120к до 22к — такова цена I/O Shipping.
Далее отключаем или перезагружаем первый сервер — Syncro на втором перехватывает ведущую роль и I/O возвращается к нормальным значениям.
Если запустить Pacemaker обратно, то кластер переключится на эту ноду обратно, ибо так писано в конфиге :)
Внеплановое: Тут мы можем, например, убить через kill -9 процесс corosync и кластер грохнет нас через STONITH. Либо просто выключить ноду по питанию. Результат один и, в общем, не отличается от планового, за исключением того, что не будет IO Shipping: второй контроллер сразу схватит массивы и скорость не упадёт до 22к IOPS.
Эпилог
За кадром остались ещё скрипты для self-monitoring ноды, тут большое поле для деятельности: проверка живости контроллера через всякие StorCLI, проверка отвечают ли массивы на I/O запросы (ioping) и тому подобное. В случае обнаружения неисправностей ноде следует совершить харакири.
Таким вот незамысловатым способом можно сделать достаточно надёжное и быстрое хранилище из подручных материалов.
Вопросы, предложения и критика приветствуются.
Всем бобра!
phozzy
Зачем такие сложности? Можно же было Ceph развернуть, и не городить велосипеды.
blind_oracle Автор
1. Ceph это распределённная система, для неё нужно, по хорошему, много нод.
2. Ceph, насколько я понимаю, не поддерживает RAID5/6-like распределение блоков, только тупая репликация, что снижает ёмкость системы как минимум вдвое. Что при цене на SSD несколько расточительно (хотя тут можно использовать SATA диски, что несколько дешевле).
3. Общая сложность реализации на Ceph не уверен что будет ниже.
phozzy
Минимально 3 ноды, хотя, на свой страх и риск можно и уменьшить эти требования.
По умолчанию только репликация, но можно и рейдом побаловаться.
За счет репликации распараллеливаются операции, что может позволить достичь результатов близких к ssd без использования ssd, что снижает стоимость системы. Хотя, можно использовать ssd для томов журнала.
Исходя из своего опыта могу сказать, что ничего особо сложного нет. Единственное, конечно, что вы вмварь используете, а у него нет своего драйвера для rbd, придется городить адаптер в iscsi или fc.
blind_oracle Автор
Спасибо, не знал что они уже реализовали Erasure Code, вкусная штука.
И, я смотрю, гибко настраивается толерантность к отказу нод, можно выставить нужное количество (а не только RAID5/6 — 1 или 2).
Там вроде бы и Cached Tiering есть — docs.ceph.com/docs/master/rados/operations/cache-tiering/
Это даже лучше чем просто журнал класть на SSD.
Да, но этот как раз достаточно простой момент — экспортировать RBD наружу не сложно.
Может в следующей вмвари и поддержку его запилят, всякий OpenStack это нынче в тренде.
В следующем проекте попробую Ceph применить, давно руки чесались. А при наличии кеширования и Erasure Code последние сомнения растаяли :)
phozzy
Можно у teraflops поинтересоваться относительно рекомендаций по кешу и журналам. Они вроде как уже давно используют в производстве.
blind_oracle Автор
Погонял наколеночный кластер Ceph из трёх ВМ + 1 клиент, очень интересно.
Использовал SSD CacheTier Pool + Erasure Code Pool под ним (правда тоже на SSD).
Всё достаточно просто и понятно, хотя редактирование CRUSH MAP могли бы сделать попроще (дамп, декомпиляция, изменение, компиляция, применение — много шагов).
Что не понравилось:
1. Нестабильная и достаточно низкая производительность на запись в моей конфигурации. Тут, конечно, много подводных камней и нужно будет погонять на реальном железе. Но даже тут у меня IOPS плавали от 0 до 2.5к, а скорость линейная от 30 до 180 Мбайт/сек.
2. Очень большая нагрузка на процессор OSD демонами (150-250% на каждом OSD-хосте) при случайном чтении всего-то на 14-15к IOPS. В этом случае Erasure Code не должен вроде как срабатывать, чего оно так проц жрёт не пойму.
В общем, штука стоящая, буду изучать дальше.
rekby
пробовал на 3-х нодах несколько лет назад ceph работал очень нестабильно по скорости.
Раз в несколько дней случались замирания дисков на несколько минут. По итогам общения в рассылке сделал вывод что это побочный эффект от профилактического сканирования данных на предмет порчи и их восстановления. Должен исчезать при большом количестве нод.
phozzy
У меня на 5 узлах подобных проблем не наблюдается. Хотя, в документации, конечно, упоминается, что глубокий скрабинг может вызвать лаги. Но в целом, наверное, многое уже оптимизировали, так как ознакомившись, с результатами тестов производительности и рекомендациями по ее повышению, я обнаружил, что многие из этих рекомендаций установлены как значения по умолчанию в новой версии.
ftarasenko
А как вы Ceph планируете в vmware отдавать?
blind_oracle Автор
Создаём RBD-image в кластере, мапим его на каком-либо хосте Ceph-клиенте как обычное блочное устройство (/dev/rbdX) и экспортируем его любым удобным способом (FC, iSCSI и т.д.)
ftarasenko
Ну то есть через прокси ноду.
А как же жалобы на нестабильность и производительность подобных решений?
phozzy
если у вмвари есть интерфейс к openstack можно через cinder отдавать
ftarasenko
У опенстек есть интерфейс к варе, но у вари нет интерфейса к RBD.
В итоге мы опять возвращаемся к ISCSI прокси-ноде, стабильность которых у всех под вопросом.