Как уже известно каждый Docker образ состоит из слоёв. Чем больше команд (инструкций) в Dockerfile тем больше слоёв, но не все команды создают слои.
Для примера возьмём Dockerfile который использует ubuntu и ставит nginx, при этом копируя файлы внутрь образа.
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y nginx
COPY index.html /usr/share/nginx/html/
CMD ["nginx", "-g", "daemon off;"]
Если не понимаете что происходит:
| № | Инструкция | Что делает | Создаёт слой? |
|---|---|---|---|
| 1 | FROM ubuntu:22.04 | скачивает базовый слой (родительский образ) | да |
| 2 | RUN apt-get install -y nginx | установка nginx | да |
| 3 | COPY | копирование файла | да |
| 4 | CMD | команда запуска | нет (метаданные) |
Получается, что при сборке образа будет создано 3 слоя и сейчас я это проверю, выполнив команду сборки образа.
docker build . -t mybuild:1.0
=> [1/3] FROM docker.io/library/ubuntu:22.04@sha256:09506232a8004baa32c47d68f1e5c307d648fdd59f5e7eaa42aaf87914100db3
=> [2/3] RUN apt-get update && apt-get install -y nginx
=> [3/3] COPY index.html /usr/share/nginx/html/
=> => writing image sha256:17a8b92dd8073184d570a69167c3862f633c69c90e49fbb5d745c56e870fd6f7
Каждая инструкция (RUN, COPY, ADD, и т.д.) в Dockerfile создаёт слой.
Как хранятся слои в Docker
Каждый слой представляет из себя директорию со сжатыми данными в ОС, где стоит Docker. Путь к этой директории /var/lib/docker/overlay2/.
Но если заглянуть в эту директорию, не зная, что и где искать ничего особенно там не найти. Ну а для того чтобы понять в какой директории какой слой необходимо:
- Вывести слои образа
docker inspect --format='' mybuild:1.0 | jq["sha256:767e56ba346ae714b6e6b816baa839051145ed78cfa0e4524a86cc287b0c4b00", "sha256:b3e9f88953eff8bf59b0042d340cbc16daf139665697ebf2ee741edd3c8b3b18", "sha256:a04b6207eb080660102f534250f1e473474068e2f41baa0f2624279036ee75b5"] - Выполняем поиск по хэшу слоя
udo grep -rnw /var/lib/docker/image/overlay2/layerdb/sha256/ -e b3e9f88953eff8bf59b0042d340cbc16daf139665697ebf2ee741edd3c8b3b18/var/lib/docker/image/overlay2/layerdb/sha256/3cf6bc112ebdd32ca7b08b5c40a279e54ec48ae101ccae6e78493293e5862640/diff:
Скорее всего необходимости лезть вручную в эти директории и не будет, но знать где лежат слои физически лишним не будет.
Как работает кэш в Docker
При сборке Docker образа (docker build) Docker сравнивает каждую инструкцию с ранее построенными образами, точнее с их слоями. Если слой не изменился, то используется кэш, т.е. уже существующий слой. Так как у каждого слоя есть хэш, Docker при сборке вычисляет хэш всех изменений инструкции (SHA256) и сравнивает с существующими слоями. Т.е. если другой образ использует тот же слой (т.е. ту же комбинацию базового слоя + команды + контента) **, Docker **возьмёт этот слой из кэша.
Docker считает, что два слоя эквивалентны, если совпадают:
- хэш предыдущего слоя (родителя);
- команда (
RUN,COPY,ADDи её аргументы); - содержимое добавляемых файлов (если есть).
Если всё это совпадает — Docker просто переиспользует существующий слой из другого образа. Т.е. благодаря кэшированию происходит оптимизация использования дискового пространства. Также при наличии кэша нет необходимости скачивать одни и и те же слои с Docker hub или создавать их заново что ускоряет процесс сборки образа.
Для примера я создам два Dockerfile с парочкой одинаковых инструкций и соберу два разных образа.
📜 Dockerfile.1
FROM python:3.11-slim
RUN pip install flask==3.0.0
📜 Dockerfile.2
FROM python:3.11-slim
RUN pip install flask==3.0.0 # <-- точно та же команда
COPY app/ /app/
CMD ["python", "/app/main.py"]
Выполню сборку первого образа с именем cache1:1.0.
docker build --progress=plain -f Dockerfile.1 -t cache1:1.0 .
[1/2] FROM docker.io/library/python:3.11-slim@sha256:8eb5fc663972b871c528fef04be4eaa9ab8ab4539a5316c4b8c133771214a61 DONE 6.3s
[2/2] RUN pip install flask==3.0.0 DONE 5.3s
Выполню сборку второго образа с именем cache2:1.0.
docker build --progress=plain -f Dockerfile.2 -t cache2:1.0 .
[1/3] FROM docker.io/library/python:3.11-slim@sha256:8eb5fc663972b871c528fef04be4eaa9ab8ab4539a5316c4b8c133771214a617 DONE 0.0s
[2/3] RUN pip install flask==3.0.0 CACHED
[3/3] COPY app/ /app/ DONE 0.0s
Из команды выше уже видно, что для сборки использовались 2 слоя из кэша, хотя в первом слое об этом и не написано. Также выполню docker inspect чтобы сравнить слои образа cache2 с cache1.
docker inspect --format='' cache1:1.0 | jq
"sha256:d7c97cb6f1fe7cae982649e9f55efe201212e8acaa64bd668c083b204e4efd4c",
"sha256:15eb6aec49b38357916ea069cb57c890841f4e40588c54ebba117f6314911d37",
"sha256:9ba402f61141e835fb247b8592f61ea2a885de652bbb368fe9cea93cbc8c324a",
"sha256:235e8192987624c55713750efe67254a8dbfe540c7a59c1d33353f928e42d80d",
"sha256:556dcdfbcdbc51dab4c5310208f5b09b7a03e7d9a627cd0defc8d2f5052e78d8"
docker inspect --format='' cache2:1.0 | jq
"sha256:d7c97cb6f1fe7cae982649e9f55efe201212e8acaa64bd668c083b204e4efd4c",
"sha256:15eb6aec49b38357916ea069cb57c890841f4e40588c54ebba117f6314911d37",
"sha256:9ba402f61141e835fb247b8592f61ea2a885de652bbb368fe9cea93cbc8c324a",
"sha256:235e8192987624c55713750efe67254a8dbfe540c7a59c1d33353f928e42d80d",
"sha256:556dcdfbcdbc51dab4c5310208f5b09b7a03e7d9a627cd0defc8d2f5052e78d8",
"sha256:0b636ec1d5fad65a1c6d8512c0ce7535824bab1c7700fb0b1c9b473ae54ea594"
Из вывода выше можно отметить что:
- При сборке
cache1:1.0все 5 слоёв были созданы с нуля. - При сборке
cache2:1.0Docker проверил каждый шаг сверху вниз:
| Слой (sha256) | Новый слой? | Комментарий |
|---|---|---|
| d7c97cb6 | нет (беру из кэша) | базовый образ python:3.11-slim |
| 15eb6aec | нет (беру из кэша) | слой из Python базового образа |
| 9ba402f6 | нет (беру из кэша) | слой из Python базового образа |
| 235e8192 | нет (беру из кэша) | слой из Python базового образа |
| 556dcdfb | нет (беру из кэша) | слой RUN pip install flask==3.0.0 |
| 0b636ec1 | да (создаю новый) | слой COPY app/ /app/ |
Порядок слоёв и кэш
При сборке образа проверка слоёв идёт сверху вниз и если вдруг инструкция какого-либо слоя меняется в Dockerfile после его сборки, то при последующей сборке все следующие слой также будут пересозданы даже если ничего не менялось.
📜 Dockerfile.2
FROM python:3.11-slim
RUN pip install php
RUN pip install flask==3.0.0
COPY app/ /app/
CMD ["python", "/app/main.py"]
Теперь если я соберу снова образ, то из кэша будет использован только один слой (FROM python:3.11-slim).
ocker build -f Dockerfile.2 -t cache2:1.0 .
=> CACHED [1/4] FROM docker.io/library/python:3.11-slim@sha256:8eb 0.0s 0.0s
=> [2/4] RUN pip install php 3.4s
=> [3/4] RUN pip install flask==3.0.0 4.3s
=> [4/4] COPY app/ /app/ 0.2s
Поэтому рекомендуется при написании Dockerfile инструкции, которые могут меняться прописывать максимально низко в файле.
Как собрать образ Docker без использования кэша
Если вдруг необходимо провести сборку образа без использования кэша нужно использовать флаг --no-cache.
docker build --no-cache -f Dockerfile.2 -t cache2:1.0 .
Когда бывает необходимо не использовать кэш:
- Когда необходимо обновить пакеты, т.е. получить актуальные версии из репозиториев
- Если базовый образ был обновлён и необходимо получить свежий базовый образ
- Когда сборка всегда строиться с нуля, независимо от предыдущего состояния (CI/CD)
Но если необходимо просто получить свежий базовый образ лучше использовать флаг --pull.
Слой контейнера
При создании контейнера поверх слоёв с образом создаётся новый слой в статусе read/write, т.е. слой перезаписываемый, в отличии от слоёв образа, которые в статусе read-only.
Т.е. если я создам контейнер, то произойдёт примерно следующее:
image layer 1 (базовый слой)
image layer 2
image layer 3
------------------------------
container layer (read-write)
Этот верхний слой контейнера хранится отдельно в директории /var/lib/docker/overlay2/. Все манипуляции внутри контейнера сохраняются в слое самого контейнера, а не в слоях образа.
docker inspect some-alpnginx | jq '.[0].GraphDriver.Data'
"LowerDir": "/var/lib/docker/overlay2/b76e86ac1387c177bb70ef2f1c0cdc58b86c228af2b890f474e76d13daf9c803-init/diff:/var/lib/docker/overlay2/a2168b993b381f985089daddef7bfc32fa15eae64575e3661b0e028c7ddf8bcc/diff:/var/lib/docker/overlay2/69e1478c7781777eaec105040d2ee9b8397882ddb8e1f320e399f3a7fbd1230c/diff:/var/lib/docker/overlay2/abb7d7bf0ad7f1b823f223652ccd1dc0f9aecdb5e6a3903a77e3832e1d0342d1/diff:/var/lib/docker/overlay2/2b96faabbd98c4847e4ff63598d11d551eddf829b0902f2fb74153247e95f927/diff:/var/lib/docker/overlay2/5d939a6cb84023566cb2513327474b7f029d69016f57ea4fec8d4f174646dc63/diff:/var/lib/docker/overlay2/d305392fcd4d3f4fa441e7cdb71b7dd41b0b77f767293735b393b9c54404312c/diff:/var/lib/docker/overlay2/c5865d2ecfa2c81b42ffeb09755886e9b907c1ac0b48aa9cd57d4cd2afb07263/diff:/var/lib/docker/overlay2/654377eb111af010abfab1e8293f37b7729584d041fe5e79007fb91293d9a75e/diff",
"MergedDir": "/var/lib/docker/overlay2/b76e86ac1387c177bb70ef2f1c0cdc58b86c228af2b890f474e76d13daf9c803/merged",
"UpperDir": "/var/lib/docker/overlay2/b76e86ac1387c177bb70ef2f1c0cdc58b86c228af2b890f474e76d13daf9c803/diff",
"WorkDir": "/var/lib/docker/overlay2/b76e86ac1387c177bb70ef2f1c0cdc58b86c228af2b890f474e76d13daf9c803/work"
Этот верхний слой контейнера хранится отдельно в директории /var/lib/docker/overlay2/. Все манипуляции внутри контейнера сохраняются в слое самого контейнера, а не в слоях образа. Тут у контейнера 4 пути:
- LowerDir - Слои образа (read-only)
- UpperDir - Слой контейнера (read-write). Всё, что ты изменяешь внутри контейнера, записывается именно сюда.
- MergedDir - Смонтированное объединение всех слоёв
- WorkDir - Техническая папка OverlayFS
Для того чтобы понять, что в какой директории лежит создам в контейнере новый файл:
docker exec some-alpnginx sh -c "echo test >> /etc/nginx/1.txt"
Этот файл будет сохранён в директории UpperDir контейнера, проверю что он там:
sudo cat /var/lib/docker/overlay2/b76e86ac1387c177bb70ef2f1c0cdc58b86c228af2b890f474e76d13daf9c803/diff/etc/nginx/1.txt
test


Комментарии