Удобный мониторинг Syslog сообщений c сетевых железок в Zabbix

Неотъемлемой частью сетевого мониторинга является сбор логов с контролируемых серверов и прочих железок. Ведь сколько бы мы ни создали отдельных элементов данных и триггеров к ним, в какой-то момент возникнет ситуация, что что-то важное мы упустили из виду и не контролируем. Итог: «У нас ничего не работает», а система мониторинга говорит, что все хорошо.Поэтому первое, что хотелось сделать — собирать все логи в заббиксе, сгруппировав их по узлу сети для того, чтобы всегда можно было пробежаться по сообщениям глазами, не тратя время на доступ на оборудование.Второе — обратить внимание и на те события, о которых и не подозреваешь.

Как это сделать на серверах или компьютерах, где установлен заббикс-агент, многие знают — есть встроенные элементы данных log[], logrt[].

Но как быть, когда нужно собирать логи с сетевого оборудования, на которое никак не водрузить Zabbix-agent«а? Вообще-то можно, конечно, настроить syslog-сервер на том же ПК, на которой есть заббикс-агент, а дальше при помощи log[] переносить эти данные в заббикс. Вот только элементы данных и триггеры по нему будут прикреплены к узлу сети с заббикс-агентом, что интуитивно малопонятно. А можно ли прикрепить эти данные непосредственно к сетевому устройству? Можно.

Для этого нам понадобится zabbix_sender, Zabbix API и rsyslog на машине с заббикс-сервером или заббикс-прокси. В качестве бонуса также получим быстрый контекстный переход в журнал syslog-сообщений с карты сети.Как будет выглядеть результат? Ну, примерно вот так: Контекстный вызов: 31e0566f578241dd92c4f17bd5147afb.png

Большими мазками архитектура решения выглядит вот так: 0ad1caf345644b77b1ec95e683466ff7.png1. Все логи с сетевых устройств падают на сервер с Zabbix сервером или прокси, на котором по совместительству расположен rsyslog.2. rsyslog запускаем скрипт, который определяет (3) с какого узла сети в Заббиксе пришло сообщение4. Сообщение уходит в заббикс через утилиту zabbix_senderНу что, начнем «прорубать» путь сообщению от сетевой железки до заббиксНа сетевом оборудовании Тут все просто. Укажите в качестве адресата для syslog-сообщений машину с Zabbix-сервером или Zabbix-proxy. Настройте оборудование на отсылку сообщений любых severity и facility.На каком-нибудь D-Link’e это может выглядеть примерно вот так:

enable syslog create syslog host 1 ipaddress 10.2.0.21 severity debug state enable А скажем на Cisco роутере вот так: cisco1# cisco1#config terminal Enter configuration commands, one per line. End with CNTL/Z. cisco1(config)#logging 10.2.0.21 cisco1(config)#service timestamps debug datetime localtime show-timezone msec cisco1(config)#service timestamps log datetime localtime show-timezone msec cisco1(config)#logging facility local3 cisco1(config)#logging trap informational cisco1(config)#end Настроили? Идем дальше.В веб-интерфейсе Заббикса Начнем с самого простого и понятного. В Zabbix«e создадим шаблон Template_Syslog и добавим в нем один единственный элемент данных: f01a271e91f941808045104f7bc12763.pngЗаполним поля следующим образом:

Поле Значение Примечание Имя Syslog Тип Zabbix траппер Ключ syslog Важно, чтобы было именно такое имя (для дальнейшей корректной работы Zabbix API) Тип информации Журнал (лог) Формат времени в журнале (логе) yyyyxMMxddxhhxmmxssxxxxxx Маска для правильного определения даты по формату в RFC5424 Далее прикрепляем этот шаблон ко всем узлам сети, с которых будем собирать syslog-сообщения. Важно в интерфейсах указать те IP-адреса, с которых будут приходить логи в Заббикс. Иначе просто не получится идентифицировать источник сообщения.c6d17771455a42d090fb69c0a0a8720d.PNG

Syslog-сервер Настроим syslog-сервер на хосте с Zabbix-сервером. В нашем случае это распостраненный rsyslog, который идет во многих дистрибутивах Linux. Если у вас syslog-ng, то там все можно сделать практически так же.В самом простом случае syslog-сервер раскладывает полученные сообщения по файлам в зависимости от facility и severity сообщений. Однако, есть и другие возможности. Например, в rsyslog существует возможность запуска произвольного скрипта для каждого сообщения. Этой функцией мы и воспользуемся.Второй вопрос, который нужно решить — идентификация оборудования, чтобы определить, в лог какого узла добавлять сообщение в Заббиксе. Его мы решим, добавив в строчку с самим сообщением ip-адрес источника в квадратных скобах.

Для всего этого создадим конфиг-файл /etc/rsyslog.d/zabbix_rsyslog.conf

#add template for network devices $template network-fmt,»%TIMESTAMP::: date-rfc3339% [%fromhost-ip%] %pri-text% %syslogtag%%msg%\n»

#exclude unwanted messages: : msg, contains, «Child connection from: ffff:10.2.0.21» ~ : msg, contains, «exit after auth (ubnt): Disconnect received» ~ : msg, contains, «password auth succeeded for 'ubnt' from: ffff:10.2.0.21» ~ : msg, contains, «exit before auth: Exited normally» ~ #action for every message: if $fromhost-ip!= '127.0.0.1' then ^/usr/local/bin/zabbix_syslog_lkp_host.pl; network-fmt & ~ Мы только что создали настройку для rsyslog, которая будет все сообщения полученные не с локального хоста форматировать определенным образом и запускать наш скрипт /usr/local/bin/zabbix_syslog_lkp_host.pl с syslog-сообщением в качестве аргумента.Заодно в разделе #exclude unwanted messages мы можем отбрасывать засоряющие логин сообщения, если они заранее известны. Пара сообщений оставлена тут в качестве примера.

Под конец настройки rsyslog не забудьте еще раскомментировать следующие строки в файле /etc/rsyslog.conf для приема Syslog-сообщений по сети через UDP.:

$ModLoad imudp $UDPServerRun 514 И все же, что делает скрипт /usr/local/bin/zabbix_syslog_lkp_host.pl, который мы указали запускать rsyslog’у? Если вкратце, он просто через zabbix_sender шлет данное сообщение на Zabbix_server или на Zabbix_proxy, ну вот примерно по такому шаблону:

/usr/bin/zabbix_sender -z *ИМЯСЕРВЕРА* -k syslog -o *SYSLOG-СООБЩЕНИЕ* -s *ИМЯУЗЛА* Но откуда скрипту знать, какое будет *ИМЯУЗЛА* (т.е. к какому узлу крепить сообщение), ведь известен только IP-адрес, с которого пришло сообщение? Для этого мы будем использовать Zabbix API, именно через него мы и сможем найти *ИМЯУЗЛА* по IP-адресу./usr/local/bin/zabbix_syslog_lkp_host.pl #!/usr/bin/perl

use 5.010; use strict; use warnings; use JSON: RPC: Legacy: Client; use Data: Dumper; use Config: General; use CHI; use List: MoreUtils qw (any); use English '-no_match_vars'; use Readonly; our $VERSION = 1.1;

Readonly my $CACHE_TIMEOUT => 600; Readonly my $CACHE_DIR => '/tmp/zabbix_syslog_cache';

my $conf = Config: General→new ('/usr/local/etc/zabbix_syslog.cfg'); my %Config = $conf→getall;

#Authenticate yourself my $client = JSON: RPC: Legacy: Client→new (); my $url = $Config{'url'} || die «URL is missing in zabbix_syslog.cfg\n»; my $user = $Config{'user'} || die «API user is missing in zabbix_syslog.cfg\n»; my $password = $Config{'password'} || die «API user password is missing in zabbix_syslog.cfg\n»; my $server = $Config{'server'} || die «server hostname is missing in zabbix_syslog.cfg\n»; my $zabbix_sender = $Config{'zabbix_sender'} || '/usr/local/bin/zabbix_sender';

die «Problems with zabbix_sender binary: $ERRNO\n» unless -e -x $zabbix_sender; #check zabbix_sender exists and is executable

my $debug = $Config{'debug'}; my ($authID, $response, $json); my $id = 0;

my $message = shift @ARGV || die «Syslog message required as an argument\n»; #Grab syslog message from rsyslog

#get ip from message my $ip;

#IP regex patter part my $ipv4_octet = q/(?:25[0–5]|2[0–4][0–9]|[01]?[0–9][0–9]?)/;

if ($message =~ / \[ ((?:$ipv4_octet[.]){3}${ipv4_octet}) \]/msx) { $ip = $1; } else { die «No IP in square brackets found in '$message', cannot continue\n»; }

my $cache = CHI→new ( driver => 'File', root_dir => $CACHE_DIR, );

my $hostname = $cache→get ($ip);

if (! defined $hostname) {

$authID = login (); my @hosts_found; my $hostid; foreach my $host (hostinterface_get ()) {

$hostid = $host→{'hostid'};

if (any { /$hostid/msx } @hosts_found) { next; } #check if $hostid already is in array then skip (next) else { push @hosts_found, $hostid; }

###########now get hostname if (get_zbx_trapper_syslogid_by_hostid ($hostid)) {

my $result = host_get ($hostid);

#return hostname if possible if ($result→{'host'}) {

if ($result→{'proxy_hostid'} == 0) #check if host monitored directly or via proxy { #lease $server as is } else { #assume that rsyslogd and zabbix_proxy are on the same server $server = 'localhost'; } $hostname = $result→{'host'}; }

}

} logout (); $cache→set ($ip, $hostname, $CACHE_TIMEOUT); }

system $zabbix_sender. ' -z ' . $server . ' -k syslog -o \'' . $message . '\' -s ' . $hostname; #run zabbix_sender

#______SUBS sub login {

$json = { jsonrpc => '2.0', method => 'user.login', params => { user => $user, password => $password

}, id => $id++, };

$response = $client→call ($url, $json);

# Check if response was successful die «Authentication failed\n» unless $response→content→{'result'};

if ($debug > 0) { print Dumper $response→content→{'result'}; }

return $response→content→{'result'};

}

sub logout {

$json = { jsonrpc => '2.0', method => 'user.logout', params => {}, id => $id++, auth => $authID, };

$response = $client→call ($url, $json);

# Check if response was successful warn «Logout failed\n» unless $response→content→{'result'};

return; }

sub hostinterface_get {

$json = {

jsonrpc => '2.0', method => 'hostinterface.get', params => { output => [ 'ip', 'hostid' ], filter => { ip => $ip, },

# limit => 1, }, id => $id++, auth => $authID, };

$response = $client→call ($url, $json);

if ($debug > 0) { print Dumper $response; }

# Check if response was successful (not empty array in result) if (!@{ $response→content→{'result'} }) { logout (); die «hostinterface.get failed\n»; }

return @{ $response→content→{'result'} }

}

sub get_zbx_trapper_syslogid_by_hostid {

my $hostids = shift;

$json = { jsonrpc => '2.0', method => 'item.get', params => { output => ['itemid'], hostids => $hostids, search => { 'key_' => 'syslog', type => 2, #type => 2 is zabbix_trapper status => 0,

}, limit => 1, }, id => $id++, auth => $authID, };

$response = $client→call ($url, $json); if ($debug > 0) { print Dumper $response; }

# Check if response was successful if (!@{ $response→content→{'result'} }) { logout (); die «item.get failed\n»; }

#return itemid of syslog key (trapper type) return ${ $response→content→{'result'} }[0]→{itemid}; }

sub host_get { my $hostids = shift;

$json = {

jsonrpc => '2.0', method => 'host.get', params => { hostids => [$hostids], output => [ 'host', 'proxy_hostid', 'status' ], filter => { status => 0, }, # only use hosts enabled limit => 1, }, id => $id++, auth => $authID, };

$response = $client→call ($url, $json);

if ($debug > 0) { print Dumper $response; }

# Check if response was successful if (!$response→content→{'result'}) { logout (); die «host.get failed\n»; } return ${ $response→content→{'result'} }[0]; #return result }

Копируем скрипт на сервер по пути /usr/local/bin/zabbix_syslog_lkp_host.pl, также создаем конфигурационный файл/usr/local/etc/zabbix_syslog.cfg с параметрами подключения к Заббиксу через API. Конфиг будет выглядеть примерно вот так:

url = http://zabbix.local/zabbix/api_jsonrpc.php user = api_user password = password server = zabbix.local debug=0 zabbix_sender= /usr/bin/zabbix_sender Скрипт использует несколько модулей Perl из CPAN, чтобы установить их выполните команды: PERL_MM_USE_DEFAULT=1 perl -MCPAN -e 'install Readonly' PERL_MM_USE_DEFAULT=1 perl -MCPAN -e 'install CHI' PERL_MM_USE_DEFAULT=1 perl -MCPAN -e 'install JSON: RPC: Legacy: Client' PERL_MM_USE_DEFAULT=1 perl -MCPAN -e 'install Config: General' Также настраиваем права на эти наши новые файлы: chmod +x /usr/local/bin/zabbix_syslog_lkp_host.pl chown zabbix: zabbix /usr/local/etc/zabbix_syslog.cfg chmod 700 /usr/local/etc/zabbix_syslog.cfg Все готово для отправки сообщений в Заббикс, осталось только перезагрузить rsyslog: service rsyslog restart С этого момента мы уже можем увидеть сообщения в заббиксе отдельно для каждого узла сети, открывая Последние данные → нужный узел сети → Syslog39b92328f1964e0abb609faca2c3e724.png

Триггеры Возможность чтения логов в системе без хождения по интерфейсам оборудования — это хорошо (не говоря даже о том, что как правило логи на оборудовании лежат в памяти и не переживают ребут), но давайте не забудем и про триггеры. Как и в случае других протоколов они помогут нам не проспать какое-нибудь судьбоносное сообщение на нашей сети.У каждого оборудования и у каждого производителя оборудования сообщения свои, поэтому, как искать важное сообщение, не зная, как оно выглядит? А вот следующим образом: Все сообщения syslog классифицируются при помощи атрибута severity, который согласно RFC5424 может принимать следующие значения:

0 Emergency: system is unusable1 Alert: action must be taken immediately2 Critical: critical conditions3 Error: error conditions4 Warning: warning conditions5 Notice: normal but significant condition6 Informational: informational messages7 Debug: debug-level messages есть у severity не только численное, но и текстовое сокращенное обозначение, присутствующее в окончательном сообщении, которое передается в Zabbix через zabbix_sender.Таким образом, мы можем искать те сообщения, которым сама железка (то есть ее производитель) присвоила достаточно высокую важность, и оповещать о них. Для этого в наш шаблон Template_Syslog добавим триггеры, для оповещения о всех событиях с severity=warning и выше: a9a53705f5124d9b896ff6ebae31cc97.png

Последнее, что осталось сделать — это настроить оповещение (действие) об этих новых syslog-сообщениях. В условиях укажем, что имя триггера содержит [SYSLOG], и что отправлять сообщение нужно через электронную почту.

55a0c28c8d6842819e503491be940dbc.png

3f0bddf86bae493cbd7ab0f2c38823c9.png

b3fb8ea46cc2482fbd5f333d4702f0a7.png

В итоге, каждый раз, когда в syslog упадет сообщение высокой важности, мы будем получать сообщение вида: 1fa47f02a8aa44dcb8767637dde74fb2.pngИ кстати, наш шаблон с триггерами по критичности аварий уже готов:

Template_Syslog 2.0 2015–03–13T14:27:56Z Templates ({Template_Syslog: syslog.str (.alert)}=1)and ({Template_Syslog: syslog.nodata (900)}=0) [SYSLOG] Alert message received 0 4 0 ({Template_Syslog: syslog.str (.crit)}=1)and ({Template_Syslog: syslog.nodata (900)}=0) [SYSLOG] Critical message received 0 3 0 ({Template_Syslog: syslog.str (.emerg)}=1)and ({Template_Syslog: syslog.nodata (900)}=0) [SYSLOG] Emergency message received 0 5 0 ({Template_Syslog: syslog.str (.err)}=1)and ({Template_Syslog: syslog.nodata (900)}=0) [SYSLOG] Error received 0 2 0 ({Template_Syslog: syslog.str (.warning)}=1)and ({Template_Syslog: syslog.nodata (900)}=0) [SYSLOG] Warning received 0 1 0

Конечно, не обязательно отлавливать все сообщения warning, error, critical и так далее. Это просто обобщенный вариант, который помогает не упустить что-то нештатное. Используя функции триггеров iregxp (), regxp (), str (), всегда можно фиксировать в логах более специфические события.

Автоматическое крепление к карте Затронем еще один важный момент, который упрощает работу с syslog-сообщениями — контекстный переход с карты сети.34be30aa4fd14a4bb97b54e039fc0673.gifМожно потратить день-другой и выстрадать добавление URL-ссылок для каждого узла сети на его syslog элемент данных руками: 4fb481daa8e1463fbad2beee24ae37d7.png

Но скорее руки отсохнут кликать по мышке, либо умом тронешься. Лучше вновь обратимся к Zabbix API за помощью в автоматизации сего рутинного дела: Для этого накидаем скрипт, который будет1) Брать все элементы карты сети2) Для всех элементов типа узел сети проверять, нет ли у него элемента данных с key=syslog3) Если есть, добавлять к списку существующих URL ссылку на просмотр этого элемента данных (если URL на Syslog уже есть, то ничего не делать)Когда скрипт будет готов, мы развернем его только на Zabbix-server’е:

/usr/local/bin/zabbix_syslog_create_urls.pl #!/usr/bin/perl #fixed URL for ZBX 2.4

use 5.010; use strict; use warnings; use JSON: RPC: Legacy: Client; use Data: Dumper; use Config: General; our $VERSION = 1.1; my $conf = Config: General→new ('/usr/local/etc/zabbix_syslog.cfg'); my %Config = $conf→getall;

#Authenticate yourself my $client = JSON: RPC: Legacy: Client→new (); my $url = $Config{'url'} || die «URL is missing in zabbix_syslog.cfg\n»; my $user = $Config{'user'} || die «API user is missing in zabbix_syslog.cfg\n»; my $password = $Config{'password'} || die «API user password is missing in zabbix_syslog.cfg\n»; my $server = $Config{'server'} || die «server hostname is missing in zabbix_syslog.cfg\n»;

my $debug = $Config{'debug'}; my ($authID, $response, $json); my $id = 0;

$authID = login ();

my $syslog_url_base = 'history.php? action=showvalues';

my @selements;

foreach my $map (@{ map_get_extended () }) { my $mapid=$map→{sysmapid}; #next unless ($mapid == 120 or $mapid == 116); #debug #put all mapelements into array @selements (so you can update map later!) @selements = @{ $map→{selements} };

foreach my $selement (@selements) { my $syslog_button_exists = 0;

if ($debug > 0) { print 'Object ID: ' . $selement→{selementid} . ' Type: ' . $selement→{elementtype} . ' Elementid ' . $selement→{elementid} .» \n»; }

# elementtype=0 hosts if ($selement→{elementtype} == 0) {

my $hostid = $selement→{elementid};

my $itemid = get_syslogid_by_hostid ($hostid); if ($itemid) {

#and add urls:

my $syslog_exists = 0; foreach my $syslog_url (@{ $selement→{urls} }) { $syslog_exists = 0;

if ($syslog_url→{name} =~ 'Syslog') {

$syslog_exists = 1; $syslog_url→{'name'} = 'Syslog';

$syslog_url→{'url'} = $syslog_url_base . '&itemids[' . $itemid. ']=' . $itemid; } } if ($syslog_exists == 0) {

#syslog item doesn’t exist… add it push @{ $selement→{urls} }, { 'name' => 'Syslog', 'url' => $syslog_url_base . '&itemids[' . $itemid. ']=' . $itemid }; }

}

}

} map_update ($mapid,\@selements); }

logout ();

#______SUBS sub get_syslogid_by_hostid { my $hostids = shift;

$json = { jsonrpc => '2.0', method => 'item.get', params => { output => ['itemid'], hostids => $hostids, search => { 'key_' => 'syslog' }, limit => 1, }, id => $id++, auth => $authID, };

$response = $client→call ($url, $json);

# Check if response was successful if (!$response→content→{'result'}) { logout (); die «item.get failed\n»; }

#return itemid of syslog key (trapper type) return ${ $response→content→{'result'} }[0]→{itemid}; }

sub login {

$json = { jsonrpc => '2.0', method => 'user.login', params => { user => $user, password => $password

}, id => $id++, };

$response = $client→call ($url, $json);

# Check if response was successful die «Authentication failed\n» unless $response→content→{'result'};

if ($debug > 0) { print Dumper $response→content→{'result'}; }

return $response→content→{'result'};

}

sub map_get {

#retrieve all maps $json = { jsonrpc => '2.0', method => 'map.get', params => { output => ['sysmapid'] }, id => $id++, auth => »$authID», };

$response = $client→call ($url, $json);

# Check if response was successful if (!$response→content→{'result'}) { logout (); die «map.get failed\n»; }

if ($debug > 1) { print Dumper $response→content→{result}; } return $response→content→{result};

}

sub logout {

$json = { jsonrpc => '2.0', method => 'user.logout', params => {}, id => $id++, auth => $authID, };

$response = $client→call ($url, $json);

# Check if response was successful warn «Logout failed\n» unless $response→content→{'result'};

return; }

sub map_get_extended { $json = { jsonrpc => '2.0', method => 'map.get', params => { selectSelements => 'extend', #sysmapids => $map, }, id => $id++, auth => $authID, };

$response = $client→call ($url, $json);

# Check if response was successful if (!$response→content→{'result'}) { logout (); die «map.get failed\n»; } if ($debug > 1) {

print Dumper $response→content→{'result'}; }

return $response→content→{'result'}; }

sub map_update { my $mapid = shift; my $selements_ref = shift; $json = { jsonrpc => '2.0', method => 'map.update', params => { selements => [@{$selements_ref}], sysmapid => $mapid, }, id => $id++, auth => $authID, };

if ($debug > 0) { print «About to map.update this\n:»; print Dumper $json; }

$response = $client→call ($url, $json);

if ($debug > 0) { print Dumper $response; }

# Check if response was successful if (!$response→content→{'result'}) { logout (); die «map.update failed\n»; } return; } И сразу добавим скрипт в cron (лучше всего под пользователем zabbix) на машине с Zabbix Server, одного раза в сутки может оказаться вполне достаточно. * 1 * * * /usr/local/bin/zabbix_syslog_create_urls.pl Также не забудем сделать файл исполняемым: chmod +x /usr/local/bin/zabbix_syslog_create_urls.pl Готово! Заббикс много чего умеет «из коробки». Однако, если нет того, что нужно Вам — отчаиваться рано. Zabbix API, а также zabbix_sender, подключаемые модули, UserParameter — все эти инструменты к ваши услугам, чтобы расширить возможности системы.

© Habrahabr.ru