惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
Stack Overflow Blog
Stack Overflow Blog
L
LINUX DO - 最新话题
Google Online Security Blog
Google Online Security Blog
Schneier on Security
Schneier on Security
Spread Privacy
Spread Privacy
www.infosecurity-magazine.com
www.infosecurity-magazine.com
雷峰网
雷峰网
Google DeepMind News
Google DeepMind News
Microsoft Azure Blog
Microsoft Azure Blog
IT之家
IT之家
V
Vulnerabilities – Threatpost
K
Kaspersky official blog
S
Schneier on Security
B
Blog
The Register - Security
The Register - Security
SecWiki News
SecWiki News
Hacker News: Ask HN
Hacker News: Ask HN
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Security Affairs
T
The Blog of Author Tim Ferriss
G
Google Developers Blog
T
Tenable Blog
P
Proofpoint News Feed
Apple Machine Learning Research
Apple Machine Learning Research
D
DataBreaches.Net
S
Secure Thoughts
Security Latest
Security Latest
H
Heimdal Security Blog
The Hacker News
The Hacker News
O
OpenAI News
AWS News Blog
AWS News Blog
量子位
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
腾讯CDC
U
Unit 42
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
L
LangChain Blog
阮一峰的网络日志
阮一峰的网络日志
T
The Exploit Database - CXSecurity.com
NISL@THU
NISL@THU
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Hugging Face - Blog
Hugging Face - Blog
The Last Watchdog
The Last Watchdog
Recorded Future
Recorded Future
V2EX - 技术
V2EX - 技术
爱范儿
爱范儿
F
Full Disclosure

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Мониторинг Kerio Connect через Zabbix 7: разбор шаблона без агентов и regex по DAT
Konstantin_T · 2026-05-22 · via Все публикации подряд на Хабре

Почему готовых шаблонов для Kerio Connect нет

Если набрать «kerio connect zabbix» в поиске, обнаруживается странная пустота. Сама Zabbix Sia рассказывает на странице zabbix.com/integrations/kerio про шаблон для Kerio Control — это файрвол, SNMP-метрики там работают штатно. А вот Kerio Connect (почтовый сервер) на этой же странице упоминается единственным предложением: «если нужен шаблон — мы готовы разработать индивидуально, обратитесь за расчётом». Бесплатной версии нет.

В официальном zabbix/community-templates Kerio Connect отсутствует. Поиск по issues и PR — ничего жизнеспособного. На linux.org.ru человек предлагает регулярки по графическому DAT-файлу, который Kerio хранит на диске для веб-консоли. Идея рабочая, но это парсинг бинарного формата, который вендор не обещает стабильным. В руководстве Kerio Connect Multi-Server (это коммерческое расширение) есть инструкция по мониторингу через Puppet и SNMP — но только для Multi-Server конфигурации и только для платных установок.

Получается: коммерческий почтовик, в нашей стране крутится в десятках организаций, в открытом доступе шаблона нет.

У меня под рукой одна такая установка — Kerio Connect 10.0.8.9228 на Windows, ~500 учётных записей, обработано 617376 входящих сообщений с момента старта, в очереди обычно 1–3 письма.

Дальше — разбор того, что выяснилось по API, какие методы реально доступны под минимальной ролью, как уложить четыре HTTPS-вызова в один Zabbix-айтем, и где пришлось делать компромиссы.

Что выяснилось про Kerio Admin API

Документация Kerio на API скудная — есть PDF на 200+ страниц с описанием методов, но многие из них работают «иногда» и зависят от роли пользователя. Я создал служебного пользователя с минимально достаточной ролью Auditor (это уровень между «никаких прав» и «администратор»), сел читать.

API живёт на https://<kerio>:4040/admin/api/jsonrpc/. JSON-RPC 2.0.

Первые грабли: формат application. Документация показывает примеры с application: "MyMonitor" — строкой. Так не работает: возвращается -32602 Invalid params. Опытным путём выяснилось, что application должно быть объектом:

{
  "jsonrpc": "2.0", "id": 1, "method": "Session.login",
  "params": {
    "userName": "zabbix_api",
    "password": "...",
    "application": {"name": "Zabbix Monitoring", "vendor": "Zabbix", "version": "7.0"}
  }
}

В ответе приходит {"result": {"token": "<hex>"}} и в HTTP-заголовке Set-Cookie: SESSION_CONNECT_WEBADMIN=...; Path=/. Дальше при каждом вызове нужно передавать и cookie, и токен в заголовке X-Token. Если что-то пропустить, Kerio начинает возвращать 1003 Access denied без подробностей.

Вторые грабли: имена методов. В документации указан метод ProductRegistration.get. Он не работает: -32602 Invalid params при любых параметрах. Правильное имя — ProductRegistration.getFullStatus. Это вообще не упомянуто в открытом PDF, нашёл его в обсуждениях на форуме разработчиков плагинов Kerio Operator.

Третьи грабли — самые болезненные: что Auditor НЕ умеет. Проверил по очереди:

Метод

Что хотелось

Что получили

SystemHealth.get

CPU/RAM/swap сервера

-32602 Invalid params при любых параметрах. Не работает даже через Batch.run обход. Видимо, требует Admin

Domains.get

Per-domain storage

1000 internal database error

Queue.get, MessageQueue.get, Queue.getMessages

Сами сообщения в очереди

Timeout. Зависают.

SystemHealth.get оказался полностью закрыт для Auditor. Полез в логи Kerio после нескольких попыток — там видно, что вызов даже не доходит до обработчика метрик. Попытался обойти через Batch.run (Kerio Admin API позволяет упаковать несколько вызовов в один батч, который при удачном раскладе исполняется в контексте чуть других проверок прав) — тот же -32602 Invalid params, даже когда сам батч содержит только SystemHealth.get. Пробовал с пустыми параметрами, с {}, с конкретными ключами вроде cpu, memory, disk — всё бесполезно. Это означает, что CPU, память и swap через Kerio API получить невозможно под минимальной ролью. Дальше будет про это, спойлер: проблема решается параллельным OS-шаблоном.

Domains.get мог бы дать per-domain quotas и storage usage. Под Auditor — 1000 internal database error. Поднимать на Admin ради этого не хочется: Admin даёт полный доступ к конфигурации почтовика, для скрапинга метрик это перебор.

Queue.get и его варианты теоретически возвращали бы тела сообщений в очереди. На практике на Kerio Connect 10.0.8 они зависают наглухо, и единственное спасение — оборвать соединение и заново залогиниться. К счастью, размер очереди есть в Statistics.get (поле storedInQueue.count), а тела сообщений для мониторинга и не нужны.

Что в итоге работает. Четыре метода, и больше, как выяснилось, не нужно:

  • Statistics.get — главное блюдо: storage, очередь, uptime, счётчики соединений по протоколам, SMTP-сообщения in/out, неудачные SMTP-аутентификации, спам checked/rejected, антивирус scanned/found, greylisting, antibombing

  • Services.get — список сервисов (SMTP, IMAP, POP3, LDAP, Web, XMPP и их Secure-варианты — всего обычно 15) с полем isRunning: boolean

  • ProductRegistration.getFullStatus — лицензия: количество мест, дата истечения, оставшиеся дни

  • Server.getVersion — версия сервера (информационное поле, полезно для отчётов и Auto-discovery)

Statistics.get возвращает примерно 8 КБ JSON на нагруженном почтовике — там около 40 числовых полей. Большинство из них при этом — строки, а не числа: "617376", "91714". Это надо учитывать при настройке Zabbix dependent items.

Архитектура: один master-айтем, четыре вызова

Самое прямолинейное решение для такого набора — HTTP Agent items, по одному на метрику. Получилось бы 24 отдельных HTTP-вызова в минуту, каждый со своим логином, своим JSON в обе стороны и своим состоянием. Kerio API при этом не безлимитный — у нагруженного почтовика на каждый логин уходит 150–300 ms (TLS handshake + аутентификация). 24 параллельных Session.login в минуту — это уже заметная нагрузка на сервис, и каждый из них видим в audit-логе.

Поэтому реализация другая — pattern «master + dependent items»:

  1. Один master-айтем выполняет последовательность Session.login → 4 вызова → Session.logout

  2. Возвращает один склеенный JSON-объект ~8 КБ

  3. 24 dependent items извлекают свои значения через JSONPath из этого объекта

  4. Один LLD-айтем тем же путём дискаверит запущенные сервисы

В Zabbix 7 master-айтем можно сделать одним из двух способов:

  • HTTP Agent + Pre-processing script (JavaScript на Duktape) — Zabbix server/proxy ходит сам

  • Script item — то же, но без promise-цепочки, проще читать

Я выбрал Script item: код укладывается в 60 строк JS, диагностика прозрачнее (всё, что бросаешь как throw, видно прямо в Zabbix UI в поле Value of last check). Вот сердцевина:

var loginResult = apiCall('Session.login', {
    userName: params.username,
    password: params.password,
    application: { name: 'Zabbix Monitoring', vendor: 'Zabbix', version: '7.0' }
});
token = loginResult.token;

var statsResp   = apiCall('Statistics.get');
var svcsResp    = apiCall('Services.get');
var licResp     = apiCall('ProductRegistration.getFullStatus');
var serverResp  = apiCall('Server.getVersion');

try { apiCall('Session.logout'); } catch (e) {}

return JSON.stringify({
    statistics: statsResp.statistics || {},
    services:   svcsResp.services    || [],
    license:    licResp.status       || {},
    server:     serverResp           || {}
});

Внутри apiCall — обычный JSON-RPC: построить тело, отправить POST, проверить статус и result.error, для Session.login дополнительно вытащить cookie из заголовка Set-Cookie и прицепить его ко всем последующим запросам через Cookie: ... и X-Token: ....

function apiCall(method, callParams) {
    reqId++;
    var body = { jsonrpc: '2.0', id: reqId, method: method };
    if (callParams) body.params = callParams;
    if (token) body.token = token;

    var http = new HttpRequest();
    http.addHeader('Content-Type: application/json');
    if (cookie) http.addHeader('Cookie: ' + cookie);
    if (token)  http.addHeader('X-Token: ' + token);

    var resp = http.post(baseUrl, JSON.stringify(body));
    var status = http.getStatus();
    if (status < 200 || status >= 300) throw 'HTTP ' + status + ' on ' + method;

    var result = JSON.parse(resp);
    if (result.error) throw method + ' error: ' + result.error.message;
    return result.result;
}

Master-айтем опрашивается раз в минуту с таймаутом 30 секунд (про таймаут чуть ниже — это важно). Dependent items привязаны к нему по master_item и получают данные мгновенно при каждом обновлении master.

JSONPath в Zabbix 7 поддерживает фильтры массивов, что критично для LLD сервисов. Master возвращает массив services[], в каждом элементе name и isRunning. Чтобы достать статус для конкретного дискаверенного сервиса:

$.services[?(@.name=="{#SERVICE}")].isRunning.first()

Фильтр [?(@.name=="{#SERVICE}")] отбирает элемент массива с подходящим именем, .isRunning.first() достаёт значение из единственного результата. На выходе — true/false. После Pre-processing «Boolean to decimal» получается 0/1, на котором уже строится триггер «service is not running».

Грабли счётчиков: рестарт, отрицательная дельта и IN_RANGE

Все счётчики в Statistics.getкумулятивные с момента запуска сервера. Точно как /proc/diskstats или ifconfig RX bytes. На запущенном почтовике это выглядит так:

{
  "received":    {"count": "617376", ...},
  "transmitted": {"count": "319307", ...},
  "smtpServer":  {"totalIncomingConnections": "1506226",
                  "authenticationFailures": "334700", ...},
  "spam":        {"checked": "91714", "rejected": "10895", ...},
  "antivirus":   {"checkedAttachments": "579391", "foundViruses": "147", ...}
}

617376 принятых писем — это не «за сегодня», это с момента рестарта почтовика. Чтобы получить осмысленный rate (например, «сколько спама пришло за последние 5 минут»), на каждое такое поле в Zabbix вешается preprocessing шаг Change per second — он считает дельту относительно предыдущего значения и делит на интервал.

И тут — четвёртые грабли: что происходит, когда Kerio перезапустился. Счётчик уходит с 617376 в 0. Следующий опрос приносит, скажем, 50. Change per second даёт дельту (50 - 617376) / 60 = -10288. Огромное отрицательное число, которое уезжает в график и портит всю аналитику.

Решение — после Change per second добавить второй preprocessing шаг In range:

In range: min=0, max=1e9, custom-on-fail=Discard value

Если расчётный rate отрицательный или абсурдно большой — значение отбрасывается, в график идёт пропуск (gap), на следующем опросе всё уже считается от новой базы. Это маскирует ровно одну точку после рестарта, что приемлемо. Альтернативы хуже: либо мириться с отрицательным выбросом, либо переписывать препроцессинг на JavaScript с хранением состояния (Zabbix умеет, но сложнее в отладке).

Пятые грабли, мелкие, но раздражающие — типы данных. Kerio в JSON возвращает большинство счётчиков строками: "617376", не 617376. Zabbix умеет преобразовывать строки в числа на лету, но storage.percentage приходит как "92" (без знака процента, просто число в строке) — для него надо явно поставить preprocessing «Trim» или «Matches regex (\d+)», иначе будет ругань про невалидное значение.

И ещё один сюрприз: uptime.days приходит числом (1), а не строкой. Единственное поле, выпадающее из общего паттерна. Если разрабатывать тесты — обязательно положить такой кейс в фикстуру, иначе сломается.

Шестые грабли — единицы измерения, которые меняются на лету. Kerio возвращает объёмы данных в формате {"value": N, "units": "..."}, где units может быть "Bytes", "KiloBytes", "MegaBytes" в зависимости от размера. На пустой очереди в JSON будет:

"storedInQueue": {"count": "0", "volume": {"value": 0, "units": "Bytes"}, "recipients": "0"}

А при наполненной очереди — "units": "MegaBytes". Это значит, что просто брать volume.value нельзя, без нормализации к единым единицам Zabbix получит мусор: то 50 байт, то 50 мегабайт в одном и том же графике. В шаблоне для метрик объёма я взял за правило брать count (количество сообщений) — оно всегда в штуках и не плавает. А там, где объём действительно нужен (storage диска), Kerio удачно возвращает фиксированные MegaBytes, и тут можно не дёргаться. Но если кто-то решит расширить шаблон метриками вроде «объём входящего трафика SMTP» — придётся писать препроцессинг с нормализацией, иначе будут грабли. Это, кстати, ещё одна причина, по которой я не стал тащить в шаблон всё подряд: тридцать метрик с понятной семантикой лучше пятидесяти с подводными камнями.

Вариант 1 — без агента: Zabbix server или proxy ходит сам

Если Zabbix server (или proxy) видит почтовик по сети — :4040 доступен, фаервол пропускает — никакого агента ставить не нужно. Шаблон Kerio Connect by Script живёт в Templates → Applications, импортируется как обычный YAML, на хост вешаются пять макросов:

Макрос

Значение

{$KERIO.API.HOST}

IP или DNS-имя сервера Kerio

{$KERIO.API.PORT}

4040

{$KERIO.API.SCHEME}

https

{$KERIO.API.USERNAME}

служебный пользователь (Auditor достаточен)

{$KERIO.API.PASSWORD}

пароль (Secret macro)

Через минуту в Monitoring → Latest data появляются значения дисков, версия, лицензия. Счётчики становятся не-нулевыми после второго опроса (потому что Change per second нужен предыдущий замер).

Этот вариант я и рекомендую как основной. Из плюсов — ноль изменений на самом почтовом сервере: ни нового пакета, ни конфига, ни перезапуска. Из минусов — путь по сети от Zabbix proxy до Kerio должен быть открыт по 4040/tcp, что не всегда возможно в больших организациях.

Вариант 2 — через Zabbix agent (когда сеть закрыта)

Часть моих клиентов держит почтовик в DMZ или сегменте, до которого централизованный Zabbix proxy просто не дотягивается через NAT и фаервол. Открывать дополнительные правила ради мониторинга — лишний согласование с безопасниками и потенциальный риск. Для таких случаев в репозитории второй шаблон — Kerio Connect by Zabbix agent. На самом сервере с Kerio (или соседнем) ставится zabbix_agent2, в него прописывается UserParameter:

UserParameter=kerio.api.master, /usr/bin/python3 /opt/zabbix-kerio-connect/src/kerio_collector.py collect /etc/zabbix/kerio_connect.conf
UserParameter=kerio.services.discovery, /usr/bin/python3 /opt/zabbix-kerio-connect/src/kerio_collector.py lld_services /etc/zabbix/kerio_connect.conf

Логика та же — login, четыре вызова, logout, склейка JSON. Только теперь это kerio_collector.py на чистом stdlib (urllib, json, ssl), без сторонних зависимостей. Чтобы можно было прогнать вне Zabbix:

python3 kerio_collector.py collect /etc/zabbix/kerio_connect.conf | jq .

Шестые грабли — самые интересные с точки зрения безопасности. Очевидное решение «давайте передадим пароль через макрос Zabbix» не работает: Zabbix host-макросы не разворачиваются внутри zabbix_agent2.conf. То есть прописать password={$KERIO.API.PASSWORD} в строке UserParameter нельзя — на агент придёт буквальный текст {$KERIO.API.PASSWORD}. Можно было бы передавать пароль аргументом в момент опроса с server’а — но тогда секрет утекает в ps aux и в логи zabbix_agent2 на уровне DEBUG.

Решение, которое в шаблоне — отдельный конфиг-файл на хосте:

# /etc/zabbix/kerio_connect.conf — chmod 0600, owner zabbix:zabbix
[api]
host = 127.0.0.1
port = 4040
scheme = https
username = zabbix_api
password = <секрет>

chmod 0600 + chown zabbix:zabbix — файл читается только агентом и root. Не идеально (всё-таки plaintext), но это паттерн, по которому живут десятки других Zabbix UserParameter-скриптов; на практике компромисс приемлемый.

Седьмые грабли — таймаут. По умолчанию zabbix_agent2.conf ставит Timeout=3 секунды. Четыре последовательных HTTPS-вызова с TLS handshake и парсингом ответов — это в среднем 800–1500 мс на нагруженном почтовике, но на холодный старт может уйти и больше. С Timeout=3 каждый второй опрос обрывался на полпути, мастер-айтем уходил в nodata, все триггеры начинали мигать. В zabbix_agent2.conf нужно ставить:

Timeout=30

И не забыть рестартнуть агента, потому что Timeout читается только при старте.

В коллекторе явно отключена проверка сертификата (ssl.CERT_NONE). На внутреннем почтовике, который никто не выставлял в интернет, это обычно нормально — Kerio из коробки идёт с самоподписанным сертификатом. Если кому-то этот компромисс не нравится — в коллекторе одна строчка правки, чтобы включить проверку по корпоративному CA.

К коллектору есть 60 unit-тестов на pytest, которые гоняются в CI на каждый коммит. Тестируются: парсинг конфига, корректное построение payload для Session.login, корректное прицепление токена и cookie, обработка ошибок API, выход с кодом и валидным JSON в случае любого исключения. Это важно для production: если коллектор падает с stack trace в stderr — Zabbix получит мусор и закрасит весь экран.

Перед тем как привязывать шаблон к хосту, имеет смысл прогнать коллектор вручную и убедиться, что он возвращает осмысленный JSON. На самом сервере достаточно одного шага:

sudo -u zabbix python3 /opt/zabbix-kerio-connect/src/kerio_collector.py \
    collect /etc/zabbix/kerio_connect.conf | jq '.statistics.storage'

Получите блок с total, occupied, percentage — значит логин прошёл и API отвечает. Если на этом шаге {"error": "..."} — значит дальше Zabbix тоже не сможет. Гораздо лучше увидеть это сразу, чем минут через десять, наблюдая nodata в Latest data и роясь в zabbix_agent2.log.

Триггеры и каскадные зависимости

Метрики ради метрик никому не нужны. Девять триггеров, которые в шаблоне:

  • API data collection failed (nodata 5 мин на master) — общий no-data корневой триггер

  • Disk usage > WARN/AVG/HIGH/DISASTER (80/90/95/99%) — четыре ступени по диску. Высший подавляет нижние, поэтому при заполненном на 96% диске виден один High-алерт, а не четыре одновременно

  • High message queue depth — очередь больше порога (по умолчанию 100 писем)

  • High SMTP authentication failure rate — частота неудачных логинов в среднем за 5 минут выше порога (по умолчанию 1/сек). Это первый признак того, что кто-то пытается перебирать пароли через ваш SMTP submission

  • Antivirus infection detected — KAV нашёл вирус хотя бы в одном письме за период (полезно, если у организации политика «при любом срабатывании AV — пересмотреть SPF/DKIM источника»)

  • License expires in less than 30 days — пора писать в закупки

  • Service {#SERVICE} is not running — какой-то из 15 дискаверенных сервисов остановлен

В Disaster-уровне диска есть смысл: на 99% Kerio начинает отбивать SMTP с 452 Insufficient system storage. То есть письмо отскочит обратно отправителю с временной ошибкой — и в худшем случае попадёт в DSN после 5-дневной retry-сессии. Это не «нужно реагировать в течение дня», это «нужно реагировать в течение часа».

И тут — самое полезное архитектурное решение шаблона. Все non-master триггеры зависят от master nodata-триггера. Это значит, что когда Kerio API недоступен (например, перезагрузка сервера), Zabbix получает один корневой алерт «API data collection failed», а 16 остальных (4 disk × 4 уровня + queue + auth + license + AV + сервисы) автоматически подавляются.

Без зависимостей выглядело бы так: API лёг → все dependent items уходят в nodata → у дисковых триггеров nodata тоже триггерит → срабатывают все четыре уровня диска одновременно, плюс «очередь зависла», плюс «лицензия не определена», плюс «каждый сервис остановлен»… В sosial-сети-для-сисадминов это называется «алерт-шторм».

Cascade конфигурируется в YAML шаблона прямо в triggers:

- uuid: 8d6b3e7a4f1c4e6da9b2c0517e4d3a91
  expression: 'last(/Kerio Connect by Script/kerio.disk.pct)>{$KERIO.DISK.WARN}'
  name: 'Disk usage > {$KERIO.DISK.WARN}%'
  priority: WARNING
  dependencies:
    - name: 'API data collection failed'
      expression: 'nodata(/Kerio Connect by Script/kerio.api.master,5m)=1'

В Zabbix UI Cause/Symptom после 6.0 — но в YAML удобнее как dependencies. Один корневой алерт на весь шаблон.

Тестирование шаблона перед production

Шаблон мониторинга — это код, и относиться к нему стоит как к коду. Особенно к шаблону, чьи триггеры будут будить дежурного в три ночи. Ложный алерт убивает доверие быстрее, чем пропущенный инцидент: после двух-трёх «опять зря разбудили» дежурный начнёт молча отщёлкивать уведомления, а в следующий раз пропустит настоящий пожар.

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

Юнит-тесты Python-коллектора (60 шт., pytest tests/). Проверяют все ветки kerio_collector.py: парсинг конфига, отсутствующая секция [api], отсутствующий ключ port, числовая конверсия, корректность payload для Session.login, прицепление токена и cookie ко всем последующим вызовам, корректная обработка result.error, корректное падение в JSON-формат при сетевой ошибке. Это страховка от «коллектор крашится из-за неожиданного формата конфига», что в production выглядит как тихий nodata.

Static check JS-коллектора. master_collector.js крутится внутри Zabbix через Duktape, и Duktape-движок отличается от Node — там нет современного JS, нет async/await. Чтобы хотя бы синтаксические ошибки не доехать до production-сервера, в CI прогоняется:

node --check src/master_collector.js
node --check src/lld_services.js

node --check парсит файл без исполнения и ругается на любую синтаксическую ошибку. Не идеально (Duktape всё равно может споткнуться на специфике), но 80% опечаток ловятся бесплатно.

Парность JS ↔ YAML. Самая болезненная история при поддержке Zabbix-шаблонов: JS-код хранится внутри YAML-файла шаблона (как поле script у master-айтема). Если разработчик правит .js-файл и забывает синхронизировать YAML — на production уезжает старая версия скрипта, а на репозиторий смотрят с гневом «у меня всё работает». Скрипт tools/sync_template_js.py --check сравнивает содержимое и падает в CI, если они разъехались. Финальная команда команды и для разработки — встроена в Makefile, гоняется одним make check.

После всего этого — ручной smoke на staging-хосте: импорт шаблона, привязка к тестовому Kerio, минута ожидания, проверка Monitoring → Latest data. Этим шагом нельзя пренебрегать: я однажды зарелизил версию, в которой LLD сервисов работал, но один из сервис-прототипов случайно был помечен Disabled, и на production в течение часа никто этого не заметил.

Что мониторится вне шаблона (и почему так)

В шаблоне намеренно нет CPU, RAM, swap и per-domain метрик. Это не упущение — это решение, основанное на ограничениях API под ролью Auditor (см. выше) и здравом смысле:

CPU/RAM/swap — Zabbix испокон веков умеет это собирать через стандартный шаблон Linux by Zabbix agent или Windows by Zabbix agent. Не надо изобретать велосипед поверх Kerio API (которое для этого всё равно не работает). Привязываете к хосту оба шаблона — application-уровневый Kerio и OS-уровневый Linux/Windows — и получаете полную картину без дублирования.

Per-domain storageDomains.get требует Admin-роль, а Admin даёт полные права на редактирование конфигурации почтовика. Создавать API-пользователя с Admin ради per-domain метрик мне показалось плохой идеей: каждый зловред, получивший доступ к pickup-файлу с этими credentials, получает полный контроль над почтовой системой. Если для конкретной организации эти метрики критичны — можно их прикрутить отдельным шаблоном «Kerio Connect by Script (Admin)» и принять на себя риски. В моём варианте этого пути нет.

Per-message данные в очередиQueue.get зависает (см. раздел про API). Если очень нужно — есть kerio_admin.exe (Windows) или kerio-admin (Linux) CLI, но это уже не Zabbix-юзкейс, это разовая диагностика, и она прекрасно живёт через SSH/RDP.

То, что описано в шаблоне — это application-layer метрики Kerio, и не более. Без претензии «закрыть весь observability одним шаблоном». Такой подход даёт меньше точек отказа и лучше живёт с обновлениями Kerio.

Выводы и репозиторий

Что получилось в итоге:

  • 24 dependent items, покрывающих диски, очередь, соединения по 6 протоколам, SMTP-сообщения in/out, SMTP-auth failures, спам, антивирус, greylisting, antibombing, лицензию и версию сервера

  • 1 LLD-правило, дискаверящее запущенные сервисы и создающее по prototype’у статус-айтем на каждый

  • 9 триггеров с каскадной зависимостью от master nodata

  • Два варианта: с прямым опросом из Zabbix server/proxy и через локальный Zabbix agent с UserParameter

  • 60 unit-тестов на pytest для Python-коллектора, node --check для JS Script item

  • MIT-лицензия

Проверено и работает на:

  • Kerio Connect 10.0.8.9228 (Windows Server)

  • Zabbix 7.0

  • Сбор через Zabbix proxy на отдельной машине

  • Около 5000 учётных записей, средняя нагрузка 600+ тысяч принятых сообщений и 90+ тысяч проверок спама с момента старта

Чего пока нет, но хотелось бы:

  • Сбор по нескольким серверам в Multi-Server-конфигурации Kerio (это коммерческий вариант, в моей установке не используется)

  • Готовый Grafana-дашборд с тем же набором метрик (Zabbix Grafana datasource, JSON в репозиторий)

Если у вас Kerio Connect и Zabbix — забирайте, пробуйте, присылайте issues. Шаблон проверен только на 10 версии и трез установках; на других версиях могут вылезти грабли, которых я не видел. PR с фиксами и расширениями принимаются.

Репозиторий: github.com/IT-for-Prof/zabbix-kerio-connect

В репозитории найдёте YAML обоих шаблонов, Python и JS коллекторы, документацию по полям API (docs/api_fields.md — то, что я выяснил опытным путём при разработке), 60 юнит-тестов и краткий setup-гайд.