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-fileBuildah
В 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-imagebuildah 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