Седьмая статья цикла о построении CDC-пайплайна с нуля. Инфраструктура работает, данные текут из PostgreSQL через Kafka в HDFS. Займемся те, что никто не любит(по крайнее мере у нас на работе). Сегодня поднимаем мониторинг и настраиваем алерты в Telegram.


Зачем я это пишу

Честно: в первую очередь это мои заметки. На работе мониторинг настроен, за разные этапы репликации отвечают разные команды. А тут у себя, мне надо понять что еще я могу мониторить там, и за что зацепиться, в случае падения части цикла.

Во вторую очередь — портфолио. Настройка мониторинга с нуля на 8 хостах с разными экспортерами показывает понимание инфраструктуры.

И в третью — это предфинальная статья цикла. Хочется довести проект до логического завершения.


Что уже есть

В предыдущих статьях я поднял:

  1. Budget Parser — Telegram-бот, парсит банковские скриншоты

  2. PostgreSQL с logical replication — источник данных

  3. Kafka + Debezium — захват изменений из WAL

  4. HDFS + Hive — хранилище с SQL-доступом

  5. Hue — веб-интерфейс для Hadoop

  6. Consumer — доставка данных в HDFS с криптографической подписью

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


Архитектура мониторинга

Гениальная схема от GPT
Гениальная схема от GPT

Почему Victoria Metrics, а не Prometheus?

На работе мы используем Victoria Metrics. Prometheus — тоже популярный выбор, но Victoria Metrics потребляет меньше памяти при том же объёме данных и полностью совместима с Prometheus по API. Все дашборды и экспортеры работают без изменений.

Для домашней лаборатории Victoria Metrics подходит: знакомый инструмент с работы, низкое потребление ресурсов, простая установка.

Почему Alertmanager отдельно от Grafana Alerting?

В Grafana есть встроенная система алертинга, и на работе я использую именно её. Но Alertmanager — стандартный компонент в экосистеме Prometheus и Victoria Metrics. Хотелось разобраться, как он устроен: маршрутизация алертов, группировка, интеграция с мессенджерами.


Контейнер для мониторинга

У меня уже был контейнер CT 306 с Kafka UI. Решил разместить весь стек мониторинга там же, увеличив ресурсы:

# На хосте Proxmox

pct stop 306

pct set 306 --memory 4096

pct resize 306 rootfs +10G

pct start 306

Было 2 GB RAM и 10 GB диска, стало 4 GB и 20 GB. Этого достаточно для Victoria Metrics, Grafana, Alertmanager и vmalert.


Victoria Metrics

Скачиваем бинарник и создаём systemd-сервис:

cd /tmp

wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.108.1/victoria-metrics-linux-amd64-v1.108.1.tar.gz

tar -xzf victoria-metrics-linux-amd64-v1.108.1.tar.gz

mv victoria-metrics-prod /usr/local/bin/victoria-metrics

useradd -r -s /sbin/nologin victoriametrics

mkdir -p /var/lib/victoria-metrics /etc/victoriametrics

chown victoriametrics:victoriametrics /var/lib/victoria-metrics

Создаём файл /etc/systemd/system/victoria-metrics.service:

[Unit]

Description=Victoria Metrics

After=network.target

[Service]

Type=simple

User=victoriametrics

ExecStart=/usr/local/bin/victoria-metrics \

    -storageDataPath=/var/lib/victoria-metrics \

    -httpListenAddr=:8428 \

    -retentionPeriod=30d \

    -promscrape.config=/etc/victoriametrics/scrape.yml

Restart=on-failure

RestartSec=5

[Install]

WantedBy=multi-user.target

Параметр -retentionPeriod=30d означает, что метрики хранятся 30 дней. Для дома этого достаточно.


Grafana и блокировки

Стандартная установка через apt:

apt install -y apt-transport-https software-properties-common wget gnupg

wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor > /usr/share/keyrings/grafana.gpg

echo "deb [signed-by=/usr/share/keyrings/grafana.gpg] https://apt.grafana.com stable main" > /etc/apt/sources.list.d/grafana.list

apt update

apt install -y grafana

На этом месте я получил 403 Access Denied. Grafana блокирует запросы из России на уровне CDN.

Решил через прямую загрузку deb-пакета:

wget https://dl.grafana.com/oss/release/grafana_11.4.0_amd64.deb

dpkg -i grafana_11.4.0_amd64.deb

apt install -f -y

systemctl enable --now grafana-server

После запуска Grafana доступна на порту 3000. Логин по умолчанию — admin/admin.


Node Exporter на всех хостах

Node Exporter собирает базовые метрики системы: CPU, память, диск, сеть. Его нужно поставить на каждый хост пайплайна.

Написал скрипт для массовой установки через Proxmox(ansible в другой раз):

#!/bin/bash

# install-node-exporter.sh

cd /tmp

wget -q https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz

tar -xzf node_exporter-1.8.2.linux-amd64.tar.gz

mv node_exporter-1.8.2.linux-amd64/node_exporter /usr/local/bin/

rm -rf node_exporter-1.8.2.linux-amd64*

useradd -r -s /sbin/nologin node_exporter 2>/dev/null || true

cat > /etc/systemd/system/node-exporter.service << 'SERVICE'

[Unit]

Description=Node Exporter

After=network.target

[Service]

Type=simple

User=node_exporter

ExecStart=/usr/local/bin/node_exporter --web.listen-address=:9100

Restart=on-failure

RestartSec=5

[Install]

WantedBy=multi-user.target

SERVICE

systemctl daemon-reload

systemctl enable node-exporter

systemctl start node-exporter

Установка на все LXC-контейнеры:

for CT in 301 302 303 304 305 306 308; do

  echo "=== Installing on CT $CT ==="

  pct push $CT /tmp/install-node-exporter.sh /tmp/install-node-exporter.sh

  pct exec $CT -- bash /tmp/install-node-exporter.sh

done

На Hadoop VM установил вручную тем же скриптом через SSH.


Специализированные экспортеры

Node Exporter показывает состояние операционной системы, но ничего не знает о PostgreSQL, Kafka или Debezium. Для каждого компонента нужен свой экспортер.

PostgreSQL Exporter (CT 302)

wget https://github.com/prometheus-community/postgres_exporter/releases/download/v0.16.0/postgres_exporter-0.16.0.linux-amd64.tar.gz

tar -xzf postgres_exporter-0.16.0.linux-amd64.tar.gz

mv postgres_exporter-0.16.0.linux-amd64/postgres_exporter /usr/local/bin/

Создаём пользователя для мониторинга в PostgreSQL:

CREATE USER exporter WITH PASSWORD '<exporter-password>';

GRANT pg_monitor TO exporter;

Файл /etc/systemd/system/postgres-exporter.service:

[Unit]

Description=PostgreSQL Exporter

After=network.target

[Service]

Type=simple

User=postgres_exporter

Environment="DATA_SOURCE_NAME=postgresql://exporter:<exporter-password>@127.0.0.1:5432/postgres?sslmode=disable"

ExecStart=/usr/local/bin/postgres_exporter --web.listen-address=0.0.0.0:9187

Restart=on-failure

[Install]

WantedBy=multi-user.target

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

Важный момент: экспортер должен слушать на 0.0.0.0, а не на localhost. Иначе Victoria Metrics не сможет достучаться до него с другого хоста.

Kafka Exporter (CT 303)

wget https://github.com/danielqsj/kafka_exporter/releases/download/v1.8.0/kafka_exporter-1.8.0.linux-amd64.tar.gz

tar -xzf kafka_exporter-1.8.0.linux-amd64.tar.gz

mv kafka_exporter-1.8.0.linux-amd64/kafka_exporter /usr/local/bin/

Файл /etc/systemd/system/kafka-exporter.service:

[Unit]

Description=Kafka Exporter

After=network.target

[Service]

Type=simple

ExecStart=/usr/local/bin/kafka_exporter \

    --kafka.server=localhost:9092 \

    --web.listen-address=0.0.0.0:9308

Restart=on-failure

[Install]

WantedBy=multi-user.target

По взрослому, тут еще должны быть серты, и стучаться с ними в 9093, например, но мне и этого достаточно.

Kafka Exporter показывает количество топиков, партиций и consumer lag — отставание consumer'ов от producer'ов. Это одна из ключевых метрик для мониторинга CDC-пайплайна.

У меня есть джоба, которая когда выполняет проверку репликации, отправляя тестовые insert(ы) и update(ы) ставит время ожидания перед select(ом) на приемнике, в зависимости от лага, забираемого curl(ом) с экспортера

JMX Exporter для Debezium (CT 304)

Debezium работает внутри Kafka Connect, который написан на Java. Метрики доступны через JMX. Чтобы собирать их в Prometheus-формате, нужен JMX Exporter.

wget https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/1.0.1/jmx_prometheus_javaagent-1.0.1.jar

mv jmx_prometheus_javaagent-1.0.1.jar /opt/kafka/

Конфигурация /opt/kafka/jmx-exporter-config.yml:

lowercaseOutputName: true

lowercaseOutputLabelNames: true

rules:

  - pattern: "debezium.([^:]+)<type=connector-metrics, context=([^,]+), server=([^,]+)><>([^:]+)"

    name: debezium_connector_$4

    labels:

      plugin: $1

      context: $2

      server: $3

    type: GAUGE

Добавляем JMX Agent в параметры запуска Kafka Connect. В файле /etc/systemd/system/kafka-connect.service добавляем переменную окружения:

Environment="KAFKA_OPTS=-javaagent:/opt/kafka/jmx_prometheus_javaagent-1.0.1.jar=9404:/opt/kafka/jmx-exporter-config.yml"

После перезапуска Kafka Connect метрики Debezium доступны на порту 9404.


Конфигурация scrape

Victoria Metrics собирает метрики по расписанию. Конфигурация /etc/victoriametrics/scrape.yml описывает, откуда их брать:

scrape_configs:

  - job_name: 'victoriametrics'

    static_configs:

      - targets: ['localhost:8428']

  - job_name: 'node'

    static_configs:

      - targets: ['localhost:9100']

        labels:

          instance: 'monitoring'

      - targets: ['192.168.0.150:9100']

        labels:

          instance: 'budget-parser'

      - targets: ['192.168.0.151:9100']

        labels:

          instance: 'pg-source'

      - targets: ['192.168.0.153:9100']

        labels:

          instance: 'kafka'

      - targets: ['192.168.0.154:9100']

        labels:

          instance: 'debezium'

      - targets: ['192.168.0.155:9100']

        labels:

          instance: 'hue'

      - targets: ['192.168.0.156:9100']

        labels:

          instance: 'consumer'

      - targets: ['192.168.0.229:9100']

        labels:

          instance: 'hadoop'

  - job_name: 'postgresql'

    static_configs:

      - targets: ['192.168.0.151:9187']

        labels:

          instance: 'pg-source'

  - job_name: 'kafka'

    static_configs:

      - targets: ['192.168.0.153:9308']

        labels:

          instance: 'kafka'

  - job_name: 'debezium'

    static_configs:

      - targets: ['192.168.0.154:9404']

        labels:

          instance: 'debezium'

После рестарта Victoria Metrics проверяем, что все targets доступны:

curl -s "http://localhost:8428/api/v1/targets" | grep '"health"'

Все 11 targets должны показывать "health": "up".


Дашборд в Grafana

Сначала добавляем Victoria Metrics как data source. В Grafana выбираем тип Prometheus (Victoria Metrics совместима по API) и указываем URL http://localhost:8428.

Готовый дашборд Node Exporter Full (ID 1860) не заработал из-за проблем с переменными. Вместо того чтобы разбираться, создал свой минималистичный дашборд.

Дашборд: статусы компонентов, системные метрики, Kafka и Debezium
Дашборд: статусы компонентов, системные метрики, Kafka и Debezium
Events/sec показывает "No data" потому что сейчас нет активных изменений в БД — это норм.
Events/sec показывает "No data" потому что сейчас нет активных изменений в БД — это норм.

Основные панели:

Секция

Панели

Статусы

All Targets Up, PostgreSQL Up, Kafka Brokers, Debezium Status

Система

CPU Usage by Host, Memory Usage by Host, Disk Usage

Kafka

Consumer Lag, Messages In/Sec, Topics

Debezium

Connector Status, Lag (ms), Queue Size

PostgreSQL

Connections, Transactions/sec, Database Size


Alertmanager и Telegram

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

wget https://github.com/prometheus/alertmanager/releases/download/v0.27.0/alertmanager-0.27.0.linux-amd64.tar.gz

tar -xzf alertmanager-0.27.0.linux-amd64.tar.gz

mv alertmanager-0.27.0.linux-amd64/alertmanager /usr/local/bin/

Конфигурация /etc/alertmanager/alertmanager.yml для отправки в Telegram:

global:

  resolve_timeout: 5m

route:

  group_by: ['alertname']

  group_wait: 30s

  group_interval: 5m

  repeat_interval: 4h

  receiver: 'telegram'

receivers:

  - name: 'telegram'

    telegram_configs:

      - bot_token: 'sdvfsjfclnaelfcnha;kcfanlfcjan'

        chat_id: <your-chat-id>

        message: |

          {{ range .Alerts }}

          {{ if eq .Status "firing" }}🔴{{ else }}🟢{{ end }} {{ .Status | toUpper }}: {{ .Labels.alertname }}

          Instance: {{ .Labels.instance }}

          {{ .Annotations.description }}

          {{ end }}

Для получения chat_id нужно написать боту что-нибудь и посмотреть ответ API:

https://api.telegram.org/bot<TOKEN>/getUpdates


vmalert — правила алертинга

Victoria Metrics имеет свой компонент для оценки правил — vmalert. Он совместим с форматом правил Prometheus.

wget https://github.com/VictoriaMetrics/VictoriaMetrics/releases/download/v1.108.1/vmutils-linux-amd64-v1.108.1.tar.gz

tar -xzf vmutils-linux-amd64-v1.108.1.tar.gz

mv vmalert-prod /usr/local/bin/vmalert

Правила алертинга /etc/vmalert/cdc-pipeline.yml:

groups:

  - name: cdc-pipeline

    interval: 30s

    rules:

      # Любой компонент недоступен

      - alert: TargetDown

        expr: up == 0

        for: 1m

        labels:

          severity: critical

        annotations:

          description: "{{ $labels.job }}/{{ $labels.instance }} недоступен более 1 минуты"

      # PostgreSQL недоступен

      - alert: PostgreSQLDown

        expr: pg_up == 0

        for: 1m

        labels:

          severity: critical

        annotations:

          description: "PostgreSQL на {{ $labels.instance }} недоступен"

      # Debezium коннектор отключился

      - alert: DebeziumDisconnected

        expr: debezium_connector_connected == 0

        for: 1m

        labels:

          severity: critical

        annotations:

          description: "Debezium коннектор отключился от БД"

      # Kafka брокеров нет

      - alert: KafkaBrokersDown

        expr: kafka_brokers == 0

        for: 1m

        labels:

          severity: critical

        annotations:

          description: "Нет доступных Kafka брокеров"

      # Высокий consumer lag

      - alert: KafkaHighConsumerLag

        expr: sum(kafka_consumergroup_lag) by (consumergroup) > 1000

        for: 5m

        labels:

          severity: warning

        annotations:

          description: "Consumer group {{ $labels.consumergroup }} отстаёт на {{ $value }} сообщений"

      # Диск заполнен > 85%

      - alert: DiskSpaceLow

        expr: (1 - (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"})) * 100 > 85

        for: 5m

        labels:

          severity: warning

        annotations:

          description: "Диск на {{ $labels.instance }} заполнен на {{ $value | printf \"%.1f\" }}%"

      # Высокое использование памяти > 90%

      - alert: HighMemoryUsage

        expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90

        for: 5m

        labels:

          severity: warning

        annotations:

          description: "Память на {{ $labels.instance }} используется на {{ $value | printf \"%.1f\" }}%"

Файл /etc/systemd/system/vmalert.service:

[Unit]

Description=vmalert

After=network.target

[Service]

Type=simple

ExecStart=/usr/local/bin/vmalert \

-datasource.url=http://localhost:8428 \

-remoteWrite.url=http://localhost:8428 \

-notifier.url=http://localhost:9093 \

-rule="/etc/vmalert/*.yml" \

-httpListenAddr=:8880

Restart=on-failure

[Install]

WantedBy=multi-user.target

Тестирование алертов

Чтобы проверить, что алерты работают, останавливаем node_exporter на одном из контейнеров:

pct exec 301 -- systemctl stop node-exporter

Через минуту в Telegram приходит сообщение:

Скрытый текст

🔴 FIRING: TargetDown

Instance: budget-parser

node/budget-parser недоступен более 1 минуты

Запускаем обратно:

pct exec 301 -- systemctl start node-exporter

Ещё через минуту:

Скрытый текст

🟢 RESOLVED: TargetDown

Instance: budget-parser

node/budget-parser недоступен более 1 минуты

Тестовые алерты в ТГ
Тестовые алерты в ТГ

Грабли, на которые можно наступить

1. Grafana 403 из России

Симптом: apt install grafana возвращает 403 Access Denied.

Причина: Grafana блокирует запросы из России на уровне CDN.

Решение: Скачать deb-пакет напрямую с dl.grafana.com.

2. Экспортеры слушают только localhost

Симптом: Victoria Metrics показывает target как down.

Причина: По умолчанию экспортеры слушают на 127.0.0.1.

Решение: Указать --web.listen-address=0.0.0.0:PORT в параметрах запуска.

3. Готовые дашборды не работают

Симптом: Импортированный дашборд показывает «No data».

Причина: Дашборд использует переменные, которые не настроены.

Решение: Создать свой дашборд под конкретный стек.

4. Алерты не отправляются в Telegram

Симптом: В логах vmalert видно firing, но в Telegram ничего не приходит.

Причина: Неправильный формат шаблона сообщения.

Решение: Упростить шаблон, убрать сложные конструкции Go template.


Итоговая архитектура

Компонент

Хост

Порт

Victoria Metrics

CT 306, 192.168.0.160

8428

Grafana

CT 306, 192.168.0.160

3000

Alertmanager

CT 306, 192.168.0.160

9093

vmalert

CT 306, 192.168.0.160

8880

Kafka UI

CT 306, 192.168.0.160

8080

Экспортеры:

Экспортер

Порт

Хосты

node_exporter

9100

Все 8 хостов

postgres_exporter

9187

pg-source

kafka_exporter

9308

kafka

JMX exporter

9404

debezium

Настроенные алерты:

Алерт

Условие

Severity

TargetDown

up == 0 (1 мин)

critical

PostgreSQLDown

pg_up == 0 (1 мин)

critical

DebeziumDisconnected

connector_connected == 0 (1 мин)

critical

KafkaBrokersDown

kafka_brokers == 0 (1 мин)

critical

KafkaHighConsumerLag

lag > 1000 (5 мин)

warning

DiskSpaceLow

disk > 85% (5 мин)

warning

HighMemoryUsage

memory > 90% (5 мин)

warning


Завершение цикла

Мониторинг был предпоследним компонентом в домашнем CDC-пайплайне. Цикл подходит к концу:

#

Статья

Что сделали

1

Budget Parser

Telegram-бот + Claude Vision

2

PostgreSQL для CDC

Logical replication

3

Kafka + Debezium

Захват изменений из WAL

4

HDFS + Hive

Хранилище с SQL-доступом

5

Hue

Веб-интерфейс для Hadoop

6

Consumer

Доставка в HDFS + криптоподпись

7

Мониторинг

Victoria Metrics + Grafana + Alerts

Полный пайплайн работает: скриншот банковской операции превращается в запись в PostgreSQL, Debezium отправляет изменение в Kafka, Consumer записывает данные в HDFS, Hive позволяет делать SQL-запросы, а мониторинг следит за здоровьем всей системы. Осталась аналитика.

Всё началось с желания разобраться, как работает CDC на работе. Теперь я понимаю каждый компонент: почему Debezium читает WAL, зачем нужен heartbeat, как устроены слои обработки данных, почему важна проверка подписи сообщений. Это знание уже помогло в рабочих задачах. На самом деле помогло, я даже удивился.


Ссылки


Спасибо всем, кто читал этот цикл. Вопросы и замечания — в комментарии.