Бэкапы и восстановление в Linux
Резервное копирование (backup) — создание независимой копии данных, из которой можно выполнить восстановление (restore) после удаления файлов, сбоя оборудования, повреждения БД или ошибочного изменения.
Успешное копирование ещё не доказывает пригодность бэкапа. Надёжная схема включает создание, безопасное хранение, мониторинг, ротацию и регулярное тестирование восстановления.
Содержание
- Основные понятия
- Стратегии бэкапов
- Что сохранять
- SQL-формат
- Custom-формат
- Directory-формат
- Выборочный дамп
- Роли и глобальные объекты
- Пароли и `.pgpass`
- Проверка дампа
- Контрольные суммы
- Права доступа
- Согласованность приложения
- systemd service и timer
Основные понятия
| Термин | Значение |
|---|---|
| Full backup | Полная копия выбранных данных |
| Incremental backup | Изменения после предыдущей копии любого типа |
| Differential backup | Изменения после последней полной копии |
| Retention | Правила и сроки хранения копий |
| RPO | Допустимая потеря данных, выраженная во времени |
| RTO | Допустимое время восстановления сервиса |
| PITR | Восстановление БД на определённый момент времени |
RPO определяет частоту копирования. Если допустима потеря не более часа данных, точки восстановления должны создаваться как минимум каждый час.
RTO определяет требования к скорости восстановления. Чем меньше RTO, тем важнее автоматизация, подготовленная инфраструктура и регулярные учения.
Правило 3-2-1
Рекомендуется иметь:
- не менее трёх экземпляров данных: оригинал и две копии;
- копии в двух независимых системах хранения;
- как минимум одну копию вне основной площадки.
Полезно дополнить схему неизменяемой или офлайн-копией. Каталог на том же диске не защищает от отказа диска, а обычное зеркало может повторить случайное удаление.
Стратегии бэкапов
Полный бэкап
Полностью копирует выбранные данные независимо от предыдущих запусков.
Преимущества: простое восстановление, независимость от цепочки копий, удобная проверка.
Недостатки: большой объём, длительное выполнение, высокая нагрузка на диск и сеть.
Инкрементальный бэкап
Содержит изменения после последнего полного или инкрементального бэкапа:
Вс: полный
Пн: изменения после воскресенья
Вт: изменения после понедельника
Ср: изменения после вторникаДля восстановления нужны полный бэкап и вся последующая цепочка. Копии создаются быстро и занимают меньше места, но повреждение одного элемента цепочки может помешать восстановлению.
Дифференциальный бэкап
Содержит все изменения после последнего полного бэкапа:
Вс: полный
Пн: изменения после воскресенья
Вт: изменения после воскресенья
Ср: изменения после воскресеньяДля восстановления нужны последний полный бэкап и выбранная дифференциальная копия. Восстановление проще, чем из инкрементальной цепочки, но ежедневные копии постепенно увеличиваются.
Пример стратегии
Для небольшого сервиса можно применять полный бэкап раз в неделю, ежедневные инкременты, дамп БД каждые несколько часов, удалённую копию и ежемесячное тестовое восстановление. Конкретный график определяется RPO, RTO, объёмом данных и допустимой нагрузкой.
Что сохранять
Обычно в бэкап включают:
- пользовательские файлы и загрузки;
- базы данных;
- конфигурацию приложения, Nginx и systemd;
- сведения о зависимостях и пакетах;
- задания автоматизации;
- необходимые сертификаты и ключи с усиленной защитой;
- инструкцию восстановления.
Кеши, временные файлы, /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.sqlON_ERROR_STOP останавливает выполнение после первой SQL-ошибки.
Custom-формат
pg_dump -Fc -h localhost -U app_user \
-d app_db -f app_db.dumpCustom-формат сжимается самим 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.dumpDirectory-формат
Параллельное создание дампа поддерживается в 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/-H— hard links;-A— ACL;-X— расширенные атрибуты;--numeric-ids— числовые UID/GID.
Удалённая учётная запись и 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 и точный шаблон имени снижают риск удаления посторонних данных.
Нельзя удалять полный бэкап, пока от него зависят сохраняемые инкрементальные или дифференциальные копии. Цепочка удаляется как единый набор после создания и проверки замены.
Безопасный порядок:
- проверить свободное место;
- создать новую копию;
- проверить архив и контрольные суммы;
- передать копию на удалённое хранилище;
- подтвердить её наличие;
- удалить устаревшие наборы.
Свободное место:
df -h /var/backups/myappТестирование восстановления
Тестирование должно проводиться регулярно и на отдельном сервере или экземпляре.
Нужно проверить:
- наличие и разумный размер файлов;
- контрольные суммы;
- читаемость архивов;
- восстановление БД без ошибок;
- наличие ролей, схем, таблиц и файлов;
- запуск приложения;
- критические пользовательские сценарии;
- фактические RPO и RTO;
- актуальность инструкции.
Тест файлов:
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Для каждой проверки фиксируют дату, использованный набор, версии ПО, длительность, выполненные проверки, обнаруженные проблемы и итоговый статус.
Общий порядок восстановления
- Выбрать проверенную точку восстановления с учётом RPO.
- Подготовить совместимые версии ОС, PostgreSQL, приложения и расширений.
- Проверить
SHA256SUMS,pg_restore --listиtar -tzf. - Восстановить файлы в отдельное место и проверить права.
- Восстановить роли, создать БД и запустить
pg_restore. - Проверить конфигурацию Nginx и systemd.
- Запустить сервисы в правильном порядке.
- Выполнить health check, SQL-проверки и тесты приложения.
- Зафиксировать время восстановления, ошибки и результат.
Примеры проверок:
sudo nginx -t
systemctl status myapp
journalctl -u myapp --since '-10 minutes'
curl --fail --show-error --silent https://example.com/healthТипичные ошибки
- Хранение копии на том же диске, что и оригинал.
- Использование только зеркала
rsync --deleteбез версий. - Копирование каталога работающей PostgreSQL обычным
tarилиrsync. - Отсутствие тестовых восстановлений.
- Хранение паролей в сценариях и логах.
- Запуск ротации до успешного создания новой копии.
- Удаление полного бэкапа, от которого зависят инкременты.
- Отсутствие мониторинга свободного места и последнего успешного запуска.
- Слишком широкие права на файлы резервных копий.
- Отсутствие актуальной пошаговой инструкции.
Краткая памятка
# 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Надёжный бэкап — это не просто файл, а регулярно проверяемый процесс восстановления.