Мониторинг Ceph
Как не пропустить падения и взлёты в жизни кластеров ceph с помощью prometheus или victoriametrics. Теория и практика мониторинга распределенного хранилища.
Теория
Делай алертинг по симптомам, а не по причинам!
(c) Джейсон Стетхем
Возможно вы не в курсе, но ceph — это распределенная система. Система, ключевые сервисы которой хранят состояние. Цеф не просто обрабатывает запросы клиентов, он принимает, хранит и отдаёт данные. При этом строит кворум на протоколе paxos для согласования состояний кластера, использует консистентное хэширование для балансировки нагрузки и данных по частям кластера, использует хартбиты для оценки состояния демонов хранилища.
Мониторинг распределенных систем — это сложно. Распределенные системы сложны, архитектура не тривиальна, так иногда вообще не понятно, работает ли система как надо, или какая-то часть частично сломалась.
Масло в огонь подливают хорошие практики. Мало у кого цеф — внешний сервис, отдаваемый клиентам напрямую как есть. Это инфраструктурный кусок, который используется в глубине продукта. Практики говорят нам: “Мониторьте симптомы, не мониторьте инфраструктуру!”. Хорошо, мы попробуем, спасибо.
Возможно это профессиональная деформация, так как я много работал с цефом для хранения дисков виртуалок, и в меньших масштабах он использовался как бэкенд для самописного S3.
Это правильный подход. Хоть он не всегда применим. Всем давно понятно, что мониторинг делают по симптомам, с точки зрения клиента. Но и мониторить работу системы мониторинга нужно, хотя клиентам абсолютно всё равно работает она или нет. Получать превентивные сообщения о заканчивающемся месте на диске тоже нужно. Как уже было сказано, цеф — это инфраструктура. В этой статье мы посмотрим, что мы можем узнать о ней изнутри.
ExporterDown
Очевидный момент, что первый алерт, который будет настроен — алерт на отвал источника данных. Экспортер работает на всех демонах ceph-mgr.
Активный MGR отдаёт данные, неактивные отдают пустую страницу.
Если экспортер недоступен, то метрики не собираются. Если нет метрик, то мы не можем оценить состояние кластера. Добавляем алерт на такое выражение:
- alert: ExporterDown
annotations:
summary: Exporter Down
details: {{ $labels.job }}
expr: up == 0
for: 30s
Здесь и далее подразумевается интервал опроса экспортеров 15 секунд. Система мониторинга — вещь сильно кастомизируемая под процессы в компании, поэтому какие-то моменты могут быть неприменимы. Самое важно в алертах, которые приведены в статье — выражение и причина, по которой выбран
for
.
Довольно стандартный алерт. Его же можно использовать, например, для node-exporter и для всех экспортеров, которые связаны с критичными сервисами.
Если нет необходимости сообщать о падении одного MGR, можно изменить алерт: count(up{job="ceph-mgr"} == 1) == 0
.
PGInactive
Плейсмент группы (placement group, PG) — одна из основных абстракций в ceph. По сути это реплицируемый контейнер с данными. Клиентские данные делятся на кусочки, которые разлетаются в разные PG. Недоступность части PG приводит к недоступности части данных.
Причины недоступности PG могут быть разные. Это могут быть программные ошибки, локальная нехватка ресурсов, флапы демонов OSD, падение большого количества OSD с уменьшением количества реплик ниже минимального. В таких ситуациях кластер не сможет вернуть ответ на запрос к некоторым данным, поэтому нам нужен алерт:
- alert: CephPGInactive
annotations:
summary: Ceph PG Inactive
details: data availability: {{ $value|humanizePercentage }}
expr: ceph_pg_active/ceph_pg_total < 1
for: 5m
В алерте мы проверяем не появились ли PG без состояния active. Подробнее о состояниях PG можно почитать тут.
Здесь может появиться желание разбить этот алерт на десяток алертов поменьше. Привязать их к появлению PG в каких-нибудь важных статусах, типа
peered
, которые не сочетаются сactive
. Это нахуй не нужно, только лишний шум и больше работы.Правда в том, что нас волнуют только те состояния, когда PG становится неактивна. Только тогда у клиентов проблемы. Значит и алертинг надо делать на это состояние.
По сути эта метрика отражает доступность клиентских данных в кластере и можно построить красивый график. К сожалению, доступность данных никак не связана с количеством запросов на которые не удалось ответить. Т.е. видимая доступность для клиентов будет отличаться от доступности данных в кластере.
Иногда PG могут быть неактивны в течении короткого интервала времени, если проводятся административные операции на кластере. Например, при добавлении OSD в кластер, или в случае падения OSD. На большом кластере количество неактивных PG может быть больше нуля в течении длительного интервала времени. Но в каждый отдельный момент времени неактивны будут разные PG. У нас нет возможности узнать какие именно PG были неактивны, поэтому мы не можем отличить две ситуации:
- Одна PG в течении минуты была неактивна.
- 1000 PG были неактивны по очереди в течении минуты.
Во второй ситуации клиенты практически не пострадают. Будет кратковременный и малозаметный всплеск латенси на каких-то запросах. В первой же — в течении минуты что-то будет недоступно, а это уже в два раза дольше дефолтного таймаута линукса на дисковую операцию.
Чтобы не получать фалс алармов при втором раскладе, в алерте используется параметр for
. Оптимальное значение подбирается на основе статистики. Просто строим график по выражению из алерта и смотрим какой интервал в разных ситуациях приведет к срабатыванию.
HealthError
Кластер ceph сообщает о своем здоровье оператору одной метрикой. Метрика называется ceph_health_status
. Это то же самое, что отображается в выводе ceph -s
. Здоровье кластера может быть в одном из трёх состояний: здоров, внимание и ошибка:). HEALTH_WARN
нас не интересует, потому что он возникает даже тогда, когда ничего не нужно предпринимать. Например, если выставлен флаг noout
, запрещающий автоматически выбрасывать OSD из кластера после падения, или флаг nodeep-scrub
, который отключает глубокий скраб и помогает избежать проблем с производительностью во время скарббинга в некоторых ситуациях.
Состояние HEALTH_ERR
может дать информацию, которую трудно или невозможно собрать иным способом. Ниже список этих событий и их названия в исходниках, которые переводят кластер в ошибку:
- Поврежденные PG.
PG_DAMAGED
- Появление PG в статусе recovery full.
PG_RECOVERY_FULL
- Ошибки, обнаруженные в результате скраба.
OSD_SCRUB_ERRORS
- Переполненный пул.
POOL_FULL
- Переполненный осд.
OSD_FULL
- Хотя бы у одного ceph-mon процент свободного места меньше, чем параметр
mon_data_avail_crit
.MON_DISK_CRIT
- Ошибка в модуле ceph-mgr.
MGR_MODULE_ERROR
- Ошибка конфигурации, при которой не соблюдаются неравенства
nearfull
<backfillfull
,backfillfull
<full
, иfull < failsafe_full
.OSD_OUT_OF_ORDER_FULL
- Коррапт или потеря мета-данных MDS.
MDS_HEALTH_DAMAGE
- Отсутствие хотя бы одного работающего MDS.
MDS_ALL_DOWN
- MGR недоступен дольше, чем
mon_mgr_inactive_grace
.now - first_seen_inactive > g_conf().get_val<int64_t>("mon_mgr_inactive_grace")
- Наличие “застрявшего” запроса (
REQUEST_STUCK
). Не путать с медленным (REQUEST_SLOW
)! Застрявшим запрос считается, если он превысил значение произведения двух настроек —mon_osd_warn_op_age * mon_osd_err_op_age_ratio
.
Подробнее в документации, либо в исходниках. Ситуация может меняться в зависимости от версии. Чтобы точно знать, где выставляется HEALTH_ERR
нужно грепать код по этому выражению:) про работу с исходным кодом можно почитать тут.
В цефе периодически случаются баги, из-за которых кластер может нелегитимно переходить в HEALTH_ERR
, хотя всё работает нормально. Бороться с этим можно и нужно патчами в апстрим.
Помимо багов, есть и другой неприятный момент. Даже в HEALTH_ERR
кластер может переходить в ситуации, которая не достаточно критична. Например, ошибки скраббинга. Стоит ли просыпаться ночью инженеру из-за ошибки скраба? Наверное, нет. Нужно ли отлавливать ошибку скраба, чтобы починить и выяснить причины? Наверное, да. Как отловить эту проблему избегая статуса? Наверное, кастомным скриптом.
Выражение для алерта:
- alert: CephClusterHealthError
annotations:
summary: Ceph Cluster Error
expr: ceph_health_status == 2
for: 5m
В некоторых случаях такой алерт даёт много фалс алармов. Тогда от него целесообразно отказаться. Замониторить свободное место в пулах и на OSD не составляет труда, как и свободное место под базой у мониторов цефа. Сложнее ситуация с залипшими запросами. Такие запросы не видно в латенси OSD, потому что они ещё находятся в обработке. Получается, что узнать эту информацию можно только сторонним инструментом. Если есть возможность мониторинга запросов на стороне клиента, я рекомендую сфокусироваться на нём.
OSDDown
Упавший OSD-демон — это не всегда авария, но это снижение уровня резервирования и повышение риска отказа в обслуживании. Об этом событии нужно знать:
- alert: CephOSDDown
annotations:
summary: Ceph OSD Down
details: {{ $labels.ceph_daemon }}
expr: ceph_osd_metadata * on (ceph_daemon,instance)
group_left() (ceph_osd_up == 0 and ceph_osd_in == 1)
Мы следим не за всеми демонами, а только за теми, которые введены в краш, т.е. находятся в состоянии in
. Так отделяется продакшен-окружение в кластере от препродакшена.
Причиной падения может быть что угодно: от банального выхода из строя диска, до небанальной нагрузки со стороны клиента или программной ошибки.
Почему бы нам не следить за каждым статусом PG и алертить, когда появляются PG в статусе, например,
degraded
? Причина в том, что на такой алерт невозможно поставить сайленс. Например, при проведении плановых работ, будет невозможно аккуратно притушить алерты.Плейсмент группы работают на нескольких OSD, а с точки зрения prometheus вообще нет возможности получить информацию о конкретной PG, даже её имя. Сайленс легко ставится на алерт о падении конкретных OSD, после чего проводятся плановые работы. В то же время, алерт на неактивные PG и на возросшую латенси на других OSD, о котором я пишу ниже, останутся в рабочем состоянии. Сайленс работает, кластер мониторится — одни плюсы.
MonQuorum
Мониторы реализуют алгоритм PAXOS. Мониторов должно быть три. Они должны находиться на разных хостах, в разных стойках. Ситуация выхода из строя большей части кворума должна быть минимизирована. Один выпавший монитор — не проблема для клиента. Но с этим надо работать:
- alert: CephMonQuorum
annotations:
summary: Ceph Mon Quorum Failed
expr: ceph_mon_metadata * on (ceph_daemon) group_left()
ceph_mon_quorum_status < 1
Причины выхода из строя монитора различны и неожиданны.
NearFullOSD
OSD хранят данные на локальном диске и место на нем может закончится. Мы можем высчитать процент, который будет соответствовать порогу near full:
- alert: CephNearFullOSD
annotations:
summary: Ceph Near Full OSD
details: {{ $labels.ceph_daemon }}: {{ printf "%.2f" $value }}% used
expr: ceph_osd_metadata * on (ceph_daemon,instance) group_left()
(ceph_osd_stat_bytes_used/ceph_osd_stat_bytes*100) > 85
Этому алерту можно задать for: 15m
, потому что показатель может плавать, а получать шквал алертов из-за этого никто не хочет. Развесистое выражение можно вынести в рекорд рул.
Место может закончится либо из-за плохой балансировки, либо из-за плохого планирования. Что делать — использовать балансировщик на upmap, либо срочно добавлять хосты в кластер.
PoolSpace
За местом в пуле тоже нужно следить:
- alert: CephPoolSpace
annotations:
summary: Ceph Pool Space
details: {{ $labels.name }}: {{ printf "%.2f" $value }}% used
expr: ceph_pool_metadata * on (pool_id,instance) group_left()
(ceph_pool_bytes_used/
(ceph_pool_bytes_used+ceph_pool_max_avail)*100) > 60
В примере выставлено 60%, эта величина зависит от процессов в компании, профиля использования кластера и его размера. Срочность такого алерта ниже, чем, например, упавшего демона OSD. Это вполне может Info или Warning или NeSrochno, зависит от вашей системы именования срочности. Выражение можно вынести в рекорд рул и строить по нему красивые графики.
Этому значению также можно задать for
больше 0. Потому что не очень интересно наблюдать, как кластер балансирует на пограничном значении.
HighLatency
Самое интересное и сложное — алерты по латенси OSD. Латенси может расти по разным причинам: слишком высокая нагрузка, мисконфиг, проблемы с производительностью железа, потери и ошибки на сети. За латенси нужно следить. И в этот момент вспоминаются красивые рассказы с конференций и из SRE Book о перцентилях и SLA. В контексте нашего хранилища есть нюансы.
Для прома доступно только среднее латенси на запись и чтение с OSD. Но латенси сильно зависит от размера блока данных, который обрабатывается в запросе. Чем больше блок, тем выше будет латенси и тем выше допустимая латенси для такого запроса. К сожалению, у нас нет информации о размерах блока. Ещё мы не знаем худшее значение в кластере на текущий момент. Мы не знаем и лучшее значение и не представляем распределения.
Придётся оперировать средним значением латенси при оценке качества обслуживания. Если у вас есть SLA или SLO на скорость доступа к данным, то вы уже знаете от чего отталкиваться. Придумайте, если его нет. В любом случае, для цефа нужно выбрать какое-то оптимальное значение.
Для этого подготовимся. Нужно оценить профиль нагрузки, возможности бэкенда и требования. Пул под бэкапы на блинах и под метаданные на NVMe. Требования к скорости создания бэкапа довольно низкие. Бэкап делается асинхронно, скорость доступа к каждому конкретному блоку особо не волнует. Важнее максимальная пропускная способность. С метаданными всё наоборот — запросы синхронные и скорость ответа должна быть минимальна.
Латенси HDD на порядки выше, чем NVMe SSD. Профиль нагрузки разный: асинхронная запись большими блоками и единичные случайные запросы к маленьким блокам.
В таком случае очень удобно использовать device class
для задания различных лимитов. При этом device class
будет учитывать и тип девайса под OSD, и профиль нагрузки, и требования к качеству обслуживания.
Вот пример для ситуации с бэкапами и метаданными:
- Пул на NVMe, там лежат какие-то мелкие объекты, доступ к которым должен быть максимально быстрым. Зададим всем OSD в нём класс
nvme_meta
. - Пул на HDD, используемый под бэкапы, без требований к латенси. Этим OSD зададим класс
hdd_backups
.
Теоретически это не очень хорошо, что мы расширяем абстракцию. Возможно, для OSD-демонов стоит иметь ещё один параметр для нашей задачи: quality profile
. Но это в теории. Сейчас такого нет, поэтому используем device class
.
Запишем значение латенси одним правилом:
- expr: ceph_osd_metadata * on (ceph_daemon,instance) group_left()
(rate(ceph_osd_op_latency_sum[1m])
/
rate(ceph_osd_op_latency_count[1m]))
record: ceph_daemon:ceph_op_latency
Здесь мы осознанно берём rate, а не irate, чтобы сгладить спайки и избежать фалс алармов и флапов.
Дальше пойдем хорошей практикой и создадим метрику-лимит на пару разных девайс-классов:
- expr: 0.015
labels:
device_class: nvme_meta
record: ceph_high_latency:threshold
- expr: 10
labels:
device_class: hdd_backups
record: ceph_high_latency:threshold
Так элегантнейшим образом мы отделим заведомо тормозные OSD от тех, что будут и должны работать быстро.
Пишем алерт:
- alert: CephHighLatency
annotations:
summary: Ceph OSD Operations Latency
details: {{ $labels.ceph_daemon }}: {{ printf "%.2f" $value }}s
expr: ceph_daemon:ceph_op_latency > on (device_class) group_left()
(ceph_high_latency:threshold)
for: 5m
И получаем отличный алертинг по латенси. Кроме одного момента. Если у нас не задан device class, то мы не будем мониторить латенси на этой OSD. Покроем этот кейс:
- alert: CephUndefinedClass
annotations:
summary: Undefined Device Class
details: {{ $labels.hostname }}: {{ $labels.ceph_daemon }}: {{ $labels.device_class }}
expr: (ceph_osd_up == 1 and ceph_osd_in == 1) * on
(instance, ceph_daemon) group_left(hostname)
ceph_osd_metadata{device_class!~"hdd_backups|nvme_meta"}
Теперь всё точно работает.
Как-то сложно всё
Система сложная и во многих моментах приходится идти на компромисс. Мониторинг неактивных PG не даёт представления о том, какой пул затронуло, хотя эту информацию хотелось бы получать легко, чтобы моментально понять кто пострадал.
Мониторинг статуса кластера даёт своеобразные результаты, в документации описано не всё, что он затрагивает. Использовать состояние HEALTH_WARN
для мониторинга нельзя, да и HEALTH_ERR
не серебряная пуля. Было бы здорово иметь возможность определить что именно приведет к переходу из одного состояния к другому. Так можно было бы оставить только то, что действительно требует вмешательства.
Информации о латенси в том виде, в котором она есть, не всегда достаточно для точечного дебага. Бывают случаи, когда какой-нибудь клиент административными действиями, вроде работы со снапшотами, может зааффектить среднее значение латенси. При этом, рост латенси на самом деле может быть только у этого клиента, остальные не почувствуют проблемы. Мы не можем отделить одно событие от другого.
Та же проблема может быть, если клиент начал очень активно работать с каким-то одним блоком данных и латенси растёт на конкретном OSD, где расположен этот блок. Но если посмотреть дампы операций, то видно, что в очереди долго ждут только запросы именно этого клиента, а все остальные работают нормально. Это всё частные случаи, но они показывают, что данных не всегда достаточно. Сильно лучше было бы, если бы информация о латенси и количестве запросов была разделена по размеру блока.
Вместо заключения
Нам удалось построить очень неплохой мониторинг неоднородных кластеров ceph, которые используются под различные задачи. Здесь практически нет информации о cephfs, потому что мы её почти не использовали. Описанных в статье практик должно хватить для хорошего фундамента независимо от кейса.
Если у вас есть возможность инструментировать клиентов кластера – делайте так. Оценивать латенси запросов и количество ошибок на стороне клиента может оказаться гораздо проще и даст более полную картину происходящего, чем использование встроенных в цеф метрик. Избегайте алертинга по причинам, стройте алертинг по симптомам.