• /
  • /

Устройство файловой системы контейнеров

Как каждый контейнер получает собственную файловую систему? Сначала разберём, что делает Docker, а затем воспроизведём это сами.

Начнем с запуска shell в контейнере Docker на основе образа Alpine.

michal@michal-lg:~$ docker run -it --entrypoint /bin/sh --rm --name "alpine-container" alpine
/ # ls
bin    dev    etc    home   lib    media  mnt    opt    proc   root   run    sbin   srv    sys    tmp    usr    var
/ # hostname
a7cbf0aea1ad
/ # cd home && ls

Мы находимся в изолированной файловой системе, и она довольно пустая. Создадим файл.

/ # echo -e "Hello there\nGeneral Kenobi" > /home/hello_there.txt
/ # cat /home/hello_there.txt 
Hello there
General Kenobi

Можно ли получить доступ к этому файлу с хоста? Можно — давайте найдём его!

Docker хранит всё в /var/lib/docker, начнём поиск оттуда во втором терминале:

root@michal-lg:/var/lib/docker# find -name hello_there.txt
./overlay2/1557145fe40a1595d090eeafa72c39a7b54cca4791ae9e3ffafabff06466125c/diff/home/hello_there.txt
./overlay2/1557145fe40a1595d090eeafa72c39a7b54cca4791ae9e3ffafabff06466125c/merged/home/hello_there.txt
root@michal-lg:/var/lib/docker# 

Файл найден в двух директориях: diff и merged. Посмотрим, что ещё есть в этих папках.

root@michal-lg:/var/lib/docker/.../diff# ls
home  root
root@michal-lg:/var/lib/docker/.../merged# ls
bin  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

Директория diff содержит пустую папку root и папку home с созданным файлом. Содержимое merged полностью совпадает с файловой системой контейнера.

Overlayfs

Docker использует файловую систему overlayfs. Overlayfs объединяет два дерева файлов — «нижний» (lower) и «верхний» (upper) слой — в общий вид merged. Docker называет «верхний» слой diff, и мы будем использовать это название.


Использование таких объединённых файловых систем, как overlayfs, основано на наблюдении: часто на одном хосте запускаются несколько контейнеров, которые могут использовать общий нижний слой — например, Alpine, Ubuntu или специализированный, вроде golang.


Нижний слой делается только для чтения, что позволяет нескольким контейнерам использовать его одновременно. Изменения записываются только в верхний слой. Давайте разберём, что произошло, когда мы создали hello_there.txt.


Когда мы создали hello_there.txt в /home, он был записан в diff, и overlayfs сформировала объединённый вид в merged.

Что, если изменить что-то в нижнем слое? Переименуем /bin/echo в /bin/echo.old.

/ # cd bin
/bin # mv echo echo.old
/bin # ls
...
chgrp          echo.old       gzip           ln             mount          printenv       setserial      umount
...

Нижний слой только для чтения, поэтому изменения происходят только в diff.

root@michal-lg:/var/lib/docker/overlay2/.../diff/bin# ls -l
total 0
c--------- 1 root root 0, 0 Nov 11 23:06 echo
lrwxrwxrwx 1 root root   12 Sep  6 13:34 echo.old -> /bin/busybox

Появилось два файла. Один — это специальный whiteout-файл для удалённого echo. Overlayfs использует такие файлы, чтобы исключить удалённые элементы из объединённого вида. Второй файл — переименованный echo.old, который оказался символической ссылкой на busybox.

Создание файловой системы контейнера

Теперь разберём, как Docker использует overlayfs для создания новой файловой системы.


Сначала создадим временные директории в /tmp для ручной настройки файловой системы контейнера.

michal@michal-lg:/tmp$ mkdir -p /tmp/container-demo/{diff,merged,work}
michal@michal-lg:/tmp$ ls container-demo/
diff  merged  work

Мы уже видели diff и merged. Папка work используется overlayfs как временное пространство, и нам не нужно о ней заботиться.


Далее скачаем Alpine minirootfs для вашей архитектуры процессора и распакуем его в /tmp/container-demo/. Мы переименуем распакованную папку в alpine.

michal@michal-lg:/tmp/container-demo$ ls
alpine  diff  merged  work

Теперь, когда всё готово, смонтируем файловую систему overlayfs.

michal@michal-lg:/tmp/container-demo$ sudo mount -t overlay  overlay  -o lowerdir=alpine,upperdir=diff,workdir=work  merged

Посмотрим содержимое merged — это будет файловая система Alpine:

michal@michal-lg:/tmp/container-demo/merged$ ls
bin  etc   lib    mnt  proc  run   srv  tmp  var
dev  home  media  opt  root  sbin  sys  usr

Если создать файл в merged, он появится в diff.

michal@michal-lg:/tmp/container-demo/merged$ echo hello > hello.txt
michal@michal-lg:/tmp/container-demo/merged$ ls
bin  etc        home  media  opt   root  sbin  sys  usr
dev  hello.txt  lib   mnt    proc  run   srv   tmp  var
michal@michal-lg:/tmp/container-demo/merged$ ls ../diff/
hello.txt

В завершение создадим новый shell-процесс и установим merged как его корневую директорию. Так контейнеры Linux видят только свою файловую систему. Все процессы, запущенные из этого shell, также будут ограничены этой файловой системой.

michal@michal-lg:/tmp/container-demo$ sudo chroot merged /bin/sh
/ # ls
bin        hello.txt  media      proc       sbin       tmp
dev        home       mnt        root       srv        usr
etc        lib        opt        run        sys        var
/ # cd ..
/ # 

Полная реализация потребовала бы использования пространств имён (namespaces) для дополнительной изоляции. Подробнее об этом можно узнать в man-страницах Linux.

У нас есть курсы под любую категорию DevOps, а также региональная скидка для казахстанцев
Инвестируйте в обучение DevOps