Иногда, в нашей работе, возникает потребность учёта всего сетевого оборудования: типы, модели, адреса, прошивки, схемы соединений и так далее. И вот, просмотрев несколько вариантов ПО, выбор пал на открытый проект GLPI. Функционал у системы богатый, конкретно в нашем случае, использовалась для учёта «железа» и, главное, для учёта схем соединения (какой порт какого коммутатора куда подключен). В рамках осуществления основной деятельности организации (предоставление доступка к интернету), возникла (конечно не вдруг, а сугубо по плану) потребность резервного копирования конфигурации коммутаторов, зачем и почему данная операция необходима — это должно быть всем известно. А о том, как это реализовать, в связке с GLPI и будет данная статья.

Для осуществления задуманного, необходим список адресов оборудования и список производителей, все эти данные есть в БД GLPI, конечно если Вы их туда предварительно занесли. IP-адреса нужны для соединения по сети, а для чего нам знать производителя? Дело в том, что каждый изготовитель сетевого оборудования, в силу разных причин, привносит туда свои «оригинальные» идеи и посему команды для управления железками различаются. В нашем конкретном случае производителей всего два, но ничто не мешает список расширить.
Операцию копирования конфига будем выполнять через telnet-соединение, тоже самое можно делать и через SNMP, но в данном случае на всём железе SNMP на запись (выполнение команд) закрыт.

Языком реализации выбран был PERL. Отчего так? Ну, в то время мной пилилось, на нём-же, ещё кое-какое ПО (сборщик и разборщик RDR, вэб морда к нему) и, по сему — перл. В качестве ОС — GNU/Linux.

Приступим к коду:

Подключаем необходимые модули:

#!/usr/bin/perl
use Net::Telnet ();
use IO::File;
use Getopt::Long;
use DBI;

Функция удаления пробелов, реализована самостоятельно, отчего и почему, уже не помню но пусть будет:

# функция удаления пробельных символов
sub trim {
    my($string)=@_;
    for ($string) {
        s/^\s+//;
        s/\s+$//;
    }
    return $string;
}

Дабы два раза не вставать, в скрипте параллельно реализовано архивирование, копирование и удаление старых файлов. По возможности используются системные команды:

# Архивируем конфиги
$date = `/bin/date "+%Y%m%d"`;
$date = trim($date);
$arch = "tar -czvf /var/srv/backup/conf/conf-".$date.".tgz /var/lib/tftpboot/";
my $result = `$arch`;
# удаляем архивы давностью в неделю
$del = '/bin/find /var/srv/backup/conf/ -ctime  +7 -name conf-*.tgz -exec rm {} \; -print > /var/log/switch-conf_backup.log';
my $delResult = `$del`;

Подключаемся к БД:

# конектимся к GLPI базе 
my $dsn = 'DBI:mysql:glpi:192.168.123.222';
my $db_user_name = 'user';
my $db_password = 'password';
my ($id, $password);
my $dbh = DBI->connect($dsn, $db_user_name, $db_password);

Выбираем необходимые адреса коммутаторов нужного производителя и, соответсвенно, «дёргаем» потребные нам процедуры (которые будут описаны ниже):

my $sth = $dbh->prepare(qq{SELECT ip FROM glpi_networkequipments WHERE manufacturers_id=1 AND states_id=1});
$sth->execute();
while (my ($ip) =
  $sth->fetchrow_array())
{
#     print "$ip\n";
    if ($ip eq "" || $ip =~ /^#+/) {
         # не делать ничего. Тут криво, согласен :)
    } else {
         #  Запускаем соответствующую процедуру
        connectSwitchEdgeCore("$ip");
    }
}
$sth->finish();
# выбираем все D-Link со статусом "Включен"
my $sth = $dbh->prepare(qq{SELECT ip FROM glpi_networkequipments WHERE manufacturers_id=3 AND states_id=1});
$sth->execute();
while (my ($ip) =
  $sth->fetchrow_array())
{
    if ($ip eq "" || $ip =~ /^#+/) {
    } else {
        connectSwitchDlink("$ip");
    }
}
$sth->finish();
$dbh->disconnect();

Стоит отметить, что значения manufacturers_id и states_id (первое — код производителя, второе — статус оборудования («введён в эксплуатацию», «в проекте» и т.д.)) могут, да и скорее всего будут, различаться, т.к. создаются они автоматически при добавлении позиций в БД. В данном случае производитель «1» — это EdgeCore («3» — D-Link) и статус «1» — «Включен» т.е. введён в эксплуатацию. Отслеживание статуса необходимо, дабы исключить попытки соединения с неактивными, по разным причинам, железяками.

Далее две, почти одинаковые, процедуры, реализующие диалог между скриптом и коммутатором на тему «а скопируй-ка братец свой текущий конфиг на tftp-сервер»:

# telnet-ом цепляемся к коммутаторам и сливаем конфиг на tftp сервер
# слив конфигурации с коммутатора EdgeCore
sub connectSwitchEdgeCore
{
    $hostname = "$_[0]";
    $username = "user";
    $passwd = "password";
    $date = `/bin/date "+%Y%m%d"`;
    $str_fn=$hostname;
    print $str_fn;

    $tftpserver="20.20.20.20";
    $conn = new Net::Telnet ( Timeout=>3, Errmode=>'return', Prompt => '/\#.?$/i');
    $conn->open(Host => $hostname);
    $conn->waitfor('/ame[: ]*$/');
    $conn->print($username);
    $conn->waitfor('/ord[: ]*$/');
    $conn->print($passwd);
    $conn->print("copy running-config tftp");
    $conn->waitfor('/ess[: ]*$/');
    $conn->print($tftpserver);
    $conn->waitfor('/ame[: ]*$/');
    $conn->print($str_fn);
    $conn->print("exit");
    $conn->print("logout");
}
# слив конфигурации с коммутатора D-Link
sub connectSwitchDlink
{
    $hostname = "$_[0]";
    $username = "user";
    $passwd = "password";
    $date = `/bin/date "+%Y%m%d"`;
    $str_fn=$hostname;
    print "$str_fn\n";

    $tftpserver="20.20.20.20";
    $conn = new Net::Telnet ( Timeout=>3, Errmode=>'return', Prompt => '/\#.?$/i');
    $conn->open(Host => $hostname);
    $conn->waitfor('/ame[: ]*$/');
    $conn->print($username);
    $conn->waitfor('/ord[: ]*$/');
    $conn->print($passwd);
    $conn->print("upload cfg_toTFTP $tftpserver dest_file $hostname");
    $conn->waitfor('/Success/');
    $conn->print("logout");
}

Адрес сервера и параметров соединения с БД, логичней было вынести в отдельный файлик, но в силу расположения небесных тел в тот момент, было оставлено так. Команды print оставлены для вывода в лог. Запуск скрипта производится по времени cron-ом.

Вот так просто и непринуждённо была автоматизирована рутинная операция. Традиционно, исходники доступны тут.

P.S. Думал в какой поток разместить статью, метался между «Администрированием» и «Разработкой», в итоге остановился на первом варианте.

Комментарии (5)


  1. tima-fey
    31.08.2017 13:37

    в rancid же все уже сделано? Зачем еще велосипед нужен?


    1. svk28 Автор
      31.08.2017 13:50

      Может и сделано, но 10 минут назад про него и не знал :) Но тогда, было принято решение дописать костыль (оный благополучно трудится уже лет 5), в несколько десятков строк кода, к тому, что уже есть, нежели городить очередной комбайн сбоку.


  1. bsv9
    31.08.2017 18:35

    Для сбора конфигов, рекомендую посмотреть в сторону oxidized github.com/ytti/oxidized


    1. sohmstyle
      01.09.2017 10:37

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


      1. NikiN
        01.09.2017 15:54

        Как минимум поддержкой кучи оборудования, и restful API