Compare commits

..

No commits in common. "32c59a400527b788774dfb1a378f2f94712b7b4b" and "c1ca7b957993fe3a3ea3694e4d1447e43a78e227" have entirely different histories.

6 changed files with 47 additions and 424 deletions

View File

@ -55,18 +55,14 @@ Zabbix агент по запросу сервера запускает скри
и зависимые элементы с препроцессингом JSONPath. и зависимые элементы с препроцессингом JSONPath.
Настройка: Настройка:
1. Импортировать шаблон в Zabbix: 1. Импортировать шаблон `zabbix/zbx_ses_template_5.0_up.xml` в Zabbix
- Zabbix 6.4: `zabbix/zbx_ses_template_up.xml`
- Zabbix 5.0: `zabbix/zbx_ses_template_5.0_up.xml`
2. Привязать шаблон к хосту (Configuration → Hosts → выберите хост → Templates) 2. Привязать шаблон к хосту (Configuration → Hosts → выберите хост → Templates)
3. В конфиге монитора `config/config.yaml` установить `mode: "userparameter"` 3. В конфиге монитора `config/config.yaml` установить `mode: "userparameter"`
4. При старте демон автоматически создаёт конфиг агента: 4. В `/etc/zabbix/zabbix_agentd.conf` добавить строку:
`/opt/ses-monitor/config/zbx_agentd_ses.conf`
5. В `/etc/zabbix/zabbix_agentd.conf` добавить строку (или скопировать конфиг в `/etc/zabbix/zabbix_agentd.d/`):
``` ```
Include=/opt/ses-monitor/config/zbx_agentd_ses.conf Include=/opt/ses-monitor/config/zbx_agentd_ses.conf
``` ```
6. Перезапустить Zabbix агента: `sudo systemctl restart zabbix-agent` 5. Перезапустить Zabbix агента: `sudo systemctl restart zabbix-agent`
## Структура проекта ## Структура проекта
@ -90,11 +86,9 @@ Zabbix агент по запросу сервера запускает скри
│ ├── example_liveness.yaml # Пример: быстрая проверка │ ├── example_liveness.yaml # Пример: быстрая проверка
│ └── example_dialog.yaml # Пример: многошаговый диалог │ └── example_dialog.yaml # Пример: многошаговый диалог
├── zabbix/ ├── zabbix/
│ ├── zbx_ses_template.xml # Шаблон Zabbix 6.4 для sender (trapper) │ ├── zbx_ses_template_5.0.xml # Шаблон для sender (trapper)
│ ├── zbx_ses_template_up.xml # Шаблон Zabbix 6.4 для UserParameter │ ├── zbx_ses_template_5.0_up.xml # Шаблон для UserParameter
│ ├── zbx_ses_template_5.0.xml # Шаблон Zabbix 5.0 для sender (trapper) │ └── zbx_ses_monitor.conf # UserParameter для Zabbix агента
│ ├── zbx_ses_template_5.0_up.xml # Шаблон Zabbix 5.0 для UserParameter
│ └── zbx_ses_monitor.conf # Резервный конфиг UserParameter
├── scripts/ ├── scripts/
│ └── zbx_get.py # Скрипт чтения JSON для UserParameter │ └── zbx_get.py # Скрипт чтения JSON для UserParameter
├── systemd/ ├── systemd/
@ -187,15 +181,8 @@ journalctl -u ses-monitor -f
### 6. Импорт шаблона Zabbix ### 6. Импорт шаблона Zabbix
В зависимости от версии Zabbix и режима доставки:
| Zabbix | sender (trapper) | userparameter |
|---|---|---|
| 6.4 | `zabbix/zbx_ses_template.xml` | `zabbix/zbx_ses_template_up.xml` |
| 5.0 | `zabbix/zbx_ses_template_5.0.xml` | `zabbix/zbx_ses_template_5.0_up.xml` |
1. Zabbix Frontend → **Configuration → Templates → Import** 1. Zabbix Frontend → **Configuration → Templates → Import**
2. Выбрать файл шаблона 2. Выбрать файл `zabbix/zbx_ses_template_5.0.xml` (для sender) или `zabbix/zbx_ses_template_5.0_up.xml` (для UserParameter)
3. Нажать Import 3. Нажать Import
4. Привязать шаблон "SES Monitor" к нужному хосту 4. Привязать шаблон "SES Monitor" к нужному хосту

View File

@ -93,7 +93,6 @@ class SESMonitorApp:
self._config.ses.base_url, self._config.ses.base_url,
zbx_cfg.metrics_file, zbx_cfg.metrics_file,
) )
self._zabbix.write_agentd_config()
else: else:
log.info( log.info(
"SES Monitor v%s запускается (SES: %s, Zabbix: %s:%d, хост: %s)", "SES Monitor v%s запускается (SES: %s, Zabbix: %s:%d, хост: %s)",
@ -241,46 +240,46 @@ class SESMonitorApp:
Zabbix-метрик (ses.scenario.ok, .total, .failed, .avg_time, .max_time, Zabbix-метрик (ses.scenario.ok, .total, .failed, .avg_time, .max_time,
.error). Это делает шаблон Zabbix универсальным не нужно добавлять .error). Это делает шаблон Zabbix универсальным не нужно добавлять
отдельный item под каждый сценарий. отдельный item под каждый сценарий.
Всегда пишет дефолтные значения метрик, даже если сценарии
отключены или не настроены иначе Zabbix-препроцессинг JSONPath
упадёт на отсутствующих ключах.
""" """
if not self._scenario_runner:
return
due_results: List[ScenarioResult] = [] due_results: List[ScenarioResult] = []
if self._scenario_runner: for scenario in self._scenarios:
for scenario in self._scenarios: name = scenario.name or "unknown"
name = scenario.name or "unknown" last_run = self._last_scenario_run.get(name, 0)
last_run = self._last_scenario_run.get(name, 0)
if now - last_run >= scenario.interval: if now - last_run >= scenario.interval:
try: try:
result = await self._scenario_runner.run_one( result = await self._scenario_runner.run_one(
self._client, scenario self._client, scenario
)
due_results.append(result)
self._last_scenario_run[name] = now
if not result.passed:
log.warning(
"Сценарий '%s': ПРОВАЛЕН за %dms (ошибок: %d шагов)",
name,
round(result.total_time_ms),
len(result.failed_steps),
) )
due_results.append(result) except Exception as e:
self._last_scenario_run[name] = now log.exception(
"Сценарий '%s': неожиданное исключение: %s", name, e
)
if not result.passed: # Агрегируем результаты и отправляем фиксированные ключи
log.warning( if due_results and self._zabbix.enabled:
"Сценарий '%s': ПРОВАЛЕН за %dms (ошибок: %d шагов)",
name,
round(result.total_time_ms),
len(result.failed_steps),
)
except Exception as e:
log.exception(
"Сценарий '%s': неожиданное исключение: %s", name, e
)
if self._zabbix.enabled:
agg = aggregate_scenario_results(due_results) agg = aggregate_scenario_results(due_results)
self._zabbix.add("ses.scenario.ok", agg["ok"]) self._zabbix.add("ses.scenario.ok", agg["ok"])
self._zabbix.add("ses.scenario.total", agg["total"]) self._zabbix.add("ses.scenario.total", agg["total"])
self._zabbix.add("ses.scenario.failed", agg["failed"]) self._zabbix.add("ses.scenario.failed", agg["failed"])
self._zabbix.add("ses.scenario.avg_time", agg["avg_time"]) self._zabbix.add("ses.scenario.avg_time", agg["avg_time"])
self._zabbix.add("ses.scenario.max_time", agg["max_time"]) self._zabbix.add("ses.scenario.max_time", agg["max_time"])
self._zabbix.add("ses.scenario.error", agg["error"]) if agg["error"]:
self._zabbix.add("ses.scenario.error", agg["error"])
async def _run_log_check(self) -> None: async def _run_log_check(self) -> None:
"""Выполняет проверку логов (если включено).""" """Выполняет проверку логов (если включено)."""

View File

@ -220,43 +220,6 @@ class UserParameterWriter:
""" """
return ( return (
"# Автоматически сгенерировано ses-monitor.\n" "# Автоматически сгенерировано ses-monitor.\n"
"# Добавьте в /etc/zabbix/zabbix_agentd.conf:\n" "# Требуется: Include=/opt/ses-monitor/config/zbx_agentd_ses.conf\n"
f"# Include={self._config.agentd_config_dir}/zbx_agentd_ses.conf\n"
"# Или скопируйте этот файл в /etc/zabbix/zabbix_agentd.d/\n"
f"UserParameter=ses.metrics,/opt/ses-monitor/scripts/zbx_get.py\n" f"UserParameter=ses.metrics,/opt/ses-monitor/scripts/zbx_get.py\n"
) )
def write_agentd_config(self) -> bool:
"""Записывает конфигурационный файл UserParameter на диск.
Создаёт или обновляет zbx_agentd_ses.conf в agentd_config_dir.
Использует атомарную запись (temp + rename).
Returns:
True если запись успешна, False при ошибке.
"""
config_dir = Path(self._config.agentd_config_dir)
config_path = config_dir / "zbx_agentd_ses.conf"
content = self.generate_agentd_config()
try:
config_dir.mkdir(parents=True, exist_ok=True)
fd, tmp_path = tempfile.mkstemp(
dir=str(config_dir),
prefix=".zbx_agentd_",
suffix=".tmp",
)
try:
os.write(fd, content.encode("utf-8"))
os.fsync(fd)
os.close(fd)
os.replace(tmp_path, str(config_path))
os.chmod(str(config_path), 0o644)
except Exception:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
raise
log.info("Конфиг Zabbix агента записан: %s", config_path)
return True
except Exception as e:
log.error("Ошибка записи конфига Zabbix агента: %s", e)
return False

View File

@ -13,7 +13,7 @@ Group=root
WorkingDirectory=/opt/ses-monitor WorkingDirectory=/opt/ses-monitor
# Запуск через venv # Запуск через venv
ExecStart=/opt/ses-monitor/.venv/bin/python3 -m ses_monitor ExecStart=/opt/ses-monitor/venv/bin/python3 -m ses_monitor
# Перезапуск при падении # Перезапуск при падении
Restart=always Restart=always

View File

@ -1,5 +1,10 @@
# Автоматически сгенерировано ses-monitor. # =============================================================================
# Добавьте в /etc/zabbix/zabbix_agentd.conf: # Резервный вариант: UserParameter для Zabbix Agent (пассивный режим).
# Include=/opt/ses-monitor/config/zbx_agentd_ses.conf # =============================================================================
# Или скопируйте этот файл в /etc/zabbix/zabbix_agentd.d/ # Используется если zabbix_sender недоступен.
UserParameter=ses.metrics,/opt/ses-monitor/scripts/zbx_get.py # Поместить в /etc/zabbix/zabbix_agentd.d/ses_monitor.conf
# и перезапустить zabbix-agent: systemctl restart zabbix-agent
#
# Скрипт ses_monitor_zabbix_agent.py должен быть установлен в
# /opt/ses-monitor/scripts/ses_monitor_zabbix_agent.py
# =============================================================================

View File

@ -1,331 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<zabbix_export>
<version>6.4</version>
<template_groups>
<template_group>
<uuid>88166f35-c76b-44e2-b8ad-333b3b4b9692</uuid>
<name>Templates</name>
</template_group>
</template_groups>
<templates>
<template>
<uuid>f4e7d78a-8163-46db-a7cf-e9a79df9a740</uuid>
<template>SES Monitor</template>
<name>SES Monitor — Service Engine Server</name>
<description>Шаблон для мониторинга диалоговой сценарной машины SES.
Метрики передаются watchdog-демоном ses-monitor через UserParameter (Zabbix агент).
JSON с метриками пишется в /var/tmp/ses_monitor/metrics.json и читается
скриптом scripts/zbx_get.py. Master-элемент: ses.metrics.
Проверяет: доступность API, лицензию, выполнение сценариев.
Сценарии задаются в YAML-файлах (config/scenarios/). Все сценарии
агрегируются в фиксированные метрики — шаблон универсален и не зависит
от количества или состава сценариев.</description>
<groups>
<group>
<name>Templates</name>
</group>
</groups>
<macros>
<macro>
<macro>{$SES_API_TIMEOUT_MS}</macro>
<value>5000</value>
<description>Порог медленного ответа API (миллисекунды)</description>
</macro>
</macros>
<items>
<item>
<uuid>b5d05b4d-7b9d-4e63-98de-7fe507748eaa</uuid>
<name>SES: все метрики (JSON)</name>
<type>ZABBIX_PASSIVE</type>
<key>ses.metrics</key>
<delay>30s</delay>
<history>7d</history>
<trends>0</trends>
<value_type>TEXT</value_type>
<description>Полный JSON со всеми метриками ses-monitor. Используется как master item для dependent items с JSONPath.</description>
</item>
<!-- Liveness -->
<item>
<uuid>b6a64924-6ecf-406a-80e1-cb5ad8f4d9e4</uuid>
<name>SES: API доступность</name>
<type>DEPENDENT</type>
<key>ses.api.liveness</key>
<master_item>
<key>ses.metrics</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<params>$["ses.api.liveness"]</params>
</step>
</preprocessing>
<history>7d</history>
<trends>30d</trends>
<value_type>UNSIGNED</value_type>
<description>0 — API недоступен, 1 — работает. Проверяется GET /ses/hostname.</description>
<triggers>
<trigger>
<uuid>b5798e51-dc19-4d90-aa68-45107a7945e5</uuid>
<expression>last(/SES Monitor/ses.api.liveness)=0</expression>
<name>SES API недоступен на {HOST.NAME}</name>
<priority>HIGH</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item>
<item>
<uuid>ce800e89-14a8-4084-bd16-0abd594b8ef4</uuid>
<name>SES: время ответа API</name>
<type>DEPENDENT</type>
<key>ses.api.response_time</key>
<master_item>
<key>ses.metrics</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<params>$["ses.api.response_time"]</params>
</step>
</preprocessing>
<history>7d</history>
<trends>30d</trends>
<value_type>FLOAT</value_type>
<units>ms</units>
<description>Время ответа GET /ses/hostname в миллисекундах.</description>
<triggers>
<trigger>
<uuid>6813663c-2e56-4b42-aecd-3dfd54d99ffb</uuid>
<expression>last(/SES Monitor/ses.api.response_time)&gt;{$SES_API_TIMEOUT_MS}</expression>
<name>SES: медленный ответ API (&gt;{$SES_API_TIMEOUT_MS} мс)</name>
<priority>WARNING</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item>
<item>
<uuid>05fa180f-9e01-4fde-b73c-fa7b1d9522f8</uuid>
<name>SES: статус лицензии</name>
<type>DEPENDENT</type>
<key>ses.license.ok</key>
<master_item>
<key>ses.metrics</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<params>$["ses.license.ok"]</params>
</step>
</preprocessing>
<history>7d</history>
<trends>30d</trends>
<value_type>UNSIGNED</value_type>
<description>0 — проблема с лицензией, 1 — в порядке.</description>
<triggers>
<trigger>
<uuid>863d9b4b-8a9d-4e0c-9b9d-fd3c8022df82</uuid>
<expression>last(/SES Monitor/ses.license.ok)=0</expression>
<name>SES: проблема с лицензией на {HOST.NAME}</name>
<priority>HIGH</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item>
<item>
<uuid>5c090eb8-6152-4fb5-a49a-ebf7d803f975</uuid>
<name>SES: дней до окончания лицензии</name>
<type>DEPENDENT</type>
<key>ses.license.days_remaining</key>
<master_item>
<key>ses.metrics</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<params>$["ses.license.days_remaining"]</params>
</step>
</preprocessing>
<history>7d</history>
<trends>30d</trends>
<value_type>FLOAT</value_type>
<units>d</units>
<description>Количество дней до окончания лицензии SES. -1 = бессрочная.</description>
</item>
<item>
<uuid>696ac7fe-11f3-42f9-a56e-c2533bc7d71c</uuid>
<name>SES: количество сценариев (роботов)</name>
<type>DEPENDENT</type>
<key>ses.robots.count</key>
<master_item>
<key>ses.metrics</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<params>$["ses.robots.count"]</params>
</step>
</preprocessing>
<history>7d</history>
<trends>30d</trends>
<value_type>UNSIGNED</value_type>
<description>Общее количество зарегистрированных сценариев (роботов).
Изменение значения может сигнализировать о проблемах конфигурации.</description>
</item>
<!-- Сценарии -->
<item>
<uuid>48930571-519a-4071-adec-fb22a732eaad</uuid>
<name>SES: тестовые сценарии — все успешны</name>
<type>DEPENDENT</type>
<key>ses.scenario.ok</key>
<master_item>
<key>ses.metrics</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<params>$["ses.scenario.ok"]</params>
</step>
</preprocessing>
<history>7d</history>
<trends>30d</trends>
<value_type>UNSIGNED</value_type>
<description>1 — все сценарии прошли, 0 — есть упавшие.</description>
<triggers>
<trigger>
<uuid>a44be8fd-d652-40a2-8be2-b9760df19975</uuid>
<expression>last(/SES Monitor/ses.scenario.ok)=0</expression>
<name>SES: тестовые сценарии мониторинга падают на {HOST.NAME}</name>
<priority>AVERAGE</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item>
<item>
<uuid>b28ac087-8070-4195-95ea-99ccb24fa370</uuid>
<name>SES: тестовые сценарии — всего</name>
<type>DEPENDENT</type>
<key>ses.scenario.total</key>
<master_item>
<key>ses.metrics</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<params>$["ses.scenario.total"]</params>
</step>
</preprocessing>
<history>7d</history>
<trends>30d</trends>
<value_type>UNSIGNED</value_type>
<description>Количество активных сценариев мониторинга.</description>
</item>
<item>
<uuid>5b003d5f-f88b-47e3-a3dc-17d196036351</uuid>
<name>SES: тестовые сценарии — упало</name>
<type>DEPENDENT</type>
<key>ses.scenario.failed</key>
<master_item>
<key>ses.metrics</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<params>$["ses.scenario.failed"]</params>
</step>
</preprocessing>
<history>7d</history>
<trends>30d</trends>
<value_type>UNSIGNED</value_type>
<description>Количество упавших сценариев за последний цикл.</description>
<triggers>
<trigger>
<uuid>4de7a731-b6b7-41c6-85a2-71a5f1a45a66</uuid>
<expression>last(/SES Monitor/ses.scenario.failed)&gt;0</expression>
<name>SES: тестовые сценарии мониторинга падают на {HOST.NAME}</name>
<priority>WARNING</priority>
<manual_close>YES</manual_close>
</trigger>
</triggers>
</item>
<item>
<uuid>0f911f5a-7529-495d-b35a-d1784a32a0de</uuid>
<name>SES: тестовые сценарии — среднее время</name>
<type>DEPENDENT</type>
<key>ses.scenario.avg_time</key>
<master_item>
<key>ses.metrics</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<params>$["ses.scenario.avg_time"]</params>
</step>
</preprocessing>
<history>7d</history>
<trends>30d</trends>
<value_type>FLOAT</value_type>
<units>ms</units>
<description>Среднее время выполнения сценария (миллисекунды).</description>
</item>
<item>
<uuid>5747ec81-622c-44dc-8eca-c9be38458dad</uuid>
<name>SES: тестовые сценарии — макс. время</name>
<type>DEPENDENT</type>
<key>ses.scenario.max_time</key>
<master_item>
<key>ses.metrics</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<params>$["ses.scenario.max_time"]</params>
</step>
</preprocessing>
<history>7d</history>
<trends>30d</trends>
<value_type>FLOAT</value_type>
<units>ms</units>
<description>Максимальное время выполнения сценария (миллисекунды).</description>
</item>
<item>
<uuid>bafb3afc-2ac4-4608-9f5b-d49d88960921</uuid>
<name>SES: тестовые сценарии — ошибка</name>
<type>DEPENDENT</type>
<key>ses.scenario.error</key>
<master_item>
<key>ses.metrics</key>
</master_item>
<preprocessing>
<step>
<type>JSONPATH</type>
<params>$["ses.scenario.error"]</params>
</step>
</preprocessing>
<history>7d</history>
<trends>0</trends>
<value_type>TEXT</value_type>
<description>Текст ошибки: "N сценариев упали". Пустая строка если всё хорошо.</description>
</item>
</items>
</template>
</templates>
</zabbix_export>