Начнем с запуска 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 полностью совпадает с файловой системой контейнера.
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.