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

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

Поэтому первое, что хотелось сделать — собирать все логи в заббиксе, сгруппировав их по узлу сети для того, чтобы всегда можно было пробежаться по сообщениям глазами, не тратя время на доступ на оборудование.
Второе — обратить внимание и на те события, о которых и не подозреваешь.

Как это сделать на серверах или компьютерах, где установлен заббикс-агент, многие знают — есть встроенные элементы данных 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 *ИМЯУЗЛА*

ОТРЕДАКТИРОВАНО: А на самом деле запускать стандартную утилиту zabbix_sender вовсе не обязательно. Ее функциональность можно реализовать и внутри самого скрипта, чтобы не дергать каждый раз /usr/bin/zabbix_sender и оптимизировать процесс. Спасибо за важное дополнение mcleod095!

Но откуда скрипту знать, какое будет *ИМЯУЗЛА* (т.е. к какому узлу крепить сообщение), ведь известен только 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;
use MIME::Base64 qw(encode_base64);
use IO::Socket::INET;
our $VERSION = 2.0;

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 $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 );
}

zabbix_send( $server, $hostname, 'syslog', $message );

#______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
}

sub zabbix_send {
    my $zabbixserver = shift;
    my $hostname     = shift;
    my $item         = shift;
    my $data         = shift;
    Readonly my $SOCK_TIMEOUT     => 10;
    Readonly my $SOCK_RECV_LENGTH => 1024;

    my $result;

    my $request =
      sprintf
      "<req>\n<host>%s</host>\n<key>%s</key>\n<data>%s</data>\n</req>\n",
      encode_base64($hostname), encode_base64($item), encode_base64($data);

    my $sock = IO::Socket::INET->new(
        PeerAddr => $zabbixserver,
        PeerPort => '10051',
        Proto    => 'tcp',
        Timeout  => $SOCK_TIMEOUT
    );

    die "Could not create socket: $ERRNO\n" unless $sock;
    $sock->send($request);
    my @handles = IO::Select->new($sock)->can_read($SOCK_TIMEOUT);
    if ( $debug > 0 ) { print "item - $item, data - $data\n"; }

    if ( scalar(@handles) > 0 ) {
        $sock->recv( $result, $SOCK_RECV_LENGTH );
        if ( $debug > 0 ) {
            print "answer from zabbix server $zabbixserver: $result\n";
        }
    }
    else {
        if ( $debug > 0 ) { print "no answer from zabbix server\n"; }
    }
    $sock->close();
    return;
}





Копируем скрипт на сервер по пути /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

Скрипт использует несколько модулей 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 упадет сообщение высокой важности, мы будем получать сообщение вида:

И кстати, наш шаблон с триггерами по критичности аварий уже готов:
Template_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/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 — все эти инструменты к ваши услугам, чтобы расширить возможности системы.
Zabbix
58.45
Company
Share post

Comments 23

    +4
    Т.е. вы предлагаете на каждый syslog-пакет форкать внешний обработчик? Хлипковато как-то выглядит.
      0
      В смысле что у меня несколько гигабайт логов в день копятся, там сообщений мнооого…
        +3
        Самое смешное то, что он не просто форкается и что-то шлет, он еще дергает Zabbix-API на каждый пакет.
        При нормальной нагрузке это отличный способ убить сервер Заббикса.
        Хотя, при нормальной нагрузке хранить сообщения в Postgres/MySQL базе Zabbix это уже плохая идея.

        Если бы эта статья была не в блоге компании Zabbix, я бы посоветовал автору не мучаться, и использовать Graylog2/Logstash + Elasticsearch.
          0
          Да, все верно насчет форка, zabbix_sender'а, конечно это не лучший вариант с точки зрения производительности, и в какой-то момент может забуксовать.
          В других случаях — это возможность работать с логами в той же консоли, где и остальной мониторинг (если используется Zabbix)
          Zabbix API дергается только в случае нового хоста, а в других случаях берется их кэша.
            0
            Zabbix API используется только в том случае, если узел сети не кеширован. В любом случае повторное использование session id избавило бы от необходимости постоянно авторизоваться.
          0
          Конечно, тут есть что оптимизировать. Лучшим вариантом было бы накапливать данные и отправлять их одним махом на Zabbix сервер используя прямое TCP соединение без участия zabbix_sender.
            0
            Именно это и позволяет делать zbxlog — www.alixen.org/projects/zbxlog
            Никаких форков — получил с сети syslog по UDP отправил заббикс серверу правильный пакет по TCP.

            Данные о связках IP<->hostname он берет напрямую из БД и к тому же, насколько я помню, кеширует.
            Обсуждение в www.zabbix.com/forum/showthread.php?t=19180
          0
          Представил себе это в нашей сети с сотнями сетевых железок и серверов… Ужаснулся.
          Тут впору собирать под Zabbix кластер на блейд-серверах и бездонное хранилище впридачу =)
            0
            Бездонное хранилище не нужно. Достаточно ограничить время хранение логов в заббиксе в элементе данных чтобы срезать неактуальное.
            Описанный вариант используется на инсталляции в 500 хостов в течение двух лет без каких либо проблем. Сервер стоит на IBM x3250M3 + два заббикс прокси на порезанных по памяти виртуалках.
              0
              Ну, у нас сильно больше 500 хостов.
              А какая версия Zabbix у вас используется, и какая база данных? Partitioning используете?
                +1
                Zabbix 2.4 + MySQL, Partitioning да, настроено.
                0
                «Срезать неактуальное» через время хранения в элементе данных возможно, только если включен housekeeper. На больших объёмах данных его рекомендуют выключать, поэтому эти логи у вас будут занимать место до тех пор, пока жива соответствующая партиция таблицы history_log. Как вариант, конечно, можно уменьшить время хранения партиций для history_log, оставив остальные таблицы как есть.
              0
              К слову:
              У функций log, logrt есть параметр «output»:
              log[«file»,«regexp»,«encoding»,«maxlines»,«mode»,«output»]
              logrt[file_regexp,«regexp»,«encoding»,«maxlines»,«mode»,«output»]

              У очень похожей функции eventlog данного параметра нет:
              eventlog[name,«regexp»,«severity»,«source»,«eventid»,«maxlines»,«mode»]

              Это очень затрудняет аккуратную настройку мониторинга виндового эвентлога. Подозреваю, что прикрутить к ней «output» совсем несложно, при наличии его уже реализованного в log, logrt.

              Пытался поднять тему на форуме — www.zabbix.com/forum/showthread.php?t=48191. Тишина…
                0
                Вроде все нормально!
                конечно у себя такое внедрять не буду, уж очень много данных и не все нужные.
                Но хотел бы сделать пару замечаний.
                1. писать скрипт не perl и потом из него дергать zabbix_sender, мне кажется не совсем правильным.
                В свое время на просторах интернета нашел вот такое

                sub zabbix_send{
                        my $zabbixserver = "";
                        my $hostname = "";
                        my $item = "";
                        my $data = "";
                        ($zabbixserver,$hostname,$item,$data) = @_;
                        my $timeout=10;
                        my $result;
                
                        my $request=sprintf("<req>\n<host>%s</host>\n<key>%s</key>\n<data>%s</data>\n</req>\n",encode_base64($hostname),encode_base64($item),encode_base64($data));
                
                        my $sock = new IO::Socket::INET ( PeerAddr => $zabbixserver, PeerPort => '10051', Proto => 'tcp', Timeout => $timeout);
                        die "Could not create socket: $!\n" unless $sock;
                        $sock->send($request);
                        my @handles=IO::Select->new($sock)->can_read($timeout);
                #       print "item - $item, data - $data\n";
                        if (scalar(@handles) > 0)
                        {
                         $sock->recv($result,1024);
                #        print "answer from zabbix server $zabbixserver: $result\n";
                        }
                #       else
                #       {
                #        print "no answer from zabbix server\n";
                #       }
                        $sock->close();
                }
                


                использовать как
                zabbix_send $zabbixserver, $hostname, $key, $value;
                


                2. Хочется все таки более гибкого скрипта. Что бы при новой инсталляции не лезть в нутрь и править параметры. в своих скриптах использовал такую конструкцию
                my $_hostname;
                my $zabbix_conf = "/etc/zabbix/zabbix_agentd.conf";
                my $zabbixhostnameitem;
                
                open my $FH, $zabbix_conf or die "Cannot open file $zabbix_conf ($!)";
                while(<$FH>){
                        if(/^Hostname=(.*)$/){
                                $_hostname = $1;
                        }
                        if(/^HostnameItem=system.hostname$/){
                                $zabbixhostnameitem = hostname();
                        }
                }
                close $FH;
                
                if(defined($zabbixhostnameitem) and !defined($_hostname)){
                        $_hostname = $zabbixhostnameitem;
                }
                


                Конечно это при условии что установлен zabbix_agent и настроен, конечно здесь только имя хоста определяется, но думаю мысль понятна.

                  0
                  Спасибо большое за важное дополнение! Протестировал и добавил функцию zabbix_send для отсылки через socket в скрипт. В статью тоже внес эти поправки.
                  Теперь /usr/bin/zabbix_sender не используется.

                  От конфига, к сожалению, труднее отказаться, так как там прописаны логин/пароль для API, которых нет в конфиге zabbix_agent…
                    0
                    Сегодня столкнулся с тем что надо было используя zabbix_sender послать не только данные но и время в которое были данные получены. Скрипт обработчик был написан на perl, поэтому использовал функцию. Ну и как положено она не заработала после небольшого дополнения.
                    В итоге после прочтения документации www.zabbix.org/wiki/Docs/protocols/zabbix_sender/2.0
                    Функция приняла такой вид
                    sub zabbix_send{
                            my ($zabbixserver,$hostname,$item,$data, $timestamp) = @_;
                            my $timeout=10;
                            my $result;
                    
                            my $request = '{"request":"sender data","data":[{"value":"' . $data . '","host":"' . $hostname . '","key":"' . $item . '"';
                            $request .= ',"clock":"' . $timestamp . '"' if defined $timestamp;
                            $request .= '}]}';
                    
                            my $sock = new IO::Socket::INET ( PeerAddr => $zabbixserver, PeerPort => '10051', Proto => 'tcp', Timeout => $timeout);
                            die "Could not create socket: $!\n" unless $sock;
                            $sock->send($request);
                            my @handles=IO::Select->new($sock)->can_read($timeout);
                            if (scalar(@handles) > 0){
                                    $sock->recv($result,1024);
                            }
                            $sock->close();
                    }
                    
                  0
                  Ребят, помогите переделать конфиг для syslog-ng, что-то навык слабоват.
                    0
                    Настроил сам:
                    template t_msgfmt {
                        template("${ISODATE} [${HOST}] ${FACILITY} ${MSGHDR}${MSG}\n");
                        #template_escape(no);
                    };
                    
                    source s_udp {
                            udp();
                    };
                    
                    filter f_remote {
                            host("192.168.0.*") or host("192.168.1.*");
                    };
                    
                    destination df_remote {
                            file("/var/log/hosts/$HOST-$LEVEL.log" template(t_msgfmt));
                    };
                    
                    destination df_prog {
                            #file("/var/log/hosts/$HOST-$LEVEL.log" template(t_msgfmt));
                            program("/usr/lib/zabbix/externalscripts/zabbix_syslog_lkp_host.pl" template(t_msgfmt));
                    };
                    
                    
                    log {
                            #file(t_msgfmt);
                            source(s_udp);
                            #filter(f_remote);
                            destination(df_prog);
                    };
                    

                    Осталось одно но — если выбрать назначением файл — прекрасно собирает логи, а если выбрать скрипт — то логи не пишутся в заббиксе, однако, если выполнить скрипт с параметром строки лога через shell, то в заббиксе данные появятся на нужном узле.

                    То есть скрипт работает, не работает лишь через syslog-ng.

                    Помогите?
                      0
                      SELinux включен?
                        0
                        вроде нет
                        root@zabbix:~# sestatus
                        The program 'sestatus' is currently not installed. You can install it by typing:
                        apt-get install policycoreutils
                        root@zabbix:~# cat /etc/sysconfig/selinux
                        cat: /etc/sysconfig/selinux: No such file or directory
                          0
                          Хотел еще добавить: если если подменить в скрипте
                          my $message = shift @ARGV   || die
                          

                          на
                          my $message = "test [192.168.250.5]"   || die
                          

                          То в заббиксе на узле 192.168.250.5 начинают появляться записи test [192.168.250.5]. Выходит, скрипт отрабатывает через syslog-ng, но не может получить строку лога как аргумент, либо не может его обработать?

                          Я к сожалению не специалист по Perl, очень трудно понять — почему так. Начитался всякого про буферизацию, пробовал методы отключения, но безуспешно.
                      0
                      Выложил слегка обновленные скрипты здесь: https://github.com/v-zhuravlev/zabbix-syslog
                        0
                        Добавлю свои 5 копеек. Для проверки скрипта выполнял в CLI сервера что-то вроде
                        /usr/local/bin/zabbix_syslog_lkp_host.pl "asdsdasd [10.169.2.6] sadasd "
                        


                        Первоначально была ошибка «Could not create socket: Connection refused». Добавил в /etc/hosts запись с hostname в строку 127.0.0.1 и ::1.

                        Спасибо, решение работает с zabbix 3.2.

                        Only users with full accounts can post comments. Log in, please.