Введение
В процессе внедрения Zabbix в нашей весьма разветвленной инфраструктуре, я столкнулся с необходимостью мониторинга аппаратной части довольно большого парка серверов HP Proliant разных моделей и поколений независимо от ОС и агентов HP.
Казалось бы, все просто: все эти сервера имеют на борту iLO, который умеет отдавать данные через IPMI, а в Zabbix есть штатная поддержка этого протокола, но, как водится, гладко было на бумаге. При детальном рассмотрении вопроса сразу появились три проблемы:
- Zabbix использует библиотеку openipmi, в которой есть баг — успешное соединение с iLO произойдет только в том случае, если оно инициировано от имени учетной записи, имеющей привилегии администратора. С точки зрения безопасности это в корне неправильно. Проблему можно решить патчем/обновлением, но она не избавляет от других,
- Снятие информации с дискретных датчиков через IPMI не поддерживается,
- И, наконец, для разных моделей серверов ключи, имена и количество датчиков различаются. Делать для каждой модели шаблоны вручную — крайне непродуктивно.
В связи с вышеизложенным, было принято решение написать отдельный механизм для взаимодействия с iLO, опираясь на скрипты и сторонние утилиты работы с IPMI. В итоге получилась довольно-таки интересная конструкция, которая:
- Использует функцию discovery, избавляющую нас от необходимости задавать вручную вообще что-либо, кроме адреса iLO,
- Отслеживает состояние температур, кулеров и питания на серверах Proliant, начиная от 5 поколения,
- Отслеживает состояние памяти и жестких дисков на серверах Proliant, начиная от 7 поколения,
- Собирает общую информацию для инвентаризации — серийные номера, номера модели, версии прошивок.
Теперь о том, как именно это было реализовано.
В качестве языка программирования был выбран perl, а в качестве источника данных — пакет
FreeIPMI. На всех подопечных серверах в iLO была создана учетная запись мониторинга с read-only правами. Логически вся конструкция делится на две части:
- Скрипт обнаружения источников данных ilo_discovery.pl — опрашивает iLO на предмет поддерживаемых параметров и ключей, парсит их и выдает в формате, понятном Zabbix,
- Скрипт получения данных ipmi_proliant.pl — по запросу выдает значение конкретного параметра.
Сразу хочу отметить, что программистом perl я не являюсь и использовал для решения задач те примеры и конструкции, которые мне были понятны, конечный же результат был достигнут — все это успешно работает.
Скрипт обнаружения
Этот скрипт выдает данные в формате zabbix discovery в зависимости от того, какой класс данных был запрошен — датчики, информация шасси и так далее. Подобное разделение обусловлено логикой шаблона, который используется совместно со скриптами.
ilo_discovery.pl#!/usr/bin/perl -w
use strict;
use warnings;
use Fcntl ':flock';
use Scalar::Util qw(looks_like_number);
use feature qw(switch);
my $server = $ARGV[0];
my $class = $ARGV[1];
my $key = $ARGV[2];
my $type="";
my $reqtype=$ARGV[3];
exit(1) if not defined $server or not defined $key;
exit(1) if not defined $reqtype and $class eq "sensor";
my $expires = 60;
my $user = 'monitoring';
my $pass = 'P@$$w0rd';
my $ipmi_cmd = '';
my $cache_file = '';
my $number = int(rand(10000));
if($class eq 'sensor') {
$cache_file = '/var/tmp/ipmi_sensors_'.$server.'-'.$number;
$ipmi_cmd = '/usr/sbin/ipmi-sensors -D LAN2_0 -h '.$server.' -u '.$user.' -p '.$pass.' -l USER -W discretereading --no-header-output --quiet-cache --sdr-cache-recreate --comma-separated-output --entity-sensor-names 2>/dev/null';
} elsif($class eq 'chassis') {
$cache_file = '/var/tmp/ipmi_chassis_'.$server.'-'.$number;
$ipmi_cmd = '/usr/sbin/ipmi-chassis -D LAN2_0 -h '.$server.' -u '.$user.' -p '.$pass.' -l USER -W discretereading --get-status 2>/dev/null';
} elsif($class eq 'fru') {
$cache_file = '/var/tmp/ipmi_fru_'.$server.'-'.$number;
$ipmi_cmd = '/usr/sbin/ipmi-fru -D LAN2_0 -h '.$server.' -u '.$user.' -p '.$pass.' -l USER -W discretereading 2>/dev/null';
} elsif($class eq 'bmc') {
$cache_file = '/var/tmp/ipmi_bmc_'.$server.'-'.$number;
$ipmi_cmd = '/usr/sbin/bmc-info -D LAN2_0 -h '.$server.' -u '.$user.' -p '.$pass.' -l USER -W discretereading 2>/dev/null';
} else {
exit(1);
}
my @rows = ();
my $results = results();
open(CACHE, '>>', $cache_file);
if(flock(CACHE, LOCK_EX | LOCK_NB)) {
truncate(CACHE, 0);
print CACHE $results;
close(CACHE);
}
open(CACHE, '<' . $cache_file);
flock(CACHE, LOCK_EX);
@rows = <CACHE>;
close(CACHE);
print "{\n";
print " \"data\":[\n";
my $flag=0;
foreach my $row (@rows) {
if($class eq 'sensor') {
my @cols = split(',', $row);
my $UCols=uc($cols[1]);
my $Section=$cols[2];
my $UKey=uc($key);
my $contains = 0;
given($UKey) {
when("TEMP") {
if ($Section eq "Temperature") {$contains = 1;}
}
when("FAN") {
if ($Section eq "Fan") {$contains = 1;}
}
when("DISK") {
if ($Section eq "Drive Slot") {$contains = 1;}
}
when("POWER METER") {
if ($Section eq "Current") {$contains = 1;}
}
when(index($_, "POWER SUPPL") != -1) {
if ($Section eq "Power Supply") {$contains = 1;}
}
when("VRM") {
if ($Section eq "Power Unit") {$contains = 1;}
}
when("MEMORY") {
if ($Section eq "Memory") {$contains = 1;}
}
}
if($contains > 0) {
if (looks_like_number($cols[3])) {
$type="numeric";
} else {
$type="discrete";
}
if (($Section eq "Fan") and (index($UCols, "FANS") != -1)) {$type="discrete";}
if (($reqtype eq "discrete") and ($Section eq "Power Supply")) {$type="discrete";}
if (($type eq $reqtype) or ($reqtype eq "all")) {
if($flag eq 1) {
print ",\n";
}
print " {\n";
print " \"{#CLASS}\":\"${class}\",\n";
print " \"{#KEY}\":\"${cols[1]}\",\n";
print " \"{#SECTION}\":\"${cols[2]}\",\n";
print " \"{#TYPE}\":\"${type}\",\n";
print " \"{#MEASURE}\":\"${cols[4]}\"}";
$flag=1;
}
}
} elsif(($class eq 'fru') or ($class eq 'bmc') or ($class eq 'chassis')) {
$type="discrete";
my @cols = split(':', $row);
my $name=$cols[0];
$name=~ s/(\s+)/ /gi;
if (($class eq 'bmc') or ($class eq 'chassis')) {$name=substr($name, 0, -1);}
my $UKey=uc($key);
my $UCols=uc($name);
if(0<=index($UCols,$UKey) and ($name)) {
if($flag eq 1) {
print ",\n";
}
print " {\n";
print " \"{#CLASS}\":\"${class}\",\n";
print " \"{#TYPE}\":\"${type}\",\n";
print " \"{#KEY}\":\"${name}\"}";
$flag=1;
}
}
}
print "]}\n";
unlink $cache_file;
sub results {
my $results = `$ipmi_cmd`;
if((defined $results) and (length $results > 0)) {
return $results;
} else {
return undef;
}
}
Скрипт получения данных
Этот скрипт выдает значение конкретных датчиков — опять же, в зависимости от того, какой класс данных был запрошен. Полученные данные кэшируются в текстовом файле, дабы случайно не заddosить iLO одновременными запросами.
ipmi_proliant.pl#!/usr/bin/perl -w
use strict;
use warnings;
use Fcntl ':flock';
my $sensor = $ARGV[0];
my $class = $ARGV[1];
my $server = $ARGV[2];
my $type = $ARGV[3];
exit(1) if not defined $server or not defined $sensor or not defined $class;
$type = 'numeric' if not defined $type;
$sensor =~ s/\'//g;
my $expires = 60;
my $user = 'monitoring';
my $pass = 'P@$$w0rd';
my $ipmi_cmd = '';
my $cache_file = '';
if($class eq 'sensor') {
$cache_file = '/var/tmp/ipmi_sensors_'.$server;
$ipmi_cmd = '/usr/sbin/ipmi-sensors -D LAN2_0 -h '.$server.' -u '.$user.' -p '.$pass.' -l USER -W discretereading --no-header-output --quiet-cache --sdr-cache-recreate --comma-separated-output --entity-sensor-names 2>/dev/null';
} elsif($class eq 'chassis') {
$cache_file = '/var/tmp/ipmi_chassis_'.$server;
$ipmi_cmd = '/usr/sbin/ipmi-chassis -D LAN2_0 -h '.$server.' -u '.$user.' -p '.$pass.' -l USER -W discretereading --get-status 2>/dev/null';
} elsif($class eq 'fru') {
$cache_file = '/var/tmp/ipmi_fru_'.$server;
$ipmi_cmd = '/usr/sbin/ipmi-fru -D LAN2_0 -h '.$server.' -u '.$user.' -p '.$pass.' -l USER -W discretereading 2>/dev/null';
} elsif($class eq 'bmc') {
$cache_file = '/var/tmp/ipmi_bmc_'.$server;
$ipmi_cmd = '/usr/sbin/bmc-info -D LAN2_0 -h '.$server.' -u '.$user.' -p '.$pass.' -l USER -W discretereading 2>/dev/null';
} else {
exit(1);
}
my @rows = ();
if(-e $cache_file) {
my @stat = stat($cache_file);
my $delta = time() - $stat[9];
if($delta > $expires or $delta < 0) {
unlink($cache_file);
}
}
if(not -e $cache_file) {
my $results = results();
open(CACHE, '>>', $cache_file);
if(flock(CACHE, LOCK_EX | LOCK_NB)) {
if(defined $results) {
truncate(CACHE, 0);
print CACHE $results;
close(CACHE);
} else {
close(CACHE);
unlink($cache_file);
exit(1);
}
}
}
open(CACHE, '<' . $cache_file);
flock(CACHE, LOCK_EX);
@rows = <CACHE>;
close(CACHE);
foreach my $row (@rows) {
if($class eq 'sensor') {
my @cols = split(',', $row);
if($cols[1] eq $sensor) {
if($type eq 'discrete') {
my $r = $cols[5];
$r =~ s/\'//g;
chop($r);
print $r;
} elsif($type eq 'numeric') {
if($cols[3] eq '' or $cols[3] eq 'N/A') {
print "0";
} else {
print $cols[3];
}
}
}
} elsif(($class eq 'chassis') or ($class eq 'bmc')) {
my @cols = split(':', $row);
my $name=$cols[0];
$name=~ s/(\s+)/ /gi;
$name=substr($name, 0, -1);
if($name eq $sensor) {
my $r = $cols[1];
$r =~ s/\'//g;
$r =~ s/^.//s;
chop($r);
print $r;
}
} elsif($class eq 'fru') {
my @cols = split(':', $row);
my $name=$cols[0];
substr($name, 0, 2) = '';
if($name eq $sensor) {
my $r = $cols[1];
$r =~ s/\'//g;
$r =~ s/^.//s;
chop($r);
print $r;
}
}
}
sub results {
my $results = `$ipmi_cmd`;
if((defined $results) and (length $results > 0)) {
return $results;
} else {
return undef;
}
}
Шаблон мониторинга
Написать скрипты — полдела. Нужно было еще правильно сконфигурировать импорт всей этой информации в Zabbix и настроить триггеры. Итогом этой работы явился шаблон мониторинга, скачать который можно
здесь.
Применение на практике
Для практического применения вышеописанной конструкции необходимо:
- Положить скрипты ilo_discovery.pl и ipmi_proliant.pl в папку, указанную в качестве хранилища ExternalScripts в конфиге Zabbix, и сделать их исполняемыми,
- Скачать и установить FreeIPMI:
# wget http://ftp.gnu.org/gnu/freeipmi/freeipmi-1.2.1.tar.gz
# tar -xvzf freeipmi-1.2.1.tar.gz
# cd freeipmi-1.2.1
# ./configure --prefix=/usr --exec-prefix=/usr --sysconfdir=/etc --localstatedir=/var --mandir=/usr/share/man
# make install
- Создать в iLO учетную запись для Zabbix и прописать ее данные в скриптах ($user и $pass),
- В веб-интерфейсе Zabbix для сервера, который мы хотим опрашивать через iLO, прописать адрес iLO в макросе {$ILO}
- Привязать к этому серверу шаблон мониторинга iLO
- Подождать, пока отработает обнаружение.
Заключение
Данный механизм мониторинга был успешно протестирован с серверами HP Proliant серий DL, ML и BL 5, 6, 7 и 8 поколений. Общая рекомендация — стараться перед его применением обновлять iLO до последних версий прошивок.
Что же касается младшей линейки серверов, имеющей на борту Lo100 вместо iLO — с ними все это тоже будет работать, но некоторая информация, получаемая со старших моделей того же поколения, будет недоступна, поскольку lo100 отдает меньше данных, чем iLO.
комментарии (7)