Привет, Хабр!
Чем больше растет инсталляция Zabbix, тем сложнее становится управлять ее конфигурацией. Особенно если речь идет не об одном сервере мониторинга, а о нескольких инсталляциях, десятках команд и сотнях инженеров, которым регулярно нужно что-то менять: пороги срабатывания, IP-адреса, триггеры, шаблоны или наборы метрик.
В какой-то момент мы столкнулись с тем, что прямой доступ к Zabbix оказался плохим решением: давать его всем нельзя, а проводить каждое изменение через команду мониторинга — долго. В результате мониторщики постепенно превратились в узкое горлышко в команде Сервисного центра, а история изменений конфигурации оказалась разбросана между встроенным аудитом Zabbix, заявками и переписками.
Тогда мы решили посмотреть на конфигурацию мониторинга как на отдельный объект управления и вынести ее в централизованную CMDB. Так появилась система, которая собирает конфигурацию из нескольких инсталляций Zabbix, предоставляет единый интерфейс для работы с настройками, поддерживает RFC-процессы, хранит историю изменений и позволяет откатывать их при необходимости.
В этой статье расскажем, как устроена архитектура решения и какие задачи нам удалось закрыть с его помощью.

О системе концептуально
Наша система — это централизованная CMDB для частичного управления конфигурацией мониторинга в нескольких инсталляциях Zabbix. Zabbix по-прежнему остается источником фактического состояния мониторинга.
В нашем случае CMDB выступает как:
агрегатор инсталляций;
интерфейс над их управлением;
система контроля изменений.
Конфигурационная единица (KE)
Базовая сущность системы — конфигурационная единица — хост в Zabbix:

Все изменения выполняются в рамках конкретного хоста и его конфигурации:


Модель работы с данными. Синхронизация
Одним из первых вопросов при проектировании системы стал способ работы с конфигурацией Zabbix. Теоретически можно было обращаться к API напрямую каждый раз, когда пользователю требуется посмотреть или изменить настройки. Однако такой подход плохо масштабируется, особенно когда нужно работать сразу с несколькими инсталляциями мониторинга.
Поэтому мы выбрали Pull-модель. С определенным интервалом система самостоятельно забирает данные из Zabbix и сохраняет их в собственной базе. В синхронизацию попадают основные объекты конфигурации: теги, макросы, группы, шаблоны, интерфейсы, триггеры и метрики.
Такой подход решает сразу несколько задач. Во-первых, пользовательский интерфейс и бизнес-логика не зависят от доступности конкретной инсталляции Zabbix. Во-вторых, появляется единая точка хранения данных для нескольких систем мониторинга. Наконец, все операции чтения выполняются из локальной базы, что заметно снижает нагрузку на API.
Чтобы избежать лишних операций записи, после каждой синхронизации мы рассчитываем контрольную сумму полученных данных и сравниваем ее с предыдущим состоянием. Если изменений нет, запись в базу не выполняется.
В результате уменьшается количество операций обновления, снижается нагрузка на СУБД и ускоряется сама процедура синхронизации.

Статусная модель RFC и атомарность применения
Любое изменение оформляется как RFC. В этой схеме пользователь сначала привязывает изменение к задаче из Service Desk:

А затем вносит изменения:

Логично, что после этой процедуры пользователь перед отправкой изменений на согласование должен видеть, что именно поменялось. Также хотелось бы понимать, что именно нужно проверять. Мы предусмотрели это: взяли пакет json-diff-ts и передали два json-объекта до применения конфига. На выходе получили уже готовый diff, который отрисовали в качестве чейнджа.
В итоге получаем:

Процесс обработки изменений в нашей системе строится вокруг статусной модели RFC:
New(draft) → approved → pending → success
↓ ↓
rejected error
Проблема атомарности
На каждом этапе мы фиксируем состояние в базе: от «Новой» задачи через «Ожидание проверки» до статуса «Проверена», после чего в работу вступает executor job. На этом этапе мы столкнулись с проблемой: Zabbix API представляет собой набор независимых методов, и при последовательном применении нескольких изменений одно может выполниться, а другое — нет.
Для себя мы нашли решение — компенсирующая транзакция. Ее логика работает в соответствии с алгоритмом.
Алгоритм
На этапе создания черновика (draft) сохраняются:
планируемые изменения;
полный текущий конфиг хоста (точка восстановления).
После перехода в статус «Проверена» executor последовательно применяет изменения через API Zabbix. При любой ошибке:
процесс останавливается;
запускается откат к сохранённому состоянию.
При успехе всех операций RFC переходит в статус «Применена».
Изменения проходят вот такой workflow:
создание draft;
review;
approve/reject;
apply.

Аудит и история
Теперь обратимся к контролю внесенных изменений.
Для аудита мы использовали механизмы postgres, написали функцию и повесили триггеры на таблицы, которые хранят данные по КЕ. И теперь каждый insert update delete фиксируется в таблице changelog.
В базе данных мы написали функционал аудита, а также триггеры на каждую таблицу (INSERT / UPDATE / DELETE). В таблицу changelog теперь записывается:
когда изменяли;
что изменяли;
детали значения до / после.
В итоге система хранит полную историю изменений, внесенных в хост:

Теперь переходим к настройке аудита RFC. Нам необходимо было добавить возможность просмотра изменений: кто создал RFC, и кто его подтвердил. Для этого мы отобразили таблицу с RFC-задачами.

Благодаря этому функционалу мы получили:
единую точку доступа: просмотр конфигурации мониторинга без доступа к Zabbix;
контроль изменений: все изменения проходят через workflow;
снижение операционной нагрузки: инженеры мониторинга освобождаются от рутинных задач, в частности от корректировки макросов, тегов, отключения триггеров и прочего.
Архитектура
В качестве взаимодействия между компонентами мы использовали прослойку Hasura, которая сэкономила нам сотни часов разработки, вместо того чтобы вручную писать множество запросов в БД.

Матчасть:
Скрытый текст
1. Scheduler (планировщик)
Запускается по cron / ticker
Собирает список всех инсталляций Zabbix
Кладет задачи синхронизации в очередь
2. Sync jobs (джобы синхронизации)
Pull-модель: забирает данные через Zabbix API
Получает: хосты, шаблоны, группы, макросы, тэги, интерфейсы
Вычисляет checksum (например, от JSON-представления конфига хоста)
Если checksum изменился — пишем в БД, нет — пропускаем (экономия нагрузки)
3. CMDB config loader (метрики и триггеры)
Тот же принцип, но свой интервал
Вынесен отдельно, так как запрос метрик — тяжёлый
4. Executor (применение изменений)
Атомарно применяет утвержденный RFC к Zabbix
Реализует компенсирующие транзакции:
сохраняет полный конфиг хоста до изменений
если любой вызов API упал → откат к сохраненному состоянию
5. База данных (PostgreSQL)
Хранит:
актуальную конфигурацию всех хостов
историю изменений (триггерный аудит)
черновики RFC
аудит RFC
аудит лог
6. Кэш — Redis
Чексуммы по хостам, шаблонам, метрикам, триггерам
7. Frontend (React + TypeScript)
Просмотр конфигурации (доступ без прямого доступа в Zabbix)
Редактирование через RFC
Визуализация JSON diff (json-diff-ts)
Этапы разработки
Разработка шла последовательно, каждый этап давал работающий результат, который сразу начинали использовать.
Технический стек:
Backend и jobs — Golang (синхронизация, executor, scheduler)
Frontend — React + TypeScript
API слой — Hasura GraphQL Engine
БД — PostgreSQL
Кэш и очереди — Redis
1 этап. Синхронизация с Zabbix
Цель — получить «золотой слепок» всех инсталляций в единой базе.
Что сделали:
Написали sync-worker на Golang, который по расписанию ходит в Zabbix API каждого заказчика
Выгрузили: хосты, шаблоны, группы, макросы, тэги, интерфейсы, метрики, триггеры
Внедрили checksum в Redis: сверили хэш текущего состояния с предыдущим, если не изменилось — не пишем в PostgreSQL
Самые тяжелые сущности (метрики и триггеры) вынесли в отдельный воркер с интервалом 15 минут и обязательной пагинацией
Для организации очередей и retry-политик использовали Redis + очередь на Golang (экспоненциальная задержка при ошибках API)
Результат первого этапа: работающая синхронизация с шести Zabbix. В БД лежит актуальная конфигурация всех хостов.
2 этап. Просмотр конфигурации
Цель — дать инженерам сервисного центра возможность смотреть настройки мониторинга без доступа к Zabbix.
Что сделали:
Написали React + TypeScript-приложение с таблицей хостов (фильтрация по заказчику, группе, шаблону)
Страницу деталей хоста: макросы, тэги, интерфейсы, привязанные шаблоны, метрики, триггеры
Добавили поиск и фильтры
Результат второго этапа: единая точка входа для просмотра мониторинга. Инженеры сервисного центра перестали писать «А посмотри в Zabbix» — теперь они все видят сами.

3 этап. Редактирование через RFC
Цель — разрешить инженерам сервисного центра изменять настройки, но под контролем мониторинга.
Что сделали:
Добавили статусную модель RFC: draft → approved → pending → success/error и rejected
Создание RFC на React + TS: выбор хоста → создание кнопки редактирования → написание
JSON diff через json-diff-ts: перед отправкой пользователь видит, что именно изменил
Ревьюер при проверке видит тот же самый diff
Написали executor на Golang — сервис, который применяет одобренные RFC к живому Zabbix
Решили проблему отсутствия транзакций: executor сохраняет snapshot хоста до изменений, при любой ошибке откатывается
Добавили Redis-блокировки (lock:host:{id}), чтобы два executor'а не меняли один хост одновременно
Аудит: триггеры PostgreSQL логируют все изменения таблиц, отдельная таблица хранит историю статусов RFC
Результат третьего этапа: инженер Сервисного центра меняет настройки сам за 3 минуты, мониторинг только проверяет diff и нажимает Approve. Всё под тикетом, всё аудируется, всё откатывается.
Резюмируем
Команда мониторинга перестала быть узким местом для типовых изменений, а инженеры сервисного центра получили контролируемый сервис для работы с конфигурацией мониторинга. Главная техническая проблема — отсутствие транзакций в Zabbix API — была решена с помощью компенсирующих транзакций. При возникновении ошибки система автоматически откатывает конфигурацию к исходному состоянию. Аудит всех изменений реализован на уровне PostgreSQL через триггеры, что дало полную и неизменяемую историю правок. Архитектура построена на pull-синхронизации с кэшированием чексумм в Redis, что минимизирует нагрузку на базу данных. Тяжелые сущности вроде метрик и триггеров вынесены в отдельные воркеры с разными интервалами опроса. Использование Hasura GraphQL в качестве API-прослойки сэкономило ресурсы на ручной разработке запросов.
Не благодарите!