Skip to main content

Расширение Syslog + Zabbix на несколько подсетей через LLD

1. Назначение доработки

Первоначальная схема Syslog + Zabbix была настроена для одной подсети:

10.44.100.0/24

Для этой подсети в Zabbix уже вручную созданы элементы данных, триггеры и теги, которые читают alert-файлы из каталога:

/var/log/remote/_alerts/10.44.100.0/

Эта схема остаётся рабочей и не удаляется. Данная инструкция описывает развитие существующей схемы: добавление автоматического обнаружения дополнительных подсетей через Zabbix LLD — Low-Level Discovery / низкоуровневое обнаружение.

Новый LLD-механизм добавляется отдельно и не должен создавать дубли для уже настроенной вручную подсети.

2. Что уже умеет rsyslog

В текущей схеме rsyslog уже формирует alert-файлы по подсети источника. Используется структура:

/var/log/remote/_alerts/<ПОДСЕТЬ_ИСТОЧНИКА>/high.log
/var/log/remote/_alerts/<ПОДСЕТЬ_ИСТОЧНИКА>/alerts_critical.log
/var/log/remote/_alerts/<ПОДСЕТЬ_ИСТОЧНИКА>/alerts_warning.log
/var/log/remote/_alerts/<ПОДСЕТЬ_ИСТОЧНИКА>/alerts_behavior.log

Каталог подсети рассчитывается по правилу /24. Например:

10.44.100.37 -> /var/log/remote/_alerts/10.44.100.0/
10.44.200.25 -> /var/log/remote/_alerts/10.44.200.0/
10.46.100.30 -> /var/log/remote/_alerts/10.46.100.0/

То есть основная логика rsyslog уже готова к нескольким IPv4-подсетям с маской /24. Доработка требуется на стороне Zabbix, чтобы он не был жёстко привязан только к каталогу 10.44.100.0.

3. Принятая архитектура

В этой доработке используется следующая логика:

10.44.100.0/24
  остаётся на старых ручных элементах данных и триггерах;
  через LLD не создаётся, чтобы не было дублей.

10.44.200.0/24
  обнаруживается автоматически через LLD;
  получает discovered items и discovered triggers.

10.46.100.0/24
  обнаруживается автоматически через LLD;
  получает discovered items и discovered triggers.

LLD будет сканировать реальные каталоги внутри:

/var/log/remote/_alerts/

Если внутри _alerts появился каталог:

10.44.200.0

Python-скрипт сформирует JSON со значениями LLD-макросов:

{#SYSLOG_NET}      = 10.44.200.0
{#SYSLOG_NET_CIDR} = 10.44.200.0/24

Zabbix использует эти макросы в прототипах элементов данных и триггеров.

4. Почему используем Zabbix agent active

Правило обнаружения создаётся с типом:

Zabbix agent active / Zabbix агент активный

Это означает, что Zabbix Server не будет пассивно опрашивать агент по этому ключу. Zabbix Agent 2 на Syslog-сервере сам получает список active checks, локально запускает Python-скрипт по расписанию и отправляет результат на Zabbix Server.

Для LLD задаём интервал:

1h

То есть примерно один раз в час агент будет запускать Python-скрипт, получать JSON со списком сетевых каталогов и отправлять его в Zabbix Server.

5. Что будет при одинаковом JSON

Если каждый час Python-скрипт возвращает один и тот же JSON, Zabbix не создаёт дубликаты.

новая сеть появилась в JSON
  -> Zabbix создаёт новые discovered items и discovered triggers;

та же сеть снова пришла в JSON
  -> Zabbix подтверждает, что объект существует, но дубли не создаёт;

сеть перестала приходить в JSON
  -> Zabbix считает её потерянным ресурсом и применяет настройки lost resources.

6. Создание файла исключений для старой ручной подсети

Чтобы LLD не создал дубли для уже настроенной вручную подсети 10.44.100.0/24, создаём файл исключений:

sudo nano /etc/zabbix/syslog_alert_networks_lld.exclude

Содержимое файла:

# Сети, которые пока НЕ должны попадать в LLD.
# Они уже обслуживаются ручными элементами данных и ручными триггерами.

10.44.100.0

Назначаем владельца и права:

sudo chown root:zabbix /etc/zabbix/syslog_alert_networks_lld.exclude
sudo chmod 640 /etc/zabbix/syslog_alert_networks_lld.exclude

Здесь владельцем должен быть root, а не syslog, потому что этот файл не относится к записи логов. Он используется только для управления логикой обнаружения Zabbix.

Параметр Расшифровка
root:zabbix root — владелец файла; zabbix — группа, которая может читать файл.
640 6 для root = чтение + запись; 4 для zabbix = только чтение; 0 для остальных = доступа нет.

7. Создание Python-скрипта для формирования JSON LLD

Создаём файл:

sudo nano /usr/local/bin/zabbix_syslog_alert_networks_lld.py

Содержимое файла:

#!/usr/bin/env python3

# Импортируем модуль json.
# Он нужен, чтобы сформировать корректный JSON для Zabbix LLD.
import json

# Импортируем модуль os.
# Он нужен для работы с файлами, каталогами и путями.
import os

# Импортируем модуль re.
# Он нужен для проверки имени каталога через регулярное выражение.
import re

# Импортируем модуль sys.
# Он нужен для корректного завершения скрипта с кодом выхода.
import sys


# Каталог, внутри которого rsyslog создаёт сетевые alert-каталоги.
# Например:
# /var/log/remote/_alerts/10.44.100.0/
# /var/log/remote/_alerts/10.44.200.0/
ALERTS_DIR = "/var/log/remote/_alerts"


# Файл со списком сетей, которые нужно исключить из LLD.
# Например, 10.44.100.0 уже обслуживается ручными элементами данных и триггерами.
EXCLUDE_FILE = "/etc/zabbix/syslog_alert_networks_lld.exclude"


# Регулярное выражение для проверки имени каталога сети /24.
# Разрешаются имена вида:
# 10.44.100.0
# 10.44.200.0
# 10.46.100.0
#
# Не разрешаются:
# test
# 10.44.100.25
# 10.44.100.0-old
NETWORK_RE = re.compile(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.0$")


def normalize_network_name(value: str) -> str:
    """
    Приводит имя сети к единому виду.

    На вход можно передать:
    10.44.100.0

    или:
    10.44.100.0/24

    На выходе всегда будет:
    10.44.100.0
    """

    # Убираем пробелы и символы перевода строки в начале и конце строки.
    value = value.strip()

    # Если сеть записана с суффиксом /24, убираем этот суффикс.
    if value.endswith("/24"):
        value = value[:-3]

    # Возвращаем нормализованное имя сети.
    return value


def read_excluded_networks() -> set[str]:
    """
    Читает файл исключений и возвращает множество сетей,
    которые не должны попадать в LLD.
    """

    # Создаём пустое множество исключённых сетей.
    excluded = set()

    # Если файл исключений отсутствует, это не ошибка.
    # Просто считаем, что исключений нет.
    if not os.path.isfile(EXCLUDE_FILE):
        return excluded

    # Открываем файл исключений в режиме чтения.
    with open(EXCLUDE_FILE, "r", encoding="utf-8") as file:

        # Читаем файл построчно.
        for line in file:

            # Убираем пробелы и перевод строки по краям.
            line = line.strip()

            # Пустые строки пропускаем.
            if not line:
                continue

            # Строки-комментарии, начинающиеся с #, тоже пропускаем.
            if line.startswith("#"):
                continue

            # Нормализуем имя сети и добавляем его в множество исключений.
            excluded.add(normalize_network_name(line))

    # Возвращаем найденные исключения.
    return excluded


def is_valid_ipv4_24_network_name(name: str) -> bool:
    """
    Проверяет, что имя каталога похоже на IPv4-сеть /24.

    Примеры допустимых имён:
    10.44.100.0
    192.168.1.0

    Примеры недопустимых имён:
    test
    10.44.100.25
    10.44.999.0
    """

    # Сначала проверяем общий формат через регулярное выражение.
    if not NETWORK_RE.match(name):
        return False

    # Разбиваем строку IP-адреса на октеты.
    # Например, "10.44.200.0" превращается в список:
    # ["10", "44", "200", "0"]
    octets = name.split(".")

    # Проверяем каждый октет отдельно.
    for octet in octets:

        # Пробуем преобразовать октет в число.
        try:
            value = int(octet)

        # Если преобразовать не удалось, значит это не IPv4-октет.
        except ValueError:
            return False

        # Каждый IPv4-октет должен быть в диапазоне от 0 до 255.
        if value < 0 or value > 255:
            return False

    # Если все проверки прошли успешно, имя считается корректным.
    return True


def main() -> int:
    """
    Основная функция скрипта.

    Она:
    1. читает файл исключений;
    2. сканирует каталог /var/log/remote/_alerts;
    3. выбирает каталоги вида X.X.X.0;
    4. исключает сети из exclude-файла;
    5. возвращает JSON для Zabbix LLD.
    """

    # Список обнаруженных сетей.
    discovered = []

    # Читаем список сетей, которые нужно исключить из LLD.
    excluded_networks = read_excluded_networks()

    # Если каталога _alerts нет, возвращаем пустой LLD-результат.
    # Это не ошибка: возможно, alert-каталоги ещё не были созданы.
    if not os.path.isdir(ALERTS_DIR):
        print(json.dumps([], ensure_ascii=False))
        return 0

    # Получаем список объектов внутри /var/log/remote/_alerts
    # и обрабатываем их в отсортированном порядке.
    for entry in sorted(os.listdir(ALERTS_DIR)):

        # Собираем полный путь к найденному объекту.
        full_path = os.path.join(ALERTS_DIR, entry)

        # Если объект не является каталогом, пропускаем его.
        if not os.path.isdir(full_path):
            continue

        # Если имя каталога не похоже на сеть /24, пропускаем его.
        if not is_valid_ipv4_24_network_name(entry):
            continue

        # Если сеть указана в файле исключений, пропускаем её.
        if entry in excluded_networks:
            continue

        # Добавляем сеть в результат LLD.
        # Эти значения станут LLD-макросами Zabbix.
        discovered.append({
            "{#SYSLOG_NET}": entry,
            "{#SYSLOG_NET_CIDR}": f"{entry}/24",
            "{#SYSLOG_ALERT_DIR}": full_path
        })

    # Печатаем JSON в stdout.
    # Именно этот вывод получит Zabbix Agent 2.
    print(json.dumps(discovered, ensure_ascii=False))

    # Возвращаем код успешного завершения.
    return 0


# Эта проверка означает:
# запускать main() только если файл запущен как самостоятельный скрипт.
if __name__ == "__main__":
    sys.exit(main())

Назначаем права на выполнение:

sudo chmod 755 /usr/local/bin/zabbix_syslog_alert_networks_lld.py

Проверяем запуск:

/usr/local/bin/zabbix_syslog_alert_networks_lld.py

Пример результата:

[
  {
    "{#SYSLOG_NET}": "10.44.200.0",
    "{#SYSLOG_NET_CIDR}": "10.44.200.0/24",
    "{#SYSLOG_ALERT_DIR}": "/var/log/remote/_alerts/10.44.200.0"
  },
  {
    "{#SYSLOG_NET}": "10.46.100.0",
    "{#SYSLOG_NET_CIDR}": "10.46.100.0/24",
    "{#SYSLOG_ALERT_DIR}": "/var/log/remote/_alerts/10.46.100.0"
  }
]

Примечание. В современных версиях Zabbix LLD принимает JSON-массив в корне документа. Старый вариант с объектом {"data": [...]} также поддерживается для совместимости, но в этой инструкции используется массив в корне.

8. Проверка скрипта от имени пользователя zabbix

Так как скрипт будет запускаться через Zabbix Agent 2, важно проверить его именно от пользователя zabbix:

sudo -u zabbix /usr/local/bin/zabbix_syslog_alert_networks_lld.py

Проверяем доступ к файлу исключений:

sudo -u zabbix cat /etc/zabbix/syslog_alert_networks_lld.exclude

Проверяем доступ к каталогу _alerts:

sudo -u zabbix ls -la /var/log/remote/_alerts/

Если есть ошибка доступа к логам, проверяем членство пользователя zabbix в группе adm:

id zabbix

Если пользователя zabbix нет в группе adm, добавляем:

sudo usermod -aG adm zabbix
sudo systemctl restart zabbix-agent2

9. Подробная расшифровка Python-скрипта

9.1. Шебанг

#!/usr/bin/env python3

Это шебанг. Шебанг — специальная первая строка скрипта в Linux/Unix. Она начинается с символов #! и говорит операционной системе, каким интерпретатором запускать файл.

В нашем случае /usr/bin/env python3 означает: найти python3 в системном окружении и выполнить этот файл через Python 3.

9.2. Импорты

import json

Подключает стандартный модуль json. Он нужен для формирования JSON-ответа, который будет передан в Zabbix.

import os

Подключает модуль os. Он нужен для работы с файловой системой: os.path.isdir(), os.path.isfile(), os.listdir(), os.path.join().

import re

Подключает модуль регулярных выражений. Он нужен для проверки, что имя каталога похоже на сеть /24.

import sys

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

9.3. Константы

ALERTS_DIR = "/var/log/remote/_alerts"

Путь к каталогу, который будет сканировать скрипт.

EXCLUDE_FILE = "/etc/zabbix/syslog_alert_networks_lld.exclude"

Путь к файлу исключений.

NETWORK_RE = re.compile(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.0$")

Регулярное выражение, которое пропускает имена вида 10.44.200.0 и не пропускает 10.44.200.15, test, 10.44.200.0-old.

9.4. Функция normalize_network_name

def normalize_network_name(value: str) -> str:

Создаётся функция с именем normalize_network_name. Она принимает строку и возвращает строку.

value = value.strip()

Метод .strip() убирает пробелы, табуляции и переводы строк по краям строки.

if value.endswith("/24"):

Проверяет, заканчивается ли строка на /24.

value = value[:-3]

Отрезает последние три символа. Например, 10.44.100.0/24 становится 10.44.100.0.

return value

Возвращает нормализованное имя сети.

9.5. Функция read_excluded_networks

def read_excluded_networks() -> set[str]:

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

excluded = set()

Создаётся пустое множество.

if not os.path.isfile(EXCLUDE_FILE):
    return excluded

Если файла исключений нет, возвращается пустое множество. Скрипт не падает из-за отсутствия файла исключений.

with open(EXCLUDE_FILE, "r", encoding="utf-8") as file:

Открывается файл в режиме чтения. Конструкция with удобна тем, что Python сам закроет файл после выхода из блока.

for line in file:

Чтение файла построчно.

line = line.strip()

Убираются пробелы и перевод строки по краям.

if not line:
    continue

Если строка пустая, она пропускается.

if line.startswith("#"):
    continue

Если строка начинается с #, она считается комментарием и пропускается.

excluded.add(normalize_network_name(line))

Нормализованное имя сети добавляется в множество исключений.

return excluded

Возвращается множество исключённых сетей.

9.6. Функция is_valid_ipv4_24_network_name

def is_valid_ipv4_24_network_name(name: str) -> bool:

Функция принимает строку и возвращает True или False.

if not NETWORK_RE.match(name):
    return False

Если имя каталога не соответствует шаблону X.X.X.0, функция возвращает False.

octets = name.split(".")

Строка разбивается по точкам. Например, 10.44.200.0 превращается в список ["10", "44", "200", "0"].

for octet in octets:

Начинается цикл по каждому октету.

try:
    value = int(octet)
except ValueError:
    return False

Скрипт пробует преобразовать октет в число. Если это невозможно, возвращается False.

if value < 0 or value > 255:
    return False

Каждый IPv4-октет должен быть в диапазоне от 0 до 255.

return True

Если все проверки прошли успешно, функция возвращает True.

9.7. Функция main

def main() -> int:

Главная функция программы.

discovered = []

Создаётся пустой список обнаруженных сетей.

excluded_networks = read_excluded_networks()

Читается файл исключений.

if not os.path.isdir(ALERTS_DIR):

Проверяется, существует ли каталог /var/log/remote/_alerts.

print(json.dumps([], ensure_ascii=False))
return 0

Если каталога нет, скрипт возвращает пустой JSON-массив [] и завершается успешно.

for entry in sorted(os.listdir(ALERTS_DIR)):

Получается список объектов внутри _alerts, затем он сортируется.

full_path = os.path.join(ALERTS_DIR, entry)

Собирается полный путь к найденному объекту.

if not os.path.isdir(full_path):
    continue

Если объект не каталог, он пропускается.

if not is_valid_ipv4_24_network_name(entry):
    continue

Если имя каталога не похоже на сеть /24, оно пропускается.

if entry in excluded_networks:
    continue

Если сеть указана в файле исключений, она пропускается.

discovered.append({
    "{#SYSLOG_NET}": entry,
    "{#SYSLOG_NET_CIDR}": f"{entry}/24",
    "{#SYSLOG_ALERT_DIR}": full_path
})

Найденная сеть добавляется в результат LLD. На каждую сеть формируются три макроса: {#SYSLOG_NET}, {#SYSLOG_NET_CIDR}, {#SYSLOG_ALERT_DIR}.

print(json.dumps(discovered, ensure_ascii=False))

Формируется JSON и печатается в stdout.

return 0

Возвращается код успешного завершения.

if __name__ == "__main__":
    sys.exit(main())

Если файл запущен как самостоятельный скрипт, выполняется функция main().

10. Добавление UserParameter для Zabbix Agent 2

Создаём конфигурационный файл:

sudo nano /etc/zabbix/zabbix_agent2.d/syslog_alert_networks.conf

Содержимое:

UserParameter=syslog.alert.networks.discovery,/usr/local/bin/zabbix_syslog_alert_networks_lld.py

Расшифровка:

UserParameter=<ключ>,<команда>

В нашем случае syslog.alert.networks.discovery — это ключ, который будет использоваться в Zabbix Discovery rule. Команда /usr/local/bin/zabbix_syslog_alert_networks_lld.py будет выполнена Zabbix Agent 2.

Перезапускаем агент:

sudo systemctl restart zabbix-agent2
sudo systemctl status zabbix-agent2 --no-pager

Проверка через zabbix_get, если он установлен и passive-проверки с localhost разрешены:

zabbix_get -s 127.0.0.1 -k syslog.alert.networks.discovery

11. Служебный скрипт подготовки alert-файлов

Этот скрипт не является обязательной частью LLD, но он полезен.

Проблема может быть такой: от новой сети пришло только событие MACFLAP, и rsyslog создал файл:

/var/log/remote/_alerts/10.44.200.0/alerts_behavior.log

Но файлов:

alerts_critical.log
alerts_warning.log
high.log

может ещё не быть. Если Zabbix уже создаст элементы данных по всем типам alert-файлов, часть элементов может временно стать Unsupported / Не поддерживается.

Чтобы этого избежать, создаём служебный скрипт, который будет создавать стандартные пустые alert-файлы во всех сетевых каталогах.

Создаём файл:

sudo nano /usr/local/sbin/prepare_syslog_alert_files.sh

Содержимое:

#!/usr/bin/env bash

# Каталог, где лежат сетевые alert-папки.
# Например:
# /var/log/remote/_alerts/10.44.100.0/
# /var/log/remote/_alerts/10.44.200.0/
ALERTS_DIR="/var/log/remote/_alerts"

# Если каталога _alerts нет, спокойно завершаем работу.
# Это не ошибка: возможно, alert-каталоги ещё не созданы.
if [ ! -d "$ALERTS_DIR" ]; then
    exit 0
fi

# Ищем только каталоги первого уровня внутри _alerts.
# -mindepth 1 — не брать сам каталог _alerts;
# -maxdepth 1 — не уходить глубже сетевых каталогов;
# -type d     — брать только каталоги.
find "$ALERTS_DIR" -mindepth 1 -maxdepth 1 -type d \
| while read -r dir; do

    # Получаем имя каталога без полного пути.
    # Например:
    # /var/log/remote/_alerts/10.44.200.0
    # превратится в:
    # 10.44.200.0
    net_name="$(basename "$dir")"

    # Проверяем, что имя каталога похоже на сеть /24.
    # Всё, что не похоже на X.X.X.0, пропускаем.
    if [[ ! "$net_name" =~ ^([0-9]{1,3}\.){3}0$ ]]; then
        continue
    fi

    # Создаём стандартные alert-файлы, если их ещё нет.
    # Если файл уже существует, touch не удаляет и не портит его содержимое.
    touch "$dir/high.log"
    touch "$dir/alerts_critical.log"
    touch "$dir/alerts_warning.log"
    touch "$dir/alerts_behavior.log"

    # Назначаем владельца и группу.
    # syslog — владелец, потому что rsyslog пишет в эти файлы.
    # adm    — группа для чтения логов, в неё должен входить пользователь zabbix.
    chown syslog:adm "$dir/high.log" \
                   "$dir/alerts_critical.log" \
                   "$dir/alerts_warning.log" \
                   "$dir/alerts_behavior.log"

    # Назначаем права:
    # 6 для владельца syslog = чтение + запись;
    # 4 для группы adm       = чтение;
    # 0 для остальных        = нет доступа.
    chmod 640 "$dir/high.log" \
              "$dir/alerts_critical.log" \
              "$dir/alerts_warning.log" \
              "$dir/alerts_behavior.log"
done

Назначаем права:

sudo chmod 755 /usr/local/sbin/prepare_syslog_alert_files.sh

Проверяем:

sudo /usr/local/sbin/prepare_syslog_alert_files.sh

12. Расшифровка bash-скрипта

#!/usr/bin/env bash

Шебанг для Bash-скрипта. Он говорит системе запускать файл через Bash.

ALERTS_DIR="/var/log/remote/_alerts"

Создаётся переменная ALERTS_DIR, в которой хранится путь к каталогу с сетевыми alert-папками.

if [ ! -d "$ALERTS_DIR" ]; then

Проверяется, существует ли каталог _alerts. Параметр -d проверяет, существует ли каталог, а ! означает отрицание.

exit 0

Скрипт завершается успешно. Код 0 означает успешное завершение.

find "$ALERTS_DIR" -mindepth 1 -maxdepth 1 -type d \

Команда find ищет только каталоги первого уровня внутри _alerts. Параметр -mindepth 1 не берёт сам каталог _alerts, -maxdepth 1 не уходит глубже первого уровня, -type d берёт только каталоги.

| while read -r dir; do

Символ | — это pipe / конвейер. Он передаёт результат команды find в цикл while. Команда read -r dir читает очередную строку и кладёт её в переменную dir.

net_name="$(basename "$dir")"

Команда basename берёт только имя последнего элемента пути. Например, /var/log/remote/_alerts/10.44.200.0 становится 10.44.200.0.

if [[ ! "$net_name" =~ ^([0-9]{1,3}\.){3}0$ ]]; then

Проверяется, похоже ли имя каталога на сеть /24. Оператор =~ сравнивает строку с регулярным выражением.

continue

Если имя каталога не подходит, цикл переходит к следующему каталогу.

touch "$dir/high.log"

Создаёт файл high.log, если его нет. Если файл уже существует, содержимое не удаляется.

chown syslog:adm ...

Меняет владельца и группу файла: syslog — владелец, adm — группа.

chmod 640 ...

Назначает права: 6 — владелец читает и пишет; 4 — группа читает; 0 — остальные не имеют доступа.

13. Добавление служебного скрипта в cron

Создаём cron-файл:

sudo nano /etc/cron.d/prepare-syslog-alert-files

Содержимое:

*/5 * * * * root /usr/local/sbin/prepare_syslog_alert_files.sh >/dev/null 2>&1
Фрагмент Расшифровка
*/5 * * * * Запускать каждые 5 минут.
root Запускать от имени root.
/usr/local/sbin/prepare_syslog_alert_files.sh Какой скрипт запускать.
>/dev/null 2>&1 Не выводить результат в консоль и не засорять почту cron.

14. Создание Discovery rule в Zabbix

Переходим в шаблон:

Data collection / Сбор данных
-> Templates / Шаблоны
-> Template App Syslog Alerts
-> Discovery rules / Правила обнаружения
-> Create discovery rule / Создать правило обнаружения

Параметры:

Name / Имя:
Syslog alert networks discovery

Type / Тип:
Zabbix agent active / Zabbix агент активный

Key / Ключ:
syslog.alert.networks.discovery

Type of information / Тип информации:
Text / Текст

Update interval / Интервал обновления:
1h

Важно. Для lost resources на первом этапе лучше не ставить мгновенное удаление. Рекомендуемый безопасный вариант: Delete lost resources / Удалять потерянные ресурсы: 30d.

15. Что именно будет делать Discovery rule

Каждый час Zabbix Agent 2 будет локально запускать Python-скрипт:

/usr/local/bin/zabbix_syslog_alert_networks_lld.py

Скрипт будет проверять каталог:

/var/log/remote/_alerts/

Если там есть каталоги:

10.44.100.0
10.44.200.0
10.46.100.0

и в exclude-файле указано:

10.44.100.0

то в Zabbix будет отправлен JSON только по новым сетям:

[
  {
    "{#SYSLOG_NET}": "10.44.200.0",
    "{#SYSLOG_NET_CIDR}": "10.44.200.0/24",
    "{#SYSLOG_ALERT_DIR}": "/var/log/remote/_alerts/10.44.200.0"
  },
  {
    "{#SYSLOG_NET}": "10.46.100.0",
    "{#SYSLOG_NET_CIDR}": "10.46.100.0/24",
    "{#SYSLOG_ALERT_DIR}": "/var/log/remote/_alerts/10.46.100.0"
  }
]

После получения JSON Zabbix создаёт реальные элементы данных и триггеры на основании item prototypes и trigger prototypes.

16. Прототипы элементов данных

16.1. Общий принцип

Старый ручной элемент данных для одной сети был привязан к пути:

/var/log/remote/_alerts/10.44.100.0/^alerts_critical\.log$

В прототипе нужно использовать LLD-макрос:

/var/log/remote/_alerts/{#SYSLOG_NET}/^alerts_critical\.log$

То есть 10.44.100.0 заменяется на {#SYSLOG_NET}, а в имени и тегах можно использовать {#SYSLOG_NET_CIDR}.

16.2. Пример item prototype: ERR_DISABLE count

Name / Имя:
Syslog PM ERR_DISABLE count: {#SYSLOG_NET_CIDR}

Type / Тип:
Zabbix agent active / Zabbix агент активный

Key / Ключ:
logrt.count["/var/log/remote/_alerts/{#SYSLOG_NET}/^alerts_critical\.log$","PM-4-ERR_DISABLE","UTF-8",{$SYSLOG_MAXLINES},skip]

Type of information / Тип информации:
Numeric (unsigned) / Числовой без знака

Теги:

component = syslog
subnet = {#SYSLOG_NET_CIDR}
file = alerts_critical.log
event_group = port_state
event = err_disable
severity_logic = immediate
item_role = counter
source = lld

16.3. Пример item prototype: ERR_DISABLE last line

Name / Имя:
Syslog PM ERR_DISABLE last line: {#SYSLOG_NET_CIDR}

Type / Тип:
Zabbix agent active / Zabbix агент активный

Key / Ключ:
logrt["/var/log/remote/_alerts/{#SYSLOG_NET}/^alerts_critical\.log$","PM-4-ERR_DISABLE","UTF-8",{$SYSLOG_MAXLINES},skip]

Type of information / Тип информации:
Log / Журнал

Теги:

component = syslog
subnet = {#SYSLOG_NET_CIDR}
file = alerts_critical.log
event_group = port_state
event = err_disable
severity_logic = immediate
item_role = last_line
source = lld

16.4. Принцип создания остальных item prototypes

Все остальные элементы данных создаются по той же логике.

Было:
/var/log/remote/_alerts/10.44.100.0/

Стало:
/var/log/remote/_alerts/{#SYSLOG_NET}/

Было в имени:
10.44.100.0/24

Стало:
{#SYSLOG_NET_CIDR}

Было в теге:
subnet = 10.44.100.0/24

Стало:
subnet = {#SYSLOG_NET_CIDR}

Также добавляем тег:
source = lld

17. Прототипы триггеров

17.1. Общий принцип

Старый ручной триггер для одной сети был привязан к конкретному пути:

/var/log/remote/_alerts/10.44.100.0/

В trigger prototype используется:

/var/log/remote/_alerts/{#SYSLOG_NET}/

В имени триггера используется {#SYSLOG_NET_CIDR}.

17.2. Пример trigger prototype: ERR_DISABLE

Name / Имя:
Syslog critical: port err-disabled: {#SYSLOG_NET_CIDR}

Severity / Важность:
High / Высокая

Expression / Выражение:

sum(/Template App Syslog Alerts/logrt.count["/var/log/remote/_alerts/{#SYSLOG_NET}/^alerts_critical\.log$","PM-4-ERR_DISABLE","UTF-8",{$SYSLOG_MAXLINES},skip],{$SYSLOG_IMMEDIATE_WIN})>={$SYSLOG_CRIT_COUNT}
and
length(last(/Template App Syslog Alerts/logrt["/var/log/remote/_alerts/{#SYSLOG_NET}/^alerts_critical\.log$","PM-4-ERR_DISABLE","UTF-8",{$SYSLOG_MAXLINES},skip]))>0

Настройки:

OK event generation / Генерация события восстановления:
None / Не создавать

Allow manual close / Разрешить ручное закрытие:
Yes / Да

Problem event generation mode / Режим генерации проблем:
Single / Одиночный

Теги:

component = syslog
subnet = {#SYSLOG_NET_CIDR}
file = alerts_critical.log
event_group = port_state
event = err_disable
severity_logic = immediate
reaction = incident
source = lld

17.3. Operational data / Операционные данные

Можно использовать такой вариант:

Счётчик: {ITEM.LASTVALUE1} | Syslog: {ITEM.LASTVALUE2}

Для триггеров, где используется несколько элементов данных, важно проверить порядок {ITEM.LASTVALUE1}, {ITEM.LASTVALUE2}, {ITEM.LASTVALUE3} в конкретном выражении.

18. Теги для ручной и LLD-схемы

Чтобы в проблемах было понятно, откуда пришёл триггер, рекомендуется добавить тег source.

Для старых ручных элементов и триггеров:

source = manual

Для новых LLD-прототипов:

source = lld

Для старой сети:

subnet = 10.44.100.0/24
source = manual

Для новых сетей:

subnet = {#SYSLOG_NET_CIDR}
source = lld

19. Проверка работы

19.1. Проверка каталогов

sudo ls -la /var/log/remote/_alerts/

Пример:

10.44.100.0
10.44.200.0
10.46.100.0

19.2. Проверка exclude-файла

cat /etc/zabbix/syslog_alert_networks_lld.exclude

Должно быть:

10.44.100.0

19.3. Проверка Python-скрипта

/usr/local/bin/zabbix_syslog_alert_networks_lld.py

Проверка от пользователя zabbix:

sudo -u zabbix /usr/local/bin/zabbix_syslog_alert_networks_lld.py

Если всё правильно, в JSON не должно быть сети:

10.44.100.0

19.4. Проверка UserParameter

zabbix_get -s 127.0.0.1 -k syslog.alert.networks.discovery

Если zabbix_get не работает из-за настроек passive-доступа, достаточно проверки запуска скрипта от пользователя zabbix.

19.5. Проверка в Zabbix

Data collection / Сбор данных
-> Hosts / Узлы сети
-> <Syslog host>
-> Latest data / Последние данные

Ищем элементы, созданные через LLD.

Также проверяем:

Data collection / Сбор данных
-> Templates / Шаблоны
-> Template App Syslog Alerts
-> Discovery rules / Правила обнаружения
-> Syslog alert networks discovery

После выполнения discovery должны появиться discovered items и discovered triggers для новых сетей.

20. Важные ограничения

Данная схема рассчитана на IPv4 и подсети /24.

Она корректно обрабатывает каталоги вида:

10.44.200.0
10.46.100.0
192.168.1.0

Но не предназначена для автоматического расчёта других масок, например:

/23
/22
/25

Если в будущем потребуется поддержка других масок, нужно будет менять логику формирования сетевого каталога в rsyslog и логику проверки имён в Python-скрипте.

21. Итоговая логика после внедрения

1. Cisco отправляет syslog на Syslog-сервер.

2. rsyslog принимает сообщения и пишет alert-события в:
   /var/log/remote/_alerts/<сеть_24>/

3. Старая сеть 10.44.100.0/24 продолжает обслуживаться ручными элементами данных и триггерами.

4. Zabbix Agent 2 один раз в час запускает локальный Python-скрипт.

5. Python-скрипт сканирует /var/log/remote/_alerts/.

6. Python-скрипт исключает 10.44.100.0 из LLD.

7. Python-скрипт формирует JSON со списком новых сетей.

8. Zabbix Agent 2 отправляет JSON на Zabbix Server.

9. Zabbix Server создаёт discovered items и discovered triggers для новых сетей.

10. Если JSON не изменился, дубли не создаются.

Итог: текущая рабочая схема для 10.44.100.0/24 сохраняется, а новые подсети подключаются через LLD без ручного копирования большого количества элементов данных и триггеров.