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

Live status information from NGINX Plus

Как видно на иллюстрации, у 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()
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


Чтобы мониторинг заработал, нужно сделать следующее:

  1. разместить скрипты в /etc/zabbix/scripts/
  2. добавить 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
  3. перезапустить zabbix-agent
  4. импортировать шаблон Zabbix
  5. присоединить шаблон Template App Nginx Plus к узлу сети
  6. проверить наличие свежих данных





На этом настройка мониторинга для Nginx Plus завершена. Использованные в статье файлы доступны в репозитарии на GitHub.

Если у вас есть вопросы, вы хотите что-то добавить или у вас что-то не заработало, напишите мне об этом (в комментариях, в Issues, или личным сообщением).

Спасибо за внимание!
Поделиться с друзьями
-->

Комментарии (2)


  1. mcleod095
    15.06.2017 13:44

    не приходилось работать с nginx plus
    но тоже сделал мониторинг nginx через zabbix но немного по другому
    https://github.com/McLeod095/ZabbixMon/tree/master/nginx_vts


    1. StraNNicK
      15.06.2017 13:49

      Интересный вариант, надо будет посмотреть подробности про vts