Создание шаблонов для системы мониторинга Zabbix

Несколько месяцев назад я задумал написать генератор шаблонов для системы мониторинга Zabbix.
Тот, кто пробовал вручную описывать сотни наблюдаемых параметров для любой системы мониторинга меня поймет. Для облегчения такой задачи в Zabbix есть импорт/экспорт шаблонов, которые позволяют существенно упростить задачу добавления новых систем. Но для создания шаблона необходимы исходные данные, в случае задачи мониторинга IPMI это имена сенсоров, их типы и максимально/минимально допустимые значения, которые могут принимать опрашиваемые сенсоры. Получить все эти данные можно также при помощи протокола IPMI, например, воспользовавшись ipmitool и каким-нибудь скриптовым языком:

#!/usr/local/bin/perl

use strict;
use Getopt::Std;

my %opts;
getopt('H:U:P:', \%opts);
die "Usage $0 -H host -U user -P password\n"
        unless ($opts{H} && $opts{U} && $opts{P});

my $ipmi_cmd = "/usr/local/bin/ipmitool";
my $ipmi_prm = "-I lan -H $opts{H} -U $opts{U} -P $opts{P}";

open(FRU_LIST, "$ipmi_cmd $ipmi_prm fru |")
        or die "Can't run $ipmi_cmd $ipmi_cmd fru: $!\n";

my $template;
my $fru;
while(<FRU_LIST>) {
        chomp;
        s/\s+$//;
        last if defined($template);
        my ($key, $value) = split(/\s*\:\s*/);
        $fru = $value if ($key =~ /FRU\s+Device\s+Description/);
        $template = $value if (($fru =~ /\/SYS/) && ($key =~ /Product\s+Name/));
}
close(FRU_LIST);
$template =~ s/\s+/_/g;

open(SENSOR_LIST, "$ipmi_cmd $ipmi_prm sensor list |")
        or die "Can't run $ipmi_cmd $ipmi_cmd sensor list: $!\n";

my %sensor;
while(<SENSOR_LIST>) {
        chomp;
        s/\s*$//;
        my ($cs, $unit) = (split(/\s*\|\s*/))[0,2];
        $sensor{$cs}{unit} = $unit;
}
close(SENSOR_LIST);

foreach my $cs (sort keys %sensor) {
        open(SENSOR_DATA, "$ipmi_cmd $ipmi_prm sensor get '$cs' |")
                or die "Can't run $ipmi_cmd $ipmi_cmd sensor get '$cs': $!\n";
        while(<SENSOR_DATA>) {
                chomp;
                next unless /\:/;
                my ($key, $value) = split(/\s*\:\s*/);
                $value = undef if ($value eq 'na');
                if ($key =~ /Sensor\s+Type/) {
                        my $type = (split(/[\(\)]/, $key))[1];
                        $sensor{$cs}{type} = $type;
                        $sensor{$cs}{dsc} = $value;
                } elsif ($key =~ /Lower\s+Non-Recoverable/) {
                        $sensor{$cs}{lnr} = $value;
                } elsif ($key =~ /Lower\s+Critical/) {
                        $sensor{$cs}{lcr} = $value;
                } elsif ($key =~ /Lower\s+Non-Critical/) {
                        $sensor{$cs}{lnc} = $value;
                } elsif ($key =~ /Upper\s+Non-Critical/) {
                        $sensor{$cs}{unc} = $value;
                } elsif ($key =~ /Upper\s+Critical/) {
                        $sensor{$cs}{ucr} = $value;
                } elsif ($key =~ /Upper\s+Non-Recoverable/) {
                        $sensor{$cs}{unr} = $value;
                }
        }
        close(SENSOR_DATA);
}

print "# template|sensor|type|dsc|unit|lnr|lcr|lnc|unc|ucr|unr\n";
foreach my $cs (sort keys %sensor) {
        printf "%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s\n",
                $template, $cs, $sensor{$cs}{type},
                $sensor{$cs}{dsc}, $sensor{$cs}{unit},
                $sensor{$cs}{lnr}, $sensor{$cs}{lcr},
                $sensor{$cs}{lnc}, $sensor{$cs}{unc},
                $sensor{$cs}{ucr}, $sensor{$cs}{unr};
}

 

Скрипт собирает:

* имя системы
* названия всех доступных сенсоров
* тип, описание, еденицы измерения и максимально/минимально допустимые значения для аналоговых сенсоров

Вывод скрипта выглядит примерно так:

# template|sensor|type|dsc|unit|lnr|lcr|lnc|unc|ucr|unr
SUN_FIRE_X4600|fp.prsnt|Discrete|Entity Presence|discrete||||||
SUN_FIRE_X4600|ft0.fm0.f0.speed|Analog|Fan|RPM|1000.000|||||12000.000
SUN_FIRE_X4600|ft0.fm0.fail|Discrete|Fan|discrete||||||
SUN_FIRE_X4600|ft0.fm0.prsnt|Discrete|Entity Presence|discrete||||||
SUN_FIRE_X4600|ft1.fm0.f0.speed|Analog|Fan|RPM|1000.000|||||12000.000
SUN_FIRE_X4600|ft1.fm0.fail|Discrete|Fan|discrete||||||
SUN_FIRE_X4600|ft1.fm0.prsnt|Discrete|Entity Presence|discrete||||||
SUN_FIRE_X4600|ft2.fm0.f0.speed|Analog|Fan|RPM|1000.000|||||12000.000


К сожалению, содержание вывода для одной и той же аппаратной платформы может быть разным. Разные версии микрокода от производителя могут по разному интерпретировать названия сенсоров и их параметры. Для серверов производства Sun это применимо в нескольких случаях:

* переход с ELOM на ILOM
* переход с ILOM 2.x на ILOM 3.x

Натравив такой скрипт на сервисный процессор можно получить все необходимые данные для построения наблона. Для создания шаблона полученную таблицу следует преобразовать в формат, понятный системе мониторинга. В
Zabbix таким форматом является XML. Не используя специальных средств, взяв за основу один из существующих шаблонов можно сгенерировать новый шаблон при помощи какого-нибудь скриптового языка ©:

#!/usr/local/bin/perl

use strict;
use POSIX qw(strftime);

my $date = strftime("%d.%m.%y", localtime);
my $time = strftime("%H.%M", localtime);

my $delay = 300;
my $history = 7;
my $trends = 365;

# 0 - Линия
# 1 - Заполнение
# 2 - Жирная линия
# 3 - Точечный
# 4 - Пунктирная линия
# 5 - Градиентная линия

my $drawtype = 1;

# 3 - Числовой (целое)
# 0 - Числовой (с плавающей точкой)
# 1 - Символ
# 2 - Журнал (лог)
# 4 - Текст

my %sensor_type = (
        Discrete => 3,
        Analog => 0,
);

# 0 - Не классифицировано
# 1 - Уведомление
# 2 - Предупреждение
# 3 - Средняя
# 4 - Высокая
# 5 - Чрезвычайная

my @thresh_type = (
        { pri => 4, dsc => 'Lower Non-Recoverable' },
        { pri => 3, dsc => 'Lower Critical' },
        { pri => 2, dsc => 'Lower Non-Critical' },
        { pri => 2, dsc => 'Upper Non-Critical' },
        { pri => 3, dsc => 'Upper Critical' },
        { pri => 4, dsc => 'Upper Non-Recoverable' },
);

my $head = <<EOF;
<?xml version="1.0" encoding="UTF-8"?>
<zabbix_export version="1.0" date="%s" time="%s">
        <hosts>
                <host name="%s">
                        <proxy_hostid>0</proxy_hostid>
                        <useip>0</useip>
                        <dns></dns>
                        <ip>0.0.0.0</ip>
                        <port>10050</port>
                        <status>3</status>
                        <useipmi>0</useipmi>
                        <ipmi_ip></ipmi_ip>
                        <ipmi_port>623</ipmi_port>
                        <ipmi_authtype>0</ipmi_authtype>
                        <ipmi_privilege>2</ipmi_privilege>
                        <ipmi_username></ipmi_username>
                        <ipmi_password></ipmi_password>
                        <groups>
                                <group>Templates</group>
                        </groups>
                        <items>
%s
                        </items>
                        <triggers>
%s
                        </triggers>
                        <graphs>
%s
                        </graphs>
                        <templates/>
                        <macros/>
                </host>
        </hosts>
        <dependencies/>
</zabbix_export>
EOF

my $item = <<EOF;
                                <item type="12" key="%s" value_type="%d">
                                        <description>%s</description>
                                        <ipmi_sensor>%s</ipmi_sensor>
                                        <delay>$delay</delay>
                                        <history>$history</history>
                                        <trends>$trends</trends>
                                        <status>0</status>
                                        <data_type>0</data_type>
                                        <units>%s</units>
                                        <multiplier>0</multiplier>
                                        <delta>0</delta>
                                        <formula>1</formula>
                                        <lastlogsize>0</lastlogsize>
                                        <logtimefmt></logtimefmt>
                                        <delay_flex></delay_flex>
                                        <authtype>0</authtype>
                                        <username></username>
                                        <password></password>
                                        <publickey></publickey>
                                        <privatekey></privatekey>
                                        <params></params>
                                        <trapper_hosts></trapper_hosts>
                                        <snmp_community></snmp_community>
                                        <snmp_oid></snmp_oid>
                                        <snmp_port>161</snmp_port>
                                        <snmpv3_securityname></snmpv3_securityname>
                                        <snmpv3_securitylevel>0</snmpv3_securitylevel>
                                        <snmpv3_authpassphrase></snmpv3_authpassphrase>
                                        <snmpv3_privpassphrase></snmpv3_privpassphrase>
                                        <applications/>
                                </item>
EOF

my $trigger = <<EOF;
                                <trigger>
                                        <description>%s</description>
                                        <type>0</type>
                                        <expression>%s</expression>
                                        <url></url>
                                        <status>0</status>
                                        <priority>%d</priority>
                                        <comments>%s</comments>
                                </trigger>
EOF

my $graph = <<EOF;
                                <graph name="%s" width="900" height="200">
                                        <ymin_type>0</ymin_type>
                                        <ymax_type>0</ymax_type>
                                        <ymin_item_key></ymin_item_key>
                                        <ymax_item_key></ymax_item_key>
                                        <show_work_period>1</show_work_period>
                                        <show_triggers>1</show_triggers>
                                        <graphtype>0</graphtype>
                                        <yaxismin>0.0000</yaxismin>
                                        <yaxismax>100.0000</yaxismax>
                                        <show_legend>0</show_legend>
                                        <show_3d>0</show_3d>
                                        <percent_left>0.0000</percent_left>
                                        <percent_right>0.0000</percent_right>
                                        <graph_elements>
                                                <graph_element item="%s">
                                                        <drawtype>$drawtype</drawtype>
                                                        <sortorder>0</sortorder>
                                                        <color>009900</color>
                                                        <yaxisside>0</yaxisside>
                                                        <calc_fnc>2</calc_fnc>
                                                        <type>0</type>
                                                        <periods_cnt>$delay</periods_cnt>
                                                </graph_element>
                                        </graph_elements>
                                </graph>
EOF

my ($tname, $items, $triggers, $graphs);
while (<>) {
        chomp;
        next if /^#/;
        my ($template,$sensor,$type,$dsc,$unit,$lnr,$lcr,$lnc,$unc,$ucr,$unr) =
                split(/\|/);

        $template = "Template_$template";
        $tname = $template;

        my $key = sprintf("%s_%s_%s", $type, $dsc, $unit);
        $key =~ s/[\s\\\/]+/_/g;
        $key = sprintf("%s[%s]", $key, $sensor);

        $items .= sprintf($item, $key, $sensor_type{$type},
                        "$type sensor for $dsc $sensor ($unit)", $sensor, $unit);

        if ($type eq 'Discrete') {
                $triggers .= sprintf($trigger, 
                        "$type sensor for $dsc $sensor on {HOSTNAME} was changed",
                        sprintf("{%s:%s.diff(0)}#0", $template, $key), 4, '');
        } elsif ($type eq 'Analog') {
                my $index = -1;
                my $op = '#';
                foreach my $tv ($lnr,$lcr,$lnc,$unc,$ucr,$unr) {
                        $index++;
                        next if ($tv =~ /^$/);
                        my $td = $thresh_type[$index]->{dsc};
                        my $tp = $thresh_type[$index]->{pri};
                        $op = '&lt;' if ($index < 3);
                        $op = '&gt;' if ($index > 2);
                        $triggers .= sprintf($trigger,
                                "$type sensor for $dsc $sensor on {HOSTNAME} changed to $td",
                                sprintf("{%s:%s.last(0)}%s%s", $template, $key, $op, $tv), $tp, '');
                }
                $graphs .= sprintf($graph, "$type sensor for $dsc $sensor ($unit)",
                                "$template:$key");
        } else {
                die "unknown sensor type: $type\n";
        }
}

printf($head, $date, $time, $tname, $items, $triggers, $graphs);


Скрипты можно объеденить в конвеер:

% ./ipmi-sensors.pl -H 10.1.1.1 -U user -P password | ./zabbix-template.pl > Template_SUN_FIRE_X4600.xml

 

и на выходе получить здоровенный файл, который можно смело импортировать в Zabbix.
В полученном шаблоне будут присутствовать все доступные для наблюдения аналоговые и дискретные сенсоры в количестве 136 штук:

Сенсоры

88 графиков для аналоговых сенсоров:

Графики

503 триггера на события, которые могут заинтересовать администратора. Например, при превышении допустимой температуры поцессора зарегистрируется событие с соотвествующим повышению температуры приоритетом:

Триггеры

Попробуйте представить сколько времени может занять создание такого шаблона вручную! Также обнаружилась она неприятная деталь: полученный шаблон будет использоваться системой
Zabbix только частично. Это связано с тем, что на текущий момент времени последняя доступная версия Zabbix (1.8.2) умеет собирать данные только с аналоговых сенсоров. Я почти написал патч, добавляющий необходимый функционал и скоро его опубликую.


Импортировав шаблон, можно добавлять новый узел сети или добавлять свойства
IPMI (адрес
сервисного процессора, имя пользователя и его пароль) к уже существующему узлу сети:

Свойства узла сети

Спустя некоторое время на вкладке "Последние данные" можно будет увидеть как данные сенсоров начнут поступать в систему мониторинга:

Показания сенсоров

Кроме пассивного наблюдения IPMI протокол в Zabbix можно использовать для управления питанием, выполняя power cycle в случае повисания или недоступности ОС.

В дополнении к заметке обещаю опубликовать некоторое количество готовых шаблонов, для всех доступных мне IPMI-совместимых систем

Не делайте из еды культа!

Очень люблю готовить и вкусно покушать. А чтобы времени на эти увлекательные занятия оставалось как можно больше, я стараюсь автоматизировать любые задачи, которые оказываются в поле моей профессиональной деятельности.
В своем скромном дневнике я буду делиться с Вами рецептами блюд, которые удаются мне особенно хорошо