Pull to refresh
699.53
Конференции Олега Бунина (Онтико)
Конференции Олега Бунина

Как сделать централизованное логирование и крепко спать по ночам

Level of difficultyMedium
Reading time12 min
Views15K

Привет, Хабр! На связи Филипп Бочаров, руководитель платформы наблюдаемости и мониторинга для более 400 продуктов экосистемы МТС, и Юлия Тальцкова, ведущий инженер сервиса логирования и кластеров Open Search с более 400 терабайтами логов клиентов. Этот материал написан на основе нашего доклада для конференции Highload++.

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

Чтобы было понятнее, с какими трудностями столкнулись, поделимся некоторыми цифрами экосистемы.

Сервис логирования — это:

  • Более 200 продуктов-потребителей, которые в этот сервис пишут и потом читают логи.

  • Около 3 000 активных пользователей — разработчики, тестировщики, архитекторы и любые другие роли, которые занимаются качеством продукта.

  • Мы гарантируем доступность 99.95% и должны решать критические проблемы за два часа в режиме 24/7.

  • Наши продукты генерируют 400 тысяч документов в секунду, а объём хранения давно превысил 400 терабайт.

Но мы не всегда оперировали такими объёмами.

Старая архитектура

Наша архитектура на старте выглядела так:

Мы начинали с обычного ELK-стека, логи приходили на logstash, записывались в Elasticsearch, а пользователи смотрели их в Kibana. Потом в эту схему добавилась Kafka, так как мы понимали, что на пиках нагрузок не успеваем записать все логи в Elasticsearch. Всё это располагалось в одном ЦОДе, а в Kafka была единая очередь. В результате горизонтального масштабирования Elasticsearch разросся до 30+ нод. Данная схема справлялась с нагрузкой в 100 тысяч документов в секунду.

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

Предпосылки новой системы

В старой схеме, еще до роста нагрузки нас смущали такие моменты:

Влияние продуктов друг на друга. Мы не могли гарантировать определённые SLA крупным потребителям. Ведь один продукт мог влиять на другой. Если у кого-то начинались проблемы и генерировалось огромное количество логов, все остальные начинали страдать. 

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

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

Лицензионные ограничения Elastic (Apache 2.0 -> SSPL 1.0). Также Elasticsearch изменил лицензионную политику — мы больше не могли предоставлять его как сервис в облаке.

Идея для новой архитектуры

Мы с командой придумали концепт новой архитектуры, реализующей такие идеи:

Переход с Elasticsearch на OpenSearch — чтобы убрать все лицензионные риски.

Множество небольших кластеров. Elasticsearch разрастался, в нём появлялось всё больше нод. Нужно было нарезать его на части. Поэтому в новой архитектуре мы предусмотрели много небольших кластеров OpenSearch, развёрнутых в разных ЦОДах и объединённых в единую систему.

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

Географическая распределенность. Экосистема МТС географически распределённая, с ЦОДами по всей России. Гонять логи из Владивостока в Москву и забивать сетевые каналы — неразумно. Поэтому мы хотели добиться, чтобы логи записывались в ближайший ЦОД, желательно в тот же, в котором эти логи породил продукт.

Верхнеуровнево новую схему представили так:

В ЦОДе расположен кластер OpenSearch, в который идёт запись логов. Данные кластера подключены к одному-единственному Search-кластеру, на который приходят пользовательские запросы. Search-кластер геораспределённый, его ноды расположены в разных ЦОДах.

Более подробная схема:

Видно, что компоненты логирования (Logstash, Kafka, Logstash Indexer и OpenSearch Dashboards) расположены в каждом ЦОДе. За счёт функции cross-cluster search пользовательские запросы приходят на дата-кластеры и поиск по данным логам происходит бесшовно для пользователя.

Шумные соседи

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

В нашей многопользовательской системе тоже есть проблема шумного соседа. В любой момент может найтись продукт, который по каким-то причинам начинает генерировать тонны логов.

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

  • Дропы на входящих logstash, если пропускная способность всего инстанса не позволяет записать в него.

  • Рост очереди логов (lag) в Kafka, из-за чего все клиенты получат задержку в отображении своих логов.

  • Падение hot-нод OpenSearch. Если мы попытаемся увеличить количество logstash indexer, которые записывают в OpenSearch, то могут сработать механизмы защиты Circuit Breakers из-за повышенной нагрузки и hot-ноды OpenSearch начнут выпадать из кластера.

  • Замедление вставки в OpenSearch. Довольно легко сформировать логи, которые не будут записаны в OpenSearch и упадут с ошибкой. Достаточно поменять тип поля с объектом на текст или превысить лимит по полям, и это замедлит запись логов для всех продуктов.

Вот как это было реализовано ранее:

В Kafka одна очередь для всех продуктов и определённое количество партиций. Также logstash indexer вычитывает это в единственный пул logstash. Схема проста в реализации, но подвержена взаимному влиянию. С этим можно бороться, вводя изоляцию продуктов между собой.

Идеальная изоляция

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

  • Пул logstash.

  • Кластер Kafka.

  • Кластер OpenSearch.

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

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

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

Появилось много новых движущихся частей, топиков, пулов logstash. Кроме того, схема очень динамичная, нагрузка — тоже, в течение суток может меняться поток логов от продуктов — как снижаться, так и расти. Надо научиться тюнить систему под постоянно меняющийся поток автоматически. Невозможно посадить инженера, чтобы он 24/7 следил за ней и вручную чинил.

Начать автоматизацию этой задачи нужно с метрик: собирать их со всех компонентов, чтобы понимать, какая нагрузка на эти компоненты приходится. Для этого мы развернули кластер Victoria Metrics и собираем туда метрики всех компонентов. В первую очередь — Kafka, чтобы отслеживать поток логов через каждый топик. После этого можно переходить непосредственно к автоматизации.

Тюним и автоматизируем:

Подбор количества партиций для каждого топика в Kafka. Если мы хотим оптимально записывать и читать данные из Kafka, то количество партиций должно быть пропорционально потоку. Этим у нас занимается самописный джоб, который назвали AutoPartition Job. Он следит за изменением метрики в Victoria Metrics и подстраивает количество партиций под поток каждого топика — больше поток, больше партиций.

Масштабирование logstash. Увеличив количество партиций, нам нужно увеличить и количество консьюмеров, иначе это не имеет смысла. Консьюмер — это logstash indexer, вычитывающий эти логи. За их масштабирование отвечает KEDA. KEDA — это event-driven фреймворк для Kubernetes, позволяющий масштабировать количество реплик в зависимости от внешних условий. В данном случае такими условиями является метрика в Victoria Metrics. Больше нагрузка — большее количество инстансов, уменьшилась нагрузка — меньше инстансов.

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

Ночные кошмары

Потревожить сон наших инженеров могут ночные кошмары. Представьте: полночь, они спят, всё тихо, и тут…

Начинают падать все logstash по очереди, разрываются соединения с потребителями, мы начинаем терять логи и трафик, видим ошибки 4xx и 5xx, жалобы и инциденты.

Оказалось, что причиной этого кошмара стали логи в некорректном формате!.

Чтобы совсем не остаться без логов, нам пришлось отключать половину клиентов по IP на Ingress и смотреть, помогло ли это. В итоге клиента, который отправлял данные сообщения, нашли. Но осознали, что это не дело: агент, получающий логи, не должен падать ни при каких условиях. Мы решили его заменить.

Выбрали агент Vector от DataDog, написанный на Rust. У него обширная библиотека плагинов и высокая производительность. Правда, пришлось переписать pipeline обработки логов на Vector Remap Language. Также во время переходного периода два агента работали вместе. Сначала мы подали небольшую нагрузку на Vector, убедились, что всё обрабатывается корректно, после чего отказались от logstash полностью. Наконец, пришлось переделать весь мониторинг, потому что метрики у Vector свои, но они есть в формате Prometheus, а готовый дашборд для Grafana есть из коробки.

Внимательный читатель заметит: на схеме logstash indexer, который вычитывает логи из Kafka и записывает их в OpenSearch, мы так и не заменили. На это было несколько причин:

  • Ситуация, когда мы получаем некорректные логи, которые не могут быть обработаны logstash, уже исключена. Мы можем их предобработать на Vector и положить в Kafka уже готовый в нужном формате.

  • Мы также самостоятельно написали плагин logstash для контроля квот, о котором расскажем дальше и переделывать его под vector не хотелось.

Дополнительным плюсом перехода с Logstash на Vector стала значительная экономия ресурсов. Там, где logstash нужно до 32Gb оперативной памяти, Vector достаточно и четырёх. В результате мы сэкономили 120 CPU и 1800 гигабайт RAM без потери производительности и качественной наблюдаемости этого стека — просто за счёт замены одной технологии на другую.

В тесноте и обиде

Вы когда-нибудь пробовали выспаться на гостевом диване под куцым одеяльцем, когда чем-то приходится жертвовать — то ноги свешиваются, то уши мерзнут? 

Так было и с нашей многопользовательской системой. Мы выросли, а наша инфраструктура — диван, на котором лежим, — не поспевала за нами. Продуктов становилось всё больше, они хотели писать всё больше логов. Объёмы хранения постоянно росли, мы постоянно сталкивались с заканчивающимся местом на SSD-дисках. А OpenSearch довольно чувствительный, у него всегда должен быть запас в 20−30%.

То есть, объёмы хранения постоянно росли, SSD-диски — дорогие, а регулятор хочет хранить логи годами.

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

В классическом понимании жизненный цикл логов — это перекладка из горячего хранилища в тёплое, а потом в холодное.

Hot. На первом этапе наш самописный ISM Policy Job реализует хранение на hot-слое OpenSearch. В данном слое на 32Gb оперативной памяти на нодах приходится 1Tb SSD, и ротация логов происходит по 10Gb или 10 дней. Так как конкретные суммы стоимости мы не можем называть, будем измерять хранение в «попугаях». Так вот, хранение одного терабайта логов в hot слое обходится в один попугай.

Warm. Тоже SSD-диски, подключенные к нодам OpenSearch. Здесь на 10 гигабайт оперативной памяти приходится 1 терабайт SSD. Это стоит нам уже половину попугая.

Cold. За счёт этого слоя пытались решить проблему с заканчивающимся местом на SSD дисках. Мы подключили корпоративный S3 к нашим кластерам OpenSearch, создали снапшоты и за счёт функции searchable snapshot восстановили индексы на отдельном snapshot-кластере. В итоге, логи лежат на S3, но поиск для пользователя по ним происходит бесшовно. Такое хранение обходится нам дешевле всего - всего 1/20 попугая за 1 TB.

Delete. Удаление происходит по объёму или по настроенному времени.

За счёт такой схемы удалось перенести более 100 терабайт на SSD и сэкономить приличную сумму. При этом хранение в S3 у нас получилось практически в 20 раз дешевле, чем на hot-слое.

Принимаем закон «о тишине»

Возвращаемся к аналогии про многоквартирный дом: все мы знаем, что ночью шуметь нельзя. Есть закон о тишине, СанПиНы и громче 30дБ шуметь не стоит. Также и нашей многопользовательской системе нужны определённые законы, ограничения, система квот, чтобы продукты не пытались получить больше ресурсов, чем мы готовы им выделить. Без квот платформе жить нельзя, и вот почему:

  • Вы не можете гарантировать потребителям какие-либо SLA в условиях, когда любой продукт в любой момент может начать потреблять в 10−20 раз больше ресурсов.

  • Отсутствие квот всегда ведёт к нерациональному использованию. Поставьте себя на место продукта. Конечно, проще запросить в два раза больше ресурсов, чем заниматься скучными задачами по оптимизации, подбору правильного уровня логирования и тому подобному.

Осталось понять, какие конкретно квоты и на какие ресурсы нужны. Мы свой рецепт писали по результатам аварий:

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

Объём хранения. С учётом заканчивающегося места на SSD дисках пришлось ввести квоту и на объём хранения.

Одни и те же квоты можно сформулировать по-разному. Мы пришли к тому, что основное требование — это прозрачность как для пользователя, так и для нас как платформы.

С точки зрения пользователя, прозрачность — это понятность. То есть квота, все правила её вычисления должны быть чётко описаны и измеряться в понятных для пользователя величинах. Ещё нужны дашборды, с помощью которых продукт всегда может отслеживать, сколько ресурсов потребляет — выше квоты или ниже. Так он может в режиме реального времени понимать, как его изменения и оптимизации влияют на потребление.

Пример таких дашбордов:

Слева дашборд по потреблению диска, справа — по документам в секунду. Видно, что наш продукт немного не укладывается в квоту по диску и иногда в пиковой нагрузке превышает квоту по документам в секунду.

С точки зрения платформы нужно учитывать все нюансы. Например, можно описать квоту не в документах в секунду, а в мегабайтах в секунду Но документы могут быть разными по размеру. Это может быть несколько килобайт, а может быть и несколько мегабайт. Но продукту, который отправляет логи, будет сложно оперировать такой квотой и определить, какой сервис и почему отправляет такой объём. Поэтому иногда нужно остановиться и не переусложнять систему.

Как вычислить конкретные цифры квот:

У нас есть ресурс, который мы распределяем между всеми продуктами. Квота на документы в секунду ограничена максимальной пропускной способностью нашей системы. У нас она составляет 400 тысяч документов в секунду. Мы могли бы раздать всем поровну: каждому из 400 продуктов по 1 000. Но продукты не равны. Есть те, которые «равнее». Поэтому в нашей экосистеме мы разделили продукты на три класса:

  1. Business support продукты, которых больше всего и которые наименее важны.

  2. Более важные business critical продукты.

  3. Mission critical продукты, без которых экосистема существовать вообще не может — их меньше всего.

Мы хотим выдавать ресурсы с приоритетом на mission critical продукты. Поэтому пришли к пропорции, в которой mission critical продуктам выделяется примерно в четыре раза больше ресурсов, чем business support продуктам.

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

Если всё это подставить в виде формулы, то получится, что business support продуктам мы раздали примерно по 680 документов в секунду, а mission critical — в четыре раза больше, то есть около 3 тысяч документов в секунду.

Применение квот 

Теперь расскажу, как технически применить эту квоту и ограничить продукты, не дав им записать больше, чем нужно.

Можно организовать rate limit на входящие точки в платформу (на балансировщике, Ingress, Nginx). Но мы заранее не подумали об этом и не включили в контракт с клиентом ID клиента в заголовок HTTP-запроса. Поэтому нам нужно было бы распарсить сам документ и достать оттуда ID клиента. На наших объёмах это довольно ресурсозатратно.

Кроме этого, данные приходят к нам батчами для ускорения производительности и теоретически в батче могут содержаться логи разных продуктов. Также, в случае rate limit на входе, если клиент, который отправляет логи, не реализовал у себя отслеживание квот или переповторы, то это может привести к потере логов.

Мы пошли другим путём и реализовали квоту на уровне вычитки из Kafka.

Для этого написали плагин для logstash indexer. Он сверяет метрики потребления из Victoria Metrics Kafka с квотой и в зависимости от этого добавляет задержку на вычитку и запись в OpenSearch.

Сравнение реализаций

У нас есть две конкурирующие схемы. Мы можем ограничивать трафик с помощью rate limit или на этапе вычитки. Обе схемы имеют свои плюсы и минусы. Если вы делаете что-то похожее, сначала надо понять, что вашим продуктам ближе.

Rate limiter: 

  • Готовая реализация.

  • Режем трафик в самой ранней точке и не нагружаем остальные компоненты цепочки.

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

Вычитка:

  • Проще логика на стороне продукта. Логи в любом случае запишутся в платформу. Другое дело, что они могут какое-то время лежать в очереди Kafka, но не потеряются.

  • Один механизм для всех протоколов. Это универсальный механизм: неважно, по какому протоколу, каким способом логи записаны в платформу — по HTTP или напрямую в Kafka. Ограничение всё равно отработает на наиболее поздних этапах.

  • Накопление лага в Kafka. Если продукт длительное время посылает выше квоты, а мы вычитываем меньше, соответственно, у нас накапливается очередь, продукт начинает терять онлайновость логов. Выбор схемы зависит от того, что продукту важнее — онлайновость логов или их гарантированная доставка.

Выводы 

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

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

  • Ищите альтернативы и считайте их стоимость. Если вы делаете архитектурные преобразования, оценивайте их в денежном эквиваленте. Это поможет объяснить бизнесу, зачем вы это делаете. Бизнесу непонятно, зачем мы переходили c logstash на Vector — для них это два непонятных слова. Но когда мы показали экономию в ресурсах и в деньгах, всё стало ясно.

  • Заранее продумывайте систему квот, простую и прозрачную. Если вы делаете многопользовательскую платформу, то система квот понадобится. Заранее подумайте, как вы будете её делать, заложите это в дизайн, потому что, по нашему опыту, вводить её постфактум очень больно. Когда вы раздали людям ресурсы, выдали возможности, а потом пытаетесь закрутить гайки, что-то отобрать и порезать, это всегда вызывает много негатива.

  • Автоматизируйте задачи обслуживания. Это самый тривиальный совет за весь HighLoad++. Обычно задачи, которые нужно автоматизировать, имеют тенденцию расти как снежный ком. Поэтому за ними надо следить, поддерживать в тонусе и максимально автоматизировать, чтобы не нагружать текучкой инженеров.

Tags:
Hubs:
+57
Comments15

Articles

Information

Website
www.ontico.ru
Registered
Founded
Employees
11–30 employees
Location
Россия