Привет. Меня зовут Григорий Рочев. В последнее время столкнулся по работе с Grafana agent. Начал разбираться с ним, как он работает под капотом и в чем его отличия от того, с чем я до этого работал (vmagent, netdata и prometheus). И снова на глаза попалась статья о том, нужен ли WAL для TSDB? И зародилась идея, рассмотреть подходы как агенты сбора метрик защищаются от потерь данных при работе с remote write протоколом на примере Grafana agent/Prometheus и VictoriaMetrics agent (vmagent)
Prometheus Agent и Grafana agent
Начнем издалека.
Чтобы разобраться, с базовыми понятия в Prometheus, такими как Metrics, Labels, Time Series, Scraping, советую заглянуть сюда.
Сэмплы (Samples) хранятся в сжатых юнитах, называемыми «чанками» (chunk). Когда сэмплы собираются Prometheus, они сохраняются в память, после чего, для фиксации сэмплов в чанках, делается запись в журнал упреждающей записи (WAL). По сути это запись в page cache и каждую минуту делается вызов fdatasync() для записи на диск содержимого значений page cache WAL. WAL необходим для того, чтобы можно было восстановить данные в память из него, даже если машина внезапно выйдет из строя. Но нужно учитывать, что в случае выхода из строя сервера (виртуальной машины), все записи, которые не успели записаться на диск - будут утеряны.
Как только чанк заполняется до 120 сэмплов или достигает chunkRange (по умолчанию составляет 2 часа), создается новый чанк, а старый чанк считается «заполненным».
Начиная с Prometheus v2.19.0, все чанки не хранятся в памяти. Как только создается новый чанк, заполненный чанк сбрасывается на диск и отображается в память с диска, при этом в памяти сохраняется только ссылка (вызов mmap). Что позволяет уменьшить потребление памяти на сервере.
Если интересно разобраться как это работает более подробно, советую посмотреть данное выступление (картинка была взята оттуда же).
Так как Prometheus не разрабатывался в long-term storage (долгосрочного хранения), поэтому если нужен хранить метрики на долгий период времени, нужно собранные метрики как-то доставить до данного стораджа. Есть разные способы достичь этого, но в данной статья я буду говорить только про remote write способ. Если интересно более подробно прочитать, что такое remote write, советую посмотреть/почитать о нем здесь.
В Prometheus для каждого remote write создается in-memory очередь, в которую записываются samples из журнала (WAL). Данные в очереди хранятся в течение двух часов, и это поведение не изменить. Начиная с Prometheus v2.8.0, данные отправляются на удаленный сторадж сразу же, как будет сделана запись в WAL. В случае, если по какой-то причине удаленный сторадж будет недоступен, Prometheus будет ожидать до тех пор, пока не появится связанность с удаленных стораджем, чтобы начать отправку неотправленных данных. Но есть ограничение, те же злополучные 2 часа, если за это время удаленный хост не будет доступен - данные потеряются.
А начиная с Prometheus v2.32.0 появляется возможность использовать фичу agent mode. Это по-сути тюнинг Prometheus для remote write - отключая такие вещи как использование локального стораджа для хранения time series, поиск, алертинг. Его основная задача собирать и отправлять сэмплы.
Вот так мы приходим к Grafana Agent. По сути своей Prometheus agent mode - это как grafana agent работает со сбором и отправкой метрик (с некоторыми нюансами). Так как именно Grafana была инициатором и контрибьютором данной фичи. Все ограничения описанные выше при работе с метриками - также относятся и к grafana agent.
Вообще, grafana agent - это такой комбайн, который умеет собирать и метрики, и логи, и трейсы. Но в данной статье сконцентрируемся только на метриках.
Соответственно, чтобы не потерять данные, при падении экземпляра Grafana Agent/Prometheus, нужно иметь как минимум по два экземпляра на одни и теже таргеты.
А что насчет шардирования? Да оно есть, вот как предлагается сделать это в Prometheus operator , т.е. администратору/devops/sre/инженеру (нужное подчеркнуть), необходимо вручную определиться, по каким лейблам будут распредяляться шарды.
Grafana agent же предлагает три варианта горизонтального масштабирования: Host Filtering, Hashmod sharding и Scraping service.
Scraping service (он же Instance sharing) - это тот же вид шардирования, что и в Prometheus operator.
Hashmode sharding - сегментирование происходит с помощью пары правил hashmod/keep relabel. Эти правила будут хешировать адрес таргетов и объединять его по модулю с количеством запущенных сегментов агента. Но важно, он не консистентный алгоритм хэширования. Что означает, что изменение количества шардов может привести к перемещению любого количества таргетов на новый шард, вплоть до 100%. И при переходе на новый сегмент любые существующие данные в WAL со старого шарда будут отброшены.
Host Filtering - просто daemonset’ом запускаем экземпляр на каждой машине, и только на этой машине осуществляется сбор таргетов. Идея интересная. На сайте графаны из минусов пишут, что может таким способом нагрузить kube-apiserver и remote write storage. Но я вижу еще два минуса: нужно проставить priorityClassName, чтобы кубелет ненароком не прибил столь важный под на ноде и в случае если таки grafana agent по какой-то причине рестартанет/упадет, он не сможет собирать метрики с данной ноды пока агент не поднимется, не перечитает WAL и сохранит данные в память. В этотмомент мы не будем знать о сотоянии ноды и на графиках у нас будут "пропуски". Как долго он будет подниматься, зависит от количества time series.
VictoriaMetrics Agent (vmagent)
Важным отличием vmagent от Grafana Agent/Prometheus - отсутствие WAL. Почему разработчики VictoriaMetrics считают, что для современных time series database он не нужен можно прочитать здесь.
А как в итоге реализована персистентность данных, например для недоступности remote write storage или если процесс vmagent рестартанет? Для этого в vmagent есть PersistentQueue (file-based buffer size for unsent data). Сэмплы которые собираются с таргетов сохраняются в чанки, а те в очередь в памяти. После чего периодически сохраняются данные на диск, также периодически происходит корректировка слишком больших чанков. В случае если после падения сервера или процесса будут битые чанки - vmagent пропустит такие чанки и это не помешает стартовать процессу и работать в штатном режиме.
Т.е. как и в случае с Grafana Agent/Prometheus, при некорректном завершении работы сервера, vmagent тоже может потерять данные - так как не сразу происходит fsync данных на диск. Но, при этом, за счет того, что при старте vmagent нет необходимости перечитывать WAL - это позволяет vmagent’у стартовать быстрее и использовать меньше памяти.
А что насчет шардирования? Оно есть и работает в кластерном режиме vmagent, распределяя нагрузку между всеми экземплярами vmagent автоматически, без необходимости вмешательства администратора. Можно также разбивать по scraping service как это реализовано в Grafana Agent/Prometheus.
Если интересно узнать больше о vmagent, советую почитать/посмотреть данный доклад.
Заключение
В данной статье, лишь прошелся по тем опенсорсным решениям для работы с remote write протоколом поддерживающим API Prometheus, с которыми работал. Хотел бы указать на то, что наличие WAL - не гарантирует, что ваши данные при скрейпе не будут потеряны. Это стоит учитывать в работе и при выборе агента. Вообще, еще множество таких приложений, например, те же telegraf, netdata или vector.io, которые умеют работать с данным протоколом.