Как стать автором
Обновить

Как разделить окружение для сборки и запуска сервиса в Docker сегодня и как это cделать завтра

Время на прочтение4 мин
Количество просмотров34K
Всего голосов 51: ↑50 и ↓1+49
Комментарии34

Комментарии 34

Спасибо за пост.
Интересная фича. Сейчас как альтернативу образам сборки использую докеризированные команды, что в моём случае (интерпретируемые языки) вполне удобно.

+1 ( была бы карма, поставил бы полноценный плюсик )
использую докеризированные команды

Вы могли бы привести пример?
Под контейнеризированными командами я подразумеваю когда некий скрипт (или бинарный файл) выполняется внутри контейнера. Например в случае задач по сборке — артефакты пакетного менеджера или компилятора могут располагаться в примантированной директории. Контейнер в этом случае живёт только время выполнения задачи сборки и никак не связан с образом времени исполнения и не нуждается в Dockerfile.
А сама команда не является образом?
Команда может выполняться в контейнере на базе специализированного образа. Для большинства распространённых сборщиков есть официальные образы, https://hub.docker.com/r/library/maven/ например, https://hub.docker.com/r/library/composer/ и подобные.
Docker всё больше напоминает мне Нео из Матрицы. С каждым днем он может всё больше и лучше.
НЛО прилетело и опубликовало эту надпись здесь
На практике сейчас также можно обходиться одним Dockerfile и при этом не держать внутри образа зависимостей, необходимых только в момент сборки. Ваш пример можно было бы переписать так:
FROM golang:1.8.1-alpine
WORKDIR /go/src/github.com/username/project
ADD . ./
# Устанавливаем зависимости, необходимые для сборки
RUN apk add --no-cache --virtual .build-requirements \
    git \
    make \
# Запускаем сборку
    && make build \
# удаляем установленные выше пакеты
    && apk del .build-requirements

Если зависимостей сборки уйма, весь этот ком не забыть почистить… Не очень изящно, имхо.

Согласен, изящность немного хромает из-за жирного RUN. Но такой способ мне кажется предпочтительнее использования нескольких Dockerfile и сложной процедуры сборки образа.
Не соглашусь.

Как минимум:
1. Не всё можно поставить в образ через менеджер пакетов.
2. Теряется кэширование слоёв. В итоге каждый раз ставим всё заново, что увеличивает общее время сборки.
Если вы используете Go для написания вашего кода, то можно писать FROM scratch и тогда размер контейнера будет равен размеру вашего бинарника. Это позволяет получать контейнеры размером в 1-2 мегабайта. Пример: https://github.com/aerokube/selenoid-ui/blob/master/Dockerfile#L1 Недостаток такого подхода — в контейнере вообще ничего нет кроме бинарника, т.е. зайти внутри и поотлаживаться не получится.
Большое спасибо за дополнение! Это самый минималистичный вариант из возможных!

т.е. помимо того, что, как написали выше, не получится сделать отладку, ещё и чистить сборочные образы придётся вручную? да ещё и промежуточных сборок не оставить.
В чём profit?


и да, отдельные докеры для сборки и запуска это возможность поддерживать их более централизованно. А так получается, на N-цать похожих сервисов придётся вместо одного докера-сборщика и одного докера-запускатора делать поддержку N-цати докеров

При использовании стадий сборки промежуточные слои остаются полностью функциональны, так что кеширование есть.

а когда более двух стадий, то каждая стадия выглядит как none:none, отличаясь только imageId? а когда несколько сборок идут на сервере для разных Dockerfile, то как понять какая стадия от какой сборки легла?

К сожалению, понять не получится.
Решаю в данный момент похожую задачу, т.к. обновиться на новый докер возможности нет, реализую следующую идею:

FROM ...
ARG APPLICATION=app=1.2.3
RUN apt-get install <dependencies> \
$APPLICATION
...


получается параметризованный образ, который по дефолту используется для запуска приложения, а когда нужно сделать сборку из командной строки в ARG передаю «devscripts debhelper ..» и все, что нужно для сборки.
Конечно во время сборки CMD тоже нужно оверрайднуть, docker run это позволяет.
Минус тот же, что уже обсуждали — нет кэширования, время сборки возрастает. После успешной сборки нужно еще раз запустить docker build
Плюс — собираю и запускаю юнит тесты фактически в той среде, в которой и запускаю готовое приложение
Вполне рабочий вариант! Многие так делают. При таком подходе обычно уменьшают размер финального образа вызывая `apt-get clean` после установки зависимостей.

Best practice и при использовании новой фичи остаются актуальны: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/
Еще не хватает такой фишки как чисто сборочный контейнер.
Т.е. когда нужно собрать, забрать артефакты и уничтожить.

Опций --rm и --force-rm у docker build не хватает?

Декларативный стиль vs процедурный. Как забрать потом артефакты без volume mount?

А потом появятся зависимости между стадиями? Как-то сам собой уже напрашивается синтаксис Makefile :)

Собственно новая фича и сделана для создания зависимостей :) Промежуточные таргеты кэшируются, так что без необходимости повторной сборки не будет.

Опередили с постом :)


А с docker-compose уже пробовал кто проверять как дружит?


особенно для компилируемых языков.

Очень хороши когда нужно разложить артефакты сборки по разным контейнерам, например, для веб-приложения статику положить в контейнер nginx, а собственно приложение упаковать отдельно. До сих пор приходилось мудрить либо сборкой контейнеров отдельно с удалением ненужного на последней стадии (зачастую без уменьшения образа), либо использовать тома для доступа из nginx к контейнеру с артефактами.

С docker-compose всё по старому, ведь по сути процесс билда не изменился, так что можно смело пользовать!

Как в нём указать разные стейджи одного докерфвайла для разных сервисов? По идее должна быть поддержка --target, но ничего не нашёл по этому поводу, ни доках, ни в ишью/ПР.

Думаю, что сейчас никаких поддержек для новой фичи в docker-compose нет, просто потому что еще даже в самом Docker не зарелизили.

Правда, есть практика использовать по одному Dockerfile на сервис. Поэтому, если есть общая часть какая-то, то может стоит вынести её в родительский image?

Некоторые фичи у них в параллели вводились, не чужие люди, как говорится.


Если создавать родительский образ, то возникает две проблемы:


  • его избыточность (решается в принципе с помощью стейджинга, то есть делается родительский образ не для конечных образов, а для билдера, из которого нужный артефакт копируется в конечный)
  • необходимость либо пересобирать вручную при изменении исходников/зависимостей, либо включать и его в список сервисов с фейковым запуском

Тогда уж проще иметь два почти одинаковых докерфайла, синкая их ручками.


Правда, есть практика использовать по одному Dockerfile на сервис.

Собственно раньше особо возможности не было использовать несколько сервисов на один докер-файл.

Не совсем понятно, зачем делать сборку в контейнере :) Сборка артефакта и создание контейнера с рабочим артефактом — два разных процесса.
Зависимости для сборки держать на хост машине не удобно. Процесс билда при использовании CI становится прозрачнее, на агентах достаточно иметь только Docker. Имхо, правильнее, собирать проект в том окружении, в котором он будет запускаться.

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

Попробовал multi-stage build — понравилось!
Нововведение очень кстати и очень вовремя.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации