Grafana: Loki – Prometheus-like счётчики и функции агрегации в LogQL и графики DNS запросов к dnsmasq

Автор: | 16/11/2019

Последний раз Loki для сбора и наблюдения за логами настраивал аж в феврале этого (см. Grafana Labs: Loki — сбор и просмотр логов), когда Loki была ещё в beta-версии.

Сейчас возникли проблемы с исходящим трафиком (объём за два месяца вырос в 4 раза), никак не можем найти виновника.

Как один из вариантов поиска этого самого виновника – решили добавить сбор статистики по DNS-запросам, что бы посмотреть к каким URL выполняются обращения и попробовать найти корреляцию между OUT трафиком с хостов в AWS, и запросами к локальному dnsmasq.

Настройка самого dnsmasq описана в посте dnsmasq: ошибки в AWS — «Temporary failure in name resolution», логи, дебаг и размер кеша, а в этом посте попробуем реализовать следующее:

  • dnsmasq записывает все запросы в локальный файл лога
  • лог тейлится promtail-ом, который отправляет их на сервер мониторинга в Loki
  • а Grafana на основании метрик из Loki будет отрисовывать красивенькие дашборды со статистикой

Описанный ниже сетап – больше Proof of Concept, так как и сама Loki ещё активно разрабатывается, и её поддержка в Grafana реализована не полностью.

Чего стоит только добавление Loki как datasource, но… как Prometheus O.o Звучит странно, выглядит ещё интереснее.

Зато, Explore в Grafana теперь поддерживает работу с логами используя функциии агрегации аналогично Prometheus – sum(), rate() и так далее.

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

Сначала поднимем стек Grafana + Loki + promtail, потом подключим сбор логов с помощью promtail с нашего Production-хоста, и посмотрим как работают функции агрегации, и какие дашборды теперь можно делать.

“Поняслася!”

Запуск Loki

Запускать будем из Docker Compose, создаём файл loki-stack.yml:

version: '2.4'

networks:
  loki:

services:

  loki:
    image: grafana/loki:master-2739551
    ports:
      - "3100:3100"
    networks:
      - loki
    restart: unless-stopped

Запускаем:

[simterm]

root@monitoring-dev:/opt/loki# docker-compose -f loki-stack.yml up

[/simterm]

Проверяем:

[simterm]

root@monitoring-dev:/home/admin# curl localhost:3100/ready
Ready

[/simterm]

Loki API документация – тут>>>.

Запуск Grafana

Аналогично делаем с Grafana, используем 6.4.4 (см. доступные версии в Docker Hub):

version: '2.4'

networks:
  loki:

services:

  loki:
    image: grafana/loki:master-2739551
    ports:
      - "3100:3100"
    networks:
      - loki
    restart: unless-stopped

  grafana:
    image: grafana/grafana:6.4.4
    ports:
      - "3000:3000"
    networks:
      - loki
    restart: unless-stopped

Запускаем, проверяем:

Логинимся с admin:admin, переходим в Datasources:

Так как Loki в Docker сети – обращаемся к ней (нему?) по адресу http://loki:

NGINX

Сетап выполняется на уже существующем и настроенном Dev хосте мониторинга, тут уже всё есть.

Конфиг /etc/nginx/conf.d/dev.loki.example.com.conf выглядит так:

upstream grafana-loki {
    server 127.0.0.1:3000;
}

server {

    listen 80;
    server_name  dev.loki.example.com;

    # Lets Encrypt Webroot                                           
    location ~ /.well-known {

    root /var/www/html;
        allow all;
    }
    
    location / {
    
        allow 194.***.***.26/29;
        allow 91.***.***.78/32;
        allow 188.***.***.94/32;
        allow 78.***.***.191/32;
        allow 176.***.***.43/32;
        allow 10.0.10.0/24;
        deny  all;

        return 301 https://dev.loki.example.com$request_uri;
    }       
}

server {

    listen       443 ssl;
    server_name  dev.loki.example.com;

#    access_log  /var/log/nginx/dev.loki.example.com-access.log proxy;
    error_log /var/log/nginx/dev.loki.example.com-error.log warn;

#    auth_basic_user_file /var/www/dev.loki.example.com/.htpasswd;
#    auth_basic "Password-protected Area";

    allow 194.***.***.26/29;
    allow 91.***.***.78/32;
    allow 188.***.***.94/32;
    allow 78.***.***.191/32;
    allow 176.***.***.43/32;
    allow 10.0.10.0/24;
    deny  all;

    ssl_certificate /etc/letsencrypt/live/dev.loki.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/dev.loki.example.com/privkey.pem;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/nginx/dhparams.pem;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:ECDHE-RSA-AES128-GCM-SHA256:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
    ssl_session_timeout 1d;
    ssl_stapling on;
    ssl_stapling_verify on;

    location / {

        proxy_redirect          off;
        proxy_set_header        Host            $host;
        proxy_set_header        X-Real-IP       $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://grafana-loki$request_uri;
    }

}

Запуск promtail

Сейчас в Grafana Explore пусто, так как никаких логов в Loki не шлём.

Создаём конфиг для promtail/opt/loki/promtail.yml:

В client опять-таки указываем URL в виде http://loki:

server:

  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

client:

  url: http://loki:3100/loki/api/v1/push

scrape_configs:

  - job_name: messages
    static_configs:
    - targets:
        - localhost
      labels:
        job: all-logs
        env: dev
        host: monitoring-dev
        __path__: /var/log/*.log

Добавляем запуск promtail в Compose файл, маунтим конфиг и указываем command для promtail, что бы он знал каким файлом настроек ему пользоваться:

...
  promtail:
    image: grafana/promtail:master-2739551
    networks:
      - loki
    volumes:
      - /opt/loki/promtail.yml:/etc/promtail/promtail.yml
    command: 
      - '-config.file=/etc/promtail/promtail.yml'
    restart: unless-stopped

Проверяем.

Вывод promtail:

[simterm]

...
promtail_1  | level=info ts=2019-11-16T09:19:57.935528884Z caller=filetargetmanager.go:257 msg="Adding target" key="{env=\"dev\", host=\"monitoring-dev\", job=\"all-logs\"}"
promtail_1  | ts=2019-11-16T09:19:57.936230518Z caller=log.go:124 component=tailer level=info msg="Seeked /var/log/dpkg.log - &{Offset:0 Whence:0}"
promtail_1  | level=info ts=2019-11-16T09:19:57.936292402Z caller=tailer.go:77 component=tailer msg="start tailing file" path=/var/log/dpkg.log
...

[/simterm]

dpkg.log пошёл, окей.

И в Grafana Explore:

Бимба!

promtail и логи dnsmasq

Переходим на Production хост, проверяем доступ к Loki:

[simterm]

root@bttrm-production-console:/home/admin# curl http://dev.logger.example.com:3100/ready
Ready

[/simterm]

Создаём конфиг promtail-dev.yml:

server:

  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

client:

  url: http://dev.loki.example.com:3100/loki/api/v1/push

scrape_configs:

  - job_name: dnsmasq
    static_configs:
    - targets:
        - localhost
      labels:
        job: dnsmasq
        env: production
        host: bttrm-prod-console
        __path__: /var/log/dnsmasq.log

Обратите внимание, что ендпоинты Локи обновились со времени последнего поста – /loki/api/v1/push.

См. документацию по Loki API тут>>>.

Запускаем его просто без Docker Compose – у меня там полный стек мониторинга Prometheus, потом добавлю новый promtail нормально, так как сейчас просто смотрим как оно вообще будет работать:

[simterm]

root@bttrm-production-console:/opt/prometheus-client# docker run -ti -v /opt/prometheus-client/promtail-dev.yml:/etc/promtail/promtail.yml grafana/promtail:master-2739551 -config.file=/etc/promtail/promtail.yml
Unable to find image 'grafana/promtail:master-2739551' locally
master-2739551: Pulling from grafana/promtail
...
Status: Downloaded newer image for grafana/promtail:master-2739551
level=warn ts=2019-11-16T09:29:00.668750217Z caller=filetargetmanager.go:98 msg="WARNING!!! entry_parser config is deprecated, please change to pipeline_stages"
level=info ts=2019-11-16T09:29:00.669077956Z caller=server.go:121 http=[::]:9080 grpc=[::]:45421 msg="server listening on addresses"
level=info ts=2019-11-16T09:29:00.66921034Z caller=main.go:65 msg="Starting Promtail" version="(version=, branch=, revision=)"
level=info ts=2019-11-16T09:29:05.669176878Z caller=filetargetmanager.go:257 msg="Adding target" key="{env=\"production\", host=\"bttrm-prod-console\", job=\"dnsmasq\"}"

[/simterm]

Эм…

А почему не пошёл сбор логов? Должна быть строка вида “msg=”start tailing file” path=/var/log/dnsmasq.log“…

И что случилось с Loki?

Что за ошибка “Error connecting to datasource: Data source connected, but no labels received. Verify that Loki and Promtail is configured properly“?

Попробовать пересоздать контейнеры?

[simterm]

root@monitoring-dev:/opt/loki# docker rm loki_grafana_1 loki_promtail_1
loki_grafana_1
loki_promtail_1

[/simterm]

Пересоздал контейнеры – завелось, ок.

А логи не собирались, потому что забыл смонтировать /var/log в запускаемый контейнер – добавляем монтирование -v /var/log:/var/log в запуск promtail:

[simterm]

root@bttrm-production-console:/home/admin# docker run -ti -v /opt/prometheus-client/promtail-dev.yml:/etc/promtail/promtail.yml -v /var/log:/var/log grafana/promtail:master-2739551 -config.file=/etc/promtail/promtail.yml
level=warn ts=2019-11-16T09:48:02.248719806Z caller=filetargetmanager.go:98 msg="WARNING!!! entry_parser config is deprecated, please change to pipeline_stages"
level=info ts=2019-11-16T09:48:02.249227598Z caller=server.go:121 http=[::]:9080 grpc=[::]:39883 msg="server listening on addresses"
level=info ts=2019-11-16T09:48:02.249381673Z caller=main.go:65 msg="Starting Promtail" version="(version=, branch=, revision=)"
level=info ts=2019-11-16T09:48:07.249262647Z caller=filetargetmanager.go:257 msg="Adding target" key="{env=\"production\", host=\"bttrm-prod-console\"}"
level=info ts=2019-11-16T09:48:07.24943453Z caller=tailer.go:77 component=tailer msg="start tailing file" path=/var/log/dnsmasq.log
ts=2019-11-16T09:48:07.249544341Z caller=log.go:124 component=tailer level=info msg="Seeked /var/log/dnsmasq.log - &{Offset:0 Whence:0}"

[/simterm]

Пошли логи:

LogQL – Loki’s logs aggregation and counters

Вот тут уже начинается самое интересное – работа с LogQL и функциями агрегации и подсчёта.

Впрочем, пока получилось завести это – пришлось повозиться (документация Loki и Grafana, как всегда, отстаёт).

Loki “Internal Server Error”

Пробуем выполнить запрос типа count_over_time({job="dnsmasq"}[5m]), и:

Тут проблема из-за… Пробелов! :facepalm:

Поправляем – добавляем пробелы между скобками, но – теперь Grafana просто ничего не находит:

count_over_time( {job="dnsmasq"}[5m] )

Prometheus как… Loki? О.О

Было очень неожиданно такое увидеть 🙂 Да и в документации ничего не сказано, но подсмотрел в grafana.slack.com.

В Grafana 6.5 вроде должно уже работать нормально, но в 6.5-beta-1 ещё не сделано.

Переходим в Datasource, и добавляем Prometheus – но как Loki.

Или наоборот – добавляем Loki, но как Prometheus?

В общем, выбираем тип Prometheus, а в URL указываем http://loki:310/loki – с /loki в конце:

И проверяем:

Няяяшка!

rate()

Попробуем использование функций, например – rate() + регулярку с выборкой хостов, к которым обращаемся:

Отлично.

Кстати – Grafana сама подставляет функции и сразу их описания:

promtail pipeline stages

Ещё одна интересная плюшка в promtail – раньше её, кажется, не было – pipeline stages.

См. документацию тут>>>.

В оригинале:

A pipeline is used to transform a single log line, its labels, and its timestamp. A pipeline is comprised of a set of stages. There are 4 types of stages:

  1. Parsing stages parse the current log line and extract data out of it. The extracted data is then available for use by other stages.
  2. Transform stages transform extracted data from previous stages.
  3. Action stages take extracted data from previous stages and do something with them. Actions can:
    1. Add or modify existing labels to the log line
    2. Change the timestamp of the log line
    3. Change the content of the log line
    4. Create a metric based on the extracted data

Filtering stages optionally apply a subset of stages or drop entries based on some condition.

То есть – строим пайплайн, который состоит из стейджев.

Стейджи бывают 4 типов:

  1. Parsing stages: парсит лог и извлекает данные, которые потом можно передать в дальнейшие стейджи
  2. Transform stages: трансформирует полученные от предыдущих стейджев данные
  3. Action stages: получает данные от предыдущих стейдж и делает что-то:
    • добавляет или удаляет лейблы
    • меняет таймштамп
    • меняет содержимое строки лога
    • создаёт метрику на основании извлечённых данных

Typical pipelines will start with a parsing stage (such as a regex or json stage) to extract data from the log line. Then, a series of action stages will be present to do something with that extracted data. The most common action stage will be a labels stage to turn extracted data into a label.


Итак, вернёмся к началу – чего мы хотим?

Мы хотим получить от dnsmasq все запросы IN A записей, извлечь из этих запросов имена хостов, и отобразить графиком – к какому доменному имени сколько запросов выполняется.

Значит, надо:

  • получить все запросы IN A
  • сохранить каждый в label
  • и потом подсчитать их

Идём к promtail на Production, и добавляем стейдж в нашу джобу – обновляем конфиг promtail-dev.yml:

server:

  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

client:

  url: http://dev.loki.example.com:3100/loki/api/v1/push

scrape_configs:

  - job_name: dnsmasq
    static_configs:
    - targets:
        - localhost
      labels:
        job: dnsmasq
        env: production
        host: bttrm-prod-console
        __path__: /var/log/dnsmasq.log

    pipeline_stages:
    - match:
        selector: '{job="dnsmasq"}'
        stages:
        - regex:
            expression: ".*query\\[A\\] (?P<query>.*\\s)"
        - labels:
            query:

В pipeline_stages делаем:

  1. выбираем джобу dnsmasq
  2. описываем стейдж regex, в котором выбираем все строки из лога, в которых есть строка query[A]
    1. далее в запросе создаём регекс группу query, в которую сохраняем строку до первого пробела
      оригинал строки:
      Nov 16 08:23:33 dnsmasq[17597]: query[A] backend-db3-master.example.com from 127.0.0.1
      в группе query получим результат:
      backend-db3-master.example.com
  3. описываем стейдж labels, в котором добавляем label query со значением backend-db3-master.example.com

Запускаем promtail:

[simterm]

root@bttrm-production-console:/home/admin# docker run -ti -v /opt/prometheus-client/promtail-dev.yml:/etc/promtail/promtail.yml -v /var/log:/var/log grafana/promtail:master-2739551 -config.file=/etc/promtail/promtail.yml
level=info ts=2019-11-16T11:56:29.760425279Z caller=server.go:121 http=[::]:9080 grpc=[::]:32945 msg="server listening on addresses"
level=info ts=2019-11-16T11:56:29.760565845Z caller=main.go:65 msg="Starting Promtail" version="(version=, branch=, revision=)"
level=info ts=2019-11-16T11:56:34.760567558Z caller=filetargetmanager.go:257 msg="Adding target" key="{env=\"production\", host=\"bttrm-prod-console\", job=\"dnsmasq\"}"
level=info ts=2019-11-16T11:56:34.760752715Z caller=tailer.go:77 component=tailer msg="start tailing file" path=/var/log/dnsmasq.log
ts=2019-11-16T11:56:34.760863031Z caller=log.go:124 component=tailer level=info msg="Seeked /var/log/dnsmasq.log - &{Offset:0 Whence:0}"

[/simterm]

Проверяем борду Grafana:

И теперь попробуем сформировать запрос:

sum (rate( ( {env="production",query=~".*\\..*"} )[5m] )) by (query)

В query=~".*\\..*" я немного накостылял, что бы убрать из вывода метрики в которых query нет, но должен быть более правильный вариант. Пока “И так сойдёт” (с)

Смотрим:

Агонь!

Так…

В именах хостов, например api.amplitude.com from – остаётся from из лога.

Почему?

Используем https://regex101.com, фиксим регулярку, получается:

.*query\[A\] (?P<query>[^\s]+)

Обновляем конфиг promtail:

...
    pipeline_stages:
    - match:
        selector: '{job="dnsmasq"}'
        stages:
        - regex:
            expression: ".*query\\[A\\] (?P<query>[^\\s]+)"
        - labels:
            query:

И надо бы как-то убрать метрики без лейблы query

Grafana DNS dashboard

Окей, в целом – всё понятно, давайте попробуем теперь нарисовать дашборду, в которой можно будет выводить статистику по DNS-запросам.

Кликаем Add query:

Задаём наш запрос:

sum (rate( ( {env="production", query=~".*\\..*"} )[5m] )) by (query)

в Legend используем подстановку из {{ query }} , что бы вывести только значение:

Окей, неплохо.

Добавим переменных, что бы можно было выбирать запросы.

Переходим в Dashboard Settings > Variables > Add variable, и…

Template variables ещё не поддерживаются для Loki…

Или я таки не разобрался, как вызвать например label_values() для Loki…

Документация тут>>>.

Хотелось сделать переменную со значениями из лейблы query, что бы была возможность выбирать конкретное доменное имя, но – увы.

Ладно.

Сделаем пока хотя бы возможность самому в дашборде задать фильтр.

Создаём переменную типа Text box:

И для выбора рабочего окружения – переменную типа Custom:

Возвращаемся к запросу, обновляем его:

sum (rate( ( {env="$env", query=~"$include"} )[5m] )) by (query)

Или с фильтром по домену:

В результате получилась такая вот борда с красивыми графиками:

Ссылки по теме