Привет! Хочу поделиться историей миграции сервисов логирования и трейсинга с компонентов Elastic Stack на Grafana Stack и тем, что из этого вышло. До миграции у нас в М2 использовались достаточно классические схемы:

  • Logstash → Elasticsearch → Kibana для логов;

  • Jaeger → Elasticsearch → Kibana (Jaeger UI) для трейсов.

Это вполне рабочий вариант, который устраивал нас первые полтора года жизни проекта. Но время шло, микросервисы плодились как грибы после дождя, рос и объем клиентских запросов. Расширение ресурсов систем логирования и трейсинга становилось все более частой задачей. Объемов хранилищ и вычислительных мощностей требовалось все больше. Вдобавок лицезии X-Pack еще сильнее толкали ценник вверх. Когда замаячили проблемы с приобретением лицензий и доступом к самим продуктам компании Elastic, стало понятно, что дальше так жить нельзя.

В процессе поиска лучшего решения были перепробованы разные варианты компонентов, написаны Kubernetes-операторы и собрано два ведра шишек. В конечном итоге схемы приобрели следующий вид:

  • Vector → Loki → Grafana;

  • Jaeger → Tempo → Grafana.

Так удалось объединить три важнейших аспекта мониторинга: метрики, логи и трейсы — в одном рабочем пространстве Grafana и получить от этого ряд плюшек. Основные из них:

  • сокращение объема дискового пространства при тех же объемах данных;

  • сокращение объема вычислительных ресурсов для работы системы;

  • отсутствие необходимости покупки лицензии;

  • свободный доступ к продукту;

  • достаточно простая настройка механизма автомасштабирования системы.

Надеюсь, эта статья будет полезна как тем, кто только выбирает систему логирования/трейсинга, так и тем, кто имеет схожие сложности.


Предпосылки миграции. Logging

Шел 2021 год, и, как упоминалось выше, мы использовали достаточно стандартную схему централизованного логирования.

В сутки набегало порядка 1 ТБ логов. Кластер состоял примерно из 10 Elasticsearch data-нод, была приобретена лицензия X-Pack (главным образом, для доменной авторизации и алертинга). Приложения в основном были развернуты в кластере Kubernetes. Для их отправки использовался Fluent Bit, далее Kafka в качестве буфера и пул Logstash под каждый namespace. В процессе эксплуатации системы мы сталкивались с различными проблемами: некоторые решались достаточно просто, для части подходил только workaround, а какие-то решить и вовсе не удавалось. Совокупность второй и третьей групп проблем побудила нас к поиску другого решения. Прежде перечислю наиболее значимые из этих проблем.

1. Потери логов при сборе

Как ни странно, первым компонентом, с которым начались проблемы, стал Fluent Bit. Время от времени он просто переставал отправлять логи отдельных подов. Анализ дебаг-логов, тюнинг буферов и обновление версии к желаемому эффекту не приводили. В качестве замены был взят Vector, который, как позже выяснилось, тоже имел подобные проблемы. Но это было исправлено в версии 0.21.0.

2. Костыли с DLQ

Следующей неприятностью стало вынужденное использование костылей при включении DLQ на Logstash. Дело в том, что Logstash не умеет сам ротировать логи, попадающие в эту очередь, и в качестве почти официального workaround предлагалось просто рестартовать инстанс после достижения порогового объема. Негативно на работу системы в целом это не влияло, так как в качестве input использовалась Kafka и сервис завершался в graceful-режиме. Но видеть постоянно растущее число рестартов подов было так себе, да и за рестартами иногда маскировались другие проблемы.

3. Боли с написанием алертов

Не слишком удобное описание правил алертинга. Можно, конечно, накликать через веб-интерфейс Kibana, но удобней все же через описание через код, как, например, в Prometheus. Синтаксис достаточно неочевидный и вырвиглазный, вот пример:

{

 "params":{

   "aggType":"count",

   "termSize":5,

   "thresholdComparator":"<",

   "timeWindowSize":15,

   "timeWindowUnit":"m",

   "groupBy":"all",

   "threshold":[50],

   "timeField":p",

   "index":["app-common*"]

 },

 "consumer":"alerts",

 "schedule":{

   "interval":"5m"

 },

 "tags":[],

 "name":"app-common",

 "enabled":true,

 "throttle":"1h",

 "rule_type_id":".index-threshold",

 "notify_when":"onActionGroupChange",

 "actions":[

   {

     "group":"threshold met",

     "id":"378045c0-2101-11ec-83cd-97f03e582f14",

     "params":{

       "level":"warning",

       "message":"There is low log rate for 15 minutes in {{rule.name}}:\n\n- Value: {{context.value}}\n- Conditions Met: {{context.conditions}} over {{params.timeWindowSize}}{{params.timeWindowUnit}}\n- Timestamp: {{context.date}}"

     }

   }

 ]

}

4. Потребление ресурсов

Но основные проблемы были связаны с увеличением стоимости потребляемых ресурсов и, как следствие, стоимости системы. Когда цена перевалила за полмиллиона в месяц, мы стали все чаще задумываться над поиском альтернатив. Самыми прожорливыми компонентами оказались Logstash и Elasticsearch, ведь JVM, как известно, неравнодушна к количеству памяти. 

5. Доступность продукта

Последней каплей стало ограничение доступа к продуктам Elastic и будущая невозможность покупки лицензии. Конечно, есть различные зеркала, но сколько они будут доступны и как часто там будут появлятся новые версии, неизвестно. Можно придумать и схему закупки лицензий через какой-нибудь параллельный импорт, но цена возрастет.

Tracing

Для централизованного сбора трейсов использовался Jaeger, который через Kafka отправлял данные в отдельный кластер «Эластика». Трейсов меньше не становилось, и приходилось масштабировать систему под сотни гигабайт трейсов ежедневно. Схема выглядела следующим образом:

Общим неудобством было также использование разных веб-интерфейсов под различные аспекты мониторинга:

  • Grafana — для метрик;

  • Kibana — для логов;

  • Jaeger UI — для трейсов.

Конечно, Grafana позволяет подключить в качестве источника данных и Elasticsearch, и Jaeger, но остается необходимость манипулировать различными синтаксисами запросов к данным.

С таких предпосылок начался наш поиск новых решений для логирования и трейсинга. Не то чтобы мы проводили какой-то сравнительный анализ различных систем. Сейчас на рынке из серьезных продуктов представлены в основном те, что требуют лицензирования, и с этим в любой момент могла возникнуть проблема. Поэтому мы решили взять opensource-проект Grafana Loki, который уже был успешно внедрен в ряде компаний, а затем очередь дошла и до Tempo.

Миграция в Loki

Итак, почему был выбран именно Loki

  • это opensource-продукт, позволяющий реализовать все необходимые нам фичи;

  • компоненты Loki потребляют ощутимо меньше ресурсов при тех же нагрузках;

  • все компоненты могут быть запущены в кластере Kubernetes, можно использовать HPA или Keda для их автоматического масштабирования;

  • данные занимают в несколько раз меньше места, так как хранятся в сжатом виде;

  • построение запросов очень схоже с языком PromQL;

  • описание алертов аналогично алертам в Prometheus;

  • ну и конечно, система отлично интегрирована с Grafana для визуализации сообщений и построения графиков.

В качестве способа развертывания был выбран Distributed helm-chart, а в качестве хранилища чанков — Object Storage. Logstash был заменен на более легковесный Vector. На небольших объемах (несколько сотен сообщений в секунду) система вполне работоспособна из коробки. Логи успевают сохраняться практически в режиме realtime, поиск свежих данных работает почти также быстро, как и в Kibana. Получившаяся схема выглядит следующим образом:

Тюнинг настроек

С увеличением нагрузки начинает страдать цепочка записи. То ingester, то distributor начинают дропать логи, возвращать таймауты и т. д. А при запросах данных, отсутствующих в кэше ingester-ов, ожидание ответа начинает приближаться к минуте или вовсе падать по таймауту. На всякий случай приведу схему того, как выглядит инсталляция Loki через Distributed chart.

В случае проблем с записью стоит обратить внимание на параметры:

limits_config.ingestrion_burst _size_mb
limits_config.rate_mb

Они отвечают за пропускную способность, и при приближении трафика к пороговым значениям сообщения будут отбиваться.

Для увеличения скорости поиска стоит использовать кэширование в Memcached или Redis. Также можно поиграть с параметрами:

limits.config.split_queries_by_interal
frontend_worker.parallelisim

В Loki может использоваться кэширование четырех типов данных:

  • чанки;

  • индексы;

  • ответы на предыдущие запросы;

  • кэширование данных для нужд дедупликации.

Стоит использовать как минимум первые три.

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

Что касается правил алертинга, пишутся они аналогично правилам Prometheus.

- alert: low_log_rate_common

  expr: sum(count_over_time({namespace="common"}[15m])) < 50

  for: 5m

  labels:

      severity: warning

      annotations:

        summary: Count is less than 50 from {{ $labels.namespace }}. Current VALUE = {{ $value }}

Для доступа к сообщениям используется язык запросов LogQL, который схож по синтаксису с PromQL. В части визуализации все выглядит примерно как в Kibana.

Очень удобной фичей является возможность настроить ссылки из лог-сообщений к соответствующим трейсам. В этом случае при нажатии на ссылку в правой половине рабочей области открывается трейс.

Миграция в Tempo

Tempo — более молодой продукт (появился в 2020-м), имеющий очень схожую с Loki архитектуру (в целом, как и Mimir). В качестве способа развертывания был выбран Distributed helm-chart.

Дистрибьюторы имеют возможность подключаться к Kafka напрямую, но в таком виде не удалось добиться скорости вычитывания, соразмерной скорости записи. С использованием Grafana agent удалось эту проблему решить.

Тюнинг настроек

В процессе конфигурирования следует уделить внимание следующим моментам:

  1. Если используется Jaeger, то спаны от Grafana agent в Distributor будут отправляться по GRPC. В кластере Kubernetes равномерное распределение по дистрибьюторам потребует настройки балансировщика, например Envoy (как вариант, через Istio).

  2. В случае проблем со скоростью записи стоит увеличить параметры: 

    1. overrides.ingestion_burst_size_bytes

    2. overrides.ingestion_rate_limit_bytes

    3. overrides.max_bytes_per_trace

  3. Также можно увеличить таймаут ожидания ответа от ingester — ingester_client.remote_timeout. Они не всегда успевают ответить за 5 секунд.

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

Заключение

От первичной инсталляции Loki в dev-среде до внедрения на production прошло около двух месяцев. Еще столько же потребовалось для Tempo. 

В итоге получилось:

  • уменьшить стоимость систем примерно примерно в 7 раз;

  • уйти от возможных проблем с лицензией;

  • совместить визуализацию метрик, логов и трейсов в единой системе;

  • организовать алертинг аналогичным Prometheus образом;

  • настроить автомасштабирование системы в зависимости от объема поступающих данных.

Справедливости ради, стоит отметить и то, чего добиться пока не удалось:

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

  • получения агрегированных значений за большой промежуток времени (больше суток), например, подсчет числа лог-сообщений функцией count_over_time;

  • производительности в сохранении спанов как в схеме с Elasticsearch. Пока удалось решить горизонтальным масштабированием и увеличением числа партиций в Kafka.

В целом, можно сказать, что опыт перехода на Grafana Stack оказался удачным, но процесс совершенствования схем логирования и трейсинга не заканчивается.

Комментарии (10)


  1. Rumantic
    19.10.2022 14:44
    +2

    Kafka устраивает полностью, никуда от нее не хотите мигрировать?


    1. javdet12 Автор
      20.10.2022 12:07
      +2

      Этот компонент системы пожалуй единственный, с которым проблем не было, поэтому нет


      1. Rumantic
        20.10.2022 12:13
        -1

        Спасибо, я тоже сейчас платформу недвижимости разрабатываю, пришло время выбрать брокера сообщений, значит Kafka будет )


  1. usego
    19.10.2022 15:02
    +2

    Так ли был нужен xpack?


    1. javdet12 Автор
      20.10.2022 12:12

      Пробовали использовать opendistro, но не всех фич в нем хватало (с ходу могу вспомнить отсутствие автокомплита в кибане, были некоторые сложности с ISM).


  1. slava_k
    19.10.2022 16:04
    +1

    Спасибо за статью, часть тех же проблем пришлось пройти недавно. Vector в целом хорош, но всё равно сырой в плане долгосрочной стабильности.


    1. javdet12 Автор
      20.10.2022 12:14
      +2

      Да возможно, но за последние пол года как минимум, в нашей схеме работает хорошо. В планах есть написать оператор для него на подобии https://banzaicloud.com/docs/one-eye/logging-operator/.


  1. meforyou
    21.10.2022 12:19

    Добрый день! Полнотекстовый поиск вы не используете? все чаще замечаю, что те кто отказывается от ElasticSearch его не используют.

    Не могли бы рассказать как тюнинговали Elastic stack?


    1. javdet12 Автор
      21.10.2022 15:31

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

      Тема тюнинга Elastic stack потянет на отдельную статью) Если есть, какие то более конкретные вопросы могу что-то подсказать.


  1. past
    22.10.2022 11:23

    Есть ощущение, что если вам нужен count_over_time по логам, то пора отобразить эту величину в метриках. Мы же с вами помним, что метрики это сжатые логи?