...

среда, 9 апреля 2014 г.

Мониторинг серверов HP через iLO в Zabbix

Введение




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

Сама собой напрашивалась мысль реализовать все это через iLO, однако задача оказалась далеко не такой тривиальной, какой выглядела изначально. В итоге ее решения получилась довольно-таки интересная конструкция, которая:



  • Использует функцию discovery, избавляющую нас от необходимости задавать вручную вообще что-либо, кроме адреса iLO,

  • Отслеживает состояние температур, кулеров и питания на серверах Proliant, начиная от 5 поколения,

  • Отслеживает состояние памяти и жестких дисков на серверах Proliant, начиная от 7 поколения,

  • Собирает общую информацию для инвентаризации — серийные номера, номера модели, версии прошивок.


Теперь о том, как именно это было реализовано.


Казалось бы, все просто: iLO умеет отдавать данные через IPMI, а в Zabbix есть штатная поддержка этого протокола, но, как водится, гладко было на бумаге. При детальном рассмотрении вопроса сразу появились три проблемы:



  1. Zabbix использует библиотеку openipmi, в которой есть баг — успешное соединение с iLO произойдет только в том случае, если оно инициировано от имени учетной записи, имеющей привилегии администратора. С точки зрения безопасности это в корне неправильно. Проблему можно решить патчем/обновлением, но она не избавляет от других,

  2. Снятие информации с дискретных датчиков через IPMI не поддерживается,

  3. И, наконец, для разных моделей серверов ключи, имена и количество датчиков различаются. Делать для каждой модели шаблоны вручную — крайне непродуктивно.


В связи с вышеизложенным, было принято решение написать отдельный механизм для взаимодействия с iLO, опираясь на скрипты и сторонние утилиты работы с IPMI. В качестве языка программирования был выбран 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 и настроить триггеры. Итогом этой работы явился шаблон мониторинга, скачать который можно здесь.

Применение на практике




Для практического применения вышеописанной конструкции необходимо:

  1. Положить скрипты ilo_discovery.pl и ipmi_proliant.pl в папку, указанную в качестве хранилища ExternalScripts в конфиге Zabbix, и сделать их исполняемыми,

  2. Скачать и установить FreeIPMI:

    # wget http://ift.tt/OC8mfg
    # 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


  3. Создать в iLO учетную запись для Zabbix и прописать ее данные в скриптах ($user и $pass),

  4. В веб-интерфейсе Zabbix для сервера, который мы хотим опрашивать через iLO, прописать адрес iLO в макросе {$ILO}

  5. Привязать к этому серверу шаблон мониторинга iLO

  6. Подождать, пока отработает обнаружение.


Заключение




Данный механизм мониторинга был успешно протестирован с серверами HP Proliant серий DL, ML и BL 5, 6, 7 и 8 поколений. Общая рекомендация — стараться перед его применением обновлять iLO до последних версий прошивок.

Что же касается младшей линейки серверов, имеющей на борту Lo100 вместо iLO — с ними все это тоже будет работать, но некоторая информация, получаемая со старших моделей того же поколения, будет недоступна, поскольку lo100 отдает меньше данных, чем iLO.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий