MOUNT_NAMESPACES(7) | Руководство программиста Linux | MOUNT_NAMESPACES(7) |
mount_namespaces - обзор пространств имён монтирования в Linux
Обзор пространств имён смотрите в namespaces(7).
Пространства имён монтирования позволяют изолировать список точек монтирования, видимый процессами в каждом экземпляре пространства имён. То есть, процессы в каждом из экземпляров пространства имён монтирования будут видеть разные иерархии в одном и том же каталоге.
Данные файлов /proc/[pid]/mounts, /proc/[pid]/mountinfo и /proc/[pid]/mountstats (описаны в proc(5)) соответствуют пространству имён монтирования, в котором расположен процесс с PID [pid] (для всех процессов, которые расположены в одном пространстве имён монтирования, данные в этих файлах одинаковы).
Когда процесс создаёт новое пространство имён монтирования с помощью clone(2) или unshare(2) с флагом CLONE_NEWNS список точек монтирования для нового пространства имён представляет собой копию списка точек монтирования вызывающего. Последующие изменения списка точек монтирования (mount(2) и umount(2)) в любом пространстве имён монтирования не влияют (по умолчанию) на список точек монтирования, видимый из другого пространства имён (но смотрите далее описание общих поддеревьев).
Отметим следующие моменты относительно пространств имён монтирования:
После завершения реализации пространств имён монтирования опыт использования показал, что полученная изоляция, в некоторых случаях, слишком велика. Например, чтобы только что смонтированный оптический диск сделать доступным в во всех пространствах имён, требуется операция монтирования в каждом пространстве имён. Для этого случаях и других в Linux 2.6.15 были добавлены общие поддеревья. Это свойство предоставляет автоматическое, управляемое распространение событий монтирования и размонтирования в пространствах имён (или, точнее, между членами равноправной группы, которые обмениваются событиями между собой).
Каждая точка монтирования помечается (в mount(2)) одним из следующих типов распространения:
Описание типа распространения, назначаемого на новое монтирование, смотрите в ЗАМЕЧАНИЯХ.
Тип распространения имеется у каждой точки монтирования; некоторые точки монтирования могут быть помечены как общие (каждая общая точка монтирования является членом определённой равноправной группы), а некоторые как индивидуальные (или подчинённые или непривязываемые).
Заметим, что тип распространения монтирования определяет, будет ли монтирование или размонтирование распространяться точки монтирования, находящиеся на одну ступень ниже точки, где возникло событие. То есть, тип распространения не влияет на распространение событий для внуков и в дальнейшем удаляемых потомков точки монтирования. Это случается, если сама точка монтирования размонтируется из-за действия типа распространения который, в сущности, влияние родительской точки монтирования.
Члены добавляются в равноправную группу, если точка монтирования помечается как общая, или:
В обоих случаях новая точка монтирования присоединяется к равноправной группе, в которую входит существующая точка монтирования.
Новая равноправная группа также создаётся, если дочерняя точка монтирования создана под существующей точкой монтирования, помеченной как общая. В этом случае, новая дочерняя точка монтирования также помечается как общая и получаемая равноправная группа состоит из всех точек монтирования, которые дублируются в равноправных группах родительской точки монтирования.
Точка монтирования перестаёт быть членом равноправной группы, когда происходит её явное размонтирование или неявное из-за удаления пространства имён монтирования (из-за отсутствия участвующих процессов).
Тип распространения точек монтирования в пространстве имён монтирования может узнать через «необязательные поля» в файле /proc/[pid]/mountinfo (описание файла смотрите в proc(5)). В необязательных полях этого файла могут появляться следующие метки:
Если нет ни одной из вышеперечисленных меток, то точка монтирования является индивидуальной.
Предположим, что на терминале в первоначальном пространстве имён монтирования мы помечаем одну точку монтирования как общую, а другую — как индивидуальную, и затем смотрим точки монтирования в /proc/self/mountinfo:
sh1# mount --make-shared /mntS sh1# mount --make-private /mntP sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//' 77 61 8:17 / /mntS rw,relatime shared:1 83 61 8:15 / /mntP rw,relatime
Из вывода /proc/self/mountinfo мы видим, что /mntS является общей точкой монтирования в равноправной группе 1, и что /mntP не имеет необязательных меток, то есть это индивидуальная точка монтирования. Первые два поля в каждой записи этого файла содержат уникальный идентификатор этой точки монтирования и идентификатор точки монтирования родительской точки монтирования. Далее в файле мы видим, что родительская точка монтирования /mntS и /mntP является корневым каталогом, /, которая смонтирована как индивидуальная:
sh1# cat /proc/self/mountinfo | awk '$1 == 61' | sed 's/ - .*//' 61 0 8:2 / / rw,relatime
На втором терминале мы создаём новое пространство имён монтирования, в котором запускаем вторую оболочку, и смотрим точки монтирования:
$ PS1='sh2# ' sudo unshare -m --propagation unchanged sh sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//' 222 145 8:17 / /mntS rw,relatime shared:1 225 145 8:15 / /mntP rw,relatime
Новое пространство имён монтирования получает копию точек монтирования из начального пространства имён монтирования. Эти новые точки монтирования имеют тот же тип распространения, но другие уникальные идентификаторы монтирования (при создании нового пространства имён монтирования передача параметра --propagation unchanged программе unshare(1) не даёт помечать все точки монтирования как индивидуальные (что делается по умолчанию)).
Далее на втором терминале мы создаём подмонтирования в каталоге /mntS и /mntP и смотрим что получилось:
sh2# mkdir /mntS/a sh2# mount /dev/sdb6 /mntS/a sh2# mkdir /mntP/b sh2# mount /dev/sdb7 /mntP/b sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//' 222 145 8:17 / /mntS rw,relatime shared:1 225 145 8:15 / /mntP rw,relatime 178 222 8:22 / /mntS/a rw,relatime shared:2 230 225 8:23 / /mntP/b rw,relatime
Из показанного выше мы видим, что /mntS/a была создана как общая (унаследовала от родительской точки монтирования), а /mntP/b — как индивидуальная точка монтирования.
Если вернуться на первый терминал и и ещё раз посмотреть параметры, то можно увидеть, что новая точка монтирования, созданная в общей точке монтирования /mntS, передалась в свою равноправную группу монтирования (в начальном пространстве имён монтирования), а новая точка монтирования, созданная в индивидуальной точке монтирования /mntP, нет:
sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//' 77 61 8:17 / /mntS rw,relatime shared:1 83 61 8:15 / /mntP rw,relatime 179 77 8:22 / /mntS/a rw,relatime shared:2
Создание подчинённой точки монтирования позволяет ей принимать распространяемые события монтирования и размонтирования из главной общей равноправной группы, но запрещает распространять события в эту главную группу. Это полезно, если требуется, скажем, принимать событие монтирования оптического диска в главной общей равноправной группе (в другом пространстве имён монтирования), но не нужно, чтобы события монтирования и размонтирования в подчинённой точке монтирования передавались в другие пространства имён.
Для демонстрации следствия подчинённости сначала создадим две общие точки монтирования в начальном пространстве имён монтирования:
sh1# mount --make-shared /mntX sh1# mount --make-shared /mntY sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//' 132 83 8:23 / /mntX rw,relatime shared:1 133 83 8:22 / /mntY rw,relatime shared:2
На втором терминале создадим новое пространство имён монтирования и посмотрим точки монтирования:
sh2# unshare -m --propagation unchanged sh sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//' 168 167 8:23 / /mntX rw,relatime shared:1 169 167 8:22 / /mntY rw,relatime shared:2
Далее в новом пространстве имён монтирования пометим одну из точек монтирования как подчинённую:
sh2# mount --make-slave /mntY sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//' 168 167 8:23 / /mntX rw,relatime shared:1 169 167 8:22 / /mntY rw,relatime master:2
Из показанного выше видно, что /mntY теперь подчинённая точка монтирования, которая принимать распространяемые события от общей равноправной группы с ID 2.
Далее в новом пространстве имён создадим подмонтирования в /mntX и /mntY:
sh2# mkdir /mntX/a sh2# mount /dev/sda3 /mntX/a sh2# mkdir /mntY/b sh2# mount /dev/sda5 /mntY/b
Если посмотреть состояние точек монтирования в новом пространстве имён монтирования можно увидеть, что /mntX/a создана как новая общая точка монтирования (наследует «общность» от родительской точки монтирования), а /mntY/b создана как индивидуальная точка монтирования:
sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//' 168 167 8:23 / /mntX rw,relatime shared:1 169 167 8:22 / /mntY rw,relatime master:2 173 168 8:3 / /mntX/a rw,relatime shared:3 175 169 8:5 / /mntY/b rw,relatime
Если вернуться на первый терминал (в начальное пространство имён монтирования), то можно увидеть, что точка монтирования /mntX/a передалась в свою равноправную группу (общую с /mntX), а точка монтирования /mntY/b нет:
sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//' 132 83 8:23 / /mntX rw,relatime shared:1 133 83 8:22 / /mntY rw,relatime shared:2 174 132 8:3 / /mntX/a rw,relatime shared:3
Теперь создадим новую точку монтирования в /mntY в первом терминале:
sh1# mkdir /mntY/c sh1# mount /dev/sda1 /mntY/c sh1# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//' 132 83 8:23 / /mntX rw,relatime shared:1 133 83 8:22 / /mntY rw,relatime shared:2 174 132 8:3 / /mntX/a rw,relatime shared:3 178 133 8:1 / /mntY/c rw,relatime shared:4
Если посмотреть точки монтирования во втором пространстве имён монтирования, то можно увидеть, что на этот раз новая точка монтирования передалась в подчинённую точку монтирования и что эта новая точка монтирования сама является подчинённой (равноправной группе 4):
sh2# cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//' 168 167 8:23 / /mntX rw,relatime shared:1 169 167 8:22 / /mntY rw,relatime master:2 173 168 8:3 / /mntX/a rw,relatime shared:3 175 169 8:5 / /mntY/b rw,relatime 179 169 8:1 / /mntY/c rw,relatime master:4
Одним из основных назначений непривязываемых точек монтирования является решение проблемы «взрыва точек монтирования» — повторяющееся выполнение привязки точки монтирования поддерева верхнего уровня в точках монтирования нижнего уровня. Эта проблема показана в сеансе далее.
Предположим, что имеется система с следующими точками монтирования:
# mount | awk '{print $1, $2, $3}' /dev/sda1 on / /dev/sdb6 on /mntX /dev/sdb7 on /mntY
Предположим, что нужно рекурсивно привязать точки монтирования корневого каталога в нескольких пользовательских домашних каталогах. Сделаем это для первого пользователя и посмотрим точки монтирования:
# mount --rbind / /home/cecilia/ # mount | awk '{print $1, $2, $3}' /dev/sda1 on / /dev/sdb6 on /mntX /dev/sdb7 on /mntY /dev/sda1 on /home/cecilia /dev/sdb6 on /home/cecilia/mntX /dev/sdb7 on /home/cecilia/mntY
Повторяя эту операцию для второго пользователя сталкиваемся с проблемой взрывного роста:
# mount --rbind / /home/henry # mount | awk '{print $1, $2, $3}' /dev/sda1 on / /dev/sdb6 on /mntX /dev/sdb7 on /mntY /dev/sda1 on /home/cecilia /dev/sdb6 on /home/cecilia/mntX /dev/sdb7 on /home/cecilia/mntY /dev/sda1 on /home/henry /dev/sdb6 on /home/henry/mntX /dev/sdb7 on /home/henry/mntY /dev/sda1 on /home/henry/home/cecilia /dev/sdb6 on /home/henry/home/cecilia/mntX /dev/sdb7 on /home/henry/home/cecilia/mntY
В /home/henry рекурсивно добавились не только точки монтирования /mntX и /mntY, но и рекурсивные точки монтирования этих каталогов, смонтированных в /home/cecilia, который мы создали на предыдущем шаге. Далее повторяя процесс для третьего пользователя, станет очевидно, что взрывной рост происходит экспоненциально:
# mount --rbind / /home/otto # mount | awk '{print $1, $2, $3}' /dev/sda1 on / /dev/sdb6 on /mntX /dev/sdb7 on /mntY /dev/sda1 on /home/cecilia /dev/sdb6 on /home/cecilia/mntX /dev/sdb7 on /home/cecilia/mntY /dev/sda1 on /home/henry /dev/sdb6 on /home/henry/mntX /dev/sdb7 on /home/henry/mntY /dev/sda1 on /home/henry/home/cecilia /dev/sdb6 on /home/henry/home/cecilia/mntX /dev/sdb7 on /home/henry/home/cecilia/mntY /dev/sda1 on /home/otto /dev/sdb6 on /home/otto/mntX /dev/sdb7 on /home/otto/mntY /dev/sda1 on /home/otto/home/cecilia /dev/sdb6 on /home/otto/home/cecilia/mntX /dev/sdb7 on /home/otto/home/cecilia/mntY /dev/sda1 on /home/otto/home/henry /dev/sdb6 on /home/otto/home/henry/mntX /dev/sdb7 on /home/otto/home/henry/mntY /dev/sda1 on /home/otto/home/henry/home/cecilia /dev/sdb6 on /home/otto/home/henry/home/cecilia/mntX /dev/sdb7 on /home/otto/home/henry/home/cecilia/mntY
Проблемы взрывного роста монтирования в показанном сценарии можно избежать, если делать каждое новое монтирование непривязываемым. В этом случае рекурсивное монтирование корневого каталоге не копирует непривязываемые точки монтирования. Сделаем такое монтирование для первого пользователя:
# mount --rbind --make-unbindable / /home/cecilia
Перед тем как продолжить, посмотрим, что непривязываемые точки монтирования действительно нельзя привязать:
# mkdir /mntZ # mount --bind /home/cecilia /mntZ mount: wrong fs type, bad option, bad superblock on /home/cecilia, missing codepage or helper program, or other error В некоторых случаях полезная информация может быть найдена в syslog - попробуйте dmesg | tail или что-то в этом роде.
Теперь создадим непривязываемое рекурсивное монтирования для остальных пользователей:
# mount --rbind --make-unbindable / /home/henry # mount --rbind --make-unbindable / /home/otto
Если посмотреть список точек монтирования, то можно увидеть, что взрывного роста не произошло, так как непривязываемые точки монтирования не скопировались в каждый пользовательский каталог:
# mount | awk '{print $1, $2, $3}' /dev/sda1 on / /dev/sdb6 on /mntX /dev/sdb7 on /mntY /dev/sda1 on /home/cecilia /dev/sdb6 on /home/cecilia/mntX /dev/sdb7 on /home/cecilia/mntY /dev/sda1 on /home/henry /dev/sdb6 on /home/henry/mntX /dev/sdb7 on /home/henry/mntY /dev/sda1 on /home/otto /dev/sdb6 on /home/otto/mntX /dev/sdb7 on /home/otto/mntY
В следующей таблице показано как влияет применение нового типа распространения (т. е., mount --make-xxxx) на текущий тип распространения точки монтирования. Строки соответствуют существующим типам распространения, а столбцы — заданию нового типа распространения. Из-за нехватки места «индивидуальная» тип сокращён до «инд», а «непривязываемая» до «неприв».
сделать-общим | сделать-подчинён | сделать-инд | сделать-неприв | |
общий | общий | общий/инд [1] | инд | неприв |
подчинён | подчинён+общий | подчинён [2] | инд | неприв |
подчинён+общий | подчинён+общий | подчинён | инд | неприв |
инд | общий | инд [2] | инд | неприв |
неприв | общий | неприв [2] | инд | неприв |
Замечания к таблице:
Предположим, что выполняется следующая команда:
mount --bind A/a B/b
Здесь A — исходная точка монтирования, B — целевая точка монтирования, a — подкаталог в точке монтирования A и b — подкаталог в точке монтирования B. Тип распространения получаемой точки, B/b, зависит от типов распространения точек монтирования A и B, и рассчитывается по следующей таблице:
исход(A) | |||||
общий | инд | подчинён | неприв | ||
цель(B) | общий | | общий | общий | подчинён+общий | некорректно |
не_общий | | общий | инд | подчинён | некорректно |
Заметим, что рекурсивное привязывание поддерева имеет такую же семантику как в операции привязывания каждой точки монтирования в поддереве (непривязываемые точки монтирования автоматически убираются из целевой точки монтирования).
Дополнительную информацию смотрите в файле Documentation/filesystems/sharedsubtree.txt дерева исходного кода ядра.
Предположим, что выполняется следующая команда:
mount --move A B/b
Здесь A — исходная точка монтирования, B — целевая точка монтирования и b — подкаталог в точке монтирования B. Тип распространения получаемой точки, B/b, зависит от типов распространения точек монтирования A и B, и рассчитывается по следующей таблице:
исход(A) | |||||
общий | инд | подчинён | неприв | ||
цель(B) | общий | | общий | общий | подчинён+общий | некорректно |
не_общий | | общий | инд | подчинён | неприв |
Замечание: перемещение точки монтирования, располагающейся ниже общей точки, некорректно.
Дополнительную информацию смотрите в файле Documentation/filesystems/sharedsubtree.txt дерева исходного кода ядра.
Предположим, что для создания точки монтирования используется следующая команда:
mount устройство B/b
Здесь B — целевая точка монтирования и b — подкаталог в точке монтирования B. Тип распространения получаемой точки, B/b, получается таким же как при привязывании, где тип распространения исходной точки монтирования всегда равен индивидуальному.
Предположим, что для размонтирования точки используется следующая команда:
unmount A
Здесь A точка монтирования на B/b, где B — родительская точка монтирования и b — подкаталог в точке монтирования B. Если B имеет общий тип распространения, то все последние монтирования в b, получающие события от точки монтирования B и не имеющие подмонтирований внутри, будут размонтированы.
Метка propagate_from:X появляется в необязательных полях записи /proc/[pid]/mountinfo в случаях, когда процесс не может видеть непосредственного мастера (т. е., путь к мастеру недоступен из корневого каталога файловой системы) и поэтому не может определить цепочку распространения между точками монтирования, которые он может видеть.
В следующем примере сначала создаётся двусвязная цепочка мастер-подчинённый между точками монтирования /mnt, /tmp/etc и /mnt/tmp/etc. Затем используется команда chroot(1), чтобы сделать точку монтирования /tmp/etc недоступной из корневого каталога, что создаёт ситуацию, где мастер для /mnt/tmp/etc недоступен из (нового) корневого каталога процесса.
Сначала привяжем корневой каталог в /mnt, а затем привяжем /proc в /mnt/proc, чтобы после этого в правильном месте chroot-окружения для chroot(1) осталась доступной файловая система proc(5).
# mkdir -p /mnt/proc # mount --bind / /mnt # mount --bind /proc /mnt/proc
Теперь убедимся, что точка монтирования /mnt является общей в новой равноправной группе (без членов):
# mount --make-private /mnt # Изолировать от любой предыдущей группы # mount --make-shared /mnt # cat /proc/self/mountinfo | grep '/mnt' | sed 's/ - .*//' 239 61 8:2 / /mnt ... shared:102 248 239 0:4 / /mnt/proc ... shared:5
Теперь привяжем /mnt/etc к /tmp/etc:
# mkdir -p /tmp/etc # mount --bind /mnt/etc /tmp/etc # cat /proc/self/mountinfo | egrep '/mnt|/tmp/' | sed 's/ - .*//' 239 61 8:2 / /mnt ... shared:102 248 239 0:4 / /mnt/proc ... shared:5 267 40 8:2 /etc /tmp/etc ... shared:102
Первоначально, эти две точки монтирования были в одной равноправной группе, но мы сделали /tmp/etc подчинённой /mnt/etc, а затем сделали /tmp/etc общей, так чтобы она могла распространять события следующему подчинённому в цепочке:
# mount --make-slave /tmp/etc # mount --make-shared /tmp/etc # cat /proc/self/mountinfo | egrep '/mnt|/tmp/' | sed 's/ - .*//' 239 61 8:2 / /mnt ... shared:102 248 239 0:4 / /mnt/proc ... shared:5 267 40 8:2 /etc /tmp/etc ... shared:105 master:102
Затем мы привязали /tmp/etc в /mnt/tmp/etc. Опять же, две точки монтирования первоначально были в одной равноправной группе, но позднее мы сделали /mnt/tmp/etc подчинённой /tmp/etc:
# mkdir -p /mnt/tmp/etc # mount --bind /tmp/etc /mnt/tmp/etc # mount --make-slave /mnt/tmp/etc # cat /proc/self/mountinfo | egrep '/mnt|/tmp/' | sed 's/ - .*//' 239 61 8:2 / /mnt ... shared:102 248 239 0:4 / /mnt/proc ... shared:5 267 40 8:2 /etc /tmp/etc ... shared:105 master:102 273 239 8:2 /etc /mnt/tmp/etc ... master:105
Из показанного выше можно видеть, что /mnt является главной для подчинённой /tmp/etc, которая, в свою очередь, является главной для подчинённой /mnt/tmp/etc.
Теперь выполним chroot(1) в каталог /mnt, который делает точку монтирования с ID 267 недоступной из (нового корневого каталога:
# chroot /mnt
Если мы проверим состояние точек монтирования внутри окружения chroot, то увидим следующее:
# cat /proc/self/mountinfo | sed 's/ - .*//' 239 61 8:2 / / ... shared:102 248 239 0:4 / /proc ... shared:5 273 239 8:2 /etc /tmp/etc ... master:105 propagate_from:102
Здесь мы видим, что точка монтирования с ID 273 является подчинённой для главной, которая входит в равноправную группу 105. Точка монтирования этой главной недоступна и поэтому появилась метка propagate_from, показывающая, что идентификатор ближайшей ведущей равноправной группы (т. е., ближайшая достижимая точка монтирования в подчинённой цепи) равен 102 (соответствует точке монтирования /mnt до выполнения chroot(1)).
Пространство имён монтирования впервые появилось в Linux 2.4.19.
Пространства имён есть только в Linux.
Тип распространения, назначаемый новой точке монтирования, зависит от типа распространения родительского каталога. Если точка монтирования имеет родителя (т. е., является не корневой точкой монтирования) и тип распространения родителя — MS_SHARED, то тип распространения новой точки монтирования будет также MS_SHARED. В противном случае типом новой точки монтирования будет MS_PRIVATE. Но смотрите, также ЗАМЕЧАНИЯ.
Несмотря на то, что тип распространения по умолчанию для новой точки монтирования во многих случаях равен MS_PRIVATE, обычно, тип MS_SHARED полезнее. По этой причине, при запуске системы systemd(1) автоматически перемонтирует все точки монтирования как MS_SHARED. Таким образом, в современных системах типом распространения по умолчанию практически является MS_SHARED.
При создании пространства имён монтирования с помощью unshare(1) чаще всего требуется создать полную изоляцию точек монтирования в новом пространстве имён, и unshare(1) (начиная с util-linux версии 2.27) отменяет изменения systemd(1), делая все точки монтирования индивидуальными в новом пространстве имён. То есть unshare(1) в новом пространстве имён монтирования выполняет эквивалент следующего:
mount --make-rprivate /
Чтобы этого не происходило в unshare(1) можно использовать параметр --propagation unchanged.
Описание типов распространения при перемещении точек монтирования (MS_MOVE) и создании привязок монтирования (MS_BIND) смотрите в Documentation/filesystems/sharedsubtree.txt.
unshare(1), clone(2), mount(2), setns(2), umount(2), unshare(2), proc(5), namespaces(7), user_namespaces(7), findmnt(8)
Файл Documentation/filesystems/sharedsubtree.txt в дереве исходного кода ядра Linux.
2018-04-30 | Linux |