В этом посте я бы хотел рассказать о том как же узнать о происходящих событиях мониторинга в Munin. Можно конечно держать открытым веб-интерфейс и постоянно наслаждаться красивыми графиками, а заодно и оперативно видеть возникающие события. Но есть способ лучше: получить от Munin нотификацию.
События в Munin.
Для начала, давайте посмотрим как в munin возникают события:
— Сервер собирает со своих нод сведения о всех сенсорах, имеющихся на них;
— Полученные для каждой метрики мгновенные значения сравниваются с заданными порогами;
— Если это значение выходит за допустимые пределы, то для соответствующей метрики возникает событие.
Как мы с Вами уже знаем в Munin есть два интересных нам события — превышение «предупреждающего» и «критического» порогов. На самом деле нам будет интересно и еще одно состояние метрики — «unknown», которое как бы намекает нам о том что реальное состояние неизвестно (а значит может быть и критическим). Таким образом, по критичности можно разделить события на три типа: «critical», «warning» и «unknown».
Пороговые значения.
Пороги определяют границы, при выходе за которые генерируется то или иное событие и бывают двух типов, соответствующих критичности возникающего события — «critical» и «warning». Пороги могут определять как верхнюю, так и нижнюю границы, то есть, всего существует 4 границы, по две для каждого состояния. При этом порог «critical» имеет более высокий приоритет, то есть если значение выходит за границы для обоих состояний, то используется именно он.
Пороги могут устанавливаться как в самом плагине, так и в конфигурационном файле и задаются в следующем формате: <нижний порог>:<верхний порог> . В этом примере, для ноды my_pc, задаются пороги для двух метрик (channel1 и channel2) плагина myplagin:
[my_pc] myplugin.channel1.warning 20:80 myplugin.channel1.critical 10:90 myplugin.channel2.warning 5:5 myplugin.channel2.critical 4:6
Разберем что же это значит:
— channel1 переходит в «warning», если его значение МЕНЬШЕ 20 или БОЛЬШЕ 80;
— channel1 переходит в «critical», если его значение МЕНЬШЕ 10 или БОЛЬШЕ 90;
— channel2 переходит в «warning», если его значение НЕ РАВНО 5;
— channel2 переходит в «critical», если его значение ОТЛИЧАЕТСЯ ОТ 5 БОЛЬШЕ ЧЕМ НА 1 (т. е. МЕНЬШЕ 4 или БОЛЬШЕ 6);
Обработка событий.
У нас нас произошло событие для какой-то метрики и что же дальше? А дальше происходит следующее: график этой метрики теперь отображается в соответствующей группе раздела «problems» и его граница окрашена в желтый или красный цвет. То есть мы сразу видим существующие проблемы.
Но самое главное — выполняется действие определенное в конфигурационном файле Munin. Официальная документация рассказывает нам следующее:
Munin при наступлении события может уведомить нас двумя способами:
— самостоятельно запустить определенную команду;
— передать информацию о событии в Nagios.
Вот пример как в конфигурации можно определить какую команду запускать:
# формат очень простой: contact.<название>.command <команда> [параметры] contact.someuser.command mail -s "Munin notification" somejuser@fnord.com
В принципе этого уже достаточно для отправки нотификации, но мы же наверняка захотим большего. Поэтому почитав документацию еще чуть-чуть, мы узнаем что текст этой нотификации передается команде через стандартный поток ввода. А еще, текст можно изменить задав его в директиве contact.<название>.text и при этом, можно не только использовать переменные Munin, но и использовать модуль perl — Text::Balanced!!! Конечно мы с Вами не сможем удержаться и не использовать такие богатые возможности :).
Передача события в формате JSON.
Да да, именно JSON, ведь всегда очень хорошо если решение задачи будет максимально простым и универсальным. Так как этот пост, к сожалению, не о perl, то я сразу представлю решение, без введения в этот замечательный язык программирования. Тем более, что используя три абзаца документации и наше решение Вы сможете легко разобраться и сами. Итак:
contact.json.text { \ "group": "${var:group}", \ "host": "${var:host}", \ "graph_category": "${var:graph_category}", \ "graph_title": "${var:graph_title}", \ "critical": [ ${loop<,>:cfields {"label": "${var:label}", "value": "${var:value}", "wlimits": "${var:wrange}", "climits": "${var:crange}", "extra": "${var:extinfo}"} }], \ "warning": [ ${loop<,>:wfields {"label": "${var:label}", "value": "${var:value}", "wlimits": "${var:wrange}", "climits": "${var:crange}", "extra": "${var:extinfo}"} }], \ "unknown": [ ${loop<,>:ufields {"label": "${var:label}", "value": "${var:value}", "wlimits": "${var:wrange}", "climits": "${var:crange}", "extra": "${var:extinfo}"} }], \ "ok": [ ${loop<,>:ofields {"label": "${var:label}", "value": "${var:value}", "wlimits": "${var:wrange}", "climits": "${var:crange}", "extra": "${var:extinfo}"} }] \ }
С таким кодом, для картинки выше, будет следующий вывод (обратите внимание на секцию «critical»):
{ "group": "virtual_machines", "host": "phy1mo1", "graph_category": "Process info", "graph_title": "Process count", "ok": [ { "label": "upsmon", "value": "2.00", "wlimits": ":", "climits": "1:", "extra": ""} ,{ "label": "upsd", "value": "1.00", "wlimits": ":", "climits": "1:", "extra": ""} ], "unknown": [ ], "warning": [ ], "critical": [ { "label": "powercom", "value": "1.00", "wlimits": ":", "climits": "2:", "extra": ""} ] }
Все что осталось это создать скрипт который будет разбирать этот JSON и, например, писать нам красивое письмо!
Скрипт для отправки письма.
Давайте в этот раз воспользуемся… ruby! И даже не спрашивайте почему — просто так. Вот ловите:
#!/usr/bin/env ruby # Наверно не у всех есть свой почтовый сервер, поэтому давайте воспользуемся gmail user = "MUNINMAIL@gmail.com" # аккаунт от которого будем посылать нотификацию password = "MUNINPASSWORD" # пароль для него recipient = "ME@gmail.com" # получатель нотификации require 'json' require 'tlsmail' require 'net/smtp' severity = { "critical" => 0, "warning" => 0, "unknown" => 0, "ok" => 0 } # Получаем аргументы (первый аргумент у нас - имя самого скрипта) group = ARGV[1] # группа в которую входит компьютер на котором произошло событие host = ARGV[2] # и его имя category = ARGV[3] # категория монитора title = ARGV[4] # и его название sensor = JSON.parse(STDIN.read) # текст нотификации (это наш JSON) # Определим какие критичности у нас есть, чтоб указать наивысшую в теме письма severity.each_pair do |sev, val| if !sensor[sev].empty? severity[sev] = 1 end end # Составим заголовок для HTML письма message = <<END From: Munin monitoring <muninmail@gmail.com> To: Me <me@gmail.com> MIME-Version: 1.0 Content-type: text/html END # Добавим тему message += "Subject: #{host}:#{title} #{severity.key(1)}\n" # И вот само тело письма message += <<END <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=us-ascii"> <style> body{font-family:Courier New;} table{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse; width: 100%;} th{text-align: left; font-size:10pt; border-width: 1px;padding: 4px;border-style: dotted;border-color: black;background-color:#DCDCDC;} th.critical{background-color: #ff0000;} th.warning{background-color: #00ffff;} th.unknown{background-color: #aaaaaa;} td{font-size:10pt; border-width: 1px;padding: 4px;border-style: dotted;border-color: black;} p{font-size:10pt; font-weight:bold; font-family:Courier New;} </style> </head> <body><table> <tr><th>Severity</th><th>Channel</th><th>Value</th><th>Limit</th><th>Additional information</th></tr> END # Пробежимся по JSON преданному munin и сделаем из него красивую HTML табличку states = (sensor.find_all {|k,v| v.is_a?(Array)}).to_h severity.each_pair do |sev, exists| if !states[sev].empty? states[sev].each do |chan| message += "<tr>\n <td class=\"#{sev}\"><b>#{sev}</b></td>\n <td>#{chan['label']}</td>\n <td>#{chan['value']}</td>\n <td>#{chan[(sev[0,1]+"limits")]}</td>\n <td>#{chan['extra']}</td>\n</tr>\n" end end end # Конец тела письма message += "</table></body>\n</html>" # И наконец отправляем готовое письмо через gmail Net::SMTP.enable_tls(OpenSSL::SSL::VERIFY_NONE) Net::SMTP.start("smtp.gmail.com", 587, "gmail.com", user, password, :plain) do |smtp| smtp.send_message message, user, recipient end
Не забываем сделать скрипт исполняемым, пропишем в munin.conf что будем запускать его и с каким текстом:
contact.json.command | /home/munin/bin/send_mail.rb /home/munin/bin/send_mail.rb "${var:group}" "${var:host}" "${var:graph_category}" "${var:graph_title}" contact.json.text { ... # смотри код выше!
И в результате получим такую табличку: