Сборка образов в CI без демона: kaniko, BuildKit, Buildah

Рейтинг: 73% · 17 голосов
Практический курс по Docker: образы, контейнеры, тома, сети, Compose и продакшен. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
Marina_DevOps
Сообщения: 54
Зарегистрирован: 11 май 2026, 05:31

Сборка образов в CI без демона: kaniko, BuildKit, Buildah

Сообщение Marina_DevOps »

Оглавление курса (46)
  1. Что такое Docker и какие задачи он решает
  2. Установка Docker и запуск первого контейнера
  3. Образы: слои, теги и реестр Docker Hub
  4. Пишем свой Dockerfile
  5. Тома и хранение данных: volumes и bind mounts
  6. Сети в Docker: связываем контейнеры между собой
  7. Переменные окружения и конфигурация контейнеров
  8. Docker Compose: поднимаем многоконтейнерное приложение
  9. Оптимизация образов: multi-stage сборка, размер и кэш слоёв
  10. Логи, отладка и мониторинг контейнеров
  11. Базовая безопасность контейнеров
  12. Подготовка к продакшену: что важно учесть
  13. Dockerfile глубже: ENTRYPOINT и CMD, HEALTHCHECK, .dockerignore, запуск не от root
  14. Реестры образов: приватные registry, push и pull, теги и digest, imagePullSecrets
  15. BuildKit и buildx: multi-arch сборки, секреты сборки, экспорт кэша
  16. Docker в CI/CD: автосборка, сканирование образов (Trivy, Docker Scout), публикация
  17. Итоговый проект и куда расти: от Dockerfile до прода, обзор оркестрации (Kubernetes, Podman, OCI)
  18. Архитектура Docker: dockerd, containerd, runc и OCI
  19. Как работает изоляция: namespaces и cgroups
  20. Образы изнутри: слои, OverlayFS и storage driver
  21. Жизненный цикл контейнера и политики перезапуска
  22. Отладка контейнеров: exec, attach, nsenter, debug
  23. Сети Docker глубже: bridge, overlay, macvlan и iptables
  24. Docker Compose глубже: profiles, healthcheck-зависимости, override
  25. Данные и тома глубже: драйверы, бэкап, права
  26. Секреты и конфигурация: как не светить пароли
  27. Ограничение ресурсов: CPU, память, OOM и cgroups
  28. Здоровье и автозапуск: HEALTHCHECK, init и сигналы
  29. Логирование контейнеров: драйверы, ротация, сбор
  30. Мониторинг контейнеров: stats, cAdvisor, Prometheus
  31. Безопасность глубже: rootless, user namespaces, seccomp, capabilities
  32. Supply chain: сканирование, SBOM и подпись образов
  33. Глубокая оптимизация образов: distroless, scratch, кэш
  34. Multi-arch и сборка под ARM: buildx и эмуляция
  35. Реестры в продакшене: Harbor и облачные registry
  36. Docker без Docker: Podman, containerd, nerdctl, Buildah
  37. От Compose к оркестрации: когда контейнеров становится много
  38. Docker Swarm: встроенная оркестрация
  39. Сборка образов в CI без демона: kaniko, BuildKit, Buildah (вы здесь)
  40. Траблшутинг Docker: типичные ошибки и как чинить
  41. Производительность и эксплуатация Docker-хоста
  42. Docker Desktop, WSL2 и альтернативы на Windows и Mac
  43. Контейнеризация приложений: бэкенд, фронтенд, БД
  44. Docker и Kubernetes: containerd, миграция, kompose
  45. Лучшие практики и антипаттерны Docker
  46. Сквозной проект: production-ready стек и путь дальше
Представь типичную картину. Пайплайн крутится в Kubernetes, раннер - это под, и тебе надо собрать образ из Dockerfile и запушить в registry. Ты по привычке пишешь docker build - и упираешься в стену: в поде нет docker-демона. Демон - это отдельный процесс dockerd, который слушает сокет /var/run/docker.sock и от рута монтирует слои, дёргает namespaces и cgroups. В обычном CI-раннере его просто нет, и поднять его нетривиально и небезопасно. Этот урок про то, как делается сборка образа без docker - через kaniko, BuildKit rootless и Buildah - и почему для k8s-нативного CI это давно must-have, а не экзотика.

Корень боли: почему docker in docker - это мина

Сначала разберём, откуда вообще берётся проблема, иначе решения покажутся магией. Классический docker build требует общения с демоном. В Kubernetes-раннере демона нет, и появляются два костыля - оба плохие.

Первый - docker in docker (DinD). Ты запускаешь внутри пода ещё один dockerd. Чтобы вложенный демон смог управлять namespaces, монтировать оверлей-файловую систему и создавать сетевые мосты, контейнеру нужен флаг privileged. А privileged-контейнер - это, грубо, рут на ноде. Он видит все устройства хоста через /dev, может перемонтировать /sys, выбраться к cgroup ноды и при удачном стечении - к соседним подам. Один скомпрометированный Dockerfile в одном проекте - и атакующий гуляет по кластеру. Для multi-tenant CI это приговор.

Второй костыль - пробросить хостовый /var/run/docker.sock внутрь пода (docker outside of docker). Звучит легче, но это ещё хуже. Сокет демона - это API без аутентификации с правами рута. Любой, кто дотянулся до docker.sock, может запустить контейнер с монтированием хостовой / и стать рутом на ноде за три команды. Плюс ты собираешь образы общим демоном вперемешку с чужими задачами - изоляции ноль.
Простое правило: если в CI всплывает privileged: true ради сборки или монтируется docker.sock - это не сборка, это дыра, которую кто-то когда-то проэксплуатирует.
Корень в том, что для сборки слоёв исторически нужны были рутовые операции. Прорыв последних лет в том, что благодаря user namespaces, fuse-overlayfs и rootless-режиму контейнерных рантаймов слои можно собирать БЕЗ рута и без привилегий. На этом и стоят инструменты ниже.

Изображение

kaniko: сборка из Dockerfile прямо в контейнере

kaniko появился в Google как ответ на вопрос docker in docker: как собрать образ из Dockerfile в контейнере без демона и без привилегий. Идея простая до изящества. Образ gcr.io/kaniko-project/executor запускается как обычный контейнер. Внутри он сам читает Dockerfile, сам разбирает инструкции и сам по очереди исполняет каждый слой - но не через демон, а напрямую в собственной файловой системе. После каждой инструкции (RUN, COPY, ADD) kaniko делает снимок дельты файловой системы, упаковывает её в tar - это и есть новый слой - и собирает корректный OCI-образ. Никакого dockerd, никаких namespaces от рута.

Код: Выделить всё

# .gitlab-ci.yml - сборка через kaniko
build:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  script:
    - mkdir -p /kaniko/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$(printf '%s:%s' "$CI_REGISTRY_USER" "$CI_REGISTRY_PASSWORD" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json
    - >-
      /kaniko/executor
      --context "${CI_PROJECT_DIR}"
      --dockerfile "${CI_PROJECT_DIR}/Dockerfile"
      --destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}"
      --cache=true
      --cache-repo "${CI_REGISTRY_IMAGE}/cache"
Разберём ключевые поля. Тег executor:debug содержит busybox-shell, поэтому в нём работает before_script и эхо в файл - у голого executor шелла нет вообще, это не Linux-дистрибутив, а статический бинарь. Файл /kaniko/.docker/config.json - это стандартный docker-конфиг с auth-токеном; именно сюда kaniko смотрит за кредами registry. Флаг --context указывает корень сборки, --destination - куда пушить (можно несколько раз), а --cache=true с --cache-repo включает кэш слоёв в registry: kaniko кладёт собранные кэш-слои отдельным репозиторием и при следующем прогоне переиспользует их, если инструкция и её входные файлы не изменились.

Важная грабля по памяти. kaniko держит слои и снапшоты файловой системы в памяти, и на жирных образах (большие COPY, тяжёлые RUN apt install) ему легко не хватает лимита - под падает с OOMKilled. Лечится осознанно: ставь limits.memory с запасом, дроби тяжёлые RUN, и помни про --snapshot-mode=redo или --use-new-run для более экономного снапшотинга. Это не баг, это плата за подход со снимками ФС.

И теперь главное про 2026 год, иначе урок устареет на старте. В июне 2025 Google заархивировал оригинальный репозиторий GoogleContainerTools/kaniko - он стал read-only, активная разработка прекращена. У kaniko остались непропатченные дыры в зависимостях. Проект подхватил форк от Chainguard (Fork Yeah, мы возвращаем kaniko), но классический образ от Google больше не получает обновлений. Вывод для прода: kaniko как концепцию знать обязательно (его всё ещё полно в живых пайплайнах), но новые проекты на нём не начинают. Дефолтная рекомендация сообщества теперь - BuildKit rootless.

BuildKit rootless: buildkitd + buildctl как замена kaniko

BuildKit - это тот самый движок, что под капотом у docker buildx (мы разбирали его в уроках про buildx и кэш). Но BuildKit умеет работать standalone, без docker-демона: отдельно демон buildkitd и отдельно клиент buildctl. А в rootless-режиме buildkitd запускается от непривилегированного пользователя в user namespace - без privileged, без docker.sock. Это и делает связку buildkit ci прямой и более быстрой заменой kaniko: по бенчмаркам BuildKit за счёт параллельного DAG-исполнения стадий и продвинутого кэша обгоняет kaniko на десятки процентов.

Почему быстрее. kaniko исполняет Dockerfile линейно, инструкция за инструкцией. BuildKit строит граф зависимостей (DAG): независимые стадии многоступенчатой сборки идут параллельно, а неизменившиеся ноды берутся из кэша без пересчёта. Плюс mount-кэши для пакетных менеджеров (--mount=type=cache) живут между сборками.

Код: Выделить всё

# .gitlab-ci.yml - rootless BuildKit, без привилегий
build:
  image:
    name: moby/buildkit:rootless
    entrypoint: [""]
  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
  before_script:
    - mkdir -p ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$(printf '%s:%s' "$CI_REGISTRY_USER" "$CI_REGISTRY_PASSWORD" | base64 | tr -d '\n')\"}}}" > ~/.docker/config.json
  script:
    - >-
      buildctl-daemonless.sh build
      --frontend dockerfile.v0
      --local context=.
      --local dockerfile=.
      --output type=image,name=${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA},push=true
      --export-cache type=registry,ref=${CI_REGISTRY_IMAGE}/cache,mode=max
      --import-cache type=registry,ref=${CI_REGISTRY_IMAGE}/cache
По полям. Образ moby/buildkit:rootless содержит и buildkitd, и buildctl, и обёртку buildctl-daemonless.sh - она поднимает временный buildkitd на время сборки и сама его гасит, тебе не надо держать долгоживущий демон. Флаг --oci-worker-no-process-sandbox нужен, потому что в CI обычно нет права на вложенный seccomp/process-sandbox: мы его отключаем, оставаясь при этом без привилегий контейнера (это разные вещи - sandbox процессов внутри и privileged самого пода). Frontend dockerfile.v0 - это парсер Dockerfile. --output type=image,...,push=true собирает и сразу пушит. А ключевое для скорости - пара --export-cache/--import-cache с type=registry и mode=max: BuildKit складывает кэш всех слоёв (включая промежуточные стадии - это и значит mode=max) в отдельный тег registry и тянет его на следующем прогоне. На горячем кэше повторная сборка измеряется секундами.

RU-реалия: gcr.io (где живёт kaniko) и docker.io бывают недоступны или ходят через зеркало, а moby/buildkit и cosign разумно заранее зеркалить во внутренний Yandex Container Registry или VK Cloud Container Registry, чтобы пайплайн не зависел от внешней сети. В --export-cache тогда указывай ref на свой registry.

Buildah и фундамент OCI: nerdctl, runc, containerd

Третий игрок - Buildah от Red Hat, часть набора Podman и с января 2025 в CNCF Sandbox. Buildah тоже daemonless и умеет rootless, но философия другая: это не только Dockerfile, а ещё и набор примитивов для сборки образа по шагам из shell-скрипта (buildah from, buildah run, buildah copy, buildah commit). Удобно, когда образ собирается нестандартно - без Dockerfile вообще.

Код: Выделить всё

# Buildah: и из Dockerfile, и пошагово
# 1) привычный путь - из Dockerfile
buildah build -t registry.example.ru/app:1.0 -f Dockerfile .

# 2) пошаговая сборка скриптом, без Dockerfile
ctr=$(buildah from alpine:3.20)
buildah run "$ctr" -- apk add --no-cache ca-certificates
buildah copy "$ctr" ./app /usr/local/bin/app
buildah config --entrypoint '["/usr/local/bin/app"]' "$ctr"
buildah commit "$ctr" registry.example.ru/app:1.0
buildah push registry.example.ru/app:1.0
Чтобы вся эта картина уложилась, держи в голове слои фундамента. Любой из этих инструментов в итоге производит OCI image (стандарт формата образа) и OCI runtime spec для запуска. Под капотом запуск контейнеров на ноде делает containerd, а низкоуровневый старт процесса в namespace - runc. Именно поэтому Kubernetes ушёл от dockershim: kubelet теперь говорит с containerd напрямую через CRI, docker-демон в цепочке стал лишним звеном. nerdctl - это докер-совместимый CLI поверх containerd, с той же логикой rootless. Все наши герои - kaniko, BuildKit, Buildah - это разные сборщики, которые на выходе дают один и тот же OCI-образ, понятный любому рантайму.

Supply chain в пайплайне: скан, SBOM, подпись, SLSA-provenance

Собрать образ без демона - полдела. Безопасный docker ci - это ещё и доказать, что в образе нет известных дыр и что он собран именно тобой. Четыре шага после сборки.
  • Скан уязвимостей - Trivy или Docker Scout прогоняют образ по базам CVE до пуша в прод-registry. trivy image --exit-code 1 --severity CRITICAL,HIGH valит пайплайн, если нашлось критичное.
  • SBOM (Software Bill of Materials) - список всех пакетов и версий внутри образа. BuildKit умеет генерить его прямо на сборке через --attest type=sbom, отдельно syft тоже подойдёт.
  • Подпись - cosign sign криптографически подписывает образ; в CI это делается keyless через OIDC-токен раннера (GitHub Actions, GitLab), без хранения приватных ключей. Потом cosign verify проверяет подпись перед деплоем в admission-контроллере.
  • SLSA-provenance - аттестация о том, КАК собран образ: какой коммит, какой Dockerfile, какой раннер. BuildKit генерит её через --provenance=mode=max (или --attest type=provenance,mode=max), оборачивая данные в in-toto attestation формата SLSA Provenance v1.

Код: Выделить всё

# Подпись и аттестация в CI (keyless, OIDC)
COSIGN_EXPERIMENTAL=1 cosign sign --yes \
  registry.example.ru/app:${CI_COMMIT_SHORT_SHA}

# Проверка перед деплоем: подпись + provenance конкретного билдера
cosign verify-attestation \
  --type slsaprovenance \
  --certificate-identity-regexp '.*' \
  --certificate-oidc-issuer https://gitlab.example.ru \
  registry.example.ru/app:${CI_COMMIT_SHORT_SHA}
Зачем это вместе. provenance отвечает на вопрос откуда взялся образ, SBOM - что внутри, скан - нет ли там дыр, подпись - не подменили ли его по дороге. В сумме это и есть SLSA: цепочка доверия от коммита до запущенного пода. И заметь - всё это естественно ложится на daemonless-сборку, потому что provenance честнее всего, когда сборщик изолирован и воспроизводим, а не крутится в общем привилегированном демоне.

Типичные грабли и антипаттерны
  • privileged: true ради сборки. Главный антипаттерн. Если видишь его в манифесте раннера - переходи на BuildKit rootless или Buildah, проблема решается без привилегий.
  • Монтирование docker.sock в под. Это раздача рута на ноде. Никогда в multi-tenant.
  • kaniko на новых проектах. Образ от Google заморожен с июня 2025 и не патчится. Либо форк Chainguard, либо сразу BuildKit.
  • OOMKilled у kaniko. Снимки ФС в памяти. Поднимай limits.memory, дроби тяжёлые RUN.
  • Кэш без mode=max. inline-кэш не сохраняет промежуточные стадии многоступенчатой сборки - повторный билд пересчитывает половину. Для CI бери --export-cache type=registry,mode=max.
  • Креды registry в открытую. Не хардкодь пароль в Dockerfile или ENV образа - используй CI-переменные и keyless-подпись через OIDC.
Мини-лаба: повтори руками

Локально, без CI, чтобы прочувствовать daemonless. Демон docker глушить не обязательно, но и пользоваться им не будем.
  • Подними локальный registry: docker run -d -p 5000:5000 --name reg registry:2 (registry демоном не считается, это просто хранилище).
  • Возьми минимальный Dockerfile (FROM alpine, RUN apk add curl, CMD ...).
  • Собери через rootless BuildKit, запустив moby/buildkit:rootless и вызвав внутри buildctl-daemonless.sh build с --output type=image,name=localhost:5000/app:1,push=true.
  • Повтори сборку с --export-cache type=registry,ref=localhost:5000/cache,mode=max и --import-cache - засеки время первого и второго прогона, разница на кэше будет наглядной.
  • Прогони trivy image localhost:5000/app:1 и посмотри список CVE. Затем cosign sign --key ... (или keyless) и cosign verify - убедись, что подпись проверяется.
  • Бонус: собери тот же образ через buildah build -t localhost:5000/app:2 . и сравни, что на выходе одинаковый OCI-образ.
Контрольные вопросы
  • Почему docker in docker с privileged и проброс docker.sock считаются небезопасными в multi-tenant CI - что именно может сделать атакующий?
  • Чем BuildKit rootless архитектурно быстрее kaniko и зачем нужен флаг --oci-worker-no-process-sandbox?
  • Что хранит кэш в режиме mode=max и почему inline-кэша недостаточно для многоступенчатой сборки?
  • Какие четыре артефакта supply chain (скан, SBOM, подпись, provenance) на какой вопрос отвечают и как они складываются в SLSA?
Итог

Сборка образа без docker в k8s-нативном CI - это норма 2026 года, а не хак. DinD и docker.sock закрыты как небезопасные. kaniko показал путь, но с июня 2025 его оригинал заморожен - знать как наследие, начинать на BuildKit rootless или Buildah. Все они дают один OCI-образ поверх containerd/runc. А поверх сборки навешивается supply chain - скан, SBOM, подпись cosign и SLSA-provenance - чтобы образ был не только собран без привилегий, но и доказуемо твой и чистый.
👍7 ❤️3 🔥 😄 🤔
Аватара пользователя
lookat
Сообщения: 1
Зарегистрирован: 03 июн 2026, 23:22

Re: Сборка образов в CI без демона: kaniko, BuildKit, Buildah

Сообщение lookat »

Полгода назад мигрировали с kaniko на buildkit rootless именно из-за OOM на жирных образах, билды реально ускорились в разы на горячем кэше. Только сейчас понял зачем нужен был oci-worker-no-process-sandbox, спасибо что разжевал разницу с privileged.
👍2 ❤️ 🔥 😄 🤔
Аватара пользователя
lunaticbliss
Сообщения: 1
Зарегистрирован: 29 май 2026, 09:00

Re: Сборка образов в CI без демона: kaniko, BuildKit, Buildah

Сообщение lunaticbliss »

А подскажи, если у нас self-hosted gitlab без внешнего OIDC, cosign keyless вообще взлетит или придётся хранить ключ в CI-переменной? И есть ли смысл тащить buildah, если buildkit уже закрывает почти всё?
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Docker Swarm: встроенная оркестрация
Следующая глава →
Траблшутинг Docker: типичные ошибки и как чинить

Все главы курса «Docker: контейнеризация от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: docker no space left on device как очиститьmulti-stage сборка docker как уменьшить образ и почистить кэшкак поднять приватный docker registry push pull и digestharbor реестр образов что это и чем отличается от registry

Вернуться в «Docker с нуля»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость