Ви розгорнули невелику зміну. Контейнер, який працював учора, тепер миттєво завершується. Docker показує неінформативне повідомлення на кшталт OCI runtime create failed, containerd згадує «shim», а runc викидає permission denied, ніби намагається бути таємничим.
Перевстановлення Docker здається дією, але зазвичай це паніка з додатковими кроками. Такі збої майже завжди діагностуються на місці: пошкоджений маунт, невідповідність cgroup, проблема драйвера сховища, блокування seccomp/AppArmor або диск, в якому закінчилося щось, за чим ви не стежили.
Ментальна модель: хто ламається й куди дивитися
Коли контейнер не запускається, потрібно зрозуміти, який рівень сигналізує про помилку. Docker — це рецепція. containerd — менеджер готелю. runc — замочник. Ядро — будівля.
Що робить (і не робить) Docker
Демон Docker (dockerd) обробляє API‑запити, керування образами, мережі, томи та координує операції часу виконання. У більшості сучасних інсталяцій Docker не створює безпосередньо процес контейнера; він делегує це containerd.
Що робить containerd
containerd керує життєвим циклом контейнера й вмістом. Він створює «tasks» і викликає OCI runtime (зазвичай runc), щоб налаштувати неймспейси, cgroups, маунти, а потім виконати процес контейнера. Якщо ви бачите помилки на кшталт failed to create shim task або shim exited, це зона containerd, але корінь проблеми часто все одно в ядрі/cgroup/файловій системі.
Що робить runc
runc — реалізація OCI runtime. Він читає OCI config.json і просить ядро зробити складну роботу: змонтувати rootfs, налаштувати неймспейси, застосувати seccomp‑фільтри, встановити capability, налаштувати cgroups, а потім exec цільовий бінар. Коли runc каже permission denied, це може означати «seccomp», «LSM (AppArmor/SELinux)», «дозволи файлової системи» або «ядро відхилило операцію». Повідомлення рідко буває специфічним. Наша робота — це уточнити.
Ядро — там, де «неможливе» стає «очевидним»
Більшість інцидентів «Docker зламався» фактично: диск заповнений (або закінчилися inode), ядро має поведінку cgroup v2, якої ви не очікували, файлова система не підтримує overlay mounts, політика безпеки блокує певні прапори clone(), або DNS/iptables змінилося кимось, хто «прибрав мотлох».
Парафразована думка (приписується John Allspaw): «Робота над надійністю — це розуміння того, як системи справді ламаються, а не як би ми хотіли, щоб вони ламалися».
Одна порада, що заощадить вам години: не починайте з перевстановлення. Почніть з доведення, який рівень ламається, і чи здатен хост запустити щось взагалі.
Швидкий план діагностики (перевірити перше/друге/третє)
Це порядок дій для ситуації «я на чергуванні й зона ураження зростає». Мета не в тому, щоб бути вичерпним. Мета — швидко знайти вузьке місце й припинити гадання.
Перше: упевніться, що хост вам не бреше
- Диск і inode на шляху даних Docker (
/var/lib/docker) і на кореневому файловому розділі. - Тиск пам’яті і активність OOM.
- Журнали ядра на предмет маунтів, seccomp, AppArmor/SELinux відмов, помилок cgroup.
Друге: отримайте помилку з правильного джерела
- docker events навколо часу помилки.
- Журнали dockerd (systemd journal) для повних стек‑трейсів.
- Журнали containerd для помилок запуску shim/runc.
Третє: ізолюйте вісь, що ламається
- Сховище: помилки overlay2 mount, пошкоджена метадані шару, XFS d_type, дивні випадки NFS/shiftfs.
- Cgroup: cgroup v2, невідповідність драйвера systemd, проблеми з правами в rootless‑режимі.
- Безпека: AppArmor/SELinux/seccomp блоки, no‑new‑privileges, відсутні capability.
- Runtime: невідповідність бінарного runc, конфіг плагіна containerd, застряглі shim‑процеси.
Коли ви знаєте вісь, перестаньте робити безладні зміни. Зробіть одну свідому дію, перевірте і рухайтесь далі. Так ви скоротите час простою і зробите постмортеми нуднішими.
Цікаві факти та контекст (чому цей стек дивний)
- Docker відокремив containerd у 2016, щоб зробити runtime‑компоненти повторно використовуваними поза Docker; Kubernetes пізніше стандартизувався на containerd для багатьох дистрибутивів.
- runc походить з libcontainer, раннього двигуна контейнерів Docker, і став еталонною реалізацією OCI runtime.
- OCI (Open Container Initiative) сформувався в 2015 для стандартизації формату образів і поведінки runtime; тому помилки з «OCI runtime» з’являються й досі.
- Shim існує, щоб контейнери пережили рестарти демонів; containerd може впасти й повернутися без вбивства кожного контейнера, тому що shim зберігає зв’язок із дочірнім процесом.
- overlay2 став домінуючим драйвером сховища, бо він швидкий і використовує kernel OverlayFS, але він вибагливий щодо можливостей файлової системи та параметрів маунту.
- cgroup v2 змінив семантику (особливо в частині делегації й контролерів), і багато припущень, що «працювало на v1», тихо або гучно не працюють на v2.
- Rootless Docker — це не просто «Docker без sudo»; він використовує user namespaces і іншу мережеву модель, і він ламається інакше (часто делікатніше), ніж режим з root.
- Seccomp‑настройки за замовчуванням консервативні; вони можуть зламати «екзотичні» системні виклики, які використовують деякі робочі навантаження (або новіша glibc) на старих ядрах.
- XFS потребує ftype=1 (d_type) для коректності overlay2; без цього ви отримаєте неймовірно заплутані помилки шарів і перейменувань.
Жарт 1/2: Контейнери «легкі», доки ви не налагоджуєте їх о 3:00 — тоді кожний неймспейс важить рівно одну тонну.
Практичні завдання з налагодження (команди, виходи, рішення)
Нижче — реальні виконувані завдання. Кожне має три частини: команду, що типовий вихід означає, і рішення, яке ви приймаєте. Виконуйте їх по порядку, коли ви загубилися; або вибірково, коли знаєте напрям.
Завдання 1: Отримайте точну помилку від Docker (не те скорочення)
cr0x@server:~$ docker ps -a --no-trunc | head -n 5
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2b2c0f3b7a0c5f8f1c2c6d5a0a7a5f0a6e9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4 myapp:latest "/entrypoint.sh" 2 minutes ago Created myapp_1
cr0x@server:~$ docker start myapp_1
Error response from daemon: OCI runtime create failed: runc create failed: unable to start container process: error during container init: open /proc/self/fd: permission denied: unknown
Значення: Це вже глибше, ніж «не вдалося запустити». Вказує на runc і конкретну операцію. /proc + permission denied часто означає LSM (AppArmor/SELinux), seccomp або дивне обмеження маунту/неймспейсу.
Рішення: Не чіпайте пакунки Docker. Йдіть прямо в journald і журнали ядра, щоб побачити, хто сказав «ні».
Завдання 2: Дивіться події Docker в реальному часі під час відтворення
cr0x@server:~$ docker events --since 10m
2026-01-03T11:12:08.123456789Z container create 2b2c0f3b7a0c (image=myapp:latest, name=myapp_1)
2026-01-03T11:12:08.234567890Z container start 2b2c0f3b7a0c (image=myapp:latest, name=myapp_1)
2026-01-03T11:12:08.345678901Z container die 2b2c0f3b7a0c (exitCode=127, image=myapp:latest, name=myapp_1)
Значення: Контейнер помирає миттєво. Коди виходу інколи на рівні програми, інколи runtime; має значення час. Миттєва смерть часто означає, що init не стартував або в entrypoint відсутній бінарник.
Рішення: Якщо подія die миттєва і код 127/126, перевірте rootfs і наявність entrypoint; якщо це помилка runtime — дивіться журнали.
Завдання 3: Швидко перегляньте конфіг контейнера (entrypoint, маунти, security opts)
cr0x@server:~$ docker inspect myapp_1 --format '{{json .HostConfig.SecurityOpt}} {{json .HostConfig.CgroupnsMode}} {{.Path}} {{json .Args}}'
null "private" /entrypoint.sh ["--serve"]
Значення: Немає спеціальних security opts, приватний cgroup namespace, entrypoint — /entrypoint.sh. Якщо помилка 127, цей шлях може не існувати або не бути виконуваним в образі.
Рішення: Якщо помилка схожа на «бінарник відсутній», запустіть образ з відомою shell або ls через інший entrypoint (якщо можливо). Якщо «permission denied» — перемикайтеся на LSM/seccomp/cgroups.
Завдання 4: Прочитайте журнали dockerd для повного стек‑трейсу
cr0x@server:~$ sudo journalctl -u docker --since "15 min ago" --no-pager | tail -n 30
Jan 03 11:12:08 server dockerd[1187]: time="2026-01-03T11:12:08.330123456Z" level=error msg="Error response from daemon: OCI runtime create failed: runc create failed: unable to start container process: error during container init: open /proc/self/fd: permission denied: unknown"
Jan 03 11:12:08 server dockerd[1187]: time="2026-01-03T11:12:08.330234567Z" level=info msg="Attempting next endpoint for containerd"
Jan 03 11:12:08 server dockerd[1187]: time="2026-01-03T11:12:08.330345678Z" level=error msg="Handler for POST /v1.45/containers/2b2c0f3b7a0c/start returned error: OCI runtime create failed: runc create failed: unable to start container process: error during container init: open /proc/self/fd: permission denied: unknown"
Значення: Docker передає помилку OCI runtime. Стек‑трейс не завжди показується, якщо не ввімкнено debug, але це підтверджує, що це не «Docker не може поговорити з containerd».
Рішення: Наступна зупинка: журнали containerd і kernel/audit. Відмови доступу — це політика, а не проводка.
Завдання 5: Прочитайте журнали containerd (shim і runc контекст)
cr0x@server:~$ sudo journalctl -u containerd --since "15 min ago" --no-pager | tail -n 30
Jan 03 11:12:08 server containerd[1044]: time="2026-01-03T11:12:08.320111222Z" level=info msg="starting containerd" revision= version=1.7.12
Jan 03 11:12:08 server containerd[1044]: time="2026-01-03T11:12:08.328222333Z" level=error msg="RunPodSandbox for "2b2c0f3b7a0c" failed" error="failed to create task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: open /proc/self/fd: permission denied: unknown"
Значення: containerd підтверджує збій runc. Якщо ви бачите «shim exited» без деталей runc, можливо, бінарник runtime впав або пошкоджено state dir.
Рішення: Якщо повідомлення containerd коротке, тимчасово ввімкніть debug‑логування (див. пізніше). Інакше переходьте до журналів ядра, щоб знайти, хто відмовив у доступі.
Завдання 6: Перевірте журнали ядра на AppArmor/SELinux/seccomp відмови
cr0x@server:~$ sudo dmesg -T | tail -n 25
[Fri Jan 3 11:12:08 2026] audit: type=1400 audit(1735902728.332:312): apparmor="DENIED" operation="open" class="file" profile="docker-default" name="/proc/self/fd/" pid=22451 comm="runc:[2:INIT]" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
Значення: Це димова гармата: AppArmor заблокував відкриття /proc/self/fd/ для профілю docker-default.
Рішення: Виправте політику AppArmor або запустіть контейнер з відповідною опцією профілю (обережно). Не відключайте AppArmor глобально, якщо ви не любите довгі пояснення пізніше.
Завдання 7: Підтвердіть статус AppArmor і завантажені профілі
cr0x@server:~$ sudo aa-status | head -n 20
apparmor module is loaded.
73 profiles are loaded.
67 profiles are in enforce mode.
docker-default
/usr/sbin/cupsd
/usr/sbin/sshd
6 profiles are in complain mode.
0 processes are unconfined but have a profile defined.
Значення: AppArmor активований і в режимі застосування. Якщо docker-default застосовується, ваш контейнер ймовірно під цим профілем, якщо не вказано інше.
Рішення: Якщо це нова поведінка, знайдіть, що змінилося: оновлення ядра, оновлення AppArmor або хто‑небудь переключив профілі. Для швидкої тимчасової міри використайте --security-opt apparmor=unconfined для конкретного робочого навантаження (потім напишіть реальну політику).
Завдання 8: Перевірте місце на диску та inode (так, щоразу)
cr0x@server:~$ df -hT / /var/lib/docker
Filesystem Type Size Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4 96G 95G 120M 100% /
/dev/nvme0n1p2 ext4 96G 95G 120M 100% /
cr0x@server:~$ df -i /var/lib/docker
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 6553600 6551000 2600 100% /
Значення: У вас закінчилось місце і inode. Будь‑що з них може ламати створення контейнерів, завантаження образів і overlay маунти. Коли Docker падає з «no space left on device», іноді це означає «нема inode», що — особливо підлий випадок.
Рішення: Спочатку звільніть місце/inode. Prune безпечно, видаліть старі логи, налаштуйте ротацію журналів або перенесіть Docker data root. Не марнуйте час на runc‑флаги, поки диск у критичному стані.
Завдання 9: Визначте драйвер сховища та Docker root dir
cr0x@server:~$ docker info --format 'Driver={{.Driver}} DockerRootDir={{.DockerRootDir}} CgroupDriver={{.CgroupDriver}} CgroupVersion={{.CgroupVersion}}'
Driver=overlay2 DockerRootDir=/var/lib/docker CgroupDriver=systemd CgroupVersion=2
Значення: overlay2 на cgroup v2 з драйвером systemd. Це звужує коло можливих помилок: проблеми маунту overlay, делегації cgroup/прав, обмеження юнітів systemd.
Рішення: Якщо помилка стосується маунтів/шарів — фокусуйтеся на overlay2 і підлягаючій ФС. Якщо вказується «cgroup» або «systemd» — перевіряйте там.
Завдання 10: Перевірте фічі файлової системи під overlay2 (XFS ftype, опції маунту)
cr0x@server:~$ findmnt -no FSTYPE,OPTIONS /var/lib/docker
ext4 rw,relatime,errors=remount-ro
cr0x@server:~$ sudo docker info | sed -n '/Backing Filesystem/,+5p'
Backing Filesystem: extfs
Supports d_type: true
Using metacopy: false
Native Overlay Diff: true
Значення: Підлягаюча ФС підтримує d_type; overlay2 має бути структурно в порядку. На XFS ви хочете ftype=1; на ext4 важливо, щоб це був локальний розділ з адекватними опціями.
Рішення: Якщо підлягаюча ФС — NFS, CIFS або щось «креативне», очікуйте проблем з overlay2. Перенесіть DockerRootDir на локальне сховище або використайте сумісний драйвер.
Завдання 11: Шукайте помилки overlay mount і «invalid argument» в журналах ядра
cr0x@server:~$ sudo dmesg -T | grep -E 'overlay|OverlayFS' | tail -n 10
[Fri Jan 3 10:58:41 2026] overlayfs: upper fs does not support RENAME_WHITEOUT
[Fri Jan 3 10:58:41 2026] overlayfs: failed to set xattr on upper
Значення: OverlayFS незадоволений можливостями upper filesystem. Це може статися через певні опції маунту, старі ядра або файлові системи, що не підтримують потрібні xattr/фічі.
Рішення: Виправте файлову систему (опції маунту, підтримку в ядрі) або перемістіть DockerRootDir. Спроба «prune images» не виправить ФС, яка не може виконувати потрібні операції overlay.
Завдання 12: Перевірте маунт cgroup v2 і доступність контролерів
cr0x@server:~$ mount | grep cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
cr0x@server:~$ cat /sys/fs/cgroup/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma
Значення: cgroup v2 змонтовано і контролери доступні. Проблеми виникають, коли контролери не делеговано або systemd блокує їх для Docker.
Рішення: Якщо в помилці згадується cgroup, підтвердіть, що cgroup драйвер Docker збігається з systemd і що версія systemd підтримує потрібні фічі.
Завдання 13: Знайдіть OOM‑вбивства, що «таємно» зупиняють контейнери
cr0x@server:~$ sudo journalctl -k --since "2 hours ago" --no-pager | grep -i -E 'oom|killed process' | tail -n 10
Jan 03 10:44:19 server kernel: Out of memory: Killed process 21902 (myapp) total-vm:812340kB, anon-rss:512000kB, file-rss:1200kB, shmem-rss:0kB, UID:0 pgtables:1400kB oom_score_adj:0
Значення: Ядро вбило ваш процес. Docker може повідомляти, що контейнер «вийшов» без корисної runtime‑помилки. Це не проблема containerd. Це хост робить триаж.
Рішення: Усуньте тиск пам’яті: підніміть ліміти, налаштуйте запити/ліміти, додайте swap (обережно) або перемістіть навантаження. Налагодження runc не поверне пам’ять.
Завдання 14: Перевірте застряглі shim‑и й зомбі runtime‑процеси
cr0x@server:~$ ps -eo pid,ppid,comm,args | grep -E 'containerd-shim|runc' | grep -v grep | head
22451 1044 containerd-shim-runc-v2 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 2b2c0f3b7a0c -address /run/containerd/containerd.sock
22460 22451 runc:[2:INIT] runc:[2:INIT]
Значення: Для кожного контейнера існує shim. Якщо shims лишаються після того, як контейнерів нема, або INIT‑процеси runc зависають, можливо, маунт застряг або є баг ядра. Часто це зависання сховища або сон у непреривному режимі.
Рішення: Не вбивайте випадкові PID першими. Визначте, чи вони у D‑стані (див. наступне завдання). Якщо так — це I/O або проблема ФС.
Завдання 15: Перевірте стан процесу на D‑стан (I/O hang) і заблоковані таски
cr0x@server:~$ ps -o pid,state,wchan,comm -p 22460
PID S WCHAN COMMAND
22460 D ovl_wa runc:[2:INIT]
cr0x@server:~$ sudo dmesg -T | tail -n 5
[Fri Jan 3 11:05:12 2026] INFO: task runc:[2:INIT]:22460 blocked for more than 120 seconds.
Значення: D‑стан означає непреривний сон, зазвичай очікування I/O. Вбивати такі процеси марно. Тут «Docker вниз» насправді означає «сховище горить».
Рішення: Перестаньте намагатись перезапустити Docker. Дослідіть сховище: диск, RAID, мережеве сховище, помилки ФС. Можливо, знадобиться перезавантаження хоста, але розумійте причину перед цим кроком.
Завдання 16: Перевірте здоров’я сокету containerd та відповідь API
cr0x@server:~$ sudo ss -lxnp | grep containerd
u_str LISTEN 0 4096 /run/containerd/containerd.sock 12345 * 0 users:(("containerd",pid=1044,fd=9))
cr0x@server:~$ sudo ctr --address /run/containerd/containerd.sock version
Client:
Version: 1.7.12
Revision: 9a8b7c6d5e4f
Server:
Version: 1.7.12
Revision: 9a8b7c6d5e4f
Значення: containerd слухає і відповідає. Якщо Docker каже, що не може підключитись до containerd, це допомагає розділити «конфіг демона» та «runtime мертвий».
Рішення: Якщо ctr працює, а docker — ні, фокусуйтесь на dockerd конфігурації, сокетах або правах. Якщо ctr зависає — containerd хворий або заблокований (часто через сховище).
Завдання 17: Використайте ctr для переліку tasks і виявлення напівстворених контейнерів
cr0x@server:~$ sudo ctr -n moby containers list | head
CONTAINER IMAGE RUNTIME
2b2c0f3b7a0c docker.io/library/myapp:latest io.containerd.runc.v2
cr0x@server:~$ sudo ctr -n moby tasks list
TASK PID STATUS
2b2c0f3b7a0c 0 STOPPED
Значення: Об’єкт контейнера існує, але task не працює (PID 0). Це сходиться з «create failed» або «init failed».
Рішення: Якщо накопичується багато STOPPED tasks — швидше за все системна проблема в runtime/сховищі/безпеці. Виправте ось, а потім очистіть завислий стан.
Завдання 18: Перевірте використання сховища Docker і prune з обачністю
cr0x@server:~$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 42 6 38.2GB 31.7GB (83%)
Containers 19 2 1.1GB 900MB (81%)
Local Volumes 15 8 120GB 0B (0%)
Build Cache 10 0 8.4GB 8.4GB
cr0x@server:~$ docker image prune -a --filter "until=168h"
Deleted Images:
deleted: sha256:...
Total reclaimed space: 28.4GB
Значення: Образи і build cache можна звільнити; томи — ні. Вихід показує, де безпечно повернути місце без видалення даних клієнтів.
Рішення: Prune images/build cache під контролем змін. Уникайте видалення томів, якщо ви не впевнені. Якщо закінчилися inode, prune також може допомогти, але перевіряйте df -i.
Завдання 19: Перевірте режим iptables/nftables, коли мережеві помилки маскуються під runtime‑помилки
cr0x@server:~$ sudo docker run --rm busybox:latest nslookup example.com
Server: 127.0.0.11
Address 1: 127.0.0.11
Name: example.com
Address 1: 93.184.216.34
cr0x@server:~$ sudo iptables -S | head -n 5
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-USER
Значення: DNS резолюція в контейнері працює; iptables‑ланки є. Якщо контейнери нічого не бачать і ви бачите FORWARD policy DROP без належних правил Docker, ви отримаєте симптоми, що виглядають як «контейнер не стартує», бо програми в ньому миттєво падають перевірки здоров’я.
Рішення: Якщо runtime успішний, але програми помирають одразу — перевіряйте мережу й DNS. Не кожен «контейнер помер» — це вина runc.
Завдання 20: Тимчасово підвищіть verbosity dockerd (безпечним чином) для отримання контексту
cr0x@server:~$ sudo mkdir -p /etc/docker
cr0x@server:~$ sudo cat /etc/docker/daemon.json
{
"log-level": "info"
}
cr0x@server:~$ sudo sh -c 'cat > /etc/docker/daemon.json <
Значення: Debug‑журнали показують, на якому кроці dockerd знаходиться і що він попросив containerd зробити. Це особливо корисно при зависаннях (ви побачите останній вдалий крок).
Рішення: Користуйтесь debug‑режимом коротко під час інциденту і поверніться до info. Debug‑журнали голосні і можуть зайняти диск — той самий, що, можливо, вже вичерпано.
Жарт 2/2: «Просто перезапустіть Docker» — операційний еквівалент постукування по торговому автомату: іноді допомагає, завжди дивно.
Три корпоративні міні‑історії з практики
1) Інцидент через хибне припущення: «permission denied означає права файлу»
У них була стабільна когорта хостів. Щотижня проходили дрібні оновлення ОС. Одного вівторка деплой на підмножині хостів почав падати з OCI runtime create failed: permission denied. Миттєве припущення: якийсь файл в образі втратив біт виконання. Команда перебудувала образ, розгорнула — і… нічого не змінилося. Та сама помилка, ті ж хости, різні digest‑и образів.
Хтось пробував класичний фікс: перевстановити Docker. На одному хості «спрацювало», що достатньо, щоб це перетворилося на забобон. На наступному хості не допомогло. Тепер мали неконсистентність і час простою — найгірше з обох світів.
Прорив зробив втомлений SRE, який перестав дивитися на Docker і почав дивитися журнали audit ядра. AppArmor‑відмови. Оновлення ОС ввело жорсткішу поведінку профілю docker-default у тій збірці дистрибутива, і одне навантаження відкрило шлях у /proc під час init, що спрацювало як відмова.
Виправлення було нудним і конкретним: встановили для постраждалого робочого навантаження індивідуальний override профілю AppArmor, а потім працювали зі службою безпеки над мінімальною зміною політики. Ніякого глобального відключення. Ніякого перевстановлення. Чому «перевстановлення спрацювало раз»? Той хост мав застарілий кеш політик, який не перезавантажився відразу — так і народжуються міфи в продакшні.
Висновок: «permission denied» — це категорія, а не діагноз. Якщо ви не перевіряєте audit/LSM журнали, ви налагоджуєте з пов’язкою на очах і ліхтариком з розрядженими батарейками.
2) Оптимізація, що відкотилася: перемістили DockerRootDir на «швидке спільне сховище»
Платформена команда хотіла швидше замінювати ноди. Вони вирішили розмістити /var/lib/docker на спільному сховищі, щоб хости могли перепризначатися без перезавантаження образів. На папері: менше pulls, швидше масштабування, менше трафіку. Насправді: вони просто підключили latency‑чутливий overlay filesystem до мережевого сховища з випадковими паузами.
Спочатку все було нормально. Контейнери стартували. Pull‑и образів були швидкі. Потім мережевий інцидент зберігання спричинив паузи 2–5 секунд. OverlayFS не встигав адекватно це обробити. runc init‑процеси почали застрягати в D‑стані. Shims залишалися, ніби привиди. Перезапуски Docker не допомагали, бо потоки ядра були заблоковані на I/O.
Інцидент загострився через вводні симптоми. Інженери бачили в логах containerd повідомлення про вихід shim і думали про баги runtime. Інші бачили випадкові «context deadline exceeded» і думали про мережу. Істина: бекенд сховища іноді ставився в паузу, а операції overlay upperdir (rename/whiteout/xattr) підсилювали проблему.
Вони відкотили DockerRootDir назад на локальні SSD. Залишили спільне сховище, але лише для явних томів, де розуміли шаблони I/O і домен відмов. Заміна нод стала трохи повільнішою. Інциденти — рідшими. Всі стали більше спати.
Висновок: розміщення layer‑store Docker на спільному сховищі — оптимізація, що часто стає податком на надійність. Якщо ви мусите так робити — тестуйте поведінку OverlayFS під затримками, а не лише пропускну здатність.
3) Нудна, але правильна практика, що врятувала ситуацію: тримати runbook «здоров’я хоста»
FinTech компанія запускала критичні батч‑джоби в контейнерах на невеликому кластері. Одної п’ятниці контейнери почали падати з failed to mount overlay: no space left on device. Люди думали, що диск повний. Насправді — ні, принаймні за df -h. Багато гігабайтів вільно.
Але on‑call мав runbook, що починався з двох команд: df -h і df -i. Inode були на 100%. Винуватець — мільйони дрібних файлів, які генерував логуючий sidecar і писав у хост‑путь (так, буває). Docker не міг створити нові метадані шарів через відсутність inode.
Вони зупинили винуватця, очистили директорію і контейнери запустилися одразу. Ніяких перевстановлень. Ніяких переміщень rootdir. Ніякого відчайдушного тюнінгу ядра. Потім додали моніторинг inode і примусову ротацію логів — і проблема так більше не повторювалася в цій формі.
Висновок: базові перевірки хоста — не нижче вашої уваги. Вони різниця між «п’ятихвилинною правкою» і «двогодинним полюванням на привидів».
Типові помилки: симптом → корінь → виправлення
1) Симптом: OCI runtime create failed: permission denied
Корінь: Часто AppArmor/SELinux відмова або seccomp заблокував syscall, а не права файлової системи.
Виправлення: Перевірте dmesg -T і audit‑журнали. Налаштуйте per‑container security опції або політику. Уникайте глобального відключення LSM.
2) Симптом: no space left on device, але df -h показує вільні GB
Корінь: Вичерпані inode або Docker data path на іншому файловому розділі, ніж ви перевіряєте.
Виправлення: df -i /var/lib/docker. Prune образи/build cache, почистіть логи, додайте моніторинг inode.
3) Симптом: failed to mount overlay: invalid argument
Корінь: Підлягаюча ФС не підтримується (наприклад, XFS без ftype=1, NFS/CIFS) або невідповідність фіч в ядрі/OverlayFS.
Виправлення: Перенесіть DockerRootDir на підтримувану локальну ФС. На XFS переконайтесь, що ftype=1. Уникайте «хитрих» маунтів для layer‑store.
4) Симптом: запуск контейнера зависає; docker‑команди вичерпують час
Корінь: Застрягле I/O (D‑стан), затримки сховища або помилки ФС, що блокують операції overlay.
Виправлення: Перевірте стан процесу (ps ... state), заблоковані таски в dmesg, здоров’я сховища. Перезапуск Docker не розблокує ядро.
5) Симптом: containerd: failed to create task і shim exited unexpectedly
Корінь: runc краш, несумісний бінарник runtime, пошкоджені state‑директорії або базова відмова (LSM/cgroup).
Виправлення: Читайте журнали containerd і ядра. Підтвердіть версії runtime; не видаляйте випадковий стан, поки не зрозумієте, що саме видаляєте.
6) Симптом: контейнери виходять миттєво після старту, без runtime‑помилки
Корінь: Програма падає швидко (відсутній конфіг, помилка DNS, неможливість підключення), або OOM‑вбивство.
Виправлення: Перевірте логи контейнера, поведінку healthcheck і журнали OOM ядра. Не ганяйтесь за runc, якщо ядро вбило додаток.
7) Симптом: демон Docker не стартує після оновлення
Корінь: Пошкоджений daemon.json, несумісні налаштування драйвера сховища, залишкові прапори від старих версій.
Виправлення: Перевірте JSON, дивіться journalctl -u docker, тимчасово поверніться до мінімальної конфігурації, потім додайте налаштування по одному.
8) Симптом: rootless контейнери падають з помилками cgroup
Корінь: Делегація cgroup не налаштована, обмеження systemd user session, відсутні контролери для користувацького slice.
Виправлення: Перевірте делегацію cgroup v2 і вимоги rootless. Rootless — не drop‑in заміна; ставтесь до нього як до іншого runtime.
Чеклісти / покроковий план (не гнійте систему)
Покрокова тріаж‑послідовність, коли контейнер не стартує
- Зафіксуйте точну помилку за допомогою
docker startіdocker ps -a --no-trunc. Вставте її в надійне місце. - Перевірте здоров’я хоста:
df -hT / /var/lib/dockerіdf -i /var/lib/dockerfree -mі журнали OOM ядраdmesg -T | tailна предмет очевидних відмов чи помилок ФС
- Отримайте журнали з джерела:
journalctl -u docker --since ...journalctl -u containerd --since ...journalctl -k --since ...
- Виберіть вісь за доказами:
- Permission denied + audit → вісь безпеки
- Overlay mount errors + dmesg overlayfs → вісь сховища
- Cgroup errors + підказки cgroup v2 → вісь cgroup
- Hangs + D‑стан процесів → вісь I/O/сховища
- Зробіть одну зміну, потім негайно перевірте. Ніякого «всі на випадок».
- Поверніть verbosity, якщо ви його ввімкнули. Debug — тимчасовий ліхтар, не постійне світло.
Чекліст «чи варто перезапускати docker/containerd?»
- Так, якщо: демони зависли, але хост здоровий, немає D‑стану, немає помилок ФС, і ви можете терпіти короткий простій.
- Ні, якщо: ядро показує заблоковані таски, операції overlay зависають, є помилки сховища або коренева ФС заповнена. Спочатку виправте хост.
- Іноді, якщо: є застряглі shims для мертвих контейнерів. Коректне зупинення і перезапуск може очистити стан, але лише після перевірки I/O.
Чекліст гігієни сховища (попереджає половину інцидентів)
- Моніторьте inode, а не лише байти.
- Тримайте DockerRootDir на локальному сховищі з відомими фічами файлової системи.
- Ротуйте журнали та логи додатків; не дозволяйте
/var/logперетворитись на смітник. - Планово очищуйте образи/build cache з урахуванням вашого циклу розгортання (із запобіжниками).
- Слідкуйте за повідомленнями ядра про overlayfs; вони часто з’являються до повного відмовлення.
FAQ
1) Чи потрібно іноді перевстановлювати Docker, щоб виправити помилки containerd/runc?
Рідко. Перевстановлення може замаскувати симптом, скидаючи конфіг/стан, але не вирішить виснаження диска, LSM‑відмови, делегацію cgroup або файлову систему, яка не підтримує overlay. Спочатку доведіть вісь проблеми.
2) Як визначити, чи проблема в Docker, containerd чи runc?
Користуйтесь журналами. Docker часто скаже «OCI runtime create failed» (це runc). Журнали containerd згадуватимуть створення shim/task. Журнали ядра/audit скажуть, якщо ядро відмовило у виконанні операції (маунт, open, syscall, cgroup).
3) Який найшвидший спосіб налагодити «permission denied»?
Шукайте LSM‑відмови: dmesg -T і audit‑журнали. Якщо бачите AppArmor/SELinux повідомлення — відповідь знайдено. Якщо ні — перевірте права файлової системи і seccomp.
4) Що означає «shim exited unexpectedly» насправді?
Це означає, що containerd запустив shim‑процес для управління task і він помер. Причини: помилка runc, пошкоджені state‑директорії або базові збої ядра. Це симптом, а не корінь.
5) Чому проблеми overlay2 виглядають як випадкові runtime‑помилки?
Бо overlay2 лежить під усім іншим. Якщо overlay маунти не вдаються, runc не зможе створити rootfs; якщо операції overlay блокуються на I/O — процеси зависають; якщо xattr/rename‑семантика не підтримується, ви отримаєте «invalid argument» в місцях, які не називають сховище.
6) Як налагоджувати без шкоди для працюючих контейнерів?
Переважайте дії лише для читання: журнали, docker info, ctr version, df, dmesg. Уникайте перезапусків демонів, поки не оціните, чи проблема локальна або системна. Якщо сховище зависає — перезапуски можуть погіршити ситуацію.
7) Якщо я ввімкну debug‑логування, який ризик?
Зайнятість диска і шум. Debug‑журнали швидко ростуть під час циклів помилок. Якщо ви вже близькі до ліміту диска/inode, debug може стати останнім соломинкою. Вмикайте коротко, збережіть потрібне, вимкніть.
8) Чому cgroup v2 спричиняє помилки запуску контейнера?
Тому що делегація й включення контролерів суворіші. Деякі налаштування припускали структуру cgroup v1 або покладалися на контролери, недоступні для Docker/systemd slice. Невідповідність драйверів cgroup (systemd vs cgroupfs) також може вдарити.
9) Чи важче налагоджувати rootless Docker?
Інакше, а не обов’язково важче. Помилки часто вказують прямо на user namespace, делегацію cgroup або мережеві обмеження. Але налагоджуйте з rootless припущеннями: інші шляхи, інші права, інші ліміти.
10) Яку одну метрику ви б додали, якщо часто бачите runtime‑помилки?
Використання inode на файловій системі, яка містить DockerRootDir, плюс лічильник «blocked task» ядра або алерти на повторювані попередження overlayfs. Моніторинг тільки байтів — шлях до несподіванок.
Висновок: наступні кроки, що справді допомагають
Коли Docker/containerd/runc починають викидати помилки, ваша задача — не знайти чарівну команду. Ваша задача — ідентифікувати вісь, що ламається — сховище, cgroups, політика безпеки або стан runtime — і застосувати цілеспрямоване виправлення, не підриваючи решту ноди.
Практичні наступні кроки:
- Прийміть швидкий план діагностики і тримайте його в нотатках для інцидентів.
- Додайте моніторинг використання inode, а не лише байтів, для файлової системи DockerRootDir.
- Зробіть журнали ядра/audit частиною стандартного потоку налагодження запуску контейнера.
- Тримайте DockerRootDir на підтримуваній локальній файловій системі; ставтесь до «швидкого спільного сховища» для layer‑store із підозрою, поки ви не перевірили його на надійність.
- Коли змінюєте політику безпеки (AppArmor/SELinux/seccomp), робіть це спочатку для окремого робочого навантаження. Глобальні відключення — як правило, перетворюються на постійні жалюгідні рішення.
Якщо ви робитимете все це, інциденти все одно траплятимуться — продакшн завжди знаходить шлях. Але ви перестанете перевстановлювати runtime як ритуал і почнете виправляти справжні проблеми системи.