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 - хорошее место, чтобы обкатать и убедиться что всё в порядке.