Помилка ‘Permission denied’ при bind‑mount у Proxmox LXC: UID/GID, непривілейовані контейнери та робоче рішення

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

Ви прив’язуєте (bind‑mount) директорію хоста в LXC‑контейнер, і зовні все виглядає нормально — доки контейнер не спробує записати, виконати chmod, chown або навіть перерахувати вміст директорії.
Тоді логи ввічливо повідомляють: відмова в доступі. Продакшн‑системи люблять ввічливі відмови, бо вони з гідністю витрачають ваш час.

Ця проблема рідко є просто «проблемою прав у Linux» в загальному вигляді. У Proxmox це зазвичай конкретне, передбачуване невідповідність між
відображенням ідентичностей у непривілейованому контейнері та власністю/ACL на боці хоста, часто ускладнена семантикою мережевого сховища.
Виправлення — не хаотичний chmod. Виправлення — узгодити ідентичності.

Що насправді відбувається (і чому очі брешуть)

Непривілейовані контейнери Proxmox LXC — це механізм безпеки: root всередині контейнера не є root на хості.
Він відображається на високий, непривілейований UID на хості, зазвичай що починається з 100000.
Це й є суть: якщо контейнер буде скомпрометовано, «root» не зможе записувати в /etc на хості, завантажувати модулі ядра або бездумно псувати дані.

Bind‑mount (Proxmox mp0, mp1 тощо) пробиває контрольований отвір у цій ізоляції. Але вони не перекладають власність файлів.
Якщо директорія на хості належить root:root (0:0), а root контейнера відображається на 100000:100000, то всередині контейнера монтування може виглядати
ніби воно належить root, але перевірки прав виконуються щодо відображених (host) ID.

Тому фраза «але всередині контейнера воно належить root» — пастка. Ваш root у контейнері — звичайний користувач на хості. Дуже з високим числом.

Переклад жаргону: помилка не про «монтування». Вона про ідентичність і авторизацію на межі VFS.

Невеликий жарт, бо далі ми ще довго говоритимемо про зміщення UID: bind‑mount як пропуск в офісі — ваша картка чудово працює, аж доки ви не намагаєтесь зайти до серверної.

Швидкий план діагностики

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

1) Підтвердьте, що контейнер непривілейований, і занотуйте idmap

  • Якщо він непривілейований, припускайте, що проблема в відображенні UID/GID, поки не доведете протилежне.
  • Якщо привілейований — права все ще можуть бути причиною, але режими відмов інші.

2) Визначте шлях на хості, прив’язаний до mp0, і перевірте власність/ACL

  • Власність на боці хоста важливіша, ніж та, що ви бачите всередині контейнера для bind‑mount.
  • ACL можуть переважати очікування від chmod, а мережеві файлові системи додають власні правила.

3) Протестуйте запис і chown із середини контейнера

  • Якщо записи не працюють, а читання — так: невідповідність власності/ACL.
  • Якщо chown не вдається, але запис працює: ймовірно, вам не потрібен chown у рантаймі; виправте додаток або задайте власність один раз.

4) Перевірте семантику бекенду сховища (NFS root_squash, SMB, ZFS datasets)

  • NFS із root_squash відомий тим, що перетворює «root» у «nobody», особливо в контейнерах.
  • ZFS datasets можуть застосовувати власний режим ACL і спадкування прав.

5) Лише потім розглядайте «обхідні шляхи»

  • Переключення на привілейований контейнер — ядерна опція. Вона лікує симптоми, вимикаючи механізм безпеки.
  • Випадковий chmod -R 777 — не лікування; це визнання провалу.

Цікаві факти та контекст (те, що дізнаєшся після інциденту)

  1. Простори ідентичностей (UID namespaces) з’явилися в ядрі Linux роками раніше, ніж багато дистрибутивів почали їх за замовчуванням використовувати; технології контейнерів чекали на безпеку й інструменти.
  2. LXC передував Docker у масовому використанні; це ближче до «системних контейнерів», ніж до «однопроцесних» образів. Інша ергономіка, інші больові точки.
  3. Proxmox обрав непривілейовані контейнери як рекомендацію за замовчуванням, бо вони значно зменшують площу ураження хоста при типовому сценарії «веб‑додаток зламали».
  4. Типове відображення Proxmox часто починається з 100000 і охоплює 65536 ID. Це не магія; це типовий розмір підлеглого діапазону.
  5. POSIX‑права оцінюються за host‑ID після відображення. Те, що ви бачите всередині контейнера, — шар перекладу, а не істина хоста.
  6. NFS root_squash придумали, щоб уникнути перетворення віддалених кореневих користувачів у root на NFS‑сервері. Контейнери не мають спеціального винятку.
  7. Shiftfs існував як поза‑ядрове рішення Ubuntu для болю зі зміщенням UID. Це не головна відповідь сьогодні, робити на нього ставку довгостроково — ризик.
  8. idmapped mounts — новіша можливість ядра, яка може вирішити це чисто, але практичність залежить від вашої версії Proxmox/ядра та інструментів.
  9. ACL старші, ніж багато інженерів думають у середовищах корпоративного Linux; це не екзотика і часто пояснює «chmod каже так, але все одно ні».

Ментальна модель: відображення UID/GID, idmap ranges, та bind‑mount

Непривілейований LXC працює через простір ідентичностей користувача. Всередині контейнера процеси мають «UID контейнера».
Ядро відображає їх на «UID хоста» перед виконанням перевірок прав у файлових системах хоста.

Типове відображення виглядає так:

  • Container UID 0 (root) → Host UID 100000
  • Container UID 1 → Host UID 100001
  • Container UID 65535 → Host UID 165535

Отже, якщо ви bind‑mount‑ите /tank/shared із хоста в /mnt/shared в контейнері, і папка на хості належить root:root,
то root контейнера не є власником у термінах хоста. Root контейнера — це host UID 100000. Root хоста — це UID 0.

Така невідповідність породжує класичні помилки:

  • Запис не вдається: директорія не записувана для host UID 100000.
  • chown не вдається: непривілейований контейнер не може довільно змінювати власність на файлових системах хоста.
  • chmod здається, що працює або ні: залежить від відображення, файлової системи та того, чи не забороняють ACL.

Фактично є чотири виробничі шляхи виходу:

  1. Зробити власність на хості відповідною відображеним IDs (звичайна, нудна, правильна відповідь).
  2. Використати ACL на хості, щоб надати доступ відображеним ID без зміни власності всього дерева.
  3. Використати idmapped mounts (якщо стек це підтримує стабільно).
  4. Не робити bind‑mount host‑шляхів; натомість використовувати сховище, призначене для спільного доступу (NFS/SMB з явним відображенням користувачів, або виділений dataset на контейнер з правильною власністю).

Перехід на привілейований контейнер — «п’ятий шлях», але це більше як натиснути тривогу, щоб вимкнути мікрохвильовку. Працює, і всі це запам’ятають.

Практичні завдання: команди, очікуваний вихід, і що робити

Ось набір ручних перевірок, які я насправді запускаю. Кожне завдання містить: команду, що означає її вивід, і що робити далі.
Команди на хості запускайте на вузлі Proxmox, команди всередині — в LXC.

Завдання 1: Перевірити, що контейнер непривілейований

cr0x@server:~$ pct config 103 | egrep 'unprivileged|lxc.idmap|mp0|mp1'
unprivileged: 1
mp0: /tank/shared,mp=/mnt/shared

Значення: unprivileged: 1 підтверджує, що у вас ввімкнене відображення UID/GID.
Якщо ви бачите рядки lxc.idmap, відображення налаштоване індивідуально і його потрібно враховувати.

Рішення: Продовжуйте припускати невідповідність відображення, поки не доведете, що доступ надано відображеним host‑ID.

Завдання 2: Переглянути відображення ідентичностей контейнера з хоста

cr0x@server:~$ cat /etc/pve/lxc/103.conf | egrep 'unprivileged|lxc.idmap'
unprivileged: 1

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

Рішення: Перевірте підлеглі діапазони subuid/subgid на вузлі; вони визначають базове зміщення UID на хості.

Завдання 3: Перевірити діапазони subordinate UID/GID на вузлі Proxmox

cr0x@server:~$ grep -E '^root:' /etc/subuid /etc/subgid
/etc/subuid:root:100000:65536
/etc/subgid:root:100000:65536

Значення: Container UID 0 відображається на host UID 100000, і у вас діапазон 65536 ID.

Рішення: Будь‑яка директорія на хості, яку ви прив’язуєте, має бути записувана для host UID/GID 100000 (або для тих host ID, на які відображається користувач вашого застосунку).

Завдання 4: Знайти хост‑шлях для точки монтування

cr0x@server:~$ pct config 103 | grep '^mp'
mp0: /tank/shared,mp=/mnt/shared

Значення: Хост‑шлях — /tank/shared. Це шлях, чия власність і ACL повинні дозволяти доступ відображеним ID.

Рішення: Спочатку перегляньте власність і дозволи на цьому шляху на хості (не всередині контейнера).

Завдання 5: Перевірити власність на хості, біти режиму та наявність ACL

cr0x@server:~$ ls -ldn /tank/shared
drwxr-x--- 5 0 0 5 Dec 26 08:41 /tank/shared

Значення: Власник UID/GID — 0:0 (root хоста). Режим 750 означає, що лише власник і група можуть читати/писати; інші — не можуть.
Host UID 100000 — це «others», тому root контейнера не зможе записати.

Рішення: Або змініть власність на 100000:100000, або додайте ACL, що дає доступ host 100000, або переструктуруйте сховище.

Завдання 6: Підтвердити, під яким користувачем працює навантаження всередині контейнера

cr0x@server:~$ pct exec 103 -- ps -eo user,uid,group,gid,comm | head
USER       UID GROUP      GID COMMAND
root         0 root         0 ps
www-data    33 www-data     33 nginx

Значення: Ваш додаток може працювати не від root. Тут nginx працює як UID 33 всередині контейнера.

Рішення: Відобразіть runtime UID контейнера на host: host UID = 100000 + 33 = 100033. Це ідентичність, якій потрібен доступ на хості.

Завдання 7: Обчислити відповідний host UID/GID для користувача контейнера

cr0x@server:~$ echo $((100000 + 33))
100033

Значення: Container UID 33 відповідає host UID 100033 (за умови стандартного відображення).

Рішення: Надайте права запису на хості для UID 100033 (і, можливо, GID 100033), а не для UID 33.

Завдання 8: Продемонструвати помилку зсередини контейнера (тест запису)

cr0x@server:~$ pct exec 103 -- bash -lc 'id && touch /mnt/shared/.permtest'
uid=0(root) gid=0(root) groups=0(root)
touch: cannot touch '/mnt/shared/.permtest': Permission denied

Значення: Класичний випадок. Монтування видно, але права на хості забороняють відображеному ID.

Рішення: Виправте власність/ACL на хості. Не намагайтесь «вирішити» це chmod всередині контейнера й сподіватися, що це збережеться.

Завдання 9: Перевірити тип монтування та опції на хості

cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /tank/shared
tank/shared zfs rw,xattr,posixacl

Значення: ZFS dataset з включеною підтримкою ACL. Це добре. Дає більш контрольовані варіанти, ніж лише chmod.

Рішення: Віддавайте перевагу наданню ACL або виділеному dataset на контейнер замість широкого розширення режимів на весь дерево.

Завдання 10: Виправити власність на хості, щоб відповідала відображеним IDs (простий випадок)

cr0x@server:~$ chown -R 100000:100000 /tank/shared

Значення: Тепер root контейнера (host 100000) володіє деревом. Всередині контейнера root має можливість запису.

Рішення: Використовуйте це, коли директорія присвячена цьому контейнеру. Уникайте для спільних даних між контейнерами, якщо не маєте чіткої стратегії спільного володіння.

Завдання 11: Альтернатива — надати доступ через ACL для відображеного користувача (менш руйнівно)

cr0x@server:~$ setfacl -m u:100033:rwx /tank/shared
cr0x@server:~$ getfacl -p /tank/shared | sed -n '1,12p'
# file: /tank/shared
# owner: root
# group: root
user::rwx
user:100033:rwx
group::r-x
mask::rwx
other::---

Значення: Host‑користувачу 100033 (www‑data контейнера) надано повний доступ без зміни власності root.

Рішення: Обирайте ACL, коли потрібно зберегти власника root на хості або коли кілька ідентичностей потребують контрольованого доступу. Пам’ятайте встановити дефолтні ACL для нових файлів, якщо потрібно.

Завдання 12: Встановити дефолтні ACL, щоб нові файли наслідували права (поширене “працювало вчора” виправлення)

cr0x@server:~$ setfacl -d -m u:100033:rwx /tank/shared
cr0x@server:~$ getfacl -p /tank/shared | grep -E '^default:user:100033'
default:user:100033:rwx

Значення: Нові файли й директорії, створені в /tank/shared, успадковують запис ACL для користувача 100033.

Рішення: Якщо ваш додаток створює піддиректорії і раптом не може в них записувати — дефолтні ACL часто є відсутнім елементом.

Завдання 13: Перевірити зсередини контейнера після змін

cr0x@server:~$ pct exec 103 -- bash -lc 'touch /mnt/shared/.permtest && ls -ln /mnt/shared/.permtest'
-rw-r--r-- 1 0 0 0 Dec 26 09:02 /mnt/shared/.permtest

Значення: Контейнер тепер може записувати. Не зациклюйтесь на відображених UID, що показуються всередині контейнера; хост правильно застосовує доступ через відображення/ACL.

Рішення: Перевірте вимоги додатка: чи потребує він chown? чи потрібен запис для групи? опрацюйте ці вимоги окремо.

Завдання 14: Діагностувати правильно “chown: Operation not permitted”

cr0x@server:~$ pct exec 103 -- bash -lc 'chown 33:33 /mnt/shared/.permtest'
chown: changing ownership of '/mnt/shared/.permtest': Operation not permitted

Значення: Непривілейовані контейнери зазвичай не можуть довільно змінювати власність на bind‑mounted шляхах хоста. Це очікувана поведінка.

Рішення: Встановіть власність на хості один раз, або змініть додаток так, щоб він не робив chown у рантаймі. Якщо додаток вимагає chown, потрібно спроектувати навколо цього (виділений dataset з попередньо заданою власністю, або інший метод шарингу).

Завдання 15: Перевірити root_squash на NFS (якщо хост‑шлях — NFS)

cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /tank/shared
nas:/export/shared nfs4 rw,relatime,vers=4.2,hard,proto=tcp,timeo=600,retrans=2,sec=sys

Значення: «Реальні» дозволи тепер контролює NFS‑сервер, плюс опції експорту типу root_squash і поведінка idmapping.

Рішення: Якщо підозрюєте root_squash — не намагайтесь зробити «root у контейнері» власником. Використайте виділений NFS‑експорт з явним UID/GID сервісу, що відповідають відображеним host‑ID, або уникайте bind‑монтування NFS у непривілейовані контейнери взагалі.

Завдання 16: Подивитися, який UID використовує хост, коли контейнер пише (форензика)

cr0x@server:~$ rm -f /tank/shared/hostview.test
cr0x@server:~$ pct exec 103 -- bash -lc 'echo hi > /mnt/shared/hostview.test'
cr0x@server:~$ ls -ln /tank/shared/hostview.test
-rw-r--r-- 1 100000 100000 3 Dec 26 09:06 /tank/shared/hostview.test

Значення: Файл належить host 100000:100000, бо його створив root контейнера.

Рішення: Використовуйте це для перевірки ваших припущень про мапінг. Якщо ви очікували 100033, але бачите 100000, значить процес запускався як root всередині контейнера.

Шаблони виправлення, що витримують навантаження

Шаблон A: Виділена директорія на хості для одного контейнера, власник — mapped root (швидко, чисто)

Якщо директорію використовує тільки один контейнер, найпростіший план: створити виділену директорію, потім chown її на mapped root ID контейнера.
Це нудно. Нудно — добре.

cr0x@server:~$ mkdir -p /tank/ct103-data
cr0x@server:~$ chown -R 100000:100000 /tank/ct103-data
cr0x@server:~$ chmod 0750 /tank/ct103-data

Чому працює: ви вирівнюєте власника хоста з відображеною ідентичністю root контейнера.

Коли уникати: спільні дані між контейнерами з різними UID‑діапазонами; отримаєте безлад mismatched ownership.

Шаблон B: ACL‑надання для конкретних сервісних користувачів контейнера (суворо, масштабовано)

Це дорослий підхід для спільних директорій: зберігайте власність хоста змістовною, надавайте доступ відображеним ID через ACL, встановлюйте дефолтні ACL для спадкування,
і припиніть вважати chmod вашим єдиним інструментом.

cr0x@server:~$ setfacl -m u:100033:rwx /tank/shared
cr0x@server:~$ setfacl -d -m u:100033:rwx /tank/shared

Чому працює: надає точно необхідний доступ і виживає при створенні нових файлів.

Узгодженість: потрібно оперувати видимістю ACL. Якщо команда не звикла рахуватися з getfacl, будуть повторні загадки.

Шаблон C: Один dataset на контейнер (особливо з ZFS), власність встановлена один раз

З ZFS на Proxmox виділені dataset для контейнера чисті: зручні квоти, снапшоти і реплікація.
Також знижують складність ACL, бо кожен dataset має одну «історію власності».

Налаштуйте dataset, змонтуйте на хост‑шлях, встановіть chown на mapped IDs, потім bind‑mount у контейнер. Тримайте межі даних чіткими.

Шаблон D: idmapped mounts (сучасно, елегантно, але перевіряйте платформу)

idmapped mounts можуть показувати дерево файлової системи з іншим відображенням UID/GID під час монтування.
Це фактично «зсування на рівні VFS» замість масового chown.

На практиці рішення залежить від версії Proxmox, підтримки ядра та чи хочете ви бути тим, хто першим буде дебажити це опів на третю ночі.
Якщо не можете відповісти на питання «як ми перевіримо це після оновлення ядра?», залишайтеся з власністю/ACL.

Шаблон E: Перестати робити bind‑mount і використовувати протокол шарингу з явною ідентичністю

Іноді правильне виправлення — архітектурне: не прив’язуйте директорії хоста в непривілейовані контейнери, коли бекенд — віддалена ФС,
або коли кілька контейнерів потребують спільного запису від різних користувачів.

Використовуйте NFS/SMB всередині контейнера з чітко визначеним сервісним акаунтом і консистентним UID/GID між системами. Або експортуйте сховище через сервісний шар.
Bind‑mount чудові — поки вони такими й залишаються.

Три історії з корпоративного життя (анонімізовано, болісно правдоподібно)

Міні‑історія 1: Інцидент через неправильне припущення

Платформена команда перемістила застаріле навантаження з VM у Proxmox LXC, щоб заощадити ресурси. Вони зробили правильно — залишили контейнер непривілейованим, перенесли конфіги, налаштували моніторинг.
Також вони прив’язали директорію хоста з завантаженнями додатка.

У стенді «працювало». У продакшні завантаження почали періодично падати. Додаток видавав загальні I/O‑помилки, і на виклику інженер робив те, що люди роблять під стресом:
він звинувачував спочатку додаток, потім мережу, потім «storage».

Неправильне припущення було простим: «root всередині контейнера достатньо root». Вони подивились на ls -l всередині контейнера,
побачили root:root і вирішили, що можуть писати.
Тим часом на хості директорія належала 0:0 з правами 750.
Root контейнера був host 100000 — фактично «other». Записи падали, коли додаток потрапляв у нові підкаталоги зі строгішими режимами.

Вирішення — одна послідовність команд і одна політика: chown виділеної директорії на mapped сервісний акаунт,
додати дефолтні ACL, щоб нові папки успадковували права, і задокументувати правило мапінгу в ранбуку.
Постмортем був коротким — найкращий варіант.

Міні‑історія 2: Оптимізація, що відкотилася

Інша організація хотіла зменшити дублювання. Вони створили одну спільну директорію на хості для «common assets» і прив’язали її до багатьох контейнерів.
Щоб «полегшити життя», вони виконали широкий chmod -R 777 на дереві і забули про це. Ніхто не любить квитки на права.

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

Ремедіація була неприємною. Потрібно було розплутати, які контейнери потребують тільки читання, які — запису, а які взагалі не мали б монту. Вони в підсумку замінили спільну записувану точку на read‑only для більшості контейнерів та окремі write‑локації для кожного контейнера.

Іронія: «оптимізація» заощадження місця створила вразливість безпеки і додаткове навантаження на операційну команду, бо аудит всесвітньо записуваної директорії в мульти‑тенантному середовищі — це хобі, а не контроль.

Міні‑історія 3: Нудна, але правильна практика, яка врятувала день

Команда фінансового сервісу мала політику: кожен bind‑mount повинен мати заявку з (1) метою, (2) власником даних, (3) режимом доступу, (4) mapped UID/GID,
і (5) кроками відкату. Інженери закочували очі. Але вони все одно дотримувались політики.

Оновлення ядра пройшло через кластер, і один контейнер почав падати при записі до свого монту. На виклику відкрили заявку,
побачили точні mapped UID/GID і очікувані дозволи хост‑шляху та виконали перевірки.
ACL директорії були перезаписані «скриптом прибирання», який нормалізував дозволи по всіх dataset.

Оскільки очікування були задокументовані, виправлення було однозначне: відновити записи ACL і дефолти, перевірити тестом на запис,
і додати виключення в скрипт прибирання, щоб пропускати керовані точки монтування.
Ніхто не пропонував «просто зробити привілейованим». Ніхто не гадати.

Було нудно. Було правильно. Це дозволило уникнути хаотичного ескалації і зменшило площу ураження.
Нудна операційна гігієна не просуває в моменті, але дає спокій ночами.

Поширені помилки: симптом → корінна причина → виправлення

1) Симптом: “Permission denied” при touch або записах всередині монту

Корінна причина: директорія хоста не записувана для відображеного host UID/GID (часто 100000+).

Виправлення: chown директорії на хості на mapped IDs або надати ACL для mapped IDs; перевірити через ls -ln на хості і тест запису в контейнері.

2) Симптом: “Operation not permitted” при chown всередині контейнера

Корінна причина: непривілейований контейнер не може змінювати власність на bind‑mount так, як справжній root.

Виправлення: встановіть правильну власність на хості заздалегідь; змініть додаток, щоб він не робив chown у рантаймі; або використайте виділені dataset і попередньо задані власники.

3) Симптом: Працює на існуючих файлах, не працює для нових піддиректорій

Корінна причина: відсутні дефолтні ACL або umask створює директорії без прав запису для mapped сервісного користувача.

Виправлення: встановіть дефолтні ACL на хості; переконайтесь у груповій власності та setgid‑біті, якщо використовуєте групову модель доступу.

4) Симптом: Хост‑шлях на NFS поводиться дивно; root не може писати навіть після chown

Корінна причина: опції експорту NFS (root_squash), або невідповідність UID між клієнтом і сервером, або проблеми idmapping в NFSv4.

Виправлення: використайте виділений NFS‑експорт з явним сервісним UID/GID, що відповідають mapped host IDs; розгляньте монтування NFS всередині контейнера з консистентною ідентичністю.

5) Симптом: зміни chmod/chown «не зберігаються» або поводяться непослідовно

Корінна причина: ACL або модель прав на рівні файлової системи (режим ACL ZFS, семантика SMB) переважають очікування chmod.

Виправлення: інспектуйте і керуйте ACL навмисно; підтверджуйте налаштування ACL файлової системи; уникайте змішування POSIX і NFSv4 ACL без плану.

6) Симптом: Ви «вирішили» проблему, зробивши контейнер привілейованим, і безпека напружена

Корінна причина: ви прибрали механізм безпеки замість вирівнювання ідентичностей.

Виправлення: поверніть непривілейований режим, якщо можливо; реалізуйте відображення власності/ACL або переробіть сховище; залишайте привілейовані контейнери лише з письмовими винятками та компенсуючими контролями.

7) Симптом: Тільки один вузол у кластері ламається після міграції контейнера

Корінна причина: незбіглі /etc/subuid//etc/subgid між вузлами кластера, або різні ACL/власність на спільному сховищі.

Виправлення: стандартизуйте підлеглі діапазони across вузлів; трактуйте їх як конфігурацію кластера. Перевіряйте кожен вузол, що може запускати контейнер.

8) Симптом: Директорія всередині контейнера здається належною «nobody» або дивним ID

Корінна причина: невідповідність відображення, віддалена мапа ідентичностей файлової системи або відсутній сервіс імен всередині контейнера.

Виправлення: оперуйте числовими ID; використайте ls -ln; не ганяйтеся за іменами користувачів, поки не зрозумієте числове відображення.

Контрольні списки / поетапний план

Покроково: безпечно виправити bind‑mount для одного контейнера

  1. На хості: підтвердьте unprivileged: 1 для контейнера.
  2. На хості: прочитайте /etc/subuid і знайдіть базовий номер (часто 100000).
  3. На хості: визначте шлях mp0 і подивіться ls -ldn.
  4. Вирішіть, хто потребує запису всередині контейнера (root чи сервісний користувач).
  5. Обчисліть mapped host UID/GID для цього користувача (база + UID контейнера).
  6. Для виділеної директорії: chown хост‑шляху на mapped UID/GID; встановіть консервативні біт‑режими.
  7. Для спільної директорії: додайте host ACL для mapped UID/GID; також встановіть дефолтні ACL.
  8. Перевірте всередині контейнера: тест запису і (за потреби) тест створення директорії.
  9. Перевірте на хості: власність файлів показує mapped IDs, а не 0:0.
  10. Занотуйте мапінг і очікування в ранбуку, прив’язавши до ID контейнера і точки монтування.

Покроково: зробити так, щоб спільне сховище не перетворилось на смітник безпеки

  1. Припиніть використовувати world‑writable права як «тимчасове виправлення». Тимчасово — як правило створює постійні інциденти.
  2. Створіть модель спільної групи, якщо доречно, але пам’ятайте: mapped GID відрізняються в непривілейованих контейнерах.
  3. Віддавайте перевагу read‑only bind‑mount для спільних активів. Права запису повинні бути рідкісними і обґрунтованими.
  4. Використовуйте ACL з явними mapped ID для тих контейнерів, яким потрібен запис.
  5. Встановіть дефолтні ACL і тестуйте створення вкладених директорій, а не лише одиночний файл.
  6. Реалізуйте періодичні аудити: перелік ACL і власності для керованих точок монтування.

Другий короткий жарт, потім до роботи: chmod 777 — як вимкнути датчик диму, бо він пищить — тихо, так; розумно, ні.

Питання й відповіді (FAQ)

1) Чому пише «відмова в доступі», навіть коли директорія всередині контейнера виглядає належною root?

Тому що власність перекладається. Root контейнера відображається на непривілейований host UID (часто 100000). Файлова система хоста перевіряє права щодо цього host UID.

2) Яке найшвидше надійне виправлення?

Якщо директорія присвячена цьому контейнеру: chown її на хості на mapped UID/GID (типово 100000:100000 для root контейнера) і тримайте права обмеженими.

3) Як відобразити користувача контейнера (наприклад www‑data UID 33) на host UID?

Візьміть підлеглий базовий номер із /etc/subuid (наприклад 100000) і додайте UID контейнера (33). Результат: 100033. Те саме для GID.

4) Можу я просто зробити chown всередині контейнера?

На bind‑mounted шляхах хоста в непривілейованих контейнерах зазвичай ні. Ви отримаєте «Operation not permitted». Встановлюйте власність на хості або використовуйте ACL.

5) Чи прийнятно переключитись на привілейований контейнер?

Іноді так, але ставтесь до цього як до винятку з компенсуючими контролями. Привілейовані контейнери значно підвищують ризик для хоста. Узгодження UID/GID майже завжди кращий шлях.

6) Чому працює на одному вузлі Proxmox, але не після міграції контейнера?

Часта причина: різні /etc/subuid//etc/subgid на вузлах. Ті самі UID контейнера відображаються на різні host UID, тому права ламаються.

7) А NFS? Чому це додатково болісно?

NFS додає серверне застосування прав і опції експорту як root_squash. Мapped UID контейнера може не відповідати очікуванням NFS‑сервера.
Розв’язуйте це через консистентні сервісні ідентичності або виділені експортні шари з явною стратегічною відповідністю UID/GID.

8) Використовувати ACL чи chown?

Використовуйте chown для виділених шляхів на контейнер. Використовуйте ACL, коли власність має залишатись root хоста або коли кілька ідентичностей потребують контрольованого доступу.
Якщо потрібно й те, й інше, ви, ймовірно, моделюєте спільне сховище — поводьтесь з ним як зі справжнім шарованим сховищем, а не як із тимчасовим bind‑mount.

9) Яка роль idmapped mounts тут?

Вони можуть вирішити переклад UID/GID чисто під час монтування, без необхідності chown даних. Але операційна підтримка змінюється залежно від ядра/інструментів.
Якщо не можете впевнено тестувати й моніторити це, лишайтеся при перевіреному підході власності/ACL.

10) Як запобігти повторенню?

Стандартизувати subuid/subgid діапазони в кластері, документувати очікування щодо власності/ACL для кожного контейнера, і додати простий тест валідації (створити файл, створити директорію).

Висновок: наступні кроки, які можна зробити сьогодні

«Відмова в доступі» при bind‑mount в Proxmox LXC майже ніколи не є містичною. Це математика плюс політика:
ваш користувач контейнера відображається на інший host UID/GID, і хост‑шлях йому не дозволяє.

Зробіть наступне:

  1. Виберіть один контейнер з проблемним bind‑mount і занотуйте: ID контейнера, mpX шлях, прапорець unprivileged і базу subuid.
  2. Обчисліть mapped host UID/GID для сервісного користувача, що потребує доступу.
  3. Виправте це або шляхом виділеного chown (однокористувацький шлях), або ACL + дефолтні ACL (спільний шлях).
  4. Перевірте через тест запису і тест створення піддиректорії. Майбутній ви дбайливо ставиться до тесту створення піддиректорії.
  5. Уніфікуйте /etc/subuid//etc/subgid по вузлах перед наступною міграцією, щоб уникнути сюрпризів.

Перефразована ідея від John Allspaw: справжня робота в операціях — проектувати системи і процеси, які роблять відмови зрозумілими і відновлюваними.

← Попередня
Athlon: рік, коли Intel справді злякалася
Наступна →
Персистентність Redis у Docker без перетворення на повільний дисковий додаток

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