Многие системные администраторы хорошо знакомы с трудоемким процессом «ручного исправления» конфигурационных файлов. Да, вполне можно себе представить ситуацию, когда несколько администраторов ежедневно исправляют сотни конфигурационных файлов на десятках серверов. Расскажу о наиболее сложных проблемах, с которыми приходится сталкиваться, и о том, как с ними бороться.
Одна из классических проблем, с которой я часто встречался на практике, является самой распространенной и звучит так:
- «Только что работало, а сейчас не хочет» – возникает, как правило, когда во время редактирования случайно было задето что-то лишнее. Например, удалены несколько строк. Если эта оплошность не внесла нарушения в синтаксис файла, то устранить ее можно будет только после того, как станет известно «что» и «где» перестало работать. В другом примере, приложение не обладает встроенным контролем синтаксиса файла конфигурации, и отыскать несколько символов, по той или иной причине оказавшихся в неположенном месте, – задача не из легких. Особенно если в файле несколько тысяч строк.
- «Я его случайно удалил!» – другая проблема, чуть менее распространенная, но не менее неприятная. Вам повезло, и у вас есть бэкап! Но радоваться рано – еще предстоит вспомнить и воссоздать модификации, которые в бэкап не попали.
- «Мои изменения куда-то делись! Но я ведь проверял, что они сохранились!» – достаточно редкое, но весьма грустное явление. Такое зачастую происходит, когда один и тот же файл одновременно редактируется несколькими людьми с разных терминалов.
- «А правил ли кто-нибудь...» – когда никто ничего не трогал, но все упало. Файлы правятся не только администраторами, как мы знаем, но и менеджерами пакетов, различными утилитами и так далее. К счастью, проблемы такого класса встречаются не так уж часто.
- «Откатите, пожалуйста, назад изменения, которые вы делали на прошлой неделе» – кто-то действительно помнит, как было до этого? И снова бэкап, ленты, сравнение файлов, и т. д.
Все эти трудности связаны с тем, что мы хотим знать: кем, когда, что и как изменилось или изменяется в настоящий момент, а также помнить историю изменений.
Теория
На все вопросы «кем, когда, что и как» нам дает ответ любая система контроля версий. С ее помощью можно видеть все вносимые изменения, а также историю о том, кем и когда они были сделаны. И многое другое.
Система контроля конфигурационных файлов состоит из двух частей.
- Репозитарий. Внутри репозитария хранится вся история изменений со всей сопутствующей информацией.
- Файлы. Назовем их «последней версией, извлеченной из репозитария». Это те самые файлы, которые непосредственно используются приложениями (такими как Apache, Bind и т. д.). Содержимое каждого из них идентично последней версии, находящейся в репозитарии.
- Основной принцип сводится к тому, что после каждого изменения конфигурационного файла его новый вариант сохраняется в репозитарии.
- На сегодняшний день системы контроля версий сменили третье поколение:
- Локальные – системы способны контролировать отдельные файлы, каждый из которых обладает собственным файлом репозитария. Представителем этого класса является RCS (Revision Control System). Это первая в истории система контроля версий с открытым кодом, созданная еще в середине 80-х годов. Несмотря на свой возраст, она по-прежнему «широко используется в узких кругах».
- Централизованные – отличительной особенностью является способность хранить несколько файлов и каталогов в одном репозитарии, который может находиться удаленно. Это завоевавшие сердца миллионов CVS (Concurrent Versions System) и позднее пришедшая ей на смену SVN (Subversion).
- Децентрализованные системы – принципиально новый подход в решении задач версионности. В последнее время они все чаще начинают использоваться в проектах с очень внушительным количеством файлов и большими, зачастую территориально разделенными группами разработчиков. Наиболее заметными реализациями с открытым кодом стали GIT (создававшаяся в первую очередь для ведения процесса разработки ядра OS Linux) и Mercurial, появившиеся практически в одно время.
С точки зрения инструмента для работы с конфигурационными файлами основное отличие между всеми этими системами в большей степени заключается в удобстве и простоте использования. К примеру, установка контроля над файлом в системе RCS может быть достигнута всего одной командой. В Mercurial – минимум двумя. А в SVN это может превратиться в настоящую эпопею – если нужный файл имеет десяток вышестоящих в иерархии каталогов, каждый из которых придется последовательно инициализировать в системе.
Функции, позволяющие осуществлять хранение конфигурационных файлов в отдаленных (remote) репозитариях, практически неприменимы. Создание локального репозитария более целесообразно, поскольку в этом случае его функционирование не зависит от работоспособности сторонних систем и обеспечивает доступность данных в случае потери связи с «внешним миром».
Единственный случай, при котором перемещение репозитариев находит свое применение, – это синхронизация конфигурационных файлов нескольких серверов в системах с распределением нагрузки. Несмотря на то что этот способ синхронизации гарантирует идентичность изменений на каждом из серверов, применяется он крайне редко. Связано это с тем, что чаще всего конфигурация синхронизируется теми же средствами, что и основной контент.
Репозитарии
Каждый из репозитариев, созданных в соответствующей системе контроля версий, имеет особенности в механизме инициализации и внутреннем устройстве.
Репозитарий RCS создается отдельно для каждого нового файла и представляет собой файл с одноименным названием и дополнительным суффиксом (*,v), находящийся в том же месте, где и оригинальный файл конфигурации. Ситуацию можно немного улучшить, создав каталог RCS. В этом случае все репозитарии будут создаваться внутри этого каталога. Однако такой каталог должен располагаться непосредственно рядом с оригинальным файлом. Вследствие этого, на каждом из серверов со временем образуется достаточно большое количество таких каталогов, рассредоточенных по всей файловой системе. Пример получившейся структуры каталогов показан на рис. 1.
Рисунок 1. Репозитарий RCS
Рассматривая SVN, первое неудобство, с которым сталкиваешься, – необходимость хранить репозитарий отдельно от извлеченной текущей версии файлов. Далее. Каждый извлеченный из репозитария каталог (отдельный файл не может быть добавлен без каталога, в котором находится!) содержит папку с метаданными репозитария (.snv). И этими метаданными ваша система наводняется гораздо сильнее, чем каталогами RCS (см. рис. 2).
Рисунок 2. Репозитарий Subversion
Самыми элегантными репозитариями, как оказалось, обладают децентрализованные системы. Всего один-единственный каталог в корне репозитария, который содержит в себе «все» (.hg – у Mercurial). Достаточно инициализировать корневой каталог сервера («/») и добавлять столько файлов, сколько потребуется. Никакого дополнительного мусора в системе нет – все в одном месте, в репозитарии (см. рис. 3).
Рисунок 3. Репозитарий Mercurial
Нюансы
Как вы думаете, нужно ли проверять состояние файла до того, как вы начинаете с ним работать? Ответ утвердительный.
Система RCS требует, чтобы перед работой файл был извлечен из репозитария, а после окончания помещен обратно. Если попытаться достать файл из репозитария дважды, будет выведено предупреждение. Но никаких предупреждений мы не увидим, если начнем извлекать файл, измененный до извлечения! Так происходит, например, в ситуациях, когда изменения нужно сделать очень срочно и терять драгоценное время на лишние команды нельзя.
Нюанс заключается в том, что если сразу не довести начатое до конца (сохранить в репозитарий или, как минимум, не заблокировать), то с вероятностью 90% эти изменения будут уничтожены руками ваших коллег. Оставшиеся 10% приходятся на бдительных администраторов, которые первым делом проверяют статус (так как RCS не выводит информацию о наличии изменений, это можно сделать, сравнив текущий файл с последней его версией в репозитарии). Все более поздние системы лишены таких сложностей.
Второй нюанс. Децентрализованные системы ориентированы на работу со всем репозитарием, а не отдельным файлом. Так что одно изменение может затрагивать несколько файлов. Это очень удобно. Нюанс заключается в том, что в ревизию попадают не только измененные файлы, а весь «конфигурационный состав». Абсолютно все контролируемые системой файлы! Это значит, что при извлечении определенной ревизии целиком (checkout) – абсолютно все файлы вернутся к состоянию, соответствующему выбранной ревизии. Такой нюанс требует хорошего понимания механизмов работы репозитария, но не является препятствием к использованию децентрализованных систем. Далее я на примере покажу, как извлечь отдельный файл.
Третий немаловажный нюанс – отсутствие возможности контролировать права доступа к файлам. Это касается как традиционных прав доступа, так и Access Control Lists (ACL – расширение для файловой системы, позволяющее более гибко управлять правами доступа). Подвержены этому все упомянутые ранее системы.
Если описать кратко, единственное, что сохраняется в метаданных о правах доступа, – это является ли файл запускаемым или нет. В остальном все извлекаемые файлы создаются точно так же, как если бы они создавались текстовым редактором. Кроме того, RCS изменяет право на запись в зависимости от состояния блокировки. Запись разрешается, только если файл заблокирован.
Нюанс с правами очень важен, если затрагиваются такие файлы, как /etc/shadow. Эту ситуацию администраторы вынуждены решать собственными силами. Кто-то прибегает к созданию файлов, в которые записывают все права, и добавляют в репозитарий.
Некоторые идут дальше и пишут скрипты для автоматизации процесса. Так на свет появилась утилита etckeeper (http://joey.kitenet.net/code/etckeeper) – созданная любителями дистрибутива Debian для реализации контроля над правами и автоматического перемещения изменений в репозитарий (операция commit) после обновления пакетов в операционной системе. К радости некоторых администраторов, etckeeper способен работать c Git, Mercurial и Bazar.
В моей практике проблеме с правами уделяется не много внимания, поскольку я стараюсь контролировать версионность исключительно тех файлов, которые действительно в этом нуждаются (файлы типа shadow или group к ним не относятся). Вы можете возразить, но мой аргумент – хорошая система бэкапа, а информация о том, как файл shadow выглядел пару лет назад, не представляет никакого интереса.
Последним нюансом будет отсутствие функционала блокировок у децентрализованных систем. Механизм не вписывается в концепцию децентрализации. К сожалению, даже etckeeper эту проблему не решает.
Кому отдать предпочтение
Вместе со мной в компании работают порядка пяти сотен программистов. Разработка ведется на множестве языков (от PHP и JAVA до С) и для самых разнообразных платформ (от Windows до Solaris и Z/OS). При выборе системы контроля версий в качестве корпоративного стандарта нам хотелось найти систему с открытым кодом, которая могла бы стать единой для всех.
Учитывая то количество проблем и нюансов, которые в моей практике удалось разрешать только при помощи децентрализации (например, с ее помощью удалось избежать остановки процесса разработки в региональных представительствах при длительном отсутствии связи с центральным офисом), вопрос о выборе типа репозитариев даже не рассматривался.
В свете «единой для всех» система должна быть кроссплатформенной. И в этом плане в лидеры вышел Mercurial благодаря наличию клиентов под множество ОС.
Далее Mercurial выделился привычным для пользователей Subversion интерфейсом и хорошей документацией. Это серьезно облегчает переход на новые «рельсы» тем, кто ранее работал с CVS или SVN.
Финальным аккордом стал проект TortoiseHG (http://tortoisehg.sourceforge.net) – клиент для MS Windows, интегрирующийся с Internet Explorer (многие почему-то к нему быстро привыкают).
Единственной преградой на пути интеграции Mercurial для нужд администраторов оставался механизм блокировок, а точнее его отсутствие. Ее удалось обойти «малой кровью» – написав небольшой скрипт, который я назвал hglock (сообразно названиям оригинальным командам системы). Актуальную версию можно скачать по ссылке http://primtech.ru/files/hglock. Возможно, стоит организовать такой функционал в виде дополнительного модуля. Но пока это не было сделано. Позже был добавлен механизм автоматической разблокировки при сохранении изменений в репозитарии. И после этих изменений нам больше ничто не препятствовало.
Нельзя однозначно сказать, какую из систем использовать лучше. Главное, чтобы решалась основная задача – «контроль за изменениями» и «ничего не терялось». А в остальном – это дело привычки или конкретной ситуации. Могу лишь добавить, что чем проще с системой работать, тем меньше шансов наделать ошибок.
Для администраторов, которые раньше не сталкивались с системами контроля версий, а также для небольших организаций, я рекомендую сначала попробовать RCS. Он самый простой как в плане понимания, так и в плане применения.
Интеграция
Для того чтобы описываемый метод работы стал не обузой, а эффективным инструментом, достаточно придерживаться нескольких правил:
Во-первых, система контроля версий должна применяться только к файлам, которые являются уникальными для данной системы и имеют версионную ценность.
Как я уже упоминал ранее, нет необходимости хранить файлы с паролями, группами или сетевыми настройками. Также нет смысла сохранять файлы, которые для большинства серверов идентичны. Например, настройки почтовой маршрутизации или правила авторизации пользователей (PAM, LDAP, SSH... ). Сюда же я отношу все файлы, которые всегда прибывают в том состоянии, в каком были установлены вместе с системой. В итоге остается только очень небольшое число файлов, с которыми действительно постоянно приходится работать. Среди них named.conf, nginx.conf, crontab (в компании, где я работаю, рядовым пользователям запрещено самостоятельно создавать записи в cron, это делается администраторами по заявке строго определенной формы), на почтовых серверах – конфигурация почтовых систем, на брандмауэрах (firewall) – непосредственно настройки фильтрации и т. д.
Во-вторых, необходимо, чтобы все администраторы в вашей команде также следовали выбранной методологии. Другими словами, необходим регламент, описывающий правила работы с конфигурационными файлами.
Для примера успешной интеграции на основе описанных правил могу сказать, что в одной очень крупной компании с помощью RCS команда из шести администраторов управляет конфигурацией приблизительно трехсот UNIX-серверов.
Практика
Практика показывает, что нагляднее всего – несколько простых примеров.
Предполагается, что все задействованные в примере конфигурационные файлы разрешено корректировать только пользователям, обладающим соответствующими привилегиями. Все приведенные ниже команды выполняются с привилегиями суперпользователя (root).
Обращаю ваше внимание на то, откуда берутся имена пользователей, сохраняемые в репозитарии. При повышении привилегий пользователя изменяется только «эффективный» (effective id) идентификатор, а «реальный» (real id) остается неизменным. Скрипт hglock использует именно «реальный» идентификатор, то есть имя пользователя (login), использовавшееся при входе в систему. Mercurial же извлекает имя пользователя из переменных окружения, а также позволяет определять его самостоятельно в файле ~/.hgrc. Это означает, что не следует использовать методы повышения привилегий, изменяющие оригинальное окружение (например «su – »). В ином случае авторство будет приписано пользователю root.
Итак, демонстрация работы администратора будет показана на сервере под управлением ОС Solaris.
Прежде всего убедимся в том, что Mercurial установлен и функционирует.
$ hg --version
Mercurial Distributed SCM (version 0.9.5)
. . .
Несколько предварительных персональных настроек пользователя:
$ id
uid=100(john) gid=1(other)
$ echo >~/.hgrc<<EOF
> [ui]
> editor = vim
> fallbackencoding = UTF-8
> username = John Doe <john@example.com>
EOF
Повышаем уровень привилегий:
$ su
Password:
# id
uid=0(root) gid=0(root)
Убедимся, что реальный идентификатор при этом остался неизменным:
# who am i
john pts/2 Nov 20 18:00 (172.30.0.215)
Теперь можно создать репозитарий. Так как в нашем примере конфигурационные файлы выходят далеко за рамки /etc, логично будет инициализировать корневой каталог:
# hg init /
По умолчанию созданный репозитарий доступен для чтения абсолютно всем. В тех случаях, когда планируется поместить в репозитарий файлы, содержащие закрытую информацию (например, ключи ssh), рекомендую применить самый старый и проверенный способ: установить ограничения доступа на уровне файловой системы.
Закроем весь репозитарий от посторонних глаз:
# chmod 700 /.gh
# ls -ld /.hg
john pts/2 Nov 20 18:00 (172.30.0.215)
Если возникает необходимость скрыть только определенный файл, права нужно изменять не у репозитария, а у соответствующего файла в каталоге /.hg/store/data . Например, файлу /etc/inet/hosts будет соответствовать файл /.hg/store/data/etc/inet/hosts.i.
Установим скрипт работы с блокировками и внесем соответствующие настройки в репозитарий:
# cp hglock /usr/bin/
# cat >>/.hg/hgrc<<EOF
> [hooks]
> port-commit = hglock -C
> post-add = hglock -A
> EOF
Если мы попробуем вывести статус репозитария, то вопреки нашим ожиданиям процесс займет несколько минут, а в результате нашему вниманию будет продемонстрирован список абсолютно всех файлов сервера.
Следующая команда решает эту проблему. Она заставляет игнорировать все файлы, о которых нет записей в репозитарии.
# echo `^` >/.hgignore
Теперь пришло время добавить несколько файлов. Обращаю внимание на то, что в Solaris файл /etc/hosts является символической ссылкой на /etc/inet/hosts.
# hg add /etc/inet/hosts
# hg add /usr/local/apache2/conf/httpd.conf
# hg add /var/spool/cron/crontabs/root
Посмотрим, как теперь выглядит вывод статуса:
# hg status
A etc/inet/hosts
A usr/local/apache2/conf/httpd.conf
A var/spool/cron/crontabs/root
Сейчас – в момент добавления файлов – мы можем убедиться, что автоматически установилась блокировка:
# cat /.hg/locksfile
etc/inet/hosts:john:15:47 2008.11.20
usr/local/apache2/conf/httpd.conf:john:15:47 2008.11.20
var/spool/cron/crontabs/root:john:15:47 2008.11.20
Хорошо. Помещаем файлы в репозитарий:
# hg commit -m "Init"
Посмотрим на блокировку теперь:
# cat /.hg/locksfile
Отлично.
При помощи следующей нехитрой команды в любой момент можно узнать о том, какие из файлов уже находятся в репозитарии:
# hg locate
etc/inet/hosts
usr/local/apache2/conf/httpd.conf
var/spool/cron/crontabs/root
Все пути к файлам указываются от корня репозитария. Если сомневаетесь, в каком из репозитариев находитесь, поможет команда:
# hg root
Все самое сложное позади. Репозитарий настроен, файлы добавлены. Теперь покажу пару несложных команд (по сути, их действительно только две), которые впишутся в вашу повседневную администраторскую жизнь.
Первая команда. Перед тем как изменять /etc/hosts, заблокируем его:
# hglock /etc/inet/hosts
Если же вас кто-то опередил, вы получите сообщение:
# hglock /etc/inet/hosts
File already locked by "other_user" at 19:00 2008.11.20.
Изменим hosts:
# cd /etc/inet
# sed –e "s/oldalias/newalias/" hosts > /tmp/1
# mv /tmp/1 hosts
Наличие изменений теперь отразится в hg status:
# hg status
M etc/inet/hosts
Вторая команда – это нечто иное, как commit.
# hg commit –m "first change" /etc/inet/hosts
В принципе имя файла можно опустить. Но так как hglock не проверяет, кем заблокирован файл, то есть опасность убрать еще и чужую блокировку.
Давайте рассмотрим еще несколько часто используемых команд.
Кем вносились последние изменения:
# cd /etc/inet
# hg log hosts
changeset: 1:3d8a608232b9
tag: tip
user: John Doe <john@example.com>
date: Thu Nov 20 00:46:46 2008 +0300
summary: first change
changeset: 0:9fe2667c2ab3
user: John Doe <john@example.com>
date: Thu Nov 20 00:44:37 2008 +0300
summary: Init.
Мы видим первую строчку комментария «first change» и номер последней ревизии (changeset) 1.
Давайте посмотрим, что в этой ревизии было нами изменено.
# hg diff -r 0 -r 1
diff -r 9fe2667c2ab3 -r 3d8a608232b9 hosts
--- a/hosts Thu Nov 20 00:44:37 2008 +0300
+++ b/hosts Thu Nov 20 00:46:46 2008 +0300
@@ -2,4 +2,4 @@
# that require network functionality will fail.
127.0.0.1 localhost.localdomain localhost
::1 localhost6.localdomain6 localhost6
-10.32.0.69 oldalias
+10.32.0.69 newalias
Как вы видите, была изменена запись с «10.32.0.69 oldalias» на «10.32.0.69 newalias».
И еще. Скажу о том, как восстановить файл, если его случайно удалили. Потому как путь к его восстановлению не совсем очевиден. Этой же командой можно извлечь только один из файлов указанной ревизии.
# rm hosts
# hg log hosts
changeset: 1:3d8a608232b9
tag: tip
user: John Doe <john@example.com>
date: Thu Nov 20 00:46:46 2008 +0300
summary: first change
changeset: 0:9fe2667c2ab3
user: John Doe <john@example.com>
date: Thu Nov 20 00:44:37 2008 +0300
summary: Init.
# hg cat -r 1 hosts > hosts
# cat hosts
# Do not remove the following line, or various programs
# that require network functionality will fail.
127.0.0.1 localhost.localdomain localhost
::1 localhost6.localdomain6 localhost6
10.32.0.69 newalias
Теперь наш файл снова на месте. У нас снова все прекрасно работает. Надеюсь, что теперь и у вас тоже.