В этой статье мы ознакомим вас с основными возможностями библиотеки zabbix_utils и примерами использования с компонентами Zabbix.

Zabbix – достаточно гибкая и универсальная система мониторинга, поддерживающая из коробки большое многообразие интеграций с различными системами. Несмотря на то, что мы активно работаем над расширением списка поддерживаемых систем для интеграции с Zabbix из коробки, иногда может возникнуть потребность интеграции пока еще неподдерживаемых, либо самописных систем и сервисов. В таком случае очень полезной будет библиотека, которая берет на себя реализацию протоколов взаимодействия с Zabbix API или работы с Zabbix server/proxy для отправки данных в Trapper и с Zabbix agent/agent2 для получения данных с узла сети. Т.к. среди DevOps и SRE инженеров, а также администраторов серверов наибольшее распространение получил Python, мы в первую очередь решили выпустить библиотеку для этого языка программирования.

Мы рады вам представить zabbix_utils – библиотеку Python для комфортной работы с Zabbix API, Zabbix server/proxy и Zabbix agent/agent2. Конечно, существуют комьюнити решения для работы в Python с упомянутыми выше компонентами Zabbix. Мы постарались учесть этот факт, собрать воедино популярные проблемы и задачи, а также наш опыт, чтобы разработать максимально удобный инструмент. Более того, мы позаботились чтобы “переезд” на него был  максимально простым и прозрачным. А благодаря официальной поддержке, Вы можете быть спокойны за то что актуальная версия библиотеки поддерживает последнюю версию Zabbix.

Сценарии использования

Библиотека zabbix_utils может быть использована в следующих сценариях, но не ограничивается ими:

  • Автоматизация Zabbix;
  • Интеграция со сторонними системами;
  • Кастомные решения мониторинга;
  • Экспорт данных (узлы, шаблоны, проблемы и т.д.);
  • Внедрение в Ваше Python приложение поддержки мониторинга Zabbix;
  • Все что  может еще прийти Вам в голову…

Вы можете использовать zabbix_utils для автоматизации работы с Zabbix, например, путем написания скриптов автоматической постановки на мониторинг объектов Вашей ИТ-инфраструктуры. Для этого может использоваться как ZabbixAPI для непосредственного управления с объектами Zabbix, так и Sender для отправки каких-либо значений на узлы, а Getter – для получения данных от агентов.

Например, у Вас есть инфраструктура, состоящая из разных филиалов, где каждый сервер или рабочая станция разворачиваются из образа с автоматически настроенным Zabbix агентом и каждый филиал имеет свою изолированную сеть из-за чего мониторинг каждого такого филиала выполняется через Zabbix proxy. Ваш самописный сервис (скрипт) может получать список этого оборудования из Вашей CMDB системы, вместе с какой-либо доп. информацией. Затем он может использовать эти данные для создания узлов и применения необходимых для них шаблонов с помощью ZabbixAPI на основании полученной информации. Если информации из CMDB недостаточно, Вы можете запросить данные напрямую от настроенного Zabbix агента с помощью Getter и использовать эту информацию для дальнейшей настройки и принятия решений во время настройки. Другая часть Вашего скрипта может обращаться к AD для получения списка пользователей филиала для актуализации списка пользователей в Zabbix через API и назначения им соответствующих прав, в зависимости от доп. информации из AD или CMDB (например, права на редактирование для владельцев серверов).

Другим применением библиотеки может быть подход, когда Вы, например, регулярно экспортируете шаблоны из Zabbix для последующей загрузки их в систему контроля версий. Вы также можете наладить механизм загрузки изменений и отката к предыдущим версиям шаблонов. Здесь все может зависеть лишь от Вашей фантазии.

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

Установка и настройка

Для начала библиотеку zabbix_utils необходимо установить. Сделать это можно двумя основными способами:

  • С помощью pip:
~$ pip install zabbix_utils
  • С помощью клонирования с Github:
~$ git clone https://github.com/zabbix/python-zabbix-utils
~$ cd python-zabbix-utils/
~$ python setup.py install

Какая-то дополнительная настройка не требуется. Единственное, для работы с API можно указать значения для следующих переменных окружения: ZABBIX_URL, ZABBIX_TOKEN, ZABBIX_USER, ZABBIX_PASSWORD.

Работа с Zabbix API

Для работы с Zabbix API необходимо импортировать класс ZabbixAPI библиотеки zabbix_utils:

from zabbix_utils import ZabbixAPI

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

Затем необходимо создать экземпляр класса ZabbixAPI и здесь есть несколько сценариев использования:

  • Использовать заданные ранее значения переменных окружения, т.е. не передавать на вход ZabbixAPI никаких параметров:
~$ export ZABBIX_URL="https://zabbix.example.local"
~$ export ZABBIX_USER="Admin"
~$ export ZABBIX_PASSWORD="zabbix"
from zabbix_utils import ZabbixAPI

api = ZabbixAPI()
  • Передать на вход только адрес Zabbix API, причем он может быть указан и как просто IP/FQDN адрес сервера или DNS имя (в таком случае будет использован протокол HTTP) и как URL к Zabbix API. При этом данные для аутентификации должны быть по-прежнему указаны в качестве значений для переменных окружения:
~$ export ZABBIX_USER="Admin"
~$ export ZABBIX_PASSWORD="zabbix"
from zabbix_utils import ZabbixAPI

api = ZabbixAPI(url="127.0.0.1") 
  • На вход ZabbixAPI, также как и в примере выше, передать только адрес Zabbix API, а данные для аутентификации передать позже с помощью вызова метода login():
from zabbix_utils import ZabbixAPI

api = ZabbixAPI(url="127.0.0.1")
api.login(user="Admin", password="zabbix")
  • Передать все параметры сразу при создании экземпляра ZabbixAPI, в таком случае последующий вызов login() выполнять не нужно:
from zabbix_utils import ZabbixAPI

api = ZabbixAPI(
    url="127.0.0.1",
    user="Admin",
    password="zabbix"
)

Класс ZabbixAPI поддерживает работу с различными версиями Zabbix, автоматически проверяя версию API при его инициализации. Вы также можете работать с версией Zabbix API и получить ее можно следующим образом:

from zabbix_utils import ZabbixAPI

api = ZabbixAPI()

# Поле версии ZabbixAPI
ver = api.version
print(type(ver).__name__, ver) # APIVersion 6.0.24

# Метод получения версии ZabbixAPI
ver = api.api_version()
print(type(ver).__name__, ver) # APIVersion 6.0.24

# Дополнительные методы работы
print(ver.major)    # 6.0
print(ver.minor)    # 24
print(ver.is_lts()) # True

В результате Вы получите объект APIVersion, который имеет поля major и minor возвращающие соответственно минорную и мажорную части текущей версии, а также метод is_lts(), возвращающий true если текущая версия LTS, и false в противном случае. Объект APIVersion также можно сравнивать с версией, записанной в виде строки, либо числа с плавающей точкой:

# Сравнение версий
print(ver < 6.4)      # True
print(ver != 6.0)     # False
print(ver != "6.0.5") # True

Если учетная запись и пароль (или начиная с Zabbix 5.4 – токен, вместо логина/пароля) не заданы в качестве значений переменных окружения или при инициализации ZabbixAPI, то необходимо вызвать метод login() для выполнения аутентификации:

from zabbix_utils import ZabbixAPI

api = ZabbixAPI(url="127.0.0.1")
api.login(token="xxxxxxxx")

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

Формат вызова методов API выглядит следующим образом:

экземпляр_класса.объект_zabbix.метод(параметры)

Например:

api.host.get()

После выполнения всех необходимых запросов к API необходимо выполнить logout(), если аутентификация была выполнена при помощи логина и пароля:

api.logout()

Больше примеров использования можно найти здесь.

Отправка значений в Zabbix server/proxy.

Часто может возникнуть необходимость отправки значений в Zabbix Trapper. Для этого нами предоставляется утилита zabbix_sender. Но если Ваш сервис или скрипт, отправляющий эти данные, написан на Python, вызывать внешнюю утилиту может быть не очень удобно. Поэтому мы разработали класс Sender, который поможет Вам отправлять на Zabbix server или proxy значения по одному или группой.

Для работы с Sender, его необходимо для начала импортировать следующим образом:

from zabbix_utils import Sender

После чего можно отправлять значения по одному:

from zabbix_utils import Sender

sender = Sender(server='127.0.0.1', port=10051)
resp = sender.send_value('example_host', 'example.key', 50, 1702511920)

либо собрать их в группу для одновременной отправки, для чего необходимо дополнительно импортировать ItemValue:

from zabbix_utils import ItemValue, Sender

items = [
    ItemValue('host1', 'item.key1', 10),
    ItemValue('host1', 'item.key2', 'Тестовое значение'),
    ItemValue('host2', 'item.key1', -1, 1702511920),
    ItemValue('host3', 'item.key1', '{"msg":"Тестовое сообщение"}'),
    ItemValue('host2', 'item.key1', 0, 1702511920, 100)
]

sender = Sender('127.0.0.1', 10051)
response = sender.send(items)

Для случаев, когда есть необходимость отправлять значений больше, чем Zabbix Trapper может принять за один раз, есть возможность для фрагментированной отправки, т.е. последовательной отправки отдельными  фрагментами (чанками). По-умолчанию, размер чанка установлен в 250 значений. Другими словами при групповой отправке значений, 400 значений переданных методу send() на отправку, будут отправлены в два этапа. сначала будут отправлены 250 значений, а после получения ответа – оставшиеся 150 значений. Размер чанка может быть изменен – для этого необходимо указать свое значение параметра chunk_size при инициализации Sender:

from zabbix_utils import ItemValue, Sender

items = [
    ItemValue('host1', 'item.key1', 10),
    ItemValue('host1', 'item.key2', 'Тестовое значение'),
    ItemValue('host2', 'item.key1', -1, 1702511920),
    ItemValue('host3', 'item.key1', '{"msg":"Тестовое сообщение"}'),
    ItemValue('host2', 'item.key1', 0, 1702511920, 100)
]

sender = Sender(server='127.0.0.1', port=10051, chunk_size=2)
response = sender.send(items)

В описанном выше примере размер чанка установлен равным 2. В этом случае 5 переданных значений будут отправлены тремя запросами по 2, 2 и 1 значение соответственно.

Если на Вашем сервере несколько сетевых интерфейсов, а значения необходимо отправлять с какого-то определенного, то Sender предоставляет возможность указать source_ip для отправляемых значений:

from zabbix_utils import Sender

sender = Sender(
    server='zabbix.example.local',
    port=10051,
    source_ip='10.10.7.1'
)
resp = sender.send_value('example_host', 'example.key', 50, 1702511920)

Также поддерживается чтение параметров подключения к серверу из файла конфигурации Zabbix agent/agent2. Для этого необходимо установить соответствующий флаг, после чего передавать параметры подключения при создании экземпляра Sender нет необходимости:

from zabbix_utils import Sender

sender = Sender(
    use_config=True,
    config_path='/etc/zabbix/zabbix_agent2.conf'
)
response = sender.send_value('example_host', 'example.key', 50, 1702511920)

Так как в файле конфигурации Zabbix agent/agent2 может быть задан один или даже несколько кластеров Zabbix, состоящих из нескольких экземпляров Zabbix server, Sender отправит данные на первый доступный сервер каждого кластера, указанного в параметре ServerActive в файле конфигурации. В случае если параметр ServerActive не указан в файле конфигурации Zabbix agent/agent2, будет взят адрес сервера из параметра Server со стандартным портом Zabbix Trapper’а – 10051.

По-умолчанию Sender вернет в качестве ответа агрегированный результат отправки по всем кластерам. Но есть возможность получить более подробную информацию по результатам отправки по каждому чанку и для каждого кластера:

print(response)
# {"processed": 2, "failed": 0, "total": 2, "time": "0.000108", "chunk": 2}

if response.failed == 0:
    print(f"Value sent successfully in {response.time}")
else:
    print(response.details)
    # {
    #     127.0.0.1:10051: [
    #         {
    #             "processed": 1,
    #             "failed": 0,
    #             "total": 1,
    #             "time": "0.000051",
    #             "chunk": 1
    #         }
    #     ],
    #     zabbix.example.local:10051: [
    #         {
    #             "processed": 1,
    #             "failed": 0,
    #             "total": 1,
    #             "time": "0.000057",
    #             "chunk": 1
    #         }
    #     ]
    # }
    for node, chunks in response.details.items():
        for resp in chunks:
            print(f"processed {resp.processed} of {resp.total} at {node.address}:{node.port}")
            # processed 1 of 1 at 127.0.0.1:10051
            # processed 1 of 1 at zabbix.example.local:10051

Больше примеров использования можно найти здесь.

Получение значений с Zabbix agent/agent2 по ключу элемента данных.

Иногда также может быть полезной возможность получать значения напрямую от Zabbix агента. Для помощи в такой задаче zabbix_utils предоставляет класс Getter. Он выполняет ту же функцию что и утилита <zabbix_get, но позволяя работать нативно внутри кода Python.
Getter максимально прост в использовании, достаточно его импортировать, создать экземпляр, передав ему адрес и порт Zabbix агента, и затем вызвать метод get(), передав ему ключ элемента данных, значение для которого необходимо получить:

from zabbix_utils import Getter

agent = Getter('10.8.54.32', 10050)
resp = agent.get('system.uname')

В том случае, когда на Вашем сервере несколько сетевых интерфейсов, а значения необходимо отправлять с какого-то определенного, Вы можете указать source_ip для подключения к агенту:

from zabbix_utils import Getter

agent = Getter(
    host='zabbix.example.local',
    port=10050,
    source_ip='10.10.7.1'
)
resp = agent.get('system.uname')

Ответ Zabbix агента будет обработан библиотекой и возвращен в виде объекта класса AgentResponse:

print(resp)
# {
#     "error": null,
#     "raw": "Linux zabbix_server 5.15.0-3.60.5.1.el9uek.x86_64",
#     "value": "Linux zabbix_server 5.15.0-3.60.5.1.el9uek.x86_64"
# }

print(resp.error)
# None

print(resp.value)
# Linux zabbix_server 5.15.0-3.60.5.1.el9uek.x86_64

Больше примеров использования можно найти здесь.

Заключение

Библиотека zabbix_utils для Python позволит Вам воспользоваться всеми преимуществами мониторинга с использованием Zabbix, не ограничивая себя только доступными на данный момент интеграциями из коробки. Она может стать полезной как для DevOps и SRE инженеров, так и для Python разработчиков, желающих реализовать поддержку мониторинга своей системы c помощью Zabbix.

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

Вопросы

В: Какие версии агентов поддерживаются для Getter?

О: Поддерживаемые версии  Zabbix агентов точно такие же, как и версии Zabbix API, они указаны в readme файле. Наша цель – создание библиотеки с полноценной поддержкой всех компонентов Zabbix с одинаковой версией.

В: Для Getter поддерживается ли шифрование агентов?

О: Поддержка шифрования пока что не встроена нами в Sender и Getter, но есть возможность написать собственную обертку с использованием сторонних библиотек для них обоих.

from zabbix_utils import Sender

def psk_wrapper(sock, tls):
    # ...
    # реализация обертки TLS PSK для сокета
    # ...

sender = Sender(
    server='zabbix.example.local',
    port=10051,
    socket_wrapper=psk_wrapper
)

Больше примеров Вы можете найти здесь.

В: Есть ли возможность задать значение таймаута для Getter?

О: Значение таймаута ожидания ответа можно задать как для Getter, так и для ZabbixAPI и Sender. Во всех случаях задается таймаут ожидания любых ответов на запросы.

# Пример задания таймаута для Sender
sender = Sender(server='127.0.0.1', port=10051, timeout=30)

# Пример задания таймаута для Getter
agent = Getter(host='127.0.0.1', port=10050, timeout=30)

В: Поддерживается параллельный (асинхронный) режим работы?

О: На данный момент библиотека не включает в себя асинхронных классов и методов, но у нас есть в планах разработка асинхронных версий ZabbixAPI и Sender.

В: Есть ли возможность указать несколько серверов при отправке через Sender без указания файла конфигурации (для работы с HA кластером)?

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

from zabbix_utils import Sender


zabbix_clusters = [
    [
        'zabbix.cluster1.node1',
        'zabbix.cluster1.node2:10051'
    ],
    [
        'zabbix.cluster2.node1:10051',
        'zabbix.cluster2.node2:20051',
        'zabbix.cluster2.node3'
    ]
]

sender = Sender(clusters=zabbix_clusters)
response = sender.send_value('example_host', 'example.key', 10, 1702511922)

print(response)
# {"processed": 2, "failed": 0, "total": 2, "time": "0.000103", "chunk": 2}

print(response.details)
# {
#     "zabbix.cluster1.node1:10051": [
#         {
#             "processed": 1,
#             "failed": 0,
#             "total": 1,
#             "time": "0.000050",
#             "chunk": 1
#         }
#     ],
#     "zabbix.cluster2.node2:20051": [
#         {
#             "processed": 1,
#             "failed": 0,
#             "total": 1,
#             "time": "0.000053",
#             "chunk": 1
#         }
#     ]
# }
Подписаться
Уведомить о
1 Comment
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Artem Z
Artem Z
1 месяц назад

Спасибо! это отличная новость для всех интеграторов и кастомизаторов zabbix

1
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x