Mount, /etc/fstab и mount namespaces
mount — одна из тех команд Linux, которые кажутся простыми («монтируй диск»), но скрывают огромную глубину: десятки опций, концепция bind mount, mount namespaces, propagation modes, и связь с контейнерами. Понимание mount это понимание того, как Linux строит своё единое дерево из десятков отдельных файловых систем.
В современных Linux всё построено на mount-ах: контейнеры — через mount namespaces, system-d-сервисы — через PrivateTmp/ProtectHome, Docker volumes — через bind mount, /tmp в RAM — через tmpfs mount. Понимание этих механизмов делает контейнерные технологии прозрачными.
В этом уроке: классический mount disk, опции (ro, noexec, nosuid), /etc/fstab для автоматики, bind mount как способ показать одно место в двух точках дерева, mount namespaces — основа контейнеров.
Mount disk — классика
# Подключить ext4 раздел в /mnt/data:
sudo mount /dev/sdb1 /mnt/data
# С опциями:
sudo mount -t ext4 -o ro,noexec,nosuid /dev/sdb1 /mnt/data
# ^^^^^^^ ^^^^^^^^^^^^^^^^^^^
# явный type опции (read-only, no execute, no suid)
# Отмонтировать:
sudo umount /mnt/data
# Если занято -- посмотреть кто держит:
sudo umount /mnt/data
# umount: /mnt/data: target is busy.
sudo lsof /mnt/data
sudo fuser -m /mnt/data
# Принудительно (только для NFS / dead processes):
sudo umount -f /mnt/data
# Lazy unmount -- отмонтировать когда освободится:
sudo umount -l /mnt/data
Опции mount — это десятки параметров, влияющих на безопасность и производительность:
noexec + nosuid + nodev — стандартный triplet для пользовательских и временных директорий. systemd ставит их автоматически для PrivateTmp.
/etc/fstab — автоматический mount при boot
/etc/fstab — описание всех mount-точек, которые должны быть автоматически смонтированы при загрузке системы:
cat /etc/fstab
# <device> <mount point> <fstype> <options> <dump> <pass>
# UUID=12345678-9abc-def0-1234-56789abcdef0 / ext4 defaults,relatime 0 1
# UUID=fedcba98-7654-3210-fedc-ba9876543210 /home ext4 defaults,relatime 0 2
# UUID=11111111-2222-3333-4444-555555555555 none swap sw 0 0
# tmpfs /tmp tmpfs defaults,size=4G 0 0
# nas:/export/share /mnt/nas nfs defaults,_netdev 0 0
Поля:
- Device — путь к устройству или специальный токен. Лучше UUID или LABEL, чем /dev/sdaX (последние могут меняться).
- Mount point — куда монтировать.
- Filesystem type — ext4, xfs, tmpfs, nfs, btrfs, vfat…
- Options — через запятую.
defaults= rw,suid,dev,exec,auto,nouser,async. - Dump — для бэкапа через dump (legacy, обычно 0).
- Pass — fsck order. 1 для root, 2 для остальных, 0 — не проверять.
# Получить UUID для записи в fstab:
sudo blkid
# /dev/nvme0n1p1: UUID="ABCD-1234" TYPE="vfat" (EFI partition)
# /dev/nvme0n1p2: UUID="..." TYPE="ext4" (root)
# /dev/sdb1: UUID="..." TYPE="ext4" PARTUUID="..."
# Тестировать новую запись fstab без перезагрузки:
sudo mount -a
# Монтирует всё в fstab, что ещё не смонтировано. Если строка плохая -- ошибка
_netdev опция — ключевая для сетевых ФС (NFS, CIFS): говорит systemd подождать сеть перед монтированием. Без неё система может зависнуть на boot, ожидая сеть в init scripts.
# Если fstab сломан и не загружается:
# Boot в emergency mode, в grub добавить:
# systemd.unit=emergency.target
# Или: nofail в опциях fstab -- не блокировать boot
Bind mount — одна директория в двух местах
mount --bind src dst — показать содержимое src в dst. Это не копия и не symlink — VFS буквально показывает inode-структуру src в точке dst.
# Простой пример:
mkdir /tmp/work
echo "data" > /tmp/work/file.txt
mkdir /tmp/mounted
sudo mount --bind /tmp/work /tmp/mounted
ls /tmp/mounted/
# file.txt
cat /tmp/mounted/file.txt
# data
# Запись через любой путь видна с другого:
echo "more" > /tmp/mounted/another.txt
ls /tmp/work/
# file.txt another.txt
# В отличие от symlink, bind mount -- настоящий новый mount point:
mount | grep work
# /dev/nvme0n1p2 on /tmp/mounted type ext4 (rw)
sudo umount /tmp/mounted
Зачем bind mount, если есть symlinks? Несколько ситуаций:
- Symlinks не работают в chroot, если target вне chroot. Bind mount работает.
- Поведение в некоторых программах. Например, sudo с symlinks может ругаться, с bind mount — нет.
- Read-only override.
mount --bind -o ro /etc/sensitive /etc/sensitiveсделает её read-only даже для root. - Different mount points. Bind mount можно отмонтировать без удаления.
- Container volumes. Docker volumes реализуются через bind mount.
mount --rbind (recursive bind) — bind включая все sub-mounts. Например, --rbind / принесёт все вложенные mount-точки.
Mount namespaces — основа контейнеров
Mount namespace — одна из главных Linux намespace-фишек. Каждый namespace имеет своё дерево mount-ов. Процессы в разных namespaces видят разные mount-точки.
# Стандартный namespace системы:
mount | wc -l
# 25 -- 25 mounts видит обычный процесс
# Создать новый mount namespace через unshare:
sudo unshare --mount --propagation private bash
# Внутри:
mount -t tmpfs none /mnt
ls /mnt # пусто, новая tmpfs
echo "in namespace" > /mnt/test
# В другом терминале (хост):
ls /mnt # пусто или старое содержимое, tmpfs не виден
mount | grep '/mnt' # ничего
# Вернуться в namespace shell:
cat /mnt/test
# in namespace
# При exit shell namespace умирает, mount исчезает
exit
Это и есть основа Docker, LXC, podman — они создают mount namespace и внутри него выстраивают своё дерево ФС:
systemd использует mount namespaces для изоляции сервисов:
# Посмотреть unit-файл:
systemctl cat nginx | head
# ProtectHome=yes -- /home недоступен через свой mount namespace
# ProtectSystem=full -- /usr, /etc read-only
# PrivateTmp=yes -- свой /tmp в отдельном tmpfs
# PrivateDevices=yes -- свой /dev только с минимумом
Эти настройки — через mount namespaces + bind mount с разными permissions. Сервис в своём namespace видит limited view.
Propagation modes — как меняются mounts
Когда вы делаете mount в одном namespace, он может (или не может) распространиться на другие namespaces. Это контролируется propagation modes:
- shared — mount распространяется во все «shared» namespaces. Default в большинстве дистрибуций.
- private — mount не распространяется никуда. Изоляция.
- slave — получает propagation от master, но не отправляет своим.
- unbindable — нельзя сделать bind mount (защита от рекурсии).
# Посмотреть propagation:
findmnt -o TARGET,PROPAGATION
# Сделать private (контейнерный подход):
sudo mount --make-private /
# Это то, что Docker делает при старте:
# 1. unshare --mount --propagation private
# 2. mount overlay для image
# 3. mount bind для volumes
# 4. chroot или pivot_root в новое дерево
Как Docker строит файловую систему контейнера
Loop devices — mount файла как ФС
Иногда нужно работать с disk image-файлом как с реальным диском:
# Создать image-файл и форматировать как ext4:
dd if=/dev/zero of=/tmp/disk.img bs=1M count=100
mkfs.ext4 /tmp/disk.img
# Mount файла как ФС через loop:
sudo mount -o loop /tmp/disk.img /mnt/image
# Теперь /mnt/image -- ext4 ФС из файла /tmp/disk.img
ls /mnt/image
# lost+found
# Создать файл, посмотреть -- работает как обычная ФС:
echo "data" | sudo tee /mnt/image/test
sudo cat /mnt/image/test
# Отмонтировать:
sudo umount /mnt/image
Loop devices — основа cloud images, ISO-mount, sparse files в виртуализации.
Попробуй сам
Проверьте все mount-точки своей системы:
findmnt
# Иерархический список всех mount-точек
# Видны типы (ext4, tmpfs, sysfs, proc, cgroup2) и опции
# Сколько mount-точек:
findmnt -n | wc -l
# Все bind mounts:
findmnt -t none --types bind 2>/dev/null
# Часто systemd создаёт bind mounts для PrivateTmp
Создайте свой mount namespace:
# В одном терминале:
sudo unshare --mount bash
mount -t tmpfs -o size=50M none /tmp
df -h /tmp # видит 50 МБ tmpfs
# Создать файлы только в этом namespace:
for i in {1..10}; do echo $i > /tmp/file$i; done
ls /tmp/ # видим 10 файлов
# В другом терминале (host):
ls /tmp/ # обычное /tmp, файлов нет
# Exit -- namespace умирает:
exit
Эксперимент с bind mount:
# Создать структуру:
mkdir -p /tmp/src /tmp/dst
echo "in src" > /tmp/src/file.txt
# Bind mount:
sudo mount --bind /tmp/src /tmp/dst
ls /tmp/dst # file.txt
# Запись в dst -- видна в src:
echo "from dst" > /tmp/dst/another.txt
ls /tmp/src/ # file.txt another.txt
# Сделать /tmp/dst read-only без изменения src:
sudo mount -o remount,ro,bind /tmp/dst
echo "test" > /tmp/dst/new.txt
# bash: /tmp/dst/new.txt: Read-only file system
# /tmp/src ещё writable:
echo "test" > /tmp/src/new.txt
# OK
# Cleanup:
sudo umount /tmp/dst
rm -rf /tmp/src /tmp/dst