Найгірші інциденти з Docker на перший погляд не виглядають драматично. Контейнер «працює», доки перестає працювати, і коли він ламається,
це часто відбувається збоку: зростає затримка, кількість повторних спроб підскакує, диски заповнюються, вузли починають свопити, і потім усе ніби «не пов’язано» — поки не виявиться, що пов’язано.
Вирішення — не «додайте ще дашбордів». Вирішення — безжальний мінімум: невеликий набір метрик і логів, які надійно спрацьовують раніше за скарги користувачів,
і спосіб опитати хост за кілька хвилин, коли дашборди брешуть або відсутні.
Мінімальний сигнал: що спостерігати і чому
«Спостережність» у світі контейнерів продають як шведський стіл: трейсинги, профайли, eBPF, мапи сервісів, модні flame graph’и,
AI для аномалій і дашборд у дванадцяти відтінках червоного. У продакшені мінімальна життєздатна спостережність простіше:
виявляти передбачувані режими відмов раніше, зі сигналами, які важко підробити і дешево збирати.
Для Docker на одному хості або у флоті ви маєте справу з трьома шарами, що ламаються по-різному:
- Рівень застосунку: помилки запитів, затримки, повторні спроби, таймаути, глибина черги. Тут кричать користувачі.
- Рівень контейнера: рестарти, OOM kill’и, CPU throttling, вичерпання дескрипторів файлів, обсяг логів.
- Рівень хоста: заповненість диска/inode, насичення IO, тиск пам’яті/своп, вичерпання conntrack, проблеми ядра.
Мінімальний набір має давати раннє попередження про передбачуване: заповнення диска, витік пам’яті, CPU throttling, цикли падіння,
та тихі вбивці типу вичерпання inode і conntrack. Якщо це закрити — ви запобігнете більшості відмов «вчора все було добре».
Правила мінімуму
- Віддавайте перевагу провісникам. «Диск 98% заповнений» — це відсталий індикатор. «Диск росте на 2% за годину» — це провісник.
- Вибирайте одне джерело істини для кожної речі. Для CPU використовуйте використання cgroup і показники throttling. Не змішуйте п’ять визначень «CPU%».
- Тримайте кардинальність під контролем. Позначення кожної метрики ідентифікатором контейнера призведе до успішного DoS вашого моніторингу.
- Оповіщуйте про симптоми, на які можна діяти. «Високе середнє навантаження» — це нісенітниця, якщо воно не пов’язане з CPU steal, IO wait або throttling.
Існують два типи команд: ті, що оповіщають про «контейнер перезапустився», і ті, що люблять дивуватися цим о 2:00.
Також суха правда: якщо ваш стек спостережності падає щоразу, коли кластер хворіє, це не спостережність; це декорація.
Ваш мінімум має бути доступним з хоста через shell.
Цікаві факти та історичний контекст (частини, що ще болять)
- cgroups були додані в Linux у 2007 році. Docker не винайшов ізоляцію ресурсів; він її упакував і зробив зручною для неправильного використання.
- Початковий драйвер логування Docker мав за замовчуванням json-file. Зручно, але на навантажених сервісах це може стати прихованим пожирачем диска.
- OverlayFS (overlay2) став стандартним драйвером зберігання в багатьох дистро в епоху 2016–2017 років. Досить швидкий, але питання «куди подівся мій диск?» стало щотижневим.
- Поведінка OOM killer’а існувала задовго до контейнерів. Контейнери просто спростили досягнення меж пам’яті і ускладнили бачити причину без правильних сигналів.
- Conntrack — це функція netfilter у Linux з ранніх ядер 2.4. Коли NAT і багато короткоживучих з’єднань зустрічаються з контейнерами, conntrack стає елементом планування потужності, а не приміткою.
- Healthcheck у Dockerfile додали після болючого досвіду в продакшені. До того «контейнер працює» вважали «сервіс здоровий», і це мило.
- systemd-journald має обмеження частоти з причиною. Логування — це IO; IO — це затримка; затримка породжує повторні спроби; повтори стають бурею. Логи можуть вас знищити.
- CPU throttling — це не «високий CPU». Це «ви попросили CPU, а ядро сказало «ні»». Ця різниця стала помітнішою з масовим поширенням контейнеризації.
Мінімальний набір метрик (за режимом відмови)
Не починайте з «збирайте все». Почніть з того, як насправді помирають Docker-системи. Нижче — мінімум, який рано ловить відмови
і дає напрям для дебагу. Це передбачає, що ви можете збирати метрики хоста (node exporter або еквівалент), метрики контейнерів
(cAdvisor або Docker API) і базові метрики застосунку (HTTP/gRPC статистика). Навіть якщо ви не використовуєте Prometheus, концепції лишаються.
1) Цикли падіння і невдалий деплой
- Швидкість лічильника перезапусків контейнера (за сервісом, не за ID контейнера). Оповіщуйте про стійкі рестарти протягом 5–10 хвилин.
- Розподіл кодів виходу. Exit 137 зазвичай OOM kill; exit 1 — зазвичай збій застосунку; exit 143 — SIGTERM (часто нормальний під час деплою).
- Збої healthcheck (якщо ви використовуєте HEALTHCHECK). Оповіщайте до рестартів, бо падіння хелсчеку — це ваш провісник.
Рішення, яке ви хочете приймати: відкат або зупинка кровотечі. Якщо швидкість рестартів виросла після деплою, не «чекайте, поки само пройде».
Воно не пройде. Контейнери — не кімнатні рослини.
2) Витоки пам’яті, тиск пам’яті та OOM kill’и
- Working set пам’яті контейнера (не лише RSS, і не cache, якщо ви не розумієте, що робите).
- Події OOM kill на рівні хоста і контейнера.
- Доступна пам’ять хоста та swap in/out. Активність свопу — це повільний катастрофічний фільм.
Пороги оповіщення: working set наближається до ліміту (наприклад, > 85% протягом 10 хв), OOM kills > 0 (негайне оповіщення), стабільний темп swap-in > 0 (попередження).
3) Диск: найпоширеніший генератор нудних відмов
- Відсоток вільного місця файлової системи хоста для кореня Docker (часто
/var/lib/docker) і для файлових систем логів. - Записи на диск байт/сек і завантаженість IO (await, svctm-подібні сигнали залежно від інструменту).
- Відсоток вільних inode. Ви можете мати «50% вільного місця» і все одно померти через відсутність inode.
- Зростання образів Docker + writable layer контейнерів (якщо можна відслідкувати). Інакше — відстежуйте темп росту
/var/lib/docker.
Шаблон оповіщення, який працює: сповіщати про time-to-full (на основі темпу росту), а не про сирий відсоток.
«Диск буде повний за 6 год» викликає дію. «Диск 82%» ігнорують, поки не стане 99%.
4) CPU: високе використання проти throttling
- Використання CPU контейнером (ядра або секунди/сек).
- Час CPU, коли контейнер був обмежений (throttled) та кількість періодів throttled. Це метрика «нас позбавили CPU».
- iowait хоста. Якщо iowait росте разом із затримками, вузьке місце — диск, а не CPU.
Якщо використання CPU помірне, але throttling високий — ви задали занадто малі ліміти або перепакували занадто багато на вузол. Користувачі відчувають це як випадкові стрибки затримки. Інженери хибно діагностують це як «мережева нестабільність», бо графіки виглядають нормально.
5) Мережа: коли «це DNS» насправді conntrack
- TCP retransmits і помилки сокетів на хості.
- Використання таблиці conntrack (% використано).
- Рівень помилок DNS від вашого резольвера (SERVFAIL, таймаут). DNS часто є жертвою, а не причиною.
6) «Золоті сигнали» застосунку (ті, які варто підключати)
- Частота запитів, рівень помилок, затримки (p50/p95/p99), насичення (глибина черги, завантаженість воркерів).
- Рівень помилок залежностей (БД, кеш, upstream API). Інциденти Docker часто проявляються як шторм залежностей.
Мінімальні оповіщення, які не зроблять вас ненависним для pager’а
- Disk time-to-full < 12h (попередження), < 2h (сигналізувати)
- Inodes time-to-zero < 12h (попередження), < 2h (сигналізувати)
- Подія OOM kill (сигналізувати)
- Цикл перезапусків: рестарти/хв > базового рівня протягом 10хв (сигналізувати)
- Співвідношення throttled time CPU > 10% за 10хв (попередження), > 25% за 5хв (сигнал) для затратно-чутливих сервісів
- Стабільний swap-in на хості (попередження), різкий сплеск major page faults (попередження/сигнал залежно від рівня)
- Conntrack використання > 80% за 10хв (попередження), > 90% (сигнал)
- App p99 latency + рівень помилок одночасно погіршуються (сигнал). Одне без іншого зазвичай — шум.
Мінімальний набір логів (і як не потонути)
Метрики кажуть, що «щось не так». Логи кажуть, «який саме тип проблеми». Мінімальна стратегія логування — не «логувати все».
Це «логувати події, які пояснюють переходи станів і відмови, і зберігати їх достатньо довго для дебагу».
stdout/stderr контейнера: ставтеся до нього як до інтерфейсу продукту
У Docker stdout/stderr — найзручніший шлях логів і найпростіший для зловживання. Якщо ваш застосунок логує JSON — добре. Якщо він логірує stack trace’и
і повні тіла запитів — теж добре, поки не стане погано. Ваш мінімум:
- Структуровані логи (бажано JSON) з timestamp, level, request ID та полями помилки.
- Банер при старті, що включає версію, checksum конфігурації та порти прослуховування.
- Однолінійні підсумки для відмов запитів і відмов залежностей.
- Лімітування частоти шумних логів (таймаути, повторні спроби). Повторювані логи мають агрегуватися, а не спамити.
Логи демонa Docker і хоста: нудні речі, що все пояснюють
- dockerd логи: завантаження образів, помилки graphdriver, помилки exec, проблеми containerd.
- логи ядра: повідомлення OOM killer’а, помилки файлової системи, мережеві падіння.
- journald/syslog: рестарти сервісів, відмови юнітів, попередження про обмеження частоти.
Зберігання логів і ротація: оберіть політику, а не надію
Якщо ви зберігаєте json-file логи без ротації, ви врешті-решт заповните диск. Це не «можливо». Це фізика.
Жарт №1: Неротовані Docker логи — як шухляда з мотлохом — нормально, поки ви не спробуєте її зачинити і кухня не перестане працювати.
Мінімальна політика для json-file:
- Встановіть max-size і max-file глобально.
- Віддавайте перевагу зовнішній відправці логів (journald, fluentd або сайдкар/агент), якщо потрібне довше зберігання.
- Оповіщуйте про ріст логів так само, як оповіщаєте про ріст диска. Логи — це просто дискові записи з думками.
Практичні завдання: команди, виводи, рішення (12+)
Дашборди хороші, поки не перестають бути корисними. Коли хост горить, потрібен невеликий набір команд, що кажуть, що ламається:
CPU, пам’ять, диск, мережа, сам Docker або ваш застосунок. Нижче завдання, які можна виконати на будь-якому Docker-хості.
Кожне містить: команду, приклад виводу, що це означає та як рішення приймати.
Завдання 1: Підтвердити радіус ураження (що працює, що перезапускається)
cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
NAMES IMAGE STATUS PORTS
api-7c9d registry/app:1.8.2 Up 3 minutes (healthy) 0.0.0.0:8080->8080/tcp
worker-2a11 registry/worker:1.8.2 Restarting (1) 12 seconds ago
redis-01 redis:7 Up 14 days 6379/tcp
Що це означає: Один контейнер у циклі перезапуску. «Restarting (1)» — не настрій; це симптом.
Рішення: Негайно трасуйте причину рестарту (код виходу, логи, OOM). Не ганяйтесь за затримками десь іще, поки не розберетесь із циклом рестартів.
Завдання 2: Дізнатись, чому контейнер помер (код виходу + натяк на OOM)
cr0x@server:~$ docker inspect -f 'Name={{.Name}} ExitCode={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}} Error={{.State.Error}} FinishedAt={{.State.FinishedAt}}' worker-2a11
Name=/worker-2a11 ExitCode=137 OOMKilled=true Error= FinishedAt=2026-01-03T00:12:41.981234567Z
Що це означає: Exit 137 з OOMKilled=true — контейнер убито ядром/cgroup через перевищення ліміту пам’яті.
Рішення: Припиніть гадати про «випадкові краші». Збільшіть ліміт пам’яті (обережно), зменшіть конкурентність або виправте витік. Також перевірте тиск пам’яті на хості.
Завдання 3: Отримати останні логи, не потонувши
cr0x@server:~$ docker logs --tail=80 --timestamps worker-2a11
2026-01-03T00:12:35.112Z level=info msg="starting worker" version="1.8.2" concurrency=64
2026-01-03T00:12:39.003Z level=error msg="failed processing job" err="context deadline exceeded" job_id=91311
2026-01-03T00:12:41.978Z level=info msg="shutdown requested"
Що це означає: Логи показують старт і кілька таймаутів, але нічого про OOM (бо OOM поза процесом).
Рішення: Корелюйте з логами ядра для OOM. Не чекайте, поки застосунок зізнається.
Завдання 4: Підтвердити OOM kill у логах ядра
cr0x@server:~$ sudo dmesg -T | tail -n 30
[Sat Jan 3 00:12:41 2026] Memory cgroup out of memory: Killed process 28144 (worker) total-vm:2145932kB, anon-rss:612344kB, file-rss:1200kB, shmem-rss:0kB
[Sat Jan 3 00:12:41 2026] oom_reaper: reaped process 28144 (worker), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Що це означає: Ядро вбило процес через ліміт пам’яті cgroup. Це остаточно.
Рішення: Розглядайте як баг у конфігурації/ємності. Відрегулюйте ліміти або виправте поведінку пам’яті застосунку; додайте оповіщення про події OOMKill.
Завдання 5: Перевірити тиск пам’яті хоста (прелюдія «усі повільні»)
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 31Gi 28Gi 620Mi 512Mi 2.4Gi 1.1Gi
Swap: 4.0Gi 1.9Gi 2.1Gi
Що це означає: Доступна пам’ять низька і своп використовується. Якщо своп активно змінюється, затримки, ймовірно, зростають.
Рішення: Зніміть тиск пам’яті: перемістіть навантаження, додайте RAM хосту, обмежте кеші, налаштуйте ліміти. Розгляньте ризик свопінгу як інцидентний.
Завдання 6: Визначити найбільших споживачів пам’яті за контейнером
cr0x@server:~$ docker stats --no-stream --format 'table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.CPUPerc}}'
NAME MEMUSAGE / LIMIT MEMPERC CPUPERC
api-7c9d 420MiB / 1GiB 41.02% 85.31%
worker-2a11 980MiB / 1GiB 95.70% 12.01%
redis-01 210MiB / 2GiB 10.25% 1.10%
Що це означає: Worker підсідає на ліміт пам’яті. Він знову OOM.
Рішення: Зменшіть конкурентність worker’а, додайте backpressure або підвищте ліміт. Якщо підвищуєте ліміт — перевірте запас хоста.
Завдання 7: Перевірити заповненість диска там, де живе Docker
cr0x@server:~$ df -h /var/lib/docker
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 200G 189G 11G 95% /
Що це означає: Корінь Docker на 95%. Це «незабаром інцидент», а не «нормально».
Рішення: Почніть очищення (неактивні образи, зупинені контейнери, кеш збірки). Також знайдіть джерело росту (логи, overlay2, томи).
Завдання 8: Знайти вичерпання inode (підступна дискова відмова)
cr0x@server:~$ df -i /var/lib/docker
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 13107200 13010000 97200 100% /
Що це означає: Місця може бути, але inode майже немає. Створення файлів завершиться невдачею; контейнери можуть ламатися дивним чином.
Рішення: Знайдіть каталоги з величезною кількістю файлів (часто кеш збірки, розпаковані шари або крихітні лог-файли). Очистіть і розгляньте налаштування файлової системи.
Завдання 9: Виміряти, що їсть диск Docker (томи vs шари vs логи)
cr0x@server:~$ sudo du -xhd1 /var/lib/docker | sort -h
1.2G /var/lib/docker/containers
6.8G /var/lib/docker/volumes
29G /var/lib/docker/buildkit
150G /var/lib/docker/overlay2
189G /var/lib/docker
Що це означає: overlay2 великий, build cache значний, а каталогу containers (логи) тут відносно мало.
Рішення: Якщо overlay2 домінує — шукайте великі writable layers і спроби росту образів. Якщо buildkit великий — проніть його. Якщо containers великий — виправте ротацію логів.
Завдання 10: Безпечно проніть (і зрозуміти радіус впливу)
cr0x@server:~$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 47 9 68.2GB 52.4GB (76%)
Containers 21 12 3.4GB 1.1GB (32%)
Local Volumes 18 11 6.8GB 1.9GB (27%)
Build Cache 163 0 29.0GB 29.0GB
Що це означає: Є багато того, що можна звільнити, особливо кеш збірки і старі образи.
Рішення: Почніть із цільової очистки. Уникайте видалення томів, якщо ви не впевнені.
cr0x@server:~$ docker builder prune -f
Deleted build cache objects:
3yq9m3c2kz7qf2n2o6...
Total reclaimed space: 28.7GB
Що це означає: Кеш збірки успішно очищено.
Рішення: Перепровірте диск. Якщо ще високо — обережно проніть невикористовувані образи.
Завдання 11: Перевірити розмір лог-файлів по контейнерах (драйвер json-file)
cr0x@server:~$ sudo ls -lh /var/lib/docker/containers/*/*-json.log | sort -k5 -h | tail -n 5
-rw-r----- 1 root root 1.2G Jan 3 00:10 /var/lib/docker/containers/9c1.../9c1...-json.log
-rw-r----- 1 root root 2.8G Jan 3 00:11 /var/lib/docker/containers/aa4.../aa4...-json.log
-rw-r----- 1 root root 3.5G Jan 3 00:11 /var/lib/docker/containers/b7d.../b7d...-json.log
-rw-r----- 1 root root 6.1G Jan 3 00:12 /var/lib/docker/containers/cc9.../cc9...-json.log
-rw-r----- 1 root root 9.4G Jan 3 00:12 /var/lib/docker/containers/f01.../f01...-json.log
Що це означає: Один або кілька контейнерів генерують величезні логи. Це може заповнити диск і гальмувати хост через IO.
Рішення: Виправте гучність логів і встановіть ротацію. В екстреному порядку обережно вкоротіть найбільшого винуватця (з втратою частини даних).
Завдання 12: Перевірити конфігурацію ротації логів Docker (і застосувати її)
cr0x@server:~$ sudo cat /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5"
}
}
Що це означає: json-file логи будуть ротуватися при 50MB, зберігаючи 5 файлів. Це мінімум, щоб уникнути смерті від логів.
Рішення: Якщо відсутнє — додайте і перезапустіть Docker у вікні технічного обслуговування. Якщо є, але файли ще великі — ваші контейнери могли створитися раніше; треба їх пересоздати.
Завдання 13: Виявити CPU throttling (ознака «ліміти занадто тісні»)
cr0x@server:~$ CID=$(docker ps -qf name=api-7c9d); docker inspect -f '{{.Id}}' $CID
b3e2f0a1c9f7f4c2b3a0d0c2d1e9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f
cr0x@server:~$ cat /sys/fs/cgroup/cpu/docker/$CID/cpu.stat
nr_periods 124030
nr_throttled 38112
throttled_time 921837239112
Що це означає: Великий nr_throttled і значний throttled_time вказують, що контейнер часто хотів CPU, але його обмежували.
Рішення: Підвищте ліміт CPU, зменшіть кількість контейнерів на вузлі або оптимізуйте «гарячі» точки по CPU. Якщо затримки корелюють — throttling це ваша сигарета з димом.
Завдання 14: Перевірити IO-тиск хоста (коли все «раптом повільно»)
cr0x@server:~$ iostat -xz 1 3
Linux 6.1.0 (server) 01/03/2026 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
18.21 0.00 6.02 21.33 0.00 54.44
Device r/s w/s rkB/s wkB/s await aqu-sz %util
nvme0n1 85.1 410.2 8120.0 65211.1 18.4 2.31 94.7
Що це означає: Високий iowait і %util близько до насичення. Диск — вузьке місце; CPU чекає на IO.
Рішення: Зменшіть write amplification (логи, шторм флешів бази), перемістіть навантаження або збільшіть IO. Не «додавайте CPU» — це не допоможе.
Завдання 15: Перевірити насичення conntrack (генератор «випадкових» таймаутів мережі)
cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_count = 245112
net.netfilter.nf_conntrack_max = 262144
Що це означає: Conntrack майже заповнений. Нові з’єднання можуть падати у неприємний, переривчастий спосіб.
Рішення: Підвищте conntrack max (з урахуванням пам’яті), зменшіть частоту створення з’єднань, налаштуйте таймаути і перевірте наявність штормів повторів.
Жарт №2: Conntrack — це місце, куди з’єднання йдуть, щоб бути запам’ятаними назавжди, що романтично — поки вузол не закінчить пам’ять.
Плейбук швидкої діагностики: перші/другі/треті перевірки
Коли затримки зростають або помилки підскакують, потрібно швидко відповісти на одне питання: який ресурс насичується або першочергово відмовляє?
Ось плейбук, який працює навіть коли ваше моніторинг відстає, відсутній або бреше.
Перша перевірка: це цикл падіння, OOM чи регрес після деплою?
- docker ps: шукайте Restarting, unhealthy або нещодавно стартовані контейнери.
- docker inspect: коди виходу, прапорець OOMKilled, статус здоров’я.
- docker logs –tail: останні 50–200 рядків на предмет фатальних помилок і конфігурації при старті.
Швидке рішення: Якщо бачите зростання рестартів після зміни — заморозьте деплойти і відкотіть. Діагностика — після стабілізації.
Друга перевірка: тиск диска і inode (бо це ламає все)
- df -h на
/var/lib/dockerі монтуваннях логів. - df -i для вичерпання inode.
- du щоб знайти, який підкаталог Docker росте.
Швидке рішення: Якщо диск > 90% і росте — почніть звільняти простір негайно. Це один із рідкісних випадків, коли «прибирання» — валідна реакція на інцидент.
Третя перевірка: насичення ресурсів хоста (пам’ять, IO, CPU throttling)
- free -h і активність свопу: підтверджує тиск пам’яті.
- iostat -xz: показує насичення IO і iowait.
- cpu.stat throttling: підтверджує, що ліміти CPU надто тісні.
Швидке рішення: Насичення означає, що треба скидати навантаження, переміщати контейнери або змінювати ліміти — дебаг застосунку не виправить фізику.
Четверта перевірка: вичерпання мережевих таблиць і симптоми DNS
- conntrack count/max: заповнення викликає таймаути.
- ss -s (не показано вище): стани сокетів; велика кількість TIME-WAIT може вказувати на churn.
- логи застосунку: таймаути до залежностей; корелюйте з conntrack і retransmits.
П’ята перевірка: здоров’я демона Docker
- dockerd логи: помилки драйвера зберігання, збої pull’ів, повідомлення «no space left».
- docker info: підтверджує драйвер зберігання, cgroup driver, root dir.
Цитата, бо вона все ще найкраще формулює роботу з інцидентами:
Сподівання — не стратегія.
— Джеймс Кемерон
Три міні-історії з корпоративного світу
Міні-історія 1: Інцидент через хибне припущення
Середня компанія керувала набором Docker-хостів для внутрішніх API. Команда вважала, що контейнери «достатньо ізольовані» і трактувала хост як просту підкладку.
У них були оповіщення по HTTP 5xx і базовому CPU. Диск? «іноді дивилися».
Одної п’ятниці в прод пішов новий фіч-реліз з випадково залишеним ввімкненим детальним логуванням. Сервіс залишався «вгору»; помилок небагато.
Але логи росли швидко, і до ранку коренева файлова система Docker була майже заповнена. Перший симптом не був «диск повний». Це були повільні деплойти, бо завантаження образів і розпакування шарів почали трясти систему.
Потім клієнти бази даних почали таймаутитись, бо IO вузла був зафіксований.
Команда витратила години на суперечки про «мережеву нестабільність», бо ping’и були в порядку і CPU не був на піку. Зрештою хтось запустив df -h і побачив 99% використання.
Вони вкоротили великий json-лог і одразу побачили відновлення сервісів.
Після інциденту помилка очевидна: «якщо застосунок здоровий, то хост в порядку». Docker не запобігає заповненню диска; він просто дає більше місць, де приховати використання диска. Виправлення теж було очевидне: оповіщення про ріст диска і ротація логів за замовчуванням, забезпечені конфігуруванням.
Міні-історія 2: Оптимізація, що відбилася назад
Інша організація хотіла кращого bin-packing і знизити витрати в хмарі. Вони загострили ліміти CPU для кількох чутливих до затримки сервісів,
аргументуючи середнім низьким використанням CPU. Вони отримали економію. Потім з’явилися графіки.
Після зміни клієнти повідомляли про «періодичну повільність». Сервіси не падали. Рівень помилок помірний. On-call бачив CPU 40–50% і вирішив, що вузол здоровий.
Тим часом p99 затримки під час піку дорівнювали подвійній і потім «таємниче» поверталися до норми.
Справжнім винуватцем був CPU throttling. Плески навантаження стикалися з лімітами і отримували throttling саме тоді, коли їм потрібні були короткі сплески для очистки черг.
ОС робила саме те, що їй наказали. Команда не мала метрик throttling, тож це виглядало як фантом.
Оптимізація, що відбилася назад, була не в самих лімітах, а в «лімітах без видимості throttling — погано». Вони додали оповіщення throttled time,
підвищили CPU-ліміти для кількох сервісів і підлаштували конкурентність під реальні дозволені ресурси.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Платформа, близька до фінансової сфери, керувала Docker-хостами з чітким базисом: ротація логів у /etc/docker/daemon.json, нічне очищення кешу збірки,
і оповіщення про time-to-full для /var/lib/docker. Ніхто не хвалився цим. Це було просто «правило».
Під час насиченого квартального батчу один worker почав виробляти набагато більше логів через таймаути до upstream. Сервіс шумів,
але не вбив хост. Логи ротувалися. Використання диска росло, але оповіщення time-to-full спрацювало рано і дозволило розібратися без паніки.
Команда простежила реальну проблему до залежності і виправила шторм повторів. Важливе — чого не сталося: Docker-хост не досяг 100% диска,
контейнери не почали падати при спробі писати тимчасові файли, і інцидент не переріс у каскадний збій.
«Нудно, але правильно» не робить захоплюючих постмортемів. Але це зменшує кількість постмортемів.
Поширені помилки: симптом → корінна причина → виправлення
1) Симптом: Контейнери перезапускаються кожні кілька хвилин, без явної помилки застосунку
Корінна причина: OOM kill через занадто низький ліміт пам’яті або витік пам’яті; логи застосунку цього не показують.
Виправлення: Підтвердьте через docker inspect OOMKilled і ядро через dmesg. Підвищте ліміт або зменшіть конкурентність; додайте оповіщення про OOM і відстежуйте working set.
2) Симптом: «No space left on device», але df показує вільне місце
Корінна причина: Вичерпання inode або зарезервовані блоки на файловій системі; іноді тиск метаданих overlay2.
Виправлення: Перевірте df -i. Видаліть каталоги з величезною кількістю файлів (кеш збірки, тимчасові файли), проніть Docker-артефакти, налаштуйте співвідношення inode у ФС при потребі.
3) Симптом: Випадкові сплески затримки, CPU виглядає нормально
Корінна причина: CPU throttling через суворі ліміти; контейнер хоче CPU, але йому відмовляють.
Виправлення: Моніторити throttled time. Підвищити ліміт CPU або зменшити конкурентність; не покладатися лише на «CPU%» як сигнал ємності.
4) Симптом: Деплойти зависають або завантаження образів повільне, потім сервіси деградують
Корінна причина: Насичення дискового IO або тряска через драйвер зберігання Docker; часто у поєднанні з майже заповненим диском.
Виправлення: Використовуйте iostat і перевірки місця на диску. Зменшіть IO (логи, шторм rebuild), додайте запас диска і уникайте важких збірок на продакшн-вузлах.
5) Симптом: Мережеві таймаути по багатьом контейнерам, особливо вихідні
Корінна причина: Conntrack таблиця майже повна; важкий NAT-шторм з’єднань.
Виправлення: Перевірте conntrack count/max; налаштуйте nf_conntrack_max і таймаути; зменшіть churn з’єднань (keepalive, pooling) і уникайте штормів повторів.
6) Симптом: Логи одного контейнера гігантські; диск вузла постійно росте
Корінна причина: json-file логи без ротації; надмірна гучність логів; або повторювані помилки в циклі.
Виправлення: Встановіть опції ротації Docker; зменшіть обсяг логів; додайте лімітування частоти; відправляйте логи поза хост, якщо потрібне зберігання.
7) Симптом: Контейнер «Up», але сервіс мертвий або повертає помилки
Корінна причина: Відсутній healthcheck або він надто слабкий (перевіряє процес, а не функціональність).
Виправлення: Додайте HEALTHCHECK, що перевіряє підключення до залежностей або реальний readiness endpoint; оповіщайте при unhealthy до рестартів.
8) Симптом: Використання диска росте, але pruning мало що дає
Корінна причина: Томи накопичують дані, або застосунок пише у writable layer контейнера; не лише неактивні образи.
Виправлення: Визначте через docker system df і du. Перемістіть записи в томи з життєвим циклом; реалізуйте політики зберігання.
Чеклісти / покроковий план
План реалізації мінімальної спостережності (тиждень, реалістично)
- Виберіть мінімальні оповіщення (time-to-full диск, inode time-to-zero, OOM kill’и, цикли рестартів, CPU throttling, насичення conntrack, latency+помилки застосунку).
- Стандартизувати логування Docker: встановіть
max-size/max-fileу/etc/docker/daemon.json; застосуйте через конфіг менеджмент. - Маркуйте сервіси розумно: метрики маркуються сервісом і середовищем, а не ID контейнера. Тримайте ID контейнера для дебагу, не для оповіщення.
- Експортуйте «золоті сигнали» застосунку: частота запитів, рівень помилок, percentiles затримки. Якщо можна тільки одне — робіть error rate + p99 latency.
- Збирайте метрики хоста для диска, inode, IO, пам’яті, swap, conntrack. Без метрик хоста метрики контейнерів вас заплутають.
- Збирайте метрики контейнерів (CPU usage, throttling, working set пам’яті, рестарти). Перевірте, порівнявши з
docker statsпід навантаженням. - Напишіть одну сторінку runbook для кожного оповіщення: що перевіряти, які команди запускати і кроки відкату/м’якшання.
- Проведіть game day: симулюйте заповнення диску (в безпечному середовищі), OOM, навантаження на conntrack. Перевірте оповіщення і плейбук.
Чекліст безпеки диска (бо диск зрадить вас)
- Ротація Docker логів налаштована і перевірена для нових контейнерів
- Оповіщення про темп росту диска / time-to-full для корня Docker
- Оповіщення про темп споживання inode / time-to-zero
- Нічне очищення кешу збірки для build-вузлів (не обов’язково для прод)
- Явне зберігання для томів (БД і черги — не сміттєві ящики)
Чекліст безпеки CPU/пам’яті
- Оповіщення про OOM kill’и і наближення working set пам’яті до лімітів
- Оповіщення про CPU throttling, не лише про використання CPU
- Ліміти виставлені після реальних навантажувальних тестів; конкурентність прив’язана до бюджетів CPU/пам’яті
- Моніторинг свопу на хості; стійкий swap-in — попереджувальний знак, на який треба реагувати
Чекліст логування (мінімальна санітарія)
- Структуровані логи з request ID та полями помилок
- Лімітування частоти повторюваних помилок
- За замовчуванням у продакшн — без тілів запитів/відповідей
- Окремі аудиторні логи від debug-логів застосунку, якщо потрібно
FAQ
1) Яке єдине найважливіше оповіщення для Docker?
Time-to-full диска (і inode time-to-zero як його рівноцінний сестра). Дискові відмови викликають каскадні збої і псують хронологію інциденту.
Якщо додати тільки одне — зробіть це «ми будемо повні за X годин».
2) Чи варто оповіщати про рестарти контейнерів?
Так, але оповіщайте про швидкість рестартів за сервісом, а не про кожен рестарт. Розгортання викликає рестарти; цикл падіння — стійку швидкість.
Також додавайте коди виходу або сигнали OOMKilled у контекст оповіщення.
3) Чому мої логи застосунку не показують OOM kill’ів?
Бо ядро вбиває процес. Застосунок не встигає акуратно залогувати прощальне повідомлення. Підтверджуйте через docker inspect і dmesg.
4) Чи підходить json-file логування для продакшну?
Підійде, якщо ви налаштуєте ротацію і розумієте, що логи живуть на файловій системі хоста. Якщо потрібне довге зберігання — відправляйте логи куди інше.
Неротований json-file — машина для заповнення диска з правдоподібною невинуватістю.
5) CPU тільки 40%, чому затримки жахливі?
Перевірте throttling CPU і iowait. «CPU 40%» може означати, що половині вашої квоти відмовлено, або CPU простає, бо чекає диск.
Графіки використання не показують відмови; показує throttling.
6) Як відрізнити проблеми з місцем на диску від проблем з IO?
Проблеми місця видно у df -h і помилках «no space left». IO-проблеми проявляються як високий iowait і велика завантаженість пристрою в iostat.
Вони часто йдуть разом, але насичення IO може бути за наявності достатньо вільного місця.
7) Чому «docker system prune» не звільняє багато місця?
Бо використання диска може бути у томах, writable layer контейнера або активних образах. docker system df покаже, що можна повернути.
Якщо проблема в томах, pruning їх не зачепить, крім явного видалення (що може бути катастрофою).
8) Чи потрібен повний розподілений трейсинг для мінімуму спостережності Docker?
Ні, для мінімуму не потрібен. Трейсинг чудовий для складних шляхів запитів, але він не врятує вас від повних дисків, OOM kill’ів чи вичерпання conntrack.
Спочатку отримайте нудні сигнали хоста/контейнера. Потім додавайте трейси там, де вони окуповуються.
9) Який розумний налаштування ротації логів?
Загальний базис — max-size=50m і max-file=5 для загальних сервісів. Сервіси з великим обсягом логів можуть вимагати менших розмірів або інших драйверів.
«Правильне» налаштування — те, що запобігає виснаженню диска і дає достатню історію для дебагу.
Висновок: наступні кроки, які реально змінюють ситуацію
Відмови Docker рідко містять містику. Зазвичай це тиск ресурсів, неправильно виставлені ліміти, докучливі логи або вичерпання мережевих таблиць — плюс тонкий шар людського заперечення.
Мінімальний набір спостережності — це спосіб швидко розвіяти заперечення.
Практичні подальші кроки:
- Впровадьте глобальну ротацію Docker логів і перевірте, що нові контейнери наслідують її.
- Додайте оповіщення про time-to-full диска і inode time-to-zero для кореня Docker.
- Оповіщайте про OOM kill’и і швидкість циклів рестартів за сервісом.
- Почніть відстежувати CPU throttling; припиніть вважати, що CPU% розповідає всю історію.
- Додайте видимість насичення conntrack, якщо у вас багато NAT або велика кількість з’єднань.
- Напишіть односторінковий runbook з точними командами, які ви запустите під тиском (використайте завдання вище).
Якщо ви зробите тільки це, ви виявите більшість відмов Docker на ранніх стадіях. І ви витратите менше часу, дивлячись на спокійні дашборди, поки продакшн тихо горить.