Как наследуется монтирование в Linux для работы Docker

Ответ

Docker использует механизм пространств имен Linux (namespaces), в частности пространство имен монтирования (mount namespace или CLONE_NEWNS), для изоляции файловой системы контейнера.

Механизм наследования:

  1. При запуске нового контейнера Docker (через runc) вызывает системный вызов clone() или unshare() с флагом CLONE_NEWNS.
  2. Это создает новое, изолированное пространство имен монтирования для процесса контейнера.
  3. Изначально это новое пространство наследует копию таблицы монтирований (mount tree) от родительского процесса (обычно dockerd или containerd).
  4. После создания пространства имен, Docker внутри него монтирует корневую файловую систему контейнера (образ) и другие необходимые файловые системы (/proc, /sys, /dev).

Ключевое следствие: Изменения в дереве монтирований внутри контейнера (например, mount /dev/sdb1 /mnt) остаются видимыми только в его собственном пространстве имен и не влияют на хост или другие контейнеры.

Практический пример с bind mount: Когда вы используете -v /host/data:/container/data, Docker создает bind mount, который отображает каталог хоста в пространство имен контейнера.

# На хосте
$ ls /host/data
file.txt

# Запуск контейнера с пробросом директории
$ docker run -it -v /host/data:/data alpine sh

# Внутри контейнера мы видим файл из хоста
/ # ls /data
file.txt
# Монтирование внутри контейнера НЕ видно на хосте
/ # mount -t tmpfs tmpfs /data

Проверка изоляции: На хосте можно увидеть, что у каждого контейнера свой mount namespace:

# Находим PID основного процесса контейнера
$ docker inspect --format '{{.State.Pid}}' <container_id>
12345

# Смотрим его пространство имен монтирования
$ ls -la /proc/12345/ns/mnt
lrwxrwxrwx 1 root root 0 Apr 10 12:00 /proc/12345/ns/mnt -> 'mnt:[4026531841]'
# У каждого контейнера будет уникальный идентификатор в квадратных скобках.

Таким образом, наследование — это начальная копия, а последующая изоляция обеспечивает безопасность и предсказуемость работы контейнеров.

Ответ 18+ 🔞

Давай разберём эту тему, а то у некоторых создаётся впечатление, будто контейнеры — это магия какая-то, ебушки-воробушки. На самом деле, под капотом у Docker всё довольно прямолинейно, хоть и хитрая жопа.

Смотри, вся изоляция файловой системы в контейнере держится на механизме пространств имён Linux, а конкретно — на пространстве имён монтирования (mount namespace или CLONE_NEWNS). Это не какая-то своя разработка, это стандартная фича ядра.

Как это работает, по шагам:

  1. Когда ты запускаешь контейнер, runc (или кто там у них сейчас) дёргает системный вызов clone() или unshare() с волшебным флагом CLONE_NEWNS.
  2. Бах — и для процесса контейнера создаётся новое, своё, отдельное пространство имён для монтирований.
  3. Вот тут важный момент: изначально это новое пространство наследует копию всей таблицы монтирований от родительского процесса (обычно это dockerd или containerd). То есть стартует не с чистого листа, а с той же картины, что и хост.
  4. А дальше Docker внутри этого свежеиспечённого пространства начинает свою работу: монтирует корневую файловую систему из образа, подцепляет /proc, /sys, /dev и всё остальное, что нужно.

Ключевое следствие, которое многие не понимают: Любые изменения в дереве монтирований внутри контейнера — например, если ты там взбрело в голову смонтировать флешку (mount /dev/sdb1 /mnt) — остаются видны только внутри его собственного пространства имён. На хост-систему или в другие контейнеры это нихуя не просачивается. Изоляция, ёпта.

Практический пример, чтобы совсем понятно стало: Допустим, ты пробрасываешь директорию с хоста внутрь контейнера через -v.

# На хосте у нас лежит файл
$ ls /host/data
file.txt

# Запускаем контейнер и пробрасываем эту папку
$ docker run -it -v /host/data:/data alpine sh

# Внутри контейнера мы этот файл видим — логично
/ # ls /data
file.txt
# А теперь сделаем что-то внутри контейнера, например, смонтируем tmpfs
/ # mount -t tmpfs tmpfs /data

И что ты думаешь? Это монтирование НЕ БУДЕТ ВИДНО на основном хосте. Оно живёт только в песочнице контейнера. Удивление пиздец, да?

Как проверить эту изоляцию на практике: На хосте можно прямо посмотреть, что у каждого контейнера свой собственный mount namespace.

# Находим PID основного процесса в контейнере
$ docker inspect --format '{{.State.Pid}}' <container_id>
12345

# Смотрим ссылку на его пространство имён монтирования
$ ls -la /proc/12345/ns/mnt
lrwxrwxrwx 1 root root 0 Apr 10 12:00 /proc/12345/ns/mnt -> 'mnt:[4026531841]'
# Вот эти цифры в квадратных скобках — уникальный идентификатор пространства. У каждого контейнера — свой.

Так что наследование — это просто начальная точка, копия. А вся соль — в последующей изоляции. Благодаря этому контейнеры и не лезут друг другу в файловые системы, и доверия к ним больше, чем к скрипту, скачанному с подозрительного форума.