Журнал изменений сервера: как вести, чтобы через полгода найти виновника
Сервер начинает вести себя странно спустя недели после того, как в него что-то поменяли: подрос p99 у одного эндпоинта, раз в сутки отваливается воркер, бэкапы стали занимать вдвое больше места. Вы открываете конфиги и не понимаете, было это всегда или появилось на прошлой неделе — потому что никто не записывал, что и когда менялось. Журнал изменений решает именно эту задачу: не найти «кто виноват» для наказания, а быстро сузить круг подозреваемых конфигов до нескольких строк, когда счёт идёт на часы простоя.
Содержание
Что именно писать в журнал
Журнал изменений — это не лог команд из истории shell и не diff в git сам по себе. Это структурированная запись о namerenii: что вы хотели изменить и зачем, а не только что фактически произошло. Минимальный набор полей, без которого запись бесполезна через полгода:
- Дата и время — обязательно в UTC или с явным указанием таймзоны. Смешение локального времени сервера и времени клиента — частая причина, почему сопоставление с логами потом не сходится на час-два.
- Кто менял — логин или ФИО, а не «admin» из общей учётки. Если на сервере несколько человек заходят под одним root, это первое, что стоит исправить до того, как заводить журнал — иначе поле «кто» всегда будет пустым по смыслу.
- Что именно изменено — конкретный файл, параметр, сервис, версия пакета. Не «правил nginx», а
nginx.conf: worker_connections 1024 → 4096. - Зачем — короткое обоснование в одно-два предложения. Это самое важное поле, и его чаще всего пропускают. «Увеличили лимит подключений» — бесполезная запись. «Увеличили worker_connections, потому что при пиковой нагрузке в пятницу видели connection refused в логах nginx» — запись, по которой через полгода понятно, было ли решение обоснованным и стоит ли его пересматривать.
- Как откатить — если изменение нетривиальное, одна строка с командой отката экономит время именно в момент, когда думать некогда.
Пример хорошей записи в plain-текстовом журнале:
2026-08-19 14:32 UTC | ivanov | nginx: worker_connections 1024 -> 4096 в /etc/nginx/nginx.conf
Причина: connection refused в access-логе при пиковой нагрузке 2026-08-15, см. graylog query #4471
Откат: вернуть 1024, systemctl reload nginx
Плохая запись — это просто факт без причины: «поменял конфиг nginx». Она хуже, чем git log, потому что не добавляет ничего, чего не было бы в diff'е, а время на её написание потрачено.
Почему «запишу потом» не работает
Ручной журнал, который ведётся отдельным шагом «после того как всё заработает», в реальности не ведётся. Причина не в лени, а в том, что момент внесения изменения и момент, когда стоило бы его записать, — это разные ментальные состояния. Пока вы правите конфиг под давлением (сайт лежит, клиент звонит), мысль записать происходящее в отдельный файл проигрывает мысли «сначала почини». А когда всё починено, писать about it уже не хочется — стресс снят, мозг переключился на следующую задачу.
Есть и более скучная причина: если запись в журнал — это отдельное действие, не привязанное ни к чему техническому, оно физически ничем не блокируется. Вы можете применить конфиг и уйти пить кофе, не открыв файл журнала вообще, и ничего не сломается прямо сейчас. Сломается позже, когда кто-то (возможно, вы сами) будет искать причину странного поведения и упрётся в пустой или устаревший журнал.
Вывод простой и практический: журнал должен быть побочным продуктом самого процесса внесения изменения, а не отдельной задачей в чек-листе. Если для того, чтобы применить изменение, вам физически нужно сначала написать, что и зачем вы делаете — журнал будет вестись сам, потому что иначе изменение не применится.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверGit как основа журнала для конфигов
Если конфигурация сервера уже лежит в git (через Ansible, обычный репозиторий /etc под etckeeper или руками), у вас уже есть 80% журнала — просто он не используется как журнал, потому что коммит-сообщения формата fix или update config не несут информации.
Первый шаг — сделать так, чтобы коммит без вменяемого сообщения физически не проходил. Простой pre-commit хук:
#!/bin/sh
# .git/hooks/commit-msg
MSG_FILE=$1
MSG=$(head -1 "$MSG_FILE")
if [ ${#MSG} -lt 15 ]; then
echo "Коммит-сообщение слишком короткое. Опишите ЧТО и ЗАЧЕМ, не только что поменяли." >&2
exit 1
fi
if ! grep -qE '(^|[^a-zA-Zа-яА-Я])(#[0-9]+|тикет|причина|reason)' "$MSG_FILE"; then
echo "Внимание: в сообщении нет ссылки на тикет или явного 'причина: ...'" >&2
# можно сделать exit 1, если хотите жёстко требовать
fi
Формат коммит-сообщения, который работает на практике — не Conventional Commits ради самих Conventional Commits, а обязательные секция «что» в первой строке и «зачем» в теле:
nginx: увеличен worker_connections 1024 -> 4096
Причина: connection refused при пиковой нагрузке 15.08, graylog #4471
Откат: git revert <hash>, systemctl reload nginx
Проверено: nginx -t перед применением, staging выдержал x1.5 нагрузки
Для конфигов вне репозитория приложения (системные файлы в /etc) неплохо работает etckeeper — он автоматически коммитит /etc в git при установке пакетов через apt/dnf-хуки и позволяет коммитить руками остальное:
apt install etckeeper
cd /etc && git log --oneline -- nginx/nginx.conf
Это даёт минимальный журнал бесплатно: кто и когда менял системные файлы, даже если человек не думал о журнале вообще. Дальше остаётся приучить команду добавлять причину в коммит вручную поверх автоматического коммита от etckeeper — про сам подход хранения конфигов в git и применения через пайплайн вместо правки по SSH подробно разобрано в статье про CI/CD для конфигов и GitOps-подход.
Изменения, которые не проходят через git
Не всё меняется через репозиторий — есть руками накатываемые пакеты, разовые команды в интерактивной сессии, изменения через панель управления хостинга, ручная правка /etc/hosts, добавление cron-задачи через crontab -e. Для этого нужен обёрточный инструмент, а не сила воли.
Практичный вариант — shell-функция, которая требует сообщение прежде, чем выполнить команду с sudo, и параллельно ведёт запись:
# добавить в /etc/profile.d/changelog.sh или в .bashrc для админов
chlog() {
if [ -z "$1" ]; then
echo "Использование: chlog \"что и зачем меняете\""
return 1
fi
printf '%s | %s | %s\n' "$(date -u +'%Y-%m-%d %H:%M UTC')" "$(whoami)" "$1" \
>> /var/log/server-changelog.log
echo "Записано в /var/log/server-changelog.log. Теперь выполняйте изменение."
}
Использование:
chlog "Добавлен cron на очистку /tmp раз в сутки, диск был заполнен на 92% (см. мониторинг за 18.08)"
crontab -e
Это не блокирует физически (человек всё равно может обойти функцию), но создаёт понятный ритуал: «сначала одна команда с описанием, потом само действие» — гораздо ниже порог трения, чем «открыть отдельный файл журнала в другом терминале и написать туда абзац текста». Если хотите жёстче — заверните применение конфигов через systemd ExecStartPre, который требует непустой файл CHANGE_REASON перед reload, или через wrapper-скрипт деплоя, который прерывает выполнение при пустом аргументе --reason.
Более надёжный, но и более тяжёлый вариант — обязательные тикеты. Если у вас уже используется Redmine, YouTrack или GitLab Issues, требование «нет тикета — нет доступа к продовому серверу» решает проблему структурно: тикет и есть журнал, а привязка коммитов к номеру тикета (git commit -m "nginx tuning, refs #421") даёт возможность потом искать по трекеру, а не по grep в текстовом файле.
Где физически хранить журнал
Вариант хранения зависит от размера команды и от того, что уже используется. Сравнение практических вариантов:
| Вариант | Плюсы | Минусы |
|---|---|---|
Текстовый файл на сервере (/var/log/server-changelog.log) | Просто, всегда под рукой, grep работает мгновенно | Теряется при переустановке сервера, легко случайно затереть, нет прав доступа на запись отдельно от чтения |
| Git-репозиторий конфигов (etckeeper / IaC) | История привязана к самим файлам, diff виден сразу, есть blame | Не годится для изменений вне файловой системы (перезапуск сервиса, ручная команда в БД) |
| Wiki / Confluence / Notion | Хорошо для крупных изменений и планового обслуживания, читаемо для не-инженеров | Легко забыть открыть отдельно от терминала, поиск по датам неудобен |
| Тикет-система (Redmine, Jira, GitLab Issues) | Обязательность через процесс, привязка к коммитам, история обсуждения | Избыточно для мелких рутинных правок, если тикет заводить дольше, чем делать само изменение |
На практике лучше всего работает комбинация: git для конфигов, текстовый chlog для разовых команд вне репозитория, тикет — только для изменений, требующих согласования. Не сводите всё к одному инструменту ради красоты — важнее, чтобы запись реально происходила там, где меняется конкретная вещь.
Копию журнала стоит держать вне самого сервера — синхронизировать /var/log/server-changelog.log в git или отправлять через syslog на центральный лог-сервер. Если сервер ляжет, журнал не должен лечь вместе с ним. О том, что ещё стоит документировать по серверу помимо журнала изменений — конфигурацию, топологию, контакты — см. статью про документацию сервера.
Ловим изменения, которые прошли мимо процесса
Даже с хорошим процессом кто-то рано или поздно поправит конфиг в обход журнала — в панике, по забывчивости, или потому что это был человек, который процесса не знал. Стоит держать независимый способ увидеть фактические изменения файловой системы, не полагаясь на добросовестность записи.
Простой вариант — auditd, который логирует обращения к конкретным файлам и каталогам на уровне ядра:
apt install auditd
auditctl -w /etc/nginx -p wa -k etc_nginx_changes
auditctl -w /etc/systemd -p wa -k systemd_changes
# посмотреть, кто и когда трогал файлы за последние сутки
ausearch -k etc_nginx_changes -ts today
Это не заменяет журнал с обоснованием «зачем», но ловит сам факт изменения независимо от того, записал его человек или нет — своего рода контрольная сумма для процесса. Для систем на пакетах полезно периодически сверять состояние с тем, что должно быть по пакетному менеджеру:
# Debian/Ubuntu — какие конфиги отличаются от исходных из пакета
dpkg -V | grep -v '^..5'
# для точечной проверки одного файла
md5sum /etc/nginx/nginx.conf
dpkg -V nginx | grep nginx.conf
Расхождение говорит о ручной правке, которую стоит найти в журнале — а если её там нет, это сигнал, что процесс где-то не сработал.
Как искать причину по журналу, когда что-то сломалось
Смысл всего процесса раскрывается именно здесь. У вас есть момент, когда симптом начался — обычно из мониторинга или из первых жалоб — и есть журнал. Задача не «найти виновного», а сузить список изменений до тех, что попадают в разумное окно перед началом проблемы.
Порядок действий на практике:
- Зафиксируйте точное время начала аномалии. Не «примерно на неделе», а конкретный момент по графику в Grafana/Zabbix/Uptime Kuma или по первой строке ошибки в логе. Если точного момента нет — берите окно между «точно было хорошо» и «точно стало плохо».
- Выгрузите изменения за окно с запасом. Аномалия часто проявляется не сразу — эффект от изменения worker_connections или лимита файловых дескрипторов может дать о себе знать через дни, когда накопится достаточно соединений. Смотрите минимум на 2-4 недели назад от начала проблемы, а не только на последний день.
# git-конфиги
git log --since="2026-08-01" --until="2026-08-20" --oneline -- /etc/nginx
# текстовый журнал
awk -v start="2026-08-01" -v end="2026-08-20" \
'$1 >= start && $1 <= end' /var/log/server-changelog.log
# системный лог пакетов Debian/Ubuntu
grep -A2 " install \| upgrade " /var/log/apt/history.log
# journalctl за конкретное окно, если ищете по конкретному сервису
journalctl -u nginx --since "2026-08-01" --until "2026-08-20"
- Сопоставьте список изменений со списком того, что физически могло повлиять на симптом. Если проблема сетевая — смотрите на изменения firewall, sysctl, DNS. Если это память — на изменения в лимитах сервисов, systemd unit-файлах, добавленные контейнеры. Не нужно проверять все изменения подряд — журнал с полем «что» позволяет быстро отфильтровать нерелевантные.
- Проверяйте гипотезы откатом в staging, а не в проде напрямую. Если журнал даёт кандидата, воспроизведите старое значение на тестовом окружении и сравните поведение — прежде чем откатывать боевой сервер вслепую.
- Зафиксируйте результат расследования там же, в журнале или связанном тикете. Даже если причина оказалась не в конфиге, а, например, во внешнем провайдере — эта запись экономит время в следующий раз, когда кто-то будет искать похожий симптом. Подробный разбор того, как оформлять сам разбор произошедшего — с хронологией, корневой причиной и action items — есть в статье про написание разбора инцидента.
Журнал хорош ровно настолько, насколько дисциплинированно фиксируется реальное намерение, а не задним числом подогнанное объяснение. Если расследование регулярно упирается в правки без обоснования — это не повод обвинять того, кто их вносил, а сигнал вернуться к процессу. Частая причина — прямые правки на проде в обход процесса вообще; если это встречается регулярно, см. разбор антипаттерна с правкой конфигов прямо на проде.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Нужен ли журнал изменений, если всё уже в Ansible и версионируется в git?
Git даёт diff и время коммита, но не всегда даёт «зачем» — если команда не приучена писать содержательные коммит-сообщения, это просто история фактов без обоснования. Git плюс дисциплина в сообщениях коммитов уже закрывает большую часть потребности, отдельный журнал нужен только для изменений вне репозитория.
Сколько хранить записи в журнале?
Дольше, чем кажется нужным на первый взгляд — минимум 6-12 месяцев, потому что деградация от неудачного изменения (например, накопление фрагментации, утечка в конфиге пула соединений) часто проявляется не сразу. Для git-репозитория ограничений по сути нет — история занимает мало места относительно самих файлов.
Что делать, если команда физически не пишет содержательные записи, несмотря на инструменты?
Проверить, не является ли сам процесс внесения изменений слишком тяжёлым или пугающим — если люди чаще применяют изменения в панике, потому что процесс согласования долгий, они будут обходить любой журнал. Иногда решение не в более строгом хуке, а в том, чтобы упростить путь легитимного изменения.
Стоит ли логировать вообще все изменения, включая мелкие?
Разумная граница — всё, что меняет поведение сервиса под нагрузкой: конфиги, лимиты, версии пакетов, сетевые правила, cron. Косметические правки (переименование файла без изменения логики) можно не фиксировать отдельной строкой — git-коммита достаточно.
Можно ли автоматически связать журнал изменений с алертами мониторинга?
Да, и это того стоит для крупных инсталляций — например, отправлять записи из chlog в тот же Grafana/Loki, куда идут метрики, вертикальными аннотациями на графиках. Тогда сопоставление времени происходит визуально, без ручного grep по датам.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →