Docker: иерархия и наследование слоев

Автор: | 26/12/2015

docker_lxcКаждый Docker-образ состоит из слоёв (layers), каждый из которых описывает какую-то инструкцию. Далее – Docker объединяет информацию из каждого слоя, и создает шаблон-образ, из которого запускается контерйнер, в котором выполняются инструкции из каждого слоя, который был включен в данный образ.

Для дальнейших примеров – возьмем образ unutu:latest:

# docker run -ti ubuntu
Unable to find image 'ubuntu:latest' locally
latest: Pulling from ubuntu
9377ad319b00: Downloading [>                                                  ]  1.08 MB/65.67 MB
a82f81f25750: Download complete
b207c06aba70: Download complete
d55e68e6cc9c: Download complete

В процессе загрузки видны четыре слоя.

Так же их можно увидеть после запуска с помощью docker images -a:

# docker images -a
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu              latest              d55e68e6cc9c        2 weeks ago         187.9 MB
<none>              <none>              b207c06aba70        2 weeks ago         187.9 MB
<none>              <none>              a82f81f25750        2 weeks ago         187.9 MB
<none>              <none>              9377ad319b00        2 weeks ago         187.7 MB

Имя репозитория <none> и тег <none> тут указывают, что он является промежуточным слоем для какого-то целого образа.

Находим все файлы и каталоги, созданные для этих слоев:

# IDS=(9377ad319b00 a82f81f25750 b207c06aba70 d55e68e6cc9c)
# for i in ${IDS[@]}; do
> echo -e "nChecking layer $in"
> find / -name "*$i*" -exec file {} ;
> done

Checking layer 9377ad319b00

/var/lib/docker/graph/9377ad319b00884df249b7820e3cf540b1c4631b3b1ee6998a0f7c3d53962e03: directory
/var/lib/docker/devicemapper/metadata/9377ad319b00884df249b7820e3cf540b1c4631b3b1ee6998a0f7c3d53962e03: ASCII text, with no line terminators
/var/lib/docker/devicemapper/mnt/9377ad319b00884df249b7820e3cf540b1c4631b3b1ee6998a0f7c3d53962e03: directory

Checking layer a82f81f25750

/var/lib/docker/graph/a82f81f257507f5cb74e833ff1ae4a6a39dfa654a161f5393f641832872b87d3: directory
/var/lib/docker/devicemapper/metadata/a82f81f257507f5cb74e833ff1ae4a6a39dfa654a161f5393f641832872b87d3: ASCII text, with no line terminators
/var/lib/docker/devicemapper/mnt/a82f81f257507f5cb74e833ff1ae4a6a39dfa654a161f5393f641832872b87d3: directory

Checking layer b207c06aba70

/var/lib/docker/graph/b207c06aba70227e0a2561bb7df20a5fd1310901da98ecc6f4da7dccdc40d961: directory
/var/lib/docker/devicemapper/metadata/b207c06aba70227e0a2561bb7df20a5fd1310901da98ecc6f4da7dccdc40d961: ASCII text, with no line terminators
/var/lib/docker/devicemapper/mnt/b207c06aba70227e0a2561bb7df20a5fd1310901da98ecc6f4da7dccdc40d961: directory

Checking layer d55e68e6cc9c

/var/lib/docker/graph/d55e68e6cc9c7f78f1c02001e1a5ce76511db044c659e5c0a4275c54473f2869: directory
/var/lib/docker/devicemapper/metadata/d55e68e6cc9c7f78f1c02001e1a5ce76511db044c659e5c0a4275c54473f2869: ASCII text, with no line terminators
/var/lib/docker/devicemapper/mnt/d55e68e6cc9c7f78f1c02001e1a5ce76511db044c659e5c0a4275c54473f2869: directory

В данном случае нас интересует каталог /var/lib/docker/graph/, который ещё назывется graph database. Именно в нем хранятся файлы слоев образов, которые нам нужны для получения всей картины.
Каждый каталог соответствует одному из имеющихся у Docker слоев:

# ls -l /var/lib/docker/graph/
total 20
drwx------ 2 root root 4096 Dec 26 17:51 9377ad319b00884df249b7820e3cf540b1c4631b3b1ee6998a0f7c3d53962e03
drwx------ 2 root root 4096 Dec 26 17:51 a82f81f257507f5cb74e833ff1ae4a6a39dfa654a161f5393f641832872b87d3
drwx------ 2 root root 4096 Dec 26 17:51 b207c06aba70227e0a2561bb7df20a5fd1310901da98ecc6f4da7dccdc40d961
drwx------ 2 root root 4096 Dec 26 17:51 d55e68e6cc9c7f78f1c02001e1a5ce76511db044c659e5c0a4275c54473f2869
drwx------ 2 root root 4096 Dec 26 17:51 _tmp

Теперь – взглянем на список доступных образов:

# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu              latest              d55e68e6cc9c        2 weeks ago         187.9 MB

Мы видим 1 образ с IMAGE ID d55e68e6cc9c.

Проверяем его каталог:

# ls -l /var/lib/docker/graph/d55e68e6cc9c7f78f1c02001e1a5ce76511db044c659e5c0a4275c54473f2869/
total 12
-rw------- 1 root root   71 Dec 26 17:51 checksum
-rw------- 1 root root 1288 Dec 26 17:51 json
-rw------- 1 root root    1 Dec 26 17:51 layersize

Файл checksum содержит в себе, как понятно из названия, контрольную сумму слоя:

# cat /var/lib/docker/graph/d55e68e6cc9c7f78f1c02001e1a5ce76511db044c659e5c0a4275c54473f2869/checksum
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4

А layersize – размер слоя (о самих данных – в другой раз).

Теперь мы можем приступить к рассмотрению иерархии слоев для образа ubuntu:latest.

Структура связей между слоями в Docker – иерархическая. Имеется некий базовый слой, на который “накладываются” остальные слои:

docer_struct

Каждый слой описывает какое-то изменение, которое должно быть выполнено с данными на запущенном контейнере.

Как мы видели – при загрузке одного образа загружается несколько файлов слоев – в нашем случае 4, которые мы и внесли в массив при поиске каталогов:

# IDS=(9377ad319b00 a82f81f25750 b207c06aba70 d55e68e6cc9c)

В каталоге каждого слоя имеется файл json, который содержит всю информацию о слое:

# cat /var/lib/docker/graph/d55e68e6cc9c7f78f1c02001e1a5ce76511db044c659e5c0a4275c54473f2869/json | python -mjson.tool
{
    "Size": 0,
    "architecture": "amd64",
    "config": {
    ...

Среди прочих ключей – тут присутствует ключ parent, который указывает на родительский слой:

# cat /var/lib/docker/graph/d55e68e6cc9c7f78f1c02001e1a5ce76511db044c659e5c0a4275c54473f2869/json | python -mjson.tool | grep parent
    "parent": "b207c06aba70227e0a2561bb7df20a5fd1310901da98ecc6f4da7dccdc40d961"

А слой b207c06aba70 – содержит:

# cat /var/lib/docker/graph/b207c06aba70227e0a2561bb7df20a5fd1310901da98ecc6f4da7dccdc40d961/json | python -mjson.tool | grep parent
    "parent": "a82f81f257507f5cb74e833ff1ae4a6a39dfa654a161f5393f641832872b87d3"

Далее:

# cat /var/lib/docker/graph/a82f81f257507f5cb74e833ff1ae4a6a39dfa654a161f5393f641832872b87d3/json | python -mjson.tool | grep parent
    "parent": "9377ad319b00884df249b7820e3cf540b1c4631b3b1ee6998a0f7c3d53962e03"

Корневой же слой этой записи уже не содержит:

# cat /var/lib/docker/graph/9377ad319b00884df249b7820e3cf540b1c4631b3b1ee6998a0f7c3d53962e03/json | python -mjson.tool | grep parent | wc -l
0

Так вместе – они образуют шаблон образа.

Если ещё раз посмотреть на процесс запуска конейнера с этим образом, мы увидим, что:

# docker run -ti ubuntu
Unable to find image 'ubuntu:latest' locally
latest: Pulling from ubuntu
9377ad319b00: Downloading [>                                                  ]  1.08 MB/65.67 MB
a82f81f25750: Download complete
b207c06aba70: Download complete
d55e68e6cc9c: Download complete

Последним загружался 9377ad319b00 – самый большой по размеру, основной слой образа. Остальные слои – мельче, и только описывают изменения по отношению к предыдущему слою.

Далее, при старте контейнера с помощью docker runDocker обратится к файлу /var/lib/docker/repositories (или /var/lib/docker/repositories-devicemapper в зависимости от драйвера UFS), в котором ищет заданный образ в локальном репозитории.

Если образ с заданным в параметре docker run репозиторием и тегом найден в файле /var/lib/docker/repositories – будет использован слой с ID, указанный для этого имени.

Т.е., у нас имеется образ репозитория ubuntu с тегом latest:

# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu              latest              d55e68e6cc9c        2 weeks ago         187.9 MB

При запуске:

# docker run -ti ubuntu

Docker обратится к файлу /var/lib/docker/repositories-devicemapper:

# cat /var/lib/docker/repositories-devicemapper | python -mjson.tool
{
    "Repositories": {
        "ubuntu": {
            "latest": "d55e68e6cc9c7f78f1c02001e1a5ce76511db044c659e5c0a4275c54473f2869"
        }
    }
}

В котором найдет имя ubuntu, тег latest (т.к. другой в параметрах не указан)  и ID слоя d55e68e6cc9c7f78f1c02001e1a5ce76511db044c659e5c0a4275c54473f2869.

После чего – он обратится к своей graph database, в которой определит зависимости с помощью тега parent в json-файлах слоев, начиная от слоя d55e68e6cc9c, что бы определить всю структуру образа.

Собственно, именно так и формируются образы Docker.

Давайте создадим свой новый образ, из имещегося образа Ubuntu.

Создаём Dockerfile файл, из которого будем собирать этот образ, в котором прописываем:

FROM ubuntu
RUN apt-get update
CMD ["bash"]

Собираем образ:

# docker build -t ubuntu_upd .
Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu
 ---> d55e68e6cc9c
Step 1 : RUN apt-get update
 ---> Running in ee764ea37105
Ign http://archive.ubuntu.com trusty InRelease
Get:1 http://archive.ubuntu.com trusty-updates InRelease [64.4 kB]
...
Fetched 21.4 MB in 32s (666 kB/s)
Reading package lists...
 ---> 4fe9b73744a4
Removing intermediate container ee764ea37105
Step 2 : CMD bash
 ---> Running in b19da762f990
 ---> c12cbab7fec0
Removing intermediate container b19da762f990
Successfully built c12cbab7fec0

Проверяем:

# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu_upd          latest              c12cbab7fec0        31 seconds ago      209.3 MB
ubuntu              latest              d55e68e6cc9c        2 weeks ago         187.9 MB

И каталог /var/lib/docker/graph/:

# ls -l /var/lib/docker/graph/
total 28
drwx------ 2 root root 4096 Dec 26 19:18 4fe9b73744a437a7b2a2abefb5dd108ad9780791bf843176963aa002cabca1c6
drwx------ 2 root root 4096 Dec 26 17:51 9377ad319b00884df249b7820e3cf540b1c4631b3b1ee6998a0f7c3d53962e03
drwx------ 2 root root 4096 Dec 26 17:51 a82f81f257507f5cb74e833ff1ae4a6a39dfa654a161f5393f641832872b87d3
drwx------ 2 root root 4096 Dec 26 17:51 b207c06aba70227e0a2561bb7df20a5fd1310901da98ecc6f4da7dccdc40d961
drwx------ 2 root root 4096 Dec 26 19:19 c12cbab7fec03e0206c997c07f0f88bd56c80cff0d807b3f5d2b7b868d356495
drwx------ 2 root root 4096 Dec 26 17:51 d55e68e6cc9c7f78f1c02001e1a5ce76511db044c659e5c0a4275c54473f2869

В котором видим два новых слоя:

drwx------ 2 root root 4096 Dec 26 19:19 c12cbab7fec03e0206c997c07f0f88bd56c80cff0d807b3f5d2b7b868d356495

и:

drwx------ 2 root root 4096 Dec 26 19:18 4fe9b73744a437a7b2a2abefb5dd108ad9780791bf843176963aa002cabca1c6

Проверяем родителей, начиная от c12cbab7fec0:

# cat /var/lib/docker/graph/c12cbab7fec03e0206c997c07f0f88bd56c80cff0d807b3f5d2b7b868d356495/json | python -mjson.tool | grep parent
    "parent": "4fe9b73744a437a7b2a2abefb5dd108ad9780791bf843176963aa002cabca1c6"

И:

# cat /var/lib/docker/graph/4fe9b73744a437a7b2a2abefb5dd108ad9780791bf843176963aa002cabca1c6/json | python -mjson.tool | grep parent
    "parent": "d55e68e6cc9c7f78f1c02001e1a5ce76511db044c659e5c0a4275c54473f2869"

Запись "parent": "d55e68e6cc9c7f78f1c02001e1a5ce76511db044c659e5c0a4275c54473f2869" указывает на наш первый образ ubuntu:latest.

Т.е. – наш образ ubuntu_upd:latest основан на тех же файлах слоев, которые используются образом ubuntu:latest + два “лишних” слоя, которые мы добавили “сами”, это c12cbab7fec0 и 4fe9b73744a4.

Каждая инструкция в Dockerfile вызывает создание нового слоя при сборке образа.

Каждый из них описывает действие, которое мы указали в нашем Dockerfile.

В нашем примере их два – это RUN apt-get update и CMD ["bash"]:

# cat /var/lib/docker/graph/c12cbab7fec03e0206c997c07f0f88bd56c80cff0d807b3f5d2b7b868d356495/json | python -mjson.tool | grep -B3 CMD
        "Cmd": [
            "/bin/sh",
            "-c",
            "#(nop) CMD ["bash"]"

И второй новый слой:

# cat /var/lib/docker/graph/4fe9b73744a437a7b2a2abefb5dd108ad9780791bf843176963aa002cabca1c6/json | python -mjson.tool | grep -B3 apt-get
        "Cmd": [
            "/bin/sh",
            "-c",
            "apt-get update"

Про контейнеры и данные в слоях – в следующей части.