Prometheus server настраивается через два места: жопу и голову аргументы запуска и конфигурационный файл. Настройку через переменные окружения в Prometheus не завезли и не планируют.
Это зависит от того, как вы будете их использовать. В идеале надо иметь два набора метрик: краткосрочные на пару недель и долгосрочные на несколько месяцев. Первые нужны, чтобы оперативно отслеживать ситуацию, вторые — чтобы видеть тенденцию на больших промежутках времени. Но это большая тема, заслуживающая отдельной статьи, а пока сделаем по-простому. Длительность хранения метрик указывается в аргументах запуска Prometheus. Есть две опции:
--storage.tsdb.retention.time=... определяет как долго Prometheus будет хранить собранные метрики. Длительность указывается так же, как и в PromQL для диапазонного вектора: 30d — это 30 дней. По умолчанию метрики хранятся 15 дней, потом исчезают. К сожалению нельзя одни метрики хранить долго, а другие коротко, как, например, в Graphite. В Prometheus лимит общий для всех.
--storage.tsdb.retention.size=... определяет сколько дискового пространства Prometheus может использовать под метрики. По-моему эта опция удобнее предыдущей: можно указать всё свободное место на диске и получить настолько долгие метрики, насколько это возможно при любом их количестве. На практике однако нельзя указывать свободное место впритык. Дело в том, что у БД метрик есть журнал упреждающей записи (WAL), который в этом лимите не учитывается. Я не понял от чего зависит максимальный размер WAL, поэтому какие-то рекомендации по запасу не могу дать. У меня максимальный размер WAL был 8 ГБ.
Если вы устанавливали Prometheus по моей инструкции без докера, то аргументы запуска вам надо прописать в /etc/systemd/system/prometheus.service в параметре ExecStart, после чего выполнить две команды:
scrape_interval — периодичность опроса экспортеров (scrape дословно с английского — это соскабливать, соскребать). В отличие от длительности хранения метрик, может быть разной для разных метрик. Во всех мануалах я встречал цифру в 15 секунд, но почему-то никто не объяснял эту магическую константу. Почему не 2, 5 или 10? В целом так: чем меньше интервал, тем выше будет разрешение графиков и тем больше места потребуется для хранения метрик. Видимо 15 секунд — это золотая середина, найденная опытным путём.
job_name — имя (для Prometheus’а) для данной группы метрик. job_name будет светиться в интерфейсе Prometheus’а на вкладке Targets, а также попадёт в метку job для группы метрик c этих машин:
Как правильно подобрать имя для job_name ? Я пробовал разные варианты и в итоге остановился на простом правиле: называть job’ы в соответствии с экспортерами: node для node_exporter, cadvisor для cadvisor’а и т.д.
После редактирования конфига надо попросить Prometheus’а перечитать свой конфиг. Для этого надо послать ему сигнал SIGHUP одним из способов:
# systemctl reload prometheus
или
$ docker kill --signal=SIGHUP prometheus
Метки можно вешать любые на ваш вкус, не только project и env.
Как убрать номер порта из таргетов
Я не люблю лишнее. Вот зачем мне номер порта в метке instance? Какая от него польза? Он только сообщает, что node_exporter работает на порту 9100, но является ли эта информация полезной? Нет. Поэтому порт надо скрыть:/etc/prometheus/prometheus.yml
Expression: up
1up{instance="localhost:9100",job="node"}
1up{instance="anotherhost:9100",job="node"}
Service discovery
Service discovery — это автоматическое обнаружение целей. Суть в том, что Prometheus будет получать список целей из какого-то внешнего источника: consul, openstack или что-то ещё. Service discovery — удобная штука, особенно если у вас инфраструктура развёрнута в облаке и машины регулярно рождаются и умирают.
Самый простой service discovery работает на файликах. В конфиге пишем так:/etc/prometheus/prometheus.yml
Prometheus засунет в джобу node все таргеты из файлов, которые лежат в /etc/prometheus/sd_files/ и оканчиваются на _nodeexporter.yml. Всё это будет работать без перечитывания конфига.
Пример файла с таргетами:/etc/prometheus/sd_files/boo_nodeexporter.yml
Удобно держать таргеты разных проектов в разных файлах. В моём примере лишь один проект с названием “boo”, остальные делаются по аналогии. Думаю разберётесь.
Чем отличается перечисление таргетов непосредственно в prometheus.yml от перечисления в отдельных файлах? В первом случае надо давать команду перечитывания конфига при редактировании, а во втором нет.
В прошлой статье я говорил, что Prometheus — это не готовое решение, а скорее фреймворк. Чтобы использовать его возможности полноценно, надо разбираться. Что ж, начнём.
PromQL — это про то, как вытаскивать метрики не из экспортеров, а уже из самого Prometheus’а. Например, чтобы узнать сколько ядер у процессора, надо написать:
count(count(node_cpu_seconds_total) without (mode)) without (cpu)
PromQL дословно расшифровывается как Prometheus query language, т.е. язык запросов. Он не имеет ничего общего с SQL, это принципиально другой язык. Поначалу он казался мне каким-то запутанным, а документация не особо помогала. Потихоньку разобрался и мне даже понравилось.
Prometheus server хранит все данные в виде временных последовательностей (time series). Каждая временная последовательность определяется именем метрики и набором меток (labels) типа ключ-значение (key-value). Давайте сразу посмотрим несколько примеров в Prometheus web UI. Напомню, он работает на localhost:9090. Чтобы не городить скриншотов, я буду показывать запросы в своём псевдо-терминале, а вы не ленитесь и пробуйте у себя.
Prometheus разрабатывался так, чтобы наблюдать за группой машин было так же легко, как за одной. И метки этому способствуют. Прежде всего они позволяют фильтровать вывод:
Кроме = и != есть ещё совпадение и несовпадение с регулярным выражением: =~, !~. Лирическое отступление: мне не нравится одинарное равно для точного сопадения. Это против правил. Должно быть двойное! Эх, молодёжь… А вот разницы в кавычках я не заметил: одинарные и двойные работают одинаково. Да, если задать несколько условий, они будут объединяться логическим И.
Возьмём другой пример. Посмотрим свободное место на дисках:
Не обязательно указывать одинаковый фильтр для всех операндов, как я только что сделал. Достаточно одного, а дальше Prometheus сам возьмёт пересечение по меткам. Следовательно, последний запрос можно с чистой совестью сократить до такого:
Как видите, можно умножать или делить на скаляр и не важно что это: константа или результат вычисления. Вообще я заметил, что в PromQL действует правило: пиши осмысленные запросы и всё будет хорошо. Не надо пытаться сложить диск с процессором и делить на память.
Агрегация
По меткам можно делать агрегацию. Смысл агрегации в том, чтобы объединить несколько однотипных метрик в одну. Например, посчитать максимальный (или средний) load average среди машин определённой группы.
Expression: foo by (label) (some_metric_name)
или
Expression: foo(some_metric_name) by (label)
Синтаксис непривычный, но вроде ничего. Все эти скобочки являются обязательными, без них работать не будет. Пробуем на нашем load average:
Expression: avg by (job)(node_load1)
0.7{job="node"}
Expression: max by (job)(node_load1)
0.96{job="node"}
Expression: min by (job)(node_load1)
0.44{job="node"}
Expression: sum by (job)(node_load1)
1.4{job="node"}
Expression: count by (job)(node_load1)
2{job="node"}
Метки работают как измерения в многомерном пространстве. Агрегация с использованием by как бы схлопывает все измерения, кроме указанного. В примере с node_load1 это не очень заметно, потому что у меня мало меток и хостов. Ок, вот пример получше:
Эта метрика показывает сколько времени каждое ядро работало в каждом режиме. В сыром виде от неё никакого толку, но сейчас это не важно. Важно, что у неё куча меток: cpu, instance, job, mode.
Expression: max by (instance) (node_cpu_seconds_total)
18309.45{instance="localhost:9100"}
3655352.98{instance="anotherhost:9100"}
Expression: max by (instance, cpu) (node_cpu_seconds_total)
17623.34{cpu="3",instance="localhost:9100"}
18295.97{cpu="0",instance="localhost:9100"}
3529407.76{cpu="0",instance="anotherhost:9100"}
18252.21{cpu="1",instance="localhost:9100"}
3655379.15{cpu="1",instance="anotherhost:9100"}
18334{cpu="2",instance="localhost:9100"}
Expression: max without (mode) (node_cpu_seconds_total)
3655736.46{cpu="1",instance="anotherhost:9100",job="node"}
18752.74{cpu="2",instance="localhost:9100",job="node"}
18022.19{cpu="3",instance="localhost:9100",job="node"}
18716.18{cpu="0",instance="localhost:9100",job="node"}
3529779.38{cpu="0",instance="anotherhost:9100",job="node"}
18670.74{cpu="1",instance="localhost:9100",job="node"}
Оператор without работает как by, но в другую сторону, по принципу: “что получится, если убрать такую-то метку”. На практике лучше использовать именно without, а не by. Почему? Дело в том, что Prometheus позволяет навесить кастомных меток при объявлении таргетов, например разный env для машин тестового и боевого окружений (как это сделать). При составлении запроса вы заранее не знаете какие дополнительные метки есть у метрики и есть ли они вообще. А если и знаете, то не факт, что их число не изменится в будущем… В любом случае при использовании by все метки, которые не были явно перечислены, пропадут при агрегации. Это скорее всего будет некритично в дашбордах, но будет неприятностью в алертах. Так что лучше подумать дважды, прежде чем использовать by. Попробуйте самостоятельно поиграться с агрегациями и понять как формируется результат. Полный список агрегирующих операторов вы найдёте в документации.
Считаем ядра
В принципе у вас уже достаточно знаний, чтобы самостоятельно посчитать ядра процессора, но я всё равно покажу. Для решения задачи нам нужна метрика node_cpu_seconds_total и оператор count, который показывает сколько значений схлопнулось при агрегации:
Результат получился правильный, но хочется видеть лишь две строчки: одну для localhost и вторую для anotherhost. Для этого предварительно надо избавиться от метки mode любым из способов:
Expression: count(node_cpu_seconds_total) without (mode)
или
Expression: max(node_cpu_seconds_total) without (mode)
Итоговый запрос:
Expression: count(count(node_cpu_seconds_total) without (mode)) without (cpu)
4{instance="localhost:9100",job="node"}
2{instance="anotherhost:9100",job="node"}
Да, мне тоже кажется, что получение простой по смыслу метрики (число ядер) выглядит как-то заковыристо. Как будто мы ухо ногой чешем. Привыкайте.
Мгновенный и диапазонный вектор
Простите, я не смог придумать лучшего перевода. В оригинале это называется instant and range vector. Сейчас мы смотрели только мгновенные вектора, т.е. значения метрик в конкретный момент времени. Почему вообще результат запроса называется вектором? Вспоминаем, что Prometheus ориентирован на работу с группами машин, не с единичными машинами. Запросив какую-то метрику, в общем случае вы получите не одно значение, а несколько. Вот и получается вектор (с точки зрения алгебры, а не геометрии). Возможно, если погрузиться в исходный код Prometheus, всё окажется сложнее, но, к счастью, в этом нет необходимости.
Диапазонный вектор (range vector) — это вектор, который хранит диапазон значений метрики за определённый период времени. Он нужен, когда этого требует арифметика запроса. Проще всего объяснить на графике функции avg_over_time от чего-нибудь. В каждый момент времени она будет вычислять усреднённое значение метрики за предыдущие X минут (секунд, часов…). По-научному это называется “скользящее среднее” (moving average). На словах как-то сложно получается, лучше взгляните на эти 2 графика:
Оранжевый получен из зелёного усреднением за 10 минут. Да, это был мой любимый load average:
Собственно, диапазонный вектор — это когда мы дописываем временной интервал в квадратных скобочках. Интервал времени для диапазонного вектора указывается очень по-человечески: 1s — одна секунда, 1m — одна минута, 1h — один час, 1d — день. А что, если нужно указать полтора часа? Просто напишите 90m.
Домашнее задание: посмотрите на графики max_over_time и min_over_time.
Типы метрик
Метрики бывают разных типов. Это важно, потому что для разных типов метрик применимы те или иные запросы.
Шкала (gauge). Самый простой тип метрик. Примеры: количество свободной/занятой ОЗУ, load average и т.д.
Счётчик (counter). Похож на шкалу, но предназначен совершенно для других данных. Счётчик может только увеличиваться, поэтому он подходит только для тех метрик, которые по своей природе могут только увеличиваться. Примеры: время работы CPU в определённом режиме (user, system, iowait…), количество запросов к веб-серверу, количество отправленных/принятых сетевых пакетов, количество ошибок. На практике вас не будет интересовать абсолютное значение счётчика, вас будет интересовать первая производная по времени, т.е. скорость роста этого счётчика, например количество запросов в минуту или количество ошибок за день.
Гистограмма (histogram). Я пока не сталкивался с таким типом, поэтому ничего путного не скажу.
Саммари (summaries). Что-то похожее на гистограммы, но другое.
Сырое значение счётчика не несёт никакого смысла, его надо оборачивать функцией rate или irate. Эти функции принимают на вход диапазонный вектор, поэтому правильный запрос выглядит так:
В чём разница между rate и irate? Первая функция для вычисления производной берёт весь диапазон (5 минут в нашем случае), а вторая берёт лишь два последних сэмпла из всего диапазона, чтобы максимально приблизиться к мгновенному значению (первоисточник). Собственно, её название расшифровывается как instant rate.
Почему мы берём диапазонный вектор за 5 минут, а не за 1 или 10? Не знаю. Почему-то так делают во всех примерах и в дашборде Node exporter full тоже так. Для rate получается, что чем меньше интервал, тем больше пиков, а чем больше интервал, тем сильнее их сглаживание. Ну, с математикой не поспоришь. Для irate величина диапазона не имеет значения. На самом деле при определённых обстоятельствах всё-таки имеет, но это настолько тонкий нюанс, что на него можно забить.
Другой вопрос: 1210 — это в каких попугаях? Во-первых смотрим исходную метрику, там явно написано bytes. Функция rate делит исходную размерность на секунды, получается байт в секунду. Вообще Prometheus предпочитает стандартные единицы измерения: секунды, метры, ньютоны и т.п. Как в школе на уроках физики.
Считаем загрузку процессора
С памятью, диском и трафиком понятно, а как посмотреть загрузку процессора? Отвечаю. То, что мы привыкли считать загрузкой процессора в процентах на самом деле вот какая штука: сколько времени (в процентном отношении) процессор не отдыхал, т.е. не находился в режиме idle.
Итак, всё начинается с метрики node_cpu_seconds_total. Сначала посмотрим сколько времени процессор отдыхал:
Любопытно, что сумма всех режимов работы процессора никогда не доходит до 100% времени:
Expression: sum (irate(node_cpu_seconds_total[5m])) without (mode)
{cpu="0",instance="anotherhost:9100",job="node"} 0.9739999999999347
{cpu="0",instance="localhost:9100",job="node"} 0.9991578947368495
{cpu="1",instance="anotherhost:9100",job="node"} 0.9083157894731189
{cpu="1",instance="localhost:9100",job="node"} 0.9985964912280734
{cpu="2",instance="localhost:9100",job="node"} 0.998456140350873
{cpu="3",instance="localhost:9100",job="node"} 0.9983508771929828
Почему так? Хороший вопрос. Я подозреваю, что оставшаяся часть времени уходит на переключение контекста процессора. Получается, что загрузка процессора, полученная по формуле выше, будет чуть-чуть завышенной. В действительности можно не переживать по этому поводу, потому что погрешность получается незначительная. Это просто у меня anotherhost работает на старом процессоре Intel Atom. На нормальных взрослых процессорах погрешность не превышает десятые доли процента.
И это всё?
Думаю, на сегодня достаточно. Да, я рассказал не про все возможности PromQL. Например, за кадром остались операторы ignoring и on, логические and и or а также сдвиг offset. Есть всякие интересности типа производной по времени deriv или предсказателя будущего predict_linear. При желании вы сможете почитать про них в документации: операторы и функции. Я же вернусь к ним, когда мы будем решать практические задачи мониторинга.
Мониторинг — это сбор метрик и представление этих метрик в удобном виде (таблицы, графики, шкалы, уведомления, отчёты). Концептуально его можно изобразить в таком виде:
Метрики — это абстракция, с которой мы имеем дело, когда говорим о мониторинге. Это какие-то числа, описывающие состояние интересующей нас штуковины. Самый простой и понятный мониторинг следит за ресурсами компьютера: загрузкой процессора, памяти, диска, сети. Аналогично можно следить за чем-то более высокоуровневым, вроде количества посетителей на сайте или среднего времени ответа сервера. Для компьютера это один хрен безликие числа.
Мониторинг — это инструмент анализа того, что происходит/происходило в системе. Следовательно, без понимания смысла собранных данных мониторинг вам не особо поможет. И наоборот: в умелых руках это мощный инструмент.
Чем больше компонентов в вашей системе (микросервисов), чем больше нагрузка на неё, чем дороже время простоя, тем важнее иметь хорошую систему мониторинга.
То, что не метрики — то логи. Их тоже надо собирать и анализировать, но это отдельная история со своими инструментами.
Сейчас модно делать мониторинг на основе Prometheus. Так ли он хорош на самом деле? На мой взгляд это лучшее, что сейчас есть из мониторинга. Оговорюсь сразу для тех, кто хочет с этим поспорить: я понимаю, что разным задачам — разные инструменты и где-то больше подходит старый проверенный Nagios. Но в целом лидирует Prometheus.
Prometheus — это не готовое решение в духе “поставил и работает” (привет, Netdata). Это платформа, набор инструментов, позволяющий сделать себе такой мониторинг, какой надо. Фреймворк, если хотите.
Эта статья про знакомство с Prometheus’ом и установку. Потом будет интереснее — про настройку непосредственно мониторинга.
Prometheus состоит из отдельных компонентов, которые общаются друг с другом по http. Всё модно-молодёжно, с веб-интерфейсами и настраивается в yaml-конфигах.
Prometheus server — центральное звено. Здесь хранятся собранные метрики, плюс имеется простенький веб-интерфейс, чтобы на них смотреть (Prometheus web UI).
Агенты, собирающие метрики и предоставляющие их в понятном для Prometheus’а виде. В зависимости от контекста называются экспортерами (exporter) или таргетами (target). Устанавливаются на целевые машины. Типичный представитель — node_exporter — собирает данные о вычислительных ресурсах: загрузку процессора, памяти, диска и т.п. Есть куча других экспортеров. Например, cadvisor собирает данные о контейнерах. Есть экспортеры для Postgres’а, для Nginx’а и т.д. С точки зрения самого Prometheus’а они ничем принципиально не отличаются друг от друга, просто каждый собирает какие-то свои метрики. Можно писать свои экспортеры, если ничего из готового вам не подходит.
Alertmanager — уведомлятор. Это он отправит письмо вам на почту, если что-то сломается. Из коробки умеет в почту, Slack, Hipchat, Pagerduty. Сторонними средствами можно прикрутить Telegram и наверное что-то ещё.
Grafana строго говоря не является частью Prometheus’а, но все её ставят и официальная документация рекомендует поступать именно так. Показывает красивые графики. Кучка графиков, собранных на одном экране, называется дашбордом (dashboard). Типа такого:
Установка
Все компоненты Prometheus’а написаны на Go и представляют собой статически скомпиленные бинари, не требующие никаких зависимостей кроме libc и готовые запускаться на любой платформе, будь то Debian, Arch или Centos. Установить их можно аж тремя способами:
по-старинке через пакетный менеджер,
взять готовые бинари с оф. сайта и засунуть их в систему в обход пакетного менеджера,
развернуть всё в докер-контейнерах.
Разумеется, у каждого способа есть свои преимущества и недостатки.
Если ставить через пакетный менеджер, то через него же вы будете получать обновления и сможете всё удалить. Однако есть нюанс. У Prometheus’а нет своих собственных репозиториев для популярных дистрибутивов, а официальные репозитории Debian/Ubuntu как всегда отстают от апстрима. Например, сейчас в Debian доступна версия 2.7.1, когда Prometheus уже 2.11.1. Arch Linux — молодец, идёт в ногу со временем.
При установке вручную Prometheus не будет числиться в списках установленного софта и с точки зрения пакетного менеджера его в системе нет. Зато вы сможете обновлять Prometheus отдельно от пакетов. При ручной установке придётся немного побывать в роли установочного скрипта (который есть в пакете), поэтому, если у вас серьёзные намерения — автоматизируйте установку/обновление через какой-нибудь Ansible. Рекомендую взять за основу готовые роли для Prometheus, Alertmanager, Node exporter, Grafana.
Если ставить Prometheus в докере, то всё про мониторинг будет лежать в одном месте. Можно это хозяйство положить под Git и иметь возможность поднять мониторинг в две команды: git clone, docker-compose up. Но надо писать правильный docker-compose.yml, париться при возникновении сетевых проблем… Короче, вы от одних проблем избавляетесь, а взамен получаете другие. В зависимости от того, с какими вам больше нравится возиться, выбирайте тот или иной вариант установки. Вам понадобятся контейнеры prom/prometheus, prom/node_exporter, prom/alertmanager. За основу рекомендую взять docker-compose.yml из репозитория docprom.
В продакшене я бы поднимал мониторинг в докере как минимум в целях безопасности. Свой личный мониторинг я устанавливал по-старинке вручную.
Установка вручную
Расскажу про ручную установку, потому что в основе других способов установки лежат те же действия, только завёрнутые в дополнительную обёртку. Этот способ подойдёт для любых дистрибутивов Linux под управлением systemd (Ubuntu, Debian, Centos, Arch и т.д.)
В статусе должно быть написано “active (running)” — значит работает. Теперь можно сходить на http://localhost:9090 и полюбоваться на интерфейс Prometheus’а. Пока что он бесполезен, но уже можно потыкать.
Важно! Если у вас /home вынесен на отдельный раздел, директиву ProtectHome=yes надо убрать, иначе node exporter будет неправильно показывать оставшееся место на разделе /home.
Можно посмотреть как node exporter отдаёт метрики:
$ curl -s http://localhost:9100/metrics
# HELP go_gc_duration_seconds A summary of the GC invocation durations.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 7.9825e-05
go_gc_duration_seconds{quantile="0.25"} 7.9825e-05
go_gc_duration_seconds{quantile="0.5"} 7.9825e-05
go_gc_duration_seconds{quantile="0.75"} 7.9825e-05
go_gc_duration_seconds{quantile="1"} 7.9825e-05
go_gc_duration_seconds_sum 7.9825e-05
go_gc_duration_seconds_count 1
...
Пока не пытайтесь что-то понять в выводе. Отдаёт и ладно.
Установка alertmanager
Alertmanager — это про алерты. Первое время алертов у вас не будет, поэтому сейчас его можно не ставить. Но лучше всё-таки поставить, чтобы всё необходимое уже было подготовлено. Идём за дистрибутивом на https://github.com/prometheus/alertmanager/releases.
Веб-интерфейс alertmanager’а доступен на http://localhost:9093. Пока в нём нет ничего интересного.
Установка Grafana
Если у вас Debian/Ubuntu или Redhat/Centos, лучше установить Grafana из пакета. Ссылочки найдёте в описании под видео на https://grafana.com/grafana/download.
# systemctl start grafana
# systemctl status grafana
Prometheus, Node exporter, Alertmanager и Grafana по умолчанию слушают на всех сетевых интерфейсах. При необходимости прикрывайтесь фаерволлом от посторонних глаз.
Первичная настройка
Теперь, когда все компоненты установлены, надо подружить их друг с другом. Сперва минимально настроим Prometheus server:/etc/prometheus/prometheus.yml
Пока не буду вдаваться в подробности настройки, этим займёмся в следующих статьях. На текущем этапе надо получить пусть примитивный, но работающий мониторинг. Основное что сейчас стоит знать:
Отступы в yml — два пробела. Всегда и везде.
scrape_interval — с какой периодичностью Prometheus server будет забирать (scrape) метрики с экспортеров.
localhost:9093 и localhost:9100 — адреса alertmanager’а и node exporter’а соответственно. Если у вас другие, замените на правильные. Если у вас node exporter установлен на несколько машин, впишите их все.
Теперь попросим prometheus перечитать конфигурационный файл:
Поздравляю, мониторинг начал мониторить! Теперь можно пойти на вкладку Graph и запросить какую-нибудь метрику, например node_load1 — это одноминутный load average. Там же можно временной график посмотреть:
В повседневной жизни вы вряд ли будете смотреть графики в этом интерфейсе. Гораздо приятнее смотреть графики в Grafana.
Открываем интерфейс Grafana по адресу http://localhost:3000. Логинимся (admin:admin), при желании меняем пароль. Практически всё в Графане настраивается тут же, через веб-интерфейс. Нажимаем на боковой панели Configuration (шестерёнка) → Preferences:
Идём на вкладку Data Sources, добавляем наш Prometheus:
Теперь добавим готовый дашборд Node exporter full. Это лучший дашборд для старта из всех, что я пересмотрел. Идём Dashboards (квадрат) → Manage → Import.
Вписываем 1860 и убираем курсор из поля ввода, иначе не начнёт импортировать. Дальше указываем data source и жмём import.
Поздравляю, самая скучная часть работы проделана! Теперь вы можете наблюдать за жизнью своих серверов в Графане.
Если у вас серьёзные намерения и вы хотите, чтобы все компоненты автоматически запускались после перезагрузки машины, не забудьте сообщить об этом systemd:
У нас пока нет алертов, не заведены пользователи в Grafana, мы ничего не понимаем в настройке. Да, наш мониторинг пока очень сырой. Но надо же с чего-то начать! Позже я напишу про настройку, про алерты и многое другое. Всё будет, друзья. Всё будет.
Блог Robust Perseption, который ведёт Brian Brazil — один из разработчиков Prometheus’а. Статьи короткие и, на мой взгляд, слишком лаконичные. Хочется больше подробностей, больше примеров.
Книга Prometheus up and running за авторством всё того же Brian Brazil. Во многом повторяет статьи блога.
В этой статье мы расскажем об установке и настройке Grafana, как пользоваться этим инструментом, вы узнаете как подключить внешние источники данных и многое другое.
Grafana — инструмент с открытым исходным кодом для визуализации данных из различных систем сбора статистики. Используется для представления в графическом виде временных рядов и текстовых данных.
Grafana поддерживает установку на самые популярные операционные системы: Linux (Debian, Ubuntu, CentOS, Fedora, OpenSuse, RedHat), Mac и Windows. Допустимыми базами данных, в которых возможно хранение данных о пользователях и дашбордах, являются SQLite (используется по умолчанию), MySQL и PostgreSQL. В качестве демонстрационных серверов будем использовать «Облачные серверы» с операционными системами CentOS Stream и Ubuntu, на которые установим Grafana с базой данных SQLite.
Перед началом установки Grafana, подготовим серверы. В консоли управления Selectel перейдём в раздел Облачная платформа и нажмём кнопку Создать сервер.
На следующем представлении выберем готовую конфигурацию сервера с 1 vCPU и 1 ГБ RAM с операционной системой CentOS 8 64-bit.
Наш сервер будет иметь 10 ГБ на жестком диске. Для целей демонстрации этого достаточно. Прокручиваем вниз и нажимаем кнопку Создать. Аналогичным образом создадим сервер с Ubuntu 20.04 LTS 64-bit.
После проделанных манипуляций оба сервера появятся в списке доступных. Приступим к установке Grafana.
Установка Grafana на CentOS Stream
Установка Grafana на CentOS Stream выполняется из репозитория разработчиков. Скачать дистрибутив можно также с официального сайта. Создадим новую конфигурацию репозитория:
sudo nano /etc/yum.repos.d/grafana.repo
Впишем в этот файл следующие переменные со значениями:
Установка завершена. Grafana на Ubuntu 20.04 готова к использованию. Проверим текущий статус сервиса:
sudo systemctl status grafana-server
В результате получим вывод:
grafana-server.service - Grafana instance
Loaded: loaded (/usr/lib/systemd/system/grafana-server.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2021-01-25 09:44:26 MSK; 10s ago
Настройка брандмауэра для доступа к Grafana
По умолчанию Grafana доступна на порту 3000. При использовании firewalld или iptables, необходимо разрешить сетевой доступ к этому порту из внешнего мира.
Набор команд для firewalld (добавление нового правила и перезагрузка сервиса firewalld для применения конфигурации):
Вписываем в раздел OUTPUT ACCEPT следующую строку:
-A INPUT -p tcp -m tcp --dport 3000 -m state --state NEW -j ACCEPT
И перезапускаем iptables:
sudo systemctl restart iptables
После выполнения описанных в этом разделе манипуляций, сетевой экран будет разрешать подключения по порту 3000. В этом можно убедиться, открыв в браузере интерфейс Grafana. Логин и пароль по умолчанию admin.
Если не удается войти в Grafana или вы поменяли пароль и не можете его вспомнить, через CLI административный пароль Web-интерфейса можно сбросить:
После выполнения команды выше, можно входить с новым паролем.
Источники данных Grafana и установка плагинов
Grafana поддерживает из коробки различные источники данных:
Prometheus,
Graphite,
OpenTSDB,
InfluxDB,
Elasticsearch и другие.
Полный список можно посмотреть в документации. Перечень встроенных источников данных может быть расширен при помощи сторонних плагинов. Один из таких плагинов — плагин Zabbix для Grafana. Мониторинг — одно из основных назначений использования Grafana.
В интерфейсе Grafana перейдём в Configuration -> Plugins и обнаружим здесь плагин для Zabbix.
Перейдем к настройке плагина и нажмём на кнопку Enable.
С момента включения плагина, в меню Data Sources появится новый источник данных — Zabbix. Нажмём на кнопку Select и настроим источник данных.
В настройках плагина укажем URL-адрес, имя пользователя и пароль для доступа по API к Zabbix.
Нажимаем на кнопку Save & Test и получаем подтверждение корректности настроек.
С этого момента данных из Zabbix могут быть использованы для создания визуализаций.
Создание и настройка панелей и дашбордов
Панели и дашборды — основные сущности представлений внутри Grafana. Каждый дашборд состоит из набора панелей. Для создания дашбордов перейдём в представление Dashboards и нажмём на кнопку New Dashboard.
На следующем шаге будет предложено добавить на дашборд новую панель.
Grafana имеет встроенные панели, которые можно сразу начинать использовать. По умолчанию представлены следующие типы:
Graph — панель с графиками с возможностью комбинировать несколько метрик на одной панели.
Stat (ранее SingleStat) — панель с одиночным графиком и возможностью отображения моментального значения метрики.
Gauge — панель в формате спидометра, есть возможность ограничить верхнее значение на шкале.
Bar Gauge — панель с возможностью отображения нескольких метрик на вертикальной гистограмме.
Table — панель с представлением в виде таблицы, на которой можно отображать значения нескольких метрик.
Text — панель для отображения произвольного текста (подписи).
Heatmap — панель для отображения тепловой карты значений метрик.
Alert list — панель для отображения событий из внешних систем.
Dashboard list — комбинированная панель для отображения дашбордов, добавленных в избранное.
News — панель для отображения новостной ленты из внешних источников.
Zabbix problems — панель для отображения событий из системы мониторинга Zabbix.
Logs — панель для отображения строчек лога, которые собираются одной из внешних систем.
Далее выберем в выпадающем меню источник данных Zabbix и укажем группу, хост, приложение и элемент данных. Если всё выполнено правильно — на графике появятся данные.
Grafana поддерживает различные типы визуализаций:
графические,
табличные,
гистограммы,
тепловые карты,
карты сетевого взаимодействия в Grafana и другие.
При создании новой панели, в правой части экрана в разделе Visualization есть возможность выбрать тип визуализации. Перейдем к созданию SingleStat (отображается как Stat) — панели с комбинацией численного и графического представлений. После ввода специфических данных для Zabbix, нажмём кнопку Apply.
В результате выполненных действий, увидим на дашборде две панели.
У каждой панели и каждого дашборда в Grafana есть свой набор настроек. Настройки первой открываются через выпадающее меню панели, а настройки второго откроются после нажатия на шестеренку в верхнем правом углу экрана.
На изображении ниже показаны настройки дашборда.
Импорт и экспорт дашбордов
В Grafana можно импортировать и экспортировать дашборды. Для импорта нужно перейти в представление Dashboards и нажать кнопку Import.
Для импорта доступны две опции: загрузка произвольного JSON и импорт готовых дашбордов с grafana.com. Во втором случае нужно указывать ID дашборда.
Для экспорта любого дашборда, необходимо нажать на специальную иконку в верхней части экрана и далее нажать Save to file. Дашборд будет сохранен в формате JSON.
Обратный прокси-сервер и SSL
Для настройки SSL в Grafana необходимо использовать обратный прокси-сервер. В этом разделе мы расскажем как настроить такой прокси на базе Nginx. Для целей демонстрации мы зарегистрировали домен grafana.tk, для которого выпустили специальный сертификат Let’s Encrypt.
После создания сервера мы настроили веб-сервер Nginx для работы через SSL. Осталось перенаправить трафик с него на порт 3000, по которому доступна Grafana. Для этого откроем конфигурационный файл веб-сервера.
sudo nano /etc/nginx/sites-available/grafana.tk
В этом файле будем работать с этим блоком конфигурации:
Таким образом мы настроили переадресацию на локальный порт 3000. Теперь можно проверить корректность конфигурации Nginx:
sudo nginx -t
Осталось перезапустить Nginx:
sudo systemctl reload nginx
Теперь наша тестовая Grafana доступна по URL https://grafana.tk. Проверим:
Кроме настроек SSL, в Grafana, для повышения уровня безопасности, можно отключить анонимный доступ и создание новых учетных записей.
Конфигурационный файл Grafana
Для тонкой настройки Grafana, можно использовать конфигурационный файл (по умолчанию /etc/grafana/grafana.ini). Здесь хранятся параметры, которые можно менять при необходимости. Рассмотрим основные блоки этого файла.
[paths]
data
Директория, в которой Grafana хранит базу данных sqlite3 и другие данные. Эта директория обычно задаётся в стартовом скрипте init.d.
logs
Директория, в которой Grafana хранит логи. Эта директория обычно задаётся в стартовом скрипте init.d.
[server]
http_addr
IP-адрес, на котором будет доступен веб-интерфейс Grafana. Если параметр не указан, то интерфейс будет доступен по всем сетевым интерфейсам сервера.
http_port
Порт, на котором будет доступен веб-интерфейс Grafana. По умолчанию номер порта 3000. Если нужно использовать порт 80, можно настроить переадресацию в iptables:
Для переадресации можно также использовать веб-сервер Nginx или Apache.
protocol
Протокол, по которому будет доступна Grafana. Варианты http или https.
domain
Может использоваться только вместе с опцией root_url (см. ниже). Важно, если вы используете аутентификацию через GitHub или Google OAuth.
enforce_domain
Переадресация на корректный домен, если заголовок хоста не соответствует домену. По умолчанию в значении false.
root_url
Полный URL-адрес для доступа к Grafana через веб-браузер. Важно, если вы используете аутентификацию через GitHub или Google OAuth.
static_root_path
Путь к директории, в которой находятся файлы HTML, JS и CSS.
cert_file
Директория с файлом сертификата (если доступ к Grafana — https).
cert_key
Директория с файлом ключа (если доступ к Grafana осуществляется по протоколу https).
[database]
Grafana нужна база данных для хранения пользователей, дашбордов и других данных. По умолчанию используется sqlite3, которая включена в дистрибутив Grafana.
type
Поддерживаются mysql, postgres или sqlite3 на выбор.
path
Параметр применим только к базе данных sqlite3. Путь к файлу данных.
host
Параметр применим только к MySQL или Postgres. Включает IP-адрес или hostname и номер порта. Например, для MySQL запущенной на одном сервере с Grafana: host = 127.0.0.1:3306
name
Имя базы данных Grafana.
user
Имя пользователя базы данных (не применимо к sqlite3).
password
Пароль пользователя базы данных (не применимо к sqlite3).
ssl_mode
Применимо только к postgres. Доступны опции: disable, require или verify-full.
[security]
admin_user
Имя привилегированного пользователя Grafana. По умолчанию admin.
admin_password
Пароль привилегированного пользователя Grafana. По умолчанию admin.
login_remember_days
Количество дней, в течении которых не требуется повторный ввод логина и пароля.
disable_gravatar
Отключение пользовательской иконки. Устанавливается в true для отключения. По умолчанию имеет значение false.
[users]
allow_sign_up
Устанавливается в false для предотвращения создания новых учетных записей действующими пользователями.
allow_org_create
Устанавливается в false для предотвращения создания новых организаций действующими пользователями. По умолчанию имеет значение true.
auto_assign_org
Устанавливается в true для автоматического добавления новых пользователей в основную организацию (id 1). Когда установлено в false, новые пользователи добавляются в новые организации, которые создаются автоматически.
auto_assign_org_role
Роль новых пользователей будет привязана к основной организации.
[auth.anonymous]
enabled
Устанавливается в true для включения анонимного доступа. По умолчанию находится в значении false
org_name
Устанавливает имя организации, которое будет использоваться для анонимных пользователей.
org_role
Указывает роль для анонимных пользователей. По умолчанию Viewer, а другие доступные Editor и Admin.
[auth.basic]
enabled
Когда установлено значение true (по умолчанию) http api будет принимать соединения с флагом basic authentication.
[auth.ldap]
enabled
Устанавливается в true для включения интеграции с LDAP (по умолчанию: false).
config_file
Путь к конфигурационному файлу LDAP (по умолчанию: /etc/grafana/ldap.toml).
[auth.proxy]
Эта функция разрешает аутентифицироваться в обратном прокси.
enabled
По умолчанию установлено в false.
header_name
По умолчанию установлено в X-WEBAUTH-USER.
header_property
По умолчанию имя пользователя, но также может принимать значение адреса электронной почты.
auto_sign_up
Устанавливается в значение true для включения автоматического входа пользователей, которые отсутствуют в Grafana DB. По умолчанию установлено значение true.
[session]
provider
Валидные значения: memory, file, mysql, postgres. По умолчанию установлено значение file.
provider_config
Эта опция должна быть сконфигурирована отдельно в зависимости от того, какой тип провайдера сессии установлен.
file: путь к файлу с сессиями, например, data/sessions.
mysql: go-sql-driver/mysql dsn config string, например, user:password@tcp(127.0.0.1:3306)/database_name
Устанавливается в true если Grafana находится только за HTTPS-прокси. По умолчанию установлено в значение false.
session_life_time
Как долго длится сессия в секундах. По умолчанию 86400 секунд (24 часа).
[analytics]
reporting_enabled
Когда опция включена, Grafana будет отправлять анонимную статистику на stats.grafana.org. Никакие IP-адреса не записываются. Данные отправляются каждые 24 часа. По умолчанию установлено в true.
google_analytics_ua_id
Если вы хотите отслеживать активности в Grafana при помощи Google analytics, укажите ваш Universal Analytics ID. По умолчанию эта функция отключена.
[dashboards.json]
Если у вас есть система, которая автоматически строит дашборды из JSON-файлов, вы можете включить эту опцию, чтобы новые дашборды были доступны через поиск в Grafana.
enabled
Значение true или false. По умолчанию выключено.
path
Полный путь к директории, содержащей JSON-файлы с дашбордами.
Заключение
Мы рассмотрели подходы к установке, настройке и работе с Grafana. Этот популярный инструмент имеет большое сообщество разработчиков, которые поддерживают проект и разрабатывают новые плагины, дашборды и развивают продукт. У Grafana регулярно выходят обновления и улучшения, поэтому можно без опасений использовать этот инструмент в продакшн средах.
Мы в Badoo постоянно мониторим свежие технологии и оцениваем, стоит ли использовать их в нашей системе. Одним из таких исследований и хотим поделиться с сообществом. Оно посвящено Loki — системе агрегирования логов.
Loki — это решение для хранения и просмотра логов, также этот стек предоставляет гибкую систему для их анализа и отправки данных в Prometheus. В мае вышло очередное обновление, которое активно продвигают создатели. Нас заинтересовало, что умеет Loki, какие возможности предоставляет и в какой степени может выступать в качестве альтернативы ELK — стека, который мы используем сейчас.
Что такое Loki
Grafana Loki — это набор компонентов для полноценной системы работы с логами. В отличие от других подобных систем Loki основан на идее индексировать только метаданные логов — labels (так же, как и в Prometheus), a сами логи сжимать рядом в отдельные чанки.
Прежде чем перейти к описанию того, что можно делать при помощи Loki, хочу пояснить, что подразумевается под «идеей индексировать только метаданные». Сравним подход Loki и подход к индексированию в традиционных решениях, таких как Elasticsearch, на примере строки из лога nginx:
Традиционные системы парсят строку целиком, включая поля с большим количеством уникальных значений user_id и item_id, и сохраняют всё в большие индексы. Преимуществом данного подхода является то, что можно выполнять сложные запросы быстро, так как почти все данные — в индексе. Но за это приходится платить тем, что индекс становится большим, что выливается в требования к памяти. В итоге полнотекстовый индекс логов сопоставим по размеру с самими логами. Для того чтобы по нему быстро искать, индекс должен быть загружен в память. И чем больше логов, тем быстрее индекс увеличивается и тем больше памяти он потребляет.
Подход Loki требует, чтобы из строки были извлечены только необходимые данные, количество значений которых невелико. Таким образом, мы получаем небольшой индекс и можем искать данные, фильтруя их по времени и по проиндексированным полям, а затем сканируя оставшееся регулярными выражениями или поиском подстроки. Процесс кажется не самым быстрым, но Loki разделяет запрос на несколько частей и выполняет их параллельно, обрабатывая большое количество данных за короткое время. Количество шардов и параллельных запросов в них конфигурируется; таким образом, количество данных, которое можно обработать за единицу времени, линейно зависит от количества предоставленных ресурсов.
Этот компромисс между большим быстрым индексом и маленьким индексом с параллельным полным перебором позволяет Loki контролировать стоимость системы. Её можно гибко настраивать и расширять в соответствии с потребностями.
Loki-стек состоит из трёх компонентов: Promtail, Loki, Grafana. Promtail собирает логи, обрабатывает их и отправляет в Loki. Loki их хранит. А Grafana умеет запрашивать данные из Loki и показывать их. Вообще Loki можно использовать не только для хранения логов и поиска по ним. Весь стек даёт большие возможности по обработке и анализу поступающих данных, используя Prometheus way. Описание процесса установки можно найти здесь.
Поиск по логам
Искать по логам можно в специальном интерфейсе Grafana — Explorer. Для запросов используется язык LogQL, очень похожий на PromQL, использующийся в Prometheus. В принципе, его можно рассматривать как распределённый grep.
Интерфейс поиска выглядит так:
Сам запрос состоит из двух частей: selector и filter. Selector — это поиск по индексированным метаданным (лейблам), которые присвоены логам, а filter — поисковая строка или регэксп, с помощью которого отфильтровываются записи, определённые селектором. В приведенном примере: В фигурных скобках — селектор, все что после — фильтр.
{image_name="nginx.promtail.test"} |= "index"
Из-за принципа работы Loki нельзя делать запросы без селектора, но лейблы можно делать сколь угодно общими.
Селектор — это key-value значения в фигурных скобках. Можно комбинировать селекторы и задавать разные условия поиска, используя операторы =, != или регулярные выражения:
{instance=~"kafka-[23]",name!="kafka-dev"}
// Найдёт логи с лейблом instance, имеющие значение kafka-2, kafka-3, и исключит dev
Фильтр — это текст или регэксп, который отфильтрует все данные, полученные селектором.
Есть возможность получения ad-hoc-графиков по полученным данным в режиме metrics. Например, можно узнать частоту появления в логах nginx записи, содержащей строку index:
Полное описание возможностей можно найти в документации LogQL.
Парсинг логов
Есть несколько способов собрать логи:
С помощью Promtail, стандартного компонента стека для сбора логов.
Использовать Fluentd или Fluent Bit, которые умеют отправлять данные в Loki. В отличие от Promtail они имеют готовые парсеры практически для любого вида лога и справляются в том числе с multiline-логами.
Обычно для парсинга используют Promtail. Он делает три вещи:
Находит источники данных.
Прикрепляет к ним лейблы.
Отправляет данные в Loki.
В настоящий момент Promtail может читать логи с локальных файлов и с systemd journal. Он должен быть установлен на каждую машину, с которой собираются логи.
Есть интеграция с Kubernetes: Promtail автоматически через Kubernetes REST API узнаёт состояние кластера и собирает логи с ноды, сервиса или пода, сразу развешивая лейблы на основе метаданных из Kubernetes (имя пода, имя файла и т. д.).
Также можно развешивать лейблы на основе данных из лога при помощи Pipeline. Pipeline Promtail может состоять из четырёх типов стадий. Более подробно — в официальной документации, тут же отмечу некоторые нюансы.
Parsing stages. Это стадия RegEx и JSON. На этом этапе мы извлекаем данные из логов в так называемую extracted map. Извлекать можно из JSON, просто копируя нужные нам поля в extracted map, или через регулярные выражения (RegEx), где в extracted map “мапятся” named groups. Extracted map представляет собой key-value хранилище, где key — имя поля, а value — его значение из логов.
Transform stages. У этой стадии есть две опции: transform, где мы задаем правила трансформации, и source — источник данных для трансформации из extracted map. Если в extracted map такого поля нет, то оно будет создано. Таким образом, можно создавать лейблы, которые не основаны на extracted map. На этом этапе мы можем манипулировать данными в extracted map, используя достаточно мощный Golang Template. Кроме того, надо помнить, что extracted map целиком загружается при парсинге, что даёт возможность, например, проверять значение в ней: “{{if .tag}tag value exists{end}}”. Template поддерживает условия, циклы и некоторые строковые функции, такие как Replace и Trim.
Action stages. На этом этапе можно сделать что-нибудь с извлечённым:
Создать лейбл из extracted data, который проиндексируется Loki.
Изменить или установить время события из лога.
Изменить данные (текст лога), которые уйдут в Loki.
Создать метрики.
Filtering stages. Стадия match, на которой можно либо отправить в /dev/null записи, которые нам не нужны, либо направить их на дальнейшую обработку.
Покажу на примере обработки обычных nginx-логов, как можно парсить логи при помощи Promtail.
Для теста возьмём в качестве nginx-proxy модифицированный образ nginx jwilder/nginx-proxy:alpine и небольшой демон, который умеет спрашивать сам себя по HTTP. У демона задано несколько эндпоинтов, на которые он может давать ответы разного размера, с разными HTTP-статусами и с разной задержкой.
Собирать логи будем с докер-контейнеров, которые можно найти по пути /var/lib/docker/containers/<container_id>/<container_id>-json.log
В docker-compose.yml настраиваем Promtail и указываем путь до конфига:
Добавляем в promtail.yml путь до логов (в конфиге есть опция «docker», которая делает то же самое одной строчкой, но это было бы не так наглядно):
scrape_configs:
- job_name: containers
static_configs:
labels:
job: containerlogs
__path__: /var/lib/docker/containers/*/*log # for linux only
При включении такой конфигурации в Loki будут попадать логи со всех контейнеров. Чтобы этого избежать, меняем настройки тестового nginx в docker-compose.yml — добавляем логирование поле tag:
Разбираем request_url. С помощью регэкспа определяем назначение запроса: к статике, к фоткам, к API и устанавливаем в extracted map соответствующий ключ.
- template:
source: request_type
template: "{{if .photo}}photo{{else if .static_type}}static{{else if .api_request}}api{{else}}other{{end}}"
При помощи условных операторов в Template проверяем установленные поля в extracted map и устанавливаем для поля request_type нужные значения: photo, static, API. Назначаем other, если не удалось. Теперь request_type содержит тип запроса.
Устанавливаем лейблы api_request, virtual_host, request_type и статус (HTTP status) на основании того, что удалось положить в extracted map.
- output:
source: nginx_log_row
Меняем output. Теперь в Loki уходит очищенный nginx-лог из extracted map.
После запуска приведённого конфига можно увидеть, что каждой записи присвоены метки на основе данных из лога.
Нужно иметь в виду, что извлечение меток с большим количеством значений (cardinality) может существенно замедлить работу Loki. То есть не стоит помещать в индекс, например, user_id. Подробнее об этом читайте в статье “How labels in Loki can make log queries faster and easier”. Но это не значит, что нельзя искать по user_id без индексов. Нужно использовать фильтры при поиске («грепать» по данным), а индекс здесь выступает как идентификатор потока.
Визуализация логов
Loki может выступать в роли источника данных для графиков Grafana, используя LogQL. Поддерживаются следующие функции:
rate — количество записей в секунду;
count over time — количество записей в заданном диапазоне.
Ещё присутствуют агрегирующие функции Sum, Avg и другие. Можно строить достаточно сложные графики, например график количества HTTP-ошибок:
Стандартный data source Loki несколько урезан по функциональности по сравнению с data source Prometheus (например, нельзя изменить легенду), но Loki можно подключить как источник с типом Prometheus. Я не уверен, что это документированное поведение, но, судя по ответу разработчиков “How to configure Loki as Prometheus datasource? · Issue #1222 · grafana/loki”, например, это вполне законно, и Loki полностью совместим с PromQL.
Добавляем Loki как data source с типом Prometheus и дописываем URL /loki:
И можно делать графики, как в том случае, если бы мы работали с метриками из Prometheus:
Я думаю, что расхождение в функциональности временное и разработчики в будущем это поправят.
Метрики
В Loki доступны возможность извлечения числовых метрик из логов и отправка их в Prometheus. Например, в логе nginx присутствует количество байтов на ответ, а также, при определённой модификации стандартного формата лога, и время в секундах, которое потребовалось на ответ. Эти данные можно извлечь и отправить в Prometheus.
Опция позволяет определять и обновлять метрики на основе данных из extracted map. Эти метрики не отправляются в Loki — они появляются в Promtail /metrics endpoint. Prometheus должен быть сконфигурирован таким образом, чтобы получить данные, полученные на этой стадии. В приведённом примере для request_type=“api” мы собираем метрику-гистограмму. С этим типом метрик удобно получать перцентили. Для статики и фото мы собираем сумму байтов и количество строк, в которых мы получили байты, чтобы вычислить среднее значение.
Таким образом можно узнать, например, четыре самых медленных запроса. Также на данные метрики можно настроить мониторинг.
Масштабирование
Loki может быть как в одиночном режиме (single binary mode), так и в шардируемом (horizontally-scalable mode). Во втором случае он может сохранять данные в облако, причём чанки и индекс хранятся отдельно. В версии 1.5 реализована возможность хранения в одном месте, но пока не рекомендуется использовать её в продакшене.
Чанки можно хранить в S3-совместимом хранилище, для хранения индексов — использовать горизонтально масштабируемые базы данных: Cassandra, BigTable или DynamoDB. Другие части Loki — Distributors (для записи) и Querier (для запросов) — stateless и также масштабируются горизонтально.
Для тестирования получаемого размера индекса я взял логи с контейнера nginx, для которого настраивался Pipeline, приведённый выше. Файл с логами содержал 406 624 строки суммарным объёмом 109 Мб. Генерировались логи в течение часа, примерно по 100 записей в секунду.
Пример двух строк из лога:
При индексации ELK это дало размер индекса 30,3 Мб:
В случае с Loki это дало примерно 128 Кб индекса и примерно 3,8 Мб данных в чанках. Стоит отметить, что лог был искусственно сгенерирован и не отличался большим разнообразием данных. Простой gzip на исходном докеровском JSON-логе с данными давал компрессию 95,4%, а с учётом того, что в сам Loki посылался только очищенный nginx-лог, то сжатие до 4 Мб объяснимо. Суммарное количество уникальных значений для лейблов Loki было 35, что объясняет небольшой размер индекса. Для ELK лог также очищался. Таким образом, Loki сжал исходные данные на 96%, а ELK — на 70%.
Потребление памяти
Если сравнивать весь стек Prometheus и ELK, то Loki «ест» в несколько раз меньше. Понятно, что сервис на Go потребляет меньше, чем сервис на Java, и сравнение размера JVM Heap Elasticsearch и выделенной памяти для Loki некорректно, но тем не менее стоит отметить, что Loki использует гораздо меньше памяти. Его преимущество по CPU не так очевидно, но также присутствует.
Скорость
Loki быстрее «пожирает» логи. Скорость зависит от многих факторов — что за логи, как изощрённо мы их парсим, сеть, диск и т. д. — но она однозначно выше, чем у ELK (в моём тесте — примерно в два раза). Объясняется это тем, что Loki кладёт гораздо меньше данных в индекс и, соответственно, меньше времени тратит на индексирование. Со скоростью поиска при этом ситуация обратная: Loki ощутимо притормаживает на данных размером более нескольких гигабайтов, у ELK же скорость поиска от размера данных не зависит.
Поиск по логам
Loki существенно уступает ELK по возможностям поиска по логам. Grep с регулярными выражениями — это сильная вещь, но он уступает взрослой базе данных. Отсутствие range-запросов, агрегация только по лейблам, невозможность искать без лейблов — всё это ограничивает нас в поисках интересующей информации в Loki. Это не подразумевает, что с помощью Loki ничего нельзя найти, но определяет флоу работы с логами, когда вы сначала находите проблему на графиках Prometheus, а потом по этим лейблам ищете, что случилось в логах.
Интерфейс
Во-первых, это красиво (извините, не мог удержаться). Grafana имеет приятный глазу интерфейс, но Kibana гораздо более функциональна.
Плюсы и минусы Loki
Из плюсов можно отметить, что Loki интегрируется с Prometheus, соответственно, метрики и алертинг мы получаем из коробки. Он удобен для сбора логов и их хранения с Kubernetes Pods, так как имеет унаследованный от Prometheus service discovery и автоматически навешивает лейблы.
Из минусов — слабая документация. Некоторые вещи, например особенности и возможности Promtail, я обнаружил только в процессе изучения кода, благо open-source. Ещё один минус — слабые возможности парсинга. Например, Loki не умеет парсить multiline-логи. Также к недостаткам можно отнести то, что Loki — относительно молодая технология (релиз 1.0 был в ноябре 2019 года).
Заключение
Loki — на 100% интересная технология, которая подходит для небольших и средних проектов, позволяя решать множество задач агрегирования логов, поиска по логам, мониторинга и анализа логов.
Мы не используем Loki в Badoo, так как имеем ELK-стек, который нас устраивает и который за много лет оброс различными кастомными решениями. Для нас камнем преткновения является поиск по логам. Имея почти 100 Гб логов в день, нам важно уметь находить всё и чуть-чуть больше и делать это быстро. Для построения графиков и мониторинга мы используем другие решения, которые заточены под наши нужды и интегрированы между собой. У стека Loki есть ощутимые плюсы, но он не даст нам больше, чем у нас есть, и его преимущества точно не перекроют стоимость миграции.
И хотя после исследования стало понятно, что мы Loki использовать не можем, надеемся, что данный пост поможет вам в выборе.
Репозиторий с кодом, использованным в статье, находится тут.
По умолчанию, метрики в Prometheus попадают методом pull — система обращается к агентам (exporter) и забирает данные. Это рекомендованный способ, но мы можем настроить получение метрик методом push с помощью Pushgateway.
Схема работы следующая: метрика отправляется скриптом на сервер с помощью Pushgateway, а Prometheus забирает данные уже с него.
Предполагается, что у нас уже установлен сервер Prometheus.
Установку Pushgateway можно выполнить на тот же сервер, что и Prometheus, либо на выделенный. В этой инструкции установим на тот же и под управлением CentOS 7.
2. Установка Pushgateway.
Сервис может быть запущен в качестве контейнера Docker. Однако, в данном руководстве, будет вариант со скачиванием бинарного файла и созданием юнита для его автозапуска.
Переходим на официальную страницу загрузки Pushgateway.
-A INPUT -p tcp -m tcp --dport 9091 -m state --state NEW -j ACCEPT
Перезапускаем iptables:
# sudo systemctl restart iptables
Проверяем, что метрики отдаются:
# curl 'localhost:9091/metrics'
Открываем браузер и вводим адрес:
# http://<IP-адрес сервера pushgateway>:9091
Вы должны увидеть меню портала:
Pushgateway готов к работе.
Переходим к настройке Prometheus.
3. Настройка Prometheus.
Необходимо добавить задание в Prometheus для подключения к серверу Pushgateway и получения с него метрик. Это делается по такому же принципу, что и получение метрик с экспортеров.
Удалите все метрики во всех группах (требуется включить API администратора с помощью флага командной строки --web.enable-admin-api в конфигурации systemd):
# curl -X PUT http://localhost:9091/api/v1/admin/wipe
5. Отправка метрик методом скрипта на Python.
В качестве примера, рассмотрим скрипт, написанный на Python.
Для начала, необходимо установить модуль prometheus_client:
В данном примере мы отправим в Prometheus через Pushgateway метрику temperature_metrics со значением 22 и job-тегом temperature_lobby.
6. Практическое применение Pushgateway.
Для операционной системы CentOS 7 есть отличная утилита hddtemp — инструмент для измерения температуры жестких дисков. Она проста в использовании и установке.