Резервні копії Docker-томів, які справді відновлюються

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

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

Цей посібник для роботи в продакшні. Не для демо. Ми робитимемо бекапи Docker-томів так, щоб вони переживали реальність, і доведемо працездатність відновлення повторюваними тренуваннями та вимірюваними перевірками.

Що ви насправді зберігаєте в резерві

«Зробіть бекап контейнера» — фраза, що звучить розумно і зазвичай невірна.

Контейнери — це тимчасові процеси плюс образ. Образ (зазвичай) можна перебудувати. Справжнє ураження від збою — у даних, що живуть поза образом:

  • Іменовані томи (керуються Docker; зазвичай під /var/lib/docker/volumes).
  • Bind mounts (шляхи хоста, змонтовані в контейнери; часто «просто папка», доки не перестає бути).
  • Секрети/конфіг (змінні середовища, змонтовані файли, swarm secrets, Compose-файли, systemd unit-файли).
  • Зовнішні сервіси (керовані бази даних, об’єктне сховище), від яких залежить контейнер, але які не містяться всередині нього.

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

Для баз даних «узгодженість» — це не абстрактне враження. Це стан. Або ви використовуєте рідні інструменти БД для бекапу, або знімаєте снапшот сховища з правильно припиненими операціями СУБД.

Цікавинки (і чому вони важливі)

  1. Docker-томи спроєктовані, щоб відокремити дані від життєвого циклу контейнера. Саме тому «видалити контейнер» не видаляє том — поки хтось не вкаже -v не подумавши.
  2. AUFS/OverlayFS популяризували копіювання при записі для шарів контейнерів. Чудово для образів; не стосується ваших персистентних даних, які живуть у томах або bind mount.
  3. Ранні користувачі контейнерів часто бекапили весь каталог /var/lib/docker. Це «працювало», поки не змінили драйвери сховища або хост відновлення. Портативність була жертвою.
  4. Вендори баз даних десятиліттями пропагували «логічні резервні копії», бо фізичні копії файлів під час запису можуть бути тихо пошкоджені без явних помилок під час копіювання.
  5. Файлові система зі знімками (ZFS, LVM, btrfs) з’явилися задовго до контейнерів. Контейнери зробили знімки популярними знову, бо вони потребують швидкого та частого захоплення з низькими накладними витратами.
  6. Tar старший за більшість вашого продакшн-флоту. Він досі тут, бо простий, потоковий і гарно інтегрується зі стисненням та шифруванням.
  7. RPO/RTO стали словами в радях правління після гучних інцидентів. Контейнери цього не змінили; вони лише спростили плутанину між «перебудовуване» та «відновлюване».
  8. Контрольні суми не опціональні в серйозних системах резервного копіювання. Тиха корупція існує на будь-якому шарі: ОЗП, диск, контролер, мережа, об’єктне сховище. Перевіряйте або будьте здивовані.

Принципи: робіть ось так, а не інакше

1) Розглядайте «бекап» як робочий процес відновлення, який ви ще не виконували

Файл резервної копії — не доказ. Успішне відновлення в чисте середовище — це доказ. Ваша мета — зменшити невизначеність, а не створювати артефакти.

2) Розділяйте «бекап даних» і «перебудову сервісу»

Ведіть два інвентарі:

  • Інвентар перебудови: образи, Compose-файли, системні конфіги, процес випуску TLS-сертифікатів, управління секретами.
  • Інвентар даних: томи, дампи БД/WAL/binlogs, завантажені файли, черги, індекси пошуку (і чи можна їх відтворити).

3) Віддавайте перевагу рідним механізмам бекапу для БД

Для PostgreSQL використовуйте pg_dump або фізичні бекапи з pg_basebackup (і WAL). Для MySQL/MariaDB — mysqldump або фізичні методи, що підходять для вашого рушія. Знімати сирі файли БД під час запису — це азартна гра.

4) Коли робите файлові бекапи, контролюйте запис

Або:

  • Зупиніть контейнер застосунку (або переведіть його в режим обслуговування/тільки для читання), а потім копіюйте; або
  • Використовуйте знімки файлової системи на хості; або
  • Використовуйте хуки припинення для БД (flush/lock) і швидко знімайте снапшот.

5) Робіть бекапи адресованими за вмістом (або хоча б з перевіркою контрольних сум)

Щонайменше: зберігайте маніфест із переліком файлів + розміри + хеші. «Файл існує» — це не перевірка.

6) Тести відновлення мають бути ізольованими й автоматизованими

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

Жарт #1: Резервна копія, яку ніколи не відновлювали, — як парашут, який ніколи не пакували: впевненість — це не тест-план.

Один цитат, бо досі правдивий

Надія — не стратегія. — приписують в опс-колах; трактуйте як перефразовану ідею, а не гарантовано точну оригінальну цитату.

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

Ці завдання навмисно буденні. Продакшн-помилки теж зазвичай буденні.

Завдання 1: Перелічіть томи і виявіть важливі

cr0x@server:~$ docker volume ls
DRIVER    VOLUME NAME
local     app_postgres_data
local     app_uploads
local     monitoring_prometheus

Що це означає: існують три іменовані томи. Ваші образи не перераховані, бо томи — незалежні об’єкти.

Рішення: класифікуйте кожний том: «повинні відновити» (БД, завантаження), «приємно мати» (кеші), «перебудовується» (Prometheus може бути перебудований залежно від політики ретенції).

Завдання 2: Інспектуйте том, щоб знайти, де він живе

cr0x@server:~$ docker volume inspect app_postgres_data
[
  {
    "CreatedAt": "2025-11-02T12:44:10Z",
    "Driver": "local",
    "Labels": {
      "com.docker.compose.project": "app"
    },
    "Mountpoint": "/var/lib/docker/volumes/app_postgres_data/_data",
    "Name": "app_postgres_data",
    "Options": null,
    "Scope": "local"
  }
]

Що це означає: ви використовуєте драйвер local; дані — це директорія на хості. Це просто й небезпечно: просто робити бекап, але легко зіпсувати, якщо копіювати «вживу».

Рішення: вирішіть, чи будете застосовувати рідні механізми бекапу застосунку (переважно для БД), або снапшот/холодну копію.

Завдання 3: Знайдіть, які контейнери використовують том (оцінка радіусу ураження)

cr0x@server:~$ docker ps --format '{{.Names}}\t{{.Mounts}}'
app-web        /var/lib/docker/volumes/app_uploads/_data
app-db         /var/lib/docker/volumes/app_postgres_data/_data
prometheus     /var/lib/docker/volumes/monitoring_prometheus/_data

Що це означає: кожний том змонтований одним контейнером. Це добре: менше процесів запису.

Рішення: якщо кілька писачів монтують той самий том, ваш план узгодженості має бути більш конкретним (блокування, quiesce або редизайн).

Завдання 4: Виявлення bind mount, що ховається на видноті

cr0x@server:~$ docker inspect app-web --format '{{json .Mounts}}'
[{"Type":"bind","Source":"/srv/app/config","Destination":"/etc/app","Mode":"ro","RW":false,"Propagation":"rprivate"},{"Type":"volume","Name":"app_uploads","Source":"/var/lib/docker/volumes/app_uploads/_data","Destination":"/var/www/uploads","Driver":"local","Mode":"z","RW":true,"Propagation":""}]

Що це означає: у вас є bind mount за /srv/app/config. Якщо ви бекапите лише Docker-томи, ви пропустите конфіг — тоді відновлення «працює», але сервіс не запуститься.

Рішення: додайте шляхи bind mount у зону бекапу або перемістіть їх у керовану систему конфігів.

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

cr0x@server:~$ df -h /var/lib/docker
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2  450G  380G   48G  89% /

Що це означає: лише 48G вільно. Тарбол великого тому може заповнити диск і вивести Docker з ладу.

Рішення: передавайте бекап потоково кудись за межі хоста (pipe до сховища), або робіть снапшот і передавайте, або звільніть місце спочатку.

Завдання 6: Дістаньте швидко розмір тому (орієнтовно, але корисно)

cr0x@server:~$ sudo du -sh /var/lib/docker/volumes/app_postgres_data/_data
23G	/var/lib/docker/volumes/app_postgres_data/_data

Що це означає: приблизно 23G на диску. Стиснення може допомогти (або ні, залежно від даних).

Рішення: плануйте політику ретенції й час передачі. 23G щодня по повільному каналу швидко стають проблемою.

Завдання 7: Холодний бекап іменованого тому за допомогою tar (безпечний для не-БД або зупиненої БД)

cr0x@server:~$ docker stop app-web
app-web
cr0x@server:~$ docker run --rm -v app_uploads:/data:ro -v /backup:/backup alpine:3.20 sh -c 'cd /data && tar -cpf /backup/app_uploads.tar .'
cr0x@server:~$ docker start app-web
app-web

Що це означає: бекап виконується у тимчасовому контейнері, який монтує том у режимі лише для читання і записує tar у /backup (директорію хоста, яку ви маєте підготувати).

Рішення: якщо зупинка застосунку неприпустима, переходьте до бекапів через снапшоти або рідних механізмів застосунку.

Завдання 8: Додайте стиснення і маніфест з контрольною сумою

cr0x@server:~$ docker run --rm -v app_uploads:/data:ro -v /backup:/backup alpine:3.20 sh -c 'cd /data && tar -cpf - . | gzip -1 > /backup/app_uploads.tar.gz'
cr0x@server:~$ sha256sum /backup/app_uploads.tar.gz
9c1e6311d2c51d6f9a9b8b3f5d65ed3db3f87e96a57c4e1b2f5c34b1b1a4d9a0  /backup/app_uploads.tar.gz

Що це означає: у вас тепер є перевірка цілісності. Зберігайте контрольну суму поруч з артефактом у репозиторії бекапів.

Рішення: якщо ви не можете створювати та перевіряти контрольні суми, у вас не операційний бекап — просто файл.

Завдання 9: Логічний бекап PostgreSQL зсередини контейнера (переважно для портативності)

cr0x@server:~$ docker exec -t app-db sh -c 'pg_dump -U postgres -Fc appdb' > /backup/appdb.dump
cr0x@server:~$ ls -lh /backup/appdb.dump
-rw-r--r-- 1 cr0x cr0x 3.2G Dec  7 02:10 /backup/appdb.dump

Що це означає: дамп у форматі custom, що підтримує паралельне відновлення і більш стійкий між незначними версіями.

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

Завдання 10: Тест відновлення PostgreSQL у тимчасовий контейнер (доказ, а не теорія)

cr0x@server:~$ docker run --rm --name pg-restore-test -e POSTGRES_PASSWORD=test -d postgres:16
2f4a6f88d3c8e6d0b0f14a27e8c2e6d84e8c4b7f6ddc5a8c1d2b3a4f5e6d7c8b
cr0x@server:~$ sleep 3
cr0x@server:~$ cat /backup/appdb.dump | docker exec -i pg-restore-test sh -c 'createdb -U postgres appdb && pg_restore -U postgres -d appdb'
cr0x@server:~$ docker exec -t pg-restore-test psql -U postgres -d appdb -c 'select count(*) from users;'
 count 
-------
 10492
(1 row)
cr0x@server:~$ docker stop pg-restore-test
pg-restore-test

Що це означає: ви відновили в чисту БД й виконали перевірочний запит. Це реальний доказ.

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

Завдання 11: Відновіть tarball іменованого тому в свіжий том

cr0x@server:~$ docker volume create app_uploads_restore_test
app_uploads_restore_test
cr0x@server:~$ docker run --rm -v app_uploads_restore_test:/data -v /backup:/backup alpine:3.20 sh -c 'cd /data && tar -xpf /backup/app_uploads.tar'
cr0x@server:~$ docker run --rm -v app_uploads_restore_test:/data alpine:3.20 sh -c 'ls -lah /data | head'
total 64K
drwxr-xr-x    5 root     root        4.0K Dec  7 02:24 .
drwxr-xr-x    1 root     root        4.0K Dec  7 02:24 ..
drwxr-xr-x   12 root     root        4.0K Dec  5 19:11 images
drwxr-xr-x    3 root     root        4.0K Dec  6 08:33 tmp

Що це означає: ви можете розпакувати архів і побачити очікувані директорії верхнього рівня.

Рішення: якщо правами/власністю має значення (а це часто так), перевірте їх на репрезентативному файлі і переконайтеся, що tar їх зберіг (-p допомагає при запуску від root).

Завдання 12: Перевірте цілісність архіву перед відновленням (виявляє пошкоджені передачі)

cr0x@server:~$ sha256sum -c /backup/app_uploads.tar.gz.sha256
/backup/app_uploads.tar.gz: OK

Що це означає: артефакт відповідає очікуваному хешу.

Рішення: якщо бачите FAILED, зупиняйтеся. Не відновлюйте сміття в продакшн. Візьміть іншу копію бекапу.

Завдання 13: Виявлення відкритих файлів, щоб оцінити ризик «живої копії»

cr0x@server:~$ sudo lsof +D /var/lib/docker/volumes/app_postgres_data/_data | head
postgres  22114  999  15u   REG  259,2  16777216  393222 /var/lib/docker/volumes/app_postgres_data/_data/base/16384/2619
postgres  22114  999  16u   REG  259,2  16777216  393223 /var/lib/docker/volumes/app_postgres_data/_data/base/16384/2620

Що це означає: PostgreSQL активно записує. Копіювання цих файлів зараз — не бекап; це генератор корупції.

Рішення: використовуйте pg_dump/pg_basebackup або зупиніть/приведіть у quiesce і знімайте снапшот.

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

cr0x@server:~$ time docker run --rm -v app_uploads_restore_test:/data -v /backup:/backup alpine:3.20 sh -c 'cd /data && rm -rf ./* && tar -xpf /backup/app_uploads.tar'
real	0m18.412s
user	0m0.812s
sys	0m3.951s

Що це означає: відновлення зайняло ~18 секунд для цього набору даних на цьому хості. Це число використовують для планування RTO (плюс час прогріву сервісу).

Рішення: якщо відновлення повільне, не оптимізуйте на око — використайте план швидкої діагностики.

Методи бекапу, що витримують випробування

Метод A: Логічні бекапи для БД (рекомендується)

Якщо у вас одна БД у томі і ви робите tar цієї директорії «бо так просто», зупиніться. Використовуйте механізми самої БД. Ви отримаєте:

  • Портативність між хостами і драйверами сховища
  • Узгодженість, гарантовану рушієм
  • Кращу діагностику: відновлення скаже, що не так

Для PostgreSQL хороша база — щоденний pg_dump -Fc плюс архівація WAL для точкового відновлення. Для MySQL — mysqldump або фізичні бекапи з binlogs.

Компроміс: логічні бекапи можуть бути повільнішими й більшими для деяких навантажень, а відновлення може займати більше часу, ніж файлове відновлення. Це бізнес-рішення — прийміть свідомо.

Метод B: «Холодний» файловий бекап тому (зупиніть записувач)

Для завантажень, конфігів, артефактів і нетранзакційних даних: зупинка контейнера (або забезпечення відсутності записів) і копіювання тому просте і зрозуміле.

  • Плюси: просто, швидко, зрозуміло
  • Мінуси: вимагає даунтайму або замороження записів; потрібно зберегти власність/ACL/xattrs, якщо це важливо

Якщо ваш застосунок використовує Linux capabilities, SELinux-лейбли або ACL, ваша команда tar повинна це зберігати. Alpine tar підходить для базових прав; якщо потрібні xattrs/ACL, використовуйте контейнер із GNU tar і відповідними прапорами.

Метод C: Резервне копіювання на основі знімків файлової системи на хості (швидко, мало даунтайму)

Якщо каталог даних Docker живе на ZFS, LVM-thin або btrfs, можна швидко створити снапшот базового датасету/тома, а потім копіювати з цього снапшоту, поки продакшн працює.

Важливо: знімок файлової системи не робить застосунок автоматично узгодженим. Для БД поєднуйте снапшоти з правильним quiesce або режимом бекапу рушія, інакше отримаєте послідовний файловий стан, який містить неузгоджені файли БД.

Метод D: Віддалені драйвери томів / мережеве сховище

Деякі команди використовують NFS, iSCSI, Ceph або блокове сховище хмари за драйвером томів Docker. Це може бути добре, але зміщує проблему бекапу:

  • Тепер ви бекапите систему зберігання, а не Docker.
  • Відновлення може вимагати того ж драйвера і конфігурації.
  • Затримки й поведінка дрібних записів можуть нашкодити БД.

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

Метод E: Образне «резервне копіювання» (пастка)

Хтось скаже «docker commit контейнера». Це створює шар образу, що містить файлову систему контейнера в той момент. Він не включає іменовані томи. Рідко захоплює bind mounts. Це також хороший спосіб назавжди зберегти секрети в образі. Не використовуйте для бекапів даних.

Жарт #2: «Ми зробили бекап контейнера» — це як отримати гарний образ сервісу, який забув усе, що коли-небудь знав.

Як довести, що відновлення працює (не лише «файл розпакувався»)

Визначте «працює» як тестовану угоду

Відновлення «працює», коли:

  • Дані відновлюються в чисте середовище без ручних підгонок.
  • Сервіс стартує з відновленими даними.
  • Невеликий набір перевірок поведінки проходить: запити повертають очікувані рядки, завантаження читаються, міграції поводяться, в логах немає ознак корупції.
  • Команда може зробити це під тиском часу за наявності рукбуку.

Побудуйте тренування з відновлення, що виконується за графіком

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

  1. Завантажити останній артефакт бекапу.
  2. Перевірити контрольні суми.
  3. Відновити в тимчасову інфраструктуру (VM, епхемерний хост або ізольована Docker-мережа).
  4. Запустити короткий набір валідаційних тестів.
  5. Опублікувати результати кудись помітно (тікети, канал у Slack, дашборд), включно з причинами збоїв.

Валідаційний набір: приклади, що реально ловлять проблеми

  • База даних: виконайте SELECT-запити на кількості рядків у ключових таблицях; перевірте, чи міграції можуть виконуватись у dry-run-режимі, якщо доступно; підтвердіть наявність індексів; підтвердіть наявність останніх міток часу.
  • Завантаження: виберіть 10 відомих файлів і перевірте контрольну суму або хоча б розмір; переконайтеся, що права дозволяють користувачу додатку їх читати.
  • Запуск застосунку: перевірте логи на відомі помилки (permission denied, відсутні ключі конфігурації, невідповідність схеми).

Доведіть RPO і RTO, а не лише коректність

Коректність відповідає на питання «чи можемо ми відновити?». RPO/RTO відповідає на питання «чи можемо ми відновити вчасно з прийнятними втратами даних?». Виміряйте:

  • RPO: час між останнім успішним бекапом і моментом інциденту (використовуйте часові мітки бекапів, не відчуття).
  • RTO: час від «починаємо відновлення» до «сервіс відповідає SLO знову». Включіть час на отримання артефактів, розпакування, перевірку й прогрів кешів.

Тримайте «набір для відновлення» з усім, що не є даними

Більшість невдалих відновлень — це не «погані дані». Це відсутня склейка:

  • Compose-файл версією зафіксований і збережений
  • Образи контейнерів версіоновані (або процес збірки відтворюваний)
  • Шлях управління секретами задокументовано
  • Мережа/порти, конфіг реверс-проксі, процес поновлення TLS-сертифікатів
  • Нотатки щодо сумісності версій бази даних

Швидка діагностика

Бекапи та відновлення дають збій передбачуваними способами. Не починайте з безпланової переробки скриптів. Почніть із звуження вузького місця.

Перше: це проблема узгодженості чи механіки?

  • Механіка: tar падає, контрольна сума не сходиться, permission denied, немає місця, повільна передача.
  • Узгодженість: відновлення завершується, але застосунок помилиться, БД повідомляє про корупцію, бракує останніх даних.

Друге: визначте найповільніший етап

  1. Отримання артефакту (мережа/об’єктне сховище)
  2. Розпакування (зв’язано з CPU)
  3. Розпакування/запис (зв’язано з диском, inode)
  4. Відновлення застосунку (відтворення WAL, міграції)

Третє: швидкі перевірки, що дають відповіді

Перевірте насичення диска (відновлення — це багато записів)

cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (server) 	12/07/2025 	_x86_64_	(8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.31    0.00    8.14   34.22    0.00   45.33

Device            r/s     rkB/s   rrqm/s  %rrqm  r_await rareq-sz     w/s     wkB/s   wrqm/s  %wrqm  w_await wareq-sz  aqu-sz  %util
nvme0n1         12.0   1456.0     0.0    0.0    1.20   121.3     980.0  84224.0   120.0   10.9   18.50    85.9    18.2   98.0

Що це означає: %util близько до 100% і підвищений w_await вказують, що ліміт — диск.

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

Перевірте, чи CPU завантажено розпакуванням

cr0x@server:~$ top -b -n 1 | head -n 15
top - 02:31:20 up 21 days,  4:12,  1 user,  load average: 7.92, 8.10, 6.44
Tasks: 212 total,   2 running, 210 sleeping,   0 stopped,   0 zombie
%Cpu(s): 92.1 us,  0.0 sy,  0.0 ni,  5.8 id,  0.0 wa,  0.0 hi,  2.1 si,  0.0 st
MiB Mem :  32158.5 total,   812.4 free,  14220.1 used,  17126.0 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  17938.4 avail Mem

Що це означає: CPU завантажений у користувацькому просторі; розпакування або підрахунок сум можуть бути вузьким місцем.

Рішення: використовуйте швидше стиснення (gzip -1 замість більш тяжкого), паралельні інструменти розпакування або зберігайте несжаті дані в швидкій внутрішній мережі.

Перевірка вичерпання inode (класично для великої кількості дрібних файлів)

cr0x@server:~$ df -ih /var/lib/docker
Filesystem     Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2   28M   27M  1.0M   97% /

Що це означає: може бути вільне місце, але немає inode; відновлення падає з «No space left on device», хоча df -h виглядає нормально.

Рішення: перемістіть томи на файлову систему з більшою кількістю inode або налаштуйте параметри створення FS; зменшіть кількість дрібних файлів; розгляньте пакування в об’єктне сховище там, де це доцільно.

Перевірка невідповідності прав/власності (поширено після tar-відновлення)

cr0x@server:~$ docker exec -t app-web sh -c 'id && ls -ld /var/www/uploads'
uid=1000(app) gid=1000(app) groups=1000(app)
drwxr-xr-x    5 root     root        4096 Dec  7 02:24 /var/www/uploads

Що це означає: застосунок запускається як UID 1000, але директорія належить root. Читання може працювати; запис — ні.

Рішення: виправляйте власність у процедурі відновлення (наприклад, chown -R 1000:1000), або зберігайте/відновлюйте власність і впевніться у відповідності мапування користувачів.

Типові помилки: симптом → корінь проблеми → виправлення

1) «Відновлення завершилося, але в застосунку бракує останніх даних»

Симптом: сервіс стартує, але пропала остання доба/тиждень даних.

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

Виправлення: впровадьте карту відповідності (том/база → ім’я артефакту). Примусово фейлити задачу, якщо розмір дампу менший за поріг. Виконуйте заплановані тренування відновлення зі sanity-запитами.

2) «Tar-резервна копія існує, але відновлення дає permission denied»

Симптом: логи застосунку показують помилки при записі у відновлені директорії.

Корінь проблеми: відновлення виконано як root без урахування очікувань UID/GID; втрачені ACL/xattrs; або контейнери працюють не від root.

Виправлення: зафіксуйте й відновіть власність; додайте крок відновлення, що застосовує правильний UID/GID; перевіряйте запис від імені користувача застосунку.

3) «БД не стартує після копіювання тому»

Симптом: PostgreSQL або MySQL відмовляються стартувати, скаржаться на WAL/binlog або пошкоджені сторінки.

Корінь проблеми: файловий копіювання зроблене під час запису БД; неповний снапшот; неузгоджений стан.

Виправлення: використовуйте логічний бекап або правильний фізичний бекап. Якщо мусите знімати снапшот, зробіть quiesce коректно і створіть атомарний снапшот.

4) «Задача бекапу триває вічно і викликає спайки затримки»

Симптом: у продакшні росте I/O latency під час бекапу; застосунок гальмує.

Корінь проблеми: читання з бекапу конкурує з продакшн-зчитуваннями; стиснення важке для CPU; сховище насичене; багато дрібних файлів.

Виправлення: обмежте швидкість читання бекапу; плануйте на непікові години; використовуйте снапшоти; змініть рівень стиснення; перерахуйте структуру даних.

5) «Ми відновили том, але застосунок все ще вказує на старі дані»

Симптом: відновлення виглядає успішним, але сервіс віддає застарілий контент.

Корінь проблеми: ви відновили в новий том, але Compose-файл все ще посилається на старий; або шляхи bind mount відрізняються.

Виправлення: явно переключайте посилання на томи; використовуйте унікальні імена проєкту для тестів відновлення; підтверджуйте монтування контейнерів через docker inspect.

6) «Перевірка контрольних сум інколи не проходить»

Симптом: випадкові невідповідності хешів у різні дні.

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

Виправлення: пишіть у тимчасове ім’я, потім перейменовуйте атомарно; зберігайте й перевіряйте маніфести; переконайтесь, що інструмент завантаження підтримує перевірку multipart; повторюйте при невідповідності й алертьте.

Три корпоративні міні-історії (анонімізовані)

Міні-історія 1: Інцидент через хибне припущення

Команда запускала клієнтський застосунок з контейнером PostgreSQL та іменованим томом. Скрипт бекапу tar-ив том щонічно. Все виглядало чисто. Були часові мітки та ретеншн. Усі спали спокійно.

Потім on-call отримав пейдж через збій хоста. Плани відновлення виглядали просто: підняти нову VM, відновити tarball у свіжий том, запустити контейнер БД. Він стартував… і одразу впав. Логи згадували проблеми з WAL і стан БД, що «виглядає, ніби її копіювали під час запису». Бо так воно і було.

Хибне припущення було тонким: «файлова копія — це бекап». Скрипт запускався о 2:00, коли навантаження було низьким, але не нульовим. PostgreSQL все ще писав. Копія створила архів, який внутрішньо був неузгодженим у спосіб, який tar ніколи не виявить.

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

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

Інша організація мала гору користувацьких завантажень. Бекапи були надто повільні, тож хтось «оптимізував» їх максимальною стисненням, щоб зекономити трафік і простір. Артефакти сильно зменшилися. Усі раділи графіку витрат.

Відновлення ніколи не тестували у повному масштабі. Перше реальне відновлення сталося під час інциденту безпеки, коли треба було швидко перебудувати хости. Вони почали розпаковувати бекап. CPU підійшов до стелі. Записи на диск вишикувалися в чергу. Пайплайн відновлення тривав годинами довше, ніж обіцяний RTO у презентації.

Технічна проблема не в тому, що стиснення — це погано. Проблема в тому, що оптимізували одну метрику (збережені байти) без вимірювання часу відновлення. Також розпаковували на тих самих продуктивних, але обмежених CPU вузлах, що перетворило відновлення на обчислювально-зв’язане завдання.

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

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

Фінансово орієнтований SaaS запускав кілька сервісів через Docker Compose. У їхніх рукбуків був щотижневий «репетиційний» тикет відновлення. On-call відновлював останній дамп БД у тимчасовий контейнер, піднімав стек застосунків в ізольованій мережі і виконував кілька API викликів.

Ніяких героїчних вчинків. Просто повторення. Вони логували часи: час завантаження, час відновлення, час першого успішного запиту. Якщо числа змінювалися, вони досліджували проблему, поки не панікували.

Одного вікенду інцидент зі сховищем пошкодив файлову систему хоста. Вони перебудували вузол, відновили томи й дампи БД і повернулися в онлайн без драми. Те, що виглядало як вдача, було просто навичкою. Команда знала заздалегідь, які артефакти валідні, скільки займає відновлення і які команди можуть зламатися, якщо щось не так.

Справжній виграш: не треба було імпровізувати. Вони виконали відпрацьований робочий процес і повернулися до звичайної помірної роздратованості від моніторингу — ідеальний емоційний стан для on-call.

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

Чекліст A: Побудуйте інвентар бекапів (за один вечір)

  1. Перелічіть томи: docker volume ls.
  2. Перелічіть bind mounts: docker inspect для кожного контейнера і витягніть Mounts.
  3. Класифікуйте дані: база даних / завантаження / кеш / відтворюване.
  4. Визначте RPO і RTO для кожного класу (хоч би приблизно — краще, ніж мовчання).
  5. Запишіть вимоги до власності/прав (UID/GID, ACL, SELinux).

Чекліст B: Впровадьте бекапи (повторювані скрипти)

  1. Для БД: впровадьте логічний бекап (або коректний фізичний) і зберігайте артефакти поза хостом.
  2. Для файлових томів: виберіть холодну копію або снапшот-орієнтований підхід; уникайте живої копії для писачів.
  3. Створюйте маніфест для кожного артефакту: таймстемп, джерело, розмір, контрольна сума, версія інструменту.
  4. Робіть бекапи атомарними: пишіть у тимчасове ім’я, потім перейменовуйте; ніколи не залишайте часткові артефакти з «фінальним» іменем.
  5. Оповіщайте про помилки та підозрілі малі вихідні файли.

Чекліст C: Доведіть відновлення (щотижня або щомісяця)

  1. Завантажте останній артефакт.
  2. Перевірте контрольну суму.
  3. Відновіть у чисте середовище (новий том/новий контейнер/новий Compose-проєкт).
  4. Запустіть перевірки валідації (запити, перевірки файлів, health endpoints).
  5. Записуйте час і результати; відкривайте тікет при відхиленнях.

Чекліст D: Руководство відновлення під час інциденту (коли вже погано)

  1. Зупиніть кровотечу: припиніть писачів (режим обслуговування, зупинка контейнерів).
  2. Визначте «останню відому хорошу» копію з логів тренувань відновлення, а не з надії.
  3. Відновлюйте в нові томи; не перезаписуйте докази, якщо це не потрібно.
  4. Піднімайте сервіси за порядком залежностей (спочатку БД, потім застосунок, потім воркери).
  5. Перевірте зовні (синтетичні перевірки) і всередині (логи, перевірки цілісності БД).
  6. Після відновлення: збережіть зламаний диск/том для судової експертизи, якщо потрібно.

FAQ

1) Чи варто бекапити /var/lib/docker?

Загалом ні. Це не портативно між драйверами сховища, версіями Docker і макетами хостів. Бекапте дані (томи й bind mounts) і дефініції (Compose-файли, конфіги) окремо.

2) Чи безпечний tar тому завжди?

Безпечний, якщо дані не змінюються або якщо застосунок толерує crash-consistent копії. Для баз даних вважайте, що це не безпечно, якщо ви не припинили запис або не використали рідні інструменти БД.

3) У чому різниця між іменованим томом і bind mount для бекапу?

Іменовані томи керуються Docker і живуть під директорією Docker. Bind mounts — це довільні шляхи хоста. З точки зору бекапу, bind mounts легше інтегрувати з традиційними інструментами хоста — поки хтось не змінить шлях і не забуде оновити зону бекапу.

4) Як бекапити томи з Docker Compose?

Compose — це лише оркестрація. Механіка бекапу та ж: використовуйте docker exec для логічних бекапів БД і docker run --rm з монтами томів для файлових бекапів. Важливе — послідовність імен, щоб скрипти знаходили правильні томи в кожному середовищі.

5) Можу я використовувати docker commit як бекап?

Ні для персистентних даних. Він не включає іменовані томи і може зберегти секрети в шарі образу. Іноді корисний для налагодження стану файлової системи контейнера, але не для відновлення після катастрофи.

6) Як часто тестувати відновлення?

Так часто, як може дозволити бізнес помилитись. Щотижня для основних баз даних — звична практика; щомісяця для менш критичних наборів. Також тестуйте після великих змін схеми, міграцій сховища або перебудов Docker-хостів.

7) Чи потрібне шифрування для бекапів томів?

Якщо бекапи містять дані клієнтів, облікові дані або приватний код — так. Шифруйте на диску і під час передачі, керуйте ключами окремо від сховища бекапів. «В приватному бакеті» — не контроль, а надія.

8) Як обходитись із різницею UID/GID між хостами?

Віддавайте перевагу стабільним числовим ID сервісних користувачів на різних хостах. Якщо це неможливо, включіть крок виправлення власності після відновлення. Перевіряйте запис від імені користувача середовища виконання, а не від root.

9) А як щодо інкрементальних бекапів для величезних томів?

Можливо, але це ускладнює процес. Для файлових томів підходять send/receive для знімків (ZFS/btrfs) або rsync-інкременти. Для БД — використовуйте WAL/binlogs або інструментарій вендора. Що б ви не обрали, тренування відновлення має включати відтворення з інкрементів — інакше ваша стратегія інкрементів теоретична.

10) Яке найнадійніше одне покращення, яке можна зробити?

Автоматизуйте перевірку відновлень у ізольованому середовищі. Це перетворює «ми думаємо» на «ми знаємо» і ловить тихі збої: порожні дампи, неправильні цілі та невідповідності прав.

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

  1. Зробіть інвентар даних: томи, bind mounts і «речі поза Docker» (секрети, конфіги).
  2. Оберіть правильні засоби бекапу: нативні для БД; холодні/снапшоти для файлових даних.
  3. Додайте цілісність: контрольні суми та маніфести, збережені з артефактами.
  4. Заплануйте тренування відновлення: відновіть у тимчасовий контейнер/стек і виконайте реальні перевірки.
  5. Виміряйте RTO: заміряйте повний час відновлення і вирішіть, чи вас влаштовує результат.

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

← Попередня
Fail2ban для пошти: правила, що насправді ловлять атаки
Наступна →
Docker: Правила маршрутів Traefik, що мовчки не працюють — як правильно виправити мітки

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