СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

H Мониторинг серверов 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. Скачать архив со скриптами и шаблоном, импортировать шаблон в 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:


Графики по получаемым данным прилагаются:


Заключение


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

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

комментарии (7)

0
alexvl ,  

Спасибо за интересную статью. Уверен, что многим она будет полезна. На самом деле дискретные датчики поддерживаются уже начиная с версии 2.2.0.

0
Vengant ,  

Да, вы правы. Просто когда рассматривал возможность использования штатных функций Zabbix для работы с IPMI, тестировал, если мне не изменяет память, на ветке 2.0. В продакшн пошел как раз на тот момент вышедший релиз 2.2, но к тому времени уже появилась идея и были первые наметки описанного здесь решения, так что переделывать я уже ничего не стал. Тем более, что, насколько я помню, через штатные функции все равно не получилось бы снимать флаги состояния шасси и тому подобные данные, не относящиеся к датчикам.

+1
saamich ,  

Мониторю у себя по похожему принципу, только:
1) скриптом создаю сразу готовый шаблон под конкретные датички.
2) таже мониторю sel логи ( наслучай если с железкой что случилось «по железу»), но туда проблемы с дисковой подсистемой не попадают, их отдельно скриптами через userparametr
3) Обновление «элементов данных» идет разово( на G8 серверах ipmi крайне тормозной)- скриптом через zabbix_trapper, а скрипт дергает сам же забикс и если обновление не пройдет — кинет алерт.

0
Vengant ,  

Тоже вариант, да. Но у нас больно много серверов, генерить шаблон под каждую модель даже в полуавтоматическом режиме — шибко долго выйдет. Поэтому и задался целью сделать универсальное решение.

А что касается trapper'а — я к нему отношусь крайне осторожно. Насколько надежно он у вас работает? Часто бывает, что не проходит обновление?

0
saamich ,   * (был изменён)

По факту шаблон генерится на «поколение»( G5, G6, G7), сервера в принципе с одинаковой начинкой и список датчиков в пределах поколения одинаковый.

Не могу сказать проблема именно в трапире или нет, у меня скрипт просто выходит с определенным кодом если «что-то пошло не так». Такие ошибки редко бывают.

0
Vengant ,  

Понятно.
Насчет поколения не согласен — для DL360 и DL380, к примеру, будут разные наборы датчиков с разными именами, даже если они принадлежат к одному поколению. А что касается тормозов IPMI в G8 — такого не замечал, но был другой прикол — на старых версиях прошивок iLO у G8 через пару-тройку опросов с периодичностью в минуту просто переставал отвечать по IPMI. После обновления прошивок на актуальные проблема более не наблюдалась.

0
saamich ,  

Да, оговорился- не поколение в целом, а на модель.

Про тормознутость IPMI я это и имел ввиду. Ловил такое поведение где-то в сентябре прошлого года, на тот момент были зашиты последние версии ilo.