Comments 33
А смотрели возможность использовать yarn для установки пакетов? Когда столкнулся с тем что
npm i
занимает значительную часть pipeline, это позволило заметно оптимизировать процесс по времени.Хорошая статья! Делал такое для golang, но столкнулся с тем, что при активной разработке go.mod
меняется всё равно не редко (особенно если сервисов много). И тут на помощь пришел BuldKit.
С ним становится ещё проще, поскольку можно указать напрямую какие директории можно шарить между разными запусками даже если поменялось что-то из слоёв. Работает как если бы шареная директория была отдельным volume который подключается на этапе сборки, можно указывать политики разделения этой директории.
А можно чуть подробнее описать что и как делалось, и какой выигрыш это дало?
Просто по моим наблюдениям с Go всё и так летает, достаточно кешировать на CI каталоги с родными кешами Go, примерно так (для CircleCI):
- save_cache:
when: always
key: v4-{{ checksum "/tmp/tools.ver" }}-{{ checksum "go.mod" }}-{{ .Branch }}
paths:
- ~/go/bin/
- ~/go/pkg/
- ~/go/src/
- ~/.cache/go-build/
- ~/.cache/golangci-lint/
И при наличии этих кешей сборка и тесты Go реально летают, даже на немаленьком проекте.
Да, примерно это и делалось — маунтились кэши в контейнеры.
Из плюсов BuildKit’а — можно явно указать разрешено ли совместное использование и изменение, или нет. Я не рискнул просто маунтить директорию поскольку не уверен в безопасности параллельных сборок в таком случае. С BuildKit можно запускать параллельно, просто результат докачки одного из запусков пропадёт.
1.-2. А можно, пожалуйста, ткнуть ссылочками?
3. С учетом п.4 у меня будет папка `node_modules_production` (так ее назовем для понимания). Не совсем понял, что с ней надо сделать. Почему ее просто не оставить в образе (и даже не `COPY` ее, как автор предлагает), она ведь нужна для runtime? И что именно «удалить, чтоб уменьшить слой»?
4. Это понимаю.
А производительность не просела в результате перехода на alpine?
только сегодня разработчики жаловались, что на сборку проекта уходит полтора часа… полтора часа Карл…
если кто-то Квартус ускорит в сто раз, нужно будет отлить ему памятник в золоте…
сорри… о наболевшем(
Можно ещё вынести процесс сборки Angular-приложения за пределы докера (в команды непосредственно для CI-системы) и кешировать его с помощью механизмов CI-системы; докеру останется только закинуть артефакт в образ Nginx.
Можно не создавать новый образ на основе node:12.16.2-alpine3.11
, а использовать его вместе с docker run
, чтобы собрать Angular-приложение напрямую в CI-системе. Но обычно CI-системы запускаются в докер-контейнере, и у разработчиков есть возможность выбрать образ контейнера, поэтому нет необходимости запускать отдельно образ с Node.js.
Эти решения выходят за рамки просто докерфайла и реализация может быть завязана на конкретную CI-систему.
P.S. Спасибо за описание поэтапной эволюции решения. Мне как новичку в докере это сильно помогает.
Меняем файл src/index.html — имитируем работу программиста.
Постоянно это делаю)
Для этого положим в проект файл `.dockerignore` и укажем, что не нужно для сборки: `/node_modules`.
Вот тут я не совсем понял. В сборке есть `npm ci`, `npm run`, неужели им не нужен `/node_modules`? Или он нужен, но мы его копируем с помощью `COPY. .`?
Но, пока что, остается такой вопрос: почему `/node_modules` надо `COPY`, а не просто оставлять в кэше? Мол, такое копирование быстрее?
Привет! Спасибо за статью! Для новичков она будет полезна. Но стоит и рассказать о подводных камнях. Попробую систематизировать
alpine — уже обсуждали тут на хабре риски связанные с этим.
https://habr.com/ru/post/486202/
https://habr.com/ru/post/415513/
docker build --from-cache регулярно ломается, да и кэширование слоев докера не очень надежно — я слышал, что бывают конфликты хэшей.
в вашем пайлплайне, если изменился базовый образ, то эти изменения не будут замечены. Это актуально для процесса разработки, когда ты наследуешься от какого-нибудь неуточненного тега типа python:3 В проде же наоборот лучше стабильность и это ок.
не рассмотрен вопрос, когда COPY стопицот файлов на overlay2 работает медленно.
докер проверяет изменился ли результат сравнением команды. Т.е. строка в докерфайле
apk --no-cache --update --virtual build-dependencies add python make g++
не изменяется и докер не увидит изменений, если в репозитории ОС появились новые пакеты, и об этом нужно думать отдельно.
без оптимизации каждая сборка бэкенда занимала 14 минут.
соглашусь, что ДЛЯ РАЗРАБОТКИ это непозволительно долго. Очень долго ждешь изменений. Для выката на продакшен — это ОТЛИЧНО. 14 минут? Да ну — там пока кубернетес прочухается, пока образы зальются… Ну, вы поняли )))
4. Спросил у автора, спрошу и у Вас, на всякий случай :) А какой вообще резон включать `node_modules` в `.dockerignore`, а потом — его `COPY`?
Спасибо.
- Не понял, почему. Это про случай, когда Python задеплоит 3.8.3 вместо 3.8.2? Но почему «эти изменения не будут замечены», разве образ
python:3
не изменится (его хэш и, следовательно, он сам), Docker это не проверит?
Объясню как это работает.
- Кэш докера пустой. Он качает с регистри python:3 последней версии. Сборка идет с 3.8.3.
- В кэше докера есть python:3 (с 3.8.2), в репозитории уже он изменен (3.8.3). Докер не ИЩЕТ в регистри новую версию. Собирает с python:3 (с питоном 3.8.2).
- Собираем с ключом
--pull Always attempt to pull a newer version of the image
Тогда идет проверка новой версии. И скачивается она.
Очень очевидное и понятное поведение, ога.
- Спросил у автора, спрошу и у Вас, на всякий случай :) А какой вообще резон включать
node_modules
в.dockerignore
, а потом — егоCOPY
?
Не знаю такого смысла — потому что если что-то включено в .dockerignore, его потом в COPY скопировать уже нельзя )))
4. То есть `COPY. .` вовсе не копирует `node_modules`? (Не ругайтесь, пожалуйста, — я новичок. Да и замысел автора… Там и если копирует — мне логика не до конца ясна, и если нет.) `.dockerignore` влияет не только на набор файлов, с которых считается хэш-сумма, но и на `COPY`?
- То есть
COPY. .
вовсе не копируетnode_modules
? (Не ругайтесь, пожалуйста, — я новичок. Да и замысел автора… Там и если копирует — мне логика не до конца ясна, и если нет.)
Это не так работает. Осознайте концепцию контекста докера. Как это работает? Докер берет каталог из команды docker build <directory>
, исключает из него файлы при помощи .dockerignore и кладет во временный каталог. Далее из временного каталога идет сборка. Соответственно, COPY выбирает файлы из этого временного каталога. COPY . .
копирует, получается, все файлы из временного каталога.
docker build --from-cache регулярно ломается
А зачем вообще нужен --from-cache? Ведь если мы сделали docker pull и образы с кешами уже доступны локально, то докер же сам их найдёт… в чём смысл явно ему указывать в каком образе их искать?
Ведь если мы сделали docker pull и образы с кешами уже доступны локально, то докер же сам их найдёт…
потому что это так не работает. Без --from-cache у вас слои не подтягиваются из старой версии образа.
В смысле? Он же при сборке пишет, что берёт их из кеша. Вот, прямо в статье пример:
$ time docker build -t app .
Sending build context to Docker daemon 409MB
Step 1/5 : FROM node:12.16.2
Step 2/5 : WORKDIR /app
---> Using cache
Это зависит от фаз луны. Допускаю, что это работает в случае, если
а) изначальный базовый образ остался тот же самый и не изменялся в рамках того же тега
б) окончательный образ имеет тот же самый тег, что и собираемый
Короче — с --from-cache
вероятность кэш-промаха СУЩЕСТВЕННО ниже
Судя по официальной документации, дела обстоят несколько иначе. Никакой "вероятности промаха" не существует — по крайней мере я никогда ничего подобного не замечал, если образ есть локально, то он будет использован в качестве кеша всегда автоматически. А --cache-from нужен для того, чтобы не делать docker pull в надежде, что скачанный образ пригодится, вместо этого мы указываем аргументом для --cache-from имя образа в удалённом репозитории, и он на ходу сам выясняет, содержит ли этот образ нужные для кеша слои и скачивает их по необходимости. https://docs.docker.com/engine/reference/commandline/build/#specifying-external-cache-sources
P.S. Помимо экономии на возможно бесполезном docker pull перед сборкой --cache-from должен дать дополнительную экономию на том, что скачает не весь образ с кешем, а только те его слои, которые реально нужны для кеша.
Следующий этап: начать пользоваться фичами гитлаба и задолбаться с докерфайлом.
Кэш докера — неуправляемое чудовище. Лучше кэшировать зависимости гитлабом. Делается в две строчки, зато есть контроль.
А еще гитлаб позволяет удобно смотреть на этапы сборки. И видеть что упал job с тестами, или job с линтером, или… Но если вы все пихаете в докерфайл, вы отказываетесь от этих фич. Решение — gitkab docker runner. Указываем ему, в каком образе запускать команды, и забываем про докер до самого конца, когда надо именно образ собирать.
Пацанам пофиг — у них все собирается в докерах и в докерах же уезжает в продакшен. Никаких других способов доставки приложения у них нет. Если это какой-нибудь веб, то это ок. Но для "коробочного" софта это не годится — там придется и deb, и rpm билдить и под разные операционные системы и все такое. И тогда я полностью согласен — начинает выгоднее быть пользование кэшем гитлаба, а не все эти сумасшедшие мультистадийные сборки… А докер… А докер в этом случае хорошо подходит для задач "заморозки" окружения для билда и для тестов.
Несколько советов о том, как ускорить сборку Docker-образов. Например, до 30 секунд