Несколько месяцев назад я задумал написать генератор шаблонов для системы мониторинга 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 = '<' if ($index < 3);
$op = '>' 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-совместимых систем