Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

Предыдущая версия справа и слева Предыдущая версия
Следующая версия
Предыдущая версия
monitoring_serverov_hp_cherez_ilo_v_zabbix [2015/01/29 22:33]
ansealk [Результаты]
monitoring_serverov_hp_cherez_ilo_v_zabbix [2015/01/29 22:35] (текущий)
Строка 1: Строка 1:
 +====== Мониторинг серверов HP через iLO в Zabbix ======
 +**Оригинал статьи:​** http://​habrahabr.ru/​post/​218781/​
 +===== Введение =====
 +
 +В процессе внедрения Zabbix в нашей весьма разветвленной инфраструктуре,​ я столкнулся с необходимостью мониторинга аппаратной части довольно большого парка серверов HP Proliant разных моделей и поколений независимо от ОС и агентов HP.
 +
 +Сама собой напрашивалась мысль реализовать все это через iLO, однако задача оказалась далеко не такой тривиальной,​ какой выглядела изначально. В итоге ее решения получилась довольно-таки интересная конструкция,​ которая:​
 +  - Использует функцию discovery, избавляющую нас от необходимости задавать вручную вообще что-либо,​ кроме адреса iLO,
 +  - Отслеживает состояние температур,​ кулеров и питания на серверах Proliant, начиная от 5 поколения,​
 +  - Отслеживает состояние памяти и жестких дисков на серверах Proliant, начиная от 7 поколения,​
 +  - Собирает общую информацию для инвентаризации — серийные номера,​ номера модели,​ версии прошивок.
 +
 +Теперь о том, как именно это было реализовано.
 +
 +Казалось бы, все просто:​ iLO умеет отдавать данные через IPMI, а в Zabbix есть штатная поддержка этого протокола,​ но, как водится,​ гладко было на бумаге. При детальном рассмотрении вопроса сразу появились три проблемы:​
 +
 +  * Zabbix использует библиотеку openipmi, в которой есть баг — успешное соединение с iLO произойдет только в том случае,​ если оно инициировано от имени учетной записи,​ имеющей привилегии администратора. С точки зрения безопасности это в корне неправильно. Проблему можно решить патчем/​обновлением,​ но она не избавляет от других,​
 +  * Снятие информации с дискретных датчиков через IPMI не поддерживается,​
 +  * И, наконец,​ для разных моделей серверов ключи, имена и количество датчиков различаются. Делать для каждой модели шаблоны вручную — крайне непродуктивно.
 +
 +В связи с вышеизложенным,​ было принято решение написать отдельный механизм для взаимодействия с iLO, опираясь на скрипты и сторонние утилиты работы с IPMI. В качестве языка программирования был выбран perl, а в качестве источника данных — пакет FreeIPMI. На всех подопечных серверах в iLO была создана учетная запись мониторинга с read-only правами. Логически вся конструкция делится на две части:
 +
 +  - Скрипт обнаружения источников данных ilo_discovery.pl — опрашивает iLO на предмет поддерживаемых параметров и ключей,​ парсит их и выдает в формате,​ понятном Zabbix,
 +  - Скрипт получения данных ipmi_proliant.pl — по запросу выдает значение конкретного параметра.
 +
 +===== Скрипт обнаружения =====
 +
 +Этот скрипт выдает данные в формате zabbix discovery в зависимости от того, какой класс данных был запрошен — датчики,​ информация шасси и так далее. Подобное разделение обусловлено логикой шаблона,​ который используется совместно со скриптами.
 +<file perl 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;
 +    }
 +}
 +</​file>​
 +
 +===== Скрипт получения данных =====
 +
 +Этот скрипт выдает значение конкретных датчиков — опять же, в зависимости от того, какой класс данных был запрошен. Полученные данные кэшируются в текстовом файле, дабы случайно не заddosить iLO одновременными запросами.
 +<file perl 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;
 +    }
 +}
 +</​file>​
 +
 +===== Шаблон мониторинга =====
 +
 +Написать скрипты — полдела. Нужно было еще правильно сконфигурировать импорт всей этой информации в Zabbix и настроить триггеры. Итогом этой работы явился шаблон мониторинга,​ в котором созданы правила обнаружения всех датчиков и иных источников данных с автоматическим созданием соответствующих триггеров и графиков.
 +
 +===== Применение на практике =====
 +
 +Для практического применения вышеописанной конструкции необходимо:​
 +  - [[https://​www.zabbix.com/​forum/​attachment.php?​attachmentid=6874&​d=1397131920|Скачать]] архив со скриптами и шаблоном,​ импортировать шаблон в Zabbix,
 +  - Положить скрипты ilo_discovery.pl и ipmi_proliant.pl в папку, указанную в качестве хранилища ExternalScripts в конфиге Zabbix, и сделать их исполняемыми,​
 +  - Скачать и установить FreeIPMI (FAQ по сборке и зависимостям лежит [[http://​www.gnu.org/​software/​freeipmi/​freeipmi-faq.html|тут]]):<​code bash>
 +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</​code>​ Для 64-битных систем строка configure будет такой:<​code bash>​./​configure --prefix=/​usr --exec-prefix=/​usr --sysconfdir=/​etc --localstatedir=/​var --mandir=/​usr/​share/​man --libdir=/​usr/​lib64</​code>​
 +  - Создать в iLO учетную запись для Zabbix и прописать ее данные в скриптах ($user и $pass),
 +  - Проверить,​ что FreeIPMI успешно подключается к iLO (адрес,​ логин и пароль подставляем свой):<​code bash>
 +/​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</​code>​В ответ мы должны получить список датчиков наподобие:<​code>​
 +    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'</​code>​
 +  - Проверить,​ что скрипт успешно парсит данные обнаружения (адрес подставляем свой):<​code bash>/​usr/​lib/​zabbix/​externalscripts/​ilo_discovery.pl 192.168.0.1 sensor temp numeric</​code>​В ответ мы должны получить примерно такой вывод:<​code>​
 +    {
 +            "​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"​},​
 +</​code>​
 +  - В веб-интерфейсе Zabbix для сервера,​ который мы хотим опрашивать через iLO, прописать адрес iLO в макросе {$ILO} (в поле адреса ipmi интерфейса ничего указывать не надо),
 +  - Привязать к этому серверу шаблон мониторинга iLO
 +  - Подождать,​ пока отработает обнаружение.
 +
 +===== Результаты =====
 +
 + ​Примерно так будет выглядеть раздел lastest data  и графики для узла с мониторингом iLO: \\
 +{{:​zabbix:​ilo:​77ffa2c3b75c140e8ab6db5ff6d980d7.png?​800|}}
 +
 +{{:​zabbix:​ilo:​d8126c2f4cfec804e0211be48f95ce34.png?​800|}}
 +
 +
 +
  
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 Яндекс.Метрика