Про отказоустойчивость распределенных систем обычно говорят на уровне архитектурных схем: несколько реплик, несколько серверов, резервное хранилище, Kubernetes-кластер. На схеме все выглядит убедительно. Но у инженеров возникает более практичный вопрос: Что произойдет, если прямо сейчас взять и выключить часть инфраструктуры? Именно это мы и проверяем.

Мы в команде платформы контейнеризации dBrain.cloud регулярно создаем реальные аварийные сценарии для продукта: выводим из строя физические серверы, отключаем несколько машин одновременно, в отдельных сценариях обесточиваем целую стойку. При этом клиентские сервисы продолжают работать под нагрузкой.
Причем проверяем мы не одну эталонную конфигурацию. Каждая новая версия платформы сначала проходит внутреннее тестирование, а затем отказоустойчивость проверяется уже в рабочих условиях. Один из таких сценариев мы регулярно проводим вместе с партнером — крупной компанией из сферы информационных технологий. Примерно раз в полгода она тестирует платформу по заранее подготовленному протоколу, а мы наблюдаем за результатами и фиксируем проблемы.
То есть вопрос у нас не в том, есть ли у системы реплики. Вопрос в другом: что произойдет с платформой, когда один из физических серверов действительно исчезнет? А если исчезнут два? А если вместе с сервером пропадет часть хранилища?
Ниже — разбор того, как проходит проверка на отказоустойчивость на разных уровнях.
Начнем с Ceph
В нашей инфраструктуре используется Ceph в двух вариантах. В основном большом пуле используется erasure coding 8+3. Для этого сценария мы допускаем одновременный выход из строя двух серверов Ceph без влияния на работоспособность кластера.
Именно это мы и проверяем. Один из вариантов теста — выключить стойку, в которой находятся два сервера Ceph. Просто отключается питание, и два сервера одновременно пропадают из кластера. Другой вариант — отдельно выключить сервер Ceph по питанию, имитируя ситуацию, когда сервер просто внезапно перестал работать. В рамках такого сценария два любых случайных сервера Ceph можно вывести из строя одновременно, а кластер продолжит работать.
Всего в этом пуле работает 120 серверов, объем данных составляет около 140 PiB.
При этом важно понимать, что речь идет не о ситуации, когда каждый пул использует все серверы Ceph одновременно. Конкретное распределение данных зависит от того, где находятся соответствующие части данных. Поэтому отключение даже большего количества серверов может вообще не затронуть какой-то пул, а для другого пула оказаться существенным.
Теоретически это означает, что запас прочности может быть больше того сценария, который мы проверяем на регулярных испытаниях. Если точно знать, где находятся соответствующие части данных, можно подобрать больше серверов для одновременного отключения.
Есть и второй вариант пула Ceph — с фактором репликации 3. На нем находятся базы данных и брокеры сообщений. Здесь допустимый сценарий другой: можно одновременно выключить один сервер. Если выключить второй сервер, на котором находится одна из необходимых реплик, пул перейдет в read only. Данные при этом не потеряются, но операции записи перестанут выполняться.
Для теста отказоустойчивости это уже аварийный сценарий, поэтому в штатном сценарии мы ограничиваемся отключением одного сервера.
Что происходит с базами данных
После хранилища можно посмотреть на сервисы, которые на нем работают. Например, PostgreSQL. У нас используется мастер и реплики. При отключении мастера происходит переключение на одну из реплик.
Сейчас у нас две запасные реплики, причем они работают на разных физических узлах. Поэтому PostgreSQL продолжает работать даже при выключении двух машин, на которых находятся соответствующие БД.
Здесь важен именно физический уровень размещения. Мы говорим не просто о двух репликах PostgreSQL, а о репликах, которые находятся на разных физических серверах. Иначе сам по себе факт наличия нескольких реплик еще не гарантировал бы, что отказ одного сервера не затронет их одновременно.
С Cassandra принцип похожий. Для keyspace используется тройная репликация. В штатном сценарии мы можем вывести из строя одну машину с Cassandra без влияния на backend-сервисы. Если выйдет из строя больше одного сервера, содержащего нужные части данных с тройной репликацией, Cassandra также может перейти в read only. Данные при этом не теряются, но новые операции записи выполняться не будут. Это уже существенная деградация сервиса и аварийная ситуация.
Для Kafka используется похожая схема: три ZooKeeper, множество брокеров и replication factor 3 для топиков. Поэтому в рамках предусмотренного сценария мы допускаем выход из строя одного backend-сервера, на котором работает соответствующий экземпляр.

Почему реплики не должны оказаться на одном сервере
Все это работает только при правильном размещении реплик. Наши распределенные базы данных описаны правилами node-anti-affinity. Они не позволяют нескольким репликам одновременно оказаться на одном физическом сервере.
Поэтому автоматический выход одного сервера из строя не должен приводить к одновременному выходу нескольких экземпляров Kafka, PostgreSQL, Cassandra и других сервисов.
При этом сами серверы распределены по стойкам. Для backend-роли используется правило: на одной стойке находится не более одного сервера соответствующей роли. Для Ceph в основном пуле допускается два сервера в стойке — именно потому, что описанный сценарий с рейдж-кодированием 8+3 рассчитан на одновременный отказ двух серверов.
А что происходит с Kubernetes
Отдельно проверяется отказоустойчивость самого Kubernetes. На master-нодах работают три распределенных экземпляра etcd. Выход одной master-ноды из строя не приводит к деградации сервисов Kubernetes.
Если выходит больше одной master-ноды, etcd может перейти в read only. В этом случае уже требуется процедура восстановления. В зависимости от ситуации это может означать возврат сервера, размещение данных static pods на доступных серверах или восстановление из резервных копий, которые также делаются на платформе.
То есть здесь, как и в случае с базами данных, есть определенная граница. Один отказ — предусмотренный сценарий, при котором система продолжает работать. Несколько отказов — уже аварийная ситуация, в которой требуется восстановление.
И это важная часть понятия отказоустойчивости: не «можно выключить сколько угодно серверов», а заранее понимать, сколько отказов система должна пережить автоматически и что делать после превышения этого предела.
Что происходит с подами после потери сервера
Когда физический сервер выходит из строя, вместе с ним временно становятся недоступны запущенные на нем поды. Но Kubernetes не должен ждать, пока этот сервер вернется. Если есть свободные ресурсы, потерянные экземпляры сервисов переподнимаются на других узлах. Нагрузка при этом продолжает обслуживаться оставшимися экземплярами.
В платформе dBrain.cloud за отдельный сценарий с зависшими подами отвечает Lost Node Cleaner. Это отдельный сервис, который отслеживает такие поды и перемещает их на другие узлы. Таким образом, после отказа сервера происходит сразу несколько вещей: нагрузка переключается на оставшиеся экземпляры, а потерянные поды восстанавливаются на доступных в кластере ресурсах.
Это позволяет не оставлять платформу надолго в состоянии, когда одна из реплик уже потеряна и следующий отказ может оказаться более критичным.
При этом автохилинг работает при распределенном CSI. Если данные хранятся на локальных дисках конкретного хоста, автоматически перенести такую базу или сервис на другой узел уже нельзя без учета особенностей конкретной архитектуры.
Что будет, если выключить стойку
Выключение стойки — более показательный сценарий, чем отключение одного сервера. Вместе с питанием стойки одновременно пропадает несколько физических машин. На них могут находиться серверы разных ролей, реплики сервисов и части хранилища. Именно поэтому серверы распределяются по стойкам.
Для backend-серверов используется правило, согласно которому на одной стойке не должно находиться более одного сервера соответствующей роли.
Для Ceph в основном пуле допускается два сервера в одной стойке — этот сценарий мы как раз и проверяем, отключая стойку целиком.
В результате тест показывает не только, что отдельный сервер можно потерять, но и что произойдет, если одновременно исчезнет несколько физических узлов. Важно: при этом клиентская нагрузка продолжает работать.
А если выключить больше?
Здесь начинается самое интересное. У каждого компонента есть своя граница.
Для одного пула Ceph можно одновременно потерять два сервера. Для другого пула с фактором репликации 3 — один. Для Cassandra в предусмотренном сценарии — один сервер. Для PostgreSQL благодаря репликам на разных физических узлах допустимо выключение двух машин. Для etcd можно потерять одну master-ноду без деградации Kubernetes.
Если выйти за эти границы, система не обязательно сразу потеряет данные или полностью остановится. В некоторых сценариях она переходит в read only, чтобы сохранить данные, но прекращает операции записи.

Нагрузка при этом никуда не исчезает
Все это проверяется не на пустом кластере. Во время испытаний платформа работает под нагрузкой. Поэтому после отключения сервера нужно не просто убедиться, что какой-то процесс остался запущен. Нужно проверить, что пользовательская нагрузка продолжает обслуживаться.
Если, например, на выключенном сервере работала реплика PostgreSQL, эта реплика становится недоступна. Нагрузка переключается на оставшиеся экземпляры. Одновременно Kubernetes понимает, что экземпляр потерян, и пытается переподнять его на свободных ресурсах.
В платформе dBrain.cloud также используются HPA и VPA. Есть еще вариант Predictive HPA, когда HPA работает на опережение: он использует исторические данные о всплесках нагрузки и может заранее увеличить ресурсы перед ожидаемым пиковым периодом, а не ждать, пока нагрузка уже превысила заданный порог.
Балансировщик тоже должен переживать отказ
Еще один уровень — балансировка нагрузки. Серверов, выполняющих роль балансировщика, несколько. Часть из них работает, часть остается резервной. Если один сервер выходит из строя, происходит переключение на другой. Это происходит за доли миллисекунды, поэтому пользователь не замечает само переключение.
Получается, что отказ одного физического сервера не вызывает остановку пользовательского трафика. Нагрузка продолжает идти через оставшиеся серверы, а вышедшие из строя экземпляры сервисов постепенно восстанавливаются на доступных ресурсах.
Что происходит с мониторингом и логами
Отказоустойчивость касается не только сервисов, которые непосредственно обслуживают пользовательские запросы. Мониторинг и логирование тоже имеют репликацию на уровне баз данных, хранилищ, метрик и логов.
Сервисы мониторинга и логирования распределены по backend-серверам. Поэтому выход из строя одного сервера этой роли не должен влиять на инфраструктурные сервисы платформы. Это важно именно во время аварийного сценария: когда часть инфраструктуры исчезает, сама система наблюдения за инфраструктурой тоже должна продолжать работать.
Почему мы не просто выключаем один сервер и говорим «все хорошо»
Потому что отказоустойчивость платформы — это не один тест. Мы проверяем разные уровни: Ceph, базы данных, брокеры сообщений, Kubernetes, размещение реплик, восстановление подов, балансировку, мониторинг и работу клиентских сервисов под нагрузкой. Причем один и тот же отказ может по-разному влиять на разные компоненты.
Например, выключение двух серверов Ceph для одного пула является допустимым сценарием, а для другого пула второй отказ уже приводит к read only.
Поэтому нельзя сказать просто: «кластер переживает два сервера». Правильнее сказать: конкретная конфигурация платформы переживает конкретный сценарий отказа. И именно эти сценарии мы заранее описываем в протоколе тестирования.
Как проходят регулярные испытания
С партнером такие испытания проводятся примерно раз в полгода. Для них мы заранее готовим протокол, в котором описываем, что именно можно выключить и как должна вести себя платформа после отказа.
При этом dBrain.cloud не проводит тест вместо партнера. Партнер самостоятельно выполняет отключения и нагрузочное тестирование, а команда dBrain выступает наблюдателем. Если что-то происходит не так, как предусмотрено сценарием, это фиксируется и дальше разбирается командой. И это не единственное тестирование платформы.
Каждая новая версия проходит внутренние проверки. Внутри используется комплекс автотестов с высоким покрытием, после чего тестирование продолжается уже в условиях, приближенных к рабочим, в том числе вместе с подрядчиками и партнерами.
Поэтому проверяется не одна версия платформы, которая когда-то прошла испытания. Новые версии тоже проходят проверку. Это особенно важно для отказоустойчивости: после изменения платформы недостаточно предположить, что поведение осталось прежним. Его нужно снова проверить.
Отказоустойчивость платформы dBrain.cloud — это реальный запас прочности системы, который регулярно подтверждается на практике. Мы проверяем конкретные сценарии отказа под нагрузкой: от отключения отдельных серверов Ceph, баз данных и мастер-нод Kubernetes до аварийного обесточивания целой стойки. А как вы тестируете отказоустойчивость своих систем и с какими сценариями сталкиваетесь? Поделитесь вашим опытом в комментариях!
Источник: habr.com