Статьи

Избавляемся от боли в Zabbix UI: как создать комбинированный график для хостов с помощью Zabbix API


Этот пост ответит на два вопроса:

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

Я укажу, как это можно сделать с помощью простого HTTP POST, а затем покажу решение, использующее библиотеку Python для доступа к API Zabix.

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

Zabbix API

Zabbix API — это REST API, представленный в 1.8, который позволяет управлять ресурсами Zabbix, такими как элементы, триггеры и графики. Ресурсы связаны друг с другом с помощью идентификаторов, поэтому обычно вам нужно получить ресурс по его имени, извлечь его идентификатор и использовать его для получения связанных ресурсов.

Документация по API в v1.8 … отсутствует, поэтому я обычно читаю документацию v2.2, затем проверяю соответствующую страницу и пример в 1.8 и как это будет работать. Документация иногда содержит ошибки, поэтому не верьте ей. (F.ex. item.get «s фильтр должен быть массив , но на самом деле является объектом.) Кроме того , не ясно, для ГЭТ, когда поля Ored и когда операция AND и можете ли вы сделать что — нибудь об этом.

Для Zabbix API есть несколько библиотек, я выбрал Python, потому что он мне нравится, и zabbix_api.py, потому что он, похоже, поддерживается и развивается. У меня была проблема с авторизацией, но мне удалось обойти ее.

Использование API

Аутентификация и авторизация

Обычно вы сначала авторизуетесь с помощью Zabbix и используете токен аутентификации, который вы получаете от него, во всех последующих вызовах.

Поймать : Zabbix API должен быть включен для пользователя

Пользователь Zabbix, используемый для связи с API, должен быть авторизован для использования API (для этого есть флажок в администрировании Zabbix). В нашей конфигурации это отключено по умолчанию, и в нашем случае пользователи должны быть добавлены в группу Zabbix api.

Если у вас нет доступа, вы сможете пройти аутентификацию с помощью API, но другие запросы не будут выполнены с «Нет доступа к API».

Общие атрибуты получения

Запросы get имеют некоторые общие атрибуты, такие как

  • output = (extended | Shortten | Refer |…) — сколько включить в вывод (см. => только идентификаторы, удлинить => как можно больше)
  • фильтр — мы увидим это ниже

Реализация

Создание графа с HTTP POST

Вы общаетесь с API, публикуя JSON. Задача easiset — выполнить:

curl -i -n -X POST --header "Content-Type: application/json" -d@- https://zabbix.example.com/api_jsonrpc.php

и вставьте туда JSON, добавьте новую строку и нажмите Control-D, чтобы завершить ввод.

Аутентифицироваться с Zabbix:

curl -i -n -X POST --header "Content-Type: application/json" -d@- https://zabbix.example.com/api_jsonrpc.php
{
    "jsonrpc": "2.0",
    "method": "user.authenticate",
    "params": {
        "user": "my zabbix username",
        "password": "my precious"
    },
    "auth": null,
    "id": 0
}
=>
{"jsonrpc":"2.0","result":"2dddea29e1d37b9f90069dd129d7a66d","id":0}
  • Я считаю, что значение id не важно, но вы должны предоставить некоторые, чтобы получить разумный ответ; 0, 1, 2 работают нормально.

Получите все элементы в группе Host Analytics, используя токен авторизации:

{
"jsonrpc":"2.0",
"method":"item.get",
"params":{
    "output":"shorten",
    "filter": {"description": "Processor load15"},
    "group": "Analytics production",
    "select_hosts": "extend",
    "limit": 10
},
"auth":"2dddea29e1d37b9f90069dd129d7a66d",
"id":2
}
=> [{"itemid":"40002","hosts":[{"maintenances":[..],"hostid":"10242","proxy_hostid":"10381","host":"my-server.example.com","dns":"my-server.example.com",...}]},{"itemid":"40003",...

Что ж, мы пропустим все остальное и перейдем к настоящему веселью — Python API.

Создание графа с помощью zabbix_api.py

Некоторые заметки:

  1. У меня были проблемы с авторизацией, мне приходилось указывать имя пользователя и пароль как в конструкторе (для http-заголовков auth. Http), так и вызывать метод login, чтобы он работал; в теории, только один из этих двух должен быть необходим. (Я мог где-то ошибиться.)
  2. По сравнению с curl есть некоторые преимущества, такие как отсутствие необходимости указывать неважные атрибуты, такие как идентификатор запроса, и автоматическое отображение между списками / dicts Python и JSON.

Прежде чем мы покажем код, давайте посмотрим, как его использовать:

bash$ ipython
In [1]: %run create_graph.py
In [2]: g = ZabbixGrapher(user="my zabbix user", passwd="my precious")
20: url: https://zabbix.example.com/api_jsonrpc.php
20: HTTP Auth enabled
20: Sending: {"params": {"password": "my precious", "user": "my zabbix user"}, "jsonrpc": "2.0", "method": "user.authenticate", "auth": "", "id": 0}
20: Response Code: 200
Logged in, auth: c417623c2d72e0f14ddab044429b80e7
In [3]: g.create_graph("CPU iowait on data nodes(avg1)", item_key="system.cpu.util[,iowait,avg1]", item_descr = None, host_group = "Analytics staging", hostname_filter_fn = lambda dns: "-data" in dns)

Длинный, страшный код сам по себе:

create_graph.py
import logging
import sys
 
from zabbix_api import ZabbixAPI, ZabbixAPIException
 
BOLD = "\033[1m"
RESET = "\033[0;0m"
 
class Palette:
    last_idx = 0
    colors = ["C04000", "800000", "191970", "3EB489", "FFDB58", "000080",
              "CC7722", "808000", "FF7F00", "002147", "AEC6CF", "836953",
              "CFCFC4", "77DD77", "F49AC2", "FFB347", "FFD1DC", "B39EB5",
              "FF6961", "CB99C9", "FDFD96", "FFE5B4", "D1E231", "8E4585",
              "FF5A36", "701C1C", "FF7518", "69359C", "E30B5D", "826644",
              "FF0000", "414833", "65000B", "002366", "E0115F", "B7410E",
              "FF6700", "F4C430", "FF8C69", "C2B280", "967117", "ECD540",
              "082567"]
 
    def next(self):
        self.last_idx = (self.last_idx + 1) % len(self.colors)
        return self.colors[self.last_idx]
 
class ZabbixGrapher:
 
    def __init__(self, user, passwd, log_level=logging.INFO):
 
        try:
            # I had to spec. user+psw here to use Basic http auth to be able
            # to log in even though I supply them to login below;
            # otherwise the call failed with 'Error: HTTP Error 401: Authorization Required'
            self.zapi = ZabbixAPI(
                server="https://zabbix.example.com",
                path="/api_jsonrpc.php",
                user=user, passwd=passwd,
                log_level=log_level) # or DEBUG
 
            # BEWARE: The user must have access to the Zabxi API enabled (be
            # in the Zabbix API user group)
            self.zapi.login(user, passwd)
            print "Logged in, auth: " + self.zapi.auth
        except ZabbixAPIException as e:
            msg = None
            if "" in str(e):
                msg = "Connection to Zabbix timed out, it's likely having temporary problems, retry now or later'"
            else:
                msg = "Error communicating with Zabbix. Please check your authentication, Zabbix availability. Err: " + str(e)
 
            print BOLD + "\n" + msg + RESET
            raise ZabbixAPIException, ZabbixAPIException(msg), sys.exc_info()[2]
 
    def create_graph(self,
                     graph_name="CPU Loads All Data Nodes",
                     item_descr="Processor load15",
                     item_key=None,
                     host_group="Analytics production",
                     hostname_filter_fn=lambda dns: "-analytics-prod-data" in dns and ("-analytics-prod-data01" in dns or dns >= "aewa-analytics-prod-data15"),
                     #show_legend = True - has no effect (old Z. version?)
                     ):
 
        palette = Palette()
        try:
 
            items = self.get_items(item_descr = item_descr, item_key = item_key, host_group = host_group)
 
            if not items:
                raise Exception("No items with (descr=" + str(item_descr) +
                                ", key=" + str(item_key) + ") in the group '" +
                                host_group + "' found")
 
            # Transform into a list of {'host':.., 'itemid':..} pairs,
            # filter out unwanted hosts and sort by host to have a defined order
            item_maps = self.to_host_itemid_pairs(items, hostname_filter_fn)
            item_maps = sorted(
                filter(lambda it: hostname_filter_fn(it['host']), item_maps),
                key = lambda it: it['host'])
 
            if not item_maps:
                raise Exception("All retrieved items filtered out by the filter function; orig. items: " +
                                str(item_maps))
 
            # The graph will be created on the 1st item's host:
            graph_host = item_maps[0]['host']
 
            ## CREATE GRAPH
            # See https://www.zabbix.com/documentation/2.0/manual/appendix/api/graph/definitions
            graph_items = []
 
            for idx, item in enumerate(item_maps):
                graph_items.append({
                        "itemid": item['itemid'],
                        "color": palette.next(),
                        "sortorder": idx
                        })
 
            graph = self.zapi.graph.create({
                    "gitems": graph_items,
                    "name": graph_name,
                    "width":900,
                    "height":200
                    #,"show_legend": str(int(show_legend))
                    })
 
            print "DONE. The graph %s has been created on the node %s: %s." % (graph_name, graph_host, str(graph))
        except Exception as e:
            msg = None
            if "No API access while sending" in str(e):
                msg = "The user isn't allowed to access the Zabbix API; go to Zabbix administration and enable it (f.ex. add the group API access to the user)'"
            else:
                msg = "Error communicating with Zabbix. Please check your request and whether Zabbix is available. Err: " + str(e)
 
            print BOLD + "\n" + msg + RESET
            raise type(e), type(e)(msg), sys.exc_info()[2]
 
    def get_items(self, item_descr = None, item_key = None, host_group = None):
        if not item_descr and not item_key:
            raise ValueError("Either item_key or item_descr must be provided")
 
        ## GET ITEMS to include in the graph
        # See (Zabbix 2.0 so not 100% relevant but better doc)
        # https://www.zabbix.com/documentation/2.0/manual/appendix/api/item/get
        filters = {}
        if item_descr: filters["description"] = item_descr
        if item_key: filters["key_"] = item_key
 
        return self.zapi.item.get({
                "output":"shorten",
                "filter": filters,
                "group": host_group,
                "select_hosts": "extend"
                })
 
    @staticmethod
    def to_host_itemid_pairs(items, hostname_filter_fn):
        # List of (host, itemid) pairs sorted by host
        items_by_host = []
 
        for item in items:
            itemid = item['itemid']
            dns = item['hosts'][0]['dns']
 
            if hostname_filter_fn(dns):
                items_by_host.append({"host": dns, "itemid": itemid})
 
        return items_by_host

Другие способы

Как отметил мой коллега Маркус Крюгер:


Вы также можете использовать автоматическую регистрацию или автоматическое обнаружение, чтобы добавить хосты в группы, а затем извлечь агрегированные данные по всем хостам в группе хостов.
(Конечно, это работает, только если вы хотите получить данные со
всех хостов в группе — но если вы не хотите получать данные, не добавляйте хост в эту группу.) Таким образом, не требуется никакой ручной работы для добавления мониторинга и построение графиков между несколькими экземплярами, мигающими в и из существования.

Некоторые ссылки:
Автоматическая регистрация  (2.0 документа, но должна быть достаточно точной и для 1.8.3)

Тем не менее, с Zabbix все еще довольно неудобно работать.

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

Вывод

Использовать API легко и быстро, особенно с Python. Работа с интерфейсом настолько медленная и мучительная, что я действительно рекомендую использовать API.