Бэкап и переезд сервера: проверка данных, логов и нового диска
Автор: Казачкин Даниил Михайлович · Обновлено
Успешная команда копирования подтверждает передачу байтов, но ещё не восстановление сервиса. В этом уроке объединим архивирование, проверку, перенос и подключение хранилища в…
Успешная команда копирования подтверждает передачу байтов, но ещё не восстановление сервиса. В этом уроке объединим архивирование, проверку, перенос и подключение хранилища в законченные процедуры. Перед работой определите допустимую потерю данных и время восстановления: от этого зависит частота копий и способ остановки записи.
Бэкап с проверяемым восстановлением
Сначала воспроизведите процедуру на обычных учебных файлах:
backup_lab=$(mktemp -d)
mkdir -p "$backup_lab/source" "$backup_lab/backups" "$backup_lab/restored"
printf 'release=7\n' > "$backup_lab/source/app.conf"
printf 'alpha\nbeta\n' > "$backup_lab/source/data.txt"
archive="$backup_lab/backups/demo.tar.gz"
tar -czf "$archive" -C "$backup_lab/source" .
tar -tzf "$archive"
(cd "$backup_lab/backups" && sha256sum demo.tar.gz > SHA256SUMS)
(cd "$backup_lab/backups" && sha256sum -c SHA256SUMS)
tar -xzf "$archive" -C "$backup_lab/restored"
diff -r "$backup_lab/source" "$backup_lab/restored"Ожидаются оба файла в списке, demo.tar.gz: OK и пустой diff. Контрольная сумма замечает изменение архива после записи списка; если злоумышленник заменил и архив, и список, доверия она не добавляет. Для подлинности нужен независимый доверенный канал или подпись.
В рабочем скрипте используйте set -euo pipefail, блокировку flock от параллельных запусков и временное имя .partial. Только после успешного tar и проверки переименуйте файл в окончательное имя внутри той же файловой системы. Дата в имени должна учитывать часовой пояс. Частичный архив нельзя включать в набор готовых копий. Для базы данных нужен согласованный dump, snapshot или штатный backup-протокол: архив живого каталога БД может быть внутренне противоречивым.
Проверяйте восстановление приложения, а не только tar -t: после извлечения конфигурация читается, ожидаемые записи присутствуют, тестовый сервис запускается. Тест проводят отдельно от production. Копия на том же диске не переживёт его потерю; передавайте проверенный архив на отдельное хранилище и проверяйте его там. GNU tar.
Ротация без удаления единственной копии
Уточним политику: сохраняем семь последних успешных ежедневных архивов и по одному последнему архиву из четырёх более старых ISO-недель. Это число успешных копий, а не семь календарных дней: после сбоя расписания последняя восстановимая копия не должна исчезнуть.
Для учебного дерева переименуйте готовые архивы в backup-2026-10-09.tar.gz и аналогичные имена разных дат. Только после успешного пробного восстановления создайте рядом файл backup-2026-10-09.tar.gz.verified, содержащий SHA-256 архива. Такой маркер относится к этим конкретным байтам; изменение архива делает его непригодным для ротации. В реальной системе маркер также связывают с результатом проверки приложения и версией процедуры восстановления.
Создайте ровно такой набор из уже подготовленного source; каждая отметка появляется только после извлечения и сравнения:
for stamp in 2026-10-{01..09} 2026-09-{30,23,16,09}; do
dated_archive="$backup_lab/backups/backup-$stamp.tar.gz"
restore_check=$(mktemp -d "$backup_lab/restore-check.XXXXXXXX")
tar -czf "$dated_archive" -C "$backup_lab/source" . || break
tar -xzf "$dated_archive" -C "$restore_check" || break
diff -r "$backup_lab/source" "$restore_check" || break
sha256sum "$dated_archive" | awk '{print $1}' > "$dated_archive.verified"
doneКаталоги restore-check остаются внутри учебного дерева для осмотра. Если цикл остановился с ошибкой, не считайте набор полным и не продолжайте удаление.
Сохраните plan-retention.py:
import datetime as dt
import hashlib
import json
import pathlib
import re
import sys
root = pathlib.Path(sys.argv[1]).resolve(strict=True)
successful = []
for archive in root.glob("backup-*.tar.gz"):
match = re.fullmatch(r"backup-(\d{4}-\d{2}-\d{2})\.tar\.gz", archive.name)
if not match or archive.is_symlink() or not archive.is_file():
continue
marker = archive.with_name(archive.name + ".verified")
if not marker.is_file() or marker.is_symlink():
continue
with archive.open("rb") as stream:
digest = hashlib.file_digest(stream, "sha256").hexdigest()
if marker.read_text().strip() != digest:
continue
successful.append((dt.date.fromisoformat(match[1]), archive.name))
successful.sort(reverse=True)
if not successful:
raise SystemExit("No verified backup; deletion plan refused")
keep = {name for day, name in successful[:7]}
weeks = set()
for day, name in successful[7:]:
week = day.isocalendar()[:2]
if week not in weeks and len(weeks) < 4:
weeks.add(week)
keep.add(name)
delete = [name for day, name in successful if name not in keep]
assert successful[0][1] in keep
print(json.dumps({"keep": sorted(keep), "delete": sorted(delete)}, indent=2))Python 3.11+ нужен для file_digest. Программа только печатает план; .partial, непроверенные и повреждённые файлы не становятся кандидатами удаления. Память расходуется на хэширование потоком, а не на загрузку всех архивов сразу. В промышленной реализации обработайте ошибочную дату/чтение явно и защищайте каталог от посторонней записи между планом и действием.
Проверьте на датах 1–9 октября, 23 и 30 сентября, 16 и 9 сентября 2026 года. Семь ежедневных — 3–9 октября; старшие представители четырёх оставшихся недель — 2 октября, 23 сентября, 16 сентября и 9 сентября. В плане удаления окажутся 1 октября и 30 сентября. При единственной успешной копии список delete должен быть пустым. Удаление упражняют только в этом временном дереве, после проверки всех имён и повторного восстановления сохранённого архива; политика удалённого хранилища проверяется отдельно.
Перенос сервера в два прохода
Сопоставьте версии приложения, UID/GID, пути, свободное место и политику доступа на приёмнике. Для GNU/Linux-дерева, когда нужны hardlinks, ACL и xattrs, рассматривают rsync -aHAX --numeric-ids; эти возможности требуют поддержки файловых систем и достаточных прав. Архивный -a один не включает все перечисленные атрибуты.
В учебном каталоге проверьте отличие содержимого от самого каталога:
mkdir -p "$backup_lab/receiver"
rsync -a --dry-run --itemize-changes \
"$backup_lab/source/" "$backup_lab/receiver/"
rsync -a "$backup_lab/source/" "$backup_lab/receiver/"
rsync -ani --checksum "$backup_lab/source/" "$backup_lab/receiver/"Последний вывод должен быть пустым. Для реального переезда первый проход выполняют при работающем сервисе, затем останавливают запись или включают согласованный read-only, делают финальный проход, проверяют данные и только после этого переключают трафик. --delete удаляет лишнее на приёмнике; добавлять его допустимо после отдельного dry-run и проверки направления. Держите старую систему доступной для отката, но не допускайте одновременной независимой записи. Семантика rsync.
Новый диск как часть процедуры
Сравните lsblk -o NAME,SIZE,MODEL,SERIAL,FSTYPE,MOUNTPOINTS до и после подключения диска, проверьте сигнатуры через wipefs -n и назначение устройства. Разметка и mkfs изменяют выбранный носитель, поэтому выполняются только после этой идентификации. В учебной VM создайте раздел и файловую систему, получите UUID через blkid, смонтируйте временно и проверьте запись от имени приложения.
Затем добавьте точную запись UUID в fstab, выполните findmnt --verify --verbose, перечитайте units systemd и испытайте штатное монтирование. Перед перезагрузкой проверьте, что приложение пишет именно в смонтированный том, а не в пустой каталог корневой файловой системы. Полная практика разметки и LVM находится в [уроке о дисках](/lessons/without-university/linux-developer-foundations/linux-developer-40).
Логи подтверждают результат переезда
В access log Nginx статус 500 относится к запросу, но сам по себе не сообщает причину приложения. При стандартном combined-формате код обычно находится в поле 9, URL — в поле 7; прежде чем применять awk, посмотрите реальный log_format. Для собственного формата лучше заранее включить JSON escaping и разбирать JSON-парсером. Сравните до и после переключения долю 5xx, проблемные URL и распределение запросов по времени; повторные обращения одного клиента не обязательно означают разных пользователей.
Свяжите access log с error log и журналом приложения по времени и идентификатору запроса. Учебные группировки и счётчики есть в [обработке текста](/lessons/without-university/linux-developer-foundations/linux-developer-37). Задание: представьте протокол одного восстановления — хэш архива, результат сравнения, метаданные владельца, успешный запрос и план отката. Пустой diff без запуска приложения подтверждает только часть процедуры.