🛡️ Кейс 4: Построение отказоустойчивой системы резервного копирования
Роль: DevOps Engineer
Длительность: ~34 часа (от разработки скриптов до комплексного тестирования и документации)
Стек: Restic, Ansible, Bash, Python, GitLab CI, S3, Systemd (user-level).
🎯 Проблема и Контекст (Baseline)
Резервное копирование критических данных (PostgreSQL, Redis) выполнялось нестабильно или вручную. Первая попытка (v1) — кастомный скрипт синхронизации с S3 с проактивной диагностикой — дала видимость, но упёрлась в системные пределы: собственный код синхронизации нужно поддерживать, а шифрования и дедупликации в нём не было. Ключевые боли:
- Отсутствие шифрования и дедупликации при хранении в облаке.
- Риск “тихой” потери данных: скрипты не возвращали корректные коды ошибок, уведомления не приходили.
- Экспоненциальный рост снапшотов и отсутствие автоматической ротации.
🛠 Инженерные решения (Action)
1. Выбор инструмента: Restic вместо расширения v1
Расширение кастомного скрипта отвергнуто: шифрование, дедупликацию и ротацию пришлось бы писать и поддерживать самостоятельно. Выбран Restic — эти свойства встроены в инструмент (AES-256 на стороне клиента, дедупликация, retention-политики), а моя задача — корректно их сконфигурировать и обернуть в надежную автоматизацию.
2. Enterprise-grade скриптинг с честной обработкой ошибок
- Разработан
backup.shс retry-логикой (3 попытки дляpg_dump) и корректными exit codes:1при полном провале,0с предупреждением при частичном успехе. - Созданы интерактивные скрипты восстановления (
restore-postgres.sh,restore-redis.sh) с обязательным подтверждениемyes/noи поддержкой безопасного режима--dry-run. - Выделен надежный Python-скрипт (
send-email.py) для отправки уведомлений с корректной обработкой UTF-8 и MIME-заголовков (вместо ненадежныхcurl-hacks).
3. Идемпотентная автоматизация через Ansible
- Создана роль
restic_backupв строгом корпоративном стиле: использование FQCN (ansible.builtin.*), разделение задач по файлам,no_log: trueдля чувствительных данных. - Траблшутинг Systemd: Решена проблема запуска user-level таймеров без прав root. Поскольку стандартный модуль
systemdтерял контекст сессии приbecome: yes, активация выполнена черезansible.builtin.shellс явной передачей переменных окруженияXDG_RUNTIME_DIRиDBUS_SESSION_BUS_ADDRESS.
4. Умная ротация и предотвращение “раздувания” хранилища
- Проблема S3: Restic считал каждую staging-директорию с уникальным timestamp новым путем, создавая дубликаты.
- Решение: Внедрены флаги
--tag daily_backupпри создании и--group-by host,tagsпри ротации (restic forget). - Проблема локальной очистки: Команда
find -mtime +1означала “строго больше 48 часов”. Формула скорректирована наfind_days = LOCAL_RETENTION_DAYS - 1(использование-mtime +0для корректной очистки старше 24 часов).
5. Бесшовная интеграция в CI/CD
- Роль добавлена в существующий шаблон с input-параметром
restic_backup_enabled. - Настроен строгий маппинг переменных: для бэкапов используются изолированные
S3_BUCKET_BACKUPS_*, что гарантирует защиту от случайной перезаписи основного хранилища.
📊 Результаты и Метрики
| Метрика | Результат |
|---|---|
| Надежность | 100% автоматизация через user-level systemd timer. Честные exit codes и алертинг. |
| Безопасность | Restic выбран за встроенные AES-256 на стороне клиента и дедупликацию; секреты скрыты через no_log. |
| Эффективность хранения | Исключен экспоненциальный рост снапшотов благодаря группировке по тегам и хостам. |
| Восстанавливаемость | Restore формализован интерактивными скриптами с --dry-run и Runbook с пошаговым восстановлением и Troubleshooting. |
🏗 Архитектура процесса резервного копирования
graph TD
subgraph Target [Target Server - User: deployer]
DB[(PostgreSQL / Redis)] -->|pg_dump / BGSAVE| Staging[Staging Directory]
Staging -->|restic backup --tag| Restic[Restic Client]
end
subgraph Automation [Automation and Orchestration]
Systemd[Systemd User Timer] -->|Trigger daily| BackupScript[backup.sh]
BackupScript -->|on fail| Email[send-email.py]
Ansible[Ansible Role] -.->|Manages| Systemd
Ansible -.->|Manages| Restic
end
subgraph Storage [Cloud Storage - S3]
Restic -->|AES-256 Encrypted + Dedup| S3[(S3 Bucket)]
S3 -->|restic forget --group-by| Cleanup[Smart Retention Policy]
end
subgraph CI [CI/CD Integration]
GitLab[GitLab CI] -->|restic_backup_enabled=true| Ansible
GitLab -->|Masked Variables| S3Creds[S3_BUCKET_BACKUPS_*]
end