Сборка контейнеров без Docker

Привет, Хабр. Очень много много сейчас слышно про Кубернетис и Docker. Про них не знает наверное, только ленивый. Но есть и другие варианты работы с контейнерами. Предлагаю перевод статьи одного энтузиаста, который решил изучить похожие инструменты.

Я хочу рассказать о том, как создавать контейнеры без использования Docker. Я буду использовать OpenFaaS, который использует образы контейнеров в формате OCI для своих рабочих нагрузок. Можно сказать, что OpenFaaS — это платформа CaaS для Kubernetes,  которая способна запускать микросервисы и бесплатно добавлять FaaS и инструменты, управляющие событиями. Начнём с демонстрации того, как использовать встроенный buildkit для интерфейса командной строки Docker, потом опишем buildkit автономный (только в Linux), а затем — конструктор контейнеров Google,  Kaniko.

А что не так с Docker?

Да всё с ним так. Он хорошо работает на armhf, arm64 и на x86_64. Основной интерфейс командной строки Docker стал намного больше, чем просто сборка/отправка/запуск, теперь он поставляется вместе с функциями Docker Swarm и EE.

Альтернативы Docker

Было несколько попыток вернуть Docker к его привычному виду, в который мы все влюбились.

  • Docker — теперь сам Docker использует containerd для запуска контейнеров и поддерживает сборку buildkit для создания высокоэффективных кэширующих сборок.

  • Сочетание Podman и buildah — детище RedHat/IBM, которые используют собственный набор инструментов OSS для генерации образов OCI. Podman позиционируется как не имеющий демонов и root-ов, но в конечном итоге ему приходится монтировать оверлейные файловые системы и использовать сокет UNIX.

  • Pouch — предлагаемый Alibaba инструмент считается «Эффективным контейнерным движком корпоративного класса». Он использует containerd так же, как Docker, и поддерживает изоляцию на уровне контейнера с помощью runc и «легкие виртуальные машины», такие как runV. Также больше внимания уделяется дистрибуции образов и изоляции.

  • Автономный BuildKit -buildkit был создан Тынисом Тийги из Docker Inc как новый конструктор контейнеров с учетом кеширования и параллелизма. buildkit в настоящее время работает только как демон, но вы можете увидеть, как люди утверждают обратное. Они разветвляют (fork) демона, а затем убивают его после сборки.

  • img — был написан Джессом Фрейзеллом и является оболочкой для buildkit. Однако он не так уж и крут по сравнению с другими вариантами. Проект активно разрабатывался до конца 2018 года, но потом обрёл лишь несколько исправлений. Есть мнение, что img имеет лучший пользовательский интерфейс buildctr, чем собственный интерфейс командной строки buildkit, но также следует отметить, что img выпущен только для x86_64 и не существует бинарников для armhf/arm64.

  • k3c —интересный эксперимент Rancher, который использует containerd и buildkit для воссоздания оригинального, тёплого лампового Docker. Включает компонент среды выполнения и вскоре планирует поддерживать архитектуры ARM.

Из всех вариантов мне больше всего нравится k3c, но он очень наивный и объединяет всё в один бинарник, который может конфликтовать с другим программным обеспечением, в настоящее время у него есть собственные бинарники containerd и buildkit.

Итак, поскольку мы изучаем «build» и хотим рассмотреть относительно стабильные варианты, давайте поговорим про:

Всё вышеперечисленное (да и многое другое) реально, так как OpenFaaS CLI умеет выводить стандартный «контекст сборки», с которым может работать любой конструктор

Создайте тестовое приложение

Начнём с HTTP Golang middleware, это нечто среднее между функцией и микросервисом, которое показывает, насколько универсальным может быть OpenFaaS.

faas-cli template store pull golang-middleware

faas-cli new --lang golang-middleware \
  build-test --prefix=alexellis2

--lang указывает шаблон сборки

build-test — это имя функции

--prefix имя пользователя Docker Hub, которое будет использоваться для загрузки нашего образа OCI.

У нас получится следующее:

./
├── build-test
│   └── handler.go
└── build-test.yml

1 directory, 2 files

Так выглядит обработчик, и его легко изменить. Дополнительные зависимости могут быть добавлены с помощью вендоринга или модулей Go.

package function

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func Handle(w http.ResponseWriter, r *http.Request) {
	var input []byte

	if r.Body != nil {
		defer r.Body.Close()

		body, _ := ioutil.ReadAll(r.Body)

		input = body
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte(fmt.Sprintf("Hello world, input was: %s", string(input))))
}

Сборка нормальным способом

Нормальными способом создания этого приложения может быть:

faas-cli build -f build-test.yml

Локальный кеш шаблона и Dockerfile также доступен по адресу

./template/golang-middleware/Dockerfile

В этот шаблон включены три образа:

FROM openfaas/of-watchdog:0.7.3 as watchdog
FROM golang:1.13-alpine3.11 as build
FROM alpine:3.12

В традиционном конструкторе каждый из образов будет загружен последовательно. Подождите несколько минут, и теперь у нас есть эти образы в нашей локальной библиотеке. Мы также можем отправить их в реестр с помощью faas-cli push -f build-test.yml.

960e35acdd1051fb317b1ca58887b374.png

Сборка с помощью Buildkit и Docker

Это простейший вариант из всех возможных, да и сборка проходит быстро.

DOCKER_BUILDKIT=1 faas-cli build -f build-test.yml

При таком подходе демон Docker автоматически переключает свой сборщик на buildkit. Buildkit даёт ряд преимуществ:

  • Более сложное кеширование

  • Если возможно, сначала выполнить более поздние инструкции — например, загрузить образ «среды выполнения» до того, как сборка на уровне «sdk» будет завершена.

  • Сверхбыстрая сборка во второй раз

  • С помощью buildkit все базовые образы могут быть загружены в нашу локальную библиотеку сразу, поскольку команды FROM (загрузка) не выполняются последовательно.

С помощью buildkit все базовые образы могут быть загружены в нашу локальную библиотеку сразу, поскольку команды FROM (загрузка) не выполняются последовательно.

FROM openfaas/of-watchdog:0.7.3 as watchdog
FROM golang:1.13-alpine3.11 as build
FROM alpine:3.11

Этот параметр работает даже на Mac, поскольку buildkit проксируется через демон Docker, работающий на виртуальной машине.

18746cf9405fec75e69dc92a4100ac91.png

Сборка с помощью автономного набора Buildkit

Для сборки с помощью автономного Buildkit нам нужно запустить buildkit отдельно на хосте Linux, поэтому мы не можем использовать Mac.

faas-cli build обычно выполняет или разветвляет docker, потому что команда — это просто оболочка. Итак, чтобы обойти это поведение, мы должны написать контекст сборки, что возможно с помощью следующей команды:

faas-cli build -f build-test.yml --shrinkwrap

[0] > Building build-test.
Clearing temporary build folder: ./build/build-test/
Preparing ./build-test/ ./build/build-test//function
Building: alexellis2/build-test:latest with golang-middleware template. Please wait..
build-test shrink-wrapped to ./build/build-test/
[0] < Building build-test done in 0.00s.
[0] Worker done.

Total build time: 0.00

Наш контекст теперь доступен в ./build/build-test/ папке с нашим кодом функции и шаблоном с его точкой входа и Dockerfile.

./build/build-test/
├── Dockerfile
├── function
│   └── handler.go
├── go.mod
├── main.go
└── template.yml

1 directory, 5 files

Теперь нам нужно запустить buildkit.

curl -sSLf https://github.com/moby/buildkit/releases/download/v0.6.3/buildkit-v0.6.3.linux-amd64.tar.gz | sudo tar -xz -C /usr/local/bin/ --strip-components=1

Если вы заглянете на страницу релизов, вы также найдете buildkit, доступный для armhf и arm64, и отлично подходящий для мультиархитектуры.

Запустите демон buildkit в новом окне:

sudo buildkitd 
WARN[0000] using host network as the default            
INFO[0000] found worker "l1ltft74h0ek1718gitwghjxy", labels=map[org.mobyproject.buildkit.worker.executor:oci org.mobyproject.buildkit.worker.hostname:nuc org.mobyproject.buildkit.worker.snapshotter:overlayfs], platforms=[linux/amd64 linux/386] 
WARN[0000] skipping containerd worker, as "/run/containerd/containerd.sock" does not exist 
INFO[0000] found 1 workers, default="l1ltft74h0ek1718gitwghjxy" 
WARN[0000] currently, only the default worker can be used. 
INFO[0000] running server on /run/buildkit/buildkitd.sock 

Начнём сборку, передав упакованное в термоусадочный материал местоположение в качестве контекста сборки. Нам нужна buildctl. Команда buildctl — это клиент для демона, который фиксирует, как построить образ и что делать, когда это будет сделано, например, экспорт tar, игнорирование сборки или отправка её в реестр.

buildctl build --help
NAME:
   buildctl build - build

USAGE:
   
  To build and push an image using Dockerfile:
    $ buildctl build --frontend dockerfile.v0 --opt target=foo --opt build-arg:foo=bar --local context=. --local dockerfile=. --output type=image,name=docker.io/username/image,push=true
  

OPTIONS:
   --output value, -o value  Define exports for build result, e.g. --output type=image,name=docker.io/username/image,push=true
   --progress value          Set type of progress (auto, plain, tty). Use plain to show container output (default: "auto")
   --trace value             Path to trace file. Defaults to no tracing.
   --local value             Allow build access to the local directory
   --frontend value          Define frontend used for build
   --opt value               Define custom options for frontend, e.g. --opt target=foo --opt build-arg:foo=bar
   --no-cache                Disable cache for all the vertices
   --export-cache value      Export build cache, e.g. --export-cache type=registry,ref=example.com/foo/bar, or --export-cache type=local,dest=path/to/dir
   --import-cache value      Import build cache, e.g. --import-cache type=registry,ref=example.com/foo/bar, or --import-cache type=local,src=path/to/dir
   --secret value            Secret value exposed to the build. Format id=secretname,src=filepath
   --allow value             Allow extra privileged entitlement, e.g. network.host, security.insecure
   --ssh value               Allow forwarding SSH agent to the builder. Format default|[=|[,]]

Вот что я сделал, чтобы получить эквивалент команды Docker с DOCKER_BUILDKIT переопределением:

sudo -E buildctl build --frontend dockerfile.v0 \
 --local context=./build/build-test/ \
 --local dockerfile=./build/build-test/ \
 --output type=image,name=docker.io/alexellis2/build-test:latest,push=true

Перед запуском этой команды вам необходимо запустить docker login или создать $HOME/.docker/config.json` с действительным набором незашифрованных учётных данных.

Вы увидите красивую анимацию ASCII для этой сборки.

6c4a181cd473a9bf342e5305bf06c91e.png

Сборка с img и buildkit

Поскольку я ранее никогда не использовал img и не слышал о том, чтобы его использовали с опциями по сравнению с более распространенными вариантами, я решил познакомиться с ним.

Первые впечатления таковы, что мультиархитектура здесь не является приоритетом и, учитывая возраст проекта, кардинальные изменения маловероятны. Для armhf или ARM64 нет бинарников.

Для x86_64последней версии v0.5.7от 7 мая 2019, сборка с Go 1.11, с 1.13 Go является актуальной версией:

sudo curl -fSL "https://github.com/genuinetools/img/releases/download/v0.5.7/img-linux-amd64" -o "/usr/local/bin/img" \
	&& sudo chmod a+x "/usr/local/bin/img"

Параметры сборки выглядят как подмножество buildctl:

img build --help
Usage: img build [OPTIONS] PATH

Build an image from a Dockerfile.

Flags:

  -b, --backend  backend for snapshots ([auto native overlayfs]) (default: auto)
  --build-arg    Set build-time variables (default: [])
  -d, --debug    enable debug logging (default: false)
  -f, --file     Name of the Dockerfile (Default is 'PATH/Dockerfile') (default: )
  --label        Set metadata for an image (default: [])
  --no-cache     Do not use cache when building the image (default: false)
  --no-console   Use non-console progress UI (default: false)
  --platform     Set platforms for which the image should be built (default: [])
  -s, --state    directory to hold the global state (default: /home/alex/.local/share/img)
  -t, --tag      Name and optionally a tag in the 'name:tag' format (default: [])
  --target       Set the target build stage to build (default: )

Вот что нам нужно для сборки:

sudo img build -f ./build/build-test/Dockerfile -t alexellis2/build-test:latest ./build/build-test/

По тем или иным причинам img фактически не удалось сделать успешную сборку. Это может быть связано с некоторыми изменениями, необходимыми для попытки запустить приложение без root-прав.

6b8295a5ccb2e034de40aece333fc07f.png

fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x1 addr=0xe5 pc=0x7f84d067c420]

runtime stack:
runtime.throw(0xfa127f, 0x2a)
	/home/travis/.gimme/versions/go1.11.10.linux.amd64/src/runtime/panic.go:608 +0x72
runtime.sigpanic()
	/home/travis/.gimme/versions/go1.11.10.linux.amd64/src/runtime/signal_unix.go:374 +0x2f2

goroutine 529 [syscall]:
runtime.cgocall(0xc9d980, 0xc00072d7d8, 0x29)
	/home/travis/.gimme/versions/go1.11.10.linux.amd64/src/runtime/cgocall.go:128 +0x5e fp=0xc00072d7a0 sp=0xc00072d768 pc=0x4039ee
os/user._Cfunc_mygetgrgid_r(0x2a, 0xc000232260, 0x7f84a40008c0, 0x400, 0xc0004ba198, 0xc000000000)

Сборка с Kaniko

Kaniko — это конструктор контейнеров Google, который нацелен на сборку контейнеров в песочнице. Вы можете использовать его как одноразовый контейнер или как отдельный бинарник.

Я взглянул на сборку в этом блоге:

docker run -v $PWD/build/build-test:/workspace \
 -v ~/.docker/config.json:/kaniko/config.json \
 --env DOCKER_CONFIG=/kaniko \
 gcr.io/kaniko-project/executor:latest \
 -d alexellis2/build-test:latest
  • Флаг –d указывает, куда нужно поместить изображение после успешной сборки.

  • -v Флаг привязки монтажа текущего каталога в контейнер Kaniko, он также добавляет свой config.json файл для пуша к удалённому реестру.

cfdd79da6fdf224a6b23cbb76e66ea89.png

В Kaniko есть некоторая поддержка кеширования, но для этого требуется ручное управление и сохранение, поскольку Kaniko работает в one-shot режиме, а не с демоном, как Buildkit.

Итоги тестирования

  • Докер — традиционный. Установка Docker может оказаться сложной задачей и нагрузить вашу систему больше, чем ожидалось. Продукт самый старый и медленный, но выполняет свою работу. Следите за сетевым мостом, установленным Docker, он может конфликтовать с другими частными сетями, использующими тот же диапазон частных IP-адресов.

  • Докер с buildkit. Это самый быстрый вариант с наименьшим количеством оттока или изменений. Включается префиксом команды DOCKER_BUILDKIT=1

  • Автономный buildkit. Этот вариант отлично подходит для сборки в кластере или для системы, в которой не требуется Docker, например для CI box или runner. Ему нужен хост Linux, и нет хорошего опыта использования его в MacOS. Возможно, путем запуска дополнительной виртуальной машины или хоста и доступа поверх TCP?

Я также хотел бы упомянуть сравнение инструментов создания образов контейнеров следующего поколения. Это лучший вариант для пользователей faasd, где пользователи полагаются только на containerd и CNI, а не на Docker или Kubernetes.

  • Kaniko. То, как мы использовали Kaniko, по-прежнему требовало установки Docker, но процесс выглядел немного иначе.

Подведение итогов

Вы можете использовать свой обычный конструктор контейнеров с OpenFaaS или faas-cli build –shrinkwrap или передать контекст сборки предпочитаемому инструменту. Вот примеры инструментов для создания контейнеров OpenFaaS:

В облаке OpenFaaS мы обеспечиваем полную автоматизацию CI/CD с использованием подхода shrinkwrap и демона buildkit. Всем остальным пользователям я бы рекомендовал использовать Docker или Docker с buildkit.

У пользователей faasd на хосте установлен только containerd docker, поэтому лучшим вариантом для них является загрузка buildkit.

Мы не затронули одну из важных частей рабочего процесса — развёртывание. Любой контейнер OCI может быть развернут в плоскости управления OpenFaaS поверх Kubernetes, если он соответствует определению бессерверной рабочей нагрузки.

© Habrahabr.ru