Как мы ломали Glusterfs



    История началась год назад, когда к нам пришёл наш друг, коллега и большой эксперт по энтерпрайз стораджам со словами: «Парни, у меня тут завалялась шикарная хранилка со всеми модными фичами. 90Tb». Особой необходимости мы в ней не видели, но, естественно, отказываться не стали. Настроили туда пару бекапов и на какое-то время благополучно забыли.

    Периодически возникали задачи типа переброса больших файлов между хостами, сборка WAL для реплик Postgre и т. д. Постепенно мы начали перекладывать в этот холодильник всё разбросанное добро, так или иначе связанное с нашим проектом. Настроили ротейт, оповещения об успешных и не очень попытках выполнения бекапа. За год эта хранилка стала одним из важных элементов нашей инфраструктуры в группе эксплуатации.

    Всё было хорошо, пока к нам опять не пришёл наш эксперт и не сказал, что свой подарок он хочет забрать. И вернуть его надо срочно.

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

    Из множества вариантов наш взгляд особенно зацепил Глустер, Гластер. Всё равно, как называть. Лишь бы результат был. Поэтому мы начали над ним измываться.

    Что за Гластер и зачем он нужен?


    Это распределённая файловая система, которая давно дружит с Openstack и интегрирована в oVIrt/RHEV. Хоть наш IaaS и не на Openstack, у Гластера большое активное комьюнити и есть нативная поддержка qemu в виде интерфейса libgfapi. Тем самым мы убиваем двух зайцев:

    1. Поднимаем сторадж для бекапов, полностью поддерживаемый нами. Больше не придётся бояться в ожидании, когда вендор пришлёт сломанную запчасть.
    2. Тестируем новый тип хранения (volume type), который мы в перспективе можем предоставить нашим заказчикам.

    Гипотезы, которые мы проверяли:

    1. Что Гластер работает. Проверено.
    2. Что он отказоустойчив — можем ребутнуть любую ноду и кластер продолжит работать, данные будут доступны. Можем ребутнуть несколько нод, данные не потеряются. Проверено.
    3. Что он надёжен — то есть не падает сам по себе, не истекает памятью и т. д. Частично верно, потребовалось много времени, чтобы понять, что проблема не в наших руках и головах, а в Striped-конфигурации вольюма, который не смог работать стабильно ни в одной из собранных нами конфигураций (детали в конце).

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

    Как поднимали


    Палок для экспериментов у нас хватало с излишком — стойка с Dell Poweredge r510 и пачка не очень шустрых SATA-двухтерабайтников, оставшихся у нас по наследству от старого S3.

    Мы прикинули, что нам не нужно хранилище больше чем на 20 ТБ, и после этого нам понадобилось около получаса, чтобы набить в два стареньких Dell Power Edge r510 по 10 дисков, выбрать ещё один сервер для роли арбитра, скачать пакетики и задеплоить его. Получилась вот такая схема.



    Мы выбрали именно striped-replicated с арбитром, потому что это быстро (данные размазываются равномерно по нескольким брикам), достаточно надёжно (реплика 2), можно пережить падение одной ноды, не получив split-brain. Как же мы ошибались…

    Главный минус нашего кластера в текущей конфигурации — это очень узкий канал, всего 1G, но для нашего назначения его вполне хватает. Поэтому данный пост не о тестировании скорости работы системы, а о её стабильности и том, что делать при авариях. Хотя в будущем мы планируем переключить его на Infiniband 56G с rdma и провести перформанс-тесты, но это уже совсем другая история.

    Глубоко вдаваться в процесс сборки кластера я не буду, здесь всё достаточно просто.

    Создаём директории для бриков:

    for i in {0..9} ; do mkdir -p /export/brick$i ; done
    

    Накатываем xfs на диски для бриков:

    for i in {b..k} ; do mkfs.xfs /dev/sd$i ; done

    Добавляем точки монтирования в /etc/fstab:

    /dev/sdb /export/brick0/ xfs defaults 0 0
    /dev/sdc /export/brick1/ xfs defaults 0 0
    /dev/sdd /export/brick2/ xfs defaults 0 0
    /dev/sde /export/brick3/ xfs defaults 0 0
    /dev/sdf /export/brick4/ xfs defaults 0 0
    /dev/sdg /export/brick5/ xfs defaults 0 0
    /dev/sdh /export/brick6/ xfs defaults 0 0
    /dev/sdi /export/brick7/ xfs defaults 0 0
    /dev/sdj /export/brick8/ xfs defaults 0 0
    /dev/sdk /export/brick9/ xfs defaults 0 0
    

    Монтируем:

    mount -a

    Добавляем в брики директорию для вольюма, который будет называться holodilnik:

    for i in {0..9} ; do mkdir -p /export/brick$i/holodilnik ; done

    Дальше нам надо спирить хосты кластера и создать вольюм.

    Ставим пакеты на всех трёх хостах:

    pdsh -w server[1-3] -- yum install glusterfs-server -y

    Запускаем Гластер:

    systemctl enable glusterd
    systemctl start glusterd

    Полезно знать, что процессов у Гластера несколько, вот их назначения:

    glusterd = management daemon
    Главный демон, управляет вольюмом, тянет за собой остальные демоны, отвечающие за брики и восстановление данных.

    glusterfsd = per-brick daemon
    У каждого брика запускает свой glusterfsd демон.

    glustershd = self-heal daemon
    Отвечает за ребилд данных у реплицированных вольюмов в случаях отвала нод кластера.

    glusterfs = usually client-side, but also NFS on servers
    Например, прилетает с пакетом нативного клиента glusterfs-fuse.

    Пирим ноды:

    gluster peer probe server2
    gluster peer probe server3
    

    Собираем вольюм, здесь важен порядок бриков — реплицируемые брики идут следом друг за другом:

    gluster volume create holodilnik stripe 10 replica 3 arbiter 1 transport tcp server1:/export/brick0/holodilnik server2:/export/brick0/holodilnik server3:/export/brick0/holodilnik server1:/export/brick1/holodilnik server2:/export/brick1/holodilnik server3:/export/brick1/holodilnik server1:/export/brick2/holodilnik server2:/export/brick2/holodilnik server3:/export/brick2/holodilnik server1:/export/brick3/holodilnik server2:/export/brick3/holodilnik server3:/export/brick3/holodilnik server1:/export/brick4/holodilnik server2:/export/brick4/holodilnik server3:/export/brick4/holodilnik server1:/export/brick5/holodilnik server2:/export/brick5/holodilnik server3:/export/brick5/holodilnik server1:/export/brick6/holodilnik server2:/export/brick6/holodilnik server3:/export/brick6/holodilnik server1:/export/brick7/holodilnik server2:/export/brick7/holodilnik server3:/export/brick7/holodilnik server1:/export/brick8/holodilnik server2:/export/brick8/holodilnik server3:/export/brick8/holodilnik server1:/export/brick9/holodilnik server2:/export/brick9/holodilnik server3:/export/brick9/holodilnik force
    

    Нам пришлось перепробовать большое количество комбинаций параметров, версий ядра (3.10.0, 4.5.4) и самого Glusterfs (3.8, 3.10, 3.13), чтобы Гластер начал вести себя стабильно.

    Также мы опытным путём выставили следующие значения параметров:

    gluster volume set holodilnik performance.write-behind on
    gluster volume set holodilnik nfs.disable on
    gluster volume set holodilnik cluster.lookup-optimize off
    gluster volume set holodilnik performance.stat-prefetch off
    gluster volume set holodilnik server.allow-insecure on
    gluster volume set holodilnik storage.batch-fsync-delay-usec 0
    gluster volume set holodilnik performance.client-io-threads off
    gluster volume set holodilnik network.frame-timeout 60
    gluster volume set holodilnik performance.quick-read on
    gluster volume set holodilnik performance.flush-behind off
    gluster volume set holodilnik performance.io-cache off
    gluster volume set holodilnik performance.read-ahead off
    gluster volume set holodilnik performance.cache-size 0
    gluster volume set holodilnik performance.io-thread-count 64
    gluster volume set holodilnik performance.high-prio-threads 64
    gluster volume set holodilnik performance.normal-prio-threads 64
    gluster volume set holodilnik network.ping-timeout 5
    gluster volume set holodilnik server.event-threads 16
    gluster volume set holodilnik client.event-threads 16

    Дополнительные полезные параметры:

    sysctl vm.swappiness=0
    sysctl vm.vfs_cache_pressure=120
    sysctl vm.dirty_ratio=5
    echo "deadline" > /sys/block/sd[b-k]/queue/scheduler
    echo "256" > /sys/block/sd[b-k]/queue/nr_requests
    echo "16" > /proc/sys/vm/page-cluster
    blockdev --setra 4096 /dev/sd[b-k]

    Стоит добавить, что эти параметры хороши в нашем случае с бекапами, а именно с линейными операциями. Для рандомных кейсов чтения/записи надо подбирать что-нибудь другое.

    Теперь мы рассмотрим плюсы и минусы разных типов подключения к Гластеру и результаты негативных тест-кейсов.

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

    1. Gluster Native Client (glusterfs-fuse) с параметром backupvolfile-server.

    Минусы:
    — установка дополнительного софта на клиенты;
    — скорость.

    Плюс/минус:
    — долгая недоступность данных в случае отвала одной из нод кластера. Проблема правится параметром network.ping-timeout на стороне сервера. Выставив параметр в 5, шара отваливается, соответственно, на 5 секунд.
    Плюс:
    — достаточно стабильно работает, не наблюдалось массовых проблем с битыми файлами.

    2. Gluster Native Client (gluster-fuse) + VRRP (keepalived).
    Настроили переезжающий IP между двумя нодами кластера и гасили одну из них.


    Минус:
    — установка дополнительного софта.

    Плюс:
    — конфигурируемый таймаут при переключении в случае отвала ноды кластера.

    Как оказалось, указание параметра backupvolfile-server или настройка keepalived необязательны, клиент сам подключается к демону Гластера (неважно, по какому из адресов), узнаёт остальные адреса и запускает запись на все ноды кластера. В нашем случае мы видели симметричный трафик с клиента на server1 и server2. Даже если вы указываете ему VIP-адрес, клиент всё равно будет использовать кластерные адреса Glusterfs. Мы пришли к выводу, что данный параметр полезен в том случае, когда клиент при старте пытается подключиться к Glusterfs-серверу, который недоступен, тогда он следом обратится к хосту, указанному в backupvolfile-server.

    Комментарий из официальной документации:
    The FUSE client allows the mount to happen with a GlusterFS “round robin” style connection. In /etc/fstab, the name of one node is used; however, internal mechanisms allow that node to fail, and the clients will roll over to other connected nodes in the trusted storage pool. The performance is slightly slower than the NFS method based on tests, but not drastically so. The gain is automatic HA client failover, which is typically worth the effect on performance.

    3. NFS-Ganesha server, с Pacemaker.

    Рекомендованный тип подключения, если по каким-то причинам вам не хочется использовать нативный клиент.

    Минусы:
    — ещё больше дополнительного софта;
    — возня с pacemaker;
    — отловили багу.

    4. NFSv3 и NLM + VRRP (keepalived).
    Классический NFS с поддержкой локов и переезжающий IP между двумя нодами кластера.

    Плюсы:
    — быстрое переключение в случае выхода из строя ноды;
    — простота настройки keepalived;
    — nfs-utils установлен на всех наших клиентских хостах по умолчанию.

    Минусы:
    — повисание NFS клиента в статусе D после нескольких минут rsync в точку монтирования;
    — падение ноды с клиентом целиком — BUG: soft lockup — CPU stuck for Xs!
    — отловили множество случаев, когда файлы ломались с ошибками stale file handle, Directory not empty при rm -rf, Remote I/O error и т. д.

    Самый худший вариант, более того, в поздних версиях Glusterfs он стал deprecated, никому не советуем.

    В итоге мы выбрали glusterfs-fuse без keepalived и с backupvolfile-server-параметром. Так как в нашей конфигурации он единственный показал стабильность, несмотря на относительно низкую скорость работы.

    Помимо необходимости в настройке высокодоступного решения, в продуктивной эксплуатации мы должны уметь восстанавливать работу сервиса в случае аварий. Поэтому после сборки стабильно работающего кластера мы приступили к деструктивным тестам.

    Нештатное выключение ноды (cold reboot)


    Мы запустили rsync большого количества файлов с одного клиента, жёстко погасили одну из нод кластера и получили очень забавные результаты. После падения ноды сначала запись остановилась на 5 секунд (за это отвечает параметр network.ping-timeout 5), после этого скорость записи в шару выросла вдвое, так как клиент больше не может реплицировать данные и начинает слать весь трафик на оставшуюся ноду, продолжая упираться в наш 1G канал.



    Когда сервер загрузился, начался автоматический процесс лечения данных в кластере, за который отвечает демон glustershd, и скорость значительно просела.

    Так можно посмотреть количество файлов, которые лечатся после отвала ноды:

    gluster volume heal holodilnik info

    ...
    Brick server2:/export/brick1/holodilnik
    /2018-01-20-weekly/billing.tar.gz
    Status: Connected
    Number of entries: 1

    Brick server2:/export/brick5/holodilnik
    /2018-01-27-weekly/billing.tar.gz
    Status: Connected
    Number of entries: 1

    Brick server3:/export/brick5/holodilnik
    /2018-01-27-weekly/billing.tar.gz
    Status: Connected
    Number of entries: 1
    ...

    По окончании лечения счётчики обнулились и скорость записи вернулась к прежним показателям.

    Отвал диска и его замена


    Отвал диска с бриком, так же, как и его замена, никак не замедлил скорость записи в шару. Вероятно, дело в том, что узкое место тут, опять же, канал между нодами кластера, а не скорость дисков. Как только у нас появятся дополнительные карты Infiniband, мы проведём тесты с более широким каналом.

    Хочется отметить, что, когда вы меняете вылетевший диск, он должен вернуться с тем же именем в sysfs (/dev/sdX). Часто происходит, что новому диску присваивается следующая буква. Я крайне не рекомендую вводить его в таком виде, так как при последующей перезагрузке он возьмёт старое название, имена блочных девайсов съедут и брики не поднимутся. Поэтому придётся провести несколько действий.

    Скорее всего, проблема в том, что где-то в системе остались точки монтирования вылетевшего диска. Поэтому делаем umount.

    umount /dev/sdX

    Также проверяем, какой процесс может держать этот девайс:

    lsof | grep sdX

    И останавливаем этот процесс.

    После этого надо сделать рескан.
    Смотрим в dmesg-H более подробную информацию о расположении вылетевшего диска:

    [Feb14 12:28] quiet_error: 29686 callbacks suppressed
    [ +0.000005] Buffer I/O error on device sdf, logical block 122060815
    [ +0.000042] lost page write due to I/O error on sdf
    [ +0.001007] blk_update_request: I/O error, dev sdf, sector 1952988564
    [ +0.000043] XFS (sdf): metadata I/O error: block 0x74683d94 ("xlog_iodone") error 5 numblks 64
    [ +0.000074] XFS (sdf): xfs_do_force_shutdown(0x2) called from line 1180 of file fs/xfs/xfs_log.c. Return address = 0xffffffffa031bbbe
    [ +0.000026] XFS (sdf): Log I/O Error Detected. Shutting down filesystem
    [ +0.000029] XFS (sdf): Please umount the filesystem and rectify the problem(s)
    [ +0.000034] XFS (sdf): xfs_log_force: error -5 returned.
    [ +2.449233] XFS (sdf): xfs_log_force: error -5 returned.
    [ +4.106773] sd 0:2:5:0: [sdf] Synchronizing SCSI cache
    [ +25.997287] XFS (sdf): xfs_log_force: error -5 returned.

    Где sd 0:2:5:0 — это:
    h == hostadapter id (first one being 0)
    c == SCSI channel on hostadapter (first one being 2) — он же PCI-слот
    t == ID (5) — он же номер слота вылетевшего диска
    l == LUN (first one being 0)

    Ресканим:

    echo 1 > /sys/block/sdY/device/delete
    echo "2 5 0" > /sys/class/scsi_host/host0/scan

    где sdY — неправильное имя заменяемого диска.

    Далее для замены брика нам надо создать новую директорию для монтирования, накатить файловую систему и примонтировать его:

    mkdir -p /export/newvol/brick
    mkfs.xfs /dev/sdf -f
    mount /dev/sdf /export/newvol/

    Выполняем замену brick:

    gluster volume replace-brick holodilnik server1:/export/sdf/brick server1:/export/newvol/brick commit force

    Запускаем лечение:

    gluster volume heal holodilnik full
    gluster volume heal holodilnik info summary

    Отвал арбитра:



    Те же 5–7 секунд недоступности шары и 3 секунды просадки, связанные с синком метаданных на кворум ноду.

    Резюме


    Результаты деструктивных тестов нас порадовали, и мы частично ввели его в прод, но радовались мы недолго…

    Проблема 1, она же известная бага
    При удалении большого количества файлов и директорий (порядка 100 000) мы откушали вот такую «красоту»:

    rm -rf /mnt/holodilnik/*
    rm: cannot remove ‘backups/public’: Remote I/O error
    rm: cannot remove ‘backups/mongo/5919d69b46e0fb008d23778c/mc.ru-msk’: Directory not empty
    rm: cannot remove ‘billing/2018-02-02_before-update_0.10.0/mongodb/’: Stale file handle

    Прочитал около 30 таких заявок, которые начинаются с 2013 года. Решения проблемы нигде нет.

    Red Hat рекомендует обновить версию, но нам это не помогло.

    Наш workaround — просто зачищать остатки битых директорий в бриках на всех нодах:

    pdsh -w server[1-3] -- rm -rf /export/brick[0-9]/holodilnik/<failed_dir_path>

    Но дальше — хуже.

    Проблема 2, самая страшная
    Мы попытались распаковать архив с большим количеством файлов внутри шары Striped вольюма и получили повисший tar xvfz в Uninterruptible sleep. Который лечится только ребутом ноды клиента.

    Осознав, что дальше так жить нельзя, мы обратились к последней не испробованной нами конфигурации, которая не вызывала у нас доверия, — erasure coding. Единственная её сложность — в понимании принципа сборки вольюма.

    Прогнав все те же самые деструктивные тесты, мы получили те же приятные результаты. Загружали в него миллионы файлов и удаляли. Как только мы ни старались, сломать Dispersed volume у нас не получилось. Мы видели более высокую нагрузку на CPU, но пока для нас это некритично.

    Сейчас он бекапит кусок нашей инфраструктуры и используется как файлопомойка для наших внутренних применений. Хотим с ним пожить, посмотреть, как он работает под разной нагрузкой. Пока понятно, что тип вольюмов «страйп» работает странно, а остальные — очень хорошо. Далее в планах — собрать 50 ТБ dispersed volume 4 + 2 на шести серверах с широким Infiniband-каналом, прогнать перфоманс-тесты и продолжать глубже вникать в принципы его работы.
    Облачные сервисы КРОК 155,80
    Облачная IaaS-платформа собственной разработки
    Поделиться публикацией
    Комментарии 17
    • 0

      Тип distributed replicated не пробовали?

      • +1
        Да, пробовали, работает стабильно, но с ним слишком много ограничений. Например, максимальный размер файла = размер брика — уже занятое на нем пространство. В этом случае, конечно, должен помочь Sharding xlator, но мы пока мы его только тестируем и рано делать выводы
      • 0
        Не пойму из мануала, можно ли на glusterfs поднять что-то наподобие виндовых групп DFS-R (хотя бы без расписания), по описаниям похоже, что он синхронный в режимах replicated или distributed replicated, то есть будет упираться в самый медленный канал связи, что всерьез мешает работе по аналогии с DFS-R через самбовые шары.

        И ещё, почему используете 3.13, а не 3.12? У гластера 3.12 описана как LTS, а 3.13 не поддерживается после выхода 4.0.
        • 0
          У него в доке есть отдельный раздел Geo Replication.
          • 0
            А, читал же — это немножко не то. DFS-R позволяет геораспределенный мультимастер-доступ к одному контенту, при этом доступ по умолчанию сперва идет к локальной (site-local) шаре, а изменения гоняются в обе стороны. (Точнее, топология у DFS-R настраиваемая, но каждое соединение двунаправленное) Гео-репликация гластера однонаправленная, и никаких колец.
          • +1
            На момент тестирования LTSкой была 3.10, на ней мы проводили большую часть тестирования, также мы пробовали стабильную версию от Red Hat (rhgs-3.3), но по сути разницы не было, основные проблемы со striped вольюмами оставались.
          • 0
            А что за режим такой «erasure coding» в glusterfs?
          • +2
            при отвале диска, описанных проблем можно избежать, используя монтирование по метке или UUID:
            e2label device [ new-label ]

            и в fstab пишем:
            LABEL=you_name_it /mount_point xfs 0 0
            либо используем UUID:
            UUID=4712c507-ae48-4e9c-8646-d859ad9406bc /mount_point xfs 0 0
            • 0
              Согласен, еще мы рассматривали вариант монтировать по симлинку by-path
              udevadm info -q symlink /dev/sdb
              disk/by-id/scsi-36d4ae52074d938002256460a3c3bc21d disk/by-id/wwn-0x6d4ae52074d938002256460a3c3bc21d disk/by-path/pci-0000:02:00.0-scsi-0:2:1:0 disk/by-uuid/c6ff2798-7302-4d52-a499-5a0109ceeb70​
            • 0

              Тиринг не включали в гластере?

              • 0
                Пока еще нет
                • 0
                  Мы словили багу при удалении данных из тиринга. Нода отвалилась ибез ряда манипуляций не завелась.
              • 0
                Очень рекомендую не использовать stripe volume, а использовать distributed и включить там sharding.
                • +1
                  Немного башевой магии уменьшит команду инициализации до вменяемых размеров сохранив гарантированный порядок:
                  gluster volume create holodilnik stripe 10 replica 3 arbiter 1 transport tcp $( for brick in {0..9}; do echo {server1,server2}:/export/brick${brick}/holodilnik; done)
                  Жаль, нельзя указывать порядок brace expansion, было бы ещё лучше 8-)
                  • 0
                    Ловко, спасибо, приму на вооружение =)

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

                  Самое читаемое