Prometehus: обзор – federation, мониторинг Docker Swarm и настройки Alertmanager

By | 04/09/2018
 

Задача – настроить Proof of Concept мониторинга, используя Prometheus, что бы показать основные его возможности.

Используется Prometheus 2.2.1 (между 1.* и 2.* существенные различия в синтаксисе).

Чего не добавлено в этот пост – это работа с Prometheus API, и хотелось подробнее остановиться на Grafana и её шаблонах, но уже отдельным постом. Кроме того, в самом тексте и в конце поста пачка ссылок, которые использовались во время написания.

Поднимать будем локально, на Virtualbox машинах.

Схема будет следующая:

  • 1 машина, jm-prometheus-server – для самого Prometheus сервера, там же Alertmanager
  • 2 машины под Docker Swarm, на каждом хосте которого будут запущены:
    • cadvisor: для получения метрик Docker-контейнеров
    • node-exporter: для получения метрик с виртуальных машин
    • плюс отдельные контейнеры на хостах:
      • хост jm-swarm-manager, тут будут:
        • blackbox-exporter: для проверки сайтов
        • prometheus: будет собирать метрики из Docker Swarm сервисов, и передавать их на Prometheus сервер на хосте jm-prometheus-server
      • хост jm-swarm-worker, тут будет:
        • nginx: для тестов blackbox

Подготовка

Запуск Virutalbox машины

Используем VirtualBox с Debian.

Запустим машину, установим на неё всё необходимое, потом склонируем.

Для упрощения запуска машины из консоли – можно использовать такой bash-скрипт:

#!/usr/bin/env bash
  
BOXNAME=$1
[[ $BOXNAME ]] || { echo -e "\nERROR: set VM name as first argument. Exit.\n"; exit 1; }

# register VM
VBoxManage createvm --name "$BOXNAME" --register

# set network interfaces
VBoxManage modifyvm "$BOXNAME" --nic1 bridged --bridgeadapter1 enp0s25 --nictype1 82540EM --cableconnected1 on

# set OS type
VBoxManage modifyvm "$BOXNAME" --ostype Debian_64

# create HDD
cd /home/setevoy/VirtualBox\ VMs/"$BOXNAME"/ && VBoxManage createhd --filename "$BOXNAME".vdi --size 10000

# add IDE
VBoxManage storagectl "$BOXNAME" --name "IDE Controller" --add ide

# attach disk
VBoxManage storageattach "$BOXNAME" --storagectl "IDE Controller"  --port 0 --device 0 --type hdd --medium "$BOXNAME".vdi

# attach ISO
VBoxManage storageattach "$BOXNAME" --storagectl IDE Controller" --port 1 --device 0 --type dvddrive --medium /home/setevoy/OS/debian-9.4.0-amd64-netinst.iso

# set memory
VBoxManage modifyvm "$BOXNAME" --memory 2048

# start VM
VBoxManage startvm "$BOXNAME"

Запускаем машину:

./mk_vboxes.sh debian_docker_base_vm
Virtual machine 'debian_docker_base_vm' is created and registered.
UUID: 1b09be7a-7dd3-49b8-afcc-27daccd0fb78
Settings file: '/home/setevoy/VirtualBox VMs/debian_docker_base_vm/debian_docker_base_vm.vbox'
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Medium created. UUID: 9dbd7c1b-4546-4821-94e9-4e82e1c45836
Waiting for VM "debian_docker_base_vm" to power on...
VM "debian_docker_base_vm" has been successfully started.

Устанавливаем ОС как обычно:

Переходим к установке Docker.

Установка Docker

Обновляем систему:

root@debian:/home/setevoy# apt update && apt -y upgrade && reboot

Устанавливаем зависимости:

root@debian:/home/setevoy# apt -y install apt-transport-https ca-certificates curl gnupg2 software-properties-common sudo

Добавляем GPG ключ Docker:

root@debian:/home/setevoy# curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -

Добавляем репозиторий Docker:

root@debian:/home/setevoy# add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"

Обновляем списки пакетов:

root@debian:/home/setevoy# apt update

Устанавливаем Docker:

root@debian:/home/setevoy# apt -y install docker-ce

Проверяем:

root@debian:/home/setevoy# docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
ca4f61b1923c: Pull complete
Digest: sha256:97ce6fa4b6cdc0790cda65fe7290b74cfebd9fa0c9b8c38e979330d547d22ce1
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
...

Добавляем пользователя setevoy в группу docker:

root@debian:/home/setevoy# usermod -aG docker setevoy

Установка Docker Compose

Устанавливаем Compose:

root@debian:/home/setevoy# curl -L https://github.com/docker/compose/releases/download/1.20.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
root@debian:/home/setevoy# chmod +x /usr/local/bin/docker-compose

Проверяем:

root@debian:/home/setevoy# docker-compose --version
docker-compose version 1.20.1, build 5d8c71b

Клонирование Virtualbox машин

Находим запущенную машину:

vboxmanage list runningvms
"debian_docker_base_vm" {1b09be7a-7dd3-49b8-afcc-27daccd0fb78}

Выключаем её:

vboxmanage controlvm debian_docker_base_vm acpipowerbutton

Создаём три клона – машину для Prometheus сервера:

vboxmanage clonevm debian_docker_base_vm --register --name jm_prometheus_server
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Machine has been successfully cloned as "jm_prometheus_server"

Для Docker Swarm менеджера:

vboxmanage clonevm debian_docker_base_vm --register --name jm_swarm_manager
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Machine has been successfully cloned as "jm_swarm_manager"

И для Docker Swarm worker:

vboxmanage clonevm debian_docker_base_vm --register --name jm_swarm_worker
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Machine has been successfully cloned as "jm_swarm_worker"

Проверяем:

vboxmanage list vms | grep jm
"jm_prometheus_server" {d47b0810-ce8a-43e1-b316-6659a5f91914}
"jm_swarm_manager" {ddefa9c7-88b9-40d8-9d4a-ace5f0194a50}
"jm_swarm_worker" {74e5559a-117c-4df8-b401-f5a030d85d5e}

И запускаем их:

vboxmanage startvm jm_prometheus_server
Waiting for VM "jm_prometheus_server" to power on...
VM "jm_prometheus_server" has been successfully started.
vboxmanage startvm jm_swarm_manager
Waiting for VM "jm_swarm_manager" to power on...
VM "jm_swarm_manager" has been successfully started.
vboxmanage startvm jm_swarm_worker
Waiting for VM "jm_swarm_worker" to power on...
VM "jm_swarm_worker" has been successfully started.

Можно добавить --type headless, что бы не создавать окна.

Логинимся, задаём имена хостов:

root@debian:/home/setevoy# hostnamectl set-hostname jm-prometheus-server
root@debian:/home/setevoy# hostname jm-prometheus-server

Повторяем на менеджере и воркере.

Создание Docker Swarm

Теперь переходим к Docker Swarm.

Логинимся на manager-хост, создаём Swarm:

root@jm-swarm-manager:/home/setevoy# docker swarm init
Swarm initialized: current node (mtdra23bnqjacjehpe584nqw7) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-602pa1w2n4wtwk9r99q55un03y3wix2digvcden0y5kzsobbru-2yru5erpvih0qd6txr86emb6s 10.11.100.194:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

Логинимся на worker, подключаем его к swarm:

root@jm-swarm-worker:/home/setevoy# docker swarm join --token SWMTKN-1-602pa1w2n4wtwk9r99q55un03y3wix2digvcden0y5kzsobbru-2yru5erpvih0qd6txr86emb6s 10.11.100.194:2377
This node joined a swarm as a worker.

На менеджере проверяем ноды:

root@jm-swarm-manager:/home/setevoy# docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
mtdra23bnqjacjehpe584nqw7 *   jm-swarm-manager    Ready               Active              Leader              18.03.0-ce
emcxnaqamthemtrljg1i941ea     jm-swarm-worker     Ready               Active                                  18.03.0-ce

Prometheus

Основы Prometheus

Prometheus – pull-based система мониторинга, т.е. сервер обращается к своим клиентам для получения данных (см. Prometheus and the Debate Over ‘Push’ Versus ‘Pull’ Monitoring).

В Prometheus такие клиенты, т.е. цели, которые надо мониторить, называются targets (собственно – цели), или instances, а группа инстансов, выполняющая одну роль в терминологии Prometheus называется job.

В определённые в конфигурации промежутки времени Prometheus будет выполнять запросы (scrape) к этим таргетам (инстансам) для получения от них метрик, которые, как правило, доступны через URI /metric.

Например – у Prometheus-сервера доступны его собственные метрики.

Запускаем контейнер с Prometheus, и проверяем:

root@jm-prometheus-server:/home/setevoy# docker run -tid -p 9090:9090 prom/prometheus
root@jm-prometheus-server:/home/setevoy# curl -s localhost:9090/metrics | head -5
HELP go_gc_duration_seconds A summary of the GC invocation durations.
TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 1.4375e-05
go_gc_duration_seconds{quantile="0.25"} 2.2796e-05
go_gc_duration_seconds{quantile="0.5"} 2.7988e-05

А node_exporter – предоставляет свои метрики:

root@jm-prometheus-server:/home/setevoy# docker run -tid -p 9100:9100 prom/node-exporter
root@jm-prometheus-server:/home/setevoy# curl -s localhost:9100/metrics | grep node | grep -v \# | head -5
node_arp_entries{device="eth0"} 1
node_boot_time 1.522487906e+09
node_context_switches 212022
node_cpu{cpu="cpu0",mode="guest"} 0
node_cpu{cpu="cpu0",mode="guest_nice"} 0
Метрики

Каждая метрика возвращает некий ключ и значение, которые будут сохранены в timeseries-базу данных Prometheus-а (TSDB).

Формат метрик Prometehus:

<metric name>{<label name>=<label value>, ...}

Например:

http_requests_total{service="service", server="pod50", env="production"}

Storage

По умолчанию Prometheus использует локальное хранилище для данных, в виде time-series базы данных собственной разработки, но поддерживает интеграцию и с другими базами – см. Remote Endpoints and Storage.

Собранные данные группируются в блоки по два часа: каждый такой блок состоит из каталога, который содержит один или несколько chunk-файлов с time-series данными за этот период времени, файл метаданных и файл индекса, который содержит имена метрик и метки (labels), например:

root@jm-prometheus-server:/home/setevoy# docker exec -ti adoring_pasteur ls -l /prometheus/
total 12
drwxr-xr-x    3 nobody   nogroup       4096 Mar 31 11:00 01C9XVE1AAJ287MTJ0FR9EPP1M
-rw-------    1 nobody   nogroup          2 Mar 31 09:30 lock
drwxr-xr-x    2 nobody   nogroup       4096 Mar 31 09:30 wal

И содержимое каталога 01C9XVE1AAJ287MTJ0FR9EPP1M:

root@jm-prometheus-server:/home/setevoy# docker exec -ti adoring_pasteur ls -l /prometheus/01C9XVE1AAJ287MTJ0FR9EPP1M
total 80
drwxr-xr-x    2 nobody   nogroup       4096 Mar 31 11:00 chunks
-rw-r--r--    1 nobody   nogroup      66320 Mar 31 11:00 index
-rw-r--r--    1 nobody   nogroup        275 Mar 31 11:00 meta.json
-rw-r--r--    1 nobody   nogroup          9 Mar 31 11:00 tombstones

См. Storage и Prometheus 2.0: New storage layer dramatically increases monitoring scalability for Kubernetes and other distributed systems.

Exporters

Если приложение или сервер сам по себе не возвращают метрики – их можно получить с помощью экспортёров, например:

  • blackbox-exporter: используется для проверки URL, DNS, TCP
  • node-exporter: для получения метрик системы, таких как потребление CPU, памяти, диска и т.д.
  • cAdvisor: для получения метрик о работе Docker engine и контейнеров, которые на нём запущены

Список различных экспортёров можно найти тут>>>.

Prometheus конфиг

В файле настроек Prometheus описываются его собственные настройки, такие как интервал для выполнения запросов к таргетам, и сами таргеты, объединённые в задачи (jobs).

Таргеты могут определяться через статический конфиг, например:

...
scrape_configs:
  - job_name: 'prometheus'
    scrape_interval: 15s
    scrape_timeout: 10s
    metrics_path: /metrics
    scheme: http
    static_configs:
    - targets:
      - 10.11.100.194:9100

Тут мы указываем опрашивать URI /metrics по HTTP каждые 15 секунд, используя статический IP swarm-воркера 10.11.100.194 и порт 9100:

Либо динамический:

...
scrape_configs:
  - job_name: 'prometheus'
    scrape_interval: 15s
    scrape_timeout: 10s
    metrics_path: /metrics
    scheme: http
#    static_configs:
#    - targets:
#      - 10.11.100.194:9100
    dns_sd_configs:
    - names:
      - node-exporter
      refresh_interval: 30s
      type: A
      port: 9100

В случае динамической конфигурации – Prometheus выполнит DNS запрос к Docker (sd в dns_sd_configsService Discovery), и начнёт собирать метрики с каждого найденного контейнера с node-exporter:

В этом примере ещё нет Swarm-а и запущен только один контейнер в Compose, но сейчас мы это исправим.

Запуск Prometheus стека

Для того, что бы наш Prometheus сервер мог собирать метрики с сервисов в Docker Swarm – можно использовать либо prometheus_proxy, либо exporter_proxy, либо воспользоваться federation – когда два Prometheus сервера работают вместе, и один из них – “основной” – собирает метрики со второго. Второй, в свою очередь, работает в Docker Swarm, и имеет доступ к его service discovery, что позволяет использовать динамические конфигурации.

Основной сервер Prometheus будет на хосте jm-prometheus-server, а запускать его будем из Docker Compose.

Обновляем prometheus.yml:

global:
  scrape_interval:     15s
  evaluation_interval: 15s

alerting:
  alertmanagers:
  - static_configs:
    - targets:

rule_files:

scrape_configs:
  - job_name: 'prometheus'
    scheme: http
    static_configs:
    - targets:
      - node-exporter:9100
      - prometheus-server:9090

Создаём базовый Compose файл, например prometheus-stack.yml, позже к нему добавим остальные сервисы (Grafana, Alertmanager, blackbox-expoter):

version: '3.3'

networks:
  prometheus:

services:

  prometheus-server:
    image: prom/prometheus
    networks:
      - prometheus
    ports:
      - 9090:9090
    volumes:
      - /home/setevoy/prometheus.yml:/etc/prometheus/prometheus.yml

  node-exporter:
    image: prom/node-exporter
    networks:
      - prometheus
    ports:
      - 9100:9100
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - --collector.filesystem.ignored-mount-points
      - "^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)"

Запускаем:

root@jm-prometheus-server:/home/setevoy# docker-compose -f prometheus-stack.yml up -d
Starting setevoy_node-exporter_1     ... done
Starting setevoy_prometheus-server_1 ... done

Проверяем:

Prometheus cross-service federation

Теперь в Docker Swarm на менеджер-ноде запустим второй Prometheus сервер, который будет собирать метрики из сервисов в Swarm, и отдавать их основному серверу – это и есть Cross-service federation.

На Swarm Manager создаём Compose файл, например foo-services.yml:

networks:
  foo-services:
    driver: overlay

services:

  prometheus-server:
    image: prom/prometheus
    networks:
      - foo-services
    ports:
      - 9090:9090
    volumes:
      - /home/setevoy/prometheus.yml:/etc/prometheus/prometheus.yml
    deploy:
      placement:
        constraints:
          - node.role == manager

  node-exporter:
    image: prom/node-exporter
    networks:
      - foo-services
    ports:
      - 9100:9100
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - --collector.filesystem.ignored-mount-points
      - "^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)"
    deploy:
      mode: global

Тут же создаём /home/setevoy/prometheus.yml, в котором используем dns_sd_configs для поиска сервисов с именем node-exporterв сети foo-services:

global:
  scrape_interval:     15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'foo-services'
    scrape_interval: 15s
    scrape_timeout: 10s
    metrics_path: /metrics
    scheme: http
    dns_sd_configs:
    - names:
      - tasks.node-exporter
      refresh_interval: 30s
      type: A
      port: 9100

Создаём стек:

root@jm-swarm-manager:/home/setevoy# docker stack deploy -c foo-services.yml foo-services
Creating network foo-services_foo-services
Creating service foo-services_node-exporter
Creating service foo-services_prometheus-server

Проверяем:

root@jm-swarm-manager:/home/setevoy# docker service ls
ID                  NAME                             MODE                REPLICAS            IMAGE                       PORTS
1gm920gcb5if        foo-services_node-exporter       global              2/2                 prom/node-exporter:latest   *:9100->9100/tcp
yqqoxacvgak3        foo-services_prometheus-server   replicated          1/1                 prom/prometheus:latest      *:9090->9090/tcp

Проверяем – где у нас запущены node-exporter-ы:

root@jm-swarm-manager:/home/setevoy# docker service ps foo-services_node-exporter
ID                  NAME                                                   IMAGE                       NODE                DESIRED STATE       CURRENT STATE           ERROR               PORTS
mnsr87po5wft        foo-services_node-exporter.rjz4b1n4w62hb4m0ghih8s33l   prom/node-exporter:latest   jm-swarm-worker     Running             Running 6 minutes ago
o6ophn50froy        foo-services_node-exporter.hi7bswznodxgc489g3o5gs9uy   prom/node-exporter:latest   jm-swarm-manager    Running             Running 6 minutes ago

Проверяем DNS – выполняем nslookup из контейнера:

root@jm-swarm-manager:/home/setevoy# docker exec -ti 2afdc8a632bc nslookup tasks.node-exporter
Server:    127.0.0.11
Address 1: 127.0.0.11
Name:      tasks.node-exporter
Address 1: 10.0.0.7 2afdc8a632bc
Address 2: 10.0.0.8 foo-services_node-exporter.rjz4b1n4w62hb4m0ghih8s33l.mnsr87po5wftbfc4jz62jatuw.foo-services_foo-services

И проверяем targets в Prometheus на swarm-manager-е:

Настройка federation

Возвращаемся к хосту jm-prometheus-server, и обновляем основной Prometeus-сервер – в prometheus.yml указываем federation, полностью сейчас он выглядит так:

global:
  scrape_interval:     15s
  evaluation_interval: 15s

alerting:
  alertmanagers:
  - static_configs:
    - targets:

rule_files:

scrape_configs:

  - job_name: 'prometheus'
    scheme: http
    static_configs:
    - targets:
      - node-exporter:9100
      - prometheus-server:9090


  - job_name: 'federate-foo-services'
    honor_labels: true
    metrics_path: '/federate'
    params:
      'match[]':
        - '{job="foo-services"}'
    static_configs:
    - targets:
      - 10.11.100.197:9090

Запускаем:

root@jm-prometheus-server:/home/setevoy# docker-compose -f prometheus-stack.yml up -d
Starting setevoy_node-exporter_1     ... done
Starting setevoy_prometheus-server_1 ... done

Проверяем на нём targets:

И метрики из Swarm:

ОК, продолжаем.

Запуск cAdvisor и NGINX

(тут я рестартовал систему на ноутбуке, так что далее в посте IP на скришотах и в конфигах поменялись)

Добавим ещё два сервиса в Swarm – cAdvisor и NGINX.

На менеджер-ноде обновляем foo-services.yml, добавляем сервис cAdvisor, деплоим его globally, и сервис nginx, который деплоим только на worker и который будем “мониторить” и выполнять над ним relabeling:

...
  cadvisor:
    image: google/cadvisor
    networks:
      - foo-services
    ports:
      - 8080:8080
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:rw
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    deploy:
      mode: global
      resources:
        limits:
          memory: 100M

  nginx-service:
    image: nginx
    networks:
      - foo-services
    ports:
      - 80:80
    deploy:
      placement:
        constraints:
          - node.role == worker

Обновляем prometheus.yml – добавляем сбор метрик из cAdvisor:

global:
  scrape_interval:     15s
  evaluation_interval: 15s

scrape_configs:

  - job_name: 'foo-services'
    scrape_interval: 15s
    scrape_timeout: 10s
    metrics_path: /metrics
    scheme: http
    dns_sd_configs:
    - names:
      - tasks.node-exporter
      refresh_interval: 30s
      type: A
      port: 9100
    - names:
      - tasks.cadvisor
      refresh_interval: 30s
      type: A
      port: 8080

Обновляем стек:

root@jm-swarm-manager:/home/setevoy# docker stack deploy -c foo-services.yml foo-services
Updating service foo-services_prometheus-server (id: yqqoxacvgak3gvo1pqi0v4igr)
Updating service foo-services_node-exporter (id: 1gm920gcb5ifk1pbzjtm2pv2x)
Creating service foo-services_cadvisor
Creating service foo-services_nginx-service

Проверяем:

root@jm-swarm-manager:/home/setevoy# docker service ls
ID                  NAME                             MODE                REPLICAS            IMAGE                       PORTS
7q5rc0pc0jah        foo-services_cadvisor            global              2/2                 google/cadvisor:latest
isr4n51ranmv        foo-services_nginx-service       replicated          1/1                 nginx:latest                *:80->80/tcp
ylqsbcxa9s4x        foo-services_node-exporter       global              2/2                 prom/node-exporter:latest   *:9100->9100/tcp
xbeolaxggjvo        foo-services_prometheus-server   replicated          1/1                 prom/prometheus:latest      *:9090->9090/tcp

Relabeling

Relabeling позволяет переопределять labels для таргетов и метрик, которые собираются Prometheus.

Замена значений меток

На скриншоте выше в Labels (“метки”) мы видим instance=”10.0.0.*:9100“:

С помощью relabaling мы можем тут изментиь имя на что-то более понятное, чем далее можно пользоваться при составлении графиков и настроек алертов, т.к. сам Prometheus для подключения использует данные из лейбла __address__. Если метка instance не содержит значения – Prometheus подставит в неё значение из метки __address__.

Итак – заменим label “instance” со значения __address__ на значение из мета-метки __meta_dns_name, которая доступна для dns_sd_configs.

Редактируем prometheus.yml на jm-swarm-manager, где работает наш “вторичный” Prometheus сервер, и добавляем relabel_configs:

...
scrape_configs:

  - job_name: 'foo-services'
    scrape_interval: 15s
    scrape_timeout: 10s
    metrics_path: /metrics
    scheme: http
    relabel_configs:
      - source_labels: [__meta_dns_name]
        target_label: instance
...

Перезапускаем сервис, проверяем:

Редактирование меток

Но теперь в Graphs мы не сможем определить на каком именно интансе большая нагрузка на CPU:

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

Для начала – вырежем tasks из имени интанса – используем replacement:

...
    relabel_configs:
      - source_labels: [__meta_dns_name]
        regex: '^tasks\.(.*).*'
        target_label: instance
        replacement: ${1}
...

И, к примеру – добавим к метке instance приватный IP и порт контейнера:

...
    relabel_configs:
      - source_labels: [__meta_dns_name, __address__]
        separator: ';'
        regex: '^tasks\.(.*).*;(.*)'
        target_label: instance
        replacement: ${1}:${2}
...

Добавление меток

Но так выглядит не слишком приятно, да и сортировка не слишком удобная – просто добавим новую метку “nodeip”:

...
    relabel_configs:
      - source_labels: [__meta_dns_name]
        separator: ';'
        regex: '^tasks\.(.*).*'
        target_label: instance
        replacement: ${1}
      - source_labels: [__address__]
        target_label: "nodeip"
...
Удаление метрик по имени

Кроме relabel_configs у Prometheus так же есть возможность работы с собираемыми метриками, используя metric_relabel_configs.

Используя его можно “на входе” удалять ненужны метрики, что бы не забивать базу Prometheus ненужными данными.

Например – удалять все метрики с метками container_network_receive_errors_total и container_network_transmit_errors_total.

Проверяем список метрик сейчас:

Обновляем конфиг, добавляем metric_relabel_configs и ction: drop:

...
    relabel_configs:
      - source_labels: [__meta_dns_name]
        separator: ';'
        regex: '^tasks\.(.*).*'
        target_label: instance
        replacement: ${1}
      - source_labels: [__address__]
        target_label: "nodeip"

    metric_relabel_configs:
      - source_labels: [__name__]
        regex: '(container_network_receive_errors_total|container_network_transmit_errors_total)$'
        action: drop
...

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

Всё – метрик container_network_receive_errors_total и container_network_transmit_errors_total нет.

Удаление метрик по метке

Кроме удаления метрики полностью – можно удалять только часть результатов.

Например – удалить все результаты метрики container_network_tcp_usage_total, у которых есть метка id со значением /system.slice*:

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

...
    metric_relabel_configs:

      - source_labels: [__name__]
        regex: '(container_network_receive_errors_total|container_network_transmit_errors_total)$'
        action: drop

      - source_labels: [id]
        regex: '^/system.slice.*$'
        action: drop
...

Проверяем:

Удаление метки

Кроме всего прочего – можно удалить и метку, например – если в ней содержится секретная информация, которую вы не хотите делать доступной.

Вырежем метку container_label_com_docker_swarm_node_id:

Обновляем конфиг, и используем labeldrop:

...
      - regex: 'container_label_com_docker_swarm_node_id'
        action: labeldrop
...

Проверяем:

См. больше действий в документации>>>.

blackbox-exporter

blackbox-exporter может использоваться для проверки удалённых сервисов по HTTP(S), DNS, ICMP и TCP.

Добавим его для проверки кода ответа запущенного ранее NGINX, плюс будем выполнять ping удалёного хоста.

Для настройки проверок у blackbox-exporter имеется свой файл конфигурации, в котором описываются “модули”, которые далее могут использоваться файле настроек Prometheus сервера.

На хосте jm-swarm-manager создаём файл настроек для blackbox-exporter, например  blackbox-exporter.yml, в котором добавляем модули:

modules:
  icmp:
    prober: icmp
    timeout: 5s

  http_200_module:
    prober: http
    timeout: 5s
    http:
      valid_status_codes: []
      method: GET
      no_follow_redirects: false
      fail_if_ssl: false
      fail_if_not_ssl: false
      fail_if_not_matches_regexp:
        - "DevOps"

Модуль icmp – всё понятно, а в модуле http_200_module мы проверяем ответ сервера (valid_status_codes: [] – по умолчанию 200), а в fail_if_not_matches_regexp – ищем слово “DevOps” в ответе (оно есть в заголовке и meta теге RTFM блога).

Добавляем запуск контейнера в Compose файл – foo-services.yml:

...
  blackbox-exporter:
    image: prom/blackbox-exporter
    command: '--config.file=/config/blackbox.yml'
    # for debug
    #command: '--config.file=/config/blackbox.yml --log.level=debug'
    networks:
      - foo-services
    volumes:
      - /home/setevoy/blackbox-exporter.yml:/config/blackbox.yml
...

Теперь для Prometheus надо создать новую job, в которой используем модули blackbox:

...
  - job_name: 'blackbox'
    metrics_path: /probe
    params:
      module:
        - http_200_module
        - icmp
    static_configs:
      - targets:
        - nginx-service
        - http://rtfm.co.ua
        - https://rtfm.co.ua
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: blackbox-exporter:9115

Перезапускаем cтек, и проверяем:

blackbox-exporter отдаёт набор метрик probe-*:

И результат проверки fail_if_not_matches_regexp: "DevOps":

В теле ответа от NGINX в Swarm слова “DevOps” нет, потому статус “Down“, а rtfm.co.ua – “Up“.

Запросы от Prometheus  в логах RTFM:

194.***.***.45 – – [05/Apr/2018:15:35:07 +0300] “GET / HTTP/1.1” 200 125349 “-” “Go-http-client/1.1” “-” “rtfm.co.ua” sn=”rtfm.co.ua” rt=0.619 ua=”unix:/var/run/rtfm-php-fpm.sock” us=”200″ ut=”0.609″ ul=”125358″ cs=-

Больше примеров конфигурации blackbox-exporter – в его example-config.

Обновление корневого Prometheus

Все эти настройки мы выполнили на “вторичном” Prometheus – теперь надо всё это добавить на основной сервер, на котором далее уже будем настраивать Alertmanager и Grafana.

Возвращаемся к хосту jm-prometheus-server, обновляем его prometheus.yml, добавляем ещё одно условие в match:

...
  - job_name: 'federate-foo-services'
    honor_labels: true
    metrics_path: '/federate'
    params:
      'match[]':
        - '{job="foo-services"}'
        - '{job="blackbox"}'
...

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

ОК – все метрики с удалённого Prometheus есть на нашем основном сервере.

Теперь, используя их, тут можно настроить отправку сообщений  об ошибках и графики.

Alertmanager

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

Сам Alertmanager уже занимается обработкой алертов – он может переслать алерт, отправить уведомление по почте или в Slack и т.д.

Интеграция Alertmanager и Slack
Получение Slack API URL

Переходим в WebHooks Slack:

Кликаем Add configuration, выбираем канал или человека:

Жмём Add integration, и получаем URL Webhook-a:

В Slack должно придти уведомление:

Настройка alerts

Для начала создадим базовый файл правил с примером из документации, назовём его alert.rules:

groups:

- name: example
  rules:

  - alert: InstanceDown
    expr: up == 0
    for: 1s
    labels:
      severity: page
    annotations:
      summary: "Instance {{ $labels.instance }} down"
      description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 1 second."
Настройки уведомлений

Далее создаём файл настроек alertmanager_config.yml, который будет использоваться самим Alertmanager для определения настроек уведомлений, документация тут>>>:

global:
  smtp_smarthost: 'mail.domain.tld:25'
  smtp_from: 'alert@domain.tld'
  smtp_auth_username: 'user'
  smtp_auth_password: 'pass'

route:
  repeat_interval: 1h
  receiver: default

receivers:
- name: 'default'
  email_configs:
  - to: 'alert@domain.tld'
  slack_configs:
  - api_url: https://hooks.slack.com/services/T16***ZAj
Запуск Alertmanager

Обновляем Compose файл prometheus-stack.yml на хосте jm-prometheus-server с основным Prometheus сервером – добавляем маппинг файла alert.rules в контейнер с Prometheus, и запуск Alertmanager:

...
  prometheus-server: 
    image: prom/prometheus 
    networks: 
      - prometheus 
    ports: 
      - 9090:9090 
    volumes: 
      - /home/setevoy/prometheus.yml:/etc/prometheus/prometheus.yml 
      - /home/setevoy/alert.rules:/etc/prometheus/alert.rules

  ...
  alertmanager:
    image: prom/alertmanager
    ports:
      - 9093:9093
    volumes:
      - /home/setevoy/alertmanager_config.yml:/etc/alertmanager/config.yml
    networks:
      - prometheus
    command:
      - '--config.file=/etc/alertmanager/config.yml'

Обновляем файл настроек Prometheus сервера – указываем URL Alertmanager-а, и файл с правилами алертов:

...
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      - alertmanager:9093

rule_files:
  - "alert.rules"
...

Перезапускаем стек, и проверяем:

Теперь – “уроним” сервис foo-services_prometheus-server на хосте jm-swarm-manager:

root@jm-swarm-manager:/home/setevoy# docker service rm foo-services_prometheus-server
foo-services_prometheus-server

Проверяем статус в targets на основном сервере мониторинга:

И проверяем Alerts – http://10.11.100.199:9090/alerts:

Сообщение в Slack:

Alert-ы и PromQL

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

В условиях уведомлений мы видели выражение вида expr: up == 0, которое выполняется к базе данных Prometheus, делается выборка значений up для каждого инстанса и выполняется сравнение полученного Value и заданого значения, тут это 0. Если инстанс вернул up == 0 – значит инстанс down, и триггер на алерт срабатывает.

Для составления запросов – используется язык PromQL (Prometheus Query Language).

count()

Пример использования count – берём метрику container_last_seen:

В ответе получаем список меток и их Value:

container_last_seen{container_label_com_docker_stack_namespace=”foo-services”,container_label_com_docker_swarm_service_id=”0ivi6banbzeeufe2b2lokd6ng”,container_label_com_docker_swarm_service_name=”foo-services_cadvisor”,container_label_com_docker_swarm_task_id=”gvlbtdljc37yj8sgsq36aj1t9″,container_label_com_docker_swarm_task_name=”foo-services_cadvisor.y22iw8hjn37vn4hg3im0217z8.gvlbtdljc37yj8sgsq36aj1t9″,id=”/docker/adc9958b46eb79f7e3dd9385e1499bfacd331e43cd4df2c73d134c57c4440a5f”,image=”google/cadvisor:latest@sha256:9e347affc725efd3bfe95aa69362cf833aa810f84e6cb9eed1cb65c35216632a”,instance=”cadvisor”,job=”foo-services”,name=”foo-services_cadvisor.y22iw8hjn37vn4hg3im0217z8.gvlbtdljc37yj8sgsq36aj1t9″,nodeip=”10.0.0.13:8080″}

В Value передаётся время в виде unix-time (кол-во секунд с January 1, 1970 UTC) с момента, когда Prometheus последний раз видел контейнер.

Выбираем метку, по которой хотим сделать выборку, например – container_label_com_docker_swarm_service_name, и выполняем count по следущему условию:

count(container_last_seen{container_label_com_docker_swarm_service_name=~"^foo-services_nginx-service$"})

Для проверки – увеличиваем кол-во контейнеров с NGINX в Docker Swarm (был 1):

root@jm-swarm-manager:/home/setevoy# docker service scale foo-services_nginx-service=5
foo-services_nginx-service scaled to 5
overall progress: 5 out of 5 tasks
1/5: running   [==================================================>]
2/5: running   [==================================================>]
3/5: running   [==================================================>]
4/5: running   [==================================================>]
5/5: running   [==================================================>]
verify: Service converged

Проверяем:

root@jm-swarm-manager:/home/setevoy# docker service ps foo-services_nginx-service
ID                  NAME                           IMAGE               NODE                DESIRED STATE       CURRENT STATE           ERROR               PORTS
75b98zrj71yi        foo-services_nginx-service.1   nginx:latest        jm-swarm-worker     Running             Running 4 minutes ago
r7hnaptsdtm7        foo-services_nginx-service.2   nginx:latest        jm-swarm-worker     Running             Running 3 minutes ago
8c63j3e9acjk        foo-services_nginx-service.3   nginx:latest        jm-swarm-worker     Running             Running 3 minutes ago
9ujlqgyoywsu        foo-services_nginx-service.4   nginx:latest        jm-swarm-worker     Running             Running 3 minutes ago
s1nj49fz0hv0        foo-services_nginx-service.5   nginx:latest        jm-swarm-worker     Running             Running 3 minutes ago

Проверяем Value выражения count(container_last_seen{container_label_com_docker_swarm_service_name=~"^foo-services_nginx-service$"}):

Теперь – добавим условие в alert.rules:

...
  - alert: nginxCountLessThen5
    expr: count(container_last_seen{container_label_com_docker_swarm_service_name=~"^foo-services_nginx-service$"}) < 5
    for: 1s
    labels:
      severity: alarm
    annotations:
      summary: "Instance {{ $labels.instance }} down"
      description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 10 seconds."

Перезапускаем Prometheus, проверяем Alerts:

Уменьшаем кол-во контейнеров с NGINX:

root@jm-swarm-manager:/home/setevoy# docker service scale foo-services_nginx-service=1
foo-services_nginx-service scaled to 1
overall progress: 1 out of 1 tasks
1/1: running   [==================================================>]
verify: Service converged

Ждём обновления значения:

И получаем сработавший алерт:

absent() и комбинирование

В алерте InstanceDown есть одна проблема: если метрики от инстанса не получаются вообще (когда контейнер “убит” и cAdvisor не возвращает метрики с тегами этого интанса) – то правило не сработает:

Что бы избежать этого – в alert.rules можно добавить условие or и absent():

...
  - alert: nginxCountLessThen5
    expr: count(container_last_seen{container_label_com_docker_swarm_service_name=~"^foo-services_nginx-service$"}) < 5 or absent(container_last_seen{container_label_com_docker_swarm_service_name=~"^foo-services_nginx-service$"})
    for: 1s
    labels:
      severity: alarm
    annotations:
      summary: "Instance {{ $labels.instance }} down"
      description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 10 seconds."
...

Функция absent() проверяем наличие метрики по фильтру, а or заставляет алерт сработать при выполнении одного из условий.

time()

Теперь рассмотрим пример использования time() для определения того, что контейнер не отвечает.

Функция time() возвращает время с 1 января 1970 (unix-epoch) по настоящий момент.

Метрика container_last_seen – возвращает кол-во секунд с 1 января 1970 до момента, когда Prometheus получил последний результат (выполнил успешний srape) от таргета.

Проверяем – выполняем:

container_last_seen{container_label_com_docker_swarm_service_name="foo-services_nginx-service"}

и:

time()

Справа, в колонке Value, получаем секунды и на основе этих данных можем выяснить время с момента последнего успешного результата, выполнив запрос вида:

time() - container_last_seen{container_label_com_docker_swarm_service_name="foo-services_nginx-service"}:

Справа внизу – 5 секунд.

Теперь, зная, что scrape_interval у нас 15 секунд, мы можем узнать, что контейнер упал (перестал отвечать), если интервал запроса выше станет более 15 секунд.

Обновляем alert.rules:

...
  - alert: nginxNoReply15Sec
    expr: (time() - container_last_seen{container_label_com_docker_swarm_service_name=~"^foo-services_nginx-service$"}) > 15
    for: 5s
    labels:
      severity: alarm
    annotations:
      summary: "Instance {{ $labels.instance }} does not reply over 15 seconds"

Перезапускаем, проверяем алерты:

На nginxCountLessThen5  не обращаем внимания – в Compose дефолтное значение 1 сервис, а алерт делался на < 5.

Теперь – стопаем NGINX на jm-swarm-manager:

root@jm-swarm-manager:/home/setevoy# docker service rm foo-services_nginx-service
foo-services_nginx-service

Проверяем значения в graphs:

И алерты:

Slack:

alert_relabel_configs

Для самого Alertmanager можно выполнить отдельную настройку меток.

Сейчас алерт nginxNoReply15Sec в сообещнии Slack передаёт кучу меток – избавимся от них (см ниже настройку шаблонов для Slack):

Обновляем prometheus.yml, в блок alerting добавляем alert_relabel_configs:

...
alerting:
  alert_relabel_configs:
  - regex: '(container_label_com_docker_swarm_node_id|container_label_com_docker_swarm_service_id|container_label_com_docker_swarm_task_id|container_label_com_docker_swarm_task_name|container_label_maintainer|id|image)'
    action: labeldrop

  alertmanagers:
  - static_configs:
    - targets:
      - alertmanager:9093
...

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

И Slack:

Другие примеры alert.rules

Пример уведомления для blackbox-експортёра.

Ранее уже рассматривали результута probe_success и fail_if_not_matches_regexp – если сайт не вернул слово DevOps в теле ответа – то blackbox будет считать его “упавшим”:

Используя Value из этой метрики – можем добавить правило:

...
  - alert: siteDown
    expr: probe_success == 0
    labels:
      restype: website
...

Сообщение в Slack:

Шаблоны Alertmanager

Что бы изменить сообщение в Slack – можно использовать шаблонизатор Prometheus.

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

К примеру, мы хотим в Slack-сообщении передавать summary и descriptionиз правила:

  - alert: siteDown
    expr: probe_success == 0
    labels:
      restype: website
    annotations:
      summary: "Site {{ $labels.instance }} down"
      description: "Site {{ $labels.instance }} of job {{ $labels.job }} have failed fail_if_not_matches_regexp code."

В $labels.* указываем любые существующие у нас метрики.

Переходим к файлу настроек Alertmanager, в данном примере это alertmanager_config.yml, обновляем slack_configs:

...
receivers:
- name: 'default'
  email_configs:
  - to: 'alert@domain.tld'
  slack_configs:
  - api_url: https://hooks.slack.com/services/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
    send_resolved: true
    title: '[{{ .Status | toUpper }}] {{ .CommonAnnotations.summary }}'
    text: '{{ .CommonAnnotations.description }}'

И результат:

Что тут используется:

  • title и text: поля из Slack API
  • .Status: поля из Alerts
  • toUpper: функция Go для строк
  • .CommonAnnotations – данные из структуры, переданной Prometheus на Alertmanager.

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

Prometheus relabeling tricks

Controlling the instance label

Prometheus metrics: instrumenting your app with custom metrics and autodiscovery on Docker containers

Monitoring Using Prometheus

How Prometheus and the blackbox exporter makes monitoring microservice endpoints easy and free of charge

Monitoring a Docker Swarm Cluster with Prometheus

Practical Services Monitoring with Prometheus and Docker

Delete Time Series from Prometheus 2.0

Swarmprom – Prometheus Monitoring for Docker Swarm

Docker Swarm instrumentation with Prometheus

Monitor your applications with Prometheus

Metrics

Prometheus metrics: instrumenting your app with custom metrics and autodiscovery on Docker containers

Tracking request duration with Prometheus

Alertmanager

AlertManager not sending summary or description

Notification Template Reference

Custom Alertmanager Templates

Alertmanager Notification Templating with Slack

TSDB

What is a Time Series Database?