ADG Оставить заявку
Блог DevOps 5 мин чтения

Docker 19.03 RC: BuildKit по умолчанию, параллельные этапы сборки и rootless mode

Включили BuildKit на CI-серверах под Docker 19.03 RC: многоэтапные сборки пошли быстрее за счёт параллельных stages и правильного кеширования слоёв.

Контекст момента

Docker 19.03 RC - BuildKit по умолчанию, rootless mode и поддержка GPU-контейнеров

Docker 19.03 вышел в RC на прошлой неделе, и самое заметное в нём - BuildKit переехал из экспериментального флага в стандартный путь. Мы уже несколько месяцев гоняли BuildKit вручную через DOCKER_BUILDKIT=1 на части пайплайнов, так что новость про «по умолчанию» нас не застала врасплох. Зато повод сделать нормальный перевод CI-серверов - появился.

Что такое BuildKit и почему это важно

В классическом docker build сборка идёт строго последовательно: каждая инструкция Dockerfile - отдельный шаг, следующий начинается только когда закончился предыдущий. BuildKit - это переработанный движок сборки с DAG-исполнителем внутри. Он разбирает зависимости между инструкциями и запускает параллельно всё, что не зависит друг от друга.

Для многоэтапных сборок это принципиальная разница. Типичный Dockerfile для managed-проекта с несколькими компонентами выглядит примерно так: один stage собирает backend, другой - frontend, третий - документацию, четвёртый собирает финальный образ из всех трёх. В классическом builder backend, frontend и документация шли последовательно. В BuildKit все три запускаются параллельно, финальный stage ждёт их все.

Как включали на CI

На наших Jenkins-агентах стоит Docker в управляемой конфигурации. Обновление до 19.03 RC - осознанный шаг: RC, не GA, поэтому сначала на одном агенте, под наблюдением несколько дней.

Включение BuildKit по умолчанию - одна строка в /etc/docker/daemon.json:

{
  "features": {
    "buildkit": true
  }
}

В 19.03 это становится дефолтом без конфига, но явно прописать - спокойнее: видно что оно включено намеренно, и нет сюрпризов при обновлении конфига демона по другим причинам.

После перезапуска демона все docker build начали идти через BuildKit автоматически. Без изменений в Jenkinsfile.

Что изменилось на практике

Результат проявился на первом же запуске. Пайплайны с многоэтапными Dockerfile, которые раньше занимали в районе 12-15 минут на сборку, прошли существенно быстрее - несколько параллельных stages вместо последовательных.

Важнее другое: BuildKit правильно работает с кешем при параллельных stage-ах. Классический builder иногда инвалидировал кеш «за компанию» - менялся один stage, и следующий за ним пересобирался даже если от него не зависел. BuildKit отслеживает зависимости точнее и пересобирает только то, что реально изменилось.

Очереди Jenkins разгрузились. Это, пожалуй, самое ощутимое следствие. Раньше несколько сборок одновременно создавали очередь на CI-агентах - длинные jobs блокировали слоты. Когда каждая сборка стала быстрее, очереди стали короче. Не волшебным образом - просто банальная арифметика: меньше времени на job, больше jobs за тот же час.

Кеш слоёв стал более предсказуемым. В BuildKit кеш хранится с учётом контекста: тот же шаг с теми же входными данными всегда попадёт в кеш независимо от того, что происходит в других ветках DAG. Раньше мы иногда ловили странные промахи кеша, которые сложно было объяснить - теперь таких случаев стало заметно меньше.

Rootless mode

В 19.03 RC появился rootless mode - запуск dockerd без root-привилегий, через rootlesskit. Технология не новая сама по себе, но теперь она поддерживается официально.

Мы посмотрели на это внимательно, потому что тема запуска демона с правами обычного пользователя на CI-агентах периодически всплывает в контексте безопасности. Пощупали на отдельной машине:

dockerd-rootless-setuptool.sh install
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
docker run hello-world

Работает. Но есть ограничения, которые важно понимать: overlay filesystem в rootless mode требует ядра с поддержкой user namespaces и fuse-overlayfs, если ядро не тянет native overlay в user namespace (нужно 4.18+). На наших агентах Ubuntu 18.04 с ядром 4.15 пришлось бы дополнительно разбираться с overlay. На Ubuntu 18.04 с HWE-ядром - уже лучше, но всё равно требует проверки.

На production CI rootless пока не включали: риск несовместимости с текущей конфигурацией выше, чем выигрыш прямо сейчас. Но направление понятное.

GPU-поддержка

Третий заметный момент в 19.03 - нативная поддержка GPU без nvidia-docker2. Теперь достаточно --gpus all в docker run, и runtime сам подключает GPU через device plugin API.

У нас пока нет GPU-нагрузки в контейнерах на продакшне, так что это осталось теоретическим наблюдением. Но несколько клиентов с ML-инфраструктурой это точно заинтересует - проще --gpus all вместо отдельного runtime и хаков с --runtime=nvidia.

Что стоит проверить при переходе

Пара вещей, которые потребовали внимания:

  • ADD и COPY с внешними URL - BuildKit по-другому обрабатывает кеш для URL. Если в Dockerfile есть ADD https://..., поведение может отличаться от ожидаемого.
  • Переменные --build-arg - BuildKit чуть строже в отслеживании того, какие build arg-ументы реально влияют на каждый слой. Аргументы, объявленные но не использованные, не инвалидируют кеш. Это правильное поведение, но если пайплайн строился в расчёте на то что любой build-arg сбрасывает кеш - нужно проверить.
  • Вывод в логах другой. Это не проблема, но неожиданно: BuildKit показывает прогресс параллельных stages одновременно, и лог выглядит иначе чем раньше. Если какой-то скрипт парсит вывод docker build - придётся адаптировать.

Где сейчас

BuildKit работает на CI уже несколько недель, инцидентов не было. Несколько Dockerfile пришлось незначительно поправить под изменившееся поведение кеша, но это скорее правки к лучшему - мы просто не замечали, что кеш инвалидировался лишний раз.

RC есть RC - на production-окружения клиентов пока не катим, ждём GA. Но CI - хорошее место, чтобы обкатать и убедиться что всё в порядке.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.