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

от автора

Введение

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

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

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

В связи с вышеизложенным, было принято решение написать отдельный механизм для взаимодействия с 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(); #print $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://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
  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.

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


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *