Книги, научные публикации

Москва. System Programming Introduction. Part 3. август 2007 - октябрь 2009 Барабанов А.Б.

Введение в системное программирование. Часть 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. Администратор, авторизуясь под локальной учетной записью на собственном компьютере, запускает на выполнение отдельные команды вида ssh staff@target sudo cmd Кто не знаком с такой работой, могут прочитать отличную статью на эту тему [4]. Дам лишь некоторые пояснения. Поскольку необходимо в конце-концов написать скрипт для автоматического выполнения настроек, то потребуется обеспечить неинтерактивное выполнение всех команд. Для этого на целевой системе (target) должна быть создана служебная запись (назовем её staff), внесенная в sudoers как stuff ALL = (root) NOPASSWD:

ALL и соответствующий ключ ssh должен быть записан в authorized_keys в директории ~/.ssh этой учетной записи. Вот тогда можно будет выполнять команды на удаленной системе с эффективностью локальных. Но и тут есть некоторые недостатки:

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

2.Вся работа состоит из серии открывающихся и потом завершающихся сеансов связи с удаленным хостом, что может привести к существенному замедлению в случае большого набора команд.

3.В процессе отправки команд на удаленную систему через конвейер ssh происходит сложная синтаксическая трансляция. Ситуацию усугубляет возможное использование таких утилит как sed и grep, требующих защиты своих служебных символов. Все это приводит к появлению возможных ошибок, в зависимости от представления входных данных. И ошибки эти очень трудно обнаружить, поскольку строки, передаваемые через ssh на ту сторону, формируются на лету и даже отладочное протоколирование в виде echo не дает 100% правильной картины.

Именно такой способ настройки применяется на большей части моих серверов в течение нескольких лет. И перечисленные недостатки заставили двигаться в переборе способов далее.

Диаграмма 4. Удаленная работа с сепаратным исполнением команд.

Способ 5. Удаленная работа с групповым исполнением команд.

Этот вариант, изображенный на диаграмме 5, отличается от предыдущего тем, что команды собираются в небольшие последовательности и записываются на удаленной стороне в скрипты через конвейерную передачу, например так cat | ssh staff@target "cat >cmd.$$" и потом запускаются на исполнение ssh staff@target "bash cmd.$$" Внутри такого скрипта могут быть сформированы и отдельные команды и управляющие директивы bash. Такой способ позволяет значительно уменьшить замедление из-за многочисленных ssh-сессий. Уменьшается вложенность синтаксических преобразований, что позволяет построить более надежные алгоритмы защиты служебных символов консольных утилит. Ну, и наконец, отладка в значительной степени упрощается, так как есть возможность сохранения истории запускаемых команд - надо просто дать скрипту cmd.$$ сериализуемое название и не удалять его после работы. Но проблема с возможным закрытием терминального окна все равно остается. Предполагаю, что многие, кто занимался подобной тематикой пришли к внедрению этого способа, но и он не решает всех проблем.

Диаграмма 5. Удаленная работа с групповым исполнением команд.

Способ 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/ 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.

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 не могу настроить $ 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 $cnf 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>& # 6- проверка зависимых процессов sudo /sbin/service postfix status | grep pid >$null || { mv $cnf0 /etc/postfix/main.cf sudo /sbin/service postfix restart >$null 2>& sudo /sbin/service postfix status | grep pid >$null && abort установки не верны # 7- система в неопределенном состоянии echo срочно вызывайте из отпуска настоящего сисадмина ;

exit - } # 8- успешное завершение rm $cnf echo smtp-сервер postfix настроен ;

exit Листинг 1. Скрипт автоматический настройки postfix.

В терминологии части 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 строк в точности соответствуют строкам в скрипте, как это сделано в программе [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], чтобы изучить, тоже будут разочарованы. Пути решения всех перечисленных вопросов и проблем начинаются с создания так называемой параметрической модели информационной системы. Но об этом уже в следующей части!

Использованные ссылки.

1.Барабанов А.Б. Введение в системное программирование. Часть 2. Вычислительная модель.

Системный администратор. №12, 2009.

2.Новость с ссылкой на пакет автоматической установки Open-Xchange, Samba PDC и проч., разработанный Геннадием Калашниковым. 04.01.2006.

3.Барабанов А.Б. Размещение пользовательских бюджетов в LDAP.

Системный администратор. №1-2, 2007.

4.Erdal Multu. Автоматизация системного администрирования с помощью ssh и scp.

LinuxFocus.org. 27.01. 5.Сайт разработчика компилятора скриптов shc - Francisco Javier Rosales Garca.

6.Руководство по настройке почтовой системы CentOS. Postfix HOWTO.

7.Вячеслав Калошин aka multik/kiltum. Презентация лальфа-бета-гамма версии скрипта настройки виртуального сервера. 29.06.    Книги, научные публикации