Perl для системного администрирования

       

Файлы узлов


Первый подход, используемый для решения проблемы связи IP-адресов с именами, является самым простым и очевидным: он заключается в создании специального файла, в котором хранится таблица соответствий IP-адресов и имен компьютеров. В системах Unix это файл /etc/ hosts, в MacOS это Macintosh HD:System Folder:Preferences:hosts и \$systemroot$\System32\Drivers\Etc\hosts в NT/2000. В NT/2000 также есть файл Imhosts, назначение которого несколько иное, но об этом мы поговорим позже. Вот как выглядит файл узлов в Unix:

127.0.0.1 localhost

192.168.1.1 everest.oog.org everest

192.168.1.2 riveridell.oog.org rivendell

Ограниченность такого подхода очень быстро становится очевидной. Если в домене oog.org TCP/IP-сети есть две связанные между собой машины, а менеджер сети хочет добавить третью, к которой надо обращаться по имени, ему придется отредактировать соответствующий файл на всех машинах. Если в oog.org появится еще одна машина, то придется поддерживать четыре файла узлов (по одному на каждой машине).

И хотя такое решение кажется совершенно непригодным, именно оно использовалось на заре появления Internet/ARPAnet. Если к сети добавлялись новые сайты, то файлы узлов необходимо было обновлять на всех сайтах, которые хотели общаться с новым. Центральный репозиторий, называемый информационным центром сети (NIC) (а точнее SRI-NIC, т. к. находился тогда на SRI), обновлял и распространял файл узлов для всей сети с именем HOSTS.TXT. Системные администраторы периодически загружали этот файл по FTP из каталога NETIN-FO на сервере SRI-NIC.

Файлы узлов используются и по сей день, несмотря на их ограниченность и замену технологиями, которые нам предстоит обсудить. Существуют ситуации, когда файлы узлов даже необходимы. Например, в SunOS машина обращается к собственному файлу /etc/hosts, чтобы определить свой IP-адрес. Файлы узлов также решают проблему «курицы и яйца», возникающую при загрузке машины. Если используемые машиной сетевые серверы имен определяются именами, то должен существовать способ определить их IP-адреса. Если же сетевые службы имен еще не действуют, то не существует способа (кроме как применять в поисках помощи широковещательные сообщения) получить эту информацию. Обычное решение - создать файл (в нем перечислено только несколько узлов), который будет использоваться для загрузки.




В маленькой сети очень полезно держать постоянно обновляемый файл узлов, в котором перечислены все машины сети. Не обязательно

даже иметь такой файл на каждой машине (т. к. другие механизмы, речь о них пойдет позже, гораздо лучше справляются с задачей распространения этой информации). Достаточно держать под рукой один файл, к которому можно обращаться вручную для просмотра адресов, а также для процедуры выдачи адреса.

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

Обратите внимание на то, что анализ файлов узлов может быть очень простым:

open(HOSTS, "/etc/hosts") or die

"Невозможно открыть файл узлов:$'\п":



while (defined ($_ = <HOSTS>)) {

next if";

# пропускаем строки, являющиеся комментариями next if /"$/;

пропускаем пустые строки

удаляем комментарии и

предваряющие их пробелы ($ip, @names) = split;

die "IP-адрес Sip уже встречался!\п"

if (exists $addrs{$ip}); $addrs{$ip) = [@names]; for (@narnes){

die "Имя узла $_ уже встречалось!\п" if (exists $names{$_});

$names{$J = $ip;

}

}

close(HOSTS);

В этом примере просматривался файл /etc/hosts (пропускались пустые строки и комментарии) и были созданы две структуры данных для дальнейшего использования. Первая структура данных - это хэш списков имен узлов, ключами которого являются IP-адреса. Вот как будет выглядеть такая структура данных для рассмотренного файла узлов:

$addrs{'127.0.0.1'} = ['localhost'];

$addrs{'192.168.1.2 } = [ rivendell.oog.org'.'rivencell']:

$addrs{'192.168.1.1'} = ['everest.oog,org'.'everes:']:

Вторая структура данных - хэш-таблица имен узлов, ключами которой являются имена. Для того же самого файла хэш будет выглядеть так:

$narres! 'localhost' ;='127.0.0.



Snakes {'eve rest'}

$names{'eve rest.oog.org'}='192.168.

$names{'rivendell'}='192.168,1.2

Snakes{'rivendell.oog.org'}= 192.168.

Заметьте, что в простой процесс анализа этого файла мы добавили дополнительную функциональность. Мы проверяем, не встречаются ли в файле повторяющиеся имена и IP-адреса (и то и другое - тревожный симптом для TCP/IP-сети). Работая с данными, относящимися к сети, используйте каждую возможность, чтобы проверить отсутствие ошибок и неверной информации. Лучше выявить ошибки в самом начале, чем потом пострадать от них, когда данные распространятся уже по всей сети. К такому важному вопросу следует еще раз вернуться в этой главе.



Генерирование файлов узлов



Теперь можно заняться более интересным делом - генерированием файлов узлов. Пусть у нас есть следующий файл базы данных для всех узлов в сети:

name: shimmer

address: 192.168.1.11

aliases: shim shimmy shimmydoodles

owner: David Davis

department: software

building: main

room: 909

manufacturer: Sun

model: Ultra60

name: bendir address: 192.168.1.3 aliases: ben bendoodles owner: Cindy Coltrane department: IT building: west room: 143

manufacturer: Apple model: 7500/100

name: Sulawesi address: 192.168.1.12 aliases: sula su-lee owner: Ellen Monk department: design building: main room: 1116 manufacturer: Apple model: 7500/100 name: sander address: 192.168.1.55 aliases: sandy micky mickydoo owner: Alex Rollins department: IT building: main room: 1101

manufacturer: Intergraph model: TD-325

Формат очень простой:

имя_поля: значение, причем -=- используется в качестве разделителя между записями. Вероятно, вам потребуются иные поля или у вас будет слишком много записей, чтобы хранение их в одном «плоском» файле было оправдано. И хотя в этой главе применяется один плоский файл, принципы, приведенные здесь, не зависят от используемой базы данных.

Вот пример программы, которую можно применять для анализа подобного файла и генерирования файла узлов:

Sdatafile ="./database"; $recordsep = "-=-\n";



open(DATA,Sdatafile) or die "Невозможно открыть файл с данными:$!\п"; $/=$recordsep; и подготовка к чтению файла базы данных по одной записи

print "#\n# host file - GENERATED BY $0\n# DO NOT EDIT BY HAND!\n#\n"; while (<DATA>) {

chomp; и удалить разделитель записей

П разбить на key1,value1,...bingo, хэш записей

%record = split /:\s*|Wm;

print "$record{address}\t$record{name} $record{aliases}\n"; } close(DATA);

Вот что получается:

#

# host file - GENERATED BY createhosts

« DO NOT EDIT BY HAND!

192.168.1.11 shimmer shim shimmy shimmydoodles 192.168.1.3 bendir ben bendoodles

192.168.1.12 Sulawesi sula su-lee

1Q9 1RR 1 R^ sanrlpr чяпН\/ mir.k\/ rnirk\/r1nn

Теперь посмотрим на некоторые более интересные Perl-технологии ил этого небольшого отрывка программы. Первое необычное наше действие - установка переменной $,/. Начиная отсюда, Perl считает кусочки текста, заканчивающиеся символами - = -у\ одной записью. Это означает, что while за один раз прочитает всю запись и присвоит ее переменной $ .

Вторая интересная вещь - это технология присвоения значений средствами split. Наша цель состоит в получении хэша, ключами которого являются имена полей, а значениями - значения полей. Зачем нам это надо, станет понятно позже, когда мы будем расширять пример. Первый шаг заключается в разбиении $_ на части при помощи spiuO. Массив, который получается в результате работы split(), приведен в табл. 5.1.

Таблица 5.1. Массив, возвращенный функцией split ()




Элемент



Значение

$record[0]

Name

$record[1]

Shimmer

$record[2]

Address

$record[3]

192.168. 1.11

$rocord[4]

Aliases

$record[5]

Shim shimmy shimmydoodles

$record[6]

Owner

$record[7]

David Davis

$record[8]

Department

$record[9]

Software

$record[10]

Building

$record[11]

Main

$record[12]

Room

$record[13]

909

$record[14]

Manufacturer

$record[l5]

Sun

$record[16]

Model

$record[17]

UltraGO
<


Присмотримся внимательно к содержимому списка. Начиная с элемента $record[0], у нас есть список пар ключ-значение (т. е. ключ=Пате, значение=5183, значение=192.168.1,11\п...), который следует просто присвоить хэшу. После создания хэша можно напечатать нужные нам части.



Вы уже приняли религию «Базы данных для системного администрирования»?



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

Использование базы данных для узлов в сети выгодно по целому-ряду причин. В этом случае вносить изменения нужно только в один файл или источник данных. Вносите изменения, запускайте определенные сценарии и, пожалуйста, у вас есть конфигурационные файлы для множества служб. В таких конфигурационных файлах почти наверняка не будет мелких синтаксических ошибок (скажем, пропущенных точек с запятыми или символов комментариев), поскольку их не коснулись человеческие руки. Если правильно написать код, то практически все остальные ошибки можно обнаружить на стадии обработки данных анализатором (parser).

Если вы еще не осознали всей мудрости этого «лучшего подхода», к концу главы сами в этом убедитесь.



Проверка ошибок в процессе генерирования файла узлов



Напечатать нужные части - это только начало. Значительное преимущество употребления отдельной базы данных, которая преобразовывается в другую форму, состоит в возможности выполнения проверки ошибок во время преобразования. Раньше уже отмечалось, что подобный контроль позволяет избавиться от проблем, вызванных такими мелкими неприятностями, как опечатки, еще до того, как данные будут распространены. Вот как будет выглядеть предыдущий пример, если в него добавить проверку опечаток:



Sdatafile ="./dataoase":

Srecorcsep = "- = -\;n:

# по одной записи за один раз

print "#\n\# host file - GENERATED BY $0\n# DO NOT EDIT BY HAND!\n#\n";

while (<DATA>) {

chomp; n удаляем разделитель записей

# разбиваем на key1,value1,... bingo, хэш записей

%record = split /:\s*|\n/m;

# проверка на неверные имена узлов

if ($record{name} =" /["-.a-zA-ZO-9]/) {

warn "!!!! $record{name} содержит недопустимые для имени узла символы

пропускаем...\п";

next;

}

# проверка на неверные псевдонимы

if ($record{aliases} =" /[~-.a-zA-ZO-9\s]/) {

warn "!!!! $record{name} содержит недопустимые для псевдонима символы,

пропускаем...\п";

next;

}

# проверка на пропущенные адреса

if (! $record{address» {

warn "!!!! $record{name} не имеет IP-адреса, пропускаем...\n";

next;

}

tt проверка на одинаковые адреса

if (defined $addrs{$record{address}>) {

warn "!!!! Дублируется IP-адрес: $record{name} &

$addrs{$record{address}}, пропускаем...\n";

next:

}

else {

$addrs{$record{address}} = $record{name};

}

print "$record{address}\t$record{name} $record{aliases}\n";

}

ClOse(DATA);

Улучшение полученного файла узлов

Позаимствуем из главы 9 «Журналы» процесс анализа выполняемого преобразования. Мы можем автоматически добавить полезные заголовки, комментарии и разделители к получаемым данным. Вот как выглядит результат преобразования той же самой базы данных:

#

К host file - GENERATED BY createhosts3 » DO NOT EDIT BY HAND!

 Converted by David N. Blank-Edelman (dnb) on Sun Jun 7 00:43:24 1998

# number of hosts in the design department: 1.

ft number of hosts in the software department: 1.

# number of hosts in the IT department: 2.

# total number of hosts: 4

#

tt Owned by Cindy Coltrane (IT): west/143

if ($record->{aliases) =" /[~-.a-zA-ZO-9\s]/) {

warn "MM ". $record->{name} '.

содержит недопустимые для псевдонима символы, пропускаем,..\n";



next; }

tt проверка на пропущенные адреса if (!$record->{address}) {

warn "!!! ! ".$record->{name} .

не имеет IP-адреса, пропускаем,.Дл";

next; >

# проверка на совпадающие адреса

if (defined $addrs{$record->{address}}) {

warn "Ml! Дублируется IP-адрес:". $record->{name}.

" & ".$addrs{$record->{address}}.", пропускаем.. ";

next; 1 else {

$addrs{$record->{address}} = $record-><name}; }

$entries{$record->{name}} = $record; n добавляем это в хэш

tt хэшей } close(DATA);

It печатаем симпатичный заголовок

print "#\n\« host file - GENERATED BY $0\n# DO NOT EDIT BY HAND!\nff\n";

print "« Converted by $user on ".scalar(localtime). "\ntt\n";

# подсчитываем число записей для каждого отдела

Я и сообщаем об этом

foreach my Sentry (keys %entries){

Sdepts{Sentries{Sentry}->{department}}++; }

foreach my Sdept (keys %depts) {

print "n number of hosts in the

Sdept department: $depts{$dept},\n"; )

print "tt total number of hosts: ".

scalar(keys %entries). "\n#\n\n";

tt обходим в цикле все узлы, выводя комментарий и саму запись f

oreach my Sentry (keys %entries) {

print "tt Owned by ", $entries{$entry}->{owner}," (",

$entries{$entry>->{department},"): ",

Sentries{Sentry}->{building}."/",

Sentries{Sentry}->{room},"\n";

print $entries{$entry}->{address},"\t",

$entries{$entry}->{name}." ",

Sentries{Sentry}->{aliases},"\n\n"; }

Самое значительное отличие данного примера от предыдущего - это способ представления данных. Поскольку в предыдущем примере не было необходимости получать информацию из хэша после печати его значений, мы могли использовать единственный хэш. Но в этом случае мы решили прочитать данные из файла в более сложную структуру данных (хэш хэшей), чтобы проанализировать их перед тем как печатать.

Можно было сохранять отдельную хэш-таблицу для каждого поля (подобно тому, как это было сделано в примере needspace из главы 2 «Файловые системы»), но красота приведенного метода состоит в его поддерживаемости. Если затем понадобится добавить в базу данных поле serial_number, нам не придется менять используемый для анализа файла код, это поле само по себе появится. Недостаток же в том, что синтаксис Perl таков, что наш код выглядит более сложным, чем он есть на самом деле.



Посмотреть на все это можно еще проще: мы будем анализировать файл точно так же, как и в предыдущем примере. Разница лишь в том, что каждую запись мы будем сохранять в новом анонимном хэше. Анонимные хэши ничем не отличаются от обычных, только обращаться к ним приходится не по имени, а по ссылке.

Чтобы построить большую структуру данных (хэш хэшей), достаточно связать каждый новый анонимный хэш с основной хэш-таблицей %entries и создать ключ со связанным с ним значением, являющимся ссылкой на этот только что заполненный анонимный хэш. Когда каждый хэш пройдет обработку, ключами %entries будут имена всех машин, а значениями - ссылки на хэш-таблицы, содержащие значения всех полей, связанных с этим именем (IP-адрес, номер кабинета и т. д.).

Вероятно, вам бы хотелось, чтобы вывод был отсортирован по IP-адресам? Никаких вопросов, просто добавьте процедуру сортировки, изменив:

foreach my Sentry (keys %entries) { на:

foreach my Sentry (sort byaddress keys %entries) { и добавьте:

sub byaddress {

@a = split(/\./,$entries{$a}->{address}):

@b = split(/\./.$e"tries{$b}-''{address)):

($a[0]<=>$b[OJ) ,!

($а[1]<=>$Ь[1!) ! i

($a[2]<=>$b[21) !|

($a[3]<=>$D[3]l;

Вот как будут выглядеть отсортированные данные:

И Owned by Cindy Coltranc (IT): west/143 192.168.1,3

 tjendir ben bei.doooles

П Owned by David Davis (software): inai'i/909

192.168.1.11 shimmer snm siimr, sniiMiydoodies

n Owned by Ellen Monk (design): rain/1116

192.168.1.12 Sulawesi sula su-lee

# Owned by Alex Rollins (IT): rnain/1101 192.168.1.55

sander sandy micky mickydoo

Сделайте так, чтобы полученные данные вам нравились. Пусть Perl поддержит ваши профессиональные и

эстетические стремления.



Внедрение системы контроля исходного кода



Перед тем как перейти к следующему способу преобразования IP-адресов в имена, хотелось бы добавить к процессу создания файла узлов еще одну хитрую возможность обработки, поскольку один-единственный файл приобретает общесетевое значение. Ошибка в этом файле повлияет на всю сеть. Чтобы обезопасить себя, нужен способ, выполняющий откат изменений, нарушивших файл. Особенно необходимо иметь возможность вернуться назад к предыдущим версиям файла.



Самый элегантный способ создать подобную машину времени - добавить к процессу систему контроля исходного кода. Такой контроль используется разработчиками с целью:

  • Регистрации всех изменений важных файлов.


  • Предотвращения ситуации, когда несколько человек одновременно изменяют один и тот же файл, тем самым ненамеренно уничтожая действия друг друга.


  • Получить возможность вернуться к предыдущим версиям файла, т. е. отказаться от изменений, вызвавших проблемы.


  • Подобные средства контроля очень полезны для системного администратора. Проверка ошибок, добавленная в разделе «Проверка ошибок в процессе генерирования файла узлов», поможет справиться лишь с некоторыми опечатками и синтаксическими ошибками, но не спасет от семантических ошибок (таких как удаление важного имени узла, присвоение ошибочного IP-адреса, ошибка в имени узла). В процесс преобразования можно добавить проверку и семантических ошибок, но отловить все возможные погрешности все равно не удастся. Мы уже говорили, что защиты от дураков не существует, потому что они изобретательны.

    Можно, наверное, предположить, что было бы лучше применить систему контроля исходного кода к процессу редактирования первоначальной базы данных, но есть две веские причины, по которым очень важно применить ее к данным, получаемым в результате:

    Время

    Для большого набора данных процесс преобразования может занять некоторое время. Если ваша сеть «упала», и вам необходимо вернуться к предыдущей версии, то необходимость наблюдать, пока Perl сгенерирует нужный файл, вас просто обескуражит (и все это при условии, что вы смогли сразу добраться до Perl).

    База данных

    Если для хранения данных вы будете использовать настоящую базу данных (а часто это правильный выбор), то может просто не существовать способа применить к ней систему контроля версий. Вероятно, вам придется писать собственные механизмы контроля версий для процесса редактирования базы данных.

    В качестве системы контроля исходного кода я выбрал систему контроля версий RCS. У RCS есть несколько возможностей, дружественных к Perl и системному администрированию:



  • RCS работает на многих платформах. Существуют версии GNU RCS 5.7 для большинства Unix-систем, Windows NT, MacOS и т. д.


  • У нее вполне определенный интерфейс командной строки. Все действия можно выполнить из командной строки даже в операционной системе, где в основном применяется графический интерфейс.


  • Ее очень легко использовать. Небольшой набор команд для выполнения основных операций можно выучить за пять минут (приложение А «Пятиминутное руководство по RCS»).


  • В RCS есть ключевые слова. В текст файлов, находящихся под контролем RCS, можно добавлять магические строки, которые будут автоматически раскрываться. Например, любое вхождение в файл строки $Date: $ будет заменено датой последнего помещения файла в систему RCS.


  • RCS бесплатна. Исходный код GNU-версии RCS распространяется свободно, кроме того, доступны уже скомпилированные версии для большинства систем. Исходный код RCS можно найти на ftp://\ ftp.gnu.org/gnu/rcs.


  • Если вы никогда не работали с RCS, загляните сначала в приложение А. Впредь будем считать, что вы знакомы с основным набором команд RCS.

    Крэйг Фретер (Craig Freter) написал объектно-ориентированный модуль Res, который упрощает применение RCS из Perl. Для этого необходимо:

  • Загрузить модуль.


  • Указать, где расположены команды RCS.


  • Создать новый объект Res, настроить его в соответствии с файлом, который вы используете.


  • Вызвать необходимые методы объекта (названные в соответствии с командами RCS).


  • Добавим модуль Res в программу генерации файла узлов, чтобы увидеть, как он работает, и применим другой способ вывода данных. Теперь они будут записываться в определенный файл, а не в стандартный поток вывода STDOUT, как было раньше. Ниже приведен только измененный код. Пропущенные строки, представленные в виде «...», можно найти в предыдущем примере:

    $outputfile="hosts.$$";

    # временный файл для вывода

    $target="hosts";

    # где сохранить преобразованные данные

    open(OUTPUT,"> Soutputfile") or die

    "Невозможно записать в файл



    $outputfile:$!\n";

    print OUTPUT "»\n\# host file - GENERATED BY $0\n

    tt DO NOT EDIT BY HAND! \nft\n"; print OUTPUT "

    Converted by $user on ".scalar(localtime)."\nfl\n";

    foreach my $dept (keys %depts) {

    print OUTPUT "tt number of hosts in the $dept department:

    $depts{$dept}.\n"; }

    print OUTPUT "tt total number of hosts: ".scalar(keys %entries)."\ne\n\n";

    обходим в цикле все узлы и выводим комментарий tt вместе с самой записью

    foreach my Sentry (sort byaddress keys %entries) { print OUTPUT

    "# Owned by ",$entries{$entry}->{owner},"

     (", $entries{$entry}->{department},"): ",

    $entries{$entry}->{building},"/".

    Sentries{$entry}->{room},"\n";

    print OUTPUT

    $entries{$entry}->{address},"\t", $entries{$entry}->{name},"

     ", Sentries{$entry}->{aliases},"\n\n"; }

    Close(OUTPUT);

    use Res;

     путь к RCS

    Rcs->bindir(Vusr/local/bin1);

    создаем новый RCS-объект

    my $rcsobj = Rcs->new;

    ft передаем ему имя получаемого файла

    $rcsobj->file($target);

    tt получаем его из репозитория RCS (он уже должен быть там)

    $rcsobj->co('-!');

    ft переименовываем вновь созданный файл

    rename($outputfile,$target) or

    die "Невозможно переименовать Soutputfile в $target:$!\n";

    помещаем его в репозиторий RCS

    $rcsobj->ci("-u","-m"."Converted by $user on ".scalar(localtime));

    В данном примере предполагалось, что целевой файл хотя бы один раз помещался в репозиторий.

    Взглянув на фрагмент записей из rlog hosts, можно понять, как действует программа:

    revision 1.5

    date: 1998/05/19 23:34:16; author: dnb; state: Exp; lines: +1 -1

    Converted by David N. Blank-Edelman (dnb) on Tue May 19 19:34:16 1998

    revision 1.4

    date: 1998/05/19 23:34:05; author: eviltwin; state: Exp; lines: +1 -1

    Converted by Divad Knalb-Namlede (eviltwin) on Tue May 19 19:34:05 1998

    revision 1.3

    date: 1998/05/19 23:33:35; author: dnb; state: Exp; lines: +20 -0

    Converted by David N. Blank-Edelman (dnb) on Tue May 19 19:33:16 1998

    Из предыдущего примера видно, что между версиями файла нет больших различий (обратите внимание на часть, включающую lines:), зато отслеживаются все изменения, происходящие при создании файла. При необходимости узнать, что именно произошло, достаточно воспользоваться командой rcsdiff. В крайнем случае, всегда можно вернуться к предыдущим версиям, если какие-либо изменения приведут сеть в неработоспособное состояние.






    Содержание раздела