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

Оригинал статьи: http://habrahabr.ru/post/218781/

Введение

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

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

  1. Использует функцию discovery, избавляющую нас от необходимости задавать вручную вообще что-либо, кроме адреса iLO,
  2. Отслеживает состояние температур, кулеров и питания на серверах Proliant, начиная от 5 поколения,
  3. Отслеживает состояние памяти и жестких дисков на серверах Proliant, начиная от 7 поколения,
  4. Собирает общую информацию для инвентаризации — серийные номера, номера модели, версии прошивок.

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

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

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

В связи с вышеизложенным, было принято решение написать отдельный механизм для взаимодействия с iLO, опираясь на скрипты и сторонние утилиты работы с IPMI. В качестве языка программирования был выбран perl, а в качестве источника данных — пакет FreeIPMI. На всех подопечных серверах в iLO была создана учетная запись мониторинга с read-only правами. Логически вся конструкция делится на две части:

  1. Скрипт обнаружения источников данных ilo_discovery.pl — опрашивает iLO на предмет поддерживаемых параметров и ключей, парсит их и выдает в формате, понятном Zabbix,
  2. Скрипт получения данных ipmi_proliant.pl — по запросу выдает значение конкретного параметра.

Скрипт обнаружения

Этот скрипт выдает данные в формате 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. Скачать архив со скриптами и шаблоном, импортировать шаблон в Zabbix,
  2. Положить скрипты ilo_discovery.pl и ipmi_proliant.pl в папку, указанную в качестве хранилища ExternalScripts в конфиге Zabbix, и сделать их исполняемыми,
  3. Скачать и установить FreeIPMI (FAQ по сборке и зависимостям лежит тут):
    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

    Для 64-битных систем строка configure будет такой:

    ./configure --prefix=/usr --exec-prefix=/usr --sysconfdir=/etc --localstatedir=/var --mandir=/usr/share/man --libdir=/usr/lib64
  4. Создать в iLO учетную запись для Zabbix и прописать ее данные в скриптах ($user и $pass),
  5. Проверить, что FreeIPMI успешно подключается к iLO (адрес, логин и пароль подставляем свой):
    /usr/sbin/ipmi-sensors -D LAN2_0 -h 192.168.0.1 -u monitor -p P@$$w0rd -l USER -W discretereading --no-header-output --quiet-cache --sdr-cache-recreate --comma-separated-output --entity-sensor-names

    В ответ мы должны получить список датчиков наподобие:

        0,System Chassis 1 UID Light,OEM Reserved,N/A,N/A,'OEM Event = 0000h'
        1,System Chassis 2 Health LED,OEM Reserved,N/A,N/A,'OEM Event = 0000h'
        2,Processor Module VRM 1,Power Unit,N/A,N/A,'Device Inserted/Device Present'
        3,Power Supply Power Supply 1,Power Supply,N/A,N/A,'Presence detected'
  6. Проверить, что скрипт успешно парсит данные обнаружения (адрес подставляем свой):
    /usr/lib/zabbix/externalscripts/ilo_discovery.pl 192.168.0.1 sensor temp numeric

    В ответ мы должны получить примерно такой вывод:

        {
                "data":[
                        {
                                "{#CLASS}":"sensor",
                                "{#KEY}":"Air Inlet 01-Inlet Ambient",
                                "{#SECTION}":"Temperature",
                                "{#TYPE}":"numeric",
                                "{#MEASURE}":"C"},
                        {
                                "{#CLASS}":"sensor",
                                "{#KEY}":"Processor 02-CPU",
                                "{#SECTION}":"Temperature",
                                "{#TYPE}":"numeric",
                                "{#MEASURE}":"C"},
    
  7. В веб-интерфейсе Zabbix для сервера, который мы хотим опрашивать через iLO, прописать адрес iLO в макросе {$ILO} (в поле адреса ipmi интерфейса ничего указывать не надо),
  8. Привязать к этому серверу шаблон мониторинга iLO
  9. Подождать, пока отработает обнаружение.

Результаты

Примерно так будет выглядеть раздел lastest data и графики для узла с мониторингом iLO:

monitoring_serverov_hp_cherez_ilo_v_zabbix.txt · Последние изменения: 2015/01/29 22:35 (внешнее изменение)
GNU Free Documentation License 1.3
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0 Яндекс.Метрика