Неотъемлемой частью сетевого мониторинга является сбор логов с контролируемых серверов и прочих железок. Ведь сколько бы мы ни создали отдельных элементов данных и триггеров к ним, в какой-то момент возникнет ситуация, что что-то важное мы упустили из виду и не контролируем. Итог: «У нас ничего не работает», а система мониторинга говорит, что все хорошо.
Поэтому первое, что хотелось сделать — собирать все логи в заббиксе, сгруппировав их по узлу сети для того, чтобы всегда можно было пробежаться по сообщениям глазами, не тратя время на доступ на оборудование.
Второе — обратить внимание и на те события, о которых и не подозреваешь.
Как это сделать на серверах или компьютерах, где установлен заббикс-агент, многие знают — есть встроенные элементы данных log[], logrt[].
Но как быть, когда нужно собирать логи с сетевого оборудования, на которое никак не водрузить Zabbix-agent’а? Вообще-то можно, конечно, настроить syslog-сервер на том же ПК, на которой есть заббикс-агент, а дальше при помощи log[] переносить эти данные в заббикс. Вот только элементы данных и триггеры по нему будут прикреплены к узлу сети с заббикс-агентом, что интуитивно малопонятно. А можно ли прикрепить эти данные непосредственно к сетевому устройству? Можно.
Для этого нам понадобится zabbix_sender, Zabbix API и rsyslog на машине с заббикс-сервером или заббикс-прокси. В качестве бонуса также получим быстрый контекстный переход в журнал syslog-сообщений с карты сети.
Как будет выглядеть результат? Ну, примерно вот так:
Контекстный вызов:
How to
Большими мазками архитектура решения выглядит вот так:
1. Все логи с сетевых устройств падают на сервер с 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 и добавим в нем один единственный элемент данных:
Заполним поля следующим образом:
Поле | Значение | Примечание |
---|---|---|
Имя | Syslog | |
Тип | Zabbix траппер | |
Ключ | syslog | Важно, чтобы было именно такое имя (для дальнейшей корректной работы Zabbix API) |
Тип информации | Журнал(лог) | |
Формат времени в журнале(логе) | yyyyxMMxddxhhxmmxssxxxxxx | Маска для правильного определения даты по формату в RFC5424 |
Далее прикрепляем этот шаблон ко всем узлам сети, с которых будем собирать syslog-сообщения. Важно в интерфейсах указать те IP-адреса, с которых будут приходить логи в Заббикс. Иначе просто не получится идентифицировать источник сообщения.
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/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.cfgn";
my $user = $Config{'user'} || die "API user is missing in zabbix_syslog.cfgn";
my $password = $Config{'password'} || die "API user password is missing in zabbix_syslog.cfgn";
my $server = $Config{'server'} || die "server hostname is missing in zabbix_syslog.cfgn";
my $zabbix_sender = $Config{'zabbix_sender'} || '/usr/local/bin/zabbix_sender';
die "Problems with zabbix_sender binary: $ERRNOn"
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 argumentn"; #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 continuen";
}
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 failedn" 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 failedn" 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 failedn";
}
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 failedn";
}
#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 failedn";
}
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
С этого момента мы уже можем увидеть сообщения в заббиксе отдельно для каждого узла сети, открывая Последние данные -> нужный узел сети -> Syslog
Триггеры
Возможность чтения логов в системе без хождения по интерфейсам оборудования — это хорошо (не говоря даже о том, что как правило логи на оборудовании лежат в памяти и не переживают ребут), но давайте не забудем и про триггеры. Как и в случае других протоколов они помогут нам не проспать какое-нибудь судьбоносное сообщение на нашей сети.
У каждого оборудования и у каждого производителя оборудования сообщения свои, поэтому, как искать важное сообщение, не зная, как оно выглядит? А вот следующим образом:
Все сообщения syslog классифицируются при помощи атрибута severity, который согласно RFC5424 может принимать следующие значения:
0 Emergency: system is unusable 1 Alert: action must be taken immediately 2 Critical: critical conditions 3 Error: error conditions 4 Warning: warning conditions 5 Notice: normal but significant condition 6 Informational: informational messages 7 Debug: debug-level messages |
есть у severity не только численное, но и текстовое сокращенное обозначение, присутствующее в окончательном сообщении, которое передается в Zabbix через zabbix_sender.
Таким образом, мы можем искать те сообщения, которым сама железка (то есть ее производитель) присвоила достаточно высокую важность, и оповещать о них. Для этого в наш шаблон Template_Syslog добавим триггеры, для оповещения о всех событиях с severity=warning и выше:
Последнее, что осталось сделать — это настроить оповещение (действие) об этих новых syslog-сообщениях. В условиях укажем, что имя триггера содержит [SYSLOG], и что отправлять сообщение нужно через электронную почту.
В итоге, каждый раз, когда в syslog упадет сообщение высокой важности, мы будем получать сообщение вида:
И кстати, наш шаблон с триггерами по критичности аварий уже готов:
<?xml version="1.0" encoding="UTF-8"?>
<zabbix_export>
<version>2.0</version>
<date>2015-03-13T14:27:56Z</date>
<groups>
<group>
<name>Templates</name>
</group>
</groups>
<templates>
<template>
<template>Template_Syslog</template>
<name>Template_Syslog</name>
<description/>
<groups>
<group>
<name>Templates</name>
</group>
</groups>
<applications>
<application>
<name>Log</name>
</application>
</applications>
<items>
<item>
<name>Syslog</name>
<type>2</type>
<snmp_community/>
<multiplier>0</multiplier>
<snmp_oid/>
<key>syslog</key>
<delay>0</delay>
<history>3</history>
<trends>365</trends>
<status>0</status>
<value_type>2</value_type>
<allowed_hosts/>
<units/>
<delta>0</delta>
<snmpv3_contextname/>
<snmpv3_securityname/>
<snmpv3_securitylevel>0</snmpv3_securitylevel>
<snmpv3_authprotocol>0</snmpv3_authprotocol>
<snmpv3_authpassphrase/>
<snmpv3_privprotocol>0</snmpv3_privprotocol>
<snmpv3_privpassphrase/>
<formula>1</formula>
<delay_flex/>
<params/>
<ipmi_sensor/>
<data_type>0</data_type>
<authtype>0</authtype>
<username/>
<password/>
<publickey/>
<privatekey/>
<port/>
<description/>
<inventory_link>0</inventory_link>
<applications>
<application>
<name>Log</name>
</application>
</applications>
<valuemap/>
<logtimefmt>yyyyxMMxddxhhxmmxssxxxxxx</logtimefmt>
</item>
</items>
<discovery_rules/>
<macros/>
<templates/>
<screens/>
</template>
</templates>
<triggers>
<trigger>
<expression>({Template_Syslog:syslog.str(.alert)}=1)and({Template_Syslog:syslog.nodata(900)}=0)</expression>
<name>[SYSLOG] Alert message received</name>
<url/>
<status>0</status>
<priority>4</priority>
<description/>
<type>0</type>
<dependencies/>
</trigger>
<trigger>
<expression>({Template_Syslog:syslog.str(.crit)}=1)and({Template_Syslog:syslog.nodata(900)}=0)</expression>
<name>[SYSLOG] Critical message received</name>
<url/>
<status>0</status>
<priority>3</priority>
<description/>
<type>0</type>
<dependencies/>
</trigger>
<trigger>
<expression>({Template_Syslog:syslog.str(.emerg)}=1)and({Template_Syslog:syslog.nodata(900)}=0)</expression>
<name>[SYSLOG] Emergency message received</name>
<url/>
<status>0</status>
<priority>5</priority>
<description/>
<type>0</type>
<dependencies/>
</trigger>
<trigger>
<expression>({Template_Syslog:syslog.str(.err)}=1)and({Template_Syslog:syslog.nodata(900)}=0)</expression>
<name>[SYSLOG] Error received</name>
<url/>
<status>0</status>
<priority>2</priority>
<description/>
<type>0</type>
<dependencies/>
</trigger>
<trigger>
<expression>({Template_Syslog:syslog.str(.warning)}=1)and({Template_Syslog:syslog.nodata(900)}=0)</expression>
<name>[SYSLOG] Warning received</name>
<url/>
<status>0</status>
<priority>1</priority>
<description/>
<type>0</type>
<dependencies/>
</trigger>
</triggers>
</zabbix_export>
Конечно, не обязательно отлавливать все сообщения warning, error, critical и так далее. Это просто обобщенный вариант, который помогает не упустить что-то нештатное. Используя функции триггеров iregxp(), regxp(), str(), всегда можно фиксировать в логах более специфические события.
Автоматическое крепление к карте
Затронем еще один важный момент, который упрощает работу с syslog-сообщениями — контекстный переход с карты сети.
Можно потратить день-другой и выстрадать добавление URL-ссылок для каждого узла сети на его syslog элемент данных руками:
Но скорее руки отсохнут кликать по мышке, либо умом тронешься. Лучше вновь обратимся к Zabbix API за помощью в автоматизации сего рутинного дела:
Для этого накидаем скрипт, который будет
1) Брать все элементы карты сети
2) Для всех элементов типа узел сети проверять, нет ли у него элемента данных с key=syslog
3) Если есть, добавлять к списку существующих URL ссылку на просмотр этого элемента данных (если URL на Syslog уже есть, то ничего не делать)
Когда скрипт будет готов, мы развернем его только на Zabbix-server'е:
#!/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.cfgn";
my $user = $Config{'user'} || die "API user is missing in zabbix_syslog.cfgn";
my $password = $Config{'password'} || die "API user password is missing in zabbix_syslog.cfgn";
my $server = $Config{'server'} || die "server hostname is missing in zabbix_syslog.cfgn";
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 failedn";
}
#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 failedn" 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 failedn";
}
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 failedn" 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 failedn";
}
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 thisn:";
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 failedn";
}
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 — все эти инструменты к ваши услугам, чтобы расширить возможности системы.
Автор: wabbit