En este articulo, te presentamos las principales capacidades de la recién lanzada librería oficial zabbix_utils y te daremos ejemplos de cómo usarla con los componentes de Zabbix.

Zabbix es una solución de monitoreo flexible y universal que se conforma de una amplía variedad de diferentes sistemas innovadores, fuera de lo convencional.

A pesar de constantemente expandir la lista de sistemas de integración compatibles de forma nativa, (por medio de templates o integraciones de webhook), puede que aún exista la necesidad de integrarse con sistemas y servicios personalizados que aún no son son admitidos. En tales casos, una librería que implementa protocolos de interacción con el Zabbix API, el server/proxy de Zabbix, o Agent/Agent2 resulta extremadamente útil.

Dado que Phyton tiene una amplia aceptación entre ingenieros de DevOps y SRE, así como administradores de server, decidimos lanzar primero una librería para este lenguaje de programación.

Nos complace presentar  zabbix_utils – una librería de Phyton para la interacción fluida con la API de Zabbix, el server/proxy de Zabbix, y el Agent/Agent2 de Zabbix. Por su puesto, existen soluciones populares dentro de la comunidad, para trabajar con estos componentes de Zabbix en Phyton. Tomando este hecho en cuenta, hemos intentado consolidar los casos y problemas mas comunes junto con nuestra experiencia para desarrollar una herramienta lo más conveniente posible. Además, nos aseguramos de que la transición a esta herramienta sea lo mas sencilla y clara posible. Gracias al soporte oficial, puedes tener la certeza de que la versión actual de la librería es compatible con la última versión de Zabbix.

Escenarios de Uso

La librería zabbix_utils  puede ser utilizada en los siguientes escenarios, pero no se limita a ellos:

  • Automatización de Zabbix
  • Integración con sistemas de terceros
  • Personalizar soluciones de monitoreo
  • Exportación de data (hosts, templates, problems, etc.)
  • Integración en tu aplicación de Phyton para soporte de monitoreo de Zabbix
  • Cualquier otra idea que se te ocurra

Puedes utilizar zabbix_utils para automatizar tareas en Zabbix, como la creación de scripts para la configuración automática del monitoreo de los objetos de tu infraestructura de TI. Esto puede implicar el uso de ZabbixAPI para la gestión directa de los objetos de Zabbix, Sender para enviar valores a los hosts y Getter para recopilar datos de los Agents. Hablaremos de Sender y Getter con más detalle dentro de este artículo.

Por ejemplo, imaginemos que tienes una infraestructura compuesta por diferentes sucursales. Cada servidor o estación de trabajo se implementa desde una imagen con un Zabbix Agent configurado automáticamente, y cada sucursal se monitorea mediante un proxy de Zabbix debido a que tiene una red aislada. Tu servicio o script personalizado puede obtener una lista de este equipo de tu sistema CMDB, junto con cualquier información adicional. Luego, puede utilizar estos datos para crear hosts en Zabbix y vincular los templates necesarios mediante ZabbixAPI según la información recibida. Si la información de CMDB no es suficiente, puedes solicitar datos directamente al Zabbix Agent usando Getter y luego utilizar esta información para una configuración adicional y la toma de decisiones durante la configuración. Otra parte de tu script puede acceder a AD para obtener una lista de usuarios de la sucursal y actualizar la lista de usuarios en Zabbix a través de la API, asignándoles los permisos y roles apropiados según la información de AD o CMDB (por ejemplo, derechos de edición para propietarios de servidores).

Otro caso de uso de la librería podría ser cuando se exportan regularmente templates de Zabbix para su posterior importación en un sistema de control de versiones. También puedes establecer un mecanismo para cargar cambios y revertir a versiones anteriores de los templates. Aquí se pueden implementar una variedad de otros casos de uso, todo depende de tus requerimientos y del uso creativo de la biblioteca.

Claro, si eres un desarrollador y hay un requerimiento para implementar el soporte de monitoreo de Zabbix para tu sistema o herramienta personalizada, puedes implementar el envío de datos que describan cualquier evento generado por tu sistema/herramienta personalizada a Zabbix utilizando Sender.

Instalación y Configuración

Para comenzar, necesitas instalar la librería zabbix_utils. Puedes hacerlo principalmente mediante dos formas diferentes:

  • Usando pip:
~$ pip install zabbix_utils
  • Clonando desde GitHub:
~$ git clone https://github.com/zabbix/python-zabbix-utils
~$ cd python-zabbix-utils/
~$ python setup.py install

No se requiere configuración adicional, pero puedes especificar valores para variables de entorno como: ZABBIX_URL, ZABBIX_TOKEN, ZABBIX_USER y ZABBIX_PASSWORD si es necesario. Estos casos de uso se describen con mas detalle a continuación.

Trabajando con Zabbix API

Para trabajar con la API de Zabbix, es necesario importar la clase ZabbixAPI de la libreria zabbix_utils:

from zabbix_utils import ZabbixAPI

Si estás utilizando una de las librerías comunitarias populares existentes, comúnmente, será suficiente con reemplazar la declaración de importación de ZabbixAPI con una importación de nuestra librería.

En ese contexto, necesitas crear una instancia de la clase ZabbixAPI. Hay varios escenarios de uso:

  • Utilizar los valores predefinidos de las variables de entorno, es decir, no pasar ningún parámetro a ZabixAPI:
~$ export ZABBIX_URL="https://zabbix.example.local"
~$ export ZABBIX_USER="Admin"
~$ export ZABBIX_PASSWORD="zabbix"
from zabbix_utils import ZabbixAPI


api = ZabbixAPI()
  • Pasar solo la dirección API de Zabbix como un input, el cual puede especificarse tanto como la dirección IP/FQDN del servidor o nombre DNS (en este caso, se usará el protocolo HTTP) o como una URL, y los datos de autenticación aún deben especificarse como valores de las variables de entorno:
~$ export ZABBIX_USER="Admin"
~$ export ZABBIX_PASSWORD="zabbix"
from zabbix_utils import ZabbixAPI

api = ZabbixAPI(url="127.0.0.1")
  • Pasar únicamente la dirección de la API de Zabbix a ZabbixAPI, como se muestra en el ejemplo anterior, y posteriormente, pasar los datos de autenticación utilizando el método login():
from zabbix_utils import ZabbixAPI

api = ZabbixAPI(url="127.0.0.1")
api.login(user="Admin", password="zabbix")
  • Pasar todos los parámetros al mismo tiempo al crear una instancia de ZabbixAPI; en ese caso, no es necesario utilizar login() posteriormente:
from zabbix_utils import ZabbixAPI

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

La clase ZabbixAPI admite trabajar con varias versiones de Zabbix, verificando automáticamente la versión de la API durante la inicialización. También puedes trabajar con la versión de la API de Zabbix como un objeto de la siguiente manera:

from zabbix_utils import ZabbixAPI

api = ZabbixAPI()

# ZabbixAPI version field
ver = api.version
print(type(ver).__name__, ver) # APIVersion 6.0.24

# Method to get ZabbixAPI version
ver = api.api_version()
print(type(ver).__name__, ver) # APIVersion 6.0.24

# Additional methods
print(ver.major)    # 6.0
print(ver.minor)    # 24
print(ver.is_lts()) # True

Como resultado, obtendrás un objeto APIVersion que tiene campos major y minor que devuelven las partes menor y mayor respectivas de la versión actual, así como el método is_lts(), que devuelve true si la versión actual es LTS (Soporte a Largo Plazo – por sus siglas en inglés Long Term Support), y de lo contrario, devuelve false. El objeto APIVersion también puede compararse con una versión representada como un string o un número flotante.

# Version comparison
print(ver < 6.4)      # True
print(ver != 6.0)     # False
print(ver != "6.0.5") # True

Si la cuenta y la contraseña (o a partir de Zabbix 5.4 – token en lugar de login/password) no se establecen como valores de variables de entorno o durante la inicialización de ZabbixAPI, entonces es necesario usar el método login() para la autenticación:

from zabbix_utils import ZabbixAPI

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

Después de la autenticación, puedes hacer cualquier solicitud de API para todas las versiones compatibles en la documentación de Zabbix.

El formato para usar métodos de la API se ve así:

api_instance.zabbix_object.method(parameters)

Por ejemplo:

api.host.get()

Después de terminar todas las solicitudes necesarias para el API, es necesario ejecutar logout() si la autenticación fue hecha usando usuario y contraseña:

api.logout()

Puedes encontrar más ejemplos de uso aquí.

Enviar Valores al Server/Proxy de Zabbix

A menudo es necesario enviar valores a Zabbix Trapper. Para este propósito, se proporciona la utilidad zabbix_sender. Sin embargo, si tu servicio o script que envía estos datos está escrito en Python, usar una utilidad externa puede no ser muy conveniente. Por lo tanto, hemos desarrollado el Sender, que te ayudará a enviar valores al servidor o proxy de Zabbix uno por uno o en grupos. Para trabajar con Sender, necesitas importarlo de la siguiente manera:

from zabbix_utils import Sender

Después de eso, puedes enviar un valor único:

from zabbix_utils import Sender

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

Alternativamente, puedes agruparlos para enviarlos simultáneamente, para lo cual necesitas importar adicionalmente ItemValue:

from zabbix_utils import ItemValue, Sender


items = [
    ItemValue('host1', 'item.key1', 10),
    ItemValue('host1', 'item.key2', 'Test value'),
    ItemValue('host2', 'item.key1', -1, 1702511920),
    ItemValue('host3', 'item.key1', '{"msg":"Test value"}'),
    ItemValue('host2', 'item.key1', 0, 1702511920, 100)
]

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

Para casos en los que existe la necesidad de enviar más valores de los que Zabbix Trapper puede aceptar a la vez, hay una opción para hacer envíos fragmentados, es decir, el envío secuencial en fragmentos separados (chunks). Por defecto, el tamaño del chunk se establece en 250 valores. En otras palabras, al enviar valores en gran cantidad, al enviar 400 valores por el método send(), estos se enviarán en dos etapas. Primero se enviarán 250 valores, y los 150 valores restantes se enviarán después de recibir una respuesta. El tamaño del chunk puede cambiarse, para hacer esto, simplemente necesitas especificar tu valor para el parámetro chunk_size al inicializar Sender:

from zabbix_utils import ItemValue, Sender


items = [
    ItemValue('host1', 'item.key1', 10),
    ItemValue('host1', 'item.key2', 'Test value'),
    ItemValue('host2', 'item.key1', -1, 1702511920),
    ItemValue('host3', 'item.key1', '{"msg":"Test value"}'),
    ItemValue('host2', 'item.key1', 0, 1702511920, 100)
]

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

En el ejemplo anterior, el tamaño del chunk se establece en 2. Entonces, los 5 valores solicitados se enviarán en tres solicitudes de dos, dos y un valor, respectivamente.

Si tu servidor tiene múltiples interfaces de red, y los valores necesitan ser enviados desde una específica, Sender proporciona la opción de especificar un source_ip para los valores enviados:

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)

También admite la lectura de parámetros de conexión desde el archivo de configuración de Zabbix Agent/Agent2. Para hacer esto, establece el flag use_config, después de lo cual no es necesario pasar parámetros de conexión al crear una instancia de 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)

Dado que el archivo de configuración de Zabbix Agent/Agent2 puede especificar uno o incluso varios clusters de Zabbix compuestos por múltiples instancias de servidores de Zabbix, Sender enviará datos al primer servidor disponible de cada cluster especificado en el parámetro ServerActive en el archivo de configuración. En caso de que el parámetro ServerActive no esté especificado en el archivo de configuración de Zabbix Agent/Agent2, se tomará la dirección del servidor del parámetro Server con el puerto estándar de Zabbix Trapper – 10051.

Por defecto, Sender devuelve el resultado agregado del envío a través de todos los clusters. Pero es posible obtener información más detallada sobre los resultados del envío para cada chunk y cada cluster:

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

Puedes encontrar más ejemplos aquí .

Obtener valores del Zabbix Agent/Agent2 por item key.

A veces también puede ser útil recuperar valores directamente del Zabbix Agent. Para ser útil en esta tarea, zabbix_utils proporciona el Getter. El Getter realiza la misma función que la utilidad zabbix_get, permitiéndote trabajar nativamente dentro del código Python. Usar Getter es sencillo; simplemente impórtalo, crea una instancia pasando la dirección y el puerto del Zabbix Agent, y luego usa el método get(), proporcionando la clave del ítem de datos para el valor que deseas recuperar:

from zabbix_utils import Getter

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

En casos en que tu servidor tenga múltiples interfaces de red, y las solicitudes necesiten ser enviadas desde una específica, puedes especificar el source_ip para la conexión del Agente:

from zabbix_utils import Getter

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

La respuesta del Zabbix Agent será procesada por la biblioteca y devuelta como un objeto de la clase 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

Encuentra mas ejemplos de uso aquí.

Conclusión

La librería zabbix_utils para Python te permite aprovechar al máximo el monitoreo utilizando Zabbix, sin limitarte a las integraciones disponibles de fábrica. Puede ser valiosa tanto para ingenieros de DevOps y SRE, como para desarrolladores de Python que buscan implementar soporte de monitoreo para su sistema usando Zabbix.

En el próximo artículo, exploraremos a fondo la integración con un servicio externo utilizando esta biblioteca para demostrar las capacidades de zabbix_utils de manera más comprensiva.

Preguntas

P: ¿Qué versiones del Agente son compatibles con Getter?

R: Las versiones compatibles de los Agentes de Zabbix son las mismas que las versiones de la API de Zabbix, como se especifica en el archivo readme. Nuestro objetivo es crear una librería con soporte completo para todos los componentes de Zabbix de la misma versión.

P: ¿Getter admite la encriptación de Agentes?

R: El soporte para encriptación aún no está incorporado en Sender y Getter, pero puedes crear tu propio wrapper utilizando librerías de terceros para ambos.

from zabbix_utils import Sender

def psk_wrapper(sock, tls):
    # ...
    # Implementation of TLS PSK wrapper for the socket
    # ...

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

Para mas ejemplos, revisa aquí.

P: ¿Es posible establecer un valor de tiempo de espera (timeout) para Getter?

R: El valor del tiempo de espera de respuesta puede establecerse para el Getter, así como para ZabbixAPI y Sender. En todos los casos, el tiempo de espera se establece para esperar cualquier respuesta a las solicitudes.

# Example of setting a timeout for Sender
sender = Sender(server='127.0.0.1', port=10051, timeout=30)

# Example of setting a timeout for Getter
agent = Getter(host='127.0.0.1', port=10050, timeout=30)

P: ¿Se admite el modo paralelo (asincrónico)?

R: Actualmente, la librería no incluye clases ni métodos asincrónicos, pero planeamos desarrollar versiones asincrónicas de ZabbixAPI y Sender.

P: ¿Es posible especificar múltiples servidores al enviar a través de Sender sin especificar un archivo de configuración (para trabajar con un HA cluster – de alta disponibilidad)?

R: Sí, es posible de la siguiente manera:

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
#         }
#     ]
# }
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x