Седьмая статья цикла о построении CDC-пайплайна с нуля. Инфраструктура работает, данные текут из PostgreSQL через Kafka в HDFS. Займемся те, что никто не любит(по крайнее мере у нас на работе). Сегодня поднимаем мониторинг и настраиваем алерты в Telegram.
Зачем я это пишу
Честно: в первую очередь это мои заметки. На работе мониторинг настроен, за разные этапы репликации отвечают разные команды. А тут у себя, мне надо понять что еще я могу мониторить там, и за что зацепиться, в случае падения части цикла.
Во вторую очередь — портфолио. Настройка мониторинга с нуля на 8 хостах с разными экспортерами показывает понимание инфраструктуры.
И в третью — это предфинальная статья цикла. Хочется довести проект до логического завершения.
Что уже есть
В предыдущих статьях я поднял:
Budget Parser — Telegram-бот, парсит банковские скриншоты
PostgreSQL с logical replication — источник данных
Kafka + Debezium — захват изменений из WAL
HDFS + Hive — хранилище с SQL-доступом
Hue — веб-интерфейс для Hadoop
Consumer — доставка данных в HDFS с криптографической подписью
Восемь компонентов на семи хостах. Любой из них может упасть, и пайплайн остановится. Без мониторинга упавший хост или переполненный диск можно не заметить неделями.
Архитектура мониторинга

Почему 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) не заработал из-за проблем с переменными. Вместо того чтобы разбираться, создал свой минималистичный дашборд.



Основные панели:
Секция | Панели |
Статусы | 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 | Telegram-бот + Claude Vision | |
2 | Logical replication | |
3 | Захват изменений из WAL | |
4 | Хранилище с SQL-доступом | |
5 | Веб-интерфейс для Hadoop | |
6 | Доставка в HDFS + криптоподпись | |
7 | Мониторинг | Victoria Metrics + Grafana + Alerts |
Полный пайплайн работает: скриншот банковской операции превращается в запись в PostgreSQL, Debezium отправляет изменение в Kafka, Consumer записывает данные в HDFS, Hive позволяет делать SQL-запросы, а мониторинг следит за здоровьем всей системы. Осталась аналитика.
Всё началось с желания разобраться, как работает CDC на работе. Теперь я понимаю каждый компонент: почему Debezium читает WAL, зачем нужен heartbeat, как устроены слои обработки данных, почему важна проверка подписи сообщений. Это знание уже помогло в рабочих задачах. На самом деле помогло, я даже удивился.
Ссылки
Спасибо всем, кто читал этот цикл. Вопросы и замечания — в комментарии.
