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

Thank you for reading this post, don't forget to subscribe!

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

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

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

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

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

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

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

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

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

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

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

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

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

Струк­ту­ра свя­зей меж­ду сло­я­ми в Docker — иерар­хи­че­ская. Име­ет­ся некий базо­вый слой, на кото­рый «накла­ды­ва­ют­ся» осталь­ные слои.

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

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

IDS=(9377ad319b00 a82f81f25750 b207c06aba70 d55e68e6cc9c)

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

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

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

Далее:

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

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

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

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

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

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

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

При запус­ке:

docker run -ti ubuntu

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

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

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

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

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

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

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

Про­ве­ря­ем:

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

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

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

и

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

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

и

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

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

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

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

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

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