Мониторинг серверов HP через iLO в Zabbix
Введение В процессе внедрения 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 ();
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 =
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 =
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.