Введение
В процессе внедрения 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 в зависимости от того, какой класс данных был запрошен — датчики, информация шасси и так далее. Подобное разделение обусловлено логикой шаблона, который используется совместно со скриптами.
#!/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 одновременными запросами.
#!/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 и настроить триггеры. Итогом этой работы явился шаблон мониторинга, скачать который можно здесь.
Применение на практике
Для практического применения вышеописанной конструкции необходимо:
- Положить скрипты 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.
ссылка на оригинал статьи http://habrahabr.ru/post/218781/
Добавить комментарий