Как я Zabbix с LLM дружил в свободное время. Архитектурный обзор взаимодействия с нейросетью. Часть 4 «Реализация»

от автора

У всех лапки

У всех лапки

Это заключительная статья цикла о том, как при четко поставленной задаче, заранее сформированном ТЗ, продуманном HLD и детальном LLD можно получить от нейросети не набор случайных скриптов, а вполне рабочий self-hosted сервис.

В первой части рассматривалась постановка задачи, формирование ТЗ и пояснения, почему даже для коммерческой LLM лучше ставить правильные, четкие задачи.

Во второй части рассмотрели требования к локальной LLM, муки выбора и сравнение небольших моделей между собой.

В третьей части рассмотрели уже HLD и LLD структуры.

В этой статье уже прикладной материал. Не про то, как правильно думать над задачей, а про то, что получилось, когда архитектурный каркас начали последовательно превращать в код.

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

Часть 1: Вводная и формирование ТЗ

Часть 2: Выбор локальной LLM

Часть 3: Формирование HLD и немного LLD

Часть 4: Что из этого вышло (Вы здесь)

Именно здесь стало особенно хорошо видно главное: нейросеть не заменила проектирование, но оказалась очень полезной, когда у нее уже были:

  • четко поставленная задача;

  • понятный периметр;

  • HLD;

  • LLD;

  • ограничения;

В общем, все правила, по которым система должна жить.

Шаг первый – формирование входных условий

Главным входом для нейросети был набор подготовленных документов, чтобы код генерировался не в вакууме. Сначала нейросеть получала рамку проекта, затем — архитектурные ограничения, а уже после этого — конкретную задачу на реализацию очередного блока.

Практическая последовательность следующая:

  1. Подготовка понятного и четкого ТЗ.

  2. Составление HLD на основе ТЗ.

  3. Детальнее описание LLD (возможно с нейросетью, однако не полностью на нее полагаясь).

  4. Реализация по модулям в соответствии с подготовленной архитектурой.

Это важный момент. Если просто скормить модели всю задачу целиком с прямолинейным запросом, на выходе получится что-то похожее на систему, но при этом произойдет смешение ролей контейнеров и модулей, возникнет дублирование ответственности элементов, с высокой вероятностью будут опущены ограничения по критичности, произойдет или упрощение архитектуры до одного приложения, или наоборот усложнение несколькими лишними сущностями.

Поэтому базовая стратегия была не писать систему целиком, а реализовывать ее маленькими управляемыми блоками внутри уже зафиксированной архитектуры.

Шаг второй – постановка задачи на реализацию

Наиболее рабочим вариантом для постановки задачи нейросети было использование именно ролевой модели, для наполнения контекстом самого запроса и однозначного определения точки зрения кем является субъект-исполнитель (нейросеть).

То есть условно не «Напиши сервис обработки алертов на основе приложенных документов»

А примерно так:

«Ты являешься экспертом-программистом Python и должен помогать инженеру-архитектору в реализации проекта анализа событий Zabbix локальной моделью Qwen3.5:4B в соответствии со следующим ТЗ, HLD и LLD.»

Дальше к этому уже прикладывались документы (ТЗ, HLD, нужный кусок LLD) и конкретное задание на очередной этап.

Например:

  • подготовить alert-receiver;

  • описать NormalizedAlertEvent;

  • реализовать очередь в Redis;

  • реализовать policy для High/Disaster;

  • реализовать suppress/flap/recovery;

  • подключить Matrix;

  • реализовать триаж и так далее.

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

Примеры рабочих промптов

Промпты на реализацию модуля

Хорошая постановка задачи для реализации выглядела примерно так :

Ты являешься экспертом-программистом Python и должен помогать инженеру-архитектору

в реализации проекта анализа событий Zabbix локальной моделью Qwen3.5:4B.

 Контекст:

— есть ТЗ;

— есть HLD;

— есть LLD;

— архитектура уже утверждена и менять ее не нужно;

— нужно реализовать только указанный модуль;

— FastAPI, Redis, Docker Compose уже приняты как технологическая основа.

Текущая задача:

реализовать alert-receiver, который:

— принимает webhook от Zabbix;

— проверяет токен;

— валидирует payload;

— нормализует событие;

— пересылает его во внутренний ingest;

— не содержит enrichment, triage, remediation и иной тяжелой логики.

 Требования:

— код на Python;

— Pydantic v2;

— понятная структура модулей;

— без лишних абстракций;

— без изменения общей архитектуры.

 

Сначала опиши структуру файлов, затем код по файлам.

Такой промпт лучше работает, так как:

  • сразу запрещает модели вносить изменения в проект и уходить в мир фантазий;

  • не оставляет сомнений в границах модуля;

  • задает формат ответа.

Все это уменьшает шанс того, что модель сделает что-то лишнее.

Промпты на доработку

Отдельно хорошо работал формат «переделай существующий код с учетом конкретного требования».

Например:

Нужно доработать текущую реализацию.

Проблема:

alert-receiver сейчас принимает webhook даже при неверном токене.

Требование:

— вернуть 401 при неверном токене;

— не передавать событие дальше;

— не ломать текущую схему нормализации;

— не менять внешний API.

Собери мне полный файл main.py целиком, для простой замены.

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

Промпты на рефакторинг

Отдельной задачей были запросы рефакторинг после того, как прототип уже заработал.

Например:

Следующим логичным шагом будет рефакторинг:

необходимо вынести доставку в отдельный слой notification-connectors внутри проекта,

чтобы Matrix и Mail не были частью main.py. Подготовь отдельный модуль для упрощения внесения изменений.

После этого следующим логичным шагом будет небольшой рефакторинг:

вынести доставку в отдельный слой notification-connectors внутри проекта,

чтобы Matrix и Mail не были частью main.py. Подготовь отдельный модуль для упрощения внесения изменений.

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

Промпты внутри проекта: как общалась локальная LLM

Отдельная тема — промпты к локальной LLM внутри самого приложения.

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

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

Промпт для триажа

Для триажа логика была такой:

  • low-severity событие;

  • ограниченный контекст;

  • нужен короткий вывод;

  • только JSON;

  • без длинных рассуждений;

  • без творческой импровизации.

Упрощенно проипт выглядел так:

You are a cautious AIOps triage assistant. /no_think

Decide whether a low-severity Zabbix alert should notify, suppress, or stay on hold.

Rules:

1. Output JSON only.

2. Allowed verdict values: notify, suppress, hold.

3. notify = actionable low-severity event worth sending to operator now.

4. suppress = obvious noise, repetition, or insignificant deviation.

5. hold = uncertain, informational, or not enough evidence.

6. Never escalate to email; low-severity notify means Matrix only.

7. Be conservative.

Return exactly this JSON schema:

{

  «verdict»: «notify|suppress|hold»,

  «classification»: «actionable|noise|uncertain»,

  «reason»: «short explanation»

}

Alert context:<>

Промпт формирования рекомендаций

С рекомендациями было еще сложнее, потому что это уже зона риска.

Модель должна только предлагать диагностику, чтобы облегчить инженеру операции по поиску причин и экономить время в базовых сценариях диагностики.

Поэтому промпт задавал:

  • только read-only действия;

  • никаких изменений конфигурации;

  • никаких restart/stop/delete;

  • никаких деструктивных команд;

  • Базовую логику поведения при recovery и др;

  • четкий JSON.

 Примерно так:

You are a cautious SRE assistant. /no_think

Generate remediation guidance for a Zabbix alert.

Rules:

1. Output JSON only.

2. Do not suggest destructive commands.

3. Commands must be diagnostic or read-only.

4. Keep it concise.

5. If this is a recovery event, return empty remediation.

6. Do not assume that host name indicates container name.

7. If alert_scope is host_os, diagnose the Linux host, not a container.

8. Only suggest docker/container commands when alert_scope is container.

9. Prefer generic Linux diagnostic commands for host_os alerts.

10. Avoid restart, rm, kill -9, stop, prune, delete, drop, truncate, format.

Return exactly this JSON schema:

{

  «summary»: «short diagnostic summary»,

  «steps»: [«…»],

  «commands»: [«…»]

}

«Alert context<>

А поверх этого уже работали guardrails в самом коде.

RCA промпт

Здесь задача была еще тоньше: осторожно предложить, может ли текущее событие быть root, child или standalone.

То есть промпт должен был не стимулировать фантазию, а наоборот ограничивать ее.

Примерно так:

You are a cautious AIOps correlation assistant./no_think

Decide whether the current alert is standalone, a root cause candidate, or a downstream child event.

Rules:

1. Output JSON only.

2. Allowed role values: standalone, root, child.

3. Use the current event and recent events in the same service/scope/domain context.

4. Prefer standalone when uncertain.

5. child role requires a parent_event_id or parent_correlation_id from recent_events.

6. Never suggest suppress_child for High or Disaster alerts.

7. suppress_child may be true only for role=child and only when confidence is high.

8. confidence must be one of: low, medium, high.

9. Synthetic website alerts may be children of recent app/container outages if service/scope/domain matches.

Return exactly this JSON schema:

{

  «role»: «standalone|root|child»,

  «kind»: «event_kind»,

  «confidence»: «low|medium|high»,

  «parent_event_id»: null,

  «reason»: «short explanation»

}

Именно слово cautious в таких сценариях оказалось очень полезным. 

Модель становилась заметно менее склонной додумывать ответы.

Какие узкие места всплыли в процессе реализации

Синхронность

До тех пор, пока в пайплайне нет LLM и enrichment, синхронный подход выглядит терпимым, но как только появляются триаж, рекомендации и корреляция, становится ясно, что простая цепочка request-response больше не подходит.

Именно поэтому разделение receiver -> ingest -> worker оказалось обязательным условием стабильности.

Ожидание и реальность

Ожидание и реальность

Matrix и токены

Здесь вышло забавнее всего, ведь казалось бы – есть room id, чат и токен, что может быть проще? Но как только появляется MAS, OAuth, refresh token и короткий lifetime, внезапно выясняется, что мы свернули в сторону от классического простого «HTTP POST в чат».

Именно здесь очень пригодилось архитектурное разделение:

  • notifier отдельно;

  • token manager отдельно;

  • dispatch отдельно.

В противном случае логика доставки быстро перемешивается с основной логикой обработки события.

Передача события Matrix

Передача события Matrix
Обновление токена Matrix

Обновление токена Matrix

Разговорчивая LLM

Без жесткого формата ответов модель быстро начинает писать слишком длинно, объяснять очевидное, уходить от JSON, а самое страшное — ДУМАТЬ на протяжении 10 минут на ноутбуке с RTX 4090, вешая все, что имеется и предлагая рассказ на целую страницу в стиле «как я провел лето».

Это особенно заметно на remediation.

Поэтому итоговый рабочий вариант был не «просто промпт», а:

  • промпт;

  • JSON schema;

  • pydantic валидация;

  • fallback;

  • guardrails.

Корреляция без детерминированной базы

Идея «пусть LLM сама поймет, что root cause, а что child» звучит заманчиво.

На практике она быстро упирается в то, что:

  • контекст ограничен;

  • событий может быть мало;

  • связи не всегда текстово очевидны;

  • корневая и дочерняя причины сильно разнесены по времени.

Поэтому детерминированный YAML-слой оказался не просто прекрасной инженерной опорой, поверх которой и строился LLM fallback (а не вместо него).

Ограниченность в ресурсах

С этим, к сожалению, ничего не поделаешь, но обработка каждого события приводила к резким всплескам по CPU, что объяснимо и все равно необходимо отметить

Как оно думает на домашнем медиа-сервере

Как оно думает на домашнем медиа-сервере

Почему и как это тестировалось

Архитектурно красивое решение на бумаге без тестирования очень быстро превращается в «работает – не трогай», поэтому следующим логичным шагом была методология проверки.

Сразу оговорюсь: это не было нагрузочным тестированием уровня enterprise-платформы. Это был вполне прикладной набор проверок для self-hosted контура на Linux-хосте и Docker.

Методология тестирования

Функциональное тестирование пайплайна

Проверялись сценарии:

  • валидный webhook;

  • невалидный токен;

  • High/Disaster без участия LLM в routing;

  • Warning через triage;

  • remediation только в read-only сценарии;

  • recovery;

  • suppress;

  • audit;

  • Matrix / Mail delivery.

Это тот уровень, на котором выясняется, что архитектура вообще реализована правильно.

Все результаты прикладывать не имеет смысла, т.к. будет перегруз информацией. Под катом пример целого аудит-лога по событию о падении Nextcloud.

Открывать на свой страх и риск — лог на 250 строк
[    "journal": {      "updated_at": "2026-05-14T13:25:32.790566+00:00",      "host": "Nextcloud WEB access",      "severity": "High",      "job_id": "81521ab1-e96d-4666-abce-e197fbd0b32b",      "suppressed": "false",      "decision_json": "{\"notify\": true, \"severity\": \"High\", \"channels\": [\"matrix\", \"mail\"], \"reason\": \"Severity is High/Disaster: mandatory notification. RCA baseline matched a parent event in the same service/scope.\", \"routing_class\": \"high_priority\", \"fingerprint\": \"nextcloud_web_access|failed_step_of_scenario__common_test_.|site_https_next.example.org.info_login_is_down\", \"repeat_count\": 13, \"suppressed\": false, \"suppress_reason\": null, \"event_phase\": \"problem\", \"open_incident_found\": false, \"recovered_from_severity\": null, \"flap_detected\": false, \"flap_event_count\": 0, \"flap_reason\": null, \"triage_applied\": false, \"triage_source\": null, \"triage_verdict\": null, \"triage_reason\": null, \"triage_classification\": null, \"correlation_applied\": true, \"correlation_role\": \"child\", \"correlation_group_id\": \"67701\", \"correlation_kind\": \"site_unavailable\", \"correlation_reason\": \"Likely downstream of container_unavailable; matched recent event in the same service/scope/domain window within 180s\", \"correlation_source\": \"deterministic\", \"correlation_confidence\": \"high\", \"parent_event_id\": \"67701\", \"parent_correlation_id\": \"95809c08-42c9-43cd-b818-148d558bf164\", \"root_cause_candidate\": false, \"correlated_event_count\": 0, \"llm_enriched\": true, \"remediation_summary\": \"Investigate why the Nextcloud web service is unreachable by checking network connectivity and service status on the host.\", \"remediation_steps\": [\"Verify if the Nextcloud web server process is running.\", \"Check if the specific port is listening and accessible from the network.\", \"Review recent system logs for application errors or crashes.\", \"Confirm if the firewall or security group rules are blocking the expected traffic.\"], \"remediation_commands\": [\"systemctl status nextcloud\", \"netstat -tlnp | grep :80\", \"journalctl -u nextcloud --since 1h\", \"curl -I https://https_next.example.org/login\"]}",      "state": "processed",      "processing_identity": "67725:problem",      "queued_at": "2026-05-14T13:19:03.241219+00:00",      "last_stage_status": "ok",      "routing_class": "high_priority",      "correlation_id": "81521ab1-e96d-4666-abce-e197fbd0b32b",      "queue_name": "alert:queue:events",      "last_stage": "worker_processed",      "delivery_at": "2026-05-14T13:25:32.781619+00:00",      "delivery_json": "{\"attempted\": true, \"matrix_attempted\": true, \"matrix_sent\": true, \"matrix_event_id\": \"$3OJdJQoTXf1BEmFp0WC9qbHTOdeVuyF6bhp-HJXqEz8\", \"matrix_error\": null, \"matrix_image_attempted\": false, \"matrix_image_sent\": false, \"matrix_image_event_id\": null, \"matrix_image_mxc_uri\": null, \"matrix_image_error\": null, \"mail_attempted\": true, \"mail_sent\": true, \"mail_error\": null, \"errors\": []}",      "decision_at": "2026-05-14T13:25:31.864527+00:00",      "worker_started_at": "2026-05-14T13:23:57.880735+00:00",      "worker_attempt": "0",      "decision_reason": "Severity is High/Disaster: mandatory notification. RCA baseline matched a parent event in the same service/scope.",      "service": "Failed step of scenario \"common test\".",      "trigger_name": "Site https://next.example.org/login is down",      "event_id": "67725",      "notify": "true",      "decision": {        "notify": true,        "severity": "High",        "channels": [          "matrix",          "mail"        ],        "reason": "Severity is High/Disaster: mandatory notification. RCA baseline matched a parent event in the same service/scope.",        "routing_class": "high_priority",        "fingerprint": "nextcloud_web_access|failed_step_of_scenario__common_test_.|site_https_next.example.org_login_is_down",        "repeat_count": 13,        "suppressed": false,        "suppress_reason": null,        "event_phase": "problem",        "open_incident_found": false,        "recovered_from_severity": null,        "flap_detected": false,        "flap_event_count": 0,        "flap_reason": null,        "triage_applied": false,        "triage_source": null,        "triage_verdict": null,        "triage_reason": null,        "triage_classification": null,        "correlation_applied": true,        "correlation_role": "child",        "correlation_group_id": "67701",        "correlation_kind": "site_unavailable",        "correlation_reason": "Likely downstream of container_unavailable; matched recent event in the same service/scope/domain window within 180s",        "correlation_source": "deterministic",        "correlation_confidence": "high",        "parent_event_id": "67701",        "parent_correlation_id": "95809c08-42c9-43cd-b818-148d558bf164",        "root_cause_candidate": false,        "correlated_event_count": 0,        "llm_enriched": true,        "remediation_summary": "Investigate why the Nextcloud web service is unreachable by checking network connectivity and service status on the host.",        "remediation_steps": [          "Verify if the Nextcloud web server process is running.",          "Check if the specific port is listening and accessible from the network.",          "Review recent system logs for application errors or crashes.",          "Confirm if the firewall or security group rules are blocking the expected traffic."        ],        "remediation_commands": [          "systemctl status nextcloud",          "netstat -tlnp | grep :80",          "journalctl -u nextcloud --since 1h",          "curl -I https://next.example.org/login"        ]      },      "delivery": {        "attempted": true,        "matrix_attempted": true,        "matrix_sent": true,        "matrix_event_id": "$3OJdJQoTXf1BEmFp0WC9qbHTOdeVuyF6bhp-HJXqEz8",        "matrix_error": null,        "matrix_image_attempted": false,        "matrix_image_sent": false,        "matrix_image_event_id": null,        "matrix_image_mxc_uri": null,        "matrix_image_error": null,        "mail_attempted": true,        "mail_sent": true,        "mail_error": null,        "errors": []      }    },    "stages": [      {        "ts": "2026-05-14T13:19:03.242598+00:00",        "stage": "ingest_queued",        "status": "ok",        "details": {          "job_id": "81521ab1-e96d-4666-abce-e197fbd0b32b",          "queue_name": "alert:queue:events"        }      },      {        "ts": "2026-05-14T13:23:57.882251+00:00",        "stage": "worker_started",        "status": "ok",        "details": {          "job_id": "81521ab1-e96d-4666-abce-e197fbd0b32b",          "attempt": 0,          "identity": "67725:problem"        }      },      {        "ts": "2026-05-14T13:23:57.887062+00:00",        "stage": "state_updated",        "status": "ok",        "details": {          "fingerprint": "nextcloud_web_access|failed_step_of_scenario__common_test_.|site_https_next.example.org.info_login_is_down",          "repeat_count": 13,          "event_phase": "problem"        }      },      {        "ts": "2026-05-14T13:23:57.890356+00:00",        "stage": "flap_evaluated",        "status": "ok",        "details": {          "flap_active": false,          "flap_event_count": 1,          "window_seconds": 120        }      },      {        "ts": "2026-05-14T13:23:57.892554+00:00",        "stage": "baseline_policy_applied",        "status": "ok",        "details": {          "notify": true,          "suppressed": false,          "routing_class": "high_priority",          "reason": "Severity is High/Disaster: mandatory notification"        }      },      {        "ts": "2026-05-14T13:23:58.112899+00:00",        "stage": "zabbix_enrichment",        "status": "ok",        "details": {          "event_url": "https://zab.example.org/tr_events.php?triggerid=33360&eventid=67725",          "graph_url": "https://zab.example.org/chart.php?from=2026-05-14+15%3A23%3A58&to=2026-05-14+16%3A23%3A58&itemids%5B0%5D=74588",          "graph_image_path": null,          "zabbix_context_keys": [            "alert_scope",            "component",            "correlation_scopes",            "domain",            "event_tags",            "host_tags",            "item_tags",            "resolved_opdata",            "scope",            "service",            "tags",            "trigger_description",            "trigger_tags"          ]        }      },      {        "ts": "2026-05-14T13:23:58.120043+00:00",        "stage": "correlation",        "status": "ok",        "details": {          "applied": true,          "role": "child",          "kind": "site_unavailable",          "group_id": "67701",          "parent_event_id": "67701",          "parent_correlation_id": "95809c08-42c9-43cd-b818-148d558bf164",          "root_cause_candidate": false,          "correlated_event_count": 0,          "reason": "Likely downstream of container_unavailable; matched recent event in the same service/scope/domain window within 180s",          "suppress_child": false,          "source": "deterministic",          "confidence": "high",          "scope_keys": [            "service:nextcloud",            "domain:next.example.org.info",            "host:nextcloud_web_access",            "service_component:nextcloud:web"          ]        }      },      {        "ts": "2026-05-14T13:25:31.861897+00:00",        "stage": "llm_remediation",        "status": "ok",        "details": {          "summary": "Investigate why the Nextcloud web service is unreachable by checking network connectivity and service status on the host.",          "steps_count": 4,          "commands_count": 4        }      },      {        "ts": "2026-05-14T13:25:31.866296+00:00",        "stage": "decision_made",        "status": "ok",        "details": {          "notify": true,          "suppressed": false,          "routing_class": "high_priority",          "reason": "Severity is High/Disaster: mandatory notification. RCA baseline matched a parent event in the same service/scope."        }      },      {        "ts": "2026-05-14T13:25:32.784658+00:00",        "stage": "delivery_completed",        "status": "ok",        "details": {          "attempted": true,          "matrix_attempted": true,          "matrix_sent": true,          "matrix_event_id": "$3OJdJQoTXf1BEmFp0WC9qbHTOdeVuyF6bhp-HJXqEz8",          "matrix_error": null,          "matrix_image_attempted": false,          "matrix_image_sent": false,          "matrix_image_event_id": null,          "matrix_image_mxc_uri": null,          "matrix_image_error": null,          "mail_attempted": true,          "mail_sent": true,          "mail_error": null,          "errors": []        }      },      {        "ts": "2026-05-14T13:25:32.789741+00:00",        "stage": "worker_processed",        "status": "ok",        "details": {          "attempt": 0,          "error": null        }      }    ]  }...

Тестирование корреляции

Для проверки корреляции нужны не одиночные алерты, а связанные события.

Все команды выполнялись на тестовом хосте. На рабочей системе такой тест может привести к OOM, деградации сервисов и потере данных.

Сценарий тестирования корреляции

Сценарий: зависимость фронта Nextcloud и состояния контейнера Nextcloud.

Поиски корневой причины

Поиски корневой причины

Имеем следующее:

  1. В Zabbix настроен мониторинг контейнеров.

  2. Имеется настроенный reverse-proxy с редиректом на фронт Nextcloud для входа.

  3. В Zabbix Настроен мониторинг страницы входа Nextcloud.

  4. Происходит отключение контейнера Nextcloud.

  5. Zabbix фиксирует отключение контейнера, передает алерт.

  6. Reverse-proxy не может соединиться с бэкендом и отдает Zabbix ошибку 503

  7. Zabbix передает алерт о недоступности.

Что ожидается от системы:

  • событие падения контейнера определяется как более вероятный root;

  • падение страницы фронта как child;

  • в audit фиксируется correlation;

  • для child-событий можно уменьшить шум, но не потерять информацию.

Результат оповещений из Matrix:

  • Падение контейнера (обратить внимание на eventid)

Matrix алерт о падении контейнера

Matrix алерт о падении контейнера
  • Падение фронта

Matrix алерт о недоступности страницы

Matrix алерт о недоступности страницы

RCA сработал правильно, но remediation снова показала границу применимости LLM: модель связала события, однако предложила systemd-команды для контейнерного приложения. Именно поэтому рекомендации остаются подсказкой оператору, а не инструкцией к автоматическому исполнению.

Для наиболее правильного remidiation можно отдельно связать сервисы и их принадлежность к административному слою (docker/виртуализация/systemd и т.д.) внутри Zabbix, на основе которой формировать более точные предлагаемые команды диагностики.

 Нагрузочный сценарий по CPU

Также команды подходят для тестовой системы – моего домашнего медиа-сервера. Для других систем необходимо изменять значения для stress-ng.

На отдельном тестовом хосте:

stress-ng --cpu 24 --timeout 600s
Выполнение

Выполнение

и htop:

Загруженность ресурсов

Загруженность ресурсов

В итоге имеем

Matrix алерт CPU

Matrix алерт CPU

Данное тестирование позволяет проверить следующие контрольные точки:

  • как алерты по CPU/RAM приходят из Zabbix;

  • как быстро они проходят через pipeline;

  • как влияет burst алертов на worker (при генерации нескольких проблем);

  • как ведет себя Ollama при параллельной нагрузке на хост;

  • не начинает ли triage слишком сильно замедлять обработку.

Спойлер – в моей конфигурации при 3-х одновременно полученных алертах обработка заняла 6 минут, что далеко от идеала.

Таким образом получен не просто синтетический бенчмарк модели, а прикладная проверка, как живет сервис в моем окружении.

Конечно, если масштабировать даже на десятки поступающих уведомлений в минуту необходимо полностью пересматривать железную составляющую для оптимизации времени получения и рекомендаций, т.к. 6 минут в проде – непозволительная роскошь.

Общий результат

В результате проект дорос до вполне рабочего self-hosted контура, который умеет:

  • принимать события из Zabbix;

  • не терять их при тяжелой обработке;

  • обогащать алерт контекстом;

  • triage’ить low-severity;

  • строить remediation для диагностики;

  • выполнять deterministic correlation;

  • подключать LLM как fallback;

  • доставлять уведомления;

  • вести audit и health.

Это далеко не корпоративный AIOps-продукт, но такой цели изначально не было. Для домашнего проекта и как проверка архитектурного подхода взаимодействия с LLM результат получился, на мой взгляд, очень показательным.

Чтобы статья не превращалась в документацию по каждому файлу все, что получилось, я вынес в локальный Gitea по ссылке с моего домашнего сервера.

Итог всего цикла

Единая мысль всего цикла сформирована в первом предложении введения каждой части:

«Грамотно поставленная задача действительно позволяет получить от LLM рабочий результат.»

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

Нейросеть не заменила архитектора или разработчика, а наоборот смогла выдать рабочее решение, в заданных основными архитектурными документами границами. Критические решения производятся в жестко заданных правилах, оставлена подсказка по основным видам корреляции, которые инженер может поправить на основе своего многолетнего опыта, ведь современный AI (неважно коммерческий огромный это проект, либо развернутая локальная система) не способен работать на основе накопленного опыта, как эксперт-инженер в своем направлении, а только предоставлять статистически наиболее вероятные ответы из огромного багажа знаний, переданного при обучении, а также из всемирной паутины (если имеет доступ) для обогащения контекста запроса человека. Она не заменила собой проектирование, инженерное мышление и ответственность за исправления, но стала инструментом ускорения работы и детализации, на основе которых и можно принять решение об устранении причины того или иного алерта.

На мой взгляд это, пожалуй, самый интересный вывод из всего проекта.

И, честно говоря, именно такой формат взаимодействия с LLM сегодня является наиболее полезным.

ссылка на оригинал статьи https://habr.com/ru/articles/1067622/