Каталог /etc на живом сервере редко остаётся в покое. Пакетный менеджер обновляет настройки при каждом обновлении, администратор правит конфиги служб, установочные скрипты дописывают свои строки в десятки файлов. Через пару недель уже трудно сказать, кто и когда сменил порт в sshd_config или вычеркнул строку из fstab. Утилита etckeeper ставит /etc под контроль системы версий git, записывает права доступа и владельцев каждого файла и сама делает коммиты в момент работы пакетного менеджера.
История изменений каталога /etc и практическая польза для администратора
Обновления пакетов приносят сюрпризы. Менеджер apt при замене конфигурационного файла предлагает выбор или сохраняет новую версию рядом со старой в файле с суффиксом .dpkg-dist, а иногда тихо оставляет оба варианта. Служба после такого обновления отказывается стартовать, и администратор начинает сравнивать копии вручную. Резервная копия недельной давности отвечает лишь на вопрос, как выглядел каталог раньше, но не показывает, какая из двадцати правок сломала загрузку.
Копии, снятые руками, быстро теряют ценность. Их никто не подписывает, между ними нет разницы в виде команды diff, а поиск нужной версии превращается в перебор архивов. С историей версий картина другая: каждое изменение получает автора, точное время и привязку к операции пакетного менеджера. Команда git log по одному файлу отвечает на вопросы, кто трогал sshd_config за последний месяц, какие строки менялись в nginx.conf и в какой момент появился лишний параметр. Для сервера, где конфигурацию правят несколько человек по очереди, такая прозрачность экономит часы разбора спорных ситуаций: не нужно гадать, чья правка добавила дублирующий блок в конфиг веб-сервера.
Сам каталог при этом широкий: в нём лежат параметры сетевых интерфейсов, расписание заданий в cron.d, настройки ssh в sshd_config, правила sudo, таблица монтирования fstab и sysctl.conf с ядерными параметрами. Половина этих файлов меняется пакетами, половина руками, и без журнала границу между ними через месяц уже не увидеть.
Хранилище версий внутри /etc и служебный файл с правами и владельцами
Хранилище живёт прямо в каталоге конфигурации: рядом с рабочими файлами появляется скрытый каталог .git. Центральный сервер не нужен, история доступна локально даже без сети, а привычные приёмы git работают без дополнительных настроек. Каждый файл в /etc попадает под наблюдение, а исключения перечисляются в .gitignore по обычным правилам git. Если какой-то путь не нужен в истории, его достаточно добавить в /etc/.gitignore, и последующие коммиты его пропустят.
Обычное хранилище версий хранит содержимое файлов и лишь бит исполняемости, но не владельца, не группу и не полный набор прав. Для исходного кода это мелочь, для /etc потеря половины смысла: сервер, где файлы ключей ssh получили чужого владельца или каталог cron.d лишился правильного режима доступа, будет работать с перебоями, а вернуть режимы одной командой git checkout не выйдет. etckeeper решает задачу отдельным служебным файлом .metadata: при каждом коммите туда записываются режим доступа, владелец и группа для каждого отслеживаемого пути, а при восстановлении эти сведения применяются обратно. Символьные ссылки тоже не теряются, их цели git хранит как содержимое.
Восстановление на этом строится в два шага: сначала содержимое файлов возвращается из хранилища, затем применяется .metadata, и права с владельцами встают на место. Без второго шага каталог после отката выглядел бы как после распаковки архива от имени обычного пользователя, а службы вроде ssh и sudo относятся к правам строго. Отдельно приятно, что метаданные едут вместе с историей: в клоне или в архиве хранилища сведения о правах не отделяются от содержимого.
Установка пакета и настройка режима автоматических коммитов
Установка в семействе Debian выглядит скромно: apt install etckeeper. Пакет сам инициализирует хранилище в /etc и сразу делает первый коммит с сообщением "initial commit", так что уже после установки вся текущая конфигурация зафиксирована. Заодно он ставит файл хука apt, добавляет ежедневное задание и подключает хук к dpkg. Настройки собраны в файле /etc/etckeeper/etckeeper.conf, основные значения там такие:
VCS="git"
HIGHLEVEL_PACKAGE_MANAGER=apt
LOWLEVEL_PACKAGE_MANAGER=dpkg
# AVOID_DAILY_AUTOCOMMITS=1
# AVOID_COMMIT_BEFORE_INSTALL=1
Параметр VCS задаёт систему версий: по умолчанию git, но поддерживаются mercurial, darcs и bzr. Строки HIGHLEVEL_PACKAGE_MANAGER и LOWLEVEL_PACKAGE_MANAGER подсказывают утилите, какие менеджеры пакетов используются в системе. Раскомментированный AVOID_DAILY_AUTOCOMMITS=1 отключает ежедневное автосохранение, AVOID_COMMIT_BEFORE_INSTALL=1 убирает коммит перед операциями пакетного менеджера. В семействе Red Hat пакет ставится тем же коротким именем, хуки подключаются к dnf, механика остаётся той же. Развёртывание на новом сервере укладывается в короткую последовательность шагов:
- поставить пакет командой apt install etckeeper и дождаться первого коммита в /etc;
- открыть etckeeper.conf, проверить значение VCS и при необходимости отключить лишние автоматические режимы;
- внести пробную правку в любой конфиг и выполнить etckeeper commit -m "проверка хранилища";
- убедиться командой git log, что история ведётся, а файл .metadata заполнился сведениями о правах и владельцах.
Если пакет ставился без инициализации или хранилище случайно убрали, всё возвращается вручную: etckeeper init создаёт .git в /etc, а следующий etckeeper commit -m "initial commit" фиксирует текущее состояние целиком.
Внутри /etc/etckeeper собраны небольшие каталоги со скриптами: init.d работает при создании хранилища, commit.d готовит каждый коммит и обновляет .metadata, pre-install.d и post-install.d сопровождают пакетные операции. При желании туда добавляются собственные шаги, и автоматизация расширяется без правки самой утилиты.
Автоматические коммиты при операциях apt через хук пакетного менеджера
Ключевая автоматизация устроена через хуки apt. Пакет кладёт файл /etc/apt/apt.conf.d/05etckeeper, в котором прописаны вызовы etckeeper pre-install перед транзакцией и etckeeper post-install после неё. Перед установкой или удалением пакетов apt выполняет pre-install, и если в /etc остались незакоммиченные правки, они сохраняются отдельным коммитом с сообщением "saving uncommitted changes in /etc prior to apt run". По завершении транзакции post-install фиксирует результат с сообщением "committing changes in /etc after apt run". Если изменений нет, лишний коммит не появляется.
Механика выручает и при спорных обновлениях. Дистрибутив иногда предлагает новую версию конфига вместо старой, и администратор соглашается, не успев разобраться. После транзакции разница между коммитом до и коммитом после видна одной командой, и лишние строки из свежего файла вычищаются за минуты, а не всплывают через неделю, когда служба уже ведёт себя странно.
Пара коммитов вокруг каждой операции пакетного менеджера рисует точные границы: состояние конфигурации накануне и состояние после. Забытая правка sshd_config не пропадёт, хук подберёт её и припишет к ближайшей операции apt. Посмотреть механику вживую можно по журналу:
cd /etc
git log --oneline -3
a1b2c3d committing changes in /etc after apt run
9f8e7d6 saving uncommitted changes in /etc prior to apt run
5544332 initial commit
Ежедневное автосохранение и ручные коммиты привычными командами git
Правки попадают в /etc не только при установке пакетов. Администратор добавляет задания в /etc/cron.d, трогает fstab перед подключением диска, меняет права на скрипты. Для таких ситуаций пакет ставит ежедневное задание /etc/cron.daily/etckeeper, которое раз в сутки делает коммит с сообщением "daily autocommit". Даже расслабленный режим, когда конфиги правятся месяц без единого ручного коммита, оставляет дневные точки восстановления. Кто предпочитает плотный контроль, отключает дневное задание параметром AVOID_DAILY_AUTOCOMMITS=1 из etckeeper.conf.
Ручная работа ничем не отличается от обычного проекта git, только репозиторий расположен в /etc:
cd /etc
git status
git diff ssh/sshd_config
git add ssh/sshd_config
git commit -m "включил вход по ключам и закрыл парольный"
etckeeper vcs log -2
Команда etckeeper vcs запускает систему версий прямо внутри каталога конфигурации: etckeeper vcs diff, etckeeper vcs status и etckeeper vcs log избавляют от переходов между каталогами и всегда работают с /etc, где бы администратор ни находился. Полезны и стандартные приёмы чтения истории: git log -p показывает правки целиком, а поиск по сообщениям коммитов быстро отделяет дневные автосохранения от содержательных действий. Перед рискованным экспериментом с конфигом полезно сделать коммит с внятным сообщением, тогда точка возврата будет под рукой даже без пакетных операций.
Кто именно менял строку, ищется по диффам: git log -p -- ssh/sshd_config показывает правки каждого коммита, а ограничение по датам через --since="1 month ago" отсекает давние изменения и оставляет свежие.
Отправка истории на центральное хранилище версий
Локальное хранилище спасает от случайных правок, но живёт на том же диске, что и сама конфигурация. Отказ диска стирает и /etc, и его историю разом. Лечится переносом истории на отдельную машину: в /etc добавляется удалённое хранилище, и коммиты периодически уходят туда командой git push.
cd /etc
git remote add origin backup:/srv/git/etc-web01.git
git push -u origin --all
Хранилище с историей конфигурации стоит держать закрытым. В /etc лежат ключи ssh, содержимое shadow и другие чувствительные файлы, поэтому доступ к удалённому хранилищу ограничивается тем же образом, что и доступ к резервным копиям. Обратный путь тоже рабочий: на новой машине достаточно установить etckeeper, положить содержимое хранилища в /etc и выполнить etckeeper init, права и владельцы подхватятся из .metadata при инициализации.
Перенос нетрудно автоматизировать: небольшой скрипт в /etc/cron.d делает push раз в сутки вслед за автосохранением, и история почти всегда лежит вне сервера. Клон хранилища на второй рабочей машине даёт запасную копию журнала и возможность сверить конфигурации двух серверов по коммитам.
Откат правки, аудит перед разбором инцидента и другие сценарии
Самый частый сценарий звучит так: вечером пятницы кто-то поправил конфиг nginx, утром сайт отвечает странно, и никто не помнит деталей. Ответ даёт журнал по конкретному файлу:
cd /etc
git log --pretty=fuller -- nginx/nginx.conf
git checkout 4c9f1e2 -- nginx/nginx.conf
etckeeper commit -m "откат правки nginx.conf к состоянию 4c9f1e2"
Вывод git log показывает автора, точное время и список правок, а checkout возвращает файл к выбранному коммиту, после чего изменение фиксируется обычным коммитом. Разницу между двумя операциями пакетного менеджера удобно смотреть командой git diff с двумя хешами: она покажет ровно то, что изменил apt, без ручных правок поверх. Аудит перед разбором инцидента строится на связке сообщений: коммиты с фразой про apt run очерчивают границы пакетных операций, дневные автосохранения задают фон, а ручные коммиты с внятными сообщениями объясняют намерения. Сопоставив моменты изменений со временем отказа службы, администратор быстро находит кандидата на источник проблемы.
Время коммита вокруг пакетной операции легко сопоставить с журналом пакетного менеджера: совпадающие минуты подсказывают, какой именно пакет затрагивал конфигурацию. Такая привязка экономит время на первом этапе разбора, когда нужно понять контекст сбоя, а не сразу искать виновника.
Завершающий совет по гигиене: правки стоит фиксировать небольшими коммитами с осмысленными сообщениями, тогда git log превращается в дневник сервера, а не в свалку безымянных строк. Если хранилище однажды станет не нужно, команда etckeeper uninit аккуратно уберёт служебные файлы из /etc. С etckeeper каталог /etc перестаёт быть местом, где правки исчезают без следа, и превращается в прозрачный журнал с точками возврата.