HomeLab.net.ua

Твоє залізо. Твої правила. Твоя мова.

NFS для Docker у Linux: права, automount і permission denied

Опубліковано

NFS для Docker у Linux: права, automount і permission denied

NFS для Docker у Linux підходить, коли кільком хостам потрібен спільний каталог або дані вже лежать на NAS чи окремому сервері. Найчастіше все ламається не на самому NFS, а на правах, порядку старту після ребута і хибному mount на хості. Робоча схема тут одна: спершу підняти mount у Linux, потім віддати його контейнеру як bind mount.

Головне рішення просте: не монтувати NFS всередину Compose через volume-драйвери, а спершу стабільно підняти mount на хості. Docker тоді бачить звичайний каталог. Таку схему простіше перевіряти, бо весь ланцюжок видно через findmnt, stat і звичайні права файлової системи.

Коли NFS справді доречний, а коли краще локальний том

NFS для Docker у Linux має сенс для медіатеки, бекапів, архівів, спільних каталогів і сервісів, яким не критична локальна затримка диска. Для баз даних, черг, індексів пошуку і дрібного синхронного I/O краще локальний том. Там поведінка передбачуваніша.

Хороший сценарій – контейнер читає великі файли або пише їх нечасто: фотоархів, відео, резервні копії, завантаження. Поганий сценарій – PostgreSQL, MariaDB, Redis, Elasticsearch. Вони чутливі до латентності, блокувань і коротких провалів мережі.

Є ще одна межа: кількість клієнтів і характер запису. Для одного-двох Docker-хостів зі спокійним I/O схема живе нормально. Коли кілька вузлів активно пишуть у той самий каталог, починаються конфлікти з блокуванням, кешем атрибутів і логікою самого застосунку.

Якщо дані потрібні тільки одному серверу, локальний SSD майже завжди кращий. Для такого випадку корисніше продумати локальний LVM у Linux або підібрати NVMe SSD для домашнього сервера, ніж тягнути мережевий mount заради звичайного container volume.

Як підготувати NFS-сервер: пакет, export і параметри без небезпечної магії

Для каталогу контейнерів потрібен звичайний export з чітко визначеною мережею клієнтів і без зайвих послаблень. Базовий варіант – rw,sync,no_subtree_check. Опцію no_root_squash краще не вмикати без конкретної причини.

На Debian або Ubuntu достатньо встановити серверний пакет і створити окремий каталог під дані контейнерів:

sudo apt update
sudo apt install nfs-kernel-server
sudo mkdir -p /srv/docker-data
sudo chown 1000:1000 /srv/docker-data
sudo chmod 775 /srv/docker-data

Далі описати export у /etc/exports. Приклад для мережі 192.168.1.0/24:

/srv/docker-data 192.168.1.0/24(rw,sync,no_subtree_check)

Після зміни конфіга експорт треба перечитати:

sudo exportfs -ra
sudo systemctl restart nfs-kernel-server
sudo exportfs -v

sync дає повільніший запис, зате менше шансів отримати биті дані після збою живлення. Для домашнього сервера це нормальний компроміс. no_subtree_check теж варто лишити, бо вкладені export часто дають зайві перевірки і дивні відмови в доступі.

Часта помилка – лікувати все через no_root_squash. Такий обхід маскує проблему з правами і відкриває root-користувачу з клієнта повний доступ на сервері. Якщо контейнер пише не туди або з неправильним UID, треба правити ownership і користувача процесу, а не послаблювати export. Деталі по NFS є в офіційній документації NFS.

Які права доступу потрібні Docker на NFS і чому ламається UID/GID

Якщо контейнер бачить каталог, але не може писати, причина майже завжди в UID/GID, а не в самому mount. NFS перевіряє числові ідентифікатори. Ім’я користувача всередині контейнера ролі не грає.

Наприклад, застосунок працює від UID 1000, а каталог на сервері належить root:root. У такій схемі читання ще може пройти, а запис закінчиться permission denied. Збоку це виглядає як проблема Docker, хоча причина банальна.

Як подивитися UID/GID контейнера

Перевірка займає хвилину. Треба подивитися, під ким іде процес у контейнері:

docker exec -it app sh
id
umask

Якщо контейнер не стартує, варто глянути документацію застосунку або Compose-файл. Часто UID/GID задають через user:, PUID, PGID або змінні середовища. Коли Compose вже розрісся, простіше спершу привести до ладу сам файл. Для цього стане в пригоді матеріал про структуру Docker Compose і типові помилки.

Що виправляти на сервері

Надійний варіант – дати каталогу той самий UID/GID, який використовує контейнер. Якщо сервіс працює від 1000:1000, серверний каталог має виглядати так:

sudo chown -R 1000:1000 /srv/docker-data
sudo find /srv/docker-data -type d -exec chmod 775 {} \;
sudo find /srv/docker-data -type f -exec chmod 664 {} \;

Іноді застосунок усередині контейнера запускається від root, а потім скидає привілеї. Через це перші файли створюються, а наступний запис падає. Таку поломку часто плутають з невдалими export options.

NFS для Docker у Linux зазвичай простіше заводиться через один спільний UID для сервісів, які пишуть у той самий каталог. Коли кожен контейнер має свій UID і всі чіпають одні файли, конфлікти з ownership накопичуються дуже швидко.

Як змонтувати NFS на Docker-хості через fstab і systemd automount

Щоб Docker не падав після ребута або короткої затримки мережі, mount краще описати в /etc/fstab з x-systemd.automount і залежністю від мережі. Тоді точка монтування піднімається за зверненням, а не блокує завантаження хоста.

На клієнті достатньо встановити пакет утиліт:

sudo apt update
sudo apt install nfs-common
sudo mkdir -p /mnt/docker-data

Далі додати рядок у /etc/fstab:

192.168.1.10:/srv/docker-data /mnt/docker-data nfs defaults,_netdev,x-systemd.automount,x-systemd.requires=network-online.target,nofail 0 0

Після цього перечитати конфігурацію й активувати mount:

sudo systemctl daemon-reload
sudo systemctl restart remote-fs.target
sudo systemctl restart mnt-docker\x2ddata.automount
ls /mnt/docker-data

nofail корисний, бо система продовжить старт, навіть якщо сервер ще не піднявся. x-systemd.automount прибирає типову гонку, коли Docker стартує раніше за мережеве сховище. Без цього після ребута легко отримати порожній локальний каталог замість мережевого mount.

Ще одна часта поломка – DNS-ім’я в fstab до появи мережі. Якщо хост стартує нестабільно, краще вказати IP-адресу сервера. Коли контейнерам окремо потрібна своя мережева топологія, це вже інша задача. Тут доречний матеріал про мережевий режим Docker, бо проблеми зі сховищем інколи плутають із суто мережевими.

Як підключити сховище в Docker Compose без дивних volume-драйверів

Стабільніший варіант для Compose – змонтувати NFS на хості й віддати контейнеру bind mount. Тоді діагностика робиться стандартними Linux-інструментами. Варіант із driver_opts теж працює, але ускладнює відладку і сильніше прив’язує поведінку до Docker volume driver.

Рекомендований варіант через bind mount

Якщо каталог уже доступний на хості в /mnt/docker-data/app, у Compose достатньо такого опису:

services:
  app:
    image: nginx:stable
    user: "1000:1000"
    volumes:
      - /mnt/docker-data/app:/usr/share/nginx/html

Тут межа відповідальності видно одразу. Linux відповідає за mount і права. Docker лише прокидає каталог у контейнер.

Коли driver_opts тільки заважає

Підключення NFS прямо як named volume зручно в коротких демо, але в робочому стеку додає прихований стан усередині Docker. Після міграції Compose-файла на інший хост або зміни параметрів mount такі томи доводиться окремо видаляти й створювати заново. Для self-hosted сервісів це зайвий шар складності.

Якщо стек уже складається з кількох сервісів, бази й кешу, краще тримати схему прозорою. Приклад нормальної організації файлів і сервісів є в матеріалі про Docker Compose для self-hosted сервісів. Там добре видно, що мережеве сховище варто відокремлювати від локальних томів бази даних.

Як перевірити, що монтування, права і контейнер працюють коректно

Перевірка йде в три кроки: mount на хості, запис під потрібним UID/GID і доступ із контейнера. Якщо кожен крок пройшов окремо, причина збоїв звужується дуже швидко. Перевіряти все тільки через логи контейнера незручно.

Перевірка mount на хості

mount | grep /mnt/docker-data
findmnt /mnt/docker-data
df -h /mnt/docker-data
stat -f /mnt/docker-data

findmnt зазвичай дає найчистішу картину: який тип файлової системи змонтований і звідки. Якщо тут видно локальний шлях замість NFS, контейнер уже пише не туди. Після невдалого старту це одна з найтиповіших пасток.

Перевірка запису під правильним UID

sudo -u \#1000 touch /mnt/docker-data/test-write
ls -ln /mnt/docker-data/test-write
rm /mnt/docker-data/test-write

Ключовий момент – тестувати не від shell-користувача, а від того UID, який працює в контейнері. Інакше легко отримати хибне відчуття, що запис працює, хоча сервіс далі падає з відмовою в доступі.

Перевірка з контейнера

docker run --rm -u 1000:1000 \
  -v /mnt/docker-data:/data \
  alpine:latest sh -c 'id && touch /data/container-test && ls -ln /data/container-test && rm /data/container-test'

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

Чому виникає permission denied на NFS і де зазвичай ламається конфіг

permission denied на NFS рідко має одну причину. Найчастіше винні неправильний UID/GID, root squash, запис у підкаталог з іншим owner, відсутній mount після ребута або контейнер, який стартував раніше за automount. Шукати поломку треба від хоста до контейнера.

Контейнер пише не під тим UID

Симптом простий: на хості руками файл створюється, у контейнері – ні. Треба звірити id усередині контейнера з ls -ln на серверному каталозі. Імена користувачів тут нічого не вирішують.

Спрацював root squash

Якщо контейнер іде від root, сервер може зіставити його з анонімним користувачем. Тоді створення файлів падає навіть при широких правах на перший погляд. Виправлення тут у запуску сервісу під конкретним UID або в коректному ownership каталогу.

Docker стартував до появи mount

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

Проблема в підкаталозі, а не в корені export

Кореневий каталог часто налаштований правильно, але застосунок створює вкладені директорії з іншим owner або жорсткою маскою прав. Після цього перший запуск проходить, а наступний падає. Перевіряти треба саме той шлях, куди сервіс реально пише кеш, uploads або тимчасові файли.

SELinux або AppArmor плутають із NFS

На деяких системах відмова в доступі виглядає як звичайний файловий збій, хоча причина в політиці безпеки. Якщо UID/GID збігаються, mount справжній, а запис усе одно блокується, треба дивитися журнали системи, а не тільки export і Compose.

journalctl -xe
dmesg | tail -n 50
docker logs <container-name>

NFS для Docker у Linux добре підходить для спільного файлового сховища з великими файлами і спокійним I/O. Для сервісів із чутливим дисковим навантаженням такий підхід швидко стає джерелом зайвої нестабільності. Якщо дані не треба ділити між вузлами, локальний том майже завжди дає менше проблем.

Коментарі 2

  1. Вітаю! Дивлячись з якого питання 😉
  2. На жаль поки не маю часу... 🙁