Docker: резервні копії, які ви ніколи не перевіряли — як правильно провести репетицію відновлення

Було корисно?

У вас є резервні копії. У вас навіть зелена галочка в якомусь дашборді. Потім вузол помирає, на виклик запускають відновлення,
і раптом єдине, що ви відновлюєте — це ваша повага до закону Мерфі.

Docker полегшує деплой додатків. Він також полегшує забути, де насправді живуть дані: томи, bind mounts,
секрети, файли з оточенням, реєстри та кілька «тимчасових» директорій, які хтось колись захардкодив о 2 ранку.

Репетиція відновлення — це продукт, а не ритуал

«Резервна копія» — це обіцянка. Репетиція відновлення — це момент, коли ви погашаєте цю обіцянку і доводите, що можете її виконати під напругою.
Результат — не просто tarball в об’єктному сховищі. Результат — повторюваний процес відновлення з відомими часовими межами.

Репетиція відновлення має одну задачу: перетворити припущення на вимірювання. Яке у вас RPO (скільки даних можна втратити)
та RTO (скільки часу можна бути недоступним)? Які частини повільні? Які частини крихкі? Які частини потребують конкретного
знання та кофеїну однієї людини?

Найбільш цінний результат репетиції часто нудний: список відсутніх файлів, неправильних прав, незнайдених секретів
і сюрпризів типу «ми думали, це в бекапі». Нудно — це добре. Нудно — це те, як ви переживете аварії.

Одна цитата, яку варто тримати на столі: Надія — не стратегія. (приписують генералу Ґордонові Р. Саллівану)

Що саме ви відновлюєте в Docker

Docker не «містить» стан. Він просто полегшує його загубити. Для репетицій відновлення розглядайте систему як шари:
стан хоста, стан контейнера, стан даних і стан деплойменту. Потім вирішіть, що саме ви обіцяєте відновити.

1) Стан даних

  • Іменовані томи (керовані Docker): зазвичай під /var/lib/docker/volumes.
  • Bind mounts: будь-де в файловій системі хоста; часто вони не входять у ту ж політику бекапів, що томи.
  • Зовнішнє сховище: NFS, iSCSI, Ceph, EBS, SAN LUNs, ZFS datasets, LVM тощо.
  • Бази даних: Postgres/MySQL/Redis/Elastic/тощо. Метод бекапу важить більше за те, де вона зберігається.

2) Стан деплойменту

  • Compose файли, файли оточення та overrides.
  • Секрети та механізм їх доставки (Swarm secrets, файли, SOPS, Vault шаблони тощо).
  • Теги образів: «latest» — це не план відновлення.
  • Доступ до реєстру: якщо ви не можете витягти образи, ви не зможете запустити сервіс.

3) Стан хоста

  • Конфігурація Docker Engine, драйвер зберігання, прапорці демона.
  • Ядро + деталі файлової системи: очікування overlay2, xfs ftype, SELinux/AppArmor.
  • Мережа: правила фаєрволу, DNS, маршрути, MTU.

4) Рuntime-стан (зазвичай не варто «відновлювати»)

Слой контейнерів і епhemeral runtime-файли можна відтворити. Якщо ви робите бекап усього Docker root каталогу
(/var/lib/docker) в надії воскресити контейнери байт у байт, ви підписуєтеся на тонкі злами.
Правильна ціль майже завжди — дані томів плюс конфіг деплойменту, а також чисте відтворення контейнерів.

Жарт №1: Якщо ваш план відновлення починається словами «Я думаю, дані на тому одному вузлі», вітаю — ви винайшли єдину точку несподіванки.

Факти й історичний контекст (щоб ви перестали повторювати помилки)

  • Факт 1: Рання епоха Docker з AUFS нормалізувала ідею, що контейнери одноразові; багато команд помилково зробили дані одноразовими також.
  • Факт 2: Перехід з AUFS на overlay2 був не лише про продуктивність — семантика відновлення й вимоги файлової системи змінилися (зокрема очікування XFS ftype=1).
  • Факт 3: Рух індустрії до «незмінної інфраструктури» зменшив потребу в відновленні хостів, але збільшив потребу відновлювати зовнішній стан (томи, об’єктні сховища, керовані БД).
  • Факт 4: Compose став типовим описом додатка для багатьох організацій, навіть коли операційна дисципліна (обертання секретів, фіксація версій, healthchecks) не поспішала за цим.
  • Факт 5: Багато інцидентів, які звинувачували «Docker», насправді — проблеми когерентності зберігання: неконсистентні файли, скопійовані під час активної роботи БД.
  • Факт 6: Ransomware змістив стратегію бекапів від «можемо відновити?» до «можемо відновити, не довіряючи тому, що атакувальник не зашифрував наші ключі?»
  • Факт 7: Реєстри образів стали критичною інфраструктурою; втрата приватного реєстру або його облікових даних може заблокувати відновлення, навіть якщо дані в безпеці.
  • Факт 8: Снапшоти файлової системи (LVM/ZFS) зробили швидкі бекапи простішими — але також надали надмірну впевненість, коли додатки не були готові до снапшотів.
  • Факт 9: Поява rootless-контейнерів змінила шляхи бекапу й модель прав; відновлення даних як root може тихо зламати rootless-рантайми пізніше.

Визначте масштаб репетиції: хост, застосунок або рівень даних

Репетиція відновлення може бути трьох різних типів. Якщо ви не оголосите, який саме робите, ви «вдасте успіх»
у простішому випадку й провалите той, що має значення.

Масштаб A: Репетиція відновлення даних (найпоширеніша, найцінніша)

Ви відновлюєте дані томів/bind mounts і розгортаєте контейнери з відомих образів та конфігурації. Це правильний дефолт
для більшості продакшн-настроєнь Docker Compose.

Масштаб B: Репетиція відновлення застосунку (деплоймент + дані)

Ви відновлюєте точний стек застосунку: Compose файли, env/секрети, reverse proxy, сертифікати та дані. Це перевіряє
припущення «усе, що потрібно для роботи». Також виявляє хворобу «ми тримали ту конфігурацію на чийомусь ноуті».

Масштаб C: Відновлення хоста (рідко, але робіть хоча б щороку)

Ви вважаєте, що вузол загинув. Ви провізуєте свіжий хост і відновлюєте на нього. Тут ви дізнаєтеся про залежність від
старих ядер, відсутніх пакетів, кастомних iptables правил, дивних MTU-хаків та невідповідностей драйвера зберігання.

Плейбук швидкої діагностики (знайти вузьке місце швидко)

Під час відновлення вас зазвичай блокує одне з чотирьох: ідентичність/облікові дані, цілісність даних,
швидкість передачі даних або коректність застосунку. Не гадать — тріажуйте в такому порядку.

Перше: чи взагалі маєте ви доступ до потрібного?

  • Чи є у вас облікові дані репозиторію бекапів та ключі шифрування?
  • Чи може хост відновлення дістатися до об’єктного сховища / сервера бекапів / NAS?
  • Чи можна витягнути образи (або у вас є кеш для air-gapped середовища)?

Друге: чи бекап повний і внутрішньо консистентний?

  • Чи маєте ви всі очікувані шляхи томів/bind-mount для застосунку?
  • Чи збігаються контрольні суми? Чи можна перелічити й витягнути файли?
  • Для баз даних: чи маєте логічний бекап або тільки crash-consistent копію файлової системи?

Третє: куди йде час?

  • Пропускна здатність мережі (egress об’єктного сховища, обмеження VPN, тротлінг)?
  • Розпаковування й крипто (однопотокові інструменти відновлення)?
  • IOPS і «штурм» відновлення мільйонів дрібних файлів?

Четверте: чому застосунок не піднімається?

  • Права/власність/SELinux-лейбли на відновлених даних.
  • Дріфт конфігурації: env vars, секрети, змінені теги образів.
  • Несумісність схем: відновлення старих даних у нову версію застосунку.

Якщо пам’ятаєте лише одне: виміряйте швидкість передачі і перевірте ключі на ранньому етапі. Все інше — вторинне.

Побудуйте реалістичне середовище відновлення

Репетиція відновлення на тому ж хості, що й продукував бекапи, — це заспокійлива брехня. Вона має ті ж закешовані образи,
ті самі вже залогінені облікові дані й ті самі вручну підлаштовані правила фаєрволу. Мета — провалитися чесно.

Що означає «реалістично»

  • Свіжий хост: нова VM або bare metal, та сама сімейство ОС, ті ж мажорні версії.
  • Ті самі мережеві обмеження: той самий маршрут до сховища бекапів, той самий NAT/VPN, той самий DNS.
  • Немає прихованого стану: не використовуйте старий /var/lib/docker; не монтуйте продакшн-томи напряму.
  • Обмеження по часу: ви тестуєте RTO; перестаньте милуватися логами і почніть таймер.

Заздалегідь визначте критерії успіху

  • RPO верифіковано: ви можете вказати на найновіший успішний бекап і показати його мітку часу та вміст.
  • RTO виміряно: від “хост провізовано” до “сервіс відповідає коректно”.
  • Коректність підтверджена: не просто «контейнери запущені», а «дані правильні».

Практичні завдання: команди, виводи, рішення

Це завдання репетиції відновлення, які я очікую бачити в руйнбуці. Кожне включає команду, що означає її вивід,
і рішення, яке ви приймаєте. Виконуйте їх на хості відновлення, якщо не зазначено інакше.

Завдання 1: Інвентаризація запущених контейнерів та їхніх маунтів (джерельне середовище)

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
NAMES               IMAGE                         STATUS
api                 registry.local/api:1.42.0     Up 3 days
postgres            postgres:15                   Up 3 days
nginx               nginx:1.25                    Up 3 days

Значення: Це мінімальний перелік «що існує». Цього замало, але це початок.
Рішення: Визначте, які контейнери мають стан (тут: postgres) і які безстанні.

cr0x@server:~$ docker inspect postgres --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
volume pgdata -> /var/lib/postgresql/data
bind /srv/postgres/conf -> /etc/postgresql

Значення: У вас є іменований том і bind mount. Дві політики бекапу, два режими відмови.
Рішення: План відновлення має захоплювати і pgdata, і /srv/postgres/conf.

Завдання 2: Перелік Docker томів і їхнє зіставлення з проектами

cr0x@server:~$ docker volume ls
DRIVER    VOLUME NAME
local     myapp_pgdata
local     myapp_redisdata
local     shared_uploads

Значення: Імена томів часто кодують імена проектів Compose. Це корисно під час відновлення.
Рішення: Визначте, які томи критичні, а які можна відновити повторно (наприклад, кеші).

Завдання 3: З’ясуйте, де томи живуть на диску (хост відновлення)

cr0x@server:~$ docker info --format '{{.DockerRootDir}}'
/var/lib/docker

Значення: Директорія Docker root за замовчуванням. Томи будуть під цим шляхом, якщо не налаштовано інакше.
Рішення: Підтвердіть, що це співпадає з вашими очікуваннями бекапу; невідповідності призводять до «відновлення вдалося, дані відсутні».

Завдання 4: Перевірте файлову систему та вільне місце перед відновленням

cr0x@server:~$ df -hT /var/lib/docker /srv
Filesystem     Type   Size  Used Avail Use% Mounted on
/dev/sda2      ext4   200G   32G  158G  17% /
/dev/sdb1      xfs    800G  120G  680G  15% /srv

Значення: У вас є запас місця. Також зверніть увагу на типи файлових систем; деякі поведінки відрізняються для overlay і прав.
Рішення: Якщо простору замало, не «пробуйте все одно». Спочатку збільшіть або оберіть більший хост.

Завдання 5: Підтвердіть драйвер зберігання Docker і сумісність ядра

cr0x@server:~$ docker info --format 'Driver={{.Driver}}; BackingFS={{.BackingFilesystem}}'
Driver=overlay2; BackingFS=extfs

Значення: overlay2 на ext4 (Docker повідомляє «extfs»). Якщо оригінальний хост використовував інший драйвер, не припускайте портативності /var/lib/docker.
Рішення: Надавайте перевагу відновленню лише томів і конфігурації; перебудовуйте контейнери з образів.

Завдання 6: Переконайтеся, що артефакт бекапу існує і є свіжим

cr0x@server:~$ ls -lh /backups/myapp/
total 4.1G
-rw------- 1 root root 1.9G Jan  2 01:05 myapp-volumes-2026-01-02.tar.zst
-rw------- 1 root root 2.2G Jan  2 01:06 myapp-bindmounts-2026-01-02.tar.zst
-rw------- 1 root root  12K Jan  2 01:06 myapp-compose-2026-01-02.tgz

Значення: Окремі артефакти для томів, bind mounts і конфігурації деплойменту — це добре. Це робить часткові відновлення можливими.
Рішення: Якщо найновіший файл старший за ваш RPO, зупиніться й ескалюйте. Відновлення застарілих даних без повідомлення — шлях у кар’єру інцидентів.

Завдання 7: Перевірте цілісність архіву перед розпакуванням

cr0x@server:~$ zstd -t /backups/myapp/myapp-volumes-2026-01-02.tar.zst
/backups/myapp/myapp-volumes-2026-01-02.tar.zst: OK

Значення: Потік стиснення не пошкоджений.
Рішення: Якщо це завершується помилкою, не розпаковуйте «трохи». Знайдіть інший набір бекапів або відновіть pipeline бекапів.

Завдання 8: Сухий прогін — перелік файлів всередині бекапу (шукаємо відсутні шляхи)

cr0x@server:~$ tar -I zstd -tf /backups/myapp/myapp-bindmounts-2026-01-02.tar.zst | head
srv/postgres/conf/postgresql.conf
srv/postgres/conf/pg_hba.conf
srv/myapp/env/.env.production
srv/nginx/conf.d/app.conf

Значення: Ви бачите очікувані файли конфігів для bind-mount.
Рішення: Якщо ключові директорії відсутні, зупиніться й виправте визначення бекапу. Репетиції відновлення — не фокуси.

Завдання 9: Відновіть bind mounts спочатку в проміжну папку (щоб не перезаписувати)

cr0x@server:~$ mkdir -p /restore-staging
cr0x@server:~$ tar -I zstd -xpf /backups/myapp/myapp-bindmounts-2026-01-02.tar.zst -C /restore-staging
cr0x@server:~$ ls -la /restore-staging/srv/postgres/conf
total 24
drwxr-xr-x 2 root root 4096 Jan  2 01:06 .
drwxr-xr-x 3 root root 4096 Jan  2 01:06 ..
-rw-r--r-- 1 root root  980 Jan  2 01:06 pg_hba.conf
-rw-r--r-- 1 root root 3150 Jan  2 01:06 postgresql.conf

Значення: Файли відновлені збереженням прав (-p). Власність має значення пізніше.
Рішення: Порівняйте staging із цільовим макетом. Лише потім переміщуйте на потрібні місця.

Завдання 10: Відновіть дані іменованого тому за допомогою хелпер-контейнера

Для іменованих томів не копіюйте вручну всередину internals Docker. Використовуйте тимчасовий контейнер, який монтує том.

cr0x@server:~$ docker volume create myapp_pgdata
myapp_pgdata
cr0x@server:~$ docker run --rm -v myapp_pgdata:/data -v /backups/myapp:/backup alpine:3.20 sh -c "cd /data && tar -I zstd -xpf /backup/myapp-volumes-2026-01-02.tar.zst --strip-components=2 ./volumes/myapp_pgdata"
tar: removing leading './' from member names

Значення: Ви витягуєте лише піддерево для цього тому в змонтований шлях тому.
Рішення: Якщо структура архіву не відповідає вашим очікуванням, зупиніться й перевірте скрипт бекапу; не імпровізуйте з частковими відновленнями.

Завдання 11: Санітарна перевірка вмісту та власності відновленого тому

cr0x@server:~$ docker run --rm -v myapp_pgdata:/data alpine:3.20 sh -c "ls -la /data | head"
total 128
drwx------    19 999      999           4096 Jan  2 01:04 .
drwxr-xr-x     1 root     root          4096 Jan  2 02:10 ..
-rw-------     1 999      999              3 Jan  2 01:04 PG_VERSION
drwx------     5 999      999           4096 Jan  2 01:04 base

Значення: Власність — 999:999, типовий для офіційного образу Postgres. Добре.
Рішення: Якщо власність неправильна (наприклад, root), виправте її зараз (chown), інакше Postgres може відмовитися стартувати.

Завдання 12: Відновіть конфіг деплойменту та закріпіть версії образів

cr0x@server:~$ mkdir -p /opt/myapp
cr0x@server:~$ tar -xpf /backups/myapp/myapp-compose-2026-01-02.tgz -C /opt/myapp
cr0x@server:~$ ls -la /opt/myapp
total 40
drwxr-xr-x 3 root root 4096 Jan  2 02:13 .
drwxr-xr-x 3 root root 4096 Jan  2 02:13 ..
-rw-r--r-- 1 root root 2241 Jan  2 01:06 docker-compose.yml
-rw------- 1 root root  412 Jan  2 01:06 .env.production

Значення: Конфіг присутній, включно з env файлом. Розглядайте його як чутливий.
Рішення: Переконайтеся, що образи зафіксовані тегами або дайджестами, яким ви довіряєте. Якщо Compose використовує latest, виправте це під час репетиції.

Завдання 13: Перевірте, чи можна витягти образи (або чи вони вже доступні)

cr0x@server:~$ docker compose -f /opt/myapp/docker-compose.yml pull
[+] Pulling 3/3
 ✔ postgres Pulled
 ✔ api      Pulled
 ✔ nginx    Pulled

Значення: Ваш шлях до реєстру, облікові дані та мережа функціонують.
Рішення: Якщо pull-операції не вдаються, план відновлення має включати зеркало реєстру, офлайн tar-образи або кроки відновлення облікових даних.

Завдання 14: Підніміть стек і слідкуйте за швидкими збоями

cr0x@server:~$ docker compose -f /opt/myapp/docker-compose.yml up -d
[+] Running 3/3
 ✔ Container myapp-postgres-1  Started
 ✔ Container myapp-api-1       Started
 ✔ Container myapp-nginx-1     Started

Значення: Контейнери запущені, але це не доказ коректності.
Рішення: Негайно перевірте логи й health endpoints. «Started» може означати «потихеньку зламано».

Завдання 15: Перевірте здоров’я та читайте перші помилки, а не останні

cr0x@server:~$ docker compose -f /opt/myapp/docker-compose.yml ps
NAME                IMAGE                         COMMAND                  SERVICE    STATUS          PORTS
myapp-api-1          registry.local/api:1.42.0     "gunicorn -c ..."        api        Up 20s (healthy) 0.0.0.0:8080->8080/tcp
myapp-nginx-1        nginx:1.25                    "/docker-entrypoint…"    nginx      Up 20s          0.0.0.0:80->80/tcp
myapp-postgres-1     postgres:15                   "docker-entrypoint…"     postgres   Up 20s (healthy) 5432/tcp

Значення: Health checks проходять. Це сильний сигнал, але не гарантія.
Рішення: Якщо health падає, перегляньте найперші релевантні логи (спочатку БД, потім застосунок, потім проксі).

cr0x@server:~$ docker logs --tail=50 myapp-postgres-1
2026-01-02 02:14:12.101 UTC [1] LOG:  database system is ready to accept connections

Значення: Postgres успішно піднявся. Якщо ви бачите «invalid checkpoint record» або «permission denied», відновлення некоректне.
Рішення: Для помилок БД визначте, чи потрібне логічне відновлення замість копії файлової системи.

Завдання 16: Доведіть коректність запитом на рівні застосунку

cr0x@server:~$ curl -fsS http://127.0.0.1:8080/health
{"status":"ok","db":"ok","version":"1.42.0"}

Значення: Ваш застосунок каже, що здоровий. Тепер перевірте дані, а не лише живучість.
Рішення: Виконайте відомий запит або бізнес-перевірку (наприклад, «конкретний запис клієнта існує»).

cr0x@server:~$ docker exec -i myapp-postgres-1 psql -U postgres -tAc "select now(), count(*) from users;"
2026-01-02 02:14:35.812396+00|1842

Значення: У вас є дані, і виглядає правдоподібно.
Рішення: Порівняйте з очікуваним діапазоном або звітом контрольних сум. Якщо count рівний нулю, ви відновили не те або вказали порожній том.

Завдання 17: Виміряйте пропускну здатність відновлення, щоб прогнозувати RTO

cr0x@server:~$ /usr/bin/time -f 'elapsed=%E cpu=%P' tar -I zstd -xpf /backups/myapp/myapp-volumes-2026-01-02.tar.zst -C /restore-staging-voltest
elapsed=0:02:41 cpu=380%

Значення: CPU — вузьке місце (380% означає ~4 ядра завантажені). Варто знати.
Рішення: Якщо відновлення зав’язло на CPU, ви можете зменшити RTO, додавши ядра, налаштувавши рівень стиснення або змінивши інструмент — до реальної аварії.

Завдання 18: Перевірте права файлів і контекст SELinux/AppArmor (за потреби)

cr0x@server:~$ getenforce
Enforcing

Значення: SELinux увімкнено. Bind mounts можуть потребувати лейблів, інакше контейнери отримають «permission denied» попри правильні UNIX-права.
Рішення: Якщо ви використовуєте SELinux, переконайтеся, що Compose використовує :z або :Z там, де потрібно, і перелейбуйте відновлені шляхи.

cr0x@server:~$ ls -lZ /srv/postgres/conf | head -n 3
-rw-r--r--. root root unconfined_u:object_r:default_t:s0 pg_hba.conf
-rw-r--r--. root root unconfined_u:object_r:default_t:s0 postgresql.conf

Значення: Тип SELinux за замовчуванням на файлах конфігів може не відповідати очікуванням контейнера.
Рішення: Якщо контейнери не можуть читати bind mounts під SELinux, потрібно перелейблити або налаштувати параметри маунту; не відключайте SELinux як «виправлення».

Завдання 19: Переконайтеся, що відновлення не замінило томи приховано

cr0x@server:~$ docker inspect myapp-postgres-1 --format '{{range .Mounts}}{{.Name}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
myapp_pgdata /var/lib/docker/volumes/myapp_pgdata/_data -> /var/lib/postgresql/data

Значення: Контейнер використовує очікуваний том.
Рішення: Якщо ви бачите незнайоме ім’я тому, ви відновили в один том, а працюєте проти іншого. Це класична помилка репетицій.

Завдання 20: Зніміть доказ і часи (щоб репетиція покращувалася)

cr0x@server:~$ journalctl -u docker --since "today" | tail -n 5
Jan 02 02:13:55 server dockerd[1023]: API listen on /run/docker.sock
Jan 02 02:14:03 server dockerd[1023]: Loading containers: done.

Значення: У вас є мітки часу для старту демона Docker і завантаження контейнерів.
Рішення: Заносьте їх у звіт репетиції разом з початком/закінченням відновлення. Якщо ви не вимірюєте, ви будете сперечатися під час інциденту.

Жарт №2: Репетиція відновлення — як чищення міжзубних проміжків: усі стверджують, що роблять це, а докази зазвичай кровоточать.

Три корпоративні міні-історії (як це ламається в реальному житті)

Міні-історія 1: Інцидент через неправильне припущення

Середня SaaS-компанія запускала Docker Compose на кількох потужних VM. Їхні бекапи були «простими»: нічний tar від
/srv плюс щотижневий снапшот диска VM. Припущення було таке, що все важливе живе в /srv.

Аварія почалася з банальної проблеми зі сховищем. VM не хотіла нормально завантажитися після інциденту хоста. Команда розгорнула
нову VM і відновила /srv з нічного бекапу. Compose піднявся. Nginx віддавав сторінки. API повертав 500.

Логи Postgres показали щойно ініційований порожній кластер бази даних. Ніхто не відновив БД — бо ніхто її не бекапив. DB використовувала
іменований Docker том, що сидів у Docker root під /var/lib/docker/volumes, поза областю бекапу. Щотижневий снапшот VM містив його,
але був занадто старий для неявного RPO компанії і керувався іншою командою.

Постмортем не був драматичним. Був гірше: очевидним. Вони сплутали «директорію даних нашого застосунку» з «де Docker зберігає стан».
Виправлення було теж не хитромудрим: проінвентаризувати маунти, явно бекапити іменовані томи і запускати квартальну репетицію відновлення
на чистому хості. І ще: перестати називати це «простими бекапами», якщо вони не включають вашу базу.

Міні-історія 2: Оптимізація, яка відкинула назад

Інша організація серйозно взялася за швидкість. Час відновлення вищий за допустимий, тож вони оптимізували.
Перейшли від логічних дампів БД до crash-consistent снапшотів файлової системи тома БД. Це було швидше й давало менші інкрементальні передавання.
Всі аплодували.

Через шість місяців знадобилося відновлення. Поганий деплой пошкодив стан застосунку, і вони відкотилися. Відновлення «працювало»
механічно: снапшот розпакували, контейнери стартували, healthchecks стали зеленими. Потім трафік підскочив, і БД почала кидати
помилки: тонкі пошкодження індексів, дивна поведінка планувальника запитів, потім crash loop.

Корінна причина була нудною, але смертельною: снапшот зробили під навантаженням, без координування контрольної точки або DB-native механізму бекапу.
Бекап тома був консистентним на рівні файлової системи, але не на рівні бази даних. Відновився швидко і зламався пізно — саме той тип помилки, що краде час.

Виправлення було компромісом: зберегти швидкі снапшоти для короткострокових «упс» відновлень, але також робити періодичні DB-native бекапи
(або використовувати підтримувані процедури бази) і валідовувати їх. Додали job, який стартує відновлену БД у сандбоксі й проганяє перевірки цілісності.
Оптимізації дозволені. Не верифіковані оптимізації — це просто ризик у формі продуктивності.

Міні-історія 3: Нудна, але правильна практика, що врятувала день

Компанія, близька до фінансів, тримала кілька клієнтських сервісів у Docker. Їхній SRE-лід не був романтиком. Кожного кварталу вони проводили
репетицію відновлення в ізольованому VPC, зі свіжим образом VM і копією репозиторію бекапів. Репетиція мала чекліст і секундомір.

Репетиція завжди включала ті самі нудні кроки: перевірка доступності ключів шифрування для on-call, верифікація маніфестів бекапів, відновлення томів
у staging, потім перемикання на місце, виконання кількох прикладів бізнес-перевірок. Нарешті — документування часу й оновлення руйнбука.
Ніхто не любив це. Ніхто не підносив це в слайдах.

Потім стався реальний інцидент: оператор випадково видалив продакшн-том і репліка швидко підхопила проблему. On-call слідував руйнбуку без імпровізацій.
Вони вже знали, що найдовший крок — розпаковування, і налаштували розмір хоста під це. Вони знали, які саме секрети потрібні і де вони живуть.
Вони вже вирішили питання з SELinux — в репетиції, а не під час аварії.

Відновлення завершилося в очікуваному вікні. Не тому, що команда була героїчною, а тому, що вони навмисно були нудними. У операх нудність — це перевага.

Чеклісти / покроковий план

План репетиції відновлення (повторювано, а не «подивимось, що станеться»)

  1. Оголосіть масштаб і критерії успіху.

    • Які сервіси? Які набори даних? Які RPO/RTO ви верифікуєте?
    • Що означає «коректно» (запити, контрольні суми, дії в UI, кількість повідомлень)?
  2. Заморозьте інвентар.

    • Експортуйте Compose файли та посилання на env/секрети.
    • Перелічіть томи й bind mounts по контейнерах.
    • Запишіть посилання на образи (теги або дайджести).
  3. Провізуйте свіжий хост для відновлення.

    • Те саме сімейство ОС, схожі CPU/пам’ять, ті ж вибори файлової системи.
    • Той самий мережевий шлях до бекапів і реєстрів (або явно інший, якщо тестуєте DR-регіон).
  4. Завантажте артефакти бекапу і верифікуйте цілісність.

    • Контрольні суми, дешифрування, перелік вмісту, перевірка міток часу.
    • Підтвердіть, що у вас є ключі та паролі в моделі доступу, якої ви очікуєте під час інциденту.
  5. Відновлюйте спочатку в staging.

    • Bind mounts у /restore-staging.
    • Томи через допоміжні контейнери у щойно створені томи.
  6. Застосуйте права, лейбли й власність.

    • Томи БД мають відповідати очікуванням UID/GID контейнера.
    • SELinux/AppArmor: забезпечте правильні лейбли та параметри маунту.
  7. Підніміть стек з зафіксованими перевіреними образами.

    • Витягніть образи; якщо pull не вдається — використайте кеш/офлайн-образи.
    • Запускайте БД першою, потім застосунок, потім edge-проксі.
  8. Перевірте коректність.

    • Health endpoint + щонайменше один запит даних для кожного критичного сервісу.
    • Для черг/кешів: перевірте, чи потрібно їх відновлювати (зазвичай ні).
  9. Виміряйте часи і напишіть звіт репетиції.

    • Початок/кінець відновлення, пропускна здатність, вузькі місця, збої, виправлення.
    • Оновіть руйнбук і автоматизуйте крихкі кроки.

Що автоматизувати після першої чесної репетиції

  • Експорт інвентарю: маунти, томи, образи, Compose конфіги.
  • Генерацію маніфесту бекапу: очікувані шляхи та томи, розміри, мітки часу.
  • Перевірки цілісності: контрольні суми, тести архівів, періодичне відновлення в сандбокс.
  • Нормалізацію прав: відомі відповідності UID/GID для сервісів.
  • Збереження образів: тримайте потрібні образи для вашого RPO-вікна (або експортуйте tar).

Поширені помилки: симптом → корінна причина → виправлення

1) «Контейнери запущені, але застосунок порожній»

Симптом: Healthchecks проходять, але користувацькі дані відсутні або скинуті до дефолту.
Корінна причина: Відновлено в неправильний няв том або Compose створив новий порожній том через невідповідність імен проектів.
Виправлення: Перевірте маунти (docker inspect), переконайтеся, що імена томів співпадають, і явно вкажіть імена томів у Compose замість покладання на неявний scoping проекту.

2) «Permission denied» на відновлених bind mounts

Симптом: Контейнери падають з помилками доступу до файлів; файли на хості виглядають нормально.
Корінна причина: Неправильні SELinux-лейбли або rootless-контейнер очікує іншої власності, ніж дала відновлення.
Виправлення: Використовуйте опції маунту :z/:Z

3) Postgres/MySQL стартує, а потім поводиться дивно під навантаженням

Симптом: БД піднімається, але потім з'являються помилки, схожі на корупцію, або вона падає.
Корінна причина: Crash-consistent файловий бекап зроблений без координування з БД; несумісний стан WAL/контрольної точки.
Виправлення: Віддавайте перевагу нативним методам бекапу бази для надійних відновлень; якщо використовуєте снапшоти, координуйте з режимом бекапу БД і валідуйте у сандбоксі.

4) Відновлення «повільне без причини»

Симптом: Години відновлення, CPU завантажений, диски недовантажені.
Корінна причина: Однопотокове розпаковування/шифрування або занадто сильне стиснення; мільйони дрібних файлів підсилюють операції з метаданими.
Виправлення: Заміряйте розпаковування, подумайте про нижчий рівень стиснення чи паралельні інструменти, реорганізуйте бекапи (наприклад, архіви по томах), щоб зменшити метадані.

5) Не вдається витягти образи під час відновлення

Симптом: Аутентифікація до реєстру падає, DNS не працює або образи зникли.
Корінна причина: Облікові дані зберігалися тільки на старому хості; реєстр позбувся тегів, на які ви покладалися; залежність від публічного реєстру й ліміти.
Виправлення: Зберігайте облікові дані реєстру в відновлюваному менеджері секретів, фіксуйте образи дайджестами або незмінними тегами, і майте офлайн-кеш/експорт критичних образів.

6) Compose «працює в проді», але падає на хості відновлення

Симптом: Той самий Compose файл, різна поведінка: порти, DNS, мережі, MTU проблеми.
Корінна причина: Прихований дріфт конфігурації хоста: sysctls, iptables, модулі ядра, кастомний daemon.json або cloud-специфічна мережа.
Виправлення: Кодизуйте провізію хостів (IaC), експортуйте та версіонуйте налаштування демона і включайте «чистий хост» репетицію щороку.

7) Бекап присутній, але ключів немає

Симптом: Ви бачите файл бекапу, але не можете його дешифрувати чи відкрити під час інциденту.
Корінна причина: Ключі шифрування/паролі закриті за людиною, мертвим ноутом або зламаним SSO шляхом.
Виправлення: Практикуйте відновлення ключів під час репетицій, зберігайте break-glass доступ правильно і верифікуйте процедуру з роллю on-call з мінімально необхідними правами.

8) Ви відновили конфіг, але не «нудні» залежності

Симптом: Застосунок стартує, але не може відправити пошту, не підключається до платіжного провайдера або колбеки падають.
Корінна причина: Відсутні TLS сертифікати, правила фаєрволу, DNS записи, секрети webhook або дозволи на вихідні з'єднання.
Виправлення: Розглядайте зовнішні залежності як частину «стану деплойменту» і тестуйте їх у репетиції (або явно стабітьте й задокументуйте).

FAQ

1) Чи слід бекапити /var/lib/docker?

Зазвичай ні. Бекапте томи і будь-які bind-mounted директорії застосунку, плюс Compose конфіг і посилання на секрети.
Бекап усього Docker root каталогу крихкий між версіями, драйверами зберігання та відмінностями хостів.

2) Який найнадійніший спосіб бекапу бази даних у Docker?

Використовуйте підтримуваний метод бекапу самої бази (логічні дампи, base backups, WAL-архівування тощо) і валідуйте відновлення в сандбоксі.
Резервні копії на рівні файлової системи можуть працювати при правильній координації, але «якось раз було нормально» — це не метод.

3) Як часто проводити репетиції відновлення?

Квартально для критичних систем — розумна відправна точка. Щомісяця, якщо система часто змінюється або якщо RTO/RPO жорсткі.
Також робіть репетицію після великих змін: міграція сховища, оновлення Docker, оновлення БД або зміна інструментів бекапу.

4) Чи можна провести репетицію без дублювання продакшн-даних (через приватність)?

Так: використовуйте масковані набори даних, синтетичні фікстури або відновлюйте в зашифроване ізольоване середовище з суворим доступом.
Але вам потрібно відновити реалістичну структуру: права, розміри, кількість файлів, схему і поведінку на рантаймі.

5) Що найбільше роздуває час відновлення?

Дрібні файли та дерева з великою кількістю метаданих, особливо в поєднанні з шифруванням і стисненням. Можна мати багато пропускної здатності,
але бути заблокованим CPU або IOPS.

6) Чи компресувати бекапи?

Зазвичай так, але обирайте стиснення, що відповідає вашим обмеженням відновлення. Якщо ви під завантаженням CPU під час відновлення,
сильне стиснення шкодить RTO. Заміряйте розпаковування під час репетицій і налаштуйте.

7) Як зрозуміти, чи відновлено потрібне?

Не довіряйте статусу контейнера. Використовуйте перевірки на рівні застосунку: виконайте SQL-запит, перевірте кількість записів, підтвердіть конкретного клієнта
або прогоніть read-only бізнес-транзакцію. Автоматизуйте ці перевірки в репетиції.

8) Чи потрібно відновлювати Redis та інші кеші?

Зазвичай ні — кеші відтворювані, і їх відновлення може занести поганий стан назад. Але потрібно переконатися, що застосунок витримає порожній кеш
і що конфігурація кешу (паролі, TLS, maxmemory) бекапиться.

9) А секрети в змінних оточення?

Якщо ваше продакшн-залежить від env-файлу, цей файл — частина стану деплойменту і має бути відновлюваним. Краще: мігруйте секрети в
менеджер секретів або еквівалент Docker secrets і включіть процедуру доступу break-glass у репетицію.

10) Чи можна робити це з Docker Compose і бути «enterprise-grade»?

Так, якщо ви трактуєте Compose як артефакт з версіонуванням, зафіксованими образами, протестованими відновленнями і дисципліною управління станом.
«Enterprise-grade» — це поведінка, а не вибір інструмента.

Висновок: наступні кроки, які ви можете зробити цього тижня

Якщо ви зробите тільки одне — заплануйте репетицію відновлення на свіжому хості і виміряйте час. Не в продакшні, не на ноуті, не «колись».
Заплануйте її й запросіть кожного, хто володіє бекапами, сховищем і застосунком. Ви хочете мати в кімнаті всі режими відмов.

Потім зробіть ці кроки за порядком:

  1. Інвентаризуйте маунти для кожного stateful контейнера і запишіть авторитетні шляхи та імена томів.
  2. Розділіть артефакти на дані (томи), bind mounts і конфіг деплойменту, щоб відновлювати селективно.
  3. Верифікуйте цілісність найновішого набору бекапів і доведіть, що маєте ключі для дешифрування під правами on-call.
  4. Відновіть у сандбокс і виконайте перевірки коректності на рівні застосунку, а не лише «контейнер запущений».
  5. Виміряйте RTO, знайдіть найповільніший крок і виправте саме його, перш ніж оптимізувати щось інше.

Резервні копії, які ви ніколи не відновлювали, — це не бекапи. Це стиснений оптимізм. Проведіть репетицію, запишіть, що зламалося, і зробіть це нудним.

← Попередня
ZFS: ECC проти non-ECC — математика ризику для реальних розгортань
Наступна →
ZFS SMB: Виправлення проблеми «Копіювання у Windows повільне»

Залишити коментар