Ви оновлюєте хост до Debian 13, розгортаєте контейнер наново, і раптом ваш «абсолютно нормальний» bind-монт перетворюється на цеглину:
Permission denied при читанні, записі, mkdir, іноді навіть при ls. Логи не допомагають. Колеги радять
chmod -R 777, ніби це вітамін.
Це зазвичай не проблема «прав». Це проблема ідентичності. Bind-монти не переводять власника; вони чесно показують
файлову систему хоста для процесу всередині цільового простору імен. Якщо відображення UID/GID не збігається, ви отримуєте відмову—цілком правильно.
Чисте вирішення — узгодити відображення UID/GID (або свідомо транслювати його через idmapped mounts), а не починати chmod-пожежу в продакшені.
Швидка діагностика — покроковий план
Коли сторінка будить вас о 02:13, вам не потрібна філософія. Потрібна коротка, надійна послідовність, що швидко знайде вузьке місце.
Ось найшвидший шлях до істини, який я знайшов на хостах Debian 13 з контейнерами та/або systemd-монтами.
1) Підтвердьте, що саме ламається: права ядра, відображення просторів імен чи LSM?
- По-перше: відтворіть проблему на хості та всередині контейнера. Якщо на хості працює, а в контейнері ні — це майже завжди відображення або LSM.
- По-друге: перевірте числові ID власності на шляху монтування (
stat), а не імена. Імена брешуть; числа — ні. - По-третє: перевірте, чи ви rootless, чи користуєтеся userns-remap або idmapped mounts. Відображення визначає, чи UID 1000 означає «вас» чи «когось іншого».
- По-четверте: швидко виключіть AppArmor/SELinux як фактор (навіть якщо ви «не використовуєте» їх).
2) Визначте, до якого класу виправлень належите
- Той самий хост, той самий контейнер: узгодьте UID/GID всередині контейнера з файлами хоста або навмисно мапте ID за допомогою idmapped mounts.
- Багато хостів / NFS: стандартизуйте числові UID/GID через каталог/SSSD або призначення UID явно; не «ліпіть» виправлення вручну на кожному вузлі.
- Kubernetes hostPath: ставтесь до нього як до «файлової системи ноди». Використовуйте послідовні ID, fsGroup або контрольований init-контейнер, а не випадкові chmod.
- Rootless: перевірте
/etc/subuidта/etc/subgid, і впевніться, що зсунутий діапазон покриває потрібні вам ID.
3) Підтвердіть однією остаточною перевіркою
Створіть файл зсередини контейнера і перевірте його UID/GID на хості. Потім навпаки: створіть на хості і спробуйте записати з контейнера.
Якщо ID не збігаються з очікуванням — перестаньте крутити біт режиму. Ви відлагоджуєте ідентичність, а не права доступу.
Що насправді роблять bind-монти (і чого вони ніколи не зроблять)
Bind-монт — це вкрай буквальний механізм. Він бере каталог (або файл) з однієї частини дерева файлової системи і робить його видимим в іншому місці.
Ось і все. Ніякої трансляції власника. Ніякої медіації прав, крім того, що вже застосовує ядро. Ніякого «милого перейменування» користувачів і груп
лише тому, що ви перемістили шлях під /var/lib/containers.
Така буквальність робить bind-монти швидкими і передбачуваними. Але саме тому вони стають джерелом інцидентів, коли змішуєте:
rootless контейнери, user namespaces, мультихостове сховище і «допоміжні» налаштування оркестраторів.
Ментальна модель, що запобігає поганим виправленням
Ядро вирішує доступ, використовуючи числові ID (UID/GID), бітові режими, ACL і політику LSM. Процес всередині контейнера залишається процесом.
Якщо цей процес має UID 1000 у своєму просторі імен, ядро може трактувати його як UID 100000 зовні, залежно від відображення. Якщо каталог
на хості належить UID 1000, а процес фактично UID 100000, він не є власником. Запис не відбудеться. Правильно.
Якщо ви «виправляєте» це через chmod -R 777, ви не вирішили невідповідність ідентичності. Ви просто відмовилися від безпеки і ускладнили
майбутнє налагодження. Також аудитори почнуть вживати слова на кшталт «системний».
Жарт №1: «chmod 777» — це як вимкнути пожежну сигналізацію, бо вона гучна. Ви спатимете краще доти, поки не станеться пожежа.
Цікаві факти та історія, що пояснюють сучасний біль
- UID та GID — це насамперед числа. Імена користувачів — це лише зручний шар зверху (традиційно через
/etc/passwdабо NSS). - Bind-монти з’явилися задовго до контейнерів. Це був файловий трюк для chroot та зміни розмітки; контейнери успадкували їх як примітив для томів.
- User namespaces — відносно «нове» в Linux. Вони існували роками до широкого використання, і деякі дистрибутиви увімкнули їх обережно через історію безпеки.
- Rootless контейнери зробили невідповідність видимою. У режимі з root’ом UID 0 часто «прориває» проблеми (поки не натрапить на NFS root-squash або правила LSM).
- NFS завжди карав недбале управління ідентичністю. Для класичного NFS числові UID/GID мають збігатися на клієнтах або ви отримаєте «працює на моєму вузлі» хаос власності.
- idmapped mounts — це функція ядра, не контейнера. Вони дозволяють ядру транслювати власність для конкретного монту без зміни на диску.
- ACL можуть перевизначити «виглядає нормально» бітові режими. Каталог може бути
drwxrwx---і все одно відмовляти доступ через забуте ACL. - Overlay-файлові системи ускладнюють сприйняття власності. У багатошарових FS ви можете бачити одну власність у шарі контейнера і іншу на бекінг-сторі хоста.
Основна причина: невідповідність UID/GID між просторами імен
Повторюваний шаблон за «Permission denied на bind-монтах» простий:
ефективний UID/GID процесу, як його бачить ядро, не є власником файлів, до яких він намагається звернутися.
І bind-монти цього не змінюють — вони лише показують це.
Де часто звинувачують Debian 13 (часто несправедливо)
Debian 13 не «ламав bind-монти». Те, що змінюється в сучасних системах, — це поширеність:
rootless Podman/Docker, systemd mount units, суворіше дефолтне налаштування та user namespace-конфігурації. Будь-який дрібний зсув у координації UID/GID
стає видимим після оновлення компонентів та повторного розгортання.
Типові сценарії невідповідності
- Rootless контейнер + директорія на хості належить вашому реальному UID. «UID 0» контейнера мапиться в високий діапазон хост-UID (з
/etc/subuid). - Різне призначення UID на різних хостах. Користувач
appмає UID 1001 на вузлі A і UID 1002 на вузлі B. NFS зберігає лише числа. - Користувач образу контейнера не відповідає користувачу сервісу на хості. Образ запускається як UID 1000, каталог на хості належить UID 2000, і ви помилково покладаєтеся на імена.
- Rootful контейнер + NFS root-squash. «Але це root у контейнері» зустрічає «root на сервері стає nobody».
- ACL або політика LSM відхиляють попри правильні бітові режими. Ви можете дивитись на
ls -lвесь день і ніколи не побачити відмови AppArmor.
Цитата інженерії надійності (парафраз): Werner Vogels говорив, що будувати системи треба з очікуванням відмов, а не вдаючи, ніби їх не буде.
Невідповідності ідентичності — це форма «очікуваної відмови» в мультиорендному Linux; проектуйте під це.
Практичні завдання: команди, виходи та рішення (12+)
Ось команди, які я реально запускаю. Кожне завдання містить: команду, що каже її вихід, і рішення, яке з цього робиться.
Припустимо, що шлях bind-монту, про який йдеться, на хості — /srv/app/data, а всередині контейнера він доступний як /data.
Завдання 1: Підтвердіть, що це bind-монт (а не інша файловa система)
cr0x@server:~$ findmnt -T /srv/app/data -o TARGET,SOURCE,FSTYPE,OPTIONS
TARGET SOURCE FSTYPE OPTIONS
/srv/app/data /dev/sdb1[/data] ext4 rw,relatime
Значення: Ви на ext4 і не дивитеся на NFS/overlay-сюрприз.
Рішення: Якщо тут показано nfs, переходьте до перевірок ідентичності для NFS. Якщо показано overlay, розглядайте це як поведінку контейнерного шару.
Завдання 2: Перевірте propagation та явно bind-ність
cr0x@server:~$ findmnt -o TARGET,SOURCE,FSTYPE,OPTIONS,PROPAGATION | grep -E '(/srv/app/data|/data)'
/srv/app/data /dev/sdb1[/data] ext4 rw,relatime shared
Значення: Propagation може впливати на те, чи з’являються монти всередині контейнерів.
Рішення: Якщо propagation — private і ви покладаєтесь на вкладені монти, ви отримаєте проблему «відсутніх даних», а не відмови в доступі. Інша історія.
Завдання 3: Перевірте числову власність і бітові режими (хост)
cr0x@server:~$ stat -c 'path=%n uid=%u gid=%g mode=%a owner=%U group=%G' /srv/app/data
path=/srv/app/data uid=2000 gid=2000 mode=750 owner=app group=app
Значення: Ядро вимагатиме UID 2000 або GID 2000 (або ACL) для запису.
Рішення: Якщо ваш контейнер виконується як UID 1000, він програє. Виправляйте відображення, не chmod’те.
Завдання 4: Визначте ідентичність процесу всередині контейнера
cr0x@server:~$ podman exec -it appctr sh -lc 'id && umask'
uid=1000(app) gid=1000(app) groups=1000(app)
0022
Значення: Усередині контейнера процес має UID/GID 1000.
Рішення: Ви маєте узгодити це з власністю хоста (UID 2000) або транслювати через idmapped mounts.
Завдання 5: Підтвердіть помилку контрольним записом (контейнер)
cr0x@server:~$ podman exec -it appctr sh -lc 'touch /data/.perm_test && echo ok'
touch: cannot touch '/data/.perm_test': Permission denied
Значення: Це не ваша програма; це файлова система. Добре — тепер можна припинити звинувачувати код.
Рішення: Продовжуйте діагностику відображення замість перегляду логів застосунку.
Завдання 6: Порівняйте запис з боку хоста від імені очікуваного користувача
cr0x@server:~$ sudo -u app touch /srv/app/data/.host_test && ls -ln /srv/app/data/.host_test
-rw-r--r-- 1 2000 2000 0 Dec 30 12:01 /srv/app/data/.host_test
Значення: UID 2000 працює, і файли належать як очікується.
Рішення: Або запускайте контейнер як UID 2000, або мапте його UID на 2000.
Завдання 7: Якщо rootless — перевірте підпорядковані діапазони ID
cr0x@server:~$ grep -E '^cr0x:' /etc/subuid /etc/subgid
/etc/subuid:cr0x:100000:65536
/etc/subgid:cr0x:100000:65536
Значення: Rootless user namespaces маплять ID контейнера в 100000+ на хості.
Рішення: Якщо каталог хоста належить UID 2000, rootless контейнер, змеплений у 100000, не співпаде. Потрібен idmapped mount або зміна власності хоста.
Завдання 8: Перевірте відображення user namespace процесу контейнера
cr0x@server:~$ pid=$(podman inspect -f '{{.State.Pid}}' appctr); echo "$pid"; sudo cat /proc/$pid/uid_map
24188
0 100000 65536
Значення: UID 0 контейнера — це хост-UID 100000; контейнерний UID 1000 — хост-UID 101000.
Рішення: Перестаньте намагатись chown монту з середини контейнера; це не вплине на хостові ID, які ви очікуєте.
Завдання 9: Перевірте ACL, що можуть перевизначати бітові режими
cr0x@server:~$ getfacl -p /srv/app/data | sed -n '1,20p'
# file: /srv/app/data
# owner: app
# group: app
user::rwx
group::r-x
other::---
Значення: Тут ACL не приносять сюрпризів.
Рішення: Якщо ви бачите ACL, що відхиляє запис, виправляйте ACL; поки не змінюйте UID/GID.
Завдання 10: Перевірте відмови AppArmor (поширено на Debian)
cr0x@server:~$ sudo journalctl -k -g DENIED --no-pager | tail -n 3
Dec 30 12:03:14 server kernel: audit: type=1400 apparmor="DENIED" operation="open" class="file" profile="containers-default" name="/srv/app/data/" pid=24188 comm="myapp"
Значення: Це не UID/GID. AppArmor блокує доступ.
Рішення: Виправте профіль AppArmor або мітки; не чіпайте власність. Якщо відмов немає, повертайтеся до діагностики відображення.
Завдання 11: Підтвердіть опції монту (nosuid/nodev/noexec не дають «Permission denied», але можуть виглядати подібно)
cr0x@server:~$ findmnt -T /srv/app/data -o OPTIONS
OPTIONS
rw,relatime
Значення: Ніякого noexec дивного.
Рішення: Якщо ваш додаток не виконує бінарні файли з монту і ви бачите noexec, це ваше виправлення.
Завдання 12: Переконайтесь, що ефективний користувач контейнера відповідає очікуванням (rootful vs rootless)
cr0x@server:~$ podman info --format '{{.Host.Security.Rootless}}'
true
Значення: Ви в rootless. Це чудово для безпеки і погано для припущень.
Рішення: Плануйте виправлення, що працюють з userns-відображенням, а не проти нього.
Завдання 13: Відтворіть невідповідність власності числовим переліком (контейнер)
cr0x@server:~$ podman exec -it appctr sh -lc 'ls -ldn /data; ls -ln /data | head'
drwxr-x--- 2 2000 2000 4096 Dec 30 12:01 /data
-rw-r--r-- 1 2000 2000 0 Dec 30 12:01 .host_test
Значення: Контейнер бачить UID 2000 на файлах, але сам запускається як UID 1000. Цей розрив — проблема.
Рішення: Оберіть між: (a) зміною користувача контейнера на 2000, (b) зміною власності хоста на змеплений ID, (c) idmapped mount.
Завдання 14: Підтвердіть NFS root-squash, якщо доречно
cr0x@server:~$ findmnt -T /srv/app/data -o FSTYPE,SOURCE
FSTYPE SOURCE
nfs4 nfs01:/exports/appdata
cr0x@server:~$ sudo -u root touch /srv/app/data/.root_touch 2>&1 | tail -n 1
touch: cannot touch '/srv/app/data/.root_touch': Permission denied
Значення: Ймовірно root-squash або політика експорту на сервері. Root на клієнті не є root на сервері.
Рішення: Виправте ідентичність на NFS-сервері (узгодження UID/GID), або свідомо налаштуйте опції експорту. Не запускайте все як root, щоб «пофіксити» це.
Чисті виправлення, які ви захистите на рев’ю
Є три «хороші» сімейства рішень. Обирайте залежно від вашої операційної реальності: один хост проти флоту, rootless проти rootful,
вимоги відповідності та чи є сховище локальним чи мережею.
Сімейство виправлень A: Узгодити UID/GID рантайму контейнера з даними хоста
Це найпростіше й найбільш нудне виправлення: якщо /srv/app/data належить UID 2000, запускайте процес контейнера як UID 2000.
Працює для bind-монтів, на більшості файлових систем і не вимагає складних функцій ядра.
З образами, що це підтримують, вкажіть користувача рантайму явно. В Compose-подібних робочих процесах ви б вказали user: "2000:2000".
Для Podman:
cr0x@server:~$ podman run -d --name appctr --user 2000:2000 -v /srv/app/data:/data:Z docker.io/library/alpine sleep 1d
...output...
Значення: Процес працює з такими ж UID/GID, що й власник каталогу хоста.
Рішення: Використовуйте це, коли контролюєте UID додатка і не потребуєте семантики «UID 0» в контейнері.
Підводний камінь: якщо ви запускаєте на багатьох хостах, UID 2000 має бути консистентним скрізь. Це не опціонально. Це суть проблеми.
Сімейство виправлень B: Стандартизувати UID/GID по флоту (ідентичність через каталог)
Це те, що роблять зрілі середовища: призначають сервісним облікам фіксовані числові ID, керують ними централізовано і роблять сховище узгодженим.
Локально створені користувачі на кожному хості будуть розходитись. Розходження призводить до відмов.
Якщо не можете централізувати — хоча б забронюйте діапазон UID/GID для сервісних ідентичностей і забезпечте його під час провіжингу.
«Ми пам’ятаємо створити користувача app з тим самим UID на кожному сервері» — це не процес; це молитва.
Сімейство виправлень C: Використовуйте idmapped mounts для чистої трансляції власності
idmapped mounts — це найближче в Linux до «bind-монту з перекладом ID». Вони дозволяють змонтувати директорію так, щоб власність
була змеплена лише для цього монту — без зміни на диску та без глобального перенумерування UID.
Це особливо привабливо коли:
- У вас є дані, що належать одному UID/GID, але образ контейнера очікує інші.
- Ви не можете виконати chown (великі набори даних, відповідність, спільні монти).
- Ви працюєте rootless і хочете, щоб контейнер бачив «розумну» власність.
Точна підводка залежить від рантайму й підтримки ядра. Принцип лишається: визначте відображення і застосуйте його до монту.
На сучасних ядрах Debian idmapped mounts життєздатні, але не припускайте, що кожен шар стеку (systemd, рантайм контейнера, оркестрація)
зробить це за вас автоматично.
Чого не робити (якщо вам потрібні рідкі інциденти)
- Не chmod’те 777 спільний каталог «щоб працювало». Ви порушите принцип найменших привілеїв і все одно отримаєте драйф ідентичності.
- Не chown терабайтів під час інциденту, якщо ви не виміряли і не можете відкотити. Це повільно, руйнівно і легко зіпсувати.
- Не «фіксьте», запускаючи все як root. NFS посміється, LSM пожаліється, і з’являться нові проблеми.
Жарт №2: Щоразу, коли хтось пропонує «запустити в привілейованому режимі», інженер з безпеки отримує ще один сивий волос і називає його на вашу честь.
Три міні-історії з корпоративного життя
1) Інцидент через хибне припущення: «імена співпадають, отже ID теж»
Середня компанія запускала stateful сервіс у контейнерах на кількох Debian-хостах. Обліковий запис сервісу всюди називався app.
Команда вважала, що це означає ту саму ідентичність скрізь. Насправді — ні.
На одному вузлі система була встановлена раніше і мала кілька локальних користувачів. Там app отримав UID 1002. На новіших вузлах —
UID 1001. Сховище було NFS-експортом, змонтованим у /srv/app/data, і NFS зберігав числову власність, а не «настрій імені».
Модель відмови була тонкою: сервіс працював на двох вузлах і падав на одному з Permission denied при записі нових файлів,
але міг читати старі. Інженери годинами тикали в налаштування контейнера, потім у параметри монтів NFS, потім думали «може, Debian щось змінив».
Прорив прийшов від людини, що зробила нудну річ: ls -ln на каталозі даних з кожного вузла, плюс id app.
Вони миттєво побачили невідповідність. Виправлення теж було нудним: стандартизувати UID/GID для app через провіжинг і
виправити власність на NFS-сервері на потрібний UID.
Урок: імена користувачів — це інтерфейс; числові ID — це контракт.
2) Оптимізація, що дала відкат: «щоб не chown, переключимося на rootless»
Інша організація хотіла зменшити blast radius при ескейпі контейнера. Вони перевели частину навантажень на rootless контейнери.
Загалом хороший крок. Але вони сприйняли це як зміну лише безпеки і не перевірили сумісність сховища.
Їхні bind-монти вказували на каталоги хоста, що належали сервісним користувачам з UID у низьких тисячах. Rootless мапування зсунуло ID контейнера
в діапазон 100000+. Раптом навантаження, що писали в /srv, почали падати. Перша реакція команди була «оптимізувати»: розширити права групи на каталог.
Це зробило деякі навантаження працездатними, але погіршило результати аудиту.
Вони потім спробували «швидке» виправлення: рекурсивно chown хост-каталогів на змеплений хост-UID. Це працювало для контейнерів, але ламало набір
хостових інструментів техобслуговування, що очікували, що сервісний користувач володіє своїми даними. Тепер у них були дві всесвіти власності, і жодна не була щаслива.
Остаточне стабільне рішення полягало в тому, щоб не трактувати ідентичність як випадковість. Вони визначили стратегію UID/GID для сервісів, задокументували,
які навантаження rootless, і використали комбінацію запуску контейнерів з узгодженими UID та вибіркового використання idmapped mounts, де старі дані не могло
бути переміщено. «Оптимізація» не була rootless; оптимізація була в управлінні навколо нього.
3) Нудна, але правильна практика, що врятувала день: preflight-перевірки в CI для ідентичності сховища
Велика команда платформи мала звичку, що здавалася смішною до першого разу: кожен pipeline розгортання мав preflight job, який запускав контейнер
з запланованим рантайм-користувачем, монтував потрібний хост-шлях і виконував базові файл-операції (mkdir, touch, fsync, rename).
Job виробляв маленький артефакт: UID/GID, які побачив контейнер, UID/GID на хості для створеного файлу і будь-які відмови ядра/LSM.
Це було не витончене. Це було послідовне. Люди іноді скаржилися, що це додає хвилин і «нічого не знаходить».
Потім хвиля вузлів була перебудована з трохи іншим порядком провіжингу і кілька сервісних обліків змістилися в призначенні UID. Preflight
спіймав це перед продакшн-роллаутом. Виправили в провіжингу, а не в режимі аварії. Жодних дзвінків посеред ночі, жодних екстрених chmod, жодних chown спільного сховища.
Це вигляд надійності: не героїзм, а процес, який робить відмову нудною і ранньою.
Поширені помилки: симптом → корінь → фікс
1) Симптом: Працює на хості, не працює в контейнері з «Permission denied»
Корінь проблеми: Container UID/GID не відповідає власності хоста, часто через rootless userns-відображення.
Виправлення: Запустіть контейнер як власник каталогу хоста (UID/GID), або використайте idmapped mounts, або навмисно підберіть власність хоста під змеплений ID.
2) Симптом: Rootful контейнер все одно не може записати в NFS bind-монт
Корінь проблеми: NFS root-squash або дозволи на сервері. Root у контейнері не особливий на сервері.
Виправлення: Узгодьте числові ID між клієнтами та сервером; свідомо налаштуйте опції експорту; не «просто запускайте як root».
3) Симптом: ls -l показує, що запис дозволений, але записи падають
Корінь проблеми: ACL або політика LSM (AppArmor/SELinux) відхиляє операції.
Виправлення: Перегляньте ACL через getfacl; перевірте відмови в логах ядра; виправте політику/профіль замість chmod/chown.
4) Симптом: Файли, створені в контейнері, на хості мають дивні високі UID (100000+)
Корінь проблеми: Rootless user namespace мапування через /etc/subuid//etc/subgid.
Виправлення: Очікуйте цього; не боріться з цим. Використайте узгоджені ID через idmapped mount або скорегуйте стратегію рантайму додатку.
5) Симптом: Деякі вузли працюють, інші падають, той самий деплой
Корінь проблеми: Драйф UID/GID між хостами; NFS або спільне сховище роблять це видимим.
Виправлення: Централізуйте ідентичність або забезпечте фіксовані числові ID у провіжингу; перевіряйте через id і ls -ln по вузлах.
6) Симптом: Зміна прав «виправляє», але потім ламає
Корінь проблеми: Ви замаскували невідповідність ідентичності розширенням прав; згодом інший сервіс або контроль безпеки очікує жорсткіші режими.
Виправлення: Поверніться до принципу найменших привілеїв; виправте відображення ідентичності; додайте preflight-тест, що перевіряє записність під правильним UID/GID.
Контрольні списки / покроковий план
Покроковий план для одного хоста Debian 13 (найпоширеніший)
-
Підтвердіть шлях, що падає, і тип монту.
Використайтеfindmnt -T. Якщо це NFS, переходьте до плану NFS. -
Запишіть власність хоста числово.
Використайтеstat -c 'uid=%u gid=%g mode=%a'на шляху хоста. -
Запишіть ідентичність рантайму контейнера.
Усередині контейнера:id. Також зафіксуйте, чи ви rootless. -
Перевірте відображення userns (особливо для rootless).
Перегляньте/proc/$pid/uid_map. Якщо UID зсунуті, припускайте невідповідність до доведення протилежного. -
Виключіть ACL та відмови LSM.
Запустітьgetfaclі перевіртеjournalctl -k -g DENIED. -
Оберіть виправлення:
- Якщо контролюєте користувача контейнера: запускайте контейнер з UID/GID власника хоста.
- Якщо не можете змінити користувача контейнера: idmapped mount (переважно) або контрольована зміна власності.
- Якщо це спільне середовище: стандартизуйте ID замість латання одного хоста.
-
Підтвердіть двостороннім тестом запису.
Створіть файл з контейнера і перевірте його UID/GID на хості; потім навпаки. -
Закріпіть рішення.
Покладіть рішення щодо UID/GID в конфігураційне управління, а не в «пам’ять племені».
Покроковий план для bind-монтів на NFS
- Визначте NFS-сервер і політику експорту. Підтвердіть очікування root-squash.
- Перевірте числову послідовність UID/GID на всіх клієнтах. Той самий сервісний обліковий запис має мати той самий UID скрізь.
- Виправте ідентичність у джерелі. Directory-backed identity або консистентний провіжинг краще за локальні заплатки.
- Уникайте chown-штурмів. Якщо потрібно виправити власність, робіть це на сервері в контрольований вікно.
- Перевірте з реальним UID застосунку. «Працює як root» — не критерій успіху для NFS.
Покроковий план для rootless контейнерів на Debian 13
- Підтвердіть режим rootless.
podman infoабо еквівалент рантайму. - Перевірте
/etc/subuidта/etc/subgid. Переконайтесь, що діапазони існують і достатні. - Огляньте uid_map/gid_map. Знайте, у що перетворюються контейнерні ID на хості.
- Оберіть стратегію власності. Або дані належать змепленим хост-IDs, або ви використовуєте idmapped mounts, щоб показати очікувані ID.
- Документуйте це. Rootless без документації щодо ідентичності — підписка на відмови.
FAQ
Чому це з’явилося після переходу на Debian 13?
Зазвичай тому, що ви змінили один шар: рантайм контейнерів, дефолти rootless, набір можливостей ядра, поведінку systemd або порядок провіжингу ідентичності.
Основне правило не змінилося: доступ контролюється числовими ID і політикою.
Чому ls -l показує знайоме ім’я, але контейнер не може писати?
Тому що процес контейнера має інший числовий UID, ніж число, що володіє каталогом. Імена вирішуються через NSS і можуть відрізнятись у різних середовищах.
Завжди перевіряйте числові ID за допомогою ls -ln або stat.
Чи можу я просто зробити chown -R каталогу на користь контейнера?
Можете, але це часто найгірший варіант: повільно для великих дерев, ризиковано для спільних даних і закріплює випадкові відображення.
Віддавайте перевагу узгодженню UID/GID рантайму або idmapped mounts, коли потрібно транслювати без переписування власності.
Яке найчистіше вирішення, коли декілька сервісів ділять один хост-шлях?
Стандартизуйте UID/GID сервісних обліків по флоту і продумайте групову власність. Якщо сервіси дійсно потребують різних уявлень власності, idmapped mounts кращі, ніж хаос chmod.
Чому rootless створює файли з UID на кшталт 101000 на хості?
Тому що user namespaces маплять контейнерні ID у підлеглий діапазон, визначений у /etc/subuid//etc/subgid.
Контейнерний UID 1000 стає хост-UID (subuid base + 1000).
Чи AppArmor справді дає «Permission denied», що схоже на проблему файлової системи?
Так. Він може відхиляти open/create операції навіть коли бітові режими виглядають дозволеними. Перевірте логи ядра на предмет відмов.
Якщо бачите AppArmor denies — виправляйте профіль, не міняйте власність.
А як щодо Kubernetes hostPath томів?
Та сама логіка: ідентичність процесу у поді має мати доступ до шляху ноди. Використовуйте явні runAsUser/runAsGroup,
або контрольований init-крок, або сховище, що уникає hostPath для stateful даних.
Чи достатньо fsGroup?
Іноді. Якщо групові дозволи правильно встановлені (g+rwx) і процес у цій групі, це спрацює.
Але це не виправить директорії, що доступні лише власнику, і не допоможе, якщо відображення user namespace перешкоджає співпадінню GID.
Як довести, що це відображення UID/GID, а не «дивні права Debian»?
Скористайтесь двостороннім тестом: створіть файл усередині контейнера і перегляньте його числового власника на хості. Потім навпаки.
Якщо ID не збігаються — вердикт: невідповідність відображення.
Коли слід використовувати idmapped mounts замість зміни користувача контейнера?
Використовуйте idmapped mounts, коли не можете змінити UID додатка (вендорний образ), не можете змінити на-дискову власність (спільні або величезні дані),
або потребуєте декілька уявлень про ті самі дані з різними семантиками власності.
Наступні кроки, що не будуть вас переслідувати
«Permission denied» на bind-монті рідко є загадкою і майже ніколи не вирішується шляхом послаблення прав.
Розглядайте це як проблему відображення ідентичності, поки не доведено зворотне.
Зробіть наступне, по порядку:
- Зафіксуйте числові UID/GID шляху на хості і процесу контейнера.
- Підтвердіть, чи user namespaces зсувають ID (rootless — звичайний підозрюваний).
- Виключіть ACL та AppArmor відмови цілеспрямованими перевірками.
- Оберіть чисте вирішення: узгодьте рантайм UID/GID, стандартизуйте ID по хостах або використайте idmapped mounts для трансляції.
- Закріпіть це в провіжингу та CI через preflight-тест на запис.
Якщо зробите лише одну річ: припиніть довіряти іменам користувачів під час налагодження прав доступу до сховища. Числові ID — це правда, а bind-монти — надто чесні.