Бэкапы и восстановление в Linux

Резервное копирование (backup) — создание независимой копии данных, из которой можно выполнить восстановление (restore) после удаления файлов, сбоя оборудования, повреждения БД или ошибочного изменения.

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

Содержание


Основные понятия

Термин Значение
Full backup Полная копия выбранных данных
Incremental backup Изменения после предыдущей копии любого типа
Differential backup Изменения после последней полной копии
Retention Правила и сроки хранения копий
RPO Допустимая потеря данных, выраженная во времени
RTO Допустимое время восстановления сервиса
PITR Восстановление БД на определённый момент времени

RPO определяет частоту копирования. Если допустима потеря не более часа данных, точки восстановления должны создаваться как минимум каждый час.

RTO определяет требования к скорости восстановления. Чем меньше RTO, тем важнее автоматизация, подготовленная инфраструктура и регулярные учения.

Правило 3-2-1

Рекомендуется иметь:

Полезно дополнить схему неизменяемой или офлайн-копией. Каталог на том же диске не защищает от отказа диска, а обычное зеркало может повторить случайное удаление.


Стратегии бэкапов

Полный бэкап

Полностью копирует выбранные данные независимо от предыдущих запусков.

Преимущества: простое восстановление, независимость от цепочки копий, удобная проверка.

Недостатки: большой объём, длительное выполнение, высокая нагрузка на диск и сеть.

Инкрементальный бэкап

Содержит изменения после последнего полного или инкрементального бэкапа:

Вс: полный
Пн: изменения после воскресенья
Вт: изменения после понедельника
Ср: изменения после вторника

Для восстановления нужны полный бэкап и вся последующая цепочка. Копии создаются быстро и занимают меньше места, но повреждение одного элемента цепочки может помешать восстановлению.

Дифференциальный бэкап

Содержит все изменения после последнего полного бэкапа:

Вс: полный
Пн: изменения после воскресенья
Вт: изменения после воскресенья
Ср: изменения после воскресенья

Для восстановления нужны последний полный бэкап и выбранная дифференциальная копия. Восстановление проще, чем из инкрементальной цепочки, но ежедневные копии постепенно увеличиваются.

Пример стратегии

Для небольшого сервиса можно применять полный бэкап раз в неделю, ежедневные инкременты, дамп БД каждые несколько часов, удалённую копию и ежемесячное тестовое восстановление. Конкретный график определяется RPO, RTO, объёмом данных и допустимой нагрузкой.


Что сохранять

Обычно в бэкап включают:

Кеши, временные файлы, /proc, /sys, /dev и /run обычно не копируют. Нельзя просто архивировать каталог работающей PostgreSQL: файловая копия может оказаться несогласованной.


PostgreSQL: pg_dump и pg_restore

pg_dump создаёт логический согласованный снимок одной базы. Во время его работы приложение обычно может продолжать чтение и запись.

SQL-формат

pg_dump -h localhost -U app_user -d app_db > app_db.sql

Восстановление выполняется через psql:

createdb -U postgres app_db_restore
psql -U postgres -d app_db_restore \
  --set=ON_ERROR_STOP=on -f app_db.sql

ON_ERROR_STOP останавливает выполнение после первой SQL-ошибки.

Custom-формат

pg_dump -Fc -h localhost -U app_user \
  -d app_db -f app_db.dump

Custom-формат сжимается самим pg_dump, поддерживает выборочное и параллельное восстановление.

Просмотр содержимого:

pg_restore --list app_db.dump

Восстановление:

createdb -U postgres app_db_restore
pg_restore -U postgres -d app_db_restore \
  --verbose app_db.dump

Параллельное восстановление:

pg_restore -U postgres -d app_db_restore \
  --jobs=4 app_db.dump

Удаление существующих объектов перед восстановлением:

pg_restore -U postgres -d app_db_restore \
  --clean --if-exists app_db.dump

--clean нельзя без проверки применять к рабочей базе: параметр удаляет существующие объекты.

Если роли и владельцы исходной среды отсутствуют:

pg_restore -U postgres -d app_db_restore \
  --no-owner --no-acl app_db.dump

Восстановление отдельной таблицы:

pg_restore -U postgres -d app_db_restore \
  --table=public.orders app_db.dump

Directory-формат

Параллельное создание дампа поддерживается в directory-формате:

pg_dump -Fd --jobs=4 -U app_user \
  -d app_db -f app_db.dumpdir

Выборочный дамп

Только структура:

pg_dump -d app_db --schema-only > schema.sql

Только данные:

pg_dump -d app_db --data-only > data.sql

Одна таблица:

pg_dump -Fc -d app_db \
  --table=public.orders -f orders.dump

Роли и глобальные объекты

pg_dump не сохраняет все глобальные объекты кластера. Роли и tablespace сохраняют отдельно:

pg_dumpall -U postgres --globals-only > globals.sql

Восстановление:

psql -U postgres -d postgres -f globals.sql

Пароли и .pgpass

Пароль не следует указывать в командной строке или хранить в общедоступном сценарии. Для автоматизации можно использовать ~/.pgpass:

hostname:port:database:username:password

Права должны быть строгими:

chmod 600 ~/.pgpass

По возможности секрет получают из специализированного хранилища.

Проверка дампа

Базовая проверка custom-архива:

pg_restore --list app_db.dump > /dev/null

Она подтверждает читаемость каталога архива, но не заменяет полное восстановление. Надёжная проверка выполняется в отдельной базе:

createdb -U postgres app_db_restore_test
pg_restore -U postgres -d app_db_restore_test \
  --no-owner --no-acl app_db.dump
psql -U postgres -d app_db_restore_test -c '\dt'
psql -U postgres -d app_db_restore_test \
  -c 'SELECT count(*) FROM public.users;'
dropdb -U postgres app_db_restore_test

Дополнительно проверяют расширения, роли, локали, владельцев и критические запросы приложения.

Физические копии и PITR

Для больших кластеров и восстановления на определённый момент используют pg_basebackup и архивирование WAL. Это отдельная схема, требующая настройки репликации, хранения WAL и точной процедуры восстановления. Она не заменяется обычным cp, tar или rsync каталога работающего сервера.


Бэкап файлов через tar

Создание gzip-архива с относительным путём:

sudo tar -czf /var/backups/myapp/files.tar.gz \
  -C /srv myapp

Здесь -c создаёт архив, -z включает gzip, -f задаёт файл, а -C /srv меняет рабочий каталог перед добавлением myapp.

Исключение кешей:

tar -czf files.tar.gz \
  --exclude='myapp/cache' \
  --exclude='myapp/tmp' \
  -C /srv myapp

Просмотр содержимого:

tar -tzf files.tar.gz

Восстановление в отдельный каталог:

mkdir -p /tmp/restore-test
tar -xzf files.tar.gz -C /tmp/restore-test

Извлечение одного файла:

tar -xzf files.tar.gz -C /tmp/restore-test \
  myapp/config/app.conf

Перед распаковкой от root нужно просмотреть пути. Непроверенный архив не следует извлекать прямо в /.

ACL и расширенные атрибуты GNU tar сохраняет дополнительными параметрами:

tar --acls --xattrs --selinux \
  -czf files.tar.gz -C /srv myapp

Поддержка зависит от реализации tar и файловой системы.


Бэкап файлов через rsync

Локальная синхронизация:

rsync -a /srv/myapp/ /mnt/backup/myapp/

Завершающий / важен. /srv/myapp/ означает содержимое каталога, а /srv/myapp — сам каталог.

Предварительная проверка:

rsync -a --dry-run --itemize-changes \
  /srv/myapp/ /mnt/backup/myapp/

Зеркалирование с удалением лишних файлов:

rsync -a --delete /srv/myapp/ /mnt/backup/myapp/

--delete опасен при ошибке в путях, поэтому сначала нужен --dry-run. Простое зеркало не является историей бэкапов: удаление или повреждение источника может распространиться на копию.

Исключения:

rsync -a --exclude='cache/' --exclude='*.tmp' \
  /srv/myapp/ /mnt/backup/myapp/

Удалённый сервер через SSH:

rsync -a -e ssh /srv/myapp/ \
  backup@backup.example.internal:/data/myapp/

Сохранение hard links, ACL, xattrs и числовых идентификаторов:

sudo rsync -aHAX --numeric-ids \
  /srv/myapp/ /mnt/backup/myapp/

Удалённая учётная запись и SSH-ключ должны иметь минимально необходимые полномочия.


Целостность и безопасность

Контрольные суммы

sha256sum app_db.dump files.tar.gz > SHA256SUMS
sha256sum --check SHA256SUMS

Контрольная сумма обнаруживает случайное изменение файла, но без подписи не защищает от намеренной подмены одновременно с файлом контрольных сумм.

Права доступа

umask 077
sudo install -d -m 0700 /var/backups/myapp
chmod 600 /var/backups/myapp/*.dump

Бэкапы часто содержат пользовательские данные, пароли, конфигурацию и ключи. Их следует шифровать при передаче и хранении, а ключ расшифрования нельзя хранить только рядом с копией.

Согласованность приложения

Файлы и БД должны относиться к согласованному состоянию. Возможные способы:

Простая остановка сервиса:

sudo systemctl stop myapp.service
# Создание копии
sudo systemctl start myapp.service

Это увеличивает простой, поэтому решение выбирают с учётом RTO.


Автоматизация бэкапов

Сценарий должен завершаться при ошибках, не допускать параллельных запусков, писать журнал, использовать временный каталог и удалять старые копии только после успешного создания новой.

Файл /usr/local/sbin/backup-myapp:

#!/usr/bin/env bash
set -Eeuo pipefail

readonly BACKUP_DIR='/var/backups/myapp'
readonly SOURCE_DIR='/srv/myapp'
readonly DATABASE='app_db'
readonly DB_USER='backup_user'
readonly RETENTION_DAYS=14
readonly STAMP="$(date '+%Y-%m-%d_%H-%M-%S')"
readonly WORK_DIR="${BACKUP_DIR}/.tmp-${STAMP}"
readonly FINAL_DIR="${BACKUP_DIR}/${STAMP}"

log() {
    printf '%s %s\n' "$(date '+%F %T')" "$*"
}

cleanup() {
    code=$?
    if (( code != 0 )); then
        log "Backup failed: exit code ${code}"
        rm -rf -- "$WORK_DIR"
    fi
}
trap cleanup EXIT

umask 077
install -d -m 0700 -- "$BACKUP_DIR"

exec 9>"${BACKUP_DIR}/backup.lock"
flock -n 9 || { log 'Backup already running'; exit 1; }

mkdir -- "$WORK_DIR"

pg_dump -U "$DB_USER" -d "$DATABASE" -Fc \
  -f "${WORK_DIR}/${DATABASE}.dump"

tar -czf "${WORK_DIR}/files.tar.gz" \
  --exclude='myapp/cache' --exclude='myapp/tmp' \
  -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"

pg_restore --list "${WORK_DIR}/${DATABASE}.dump" > /dev/null
tar -tzf "${WORK_DIR}/files.tar.gz" > /dev/null

(
  cd -- "$WORK_DIR"
  sha256sum "${DATABASE}.dump" files.tar.gz > SHA256SUMS
)

mv -- "$WORK_DIR" "$FINAL_DIR"

find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 \
  -type d -name '20??-??-??_??-??-??' \
  -mtime "+${RETENTION_DAYS}" -exec rm -rf -- {} +

log "Backup completed: ${FINAL_DIR}"

Права и проверки:

sudo chown root:root /usr/local/sbin/backup-myapp
sudo chmod 700 /usr/local/sbin/backup-myapp
bash -n /usr/local/sbin/backup-myapp
shellcheck /usr/local/sbin/backup-myapp
sudo /usr/local/sbin/backup-myapp

Это шаблон: пути, пользователь БД, .pgpass, исключения и права нужно адаптировать.

systemd service и timer

/etc/systemd/system/backup-myapp.service:

[Unit]
Description=Backup myapp files and PostgreSQL database
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/backup-myapp

/etc/systemd/system/backup-myapp.timer:

[Unit]
Description=Run myapp backup every night

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=10m
Unit=backup-myapp.service

[Install]
WantedBy=timers.target

Применение:

sudo systemctl daemon-reload
sudo systemctl enable --now backup-myapp.timer
systemctl list-timers --all

Ручной запуск и журнал:

sudo systemctl start backup-myapp.service
systemctl status backup-myapp.service
journalctl -u backup-myapp.service --since today

Проверка календарного выражения:

systemd-analyze calendar '*-*-* 02:30:00'

Ротация бэкапов

Простая политика может хранить все копии 14 или 30 дней. Схема GFS хранит, например, 7 ежедневных, 4 еженедельные и 12 ежемесячных копий.

Предварительный просмотр файлов старше 30 дней:

find /var/backups/myapp \
  -mindepth 1 -maxdepth 1 -type f \
  -name 'backup-*.tar.gz' -mtime +30 -print

Удаление после проверки:

find /var/backups/myapp \
  -mindepth 1 -maxdepth 1 -type f \
  -name 'backup-*.tar.gz' -mtime +30 -delete

-mindepth, -maxdepth и точный шаблон имени снижают риск удаления посторонних данных.

Нельзя удалять полный бэкап, пока от него зависят сохраняемые инкрементальные или дифференциальные копии. Цепочка удаляется как единый набор после создания и проверки замены.

Безопасный порядок:

  1. проверить свободное место;
  2. создать новую копию;
  3. проверить архив и контрольные суммы;
  4. передать копию на удалённое хранилище;
  5. подтвердить её наличие;
  6. удалить устаревшие наборы.

Свободное место:

df -h /var/backups/myapp

Тестирование восстановления

Тестирование должно проводиться регулярно и на отдельном сервере или экземпляре.

Нужно проверить:

Тест файлов:

restore_dir=$(mktemp -d)
tar -xzf files.tar.gz -C "$restore_dir"
find "$restore_dir" -maxdepth 3 -type f | head

Тест PostgreSQL:

createdb -U postgres app_db_restore_test
pg_restore -U postgres -d app_db_restore_test \
  --no-owner --no-acl app_db.dump
psql -U postgres -d app_db_restore_test -c '\dt'
dropdb -U postgres app_db_restore_test

Для каждой проверки фиксируют дату, использованный набор, версии ПО, длительность, выполненные проверки, обнаруженные проблемы и итоговый статус.


Общий порядок восстановления

  1. Выбрать проверенную точку восстановления с учётом RPO.
  2. Подготовить совместимые версии ОС, PostgreSQL, приложения и расширений.
  3. Проверить SHA256SUMS, pg_restore --list и tar -tzf.
  4. Восстановить файлы в отдельное место и проверить права.
  5. Восстановить роли, создать БД и запустить pg_restore.
  6. Проверить конфигурацию Nginx и systemd.
  7. Запустить сервисы в правильном порядке.
  8. Выполнить health check, SQL-проверки и тесты приложения.
  9. Зафиксировать время восстановления, ошибки и результат.

Примеры проверок:

sudo nginx -t
systemctl status myapp
journalctl -u myapp --since '-10 minutes'
curl --fail --show-error --silent https://example.com/health

Типичные ошибки


Краткая памятка

# PostgreSQL: создать и проверить дамп
pg_dump -Fc -U app_user -d app_db -f app_db.dump
pg_restore --list app_db.dump

# PostgreSQL: восстановить
createdb -U postgres app_db_restore
pg_restore -U postgres -d app_db_restore app_db.dump

# Сохранить глобальные объекты
pg_dumpall -U postgres --globals-only > globals.sql

# Создать и проверить файловый архив
tar -czf files.tar.gz -C /srv myapp
tar -tzf files.tar.gz

# rsync с предварительной проверкой
rsync -a --delete --dry-run --itemize-changes \
  /srv/myapp/ /mnt/backup/myapp/

# Контрольные суммы
sha256sum app_db.dump files.tar.gz > SHA256SUMS
sha256sum --check SHA256SUMS

# Таймер и журнал
systemctl list-timers --all
journalctl -u backup-myapp.service

Надёжный бэкап — это не просто файл, а регулярно проверяемый процесс восстановления.