Лучшие практики для сборки / развертывания образов докеров

Я только что закончил базовый конвейер для моего углового приложения, которое выполняется в образе Node в Docker. Итак, процесс работает следующим образом: нажмите на Gitlab> Подключиться к Jenkins Build> Сценарий развертывания на docker build изображение и нажмите Quay> Опубликовать сценарий, чтобы предложить службе Rancher обновить контейнер и обновить изображение> Завершить.

Теперь проблема, с которой я столкнулся, заключается в том, что образ базового узла довольно большой, то есть, когда я нажимаю простое изменение, для завершения конвейера сборки требуется много времени (~8 минут). Это кажется неоправданным для каждого крошечного изменения, а переход на Quay и последующую публикацию на платформе Rancher означает, что я перехожу с 250 МБ на набережную и 250 МБ на Rancher.

У меня есть несколько "микро-сервисов", запланированных для развертывания, но если каждый раз, когда я хочу развернуть один из них в среде разработки и перемещать столько данных каждый раз, это кажется несколько неэффективным… Что я делаю неправильно, что я пропускаю, и есть ли какие-либо рекомендации для лучшей практики при создании / развертывании / размещении сервисов на основе контейнеров?

1 ответ

Решение

Сначала немного информации об изображениях, сборках, реестрах и клиентах.

Изображения и слои

Сборка образов Docker работает со слоями. Каждый шаг в вашем Dockerfile фиксирует слой, который накладывается поверх предыдущего.

FROM node                   ---- a6b9ffdcf522
RUN apt-get update -y        --- 72886b467bd2
RUN git clone whatever        -- 430615b3487a
RUN npm install                - 4f8ddac8d3b5 mynode:latest

Каждый слой, составляющий изображение, индивидуально идентифицируется контрольной суммой sha256. IMAGE ID в docker images -a это короткий фрагмент этого.

Бег dockviz images -t на хосте сборки даст вам лучшее представление о дереве слоев, которые могут накапливаться. Во время сборки вы можете увидеть, как растет ветвь, а затем последний слой в конечном итоге помечается, но этот слой остается в дереве и сохраняет ссылку на своих родителей.

Кэширование сборки

Сборки Docker кэшируются по умолчанию на каждом этапе сборки. Если RUN команда в файле Docker не изменилась или COPY исходные файлы, которые вы копируете, не изменились, тогда этот этап сборки не должен запускаться снова. Уровень остается таким же, как и идентификатор контрольной суммы sha256, и докер пытается создать следующий уровень.

Когда докер перейдет к шагу, который необходимо перестроить, "дерево" изображений, которое представляет dockviz, ответвляется, чтобы создать новый слой с новой контрольной суммой. Любые шаги после этого необходимо выполнить снова и создать слой в новой ветви.

реестры

Реестры тоже понимают это наслоение. Если вы изменяете только самый верхний слой в своем новом теговом изображении, это единственный слой, который должен быть загружен в реестр ( есть предостережения, он лучше всего работает с недавней версией docker-1.10.1+ и реестром 2.3+) В реестре уже будет копия большинства идентификаторов изображений, которые составляют ваше новое "изображение", и нужно будет отправлять только новые слои.

клиенты

Клиенты реестра Docker работают со слоями одинаково. При вытягивании изображения оно фактически загружает отдельные слои (капли), которые составляют изображение. Вы можете увидеть это из списка идентификаторов изображений, напечатанных, когда вы docker pull или же docker run новое изображение. Опять же, если большинство слоев одинаковы, тогда для обновления потребуется загрузить только те самые верхние слои, которые изменились, что экономит драгоценное время.

Минимизация времени сборки

Итак, на чем вы хотите сосредоточиться

  • Держите размеры изображения маленькими
  • Воспользуйтесь преимуществами кэширования сборки
  • Используйте общие "помеченные" родительские изображения.

Держите размеры изображения маленькими

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

Если вы можете избежать использования полной ОС, сделайте это. Когда вы можете запускать приложения на busybox или же alpine Это заставляет богов докеров улыбаться. сборка alpine + a node.js занимает менее 50 МБ. Go двоичные файлы - отличный пример минимизации размера. Они могут быть статически скомпилированы и не иметь зависимостей, поэтому могут быть запущены на пустом месте scratch образ.

Воспользуйтесь преимуществами Dockers для построения кэширования

Важно, чтобы ваши наиболее часто изменяющиеся артефакты (скорее всего, ваш код) запаздывали в вашей записи. Dockerfile, Сборка будет замедляться, если сборка должна обновить полные 50 МБ данных для одного небольшого изменения файла, что делает кеш недействительным для шага сборки.

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

Все остальное в сборке, которое редко обновляется, должно идти в начало Dockerfile,

Используйте общие "помеченные" родительские изображения

Хотя контрольная сумма изображения была несколько исправлена ​​в Docker 1.10 и далее, использование общего родительского изображения гарантирует, что вы будете начинать с того же идентификатора общего изображения, где бы вы ни использовали это изображение с FROM,

До Docker 1.10 идентификаторы изображений были просто случайным uuid. Если у вас были сборки, работающие на нескольких хостах, все слои могут быть признаны недействительными и заменены в зависимости от того, какой хост их построил. Даже если слои были на самом деле то же самое.

Общие родительские изображения также помогают, когда у вас есть несколько служб и несколько Dockerfile которые в основном одинаковы. Всякий раз, когда вы начинаете повторять шаги сборки в нескольких Dockerfile s, вытяните эти шаги в общее родительское изображение, чтобы слои были определенно разделены между всеми вашими сервисами. По сути, вы уже получаете это, используя node изображение как ваша база.

Трюки Node.js

Если вы работаете npm install после того, как ваш код развернет каждую сборку и у вас будет ряд зависимостей, npm install вызывает много повторной работы, которая на самом деле не сильно меняет каждую сборку. Возможно, стоит создать рабочий процесс для node_modules до изменения кода. Тогда npm install нужно только запустить вас, когда package.json обновляется

FROM node
WORKDIR /app
COPY package.json /app/package.json
RUN npm install && rm -rf ~/.npm
COPY . /app/
CMD [ "node", "/app/server.js" ]

Постановочная сборка

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

FROM node:8 AS build
WORKDIR /build
RUN apt-get update \
 && apt-get install build-essential;
COPY package.json /build/package.json
RUN npm install; \
 && rm -rf ~/.npm;
# Stage 2 app image
FROM node:8-slim
WORKDIR /app
COPY --from=build /build/node_modules /app/node_modules
COPY . /app/ 
CMD [ "node", "/app/server.js" ]

Другие вещи

Убедитесь, что у вашего хоста сборки есть ssd и хорошее интернет-соединение, потому что будут времена, когда вам придется делать полную перестройку, поэтому чем быстрее, тем лучше. AWS обычно работает хорошо, потому что пакеты и образы, которые вы извлекаете и отправляете, вероятно, также размещены на AWS. AWS также предоставляет услуги регистрации изображений (ECR) только за стоимость хранения.

Другие вопросы по тегам