В состав Nginx Plus входят расширенные средства мониторинга и диагностики. О том, как интегрировать их с Zabbix я и хочу рассказать.

Как видно на иллюстрации, у Nginx Plus есть неплохой дашборд, отображающий его состояние. Это здорово, но если уже используется Zabbix, хотелось бы отслеживать эти данные в нём. К счастью, Nginx Plus позволяет получить текущий статус в виде файла в формате JSON.
Информация, выдаваемая этим json-файлом, делится на два типа:
1. касающаяся сервера в целом
2. касающаяся upstreams
Если о состоянии сервера в целом вопросов не возникает, то с апстримами несколько сложнее.
Поскольку количество апстримов и их пиров заранее неизвестно, имеет смысл определять их динамически с помощью низкоуровневых запросов заббикса (LLD).
для разбора json из Nginx используем скрипт на питоне:
Для того, чтобы выяснить, сколько апстримов и пиров есть у сервера, используем скрипт автообнаружения:
Чтобы мониторинг заработал, нужно сделать следующее:


На этом настройка мониторинга для Nginx Plus завершена. Использованные в статье файлы доступны в репозитарии на GitHub.
Если у вас есть вопросы, вы хотите что-то добавить или у вас что-то не заработало, напишите мне об этом (в комментариях, в Issues, или личным сообщением).
Спасибо за внимание!

Как видно на иллюстрации, у Nginx Plus есть неплохой дашборд, отображающий его состояние. Это здорово, но если уже используется Zabbix, хотелось бы отслеживать эти данные в нём. К счастью, Nginx Plus позволяет получить текущий статус в виде файла в формате JSON.
Информация, выдаваемая этим json-файлом, делится на два типа:
1. касающаяся сервера в целом
2. касающаяся upstreams
Если о состоянии сервера в целом вопросов не возникает, то с апстримами несколько сложнее.
Поскольку количество апстримов и их пиров заранее неизвестно, имеет смысл определять их динамически с помощью низкоуровневых запросов заббикса (LLD).
для разбора json из Nginx используем скрипт на питоне:
nginx-stats.py
#! /usr/bin/python2 # -*- coding: utf-8 -*- # script awaits command line args for input # json keys from nginx status use as args # examples: # nginx-stats.py requests current (get current requests count) # nginx-stats.py upstreams hg-backend peers 10.0.0.1 requests (get count requests to peer 10.0.0.1, for upstream hg-backend) # into Zabbix templates it looks like: # 1) all active connections [connections,active] # 2) all current requests [requests,current] # Upstreams: # 4) active connections [upstreams,{#UPSTREAM},peers,{#NODE_IP},active] # 5) status [upstreams,{#UPSTREAM},peers,{#NODE_IP},state] # 7) requests per second [upstreams,{#UPSTREAM},peers,{#NODE_IP},requests] # 8) responses for every HTTP-code per second [upstreams,{#UPSTREAM},peers,{#NODE_IP},responses,Xxx] # 9) summ active connections [upstreams,{#UPSTREAM},active] # 10) summ requests per second [upstreams,{#UPSTREAM},requests] # 11) summ responses for every HTTP-code per second [upstreams,{#UPSTREAM},responses,Xxx] import json, sys, os, urllib # parse json from nginx to doct data url="http://demo.nginx.com/status" directory="/tmp/nginx-stats/" response = urllib.urlopen(url) data = json.loads(response.read()) maxTime = float(3600) # in seconds avgTime = float(60) # average during, in seconds def printInt(float): print(int(round(float))) if not os.path.exists(directory): os.makedirs(directory) tmpfile = directory + str(sys.argv[1]) for i in range(2, len(sys.argv)): tmpfile = tmpfile + "." + str(sys.argv[i]) # test for file with data from previous run # if not - create it with current data and exit # if yes - read it to timestampDelta for count req/s and res/s try: json.loads(open(tmpfile).read()) except IOError as e: with open(tmpfile, 'w') as delta_file: json.dump(data, delta_file) sys.exit() except ValueError as e: with open(tmpfile, 'w') as delta_file: json.dump(data, delta_file) sys.exit() else: with open(tmpfile) as data_file: data_delta = json.load(data_file) timestampDelta = data_delta["timestamp"] # check load_timestamp with data from previous run # if it have another value, create temp file # with current data and exit. if int(data['load_timestamp']) <> int(data_delta['load_timestamp']): with open(tmpfile, 'w') as delta_file: json.dump(data, delta_file) sys.exit() # check timestamp file with data from previous run # if it older then maxTime (1 hour by default) # create it with current data and exit. if int(data['timestamp']) - int(timestampDelta) > (maxTime * 1000) : with open(tmpfile, 'w') as delta_file: json.dump(data, delta_file) sys.exit() delta = (data['timestamp'] - timestampDelta) / (avgTime * 1000) if ((str(sys.argv[1])) == "connections") or ((str(sys.argv[1])) == "requests"): print data[str(sys.argv[1])][str(sys.argv[2])] # print all active connections or all current connections elif (str(sys.argv[1])) == "upstreams": ip_data = dict([[v['server'],v] for v in data['upstreams'][str(sys.argv[2])]['peers']]) ip_data_delta = dict([[v['server'],v] for v in data_delta['upstreams'][str(sys.argv[2])]['peers']]) if ((str(sys.argv[3])) == "active") or ((str(sys.argv[3])) == "requests") or ((str(sys.argv[3])) == "responses"): summ_active = summ_requests = summ_responses_1xx = summ_responses_2xx = summ_responses_3xx = summ_responses_4xx = summ_responses_5xx = 0 for i in ip_data.keys(): summ_active = summ_active + ip_data[i]['active'] summ_requests = summ_requests + (ip_data[i]['requests'] - ip_data_delta[i]['requests']) / delta summ_responses_1xx = summ_responses_1xx + (ip_data[i]['responses']['1xx'] - ip_data_delta[i]['responses']['1xx']) / delta summ_responses_2xx = summ_responses_2xx + (ip_data[i]['responses']['2xx'] - ip_data_delta[i]['responses']['2xx']) / delta summ_responses_3xx = summ_responses_3xx + (ip_data[i]['responses']['3xx'] - ip_data_delta[i]['responses']['3xx']) / delta summ_responses_4xx = summ_responses_4xx + (ip_data[i]['responses']['4xx'] - ip_data_delta[i]['responses']['4xx']) / delta summ_responses_5xx = summ_responses_5xx + (ip_data[i]['responses']['5xx'] - ip_data_delta[i]['responses']['5xx']) / delta if (str(sys.argv[3])) == "active": print summ_active elif (str(sys.argv[3])) == "requests": printInt (summ_requests) elif (str(sys.argv[3])) == "responses": if (str(sys.argv[4])) == "1xx": printInt (summ_responses_1xx) elif (str(sys.argv[4])) == "2xx": printInt (summ_responses_2xx) elif (str(sys.argv[4])) == "3xx": printInt (summ_responses_3xx) elif (str(sys.argv[4])) == "4xx": printInt (summ_responses_4xx) elif (str(sys.argv[4])) == "5xx": printInt (summ_responses_5xx) else: sys.exit() else: sys.exit() elif ((str(sys.argv[5])) == "active") or ((str(sys.argv[5])) == "state"): print ip_data[str(sys.argv[4])][str(sys.argv[5])] # print peer's active connections or peer's state elif (str(sys.argv[5])) == "requests": printInt ((ip_data[str(sys.argv[4])]['requests'] - ip_data_delta[str(sys.argv[4])]['requests']) / delta) elif (str(sys.argv[5])) == "responses": if (str(sys.argv[6])) == "1xx": printInt ((ip_data[str(sys.argv[4])]['responses']['1xx'] - ip_data_delta[str(sys.argv[4])]['responses']['1xx']) / delta) elif (str(sys.argv[6])) == "2xx": printInt ((ip_data[str(sys.argv[4])]['responses']['2xx'] - ip_data_delta[str(sys.argv[4])]['responses']['2xx']) / delta) elif (str(sys.argv[6])) == "3xx": printInt ((ip_data[str(sys.argv[4])]['responses']['3xx'] - ip_data_delta[str(sys.argv[4])]['responses']['3xx']) / delta) elif (str(sys.argv[6])) == "4xx": printInt ((ip_data[str(sys.argv[4])]['responses']['4xx'] - ip_data_delta[str(sys.argv[4])]['responses']['4xx']) / delta) elif (str(sys.argv[6])) == "5xx": printInt ((ip_data[str(sys.argv[4])]['responses']['5xx'] - ip_data_delta[str(sys.argv[4])]['responses']['5xx']) / delta) else: sys.exit() else: sys.exit() else: sys.exit() with open(tmpfile, 'w') as delta_file: json.dump(data, delta_file)
Для того, чтобы выяснить, сколько апстримов и пиров есть у сервера, используем скрипт автообнаружения:
nginx-discovery.py
#! /usr/bin/python2 # -*- coding: utf-8 -*- # parse json from nginx again # we need to make /another/ json for zabbix # warning: this script not send any values to Zabbiz, only upstreams and peers names. # this script doing just one thing - make json for zabbix with right format # unfortunately, json.dumps gives a slightly different format import json, re, urllib # parse json from nginx to dict data url="http://demo.nginx.com/status" response = urllib.urlopen(url) data = json.loads(response.read()) # forming json for LLD zabbix where {#UPSTREAM} - upstream's name # {#NODE_IP} - peer's IP address result="{\n\"data\":[\n" for i in sorted(data['upstreams'].keys()): ip_data = dict([[v['server'],v] for v in data['upstreams'][i]['peers']]) for j in sorted(ip_data.keys()): result = result + "{\n" result = result + "\"{#UPSTREAM}\":\""+str(i)+"\",\n" result = result + "\"{#NODE_IP}\":\""+str(j)+"\"\n" result = result + "},\n" result = re.sub("},\n$", "", result) + "}]\n}\n" print result
Чтобы мониторинг заработал, нужно сделать следующее:
- разместить скрипты в /etc/zabbix/scripts/
- добавить UserParameter в zabbix-agent
echo 'UserParameter=nginx.stat.[*],/etc/zabbix/scripts/nginx-stats.py $1 $2 $3 $4 $5 $6' > /etc/zabbix/zabbix_agentd.d/userparameter_nginx_plus.conf echo 'UserParameter=nginx.discovery,/etc/zabbix/scripts/nginx-discovery.py' >> /etc/zabbix/zabbix_agentd.d/userparameter_nginx_plus.conf - перезапустить zabbix-agent
- импортировать шаблон Zabbix
- присоединить шаблон Template App Nginx Plus к узлу сети
- проверить наличие свежих данных


На этом настройка мониторинга для Nginx Plus завершена. Использованные в статье файлы доступны в репозитарии на GitHub.
Если у вас есть вопросы, вы хотите что-то добавить или у вас что-то не заработало, напишите мне об этом (в комментариях, в Issues, или личным сообщением).
Спасибо за внимание!