Images
В проекте Docker впервые предложили паковать контейнеры в послойные образы в 2013. Это позволило переносить контейнеры между машинами.
skopeo, jq
Проверим работу с образами на утилите skopeo и пробном контейнере:
skopeo copy docker://saschagrunert/mysterious-image oci:mysterious-image
sudo apt install tree
tree mysterious-image
Видим, что мы скачали индекс образа (image index) и blob. Изучим индекс:
jq . mysterious-image/index.json
{
"schemaVersion": 2,
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:bc2baac64f1088c56c259a21c280cde5a0110749328454a2b931df6929315acf",
"size": 559
}
]
}
По сути индекс есть манифест более высокого уровня, который содержит указатели на конкретные манифесты для определённых ОС (linux) и архитектур (amd).
jq . mysterious-image/blobs/sha256/bc2baac64f1088c56c259a21c280cde5a0110749328454a2b931df6929315acf
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:0503825856099e6adb39c8297af09547f69684b7016b7f3680ed801aa310baaa",
"size": 2789742
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:6d8c9f2df98ba6c290b652ac57151eab8bcd6fb7574902fbd16ad9e2912a6753",
"size": 120
}
]
}
Image manifest указывает на расположение конфига и набора слоёв для образа контейнера на конкретной ОС и архитектуре. Поле size
указывает общий размер объекта. Теперь можно исследовать далее:
jq . mysterious-image/blobs/sha256/0503825856099e6adb39c8297af09547f69684b7016b7f3680ed801aa310baaa
{
"created": "2019-08-09T12:09:13.129872299Z",
"architecture": "amd64",
"os": "linux",
"config": {
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh"
]
},
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:1bfeebd65323b8ddf5bd6a51cc7097b72788bc982e9ab3280d53d3c613adffa7",
"sha256:56e2c46a3576a8c1a222f9a263dc57ae3c6b8baf6a68d03065f4b3ea6c0ae8d1"
]
},
"history": [
{
"created": "2019-07-11T22:20:52.139709355Z",
"created_by": "/bin/sh -c #(nop) ADD file:0eb5ea35741d23fe39cbac245b3a5d84856ed6384f4ff07d496369ee6d960bad in / "
},
{
"created": "2019-07-11T22:20:52.375286404Z",
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
"empty_layer": true
},
{
"created": "2019-08-09T14:09:12.848554218+02:00",
"created_by": "/bin/sh -c echo Hello",
"empty_layer": true
},
{
"created": "2019-08-09T12:09:13.129872299Z",
"created_by": "/bin/sh -c touch my-file"
}
]
}
Распакуем базовый первый слой из архива и изучим его:
$ mkdir rootfs
$ tar -C rootfs -xf mysterious-image/blobs/sha256/0503825856099e6adb39c8297af09547f69684b7016b7f3680ed801aa310baaa
$ tree -L 1 rootfs/
rootfs/
├── bin
├── dev
├── etc
├── home
├── lib
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin
├── srv
├── sys
├── tmp
├── usr
└── var
Это файловая система ОС! Можно изучить версию дистрибутива
$ cat rootfs/etc/issue
Welcome to Alpine Linux 3.10
Kernel \r on an \m (\l)
$ cat rootfs/etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.10.1
PRETTY_NAME="Alpine Linux v3.10"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"
Распакуем следующий слой и изучим его:
$ tar -C layer -xf mysterious-image/blobs/sha256/6d8c9f2df98ba6c290b652ac57151eab8bcd6fb7574902fbd16ad9e2912a6753
$ tree -L 1 layer/
layer/
└── my-file
1 directory, 1 file
Тут лежим доп файл, созданный командой "/bin/sh -c touch my-file"
- это можно увидеть в секции history. По сути исходный Dockerfile выглядел так:
FROM alpine:latest
RUN echo Hello
RUN touch my-file
Buildah
В 2017 году Red Hat разработали инструмент для создания образов контейнеров по стандарту OCI - как аналог docker build.
Создадим Dockerfile vim Dockerfile
и впишем в него:
FROM alpine:latest
RUN echo Hello
RUN touch my-file
Запустим сборку контейнера на базе этого файла - buildah bud
Buildah поддерживает много команд:
buildah images # список образов
buildah rmi # удалить все образы
buildah ps # показать запущенные контейнеры
Почему вдруг buildah ps
показ запущенных контейнеров, когда это инструмент для их СОЗДАНИЯ? А потому что в процессе создания как в buildah, так и в docker идёт запуск промежуточных контейнеров, их модификация в runtime. Каждый шаг модификации создаёт записи в history. Это потенциальная проблема ИБ: можно влезть в контейнер, пока он собирается (и запущен), если там что-то большое, и модифицировать его.
Например, приготовим манифест:
FROM debian:buster
RUN apt-get update -y && \
apt-get install -y clang
Начнём сборку:
docker build -t clang
Залезем в работающий контейнер:
> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
05f4aa4aa95c 54da47293a0b "/bin/sh -c 'apt-get…" 11 seconds ago Up 11 seconds interesting_heyrovsky
> docker exec -it 05f4aa4aa95c sh
# echo "Hello world" >> my-file
После сборки можно удостовериться, что созданный файл на месте:
> docker run clang cat my-file
Hello world
Создание контейнеров без Dockerfile
У buildah есть императивные команды к любой команде из Dockerfile, типа RUN
или COPY
. Это даёт огромную гибкость, т.к вместо огромных Dockerfile можно делить процесс создания контейнеров на части, между ними запускать любые вспомогательные инструменты UNIX.
Создадим базовый контейнер с Alpine Linux и посмотрим как он работает:
buildah from alpine:latest
buildah ps
Можно запускать команды в контейнере, а также создать в нёс файл:
> buildah run alpine-working-container cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.20.3
PRETTY_NAME="Alpine Linux v3.20"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues"
echo test > file
> buildah copy alpine-working-container test-file file
86f68033be6f25f54127091bb410f2e65437c806e7950e864056d5c272893edb
По-умолчанию, buildah не делает записи history в контейнер, это значит порядок команд и частота их вызова не влияют на итоговые слои. Можно поменять это поведение ключом --add-history
или переменной ENV BUILDAH_HISTORY=true
.
Сделаем коммит нового контейнера в образ для финализации процесса:
buildah commit alpine-working-container my-image
buildah images # новый образ теперь в локальном реестре
Можно выпустить образ в реестр Docker, либо на локальный диск в формате OCI:
> buildah push my-image oci:my-image
> jq . my-image/index.json
{
"schemaVersion": 2,
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:bd3f673eb834eb86682a100768a14ae484b554fb42083666ba1b88fe4fe5c1ec",
"size": 1189
}
]
}
Теперь сделаем в обратную сторону: удалим образ my-image из реестра и вытащим его с диска из формата OCI:
buildah rmi my-image
buildah images
buildah pull oci:my-image
Контейнер alpine-working-container при этом ещё работает. Запустим CLI в контейнере:
buildah run -t alpine-working-container sh
ls
cat file
Проведём mount файл-системы контейнера на локальную (В ДРУГОМ ТЕРМИНАЛЕ):
> buildah unshare --mount MOUNT=alpine-working-container
> echo it-works > "$MOUNT"/test-from-mount
> buildah commit alpine-working-container my-new-image
buildah unshare
создаёт новое пространство имён, что позволяет подключить файловую систему как текущий не-root пользователь;--mount
автоматически подключает, путь кладём в переменную среды MOUNT;- Далее мы делаем commit на изменения, и mount на автомате убирается при покидании buildah unshare сессии.
Мы успешно модифицировали файловую систему контейнера через локальный mount. Проверим наличие файла:
> buildah run -t alpine-working-container cat test-from-mount
it-works
Вложенные контейнеры
У buildah нет даемона, значит, не нужно подключать docker.sock в контейнер для работы с Docker CLI. Это даёт гибкость и возможность делать вложенные схемы: установим buildah в контейнер, созданный buildah:
> buildah from opensuse/tumbleweed
tumbleweed-working-container
> buildah run -t tumbleweed-working-container bash
# zypper in -y buildah
Теперь вложенный buildah готов к использованию. Нужно указать драйвер хранения VFS в контейнере, чтобы получить рабочий стек файловой системы:
# buildah --storage-driver=vfs from alpine:latest
# buildah --storage-driver=vfs commit alpine-working-container my-image
Получили вложенный контейнер. Заложим его в хранилище на локальной машине -> для начала заложим образ в /my-image:
buildah --storage-driver=vfs push my-image oci:my-image
Выходим из вложенного контейнера (В ДРУГОМ ТЕРМИНАЛЕ) и копируем образ из рабочего контейнера путём mount его файловой системы:
> buildah unshare
> export MOUNT=$(buildah mount tumbleweed-working-container)
> cp -R $MOUNT/my-image .
> buildah unmount tumbleweed-working-container
Теперь мы вытаскиваем образ контейнера из директории прямо в локальный реестр buildah:
> buildah pull oci:my-image
> buildah images my-image
ВАЖНОЕ ЗАМЕЧАНИЕ: все действия с buildah не потребовали sudo. Buildah создаёт всё необходимое для каждого пользователя в папках:
~/.config/containers
, конфигурация~/.local/share/containers
, хранилища контейнеров
Декомпозиция Dockerfile в несколько разных с помощью CPP макросов.
podman
Инструмент для замены Docker. podman использует buildah как API для создания Dockerfile с помощью podman build
. Это значит, что они разделяют одно хранилище под капотом. А это значит, что podman может запускать созданные buildah контейнеры:
buildah images
podman run -it my-image sh
/ # ls