Рубрика:
Карьера/Образование /
Кафедра
|
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|
АЛЕКСЕЙ БАРАБАНОВ, системный администратор, аутсорсер и консультант по вопросам применения информационных технологий. Хобби – программирование
Кодирование Введение в системное программирование. Часть 3
Кодирование – это момент истины всех программных проектов. Рисовать ромбики да квадратики может любой, но компьютеры пока не понимают таких абстракций. Напишем программу, как это принято
Краткое введение в тему: в предыдущей части [1] был построен обобщенный универсальный алгоритм системного администрирования. Предполагается, что он пригоден для создания программ автоматического администрирования. Вот и воспользуемся этой наработкой, чтобы выяснить, какую реальную пользу можно извлечь из подобного алгоритма.
Инструментальная платформа
Прежде всего договоримся об инструментарии. В качестве целевой платформы я выбираю GNU/Linux, и следовательно, все написанное далее можно будет с минимальными коррекциями с успехом применить практически в любой UNIX-системе. Но это не значит, что предложенные методы нельзя использовать для управления семейством операционных систем MS Windows. Без проблем! Надо только подобрать небольшой «напильник», как говорят. Однако проприетарные платформы далее не будут рассматриваться принципиально, потому что разрабатываемый проект должен оставаться в первую очередь свободным!
И поскольку выбран GNU/Linux, то вполне естественно, если в качестве инструментария будут использоваться консольные утилиты и команды оболочки bash. Оболочка bash имеет достаточно мощный встроенный язык, и все это вместе с юниксовыми утилитами позволяет решить практически любую задачу администрирования. И вот пример такого решения [2]. Скрипт достаточно старый. Возможно, автор давно его модернизировал или, напротив, забросил. Но за неимением иного публичного примера я буду «терзать» труд Геннадия Калашникова, да простит он мне это! Итак, начнем подбирать форму построения скрипта для автоматизации.
Способ 1. Локальная работа от суперпользователя
Изобразим вариант его работы схематично на рис. 1. По вертикали вниз отложено течение времени. По горизонтали вправо – эскалация привилегий. Сисадмин сразу начинает работу в режиме суперпользователя на локальной системе. Соответственно в таком варианте все консольные команды без каких-либо значительных изменений могут быть положены в основу автоматического скрипта. Примерно так и сделано в [2]. Критике подобной практики посвящены многочисленные обсуждения в форумах специалистов, предполагаю, это уже прописано в учебниках, и у многих сисадминов такие приемы не используются. Или принято утверждать, что «не» используются. Положительным свойством такого подхода надо признать простоту и, как следствие, удобство отладки.
Рисунок 1. Локальная работа от суперпользователя
Но в инструментальном плане универсальные скрипты, пригодные для практического запуска на необследованных системах (здесь обсуждается администрирование, но возможен альтернативный, пользовательский взгляд: неизвестные скрипты на «своих» системах), категорически нельзя создавать в расчете на выполнение от имени суперпользователя, потому что всякая эскалация привилегий должна быть явной!
Способ 2. Локальная работа с эскалацией привилегий с помощью su
Схематично такой подход изображен на рис. 2. Можно так сказать, что это «общерекомендованный ортодоксальный» способ. Администратор работает локально от пользовательской учетной записи, и в случае необходимости выполняет привилегированные команды с помощью утилиты su, которая в обязательном порядке требует подтверждения полномочий путем введения парольной фразы. Все политкорректно, но в работу вносится неизбежная интерактивность. Ее можно ограничить, объединив все команды в один скрипт. Например, так можно использовать программу [2], запуская ее изначально от непривилегированного пользователя с помощью команды su. Но тогда будет потеряна избирательность эскалации прав. Резюме: непригодно!
Рисунок 2. Локальная работа с эскалацией привилегий с помощью su
Способ 3. Локальная работа с эскалацией привилегий с помощью sudo
Этому варианту отвечает рис. 3. Сейчас такой способ с легкой руки разработчиков Knoppix-а стал повсеместной практикой. Утилита sudo позволяет сменить права на суперпользовательские без ввода пароля, только лишь на основании ранее выполненной авторизации учетной записи, внесенной в файл настроек sudoers. Это самый правильный подход в создании локальных автоматических установочных скриптов. Предварительное занесение админской учетной записи в sudoers давно уже для многих системных администраторов стало нормой. У меня именно так.
Здесь уместно отметить, что корректно написанные руководства по установке могут практически без особого труда быть переложены в форму консольных команд. Например, статья [3] написана как раз с таким расчетом. И хотя все исполнение там идет от суперпользователя, команды составлены так, что интерактивность их минимальна. А подсистема эскалации прав не использована лишь потому, что ее описание не укладывалось в тему статьи. Забегая вперед, открою секрет – упомянутая работа [3] вообще бэк-порт из скрипта автоматической установки LDAP. Таким образом, при создании локальных административных скриптов желательно использовать явную, не интерактивную эскалацию привилегий с помощью sudo. И такая работа требует соответственной настройки локальной учетной записи системного администратора. А что если нужно выполнить команду на удаленной системе?
Рисунок 3. Локальная работа с эскалацией привилегий с помощью sudo
Способ 4. Удаленная работа с сепаратным исполнением команд
Думаю, не ошибусь, если предположу, что подобный способ используют очень и очень многие. Схематично он изображен на рис. 4.
Рисунок 4. Удаленная работа с сепаратным исполнением команд
Администратор, авторизуясь под локальной учетной записью на собственном компьютере, запускает на выполнение отдельные команды вида:
ssh staff@target sudo cmd1
Кто незнаком с такой работой, могут прочитать отличную статью на эту тему [4]. Дам лишь некоторые пояснения. Поскольку необходимо в конце концов написать скрипт для автоматического выполнения настроек, то потребуется обеспечить неинтерактивное выполнение всех команд. Для этого на целевой системе (target) должна быть создана служебная запись (назовем ее staff), внесенная в sudoers как:
stuff ALL = (root) NOPASSWD: ALL
и соответствующий ключ ssh должен быть записан в authorized_keys в директории ~/.ssh этой учетной записи. Вот тогда можно будет выполнять команды на удаленной системе с эффективностью локальных.
Но и тут есть некоторые недостатки:
- Такая схема требует наличия бесперебойного удаленного доступа во время выполнения всей серии команд, собранных в единую программу. Это может вызвать затруднения в настройке таких подсистем, где в процессе работы приходится менять параметры сетевых соединений. И это вне зависимости от того, что, возможно, в конце выполнения серии команд связь снова будет восстановлена.
- Вся работа состоит из серии открывающихся и потом завершающихся сеансов связи с удаленным хостом, что может привести к существенному замедлению в случае большого набора команд.
- В процессе отправки команд на удаленную систему через конвейер ssh происходит сложная синтаксическая трансляция. Ситуацию усугубляет возможное использование таких утилит, как sed и grep, требующих защиты своих служебных символов. Все это приводит к появлению возможных ошибок в зависимости от представления входных данных. И ошибки эти очень трудно обнаружить, поскольку строки, передаваемые через ssh на «ту» сторону, формируются налету, и даже отладочное протоколирование в виде echo не дает 100% правильной картины.
Именно такой способ настройки применяется на большей части моих серверов в течение нескольких лет. И перечисленные недостатки заставили «двигаться» в переборе способов далее.
Способ 5. Удаленная работа с групповым исполнением команд
Этот вариант, изображенный на рис. 5, отличается от предыдущего тем, что команды собираются в небольшие последовательности и записываются на удаленной стороне в скрипты через конвейерную передачу, например, так:
cat | ssh staff@target "cat >cmd.$$"
и потом запускаются на исполнение:
ssh staff@target "bash cmd.$$"
Внутри такого скрипта могут быть сформированы и отдельные команды и управляющие директивы bash. Такой способ позволяет значительно уменьшить замедление из-за многочисленных ssh-сессий. Уменьшается вложенность синтаксических преобразований, что позволяет построить более надежные алгоритмы защиты служебных символов консольных утилит.
Рисунок 5. Удаленная работа с групповым исполнением команд
Ну и, наконец, отладка в значительной степени упрощается, так как есть возможность сохранения истории запускаемых команд – надо просто дать скрипту cmd.$$ сериализуемое название и не удалять его после работы. Но проблема с возможным закрытием терминального окна все равно остается. Предполагаю, что многие, кто занимался подобной тематикой, пришли к внедрению этого способа, но и он не решает всех проблем.
Способ 6. Удаленная работа в автономном терминальном окне
Это, безусловно, самый изощренный и универсальный вариант удаленного выполнения команд (см. рис. 6). Команды не просто группируются во временный скрипт, но и запуск его происходит с помощью консольного оконного менеджера screen, например, так:
ssh staff@target "screen -S scr$$ -dm \"bash cmd.$$\""
Данным способом обеспечивается асинхронное и совершенно независимое выполнение последовательностей команд. И сами эти команды могут выполнять все действия, от изменения настроек сети до управляемой перезагрузки серверов. Работа программы автоматической настройки, построенная по такому способу, не зависит от возможных прерываний связи и может даже обеспечить конкурентное исполнение потоков заданий. Однако при такой организации теряется возможность получения кода возврата очевидным способом, да и не только. Даже протокол работы команд, который во всех ранее перечисленных способах можно было просто наблюдать на экране монитора, теперь становится недоступен. Значит, потребуется предусмотреть возможность сохранения кода возврата и, если нужно, протокола работы на удаленной стороне и обеспечить цикл ожидания завершения процесса screen, чтобы в итоге получить и то, и другое.
Рисунок 6. Удаленная работа в автономном терминальном окне
Выбор модели разработки
Из вышеперечисленных способов следует особо отметить способ 3 и способы 5-6.
Взаимосвязь способа 3 и обычного текстового описания уже была отмечена (см. [3]). И точно так же, как по текстовому пошаговому руководству, можно написать последовательность консольных команд, так и в обратную сторону – ничего не должно мешать написать инструкцию по скрипту установки.
Ну а 5-й и 6-й способы фактически производят отправку сформированного по способу 3 скрипта на удаленный хост и дальнейшее его выполнение. Иначе говоря, они являются технологической «оберткой» для все того же скрипта, полученного по текстовому руководству и предназначенного для локального исполнения. Причем здесь число инкапсуляций может быть произвольно увеличено. Например, отсылаемый скрипт может быть обработан обфускатором [5] для защиты ноу-хау системного администратора, производящего удаленную настройку и прочее, и прочее.
Таким образом, чтобы исключить излишнюю детализацию описания, последовательность разработки построю таким образом: инструкция, локальный скрипт по способу 3, удаленное исполнение по способу 5 или 6. Это не временный учебный план. Создание рабочих скриптов тоже лучше строить именно по такой схеме, начиная с текстового описания и продолжая локальным скриптом с явной эскалацией привилегий. Естественно, в конце цепочки разработки должна стоять некая продукционная система. Здесь предполагается, что это исполнитель удаленных скриптов по способу 5 или 6, разработку которого отложим на потом. То есть сейчас обсуждается лишь первый шаг данной цепочки преобразований, а именно:
От руководства к локальному скрипту
Начнем, как и обещано, с руководства. Например, с инструкции по настройке почтовой системы CentOS [6]. Хотя оно и так не велико, дополнительно упростим задачу, ограничившись только настройкой Postfix. В тексте [6] за это отвечает раздел 3.1. Актуальными являются лишь первый абзац этого раздела и 8 строк настроек в main.cf. Будем воспринимать данное руководство как техническое задание на разработку локального скрипта автоматической настройки (причем в точности с описанными параметрами).
Итак, текст просто указывает на место, где надо произвести настройки – в main.cf, а сами настройки должны быть следующими:
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
inet_interfaces = all
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
mynetworks = 192.168.0.0/24, 127.0.0.0/8
relay_domains =
home_mailbox = Maildir/
Выбор именно Postfix как объекта настройки не случаен. Этот smtp-сервер имеет встроенную утилиту конфигурирования postconf, что позволит обойтись без привлечения сложных комбинаций grep/awk/sed и так далее.
Попробуем построить скрипт согласно обобщенному алгоритму из части 2 [1]. Сначала подготовим все необходимые элементы. Настройка избранного параметра postfix производится следующим образом:
sudo postconf -e myhostname=mail.example.com
Предварительный опрос установленного ранее значения можно сделать как:
postconf myhostname
Хочу обратить внимание: эскалация прав требуется только для модификации. Все операции изменяют содержимое main.cf. Это значит, что данный файл надо будет сохранить до начала настроек, чтобы в случае возникновения проблем можно было произвести откат. Зависимый процесс – это сам postfix. Его перезапуск также очевиден:
sudo service postfix restart
Элемент проверки начальных зависимостей прежде всего выражается в анализе установки пакета postfix. Проверяется она с помощью команды:
rpm -q postfix
а ставится при необходимости как:
yum install postfix
Но поскольку данную зависимость можно, и нужно, устранить в процессе настройки, то процедуру проверки и установки postfix просто помещаем в самом начале исполнения. А вот другую зависимость, что требует средой исполнения считать CentOS, можно только проверить, и в случае несоблюдения завершить скрипт с ошибкой. Завершающее действие, которое должно присутствовать в разрабатываемом скрипте, – включение postfix в последовательность холодного старта.
Описывать пошаговое создание скрипта не позволит объем журнальной публикации, да и не стоит такая примитивная задачка детального рассмотрения. Даже если кому-то подобное внове, то после перечисленных выше наводящих подсказок с этой задачкой справятся, надеюсь, все. Потому сразу дам окончательный вариант, который почти в точности соответствует рекомендациям к построению таких алгоритмов из части 2 [1], за исключением того, что введены некоторые служебные макросы и добавлены служебные подпрограммы для сокращения текста. Итак, локальная форма настроечного скрипта представлена в листинге 1.
Листинг 1. Скрипт автоматический настройки postfix
null=/dev/null
cnf0=/etc/postfix/main.cf.$$
pconf=/usr/sbin/postconf
chkc=/sbin/chkconfig
abort() { echo $1 ; exit -1 ; }
abort2() { mv $cnf0 /etc/postfix/main.cf ; abort "$1" ; }
cnt=0 # счетчик срабатываний
checkconf() { $pconf $1 | awk 'BEGIN{FS="= "} {print $2}' | grep "^$2$" >$null ; }
fixconf() {
local t ; t=$2 ; [ -n "$t" ] || t=" "
checkconf $1 $t || {
sudo $pconf -e "$1=$t"
checkconf $1 $t || abort2 не могу настроить $1
cnt=$((cnt+1))
}
}
# 1- проверка зависимостей
grep CentOS /etc/redhat-release >$null 2>&1 || abort использовать только в CentOS
# 2- проверка пакетов
rpm -q postfix >$null || {
sudo yum install postfix
rpm -q postfix || abort не могу поставить postfix
cnt=$((cnt+1))
}
# 3- проверка параметров
sudo cp /etc/postfix/main.cf $cnf0
fixconf myhostname mail.example.com
fixconf mydomain example.com
fixconf myorigin "\$mydomain"
fixconf inet_interfaces all
fixconf mydestination "\$myhostname,localhost.\$mydomain,localhost,\$mydomain"
fixconf mynetworks "192.168.0.0/24,127.0.0.0/8"
fixconf relay_domains
fixconf home_mailbox Maildir/
# 4- проверка активности
LC_ALL=C $chkc --list postfix | grep $(runlevel | awk '{print $2}'):on >$null || {
sudo $chkc postfix on
LC_ALL=C $chkc --list postfix | grep $(runlevel | awk '{print $2}'):on >$null || abort2 не могу активировать postfix
cnt=$((cnt+1))
}
# 5- рестарт зависимых процессов
[ $cnt -gt 0 ] && sudo /sbin/service postfix restart >$null 2>&1
# 6- проверка зависимых процессов
sudo /sbin/service postfix status | grep pid >$null || {
mv $cnf0 /etc/postfix/main.cf
sudo /sbin/service postfix restart >$null 2>&1
sudo /sbin/service postfix status | grep pid >$null && abort установки не верны
# 7- система в неопределенном состоянии
echo срочно вызывайте из отпуска настоящего сисадмина ; exit -2
}
# 8- успешное завершение
rm $cnf0
echo smtp-сервер postfix настроен ; exit 0
В терминологии части 2 процедурам соответствуют фрагменты со 2-го по 4-й листинга 1, а сам скрипт соответствует решению. Конечно, не надо слишком серьезно относится к этому чисто учебному примеру, его практическая польза минимальна. Однако, учитывая что настройка smtp-сервера является очень распространенной и весьма типичной задачей и то, что в этом очень маленьком скрипте сведены воедино все характерные компоненты таких программ, то его вполне достаточно, чтобы сделать оценку подобного промежуточного результата.
Противоречия
Если сравнить исходный текст [6], абзац и восемь строк настроек, то очевидно, что программа получилась значительно большей. В нее были добавлены не учтенные в руководстве [6] обстоятельства и данные, потому что даже самое маленькое программное решение должно включать все, казалось бы, априорно очевидные элементы, которые обычно оставляются на усмотрение подготовленных читателей.
В ходе написания скрипта были сделаны некоторые стилевые преобразования для удобства программирования – применены служебные макросы, и часть повторяющегося кода вынесена в подпрограммы. На первый взгляд не видно, но такие макросы, как chkc=/sbin/chkconfig, на самом деле устанавливают скрытую зависимость от расположения утилиты chkconfig в системе. Более того, замена тривиальным $(which chkconfig) тоже будет платформеннозависимой. Например, в openSUSE такое будет работать. Там даже можно положиться на поиск по $PATH и не указывать путь вовсе. А вот в CentOS и RHEL для правильного исполнения надо использовать только описанный в скрипте код.
Следующим элементом, зависимым от предпочтений кодировщика, является группирование команд в подпрограммы. В тексте на одну страничку не опасно, но в большом проекте это может привести к скрытому увеличению вложенности кода. В сегменте «3- проверка параметров» можно увидеть, что передача значения $mydomain потребовала размещения защитного слеша перед знаком доллара. Но не все подпрограммы делают простую подстановку параметров. Возможно, где-то потребуется использовать конструкцию eval для подстановки значений в вызов awk, например (там знак доллара уже зарезервирован). То есть в таком случае надо точно знать глубину вложений и число разыменований, чтобы использовать нужное число защитных символов. Конечно, можно писать код без подпрограмм, но тогда даже подобная простейшая задача по трудоемкости ручного кодирования вырастет вдвое! Соответственно отладка и поиск ошибок точно так же усложнятся как минимум в два раза!
Можно поступить просто: в каждой подпрограмме проводить сканирование аргументов и размещение дополнительных защитных слешей, например, так: sed 's/\$/\\\$/'. Но это надо будет сделать для всех таких символов, для «$», для «'», для «\» и многих других, что отнюдь не упростит код. Все вышесказанное приводит к тому, что визуально выделить в полученном скрипте те самые 8 строк настроек очень трудно.
Синтаксис программы ничем не напоминает исходное руководство [6]. Если допустимо утверждать, что по руководству можно построить программу, то создать по такой программе руководство уже нельзя! Что это значит? Как минимум то, что такой скрипт затруднителен в понимании, не обладает свойством самодокументированности, сложен в отладке и, как следствие, тяжелый в сопровождении.
Давайте зафиксируем парадокс: из ясного и понятного руководства был создан малопонятный скрипт, который для своего использования требует наличия руководства уже к скрипту! Хотя изначально программа автоматизированного администрирования создавалась как эквивалент и замена документации. Проблема эта не надуманная. Учтите, типичный цикл разработки новых релизов платформ на основе GNU/Linux не превышает года. Значит, даже если не учитывать возможное портирование на другие совместимые платформы и выходы новых релизов использованного ПО, все равно минимум раз в год придется проводить повторное тестирование и отладку такого скрипта. А он может иметь нешуточные размеры в сотни килобайт текста! И это без учета требований из части 2, которые превратили 8 строк документации в 50 строк программы bash.
Обычно 8 строк в точности соответствуют 8 строкам в скрипте, как это сделано в программе [2]. Однако скрипт Геннадия Калашникова имеет объем 129 Кбайт! Он до сих пор доступен в Интернете, хотя скорее всего уже не актуален для новых версий программного обеспечения. А другие авторы, осознавая тщетность усилий, даже не переводят свои разработки в ранг релизов и изымают их из обращения, например, как это сделал Вячеслав Калошин [7]. Поскольку скрипт изъят автором из публичного доступа (в Интернете и рукописи горят!), то я могу лишь предложить на веру принять, что в этой программе объемом в 26272 байта вопрос настройки postfix решен в точности, как и в работе [2].
Таким образом, практика показывает, что требования полноты и универсальности в процессе разработки подобных программных проектов вступают в противоречие не только с требуемой логикой алгоритмов (см. [1]), но и очень часто приводят к невозможности развития или сопровождения получаемого программного продукта. Тут уже можно сознаться, что автоматизация системного администрирования таким путем недостижима.
***
Скриптовые консольные языки, например bash, обладают необходимой инструментальной и в какой-то степени синтаксической мощностью для решения задач системного администрирования. Но они не имеют, точнее, не содержат средств выражения или отражения специфической семантики предметной области. Спросите, а при чем тут семантика и зачем она нужна? А вот потеря «этой самой» семантики и ведет к тому, что по инструкции можно написать программу, а по программе создать инструкцию практически нельзя. И спорить с этим не надо! Иначе каждый второй проект с открытыми исходными текстами не страдал бы от отсутствия адекватной сопровождающей документации. Чтобы не создавать еще один «проект-сироту», займемся проблемами семантики. Снова посмотрим секцию «3- проверка...». Внутри скрипта часть аргументов командных строк содержат лексические повторы. Например, mail.example.ru включает в себя подстроку example.ru, которая также используется в качестве независимого аргумента. Очевидно, не просто так, потому что example.ru является доменом для mail. Данную семантическую связь можно отразить в макроподстановке:
tld=example.com
fixconf myhostname mail.$tld
fixconf mydomain $tld
Текст стал более читаемым? Отнюдь! Кроме того, простое перечисление макросов в начале скрипта (как в [7] – поверьте на слово) или в ini-файле, как в [2], решает проблему лишь отчасти, так как в проекте, включающем много скриптов-решений, такой подход создаст проблему стандартизации точно так же, как ее создает выделение части кода в общую библиотеку, приводящее к возникновению стандартов API.
Кроме того, example.ru не просто какая-то литеральная строка, это строка, написанная в соответствии с форматом FQDN. Поищите проверку форматов параметров в [2] – не найдете! Кто успел скопировать [7], чтобы изучить, тоже будут разочарованы. Пути решения всех перечисленных вопросов и проблем начинаются с создания так называемой параметрической модели информационной системы. Но об этом уже в следующей части!
- Барабанов А. Вычислительная модель. Введение в системное программирование. //Системный администратор, №12, 2009 г. – С. 18-22.
- Новость со ссылкой на пакет автоматической установки Open-Xchange, Samba PDC и проч., разработанный Геннадием Калашниковым (04.01.2006) – http://www.opennet.ru/openforum/vsluhforumID3/13191.html.
- Барабанов А. Размещаем пользовательские бюджеты в LDAP. Часть 1. //Системный администратор, №1, 2007 г. – С. 36-43.
- Барабанов А. Размещаем пользовательские бюджеты в LDAP. Часть 2. //Системный администратор, №2, 2007 г. – С. 32-37.
- Erdal Multu. Автоматизация системного администрирования с помощью ssh и scp. LinuxFocus.org (27.01.2003) – http://www.linuxfocus.org/Russian/January2003/article278.shtml.
- Сайт разработчика компилятора скриптов shc – Francisco Javier Rosales Garcia – http://www.datsi.fi.upm.es/~frosal.
- Руководство по настройке почтовой системы CentOS. Postfix HOWTO – http://wiki.centos.org/HowTos/postfix.
- Вячеслав Калошин aka multik/kiltum. Презентация «альфа-бета-гамма»-версии скрипта настройки виртуального сервера.(29.06.2007) – http://kiltum.livejournal.com/1185215.html.
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|