Pull to refresh

Бюджетное SAN-хранилище на LSI Syncro, часть 2

Reading time 18 min
Views 12K


Продолжу, первая часть тут.

Кластер


Итак, приступим к настройке софта, управляющего кластером.
У нас это будет 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
# 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

Именование сетевых интерфейсов (intN, extN) тут по принципу встроенный это или внешний адаптер + порядковый номер порта на нём, мне так удобнее.

Далее настраиваем 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 параметры:
    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
    
    Это нужно для того, чтобы ядро в любой непонятной (а OOPS и всякие NMI это скверно) ситуации резетило сервер и давало второй ноде полноценно взяться за дело. Если ядро будет ещё более или менее живое, то этот функционал должен отработать быстрее чем Watchdog и STONITH.

  • Настроить 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) и тому подобное. В случае обнаружения неисправностей ноде следует совершить харакири.

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

Всем бобра!
Tags:
Hubs:
+10
Comments 13
Comments Comments 13

Articles