Помилки Docker containerd/runc: як налагоджувати без перевстановлення

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

Ви розгорнули невелику зміну. Контейнер, який працював учора, тепер миттєво завершується. 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): «Робота над надійністю — це розуміння того, як системи справді ламаються, а не як би ми хотіли, щоб вони ламалися».

Одна порада, що заощадить вам години: не починайте з перевстановлення. Почніть з доведення, який рівень ламається, і чи здатен хост запустити щось взагалі.

Швидкий план діагностики (перевірити перше/друге/третє)

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

Перше: упевніться, що хост вам не бреше

  1. Диск і inode на шляху даних Docker (/var/lib/docker) і на кореневому файловому розділі.
  2. Тиск пам’яті і активність OOM.
  3. Журнали ядра на предмет маунтів, seccomp, AppArmor/SELinux відмов, помилок cgroup.

Друге: отримайте помилку з правильного джерела

  1. docker events навколо часу помилки.
  2. Журнали dockerd (systemd journal) для повних стек‑трейсів.
  3. Журнали containerd для помилок запуску shim/runc.

Третє: ізолюйте вісь, що ламається

  1. Сховище: помилки overlay2 mount, пошкоджена метадані шару, XFS d_type, дивні випадки NFS/shiftfs.
  2. Cgroup: cgroup v2, невідповідність драйвера systemd, проблеми з правами в rootless‑режимі.
  3. Безпека: AppArmor/SELinux/seccomp блоки, no‑new‑privileges, відсутні capability.
  4. 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.

Чеклісти / покроковий план (не гнійте систему)

Покрокова тріаж‑послідовність, коли контейнер не стартує

  1. Зафіксуйте точну помилку за допомогою docker start і docker ps -a --no-trunc. Вставте її в надійне місце.
  2. Перевірте здоров’я хоста:
    • df -hT / /var/lib/docker і df -i /var/lib/docker
    • free -m і журнали OOM ядра
    • dmesg -T | tail на предмет очевидних відмов чи помилок ФС
  3. Отримайте журнали з джерела:
    • journalctl -u docker --since ...
    • journalctl -u containerd --since ...
    • journalctl -k --since ...
  4. Виберіть вісь за доказами:
    • Permission denied + audit → вісь безпеки
    • Overlay mount errors + dmesg overlayfs → вісь сховища
    • Cgroup errors + підказки cgroup v2 → вісь cgroup
    • Hangs + D‑стан процесів → вісь I/O/сховища
  5. Зробіть одну зміну, потім негайно перевірте. Ніякого «всі на випадок».
  6. Поверніть 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 — і застосувати цілеспрямоване виправлення, не підриваючи решту ноди.

Практичні наступні кроки:

  1. Прийміть швидкий план діагностики і тримайте його в нотатках для інцидентів.
  2. Додайте моніторинг використання inode, а не лише байтів, для файлової системи DockerRootDir.
  3. Зробіть журнали ядра/audit частиною стандартного потоку налагодження запуску контейнера.
  4. Тримайте DockerRootDir на підтримуваній локальній файловій системі; ставтесь до «швидкого спільного сховища» для layer‑store із підозрою, поки ви не перевірили його на надійність.
  5. Коли змінюєте політику безпеки (AppArmor/SELinux/seccomp), робіть це спочатку для окремого робочого навантаження. Глобальні відключення — як правило, перетворюються на постійні жалюгідні рішення.

Якщо ви робитимете все це, інциденти все одно траплятимуться — продакшн завжди знаходить шлях. Але ви перестанете перевстановлювати runtime як ритуал і почнете виправляти справжні проблеми системи.

← Попередня
SPF: занадто багато DNS-запитів — скоротіть безпечно та пройдіть перевірки
Наступна →
ZFS ARC проти кешу сторінок Linux: хто перемагає і чому це важливо

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