В этом посте я бы хотел рассказать о том как же узнать о происходящих событиях мониторинга в 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 { ... # смотри код выше!
И в результате получим такую табличку:
