Neste artigo, vamos apresentar as principais capacidades da recém-lançada biblioteca oficial zabbix_utils e fornecer exemplos de como usá-la com os componentes do Zabbix.

Zabbix é uma solução flexível e universal de monitoramento que se integra facilmente com uma ampla variedade de sistemas diferentes desde o primeiro momento.

Apesar de estar em constante expansão na lista de sistemas com suporte nativo para integração (via modelos ou integrações de webhook), pode haver a necessidade de integrar sistemas e serviços personalizados que ainda não são suportados. Em tais casos, uma biblioteca que cuide da implementação dos protocolos de interação com a API do Zabbix, o servidor/proxy do Zabbix ou o Agent/Agent2 do Zabbix se torna extremamente útil.

Como o Python é amplamente adotado entre engenheiros de DevOps, SREs e administradores de servidores, decidimos lançar uma biblioteca para esta linguagem de programação primeiro.

Estamos felizes em apresentar o zabbix_utils – uma biblioteca Python para interação perfeita com a API do Zabbix, o servidor/proxy do Zabbix e o Agent/Agent2 do Zabbix. Claro, existem soluções populares da comunidade para trabalhar com esses componentes do Zabbix em Python. Mantendo esse fato em mente, tentamos consolidar problemas e casos populares, juntamente com nossa experiência, para desenvolver a ferramenta mais conveniente possível. Além disso, garantimos que a transição para a ferramenta seja o mais direta e clara possível. Graças ao suporte oficial, você pode ter certeza de que a versão atual da biblioteca é compatível com a versão mais recente do Zabbix.

Cenários de Uso

A biblioteca zabbix_utils pode ser usada nos seguintes cenários, mas não se limita a eles:

  • Automação do Zabbix
  • Integração com sistemas de terceiros
  • Soluções de monitoramento personalizadas
  • Exportação de dados (hosts, modelos, problemas, etc.)
  • Integração em sua aplicação Python para suporte de monitoramento do Zabbix
  • Qualquer outra coisa que você possa imaginar

Você pode usar o zabbix_utils para automatizar tarefas do Zabbix, como configurar automaticamente a monitoração de objetos de infraestrutura de TI. Isso pode envolver o uso do ZabbixAPI para o gerenciamento direto de objetos do Zabbix, o Sender para enviar valores para hosts e o Getter para coletar dados dos Zabbix Agents. Vamos discutir o Sender e o Getter com mais detalhes posteriormente neste artigo.

Por exemplo, imagine que você tenha uma infraestrutura composta por diferentes filiais. Cada servidor ou estação de trabalho é implantado a partir de uma imagem com um Zabbix Agent configurado automaticamente e cada filial é monitorada por um proxy do Zabbix, pois possui uma rede isolada. Seu serviço ou script personalizado pode buscar uma lista deste equipamento no seu sistema CMDB, juntamente com qualquer informação adicional.

Em seguida, pode usar esses dados para criar hosts no Zabbix e vincular os modelos necessários usando o ZabbixAPI com base nas informações recebidas. Se as informações do CMDB forem insuficientes, você pode solicitar dados diretamente do Zabbix Agent configurado usando o Getter e, em seguida, usar essas informações para configuração adicional e tomada de decisões durante a instalação. 

Outra parte do seu script pode acessar o AD para obter uma lista de usuários da filial para atualizar a lista de usuários no Zabbix por meio da API e atribuir a eles as permissões e funções apropriadas com base nas informações do AD ou CMDB (por exemplo, direitos de edição para proprietários de servidores).

Outro caso de uso da biblioteca pode ser quando você regularmente exporta modelos do Zabbix para posterior importação em um sistema de controle de versão. Você também pode estabelecer um mecanismo para carregar alterações e retornar a versões anteriores dos modelos. Aqui, uma variedade de outros casos de uso também podem ser implementada – tudo depende de seus requisitos e do uso criativo da biblioteca.

Claro, se você é um desenvolvedor e há uma necessidade de implementar o suporte de monitoramento do Zabbix para o seu sistema ou ferramenta personalizada, você pode implementar o envio de dados descrevendo quaisquer eventos gerados pelo seu sistema/ferramenta personalizada para o Zabbix usando o Sender.

Instalação e Configuração

Para começar, você precisa instalar a biblioteca zabbix_utils. Você pode fazer isso de duas maneiras principais:

    • Usando o pip:

~$ pip install zabbix_utils

    • Fazendo uma cópia do GitHub:

~$ git clone https://github.com/zabbix/python-zabbix-utils
~$ cd python-zabbix-utils/
~$ python setup.py install

Não é necessária nenhuma configuração adicional. Mas você pode especificar valores para as seguintes variáveis de ambiente: ZABBIX_URL, ZABBIX_TOKEN, ZABBIX_USER, ZABBIX_PASSWORD, se necessário. Esses casos de uso são descritos com mais detalhes abaixo.

Trabalhando com a API do Zabbix

Para trabalhar com a API do Zabbix, é necessário importar a classe ZabbixAPI da biblioteca zabbix_utils:

from zabbix_utils import ZabbixAPI

Se você estiver usando uma das populares bibliotecas da comunidade, na maioria dos casos, será suficiente substituir a instrução de importação do ZabbixAPI por uma importação de nossa biblioteca.

Nesse ponto, você precisa criar uma instância da classe ZabbixAPI. Existem vários cenários de uso:

    • Usar os valores predefinidos das variáveis de ambiente, ou seja, não passar nenhum parâmetro para o ZabbixAPI:

~$ export ZABBIX_URL="https://zabbix.example.local"
~$ export ZABBIX_USER="Admin"
~$ export ZABBIX_PASSWORD="zabbix"

from zabbix_utils import ZabbixAPI

api = ZabbixAPI()

    • Passar apenas o endereço da API do Zabbix como entrada, que pode ser especificado como o endereço IP/FQDN do servidor ou o nome DNS (nesse caso, o protocolo HTTP será usado) ou como uma URL, e os dados de autenticação ainda devem ser especificados como valores das variáveis de ambiente:

~$ export ZABBIX_USER="Admin"
~$ export ZABBIX_PASSWORD="zabbix"

from zabbix_utils import ZabbixAPI
api = ZabbixAPI(url="127.0.0.1")

    • Passar apenas o endereço da API do Zabbix para o ZabbixAPI, como no exemplo acima, e passar os dados de autenticação posteriormente usando o método login():

from zabbix_utils import ZabbixAPI
api = ZabbixAPI(url="127.0.0.1")
api.login(user="Admin", password="zabbix")

    • Passar todos os parâmetros de uma vez ao criar uma instância do ZabbixAPI; neste caso, não é necessário chamar o login() posteriormente:

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

A classe ZabbixAPI suporta o trabalho com várias versões do Zabbix, verificando automaticamente a versão da API durante a inicialização. Você também pode trabalhar com a versão da API do Zabbix como um objeto da seguinte forma:

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, você obterá um objeto APIVersion que possui campos major e minor que retornam as partes minor e major respectivas da versão atual, bem como o método is_lts() que retorna verdadeiro se a versão atual for LTS (Long Term Support) e falso caso contrário. O objeto APIVersion também pode ser comparado com uma versão representada como uma string ou um número float:

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

Se a conta e a senha (ou a partir do Zabbix 5.4 – o token em vez de login/senha) não forem definidos como valores de variáveis de ambiente ou durante a inicialização do ZabbixAPI, é necessário chamar o método login() para autenticação:

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

Após a autenticação, você pode fazer qualquer solicitação de API descrita para todas as versões suportadas na documentação de Zabbix.

O formato para chamar os métodos da API é este:

api_instance.zabbix_object.method(parameters)

Por exemplo:

api.host.get()

Após concluir todas as solicitações de API necessárias, é necessário executar logout() se a autenticação foi feita usando login e senha:

api.logout()

Mais exemplos de uso podem ser encontrados aqui.

Enviando Valores para o Server/Proxy do Zabbix

Muitas vezes, há a necessidade de enviar valores para o Trapper do Zabbix. Para esse fim, é fornecida a utilidade zabbix_sender

No entanto, se seu serviço ou script que envia esses dados estiver escrito em Python, chamar uma utilidade externa pode não ser muito conveniente. Portanto, desenvolvemos o Sender, que o ajudará a enviar valores para o servidor ou proxy do Zabbix um por um ou em grupos. Para trabalhar com o Sender, você precisa importá-lo da seguinte forma:

from zabbix_utils import Sender

Depois disso, você pode enviar um único valor:

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

Outra alternativa é colocá-los em um grupo para envio simultâneo, para o qual você precisa importar o ItemValue adicionalmente:

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 em que há a necessidade de enviar mais valores do que o Trapper do Zabbix pode aceitar de uma só vez, existe a opção de envio fragmentado, ou seja, envio sequencial em fragmentos separados (chunks). Por padrão, o tamanho do chunk é definido para 250 valores. Em outras palavras, ao enviar valores em massa, os 400 valores passados para o método send() para envio serão finalizados em duas etapas. 

Primeiro, 250 valores serão enviados, e os 150 valores restantes serão enviados após receber uma resposta. O tamanho do chunk pode ser alterado, para isso, você só precisa especificar seu valor para o parâmetro chunk_size ao inicializar o 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)

No exemplo acima, o tamanho do chunk está definido como 2. Portanto, os 5 valores passados serão enviados em três solicitações de dois, dois e um valor, respectivamente.

Se o seu servidor tiver várias interfaces de rede e os valores precisarem ser enviados de uma específica, o Sender oferece a opção de especificar um source_ip para os 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)

Ele também suporta a leitura dos parâmetros de conexão do arquivo de configuração do Zabbix Agent/Agent2. Para fazer isso, defina a flag use_config, após o que não é necessário passar os parâmetros de conexão ao criar uma instância do 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)

Como o arquivo de configuração do Zabbix Agent/Agent2 pode especificar um ou mesmo vários clusters do Zabbix consistindo em várias instâncias do servidor Zabbix, o Sender enviará dados para o primeiro servidor disponível de cada cluster especificado no parâmetro ServerActive do arquivo de configuração. 

Caso o parâmetro ServerActive não seja especificado no arquivo de configuração do Zabbix Agent/Agent2, o endereço do servidor com a porta padrão do Zabbix Trapper – 10051 será utilizado.

Por padrão, o Sender retorna o resultado agregado do envio em todos os clusters. Mas é possível obter informações mais detalhadas sobre os resultados do envio para cada chunk e 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

Veja mais exemplos aqui.

Obtendo Valores do Zabbix Agent/Agent2 pelo item key.

Às vezes, também pode ser útil recuperar diretamente valores do Zabbix Agent. Para auxiliar nesta tarefa, o zabbix_utils fornece o Getter. Ele realiza a mesma função da utilidade zabbix_get, permitindo que você trabalhe nativamente dentro do código Python.

O Getter é fácil de usar; basta importá-lo, criar uma instância passando o endereço e a porta do Zabbix Agent e depois chamar o método get(), fornecendo a chave do item de dados para o valor que você deseja recuperar:

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

Nos casos em que seu servidor possui várias interfaces de rede e as solicitações precisam ser enviadas de uma específica, você pode especificar o source_ip para a conexão do Agent:

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

A resposta do Zabbix Agent será processada pela biblioteca e retornada como um objeto da classe 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

Veja mais exemplos de uso aqui.

Conclusão

A biblioteca zabbix_utils para Python permite que você aproveite ao máximo o monitoramento usando o Zabbix, sem se limitar às integrações disponíveis por padrão. 

Pode ser valiosa tanto para engenheiros de DevOps e SRE quanto para desenvolvedores Python que desejam implementar suporte de monitoramento para seu sistema usando o Zabbix.

No próximo artigo, exploraremos detalhadamente a integração com um serviço externo usando esta biblioteca para demonstrar mais abrangentemente as capacidades do zabbix_utils.

Perguntas

P: Quais versões do Agent são suportadas para o Getter?

R: As versões suportadas dos Zabbix Agents são as mesmas das versões da API Zabbix, conforme especificado no arquivo README. Nosso objetivo é criar uma biblioteca com suporte completo para todos os componentes do Zabbix da mesma versão.

P: O Getter suporta a criptografia do Agent?

R: O suporte à criptografia ainda não está incorporado ao Sender e ao Getter, mas você pode criar seu próprio wrapper usando bibliotecas de terceiros 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
)

Veja mais exemplos aqui.

P: É possível definir um valor de tempo limite (timeout) para o Getter?

R: O valor de tempo limite para a resposta pode ser definido para o Getter, bem como para o ZabbixAPI e Sender. Em todos os casos, o tempo limite é definido para aguardar respostas para solicitações.

# 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: O modo paralelo (assíncrono) é suportado?

R: Atualmente, a biblioteca não inclui classes e métodos assíncronos, mas planejamos desenvolver versões assíncronas do ZabbixAPI e Sender.

P: É possível especificar vários servidores ao enviar através do Sender sem especificar um arquivo de configuração (para trabalhar com um cluster de alta disponibilidade)?

R: Sim, é possível da seguinte forma:

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