Відкат Docker Compose: Найшвидший шлях назад після невдалого деплою

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

Ви випустили зміну в Compose, healthchecks почали сигналізувати, і ваш on-call мозок тепер робить ту саму річ, коли не може згадати, чи docker compose down видаляє томи за замовчуванням (ні—якщо ви не наказали).
Тим часом бізнес просить таймлайн, дашборд червоний, а нотатки до деплою кажуть «малий рефакторинг».

Це керівництво з відкату, яке вам потрібне, коли ви втомлені, нетерплячі й намагаєтеся стабілізувати продакшен, поки Slack не вирішить, що ви автоматично стали інцидент-лідером.

Принцип: відкат — це зміна, а не молитва

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

Відкат у Compose зводиться до трьох речей:

  1. Контейнеры: запустити попередній відомо-робочий образ знову.
  2. Конфіг: відновити попередній Compose-файл (та будь-які env-файли), який сформував той відомо-робочий стан.
  3. Стан: вирішити, що робити з томами та змінами схеми/даних.

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

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

Одна цитата, яку варто тримати в голові під час інцидентів, походить від John Allspaw (ідея переказана): Надійність — це про те, як системи поводяться під навантаженням, а не про те, як вони виглядають на діаграмах.
Відкати — це тест навантаження, який ви не планували.

Цікаві факти та контекст (чому відкати в Compose дивні)

  • Compose починався як “Fig” (2013): його створили, щоб зробити багатоконтейнерні дев-оточення зручними, а не для ведення історії продакшен-деплоїв.
  • “docker-compose” (Python) проти “docker compose” (плагін CLI): сучасний плагін краще інтегрується з Docker contexts і саме туди спочатку потрапляють нові фічі.
  • У Compose немає вбудованого «undo»: на відміну від деяких оркестраторів, немає нативного контролера відкату. Ваша «історія» — це ваш Git-репозиторій і реєстр образів.
  • Теги образів не є незмінними, якщо ви їх не зафіксуєте: теги типу latest можна перемістити, перезаписати або повторно запушити. Digests не брешуть.
  • ID контейнерів — тимчасові; томи — ні: за замовчуванням Compose зберігає іменовані томи, що добре для персистентності і страшно для сюрпризів під час відкату.
  • Compose використовує іменування проекту для обсягу: ім’я проекту контролює імена контейнерів/мереж/томів. Зміна імені проекту може «полишити» попередній стек напризволяще.
  • Healthchecks з’явились порівняно недавно щодо «працює на моєму лаптопі»: багато стеків все ще «стартують», навіть якщо фактично не працюють. Healthchecks пришвидшують відкати, бо дають сигнал стоп/йти.
  • Поведінка pull з реєстру залежить від локальних даних: якщо старий образ вже є на диску, відкат може бути миттєвим; якщо потрібно тягнути з повільного реєстру — ось ваше пляшкове горлечко.

Висновок: Compose цілком може працювати в продакшені, але він не тримає вас за руку під час безпечного відкату.
М’язи потрібно накачати самим.

Найшвидші шляхи відкату (виберіть один)

Шлях A: повернути попередній коміт Git і перебудувати

Це найчистіше, коли збій спричинений конфігом Compose, env-перемінними, entrypoint-ами, лімітами ресурсів
або поганим тегом образу, який ввели в Compose-файлі.

Це також найкраще для аудиту: ваш відкат — це коміт, а не серія панічних команд у терміналі.
Якщо ви деплоїте з репозиторію на хості, відкат зазвичай — git checkout і docker compose up -d.

Шлях B: зафіксувати відомо-робочий digest образу і перебудувати

Якщо Compose-файл «в порядку», але тег образу вказує на погану збірку, не витрачайте час на переговори з тегами.
Зафіксуйте точний digest останнього відомо-робочого образу. Digests — найнадійніша істина о 3-й ранку.

Шлях C: зупинити кровотечу — запустити попередній образ вручну

Це шлях «break glass». Він швидший, ніж правити Compose-файл, коли ваша тулчейн для деплою зламана,
або коли сервіс має бути повернений у роботу просто зараз, а ви прибираєте наслідки пізніше.

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

Шлях D: відновлення даних/стану (тільки якщо потрібно)

Це для випадків, коли поганий деплой змінив стан так, що його терпіти не можна:
деструктивні міграції, некоректні фон-джоби або обробник черги, який «допоміг» обробити повідомлення в ніщо.

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

Жарт №1: плани відкату як вогнегасники — усі люблять їх мати, але ніхто не хоче виявити, що вони декоративні.

Швидкий план діагностики (знайти пляшкове горлечко за хвилини)

Мета — не перетворитися на криміналіста. Мета — відповісти: «Чи відкат — правильний крок, і що його блокує?»
Дійте зверху вниз і зупиняйтесь, коли маєте впевнений план.

По-перше: проблема в додатку, контейнері чи хості?

  • Перевірте health контейнерів і цикли рестарту: якщо контейнери перезапускаються — ви в crash-loop. Відкат часто правильний.
  • Перевірте логи на очевидні помилки: відсутні env-перемінні, помилки міграцій, connection refused, синтакс помилки конфігу.
  • Перевірте ресурси хоста: диск заповнений, OOM kills, вичерпані inodes. Відкат не вирішить повний диск.

По-друге: чи можна швидко повернути старі артефакти?

  • Чи старий образ ще локальний? Якщо так — відкат за хвилини. Якщо ні — потрібні pulls і доступ до реєстру.
  • Чи знаєте ви останній робочий digest? Якщо так — зафіксуйте його. Якщо ні — готуйтеся до археології в логах CI або метаданих реєстру.
  • Чи торкався деплой стану? Якщо так — оцініть сумісність назад і чи потрібен відкат даних або виправлення вперед.

По-третє: оберіть стратегію відкату

  • Регрес конфігу: поверніть коміт Compose.
  • Погана збірка образу: зафіксуйте digest.
  • Проблема хоста/ресурсів: виправте хост спочатку, потім вирішіть, чи потрібен відкат.
  • Мутація стану: координуйте: лише відкат додатку може не спрацювати.

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

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

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

Завдання 1: Підтвердити, що Compose вважає проект

cr0x@server:~$ docker compose ls
NAME            STATUS              CONFIG FILES
payments        running(6)          /srv/payments/compose.yaml

Що це значить: У вас є Compose-проєкт з іменем payments і Docker бачить його як запущений.
Якщо ваше ім’я проєкту змінилося нещодавно, ви можете дивитися не той стек.

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

Завдання 2: Визначити, які сервіси рестартують або нездорові

cr0x@server:~$ docker compose -p payments ps
NAME                 IMAGE                              COMMAND                  SERVICE   STATUS                    PORTS
payments-api-1       registry.local/payments:1.9.2      "/entrypoint.sh"         api       Restarting (1) 8s ago
payments-web-1       registry.local/payments-web:1.9.2  "nginx -g 'daemon off;'" web       Up 3 minutes (healthy)     0.0.0.0:443->443/tcp
payments-db-1        postgres:15.6                      "docker-entrypoint.s…"   db        Up 2 days (healthy)        5432/tcp

Що це значить: API в crash-loop. Web і DB виглядають здоровими. Це кандидат на відкат образу додатку.

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

Завдання 3: Забрати останні 200 рядків логів від падаючого сервісу

cr0x@server:~$ docker compose -p payments logs --no-color --tail=200 api
api-1  | ERROR: missing required env var PAYMENTS_SIGNING_KEY
api-1  | FATAL: cannot start

Що це значить: Це регрес конфігу/env, а не баг коду. Хтось додав обов’язкову env-перемінну.

Рішення: Відкотити конфіг Compose (env-файл або compose.yaml) або відновити відсутній секрет. Відкат образу може не допомогти, якщо образ теж очікує цю змінну.

Завдання 4: Перевірити, який конфіг Compose фактично використовує (зрізаний)

cr0x@server:~$ docker compose -p payments config
services:
  api:
    environment:
      PAYMENTS_SIGNING_KEY: ""
    image: registry.local/payments:1.9.2
    restart: always

Що це значить: Compose розв’язав env-перемінну як порожній рядок. Ось чому додаток відмовляється стартувати.

Рішення: Виправити інжекцію env (env_file, exported env, secrets) або відкотити зміну. Не «хотфіксьте» шляхом правки контейнерів — це не переживе рестарту.

Завдання 5: Перевірити, чи попередній образ ще локальний (потенційно швидкий відкат)

cr0x@server:~$ docker images --digests | grep registry.local/payments | head
registry.local/payments   1.9.2   sha256:aa11...   3d2f1c9d0b2a   45 minutes ago   312MB
registry.local/payments   1.9.1   sha256:bb22...   9a8e7d6c5b4f   2 days ago       311MB

Що це значить: Старий образ (1.9.1) уже на диску. Це ваш золотий шлях: відкат без очікування pull-ів.

Рішення: Віддайте перевагу негайному переходу на 1.9.1 (або його digest), а потім робіть глибшу діагностику після стабілізації.

Завдання 6: Зафіксувати сервіс на відомому працюючому тегу і перезапустити тільки цей сервіс

cr0x@server:~$ sed -i 's|registry.local/payments:1.9.2|registry.local/payments:1.9.1|g' /srv/payments/compose.yaml
cr0x@server:~$ docker compose -p payments up -d --no-deps api
[+] Running 1/1
 ✔ Container payments-api-1  Started

Що це значить: Була пересоздана лише API-контейнер. Залежності залишилися недоторканими.

Рішення: Використовуйте --no-deps під час відкату, коли залежності здорові. Не чіпайте робочі контейнери під час інциденту, якщо вам не подобається множити змінні.

Завдання 7: Підтвердити, що відкат «спрацював» (образ і здоров’я)

cr0x@server:~$ docker compose -p payments ps api
NAME             IMAGE                            COMMAND            SERVICE   STATUS              PORTS
payments-api-1   registry.local/payments:1.9.1    "/entrypoint.sh"   api       Up 20 seconds

Що це значить: Контейнер працює на 1.9.1. Якщо у вас є healthchecks — зачекайте на healthy.

Рішення: Якщо він тримається і обслуговує трафік — ви купили час. Якщо все ще падає — проблема може бути в конфігу/стані, а не в коді.

Завдання 8: Зафіксувати по digest (стрімкіше за теги)

cr0x@server:~$ docker inspect --format '{{index .RepoDigests 0}}' registry.local/payments:1.9.1
registry.local/payments@sha256:bb22cc33dd44ee55ff66...

Що це значить: У вас є незмінний digest для відомо-робочої збірки.

Рішення: Оновіть Compose, щоб використовувати digest, коли потрібна впевненість, особливо якщо теги можуть бути перезаписані.

Завдання 9: Переключити Compose на digest і перебудувати (запобігає дрейфу тегів)

cr0x@server:~$ sed -i 's|registry.local/payments:1.9.1|registry.local/payments@sha256:bb22cc33dd44ee55ff66...|g' /srv/payments/compose.yaml
cr0x@server:~$ docker compose -p payments up -d --no-deps api
[+] Running 1/1
 ✔ Container payments-api-1  Recreated

Що це значить: Ви перебудували сервіс, вказавши образ за content-addressed посиланням.

Рішення: Для пост-інцидентної стабілізації зафіксуйте digests для критичних сервісів. Після впровадження контролів можна повернутися до зручних semver-тегів.

Завдання 10: Виявити, чи відкат блокує тиск на диск хоста

cr0x@server:~$ df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2  118G  117G  0G   100% /

Що це значить: Коренева файловa система заповнена. Pull-инг образів, запис логів і старт контейнерів може падати хаотично.

Рішення: Звільніть місце на диску перед будь-якими діями. Відкати також потребують дискового простору — особливо якщо треба підтягти старі образи.

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

cr0x@server:~$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          42        18        28.6GB    11.2GB (39%)
Containers      35        9         1.3GB     1.1GB (84%)
Local Volumes   14        10        96.4GB    0B (0%)
Build Cache     0         0         0B        0B
cr0x@server:~$ docker container prune -f
Deleted Containers:
c8a1...
Total reclaimed space: 1.1GB

Що це значить: Контейнери займали місце; томи — велика частка, але їх не очистити цією командою.

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

Завдання 12: Визначити, чи випадково створили осиротілі сервіси

cr0x@server:~$ docker compose -p payments up -d
[+] Running 6/6
 ✔ Container payments-web-1  Running
 ✔ Container payments-api-1  Running
 ✔ Container payments-db-1   Running
 ! Found orphan containers ([payments-worker-1]) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up.

Що це значить: Хтось перейменував/видалив сервіс, а старий контейнер досі існує.

Рішення: Під час відкату не видаляйте орфанів сліпо. Цей орфан може виконувати важливу роботу (наприклад, опорожнення черги). Оцініть, потім прибирайте свідомо.

Завдання 13: Підтвердити причину виходу контейнера (OOM чи збій додатку)

cr0x@server:~$ docker inspect -f '{{.State.Status}} {{.State.ExitCode}} OOMKilled={{.State.OOMKilled}}' payments-api-1
exited 137 OOMKilled=true

Що це значить: Exit code 137 з OOMKilled вказує, що ядро вбило ваш контейнер. Це не регрес коду, поки не доведено протилежне.

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

Завдання 14: Перевірити тиск пам’яті на хості та недавні OOM-логи

cr0x@server:~$ free -m
               total        used        free      shared  buff/cache   available
Mem:           15984       15410         120         210         454         260
Swap:           2047        2047           0
cr0x@server:~$ journalctl -k --since "30 min ago" | tail -n 5
kernel: Out of memory: Killed process 28411 (payments-api) total-vm:2456120kB, anon-rss:1220040kB, file-rss:0kB, shmem-rss:0kB

Що це значить: Хост витратив пам’ять і своп заповнений. Будь-яка версія може померти; сам відкат — це пластир.

Рішення: Зменшіть навантаження, зменшіть інші сервіси, додайте пам’ять або встановіть розумні ліміти. Потім перебудуйте. Інакше ви «відкотитесь» в ту саму OOM-пастку.

Завдання 15: Перевірити, що змінилося між двома ревізіями Compose (diff конфігу)

cr0x@server:~$ cd /srv/payments
cr0x@server:~$ git log --oneline -5
a12b9f3 bump api to 1.9.2 and add signing key
6c0dd21 pin postgres and tune healthcheck
b19e8aa add worker concurrency env var
cr0x@server:~$ git show a12b9f3 -- compose.yaml | sed -n '1,120p'
diff --git a/compose.yaml b/compose.yaml
index 31e2c0a..8f19c11 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -12,6 +12,7 @@ services:
   api:
     image: registry.local/payments:1.9.2
     environment:
+      - PAYMENTS_SIGNING_KEY=${PAYMENTS_SIGNING_KEY}

Що це значить: Нова версія додала обов’язковий секрет. Інжекція env у вас не спрацювала.

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

Завдання 16: Відкотитися через Git до останнього відомо-робочого конфігу

cr0x@server:~$ cd /srv/payments
cr0x@server:~$ git checkout 6c0dd21 -- compose.yaml .env
cr0x@server:~$ docker compose -p payments up -d
[+] Running 3/3
 ✔ Container payments-api-1  Recreated
 ✔ Container payments-web-1  Running
 ✔ Container payments-db-1   Running

Що це значить: Ви відкотили конфіг і застосували його. API-контейнер був пересозданий відповідно.

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

Завдання 17: Швидко перевірити доступність зовні (перевірка на рівні сервісу)

cr0x@server:~$ curl -fsS -o /dev/null -w "%{http_code}\n" https://localhost/health
200

Що це значить: Локальна ендпоінт повертає 200. Це не повний інтеграційний тест, але швидкий сигнал.

Рішення: Якщо локальний health OK, але користувачі все ще бачать помилки — проблема може бути в DNS, балансувальнику, фаєрволі або upstream-залежностях.

Завдання 18: Зняти снапшот іменованого тому перед тим, як його чіпати (дешеве страхування)

cr0x@server:~$ docker volume ls | grep payments
local     payments_dbdata
cr0x@server:~$ mkdir -p /var/backups/payments
cr0x@server:~$ docker run --rm -v payments_dbdata:/data -v /var/backups/payments:/backup alpine sh -c "cd /data && tar -czf /backup/payments_dbdata_$(date +%F_%H%M).tgz ."

Що це значить: Ви створили tar-архів вмісту тому. Це не ідеально для живих баз даних, але краще, ніж «ми нічого не робили».

Рішення: Для реальних БД віддавайте перевагу нативним інструментам бекапу. Але коли ви збираєтесь робити ризикову операцію — зробіть снапшот або бекап спочатку.

Жарт №2: «Просто відкоти» — прекрасна фраза, поки ви не зрозумієте, що база даних не отримала меморандум.

Три міні-історії з корпоративного життя

1) Інцидент через хибне припущення

Середня компанія тримала payments API на одному потужному хості з Docker Compose.
Процес деплою був «підтягнути нові образи, виконати docker compose up -d».
У них були моніторинг, алерти і черговий на зміни. Вони також мали одне припущення: теги достатньо незмінні.

Розробник запушив payments-api:1.8.4 в реєстр, помітив, що прапорець збірки невірний, і перезапушив той самий тег з виправленим образом.
Жодної зловмисності; проста звичка з дев-оточень. Реєстр дозволив це. Ніхто не помітив.
Через два дні стався інцидент після звичайного рестарту ноди. Нода піднялася, Compose стартував контейнери,
і він підтягнув 1.8.4 — але не той образ, який усі запускали. Той самий тег, інші біти.

Черговий зробив те, що вказував ранбуку: відкотив до 1.8.4. Відкат «спрацював» в сенсі, що нічого не змінилося.
Вони витратили годину на пошук фантомних конфіг-різниць і підозрювали апдейти ядра.
Насправді проблема була простіша: їх ціль відкату не була ціллю. Це був рухливий ярлик.

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

2) Оптимізація, що обернулась проти

Команда платформи даних хотіла швидших деплоїв. Їх Compose-стек мав півдюжини сервісів і спільну мережу.
Тягнути образи під час деплою було повільно, тож вони оптимізували: агресивно зберігали образи локально і уникали pull-ів, якщо можна.
Вони також поставили restart=always усюди, бо ж доступність, правда?

Погана реліз привіз тонкий витік пам’яті у воркер-сервісі. Спочатку все виглядало нормально.
За кілька годин пам’ять росла, ядро почало агресивно звільняти, і зрештою OOM вбив контейнери.
З restart=always воркери миттєво перезапускалися, завантажувалися, знову текли пам’ять і тряслися.
Тим часом хост так втратив пам’ять, що «хороші» сервіси теж почали падати.

Черговий намагався відкотитися. Але хост був під таким тиском, що навіть старт старого образу був ненадійний.
Логи обрізались. Exec у контейнери таймаутив. «Відкат» перетворився на гонитву між ядром і оператором.
Саме стратегія рестарту, яка допомагала при транзитивних проблемах, перетворила контрольований інцидент на шумний.

Остаточне поліпшення: вони задали ліміти пам’яті й резервували запас, налаштували політики рестарту по сервісах,
і додали canary-перевірку під час деплою для росту використання пам’яті під навантаженням. Оптимізація — уникати pull-ів — не був справжнім лиходієм.
Відсутність запобіжників була.

3) Нудна, але правильна практика, що врятувала

Команда внутрішніх інструментів тримала Compose на парі хостів, кожен з «папкою проекту».
Їх практика була болісно нудною: кожен деплой — це Git-коміт, кожен коміт фіксував digests образів у Compose-файлі,
і кожен реліз мав нотатку «last known good» в репозиторії. Вони також робили нічні бекапи томів і тестували відновлення щомісяця.
Так, справді.

Реліз привіз міграцію схеми, яка мала бути додатковою.
Але не була. Вона перейменувала колонку, якою користувався старіший кодовий шлях, і зламала job звітності.
Продакшен API був в основному цілим, але job заспамив помилки і створив додаткове навантаження.

Реакція на інцидент була майже нудною. Вони відк Otилися до попереднього Git-коміту тільки для job-сервісу,
перебудували з --no-deps і підтвердили, що система звітності відновилась.
Потім запланували forward-fix міграцію з правильною зворотною сумісністю.
Без героїзму, без war room, який перетворився на семінар з філософії.

Ключова деталь: оскільки вони фіксували digests, їм не довелося вгадувати, що означає «попередня версія».
Оскільки вони практикували відновлення, вони знали, що станеться, якщо потрібен відкат даних.
Вони не відновлювали дані. Не було потреби. Але опція існувала, і це змінює ставлення людей.

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

1) «Відкат деплою пройшов, але нічого не змінилось»

Симптом: Ви змінюєте тег назад, запускаєте docker compose up -d, але сервіс все ще зламаний.

Корінна причина: «Старий» тег вказує на нові біти (тег перезаписано), або Compose не пересоздав контейнер, бо не виявив зміни.

Фікс: Зафіксуйте по digest і примусово пересоздайте, якщо потрібно.

2) «Контейнер стартує, а потім миттєво виходить»

Симптом: Crash-loop; логи показують відсутні env-перемінні або помилки парсингу конфігу.

Корінна причина: Дрифт конфігу, відсутня інжекція секрету або тепер додаток вимагає змінну оточення.

Фікс: Використайте docker compose config, щоб побачити розв’язані значення. Відновіть попередні env/compose-коміти або правильно надайте відсутній секрет.

3) «Відкат зробив ще гірше; тепер кілька сервісів впали»

Симптом: Ви відкотили одну зміну і раптом DB, проксі та воркери перезапускаються.

Корінна причина: Ви виконали повний docker compose down (або видалили мережі) і пересоздали все, спричинивши churn залежностей або зміну IP у крихкому стеку.

Фікс: Віддавайте перевагу docker compose up -d --no-deps <service> і уникайте демонтажу мереж під час інциденту.

4) «Стара версія більше не може говорити з базою даних»

Симптом: Після відкату додаток повідомляє про відсутні колонки/таблиці або несумісну схему.

Корінна причина: Погані deploy-міграції застосували несумісні зміни схеми.

Фікс: Або деплойте вперед виправлення, яке підтримує нову схему, або виконуйте скоординований відкат даних (бекап/відновлення), якщо це можливо.

5) «Відкат повільний, бо pull-и займають вічність»

Симптом: docker compose up зависає на подтягуванні образів; час відновлення розтягується.

Корінна причина: Старі образи не кешовані локально, смуга реєстру обмежена або проблеми DNS/мережі.

Фікс: Тримайте останні відомо-робочі образи локально (або попередньо тягніть під час деплою), перевіряйте доступність реєстру і розгляньте внутрішнє mirror/cache.

6) «Ми відкочувались, але користувачі все ще бачать помилки»

Симптом: Healthchecks проходять, але реальний трафік періодично падає.

Корінна причина: Змінилися зовнішні залежності (налаштування LB, TLS сертифікат, DNS), або частковий відкат залишив мікс версій у системі.

Фікс: Підтвердіть end-to-end поведінку через curl зовні, перевірте маршрути реверс-проксі та переконайтеся, що всі залежні сервіси сумісні версіями.

7) «Все перезапускається; логи порожні»

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

Корінна причина: OOM kills, диск заповнений або збій перед flush-ом stdout.

Фікс: Перевірте docker inspect на OOMKilled, дивіться journalctl -k і вирішуйте тиск хоста спочатку.

8) «Відкат видалив дані»

Симптом: БД запускається порожньою після «відкату».

Корінна причина: Хтось виконав docker compose down -v або видалив іменовані томи, або змінив імена томів через зміну імені проєкту.

Фікс: Зупиніться. Визначте томи через docker volume ls. Відновіть з бекапів. Запобігайте, зафіксувавши ранбуки і використовуючи явні імена томів.

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

Чекліст: 10-хвилинний відкат (коли стан сумісний)

  1. Заморозити деплої: зупиніть CI/CD від пушу нових змін під час тріажу.
  2. Визначити падаючі сервіси: docker compose ps. Оберіть найменший радіус ураження першим.
  3. Читати логи: docker compose logs --tail=200 <service>. Вирішіть: конфіг vs код vs хост.
  4. Підтвердити здоров’я хоста: диск (df -h), пам’ять (free -m), OOM-логи ядра.
  5. Знайти останній відомо-робочий образ: локальний кеш (docker images), запис CI або метадані реєстру.
  6. Відкотити один сервіс: змінити тег/digest, потім docker compose up -d --no-deps <service>.
  7. Перевірити здоров’я: docker compose ps, потім реальний запит (curl).
  8. Оголосити статус: «Сервіс стабільний на X версії; розслідування кореневої причини триває.»
  9. Зібрати докази: логи, digests, commit ID. Майбутньому вам потрібні квитанції.
  10. Планувати фікс: не живіть в відкаті вічно; заплануйте правильний ремонт.

Чекліст: відкат, коли міграції могли змінити стан

  1. Визначте, що виконувалось: перевірте логи додатка на повідомлення про міграції; перевірте таблицю міграцій у БД, якщо є.
  2. Оцініть сумісність: чи зможе стара версія працювати з новою схемою? Якщо ні — відкат додатку не відновить сервіс.
  3. Обрати стратегію:
    • Віддайте перевагу forward fix, якщо можете швидко випустити сумісний додаток.
    • Відновлення даних, якщо деплой пошкодив або видалив дані, або forward fix занадто повільний.
  4. Захистіть поточний стан: зробіть снапшот/бекап томів перед тим, як щось чіпати.
  5. Координуйте час простою: відновлення app+DB — це не соло-операція в навантаженій компанії.
  6. Перевірте відновлення: версія схеми, перевірки кількості рядків і функціональні тести додатка.

Чекліст: запобігти «театру відкату» в Compose (виглядає як відкат, але не є ним)

  1. Перестаньте використовувати плаваючі теги (latest) у продакшен Compose-файлах.
  2. Записуйте і деплойте за digest для критичних сервісів.
  3. Тримайте «останній відомо-робочий» в Git (compose + env + будь-який конфіг, змонтований у контейнери).
  4. Переконайтеся, що healthchecks існують і відображають реальну готовність, а не лише «процес існує».
  5. Практикуйте відкат щокварталу на staging-хості, що нагадує продакшен.
  6. Резервуйте томи методом, відповідним до даних (перевага — нативним інструментам БД).

FAQ

1) Чи має Docker Compose вбудовану команду відкату?

Ні. Compose застосовує те, що ви оголосили. Ваш відкат — «задекларувати попередній стан» і застосувати його знову,
зазвичай через revert у Git і/або фіксацію старішого digest образу.

2) Який найшвидший безпечний відкат, якщо зламаний лише один сервіс?

Відкотіть тільки цей сервіс: оновіть його посилання на образ і запустіть docker compose up -d --no-deps <service>.
Не чіпайте здорові залежності.

3) Чи слід використовувати docker compose down під час відкату?

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

4) Чи відкат видалить мою базу даних?

За замовчуванням — ні. Іменовані томи зберігаються через up/down. Втрата даних зазвичай відбувається, коли хтось запускає
docker compose down -v або видаляє іменовані томи, або змінює імена томів через зміну імені проєкту.

5) Теги проти digest: що слід використовувати в продакшені?

Для продакшен-відкатів digests кращі, бо це незмінні ідентифікатори вмісту образу.
Теги зручні для людей, але не трактуйте їх як доказ.

6) Як знайти останній відомо-робочий digest?

Найкращий варіант: ваш pipeline деплою його записує. Наступний: він вже в вашому Compose-файлі з останнього релізу.
Інакше інспектуйте локально кешовані образи або консультуйтесь з метаданими реєстру і корелюйте з реліз-нотатками.

7) Що робити, якщо поганий деплой виконав несумісні міграції?

Відкат додатку може не допомогти. Або випускаєте forward-fix, який підтримує нову схему,
або координуєте відновлення даних. Саме тому міграції типу «expand/contract» і зворотна сумісність не опційні в продакшені.

8) Чому Compose іноді не пересоздає контейнер після зміни?

Якщо Compose не виявив суттєвої зміни (або ви правили не той файл/шлях), він може зберегти існуючий контейнер.
Перевірте через docker compose config і docker compose ps. За потреби примусово пересоздайте потрібний сервіс.

9) Який найкращий спосіб протестувати відкат, не впливаючи на продакшен-трафік?

Запустіть старий образ як паралельний сервіс на іншому порту або з іншим ім’ям проєкту, перевірте його на staging-залежностях,
потім переключіть трафік на проксі. Compose не дає зсуву трафіку за замовчуванням; це треба реалізувати (наприклад, в Nginx/Traefik/HAProxy).

10) Скільки старих образів тримати локально, щоб відкат був швидким?

Тримайте принаймні останній відомо-робочий і ще один попередній реліз для кожного критичного сервісу, якщо дозволяє диск.
Правильна кількість залежить від розміру образів і бюджету диска, але «нуль» — явно неправильний вибір.

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

Відкат у Compose не має бути драматичним. Він має бути детерміністичним. Якщо ви візьмете одне з цього:
перестаньте довіряти тегам в екстрених ситуаціях і припиніть ставитися до змін стану як до побічного ефекту.

  1. Запишіть «останній відомо-робочий» як digest і Git-коміт для кожного релізу.
  2. Додайте healthchecks, що відображають готовність, а не лише «процес існує».
  3. Попрактикуйте відкат одного сервісу з --no-deps на непроDUCTION-хості.
  4. Визначте політику для стану: які сервіси можуть виконувати деструктивні міграції і як їх відміняти.
  5. Забюджетьте місце на диску та пам’ять, щоб відкат не блокувався тим, що хост «горить».

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

← Попередня
Proxmox Ceph: OSD недоступний — диск, мережа чи сервіс — швидка ідентифікація
Наступна →
Чи отримають споживчі ПК уніфіковані дизайни GPU+NPU?

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