背景
正常来讲,我们在自动化发布的过程中难免会造成告警风暴,那么我们可以做些什么来规避这个问题呢,一个比较简单的方式就是在发布前禁用掉zabbix的触发器,发布完再启用,本篇博客就是在这个背景下进行创作的,本次用到的脚本如下所示:
~/Downloads/tmp tree
.
├── main.py
├── zhtrigfinder.py
└── ztrigswitcher.py
0 directories, 3 files
代码分两块,一个是main.py,一个是github上的开源脚本zhtrigfinder.py和ztrigswitcher.py,仓库名叫q1x/zabbix-gnomes,感谢,该仓库年久失修,为了防止失联本人fork了一下,也在下面把源码直接贴了出来,请把两块代码放到同一级目录。
整个项目依赖python2,主要是这个七八年前的上古开源脚本zhtrigfinder.py和ztrigswitcher.py也依赖python2。
依赖安装
python2 -m pip install pyzabbix==1.0.0 requests==2.26.0
使用
上述脚本的后两个脚本依赖一个叫zbx_prod.conf的下面格式的配置文件:
[Zabbix API]
username=johndoe
password=verysecretpassword
api=https://zabbix.mycompany.com/path/to/zabbix/frontend/
no_verify=true
主代码main.py命令行参数如下:
~/Downloads/tmp python main.py -h
usage: main.py [-h] [-k KEYWORDS] [-D] [-E]
A wrapper for zabbix tool.
optional arguments:
-h, --help show this help message and exit
-k KEYWORDS, --keywords KEYWORDS
search keywords
-D, --disabled disable the trigger
-E, --enabled enable the trigger
主代码main.py的运行方式如下:
python2 main.py -D -k 'XXX-DOCKER-WEB'
python2 main.py -E -k 'XXX-DOCKER-WEB'
代码
main.py如下:
#!/usr/bin/env python2
# -*- encoding: utf-8 -*-
import sys
import time
import logging
import argparse
import subprocess
import traceback
def logger_getter():
today = time.strftime("%Y-%m-%d", time.localtime())
logger = logging.getLogger()
if not len(logger.handlers):
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s ||| %(levelname)s ||| %(lineno)d ||| %(funcName)s ||| %(message)s",
datefmt='%Y-%m-%d %H:%M:%S')
file_handler = logging.FileHandler('/data/logs/debug.log' + '.' + today,encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
return logger
def check(keywords, host_ip):
try:
logger_getter().debug('Finding the system of {0} on host {1}'.format(keywords,host_ip))
trigger_id_list = subprocess.check_output("python2 /data/srv/salt/zhtrigfinder.py -c /data/srv/salt/zbx_prod.conf -s '{0}' -n '{1}'".format(keywords, host_ip), shell=True)
return trigger_id_list.strip()
except subprocess.CalledProcessError:
logger_getter().debug('Not found the system of {0} on host {1}'.format(keywords,host_ip))
return ''
def take_action(keywords, host_ip_list):
count = 0
# 命中host机器
target_host_ip = ''
for host_ip in host_ip_list:
trigger_id_list = check(keywords, host_ip)
if trigger_id_list:
count = count + 1
target_host_ip = host_ip
trigger_id_list = [id_ for id_ in trigger_id_list.strip().split('\n')]
for id_ in trigger_id_list:
try:
if args.disabled:
switcher_result = subprocess.check_output("python2 /data/srv/salt/ztrigswitcher.py -c /data/srv/salt/zbx_prod.conf -D -t " + str(id_), shell=True).strip()
if switcher_result == 'Disabled':
logger_getter().debug("1st: The trigger of {0}'s disable on host {1} is confirmed OK by API status!".format(keywords, host_ip))
elif args.enabled:
switcher_result = subprocess.check_output("python2 /data/srv/salt/ztrigswitcher.py -c /data/srv/salt/zbx_prod.conf -E -t " + str(id_),shell=True).strip()
if switcher_result == 'Enabled':
logger_getter().debug("1st: The trigger of {0}'s enable on host {1} is confirmed OK by both db and API status, and the value is 0!".format(keywords, host_ip))
except subprocess.CalledProcessError:
logger_getter().debug("Exception: Unknown error happened for switch the trigger status, the keywords is {0} and the host ip is {1}".format(args.keywords, host_ip))
logger_getter().debug(traceback.format_exc())
sys.exit(0)
break
else:
continue
if count == 0:
logger_getter().debug('Exception: no trigger id is found on all hosts for the keywords of {0}.'.format(keywords))
else:
if args.disabled:
logger_getter().debug("The system's trigger of {0} is disabled on {1}!".format(keywords, target_host_ip))
elif args.enabled:
logger_getter().debug("The system's trigger of {0} is enabled on {1}!".format(keywords,target_host_ip))
def main(args):
keywords = args.keywords
host_ip_list = ['192.168.1.1','192.168.1.2']
take_action(keywords, host_ip_list)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='A wrapper for zabbix tool.')
parser.add_argument(
'-k','--keywords',
help='search keywords',
type=str)
parser.add_argument(
'-D','--disabled',
help='disable the trigger',
action='store_true')
parser.add_argument(
'-E','--enabled',
help='enable the trigger',
action='store_true')
args = parser.parse_args()
main(args)
整个代码你只需要关注并修改一下logging库写日志的路径,以及前面提到的上古脚本的放置路径,以及main()中的host_ip_list = [‘192.168.1.1’,‘192.168.1.2’],自行修改为你们生产环境的zabbix agent探测机器的节点即可。
zhtrigfinder.py如下:
#!/usr/bin/env python
#
# import needed modules.
# pyzabbix is needed, see https://github.com/lukecyca/pyzabbix
#
import argparse
import ConfigParser
import os
import os.path
import sys
import distutils.util
from pyzabbix import ZabbixAPI
# define config helper function
def ConfigSectionMap(section):
dict1 = {}
options = Config.options(section)
for option in options:
try:
dict1[option] = Config.get(section, option)
if dict1[option] == -1:
DebugPrint("skip: %s" % option)
except:
print("exception on %s!" % option)
dict1[option] = None
return dict1
# set default vars
defconf = "~/.zbx.conf"
username = ""
password = ""
api = ""
noverify = ""
# Define commandline arguments
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,description='Tries to find triggers configured for the specified Zabbix host.', epilog="""
This program can use .ini style configuration files to retrieve the needed API connection information.
To use this type of storage, create a conf file (the default is $HOME/.zbx.conf) that contains at least the [Zabbix API] section and any of the other parameters:
[Zabbix API]
username=johndoe
password=verysecretpassword
api=https://zabbix.mycompany.com/path/to/zabbix/frontend/
no_verify=true
""")
group = parser.add_mutually_exclusive_group(required=False)
group2 = parser.add_mutually_exclusive_group(required=False)
parser.add_argument('hostname', help='Hostname to find the configured triggers on')
parser.add_argument('-u', '--username', help='User for the Zabbix api')
parser.add_argument('-p', '--password', help='Password for the Zabbix api user')
parser.add_argument('-a', '--api', help='Zabbix API URL')
parser.add_argument('--no-verify', help='Disables certificate validation when using a secure connection',action='store_true')
parser.add_argument('-c','--config', help='Config file location (defaults to $HOME/.zbx.conf)')
group.add_argument('-n', '--numeric', help='Return numeric triggerids instead of descriptions',action='store_true')
group.add_argument('-e', '--extended', help='Returns trigger id, value, status, state, severity, description and expression separated by ":". See https://www.zabbix.com/documentation/2.2/manual/api/reference/trigger/object for more information.',action='store_true')
group2.add_argument('-s', '--search', help='Show only triggers with a description containing this search string')
group2.add_argument('-A', '--active', help='Show only active triggers',action='store_true')
args = parser.parse_args()
# load config module
Config = ConfigParser.ConfigParser()
Config
# if configuration argument is set, test the config file
if args.config:
if os.path.isfile(args.config) and os.access(args.config, os.R_OK):
Config.read(args.config)
# if not set, try default config file
else:
if os.path.isfile(defconf) and os.access(defconf, os.R_OK):
Config.read(defconf)
# try to load available settings from config file
try:
username=ConfigSectionMap("Zabbix API")['username']
password=ConfigSectionMap("Zabbix API")['password']
api=ConfigSectionMap("Zabbix API")['api']
noverify=bool(distutils.util.strtobool(ConfigSectionMap("Zabbix API")["no_verify"]))
except:
pass
# override settings if they are provided as arguments
if args.username:
username = args.username
if args.password:
password = args.password
if args.api:
api = args.api
if args.no_verify:
noverify = args.no_verify
# test for needed params
if not username:
sys.exit("Error: API User not set")
if not password:
sys.exit("Error: API Password not set")
if not api:
sys.exit("Error: API URL is not set")
# Setup Zabbix API connection
zapi = ZabbixAPI(api)
if noverify is True:
zapi.session.verify = False
# Login to the Zabbix API
zapi.login(username, password)
##################################
# Start actual API logic
##################################
# set the hostname we are looking for
host_name = args.hostname
hosts = zapi.host.get(output="extend", filter={"host": host_name})
if hosts:
# Find triggers
if args.search:
triggers = zapi.trigger.get(filter={'host':host_name},output='extend',search={'description':args.search},expandExpression=1,expandDescription=1)
elif args.active:
triggers = zapi.trigger.get(filter={'host':host_name,'value':1},output='extend',monitored=1,active=1,expandExpression=1,expandDescription=1)
else:
triggers = zapi.trigger.get(filter={'host':host_name},output='extend',expandExpression=1,expandDescription=1)
if triggers:
if args.extended:
# print(ids and descriptions)
for trigger in triggers:
print(format(trigger["triggerid"])+":"+format(trigger["value"])+":"+format(trigger["status"])+":"+format(trigger["state"])+":"+format(trigger["priority"])+":"+format(trigger["description"])+":"+format(trigger["expression"]))
else:
if args.numeric:
# print(ids)
for trigger in triggers:
print(format(trigger["triggerid"]))
else:
# print(descriptions)
for trigger in triggers:
print(format(trigger["description"]))
else:
sys.exit("Error: No matching triggers found on "+ host_name)
else:
sys.exit("Error: Could not find host "+ host_name)
# And we're done...
ztrigswitcher.py如下:
#!/usr/bin/env python
#
# import needed modules.
# pyzabbix is needed, see https://github.com/lukecyca/pyzabbix
#
import argparse
import ConfigParser
import os
import os.path
import sys
import distutils.util
from pyzabbix import ZabbixAPI
# define config helper function
def ConfigSectionMap(section):
dict1 = {}
options = Config.options(section)
for option in options:
try:
dict1[option] = Config.get(section, option)
if dict1[option] == -1:
DebugPrint("skip: %s" % option)
except:
print("exception on %s!" % option)
dict1[option] = None
return dict1
# set default vars
defconf = "~/.zbx.conf"
username = ""
password = ""
api = ""
noverify = ""
# Define commandline arguments
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,description='Switches the host inventory mode for the specified host(s) or hostgroup(s). The default setting is to switch to "automatic" mode.', epilog="""
This program can use .ini style configuration files to retrieve the needed API connection information.
To use this type of storage, create a conf file (the default is $HOME/.zbx.conf) that contains at least the [Zabbix API] section and any of the other parameters:
[Zabbix API]
username=johndoe
password=verysecretpassword
api=https://zabbix.mycompany.com/path/to/zabbix/frontend/
no_verify=true
""")
group = parser.add_mutually_exclusive_group(required=True)
parser.add_argument('-t','--triggerid', help='Numeric trigger ID to change status on')
parser.add_argument('-u', '--username', help='User for the Zabbix api')
parser.add_argument('-p', '--password', help='Password for the Zabbix api user')
parser.add_argument('-a', '--api', help='Zabbix API URL')
parser.add_argument('--no-verify', help='Disables certificate validation when nventory_mode using a secure connection',action='store_true')
parser.add_argument('-c','--config', help='Config file location (defaults to $HOME/.zbx.conf)')
group.add_argument('-E', '--enable', help='Set trigger to enabled', action='store_true')
group.add_argument('-D', '--disable',help='Set trigger to disabled', action='store_true')
args = parser.parse_args()
# load config module
Config = ConfigParser.ConfigParser()
Config
# if configuration argument is set, test the config file
if args.config:
if os.path.isfile(args.config) and os.access(args.config, os.R_OK):
Config.read(args.config)
# if not set, try default config file
else:
if os.path.isfile(defconf) and os.access(defconf, os.R_OK):
Config.read(defconf)
# try to load available settings from config file
try:
username=ConfigSectionMap("Zabbix API")['username']
password=ConfigSectionMap("Zabbix API")['password']
api=ConfigSectionMap("Zabbix API")['api']
noverify=bool(distutils.util.strtobool(ConfigSectionMap("Zabbix API")["no_verify"]))
except:
pass
# override settings if they are provided as arguments
if args.username:
username = args.username
if args.password:
password = args.password
if args.api:
api = args.api
if args.no_verify:
noverify = args.no_verify
# test for needed params
if not username:
sys.exit("Error: API User not set")
if not password:
sys.exit("Error: API Password not set")
if not api:
sys.exit("Error: API URL is not set")
# Setup Zabbix API connection
zapi = ZabbixAPI(api)
if noverify is True:
zapi.session.verify = False
# Login to the Zabbix API
zapi.login(username, password)
##################################
# Start actual API logic
##################################
if not args.triggerid:
sys.exit("Error: Triggerid not found")
if args.enable:
status=int('0')
elif args.disable:
status=int('1')
else:
sys.exit("Error: Trigger status not provided")
trigger=zapi.trigger.get(filter={'triggerid':args.triggerid},output='triggerid')
if trigger:
result=zapi.trigger.update(triggerid=args.triggerid, status=status)
check=zapi.trigger.get(filter={'triggerid':args.triggerid}, output=['description','status'], expandDescription=1)
if check[0]['status'] == '0':
mode="Enabled"
print(mode)
elif check[0]['status'] == '1':
mode="Disabled"
print(mode)
else:
sys.exit("Error: Something went wrong!")
#print(format(mode) + " : " + format(check[0]['description']))
else:
sys.exit("Error: Trigger not found")
# sys.exit("Error: No trigger id provided.")
# try:
# # Apply the linkage
# result=zapi.host.massupdate(hosts=hlookup,inventory_mode=invm)
# except:
# sys.exit("Error: Something went wrong while performing the update")
#
#if args.extended:
# hosts=zapi.host.get(output='extend',hostids=result['hostids'])
# hostnames=""
# for host in range(len(hosts)):
# if not hostnames:
# hostnames = str(hosts[host]['host'])
# else:
# hostnames = hostnames + ", " + str(hosts[host]['host'])
# print("Inventory mode switched to \"" + args.mode + "\" on: " + hostnames)
#
# And we're done...