Симптом нудний. «ПОМИЛКА ЗАВДАННЯ: запуск контейнера ‘101’ не вдався.» Наслідки — ні. Ви на чергуванні, сервіс упав, і помилка виглядає як записка вимагальника, написана трьома підсистемами, яким не надто подобається одна з одною: LXC, cgroups і AppArmor.
Якщо ви сприймаєте ці повідомлення як шум, ви будете метушитися — перемикати «nesting», перезавантажувати вузли та молитися на ядро. Якщо навчитеся їх читати, зазвичай виправите проблему за хвилини і зрозумієте, чи маєте справу з відмовою контрольної площини на рівні хоста (cgroups), політичною відмовою (AppArmor) або просто з проблемою файлової системи/мапування ідентифікаторів.
Ментальна модель: що має вдались для запуску LXC
Запуск LXC на Proxmox — це ланцюг залежностей. Коли він не вдається, помилка зазвичай правдива — просто не надає контексту. Вам потрібен цей ланцюг у голові, щоб локалізувати зламане ланкою.
Ланцюг, простими словами
- Proxmox викликає LXC через
lxc-start(з обгорткою відpve-container), створюючи дерево процесів контейнера. - Налаштовуються неймспейси ядра: mount, PID, мережа, user (для unprivileged) тощо.
- Створюються cgroups і приєднуються контролери (CPU, memory, pids, io). Якщо cgroups змонтовані неправильно, відсутні контролери або несумісні з очікуваннями конфігурації, запуск зупиняється.
- Застосовується політика безпеки: завантажується/застосовується профіль AppArmor; можуть бути прикріплені seccomp-фільтри.
- Монтується коренева ФС: bind-монти (напр.
/dev,/proc,/sys), підключається том збереження, опційні монти зmp0і т.д. - Всередині контейнера стартує init (часто systemd). Якщо systemd не може змонтувати cgroups або отримує відмову, він може одразу завершитися — це виглядає як «контейнер не запускається».
Практичний висновок: помилки cgroups частіше на рівні хоста (конфіг вузла, параметри завантаження ядра, структура монтування), тоді як AppArmor — це рівень політик (профіль блокує конкретну операцію, зазвичай після вмикання nesting, FUSE, CIFS або доступу до привілейованих пристроїв).
Цитата, яку варто тримати на екрані: «Надія — це не стратегія.»
— парафразована ідея, яку часто приписують операційним лідерам. Сенс простий: припиніть гадати і слідуйте слідам доказів.
Короткий жарт #1: Якщо ваш контейнер не запускається через cgroups — вітаю, ви виявили проблему, яку не можна виправити додаванням ще YAML.
Швидкий план діагностики (перевірити першим/другим/третім)
Це шлях «в мене є п’ять хвилин». Він не вичерпний; розрахований знайти вузьке місце швидко і вирішити, чи можна виправити на місці або потрібно спустошувати вузол.
Першим: упевніться, що це не проста конфігурація або проблема з диском
- Перегляньте журнал завдання в Proxmox (зазвичай там перша жорстка відмова).
- Перевірте конфіг контейнера на підозрілі монти, nesting та налаштування unprivileged.
- Переконайтеся, що rootfs-том існує і може бути змонтований на хості.
Другим: вирішіть, чи пахне це cgroups чи AppArmor
- Якщо бачите рядки типу
cgroup,cgroup2,controllers,cgroupfs,failed to mount /sys/fs/cgroup: починайте з cgroups. - Якщо бачите
apparmor="DENIED",profile=,operation=,audit: починайте з AppArmor. - Якщо пише
permission denied, але без рядків аудиту AppArmor, підозрюйте ID-мепи, права на точки монтування або дозволи сховища.
Третім: ізолюйте — один контейнер чи весь вузол
- Запустіть відомий робочий маленький контейнер. Якщо і він падає — це вузол.
- Перевірте, чи інші контейнери стартують; якщо ні — перестаньте копатися в окремих конфігах і виправляйте підложку хоста.
- Пошукайте недавні зміни: оновлення ядра, оновлення Proxmox, увімкнений nesting, змінений режим AppArmor, режим cgroups, нові точки монтування.
Рішуче правило: якщо це проблема на рівні вузла з монтуванням/контролерами cgroups, ви не «фіксуєте» це по-окремому для кожного контейнера. Виправляйте вузол або евакуюйте робочі навантаження. Усе інше — марна праця з приємнішим звуком клавіатури.
Як читати помилки cgroups і AppArmor без здогадок
Де записано істину
UI Proxmox показує узагальнену помилку. Діюча інформація зазвичай в одному з цих місць:
- Журнал завдання (те, що бачить UI, але іноді обрізане)
journalctlна хості (systemd та повідомлення ядра)/var/log/syslogна Debian-подібних хостах- Кільце ядра (
dmesg) - Власні логи LXC (вищий рівень деталізації через debug start)
- Аудит-логи для відмов AppArmor (часто видно через journal)
Читання помилок cgroups: ключові слова важливі
Помилки cgroups зазвичай групуються так:
- Проблеми монтування: LXC не може змонтувати або доступитися до
/sys/fs/cgroup(поширено при невідповідності очікувань v2 vs конфіг хоста). - Доступність контролерів: потрібні контролери (наприклад,
memoryабоpids) недоступні або не делеговані належним чином. - Проблеми з дозволами/власністю: проблеми делегації при використанні systemd-керованих cgroups та unprivileged контейнерів.
- Плутанина гібридної ієрархії: невідповідність v1 vs v2 vs hybrid, іноді після оновлень.
Коли ви бачите помилку на кшталт Failed to mount "cgroup" або cgroup2: No such file or directory, не інтерпретуйте її як «контейнер зламався». Витлумачте так: макет файлової системи cgroup на хості не відповідає тому, що LXC очікує для конфігурації цього контейнера та ініт-системи хоста.
Читання помилок AppArmor: рядок відмови — це міні-доповідь про інцидент
Рядок відмови AppArmor містить: часову мітку, профіль, операцію, запитуваний ресурс і іноді «name» (шлях). Зазвичай цього достатньо, щоб вирішити, чи слід вам:
- Змінити можливості контейнера (nesting, keyctl, fuse, типи монтувань)
- Виправити права/маркування файлу на хості
- Перевести профіль AppArmor у режим complain тимчасово, поки збираєте докази
- Припинити робити ризикову операцію (мій улюблений варіант у продакшені)
AppArmor не «випадково лампочка контейнери». Воно робить саме те, що ви попросили: блокує дії, яких немає в політиці. Дратує те, що ми часто не усвідомлювали, що ми це попросили.
Практичні завдання: команди, очікуваний вивід, і рішення
Це ті ходи, якими я фактично користуюся під тиском. Кожен із них включає рішення, яке слід прийняти на основі виводу. Виконуйте їх по порядку, якщо загубилися; вибирайте вибірково, якщо вже маєте підказку.
Завдання 1: Витягніть точну помилку з виводу завдання Proxmox (CLI)
cr0x@server:~$ pct start 101
starting container 101
lxc-start: 101: conf.c: run_buffer: 312 Script exited with status 1
lxc-start: 101: start.c: __lxc_start: 2107 Failed to initialize container "101"
TASK ERROR: startup for container '101' failed
Що це означає: Сам LXC зазнав невдачі; Proxmox просто звітує. Повідомлення загальне; потрібні глибші логи.
Рішення: Негайно переходьте до журналів systemd та доказів AppArmor/cgroups (наступні завдання). Не редагуйте випадково конфіг.
Завдання 2: Отримайте конфіг контейнера і перевірте «гострі місця»
cr0x@server:~$ cat /etc/pve/lxc/101.conf
arch: amd64
cores: 2
hostname: app-101
memory: 2048
net0: name=eth0,bridge=vmbr0,firewall=1,ip=dhcp,type=veth
ostype: debian
rootfs: local-lvm:vm-101-disk-0,size=8G
unprivileged: 1
features: nesting=1,keyctl=1
mp0: /srv/shared,mp=/mnt/shared,backup=0
Що це означає: Контейнер unprivileged, з увімкненим nesting+keyctl і хостовим bind-монтом (mp0). Ці три — поширені джерела помилок AppArmor та проблем з дозволами.
Рішення: Якщо ви нещодавно увімкнули nesting або додали mp0, підозрюйте AppArmor або проблеми власності/ID-мепінгу першими.
Завдання 3: Перевірте режим cgroup на хості (v1 чи v2)
cr0x@server:~$ stat -fc %T /sys/fs/cgroup
cgroup2fs
Що це означає: Хост використовує єдину ієрархію cgroups v2.
Рішення: Якщо контейнер або LXC очікують старіший v1-монтаж (рідко на сучасному PVE, але можливо з давніми конфігами), плануйте вирівняти. Також перевірте доступність контролерів (наступне завдання).
Завдання 4: Перевірте, що потрібні cgroup-контролери доступні та увімкнені
cr0x@server:~$ cat /sys/fs/cgroup/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma
Що це означає: Це контролери, які підтримує ядро і які доступні у кореневому cgroup. Якщо memory або pids відсутні, контейнери з лімітами пам’яті/pids можуть зазнавати невдач або некоректної роботи.
Рішення: Відсутні контролери — це проблема конфігурації вузла/ядра. Перестаньте дебажити по-контейнерно й виправляйте параметри завантаження/конфіг хоста.
Завдання 5: Перевірте, чи systemd делегував контролери належним чином
cr0x@server:~$ systemctl show -p DefaultControllers
DefaultControllers=cpu cpuacct io memory pids
Що це означає: На системах з systemd делегація і конфіг контролерів впливає на здатність LXC створювати/керувати cgroups. Новіші systemd працюють із v2 по-іншому, ніж старі версії.
Рішення: Якщо після оновлення ви бачите дивні речі (відсутні очікувані контролери), підозрюйте взаємодію systemd+cgroups. Перевірте нещодавні зміни пакетів і розгляньте контрольоване перезавантаження після виправлення параметрів завантаження.
Завдання 6: Перегляньте монтування на предмет дивностей cgroup (гібридні макети, відсутній монтаж)
cr0x@server:~$ mount | grep -E 'cgroup|cgroup2'
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
Що це означає: Чисте v2-монтування. Якщо бачите кілька v1-монтажів поряд із cgroup2, можливо у вас гібридний режим.
Рішення: Якщо LXC скаржиться на монтування cgroup2, а ви в v1 або гібриді, вирівняйте хост (параметри завантаження) з тим, що очікує ваша версія PVE.
Завдання 7: Витягніть відповідний зріз журналу для спроби старту контейнера
cr0x@server:~$ journalctl -u pve-container@101.service -n 200 --no-pager
Dec 26 10:18:01 pve1 systemd[1]: Starting PVE LXC Container: 101...
Dec 26 10:18:01 pve1 lxc-start[18277]: lxc-start: 101: cgfsng.c: cgroup_init: 846 Failed to create cgroup /lxc/101
Dec 26 10:18:01 pve1 lxc-start[18277]: lxc-start: 101: cgfsng.c: cgroup_init: 850 No such file or directory
Dec 26 10:18:01 pve1 systemd[1]: pve-container@101.service: Main process exited, code=exited, status=1/FAILURE
Dec 26 10:18:01 pve1 systemd[1]: pve-container@101.service: Failed with result 'exit-code'.
Що це означає: LXC не зміг створити шлях cgroup, який він хотів. Зазвичай це пов’язано з макетом файлової системи cgroup, делегацією або застарілою/неправильною конфігурацією кореневого cgroup.
Рішення: Залишайтеся в «lane» cgroups. Шукайте відсутні дозволи на /sys/fs/cgroup, проблеми делегації або зламане дерево systemd cgroup.
Завдання 8: Перевірте відмови AppArmor навколо часу збою
cr0x@server:~$ journalctl -k -g 'apparmor="DENIED"' -n 50 --no-pager
Dec 26 10:18:01 pve1 kernel: audit: type=1400 audit(1735208281.214:915): apparmor="DENIED" operation="mount" profile="lxc-container-default-cgns" name="/sys/kernel/security" pid=18290 comm="(mount)" flags="rw, nosuid, nodev, noexec"
Що це означає: Контейнер намагався виконати операцію mount, яку заблокував застосований профіль. Це може перешкодити systemd всередині контейнера завантажитися.
Рішення: Вирішіть, чи потрібен цей монтування. Якщо він походить від nesting/очікувань systemd, виправте функції контейнера або налаштуйте політику контрольовано. Не відключайте AppArmor глобально без причин.
Завдання 9: Переконайтеся, що AppArmor увімкнений і профілі завантажені
cr0x@server:~$ aa-status
apparmor module is loaded.
60 profiles are loaded.
58 profiles are in enforce mode.
2 profiles are in complain mode.
0 processes are unconfined but have a profile defined.
Що це означає: AppArmor активний. Якщо там написано «module is not loaded», то AppArmor не є вашим блокером — рядків «DENIED» не буде.
Рішення: Якщо існують відмови й AppArmor в режимі enforce, ставте ці відмови в ранг першопричин, а не фоновий шум.
Завдання 10: Запустіть LXC з debug-логуванням (суржикально, шумно, корисно)
cr0x@server:~$ lxc-start -n 101 -F -l DEBUG -o /tmp/lxc-101.debug.log
lxc-start: 101: start.c: lxc_init: 846 Container "101" initialized
lxc-start: 101: cgfsng.c: cgroup_init: 846 Failed to create cgroup /lxc/101
lxc-start: 101: start.c: __lxc_start: 2107 Failed to initialize container "101"
Що це означає: Підтверджує, що відмова відбувається під час ініціалізації cgroup, а не при монтуванні rootfs чи налаштуванні мережі.
Рішення: Припиніть переслідувати AppArmor, якщо логи debug завершуються до застосування профілю безпеки. Спочатку виправляйте cgroups.
Завдання 11: Підтвердіть, що rootfs-том контейнера існує й доступний
cr0x@server:~$ pvesm status
Name Type Status Total Used Available
local dir active 100.00G 42.10G 57.90G
local-lvm lvmthin active 60.00G 25.00G 35.00G
Що це означає: Бекенд збереження активний. Якщо він неактивний, ви отримаєте вводні «permission denied» або помилки монтування, що нагадуватимуть AppArmor.
Рішення: Якщо сховище впало — виправляйте його насамперед. Контейнери не можуть змонтувати вигадані диски.
Завдання 12: Підтвердіть, що LV rootfs фактично існує (приклад LVM-thin)
cr0x@server:~$ lvs -a -o lv_name,vg_name,lv_size,lv_attr | grep vm-101-disk-0
vm-101-disk-0 pve 8.00g Vwi-a-tz--
Що це означає: Том існує. Якщо ні — контейнер не запуститься.
Рішення: Відсутній том означає проблему відновлення/бекапа або помилкове посилання в 101.conf. Виправте конфіг або відтворіть том.
Завдання 13: Перевірте джерельні шляхи bind-монту і дозволи на хості
cr0x@server:~$ ls -ld /srv/shared
drwxr-x--- 2 root root 4096 Dec 25 09:12 /srv/shared
Що це означає: Для unprivileged контейнера bind-монт, який належить root:root з суворими дозволами, може зламати запуск, якщо LXC намагається встановити власника монту чи init контейнера очікує доступ.
Рішення: Або відкоригуйте власність під маповані ID контейнера (див. завдання про ID map), або змонтуйте тільки для читання, або припиніть bind-монтувати хост-шляхи в unprivileged контейнери без плану власності.
Завдання 14: Перевірте ID-мапінг для unprivileged контейнера
cr0x@server:~$ grep -E '^(root|lxc)' /etc/subuid /etc/subgid
/etc/subuid:root:100000:65536
/etc/subgid:root:100000:65536
Що це означає: Хост має підлеглі діапазони UID/GID для root, які потрібні unprivileged контейнерам. Якщо їх бракує, unprivileged контейнери часто падають з невизначеними помилками доступу.
Рішення: Якщо діапазони відсутні або занадто малі для вашого використання idmap, виправте /etc/subuid і /etc/subgid, потім перезапустіть відповідні сервіси або виконайте перезавантаження.
Завдання 15: Підтвердіть, що ядро підтримує потрібні неймспейси і функції cgroup
cr0x@server:~$ zgrep -E 'CONFIG_CGROUPS=|CONFIG_USER_NS=|CONFIG_CGROUP_BPF=|CONFIG_CGROUP_FREEZER=' /boot/config-$(uname -r)
CONFIG_CGROUPS=y
CONFIG_USER_NS=y
CONFIG_CGROUP_BPF=y
# CONFIG_CGROUP_FREEZER is not set
Що це означає: Базові функції присутні. Відсутність CONFIG_USER_NS вбила б unprivileged контейнери одразу. Відсутність freezer зазвичай не фатальна на сучасних системах, але може впливати на деякі поведінки/інструменти.
Рішення: Якщо ключові параметри відсутні — ви на невідповідному ядрі або в кастомній збірці. Використовуйте підтримувану серію ядер Proxmox і не імпровізуйте.
Завдання 16: Перевірте, чи systemd контейнера падає через монтування cgroups
cr0x@server:~$ pct start 101 --debug
lxc-start: 101: conf.c: run_buffer: 312 Script exited with status 1
lxc-start: 101: conf.c: run_buffer: 313 Script exited with status 1: mount: /sys/fs/cgroup: permission denied
Що це означає: Відмова відбувається під час монтування cgroups всередині контейнера. Це може бути AppArmor denial, відсутня делегація або невідповідність конфігу LXC щодо версії cgroup.
Рішення: Скоординуйте це з відмовами AppArmor (Завдання 8). Якщо відмов немає, перевірте делегацію/дозволи cgroup і функції контейнера.
Режими відмов cgroups, які зупиняють контейнери
1) Невідповідність єдиної ієрархії cgroups v2 з очікуваннями
cgroups v2 — сучасний підхід: одна ієрархія, послідовна семантика, покращена делегація. Але реальність заплутана: старі образи контейнерів, старі версії LXC або отримані конфіги можуть очікувати v1-монтажі (як окремий /sys/fs/cgroup/memory).
На Proxmox хост зазвичай керується systemd і все більше переходить на v2. Якщо послідовність запуску контейнера намагається змонтувати контролери v1 або очікує v1-шляхи, ви побачите помилки монтування або «No such file or directory» під час ініціалізації cgroup.
Що робити: Вирівняйте. Перевага — використовувати підтримуване ядро і стек LXC від Proxmox як єдине ціле. Уникайте примусового включення спадкового режиму cgroup, якщо у вас немає чіткого суміснісного вимогу і плану відкату.
2) Контролери є, але не делеговані (проблема межі systemd)
Навіть коли контролери присутні, процес, що створює контейнер, повинен мати дозвіл на створення під-cgroup і увімкнення контролерів. У v2 увімкнення контролерів є явним і може бути заблоковане конфігурацією батьківського cgroup.
Симптоми виглядають як «Failed to create cgroup» або «Operation not permitted», іноді лише для деяких контейнерів (тих, що встановлюють ліміти, або тих, що запускаються через різні юніти).
Що робити: Розглядайте це як проблему конфігурації вузла. Перевірте делегацію systemd і дерево cgroup. Якщо нещодавно змінювали спосіб запуску контейнерів (кастомні юніти, обгортки), відкотіть це в першу чергу.
3) Read-only або зламаний /sys/fs/cgroup всередині контейнера
Деякі образи контейнерів розраховані на Docker-стиль і вважають, що можуть керувати cgroups або монтувати певні псевдо-файлові системи. LXC контейнери часто працюють у жорсткіших межах. Якщо systemd всередині контейнера хоче змонтувати і йому відмовлено, він швидко виходить. Контейнер «запускається» і «зупиняється» миттєво, що виглядає як проблема Proxmox.
Що робити: Визначте, чи має systemd усередині контейнера бути PID 1 взагалі. Якщо повинен бути — коректно відрегулюйте фічі контейнера. Інакше, запускайте простіший init для цього навантаження.
4) Регресії ядра та «корисні» параметри завантаження
Іноді вузол оновлюють і поведінка cgroups змінюється. Іноді хтось додав параметри завантаження для іншої проблеми і випадково зламав контейнери. cgroups чутливі до cmdline ядра і налаштувань systemd за замовчуванням.
Моя операційна позиція: тримайте вузли уніфікованими. Якщо потрібно «пофігачити» один вузол — ви створюєте майбутній інцидент із відкладеним детонатором.
Режими відмов AppArmor: відмови, профілі та пастка «вчора працювало»
Основи AppArmor, які вам справді потрібні
AppArmor — це обов’язковий контроль доступу за профілями: він обмежує, що процес може робити, навіть якщо Unix-права кажуть «дозволено». LXC на Proxmox використовує профілі AppArmor для обмеження контейнерів. Коли щось в контейнері намагається виконати заблоковану операцію (монтування, доступ до інтерфейсів ядра, використання FUSE тощо), ви отримаєте відмову в ядрі audit-лозі.
1) Nesting увімкнений — поведінка, яку AppArmor блокує за замовчуванням
Nesting — це функція, яка робить людей сміливими: «Давайте запустимо Docker усередині цього контейнера». Вона розширює набір syscall і поведінку монтувань всередині контейнера, що конфліктує з консервативними політиками. Раптом контейнер хоче змонтувати overlayfs, доступитися до /sys/kernel/security, використовувати keyrings або маніпулювати cgroups так, як AppArmor каже «ні».
Що робити: Якщо вам потрібні Docker/Kubernetes всередині чогось — використовуйте VM, якщо серйозно. Nested контейнери гарні, поки не стануть вашим хобі з реагування на інциденти.
2) Bind-монти та хост-шляхи: подвійні проблеми політики і власності
Bind-монти (mp0, mp1 тощо) — найшвидший спосіб зробити LXC корисним, і одночасно найшвидший спосіб його зламати. AppArmor може обмежувати певні операції монтування, а unprivileged контейнери можуть не мати доступу до змонтованих файлів через UID/GID мапування. Ці відмови можуть проявлятися або як відмови AppArmor, або як загальні помилки доступу.
Що робити: Ставтеся до bind-монтів як до контракту інтерфейсу. Вирішіть питання власності та шаблони доступу заздалегідь. Документуйте це в конфіг контейнера. Не «просто змонтуйте /srv» і надійтеся.
3) «Вимкнути AppArmor» — неправильний рефлекс
Так, відключення AppArmor може змусити контейнер стартувати. Воно також може драматично погіршити безпеку хоста. Ваша ціль — не «зелений статус», а правильний контроль.
Кращий підхід: Використовуйте відмови AppArmor, щоб ідентифікувати точну операцію, яка блокується, а потім вирішіть, чи ця операція необхідна й безпечна. Інколи правильне виправлення — це прибрати функцію, що спричинила поведінку. Іноді — змінити спосіб запуску навантаження. Інколи — коректно відрегулювати профіль, обережно, мінімально і послідовно по всіх вузлах.
Короткий жарт #2: Вимкнути AppArmor, щоб виправити помилку старту — все одно, що зняти пожарний датчик, бо він голосно сигналить.
Три корпоративні міні-історії з реальних випадків
Міні-історія 1: Інцидент через неправильне припущення
Команда мігрувала сервіс пакетної обробки з ВМ в LXC, щоб «зекономити накладні витрати». Припущення було просте: контейнери — це просто легкі ВМ. Вони помилилися там, де це важливо — керуючі площини ядра спільні, а systemd всередині контейнера вибагливий щодо cgroups.
Після планового оновлення ОС на вузлі Proxmox підмножина контейнерів перестала запускатися. Логи казали «Failed to create cgroup» і «permission denied» на /sys/fs/cgroup. Команда фокусувалась на контейнерах: перебудовували образи, відкатували зміни додатків, навіть відновлювали з бекапу. Нічого не допомагало. Вузол відмовлявся надавати макет cgroup, якого очікували контейнери.
Справжня проблема — невідповідність режиму cgroups під час завантаження, внесена під час техобслуговування. Вузол зійшов у гібридний режим, який не відповідав решті кластера. Більшість контейнерів стартували, бо не встановлювали жорсткі обмеження ресурсів; пакетні воркери — ні. Припущення «якщо один контейнер стартує, вузол нормальний» виявилося хибним. Деякі робочі навантаження чутливі до cgroups більше за інші.
Виправлення було нудним: вирівняли параметри ядра і поведінку systemd з рештою флоту, потім перезавантажили у вікні техпідтримки. Урок закарбувався: у світі контейнерів «працює для одного» — це не доказ коректності, а доказ неповного покриття.
Міні-історія 2: Оптимізація, яка повернулась бумерангом
Платформена команда хотіла швидшого провізионінгу. Вони стандартизувалися на unprivileged контейнерах (добре) і додали спільний хост-каталог, змонтований у десятки LXC для кешування артефактів (сумнівно). Каталог кешу належав root на хості, але в кожному контейнері мав бути доступним для запису користувачем програми.
Щоб «вирішити» права, хтось масово увімкнув nesting і keyctl, потім налаштував кілька контейнерів запускати допоміжні скрипти на старті, щоб chown директорії. Це працювало в тесті. У продакшні AppArmor почав блокувати операції, пов’язані з монтуванням, викликані systemd-юнітами та скриптами. Контейнери гойдалися: старт, спроба монтування, відмова, вихід. Моніторинг бачив їх як «нестабільні», а не «заблоковані політикою».
Повернення назад полягало не лише у відмовах. Більша проблема була в операційній моделі: десятки контейнерів залежали від спільного хост-шляху з імпліцитним транслюванням власності і рантаймовою мутацією. Кожна зміна прав була подією для всього кластера. Дебаг перетворився на археологію.
Зрештою вони перестали намагатися зробити bind-монти сховищем даних. Кеш-пакети перемістили в справжній сервіс, а bind-монти лишили для простих даних тільки для читання. Старт став знову нудним. Це і є перемога: нудьга — це фіча.
Міні-історія 3: Нудна, але правильна практика, що врятувала
Група інфраструктури експлуатувала вузли Proxmox як «скотину», а не як «улюбленців» — принаймні наскільки це можливо у віртуалізації. У них були дві звички, що здавалися нудними: вони зберігали «відомий хороший маленький контейнер» як шаблон для smoke-тестів і фіксували зміну стану вузла (версія ядра, прапори завантаження, статус AppArmor, режим cgroup) після кожної зміни.
Одного ранку, після хвилі оновлень безпеки, вузол почав відкидати нові запуски контейнерів з помилками cgroup. Замість того, щоб ритися в конфігах продуктивного навантаження, черговий запустив маленький smoke-test контейнер. Він упав так само. Ця одна точка даних скоротила простір пошуку: це не було питанням образів додатків або точок монтування; це підложка вузла.
Вони порівняли запис drift вузла з робочим піром. Єдина різниця — тонка зміна параметра завантаження, введена під час попереднього розслідування і залишена як бананова шкірка. Вони її відкотили, перезавантажили вузол у контрольованому режимі, і робочі навантаження відновилися.
Ніяких героїв. Ніяких «тимчасових» хаків, що стали постійними. Правильна практика не була гламурною — вона була послідовністю і повторюваним тестом. У операціях гламур — це часто знак, що ви збираєтеся зробити те, про що потім пошкодуєте.
Типові помилки: симптом → корінь проблеми → виправлення
1) Симптом: «Failed to create cgroup /lxc/101»
Корінь проблеми: Невідповідність макету файлової системи cgroup або делегації (часто увімкнення контролера v2 або відсутній монтаж cgroup).
Виправлення: Перевірте, що /sys/fs/cgroup змонтований правильно, контролери присутні, а делегація systemd адекватна. Вирівняйте конфігурацію вузла; не ремапте по-контейнерно.
2) Симптом: «mount: /sys/fs/cgroup: permission denied» під час старту
Корінь проблеми: Відмова AppArmor на монтування, або контейнер намагається виконати операції монтування, не дозволені профілем, іноді спричинено nesting.
Виправлення: Перевірте рядки аудиту ядра. Приберіть або обмежте nesting/keyctl, якщо не потрібні. Якщо потрібні — використовуйте VM або ретельно налаштуйте профіль.
3) Симптом: Контейнер стартує, а потім негайно зупиняється; UI не дає зрозумілої помилки
Корінь проблеми: PID 1 всередині контейнера (часто systemd) завершується рано через cgroups або політику безпеки.
Виправлення: Використайте debug-старт і журнали. Шукайте помилки systemd щодо cgroups. Вирішіть, чи systemd доречний; якщо так — виправте обмеження cgroup/AppArmor.
4) Симптом: Працює як privileged, не працює як unprivileged
Корінь проблеми: Проблеми ID-мепування / subuid / subgid, невідповідність власності bind-монту або хост-шляхи недоступні під змапованими ID.
Виправлення: Перевірте /etc/subuid//etc/subgid. Виправте власність шляхів монтування відповідно до змапованого діапазону. Уникайте використання privileged як «фіксу» — це інша модель безпеки.
5) Симптом: Тільки контейнери з лімітами ресурсів падають
Корінь проблеми: Відсутні контролери (memory/pids) або проблеми увімкнення контролерів у v2.
Виправлення: Підтвердіть наявність контролерів і делегацію systemd. Виправте конфіг вузла; тримайте вузли уніфікованими.
6) Симптом: Відмови AppArmor згадують /sys/kernel/security або операції монтування
Корінь проблеми: Контейнер намагається доступитися до інтерфейсів безпеки ядра або змонтувати чутливі псевдо-файлові системи — часто через nesting або спеціальні навантаження.
Виправлення: Переоцініть, чому навантаження це потребує. Краще — VM для вкладених рантаймів. Якщо потрібно, звузьте зміни політики і тестуйте через оновлення.
7) Симптом: «No such file or directory» для шляхів cgroup
Корінь проблеми: Очікування шляхів v1 на системі v2, або застаріла конфігурація LXC, що посилається на старі точки монтування.
Виправлення: Приберіть спадкові хаки монтування cgroup з конфігу контейнера. Переконайтеся, що хост і версія LXC сумісні й уніфіковані по кластеру.
8) Симптом: Додавання mp0 призвело до того, що контейнер перестав стартувати
Корінь проблеми: Відсутній хост-шлях, неправильні дозволи або обмеження монтування AppArmor.
Виправлення: Переконайтеся, що джерельний шлях на хості існує і дозволи відповідають мапінгу unprivileged. Спробуйте тимчасово видалити монтування, щоб підтвердити причинно-наслідковий зв’язок; потім переробіть стратегію доступу.
Контрольні списки / покроковий план
Контрольний список A: П’ятихвилинна триаж (один контейнер не стартує)
- Запустіть
pct start <id>з CLI, щоб отримати сирий помилковий вивід. - Огляньте
/etc/pve/lxc/<id>.confна предмет:unprivileged,features,mp*, кастомніlxc.*рядки. - Перевірте
journalctl -u pve-container@<id>.service -n 200для першої конкретної відмови. - Пошукайте відмови AppArmor у межах часової мітки:
journalctl -k -g 'apparmor="DENIED"'. - Якщо помилка cgroup-ша, підтвердіть режим cgroup і контролери (
stat -fc %T /sys/fs/cgroup,cat /sys/fs/cgroup/cgroup.controllers). - Тимчасово видаліть найновішу ризикову зміну (новий bind-монт, nesting), щоб підтвердити причинність.
Контрольний список B: Відмова рівня вузла (кілька контейнерів не стартують)
- Запустіть відомий робочий маленький контейнер. Якщо він теж падає — вважайте проблему вузловою.
- Перевірте монтування cgroup і режим:
mount | grep cgroupіstat -fc %T /sys/fs/cgroup. - Перевірте набір контролерів:
cat /sys/fs/cgroup/cgroup.controllers. - Перегляньте очевидний шум ядра/аудиту:
dmesg -T | tail -n 200. - Переконайтеся, що модуль AppArmor завантажений і у режимі enforce:
aa-status. - Порівняйте пакети/версію ядра вузла з робочим вузлом (той же кластер):
uname -r,pveversion -v. - Відкотіть drift (прапори завантаження, кастомні юніти) перед тим, як робити «креативні фікси».
- Якщо не вдається швидко відновити роботу — евакуюйте навантаження і ремонтуйте у вікні техобслуговування. Продакшен любить рішучі кроки.
Контрольний список C: Коли потрібно щось змінити (дисципліна безпечних змін)
- Робіть одну зміну за раз: видаліть nesting, приберіть монтування, відкотіть прапор завантаження — ніколи не все одразу.
- Збирайте докази «до/після»: зріз журналу, рядки відмов, список контролерів cgroup.
- Тестуйте з двома контейнерами: один «простий» і один «складний» (ліміти ресурсів + bind-монт + systemd).
- Розповсюджуйте виправлення послідовно на всі вузли; непослідовність генерує інциденти.
Цікаві факти та історичний контекст (те, що забувають)
- cgroups з’явилися наприкінці 2000-х як механізм ядра для обліку та обмеження ресурсів на рівні груп процесів — задовго до того, як «контейнери» стали маркетинговим терміном.
- LXC передував популярності Docker; це один з давніших підходів «системних контейнерів», ближчий до легких ВМ, ніж до контейнерів для додатків.
- cgroups v2 — це не просто v1 з іншим ім’ям; він змінює семантику (єдина ієрархія та явне увімкнення контролерів), тому невідповідності болючі.
- systemd став центральним актором у менеджменті cgroups в Linux; навіть якщо ви не просили цього, він тепер володіє більшою частиною дерева cgroup на більшості дистрибутивів.
- AppArmor базується на шляхах (на відміну від SELinux з маркуванням), що робить відмови більш читабельними, але політики можуть бути крихкими при зміні шляхів.
- Unprivileged контейнери покладаються на user namespaces; хост змінює root контейнера на непривілейований UID-діапазон, що зручно, поки ви не bind-монтували хост-шляхи без плану власності.
- Функція «nesting» — це компроміс: вона дає можливість навантаженням поведінки, схожої на ядро, але знижує ізоляцію, яку ви, можливо, очікували від LXC.
- Багато відмов при запуску — це насправді збої PID 1: systemd всередині контейнера виходить, бо не може змонтувати cgroups або отримує відмову, а LXC звітує «контейнер зазнав невдачі».
- Існували гібридні режими cgroups, але вони операційно незручні; коли флот дрейфує між режимами, ви отримуєте помилки «працює на вузлі A».
FAQ
1) Як швидко визначити, cgroups це чи AppArmor?
Пошукайте в журналі ядра відмови: journalctl -k -g 'apparmor="DENIED"'. Якщо є релевантні записи в час збою — це політика. Якщо логи показують помилки створення/монтування cgroup без відмов — це підложка (cgroups/systemd).
2) UI показує «startup failed», але контейнер працював вчора. Що змінилося?
Зазвичай: оновлення ядра, оновлення systemd, оновлення Proxmox або зміна конфігу контейнера (nesting, bind-монти, ліміти ресурсів). Почніть з порівняння pveversion -v і uname -r з робочим вузлом.
3) Чи прийнятно переключити контейнер на privileged як виправлення?
Це інструмент діагностики, а не фікс. Якщо privileged стартує, а unprivileged — ні, ви дізналися, що ймовірно проблема з ID-мепінгом, bind-власністю або обмеженнями user namespace. Виправте це і поверніться до unprivileged, якщо у вас немає формального ризикового дозволу.
4) Чи потрібен nesting для запуску Docker всередині LXC?
Часто так, і це проблема. Якщо ви серйозно ставитеся до надійного запуску Docker/Kubernetes, віддавайте перевагу VM. LXC nesting можна налаштувати, але це операційно крихке і часто конфліктує з AppArmor та очікуваннями cgroups.
5) Що зазвичай означає «Failed to mount /sys/fs/cgroup» всередині контейнера?
Або контейнер намагається змонтувати cgroups, але йому не дозволяють (AppArmor), або конфіг/делегація cgroup хоста не підтримує те, що очікує init контейнера. Спочатку зіставте з відмовами AppArmor, потім перевірте режим cgroup і контролери хоста.
6) Чому після оновлення вузла тільки деякі контейнери падають?
Бо не всі контейнери використовують однакові інтерфейси ядра. Контейнери з лімітами пам’яті/pids, systemd, nesting або складними монтуваннями активніше зачіпають cgroups і AppArmor.
7) Чи можна ігнорувати відмови AppArmor, якщо контейнер все ж запускається?
Ні. Відмова, «яку ви сьогодні «підіймете», може перетворитися на жорстку помилку після оновлення пакету. Розглядайте відмови як технічний борг з відсотками.
8) Мій bind-монт працює в privileged контейнері, але не в unprivileged. Чому?
Unprivileged контейнери мапують UIDs/GIDs; «root» всередині не є root на хості. Власність і дозволи хост-шляху мають відповідати змапованому діапазону, або контейнер не зможе отримати доступ. Виправте власність або переробіть стратегію монтування.
9) Чи варто вимкнути AppArmor, щоб пройти інцидент?
Лише як крайній захід і лише якщо ви розумієте площу ураження. Краще прибрати тригерну функцію (наприклад nesting) або виправити конкретну заблоковану поведінку. Вимкнення AppArmor — це зміна політики, а не «рестарт».
10) Який найбезпечніший спосіб тестувати зміни?
Використовуйте маленький відомий хороший шаблон контейнера, внесіть одну зміну, протестуйте старт/стоп, потім протестуйте «складний» контейнер (systemd + ліміти + монтування). Послідовність по вузлах важливіша за героїчний дебаг на одному сервері.
Висновок: практичні наступні кроки
Коли LXC не запускається на Proxmox і логи згадують cgroups або AppArmor, система каже вам точно, що впало — просто не в дружньому порядку. Ваше завдання — класифікувати відмову: підложка (cgroups/systemd/ядро) проти політики (AppArmor) проти власності/сховища (bind-монти, idmap).
Наступні кроки, які я б реально зробив:
- Зберіть докази: вивід
pct start,journalctl -u pve-container@IDі будь-які відмови AppArmor. - Якщо кілька контейнерів падають — припиніть дебаг окремих конфігів і негайно перевірте режим/контролери cgroup хоста.
- Якщо AppArmor блокує монтування або доступ до безпеки ядра — тимчасово приберіть тригерну функцію (nesting/bind-монт), потім обережно поверніть або перемістіть навантаження в VM.
- Запишіть, що змінилося (ядро, прапори завантаження, функції контейнера). Дрейф — це те, як «один дивний вузол» стає повторюваним простоєм.
Виправляйте реальний шар. Тримайте вузли уніфікованими. І коли вас тягне вимкнути систему безпеки, щоб «запустити», зупиніться і перечитайте рядок відмови ще раз. Зазвичай він правий.