Pull to refresh

Comments 27

Немного оффтоп, но:

Мulti stage сборка позволяет использовать несколько инструкций FROM, и как итог используется сразу 2 базовых образа

Насколько я помню, данный подход не позволяет полноценно совместить два образа, только использовать один как первый этап другого. А если действительно нужен образ, в котором, например, одновременно и python, и node.js? Есть какой-то способ добиться этого без лишних усилий?

Использовать готовый образ может быть опасно. Почему вы должны доверять автору?

Я обычно беру официальный образ Python и сам установливаю в него Node.js пакетным менеждером ОС. Или наоборот, в официальный образ с Node.js устанавливаю Python.

Тогда надо брать базовый и собирать свой с нужными зависимостями

Думаю стоит пересмотреть стратегию запуска приложения.

Тут или нода используется для подготовки (тогда подойдет Мulti stage) или это отдельный сервис, а если отдельный сервис, то логично поместить его в отдельный контейнер).

P/S Бывает удобней запустить все сервисы в одном контейнере, тогда можно взять базовый образ наиболее подходящий для обоих языков и установить нужное ПО в процессе сборки контейнера. Но тут уже придется возиться с контролем запуска или использовать спец ПО для этого.

Случай был следующий: есть старый скрипт на перле, который с помощью учебника по некрофилии и какой-то матери удалось работать на своих данных (через файл). К нему нужно было сделать UI поверх API, для чего вполне подходил питон. Вот и понадобился контейнер, где было бы и то, и другое. Такая поделка была актуальна пару недель, потом благополучно отправлена на свалку.

Надеюсь, учебник был всё же по некромантии.

Здесь

FROM golang:1.16-buster AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY *.go ./
RUN go build -o /hello_go_http
...

не будет лучше сделать так?

FROM golang:1.16-buster AS builder
WORKDIR /app
COPY . .
RUN go build -o /hello_go_http

при build всё подтянется само.
Или я просто не понимаю тонкостей (

Если выполняется команда RUN, зависящая от какого-то набора файлов, то лучше предварительно скопировать только этот набор файлов. Если при последующих сборках эти файлы не будут меняться, команда RUN не будет выполняться, возьмётся готовый слой. Копируя всё сразу, вы рискуете скопировать какие-то файлы, которые были изменены, но на ход сборки невлияют, однако команда RUN всё равно будет выполнена — лишнее время и сброс всех слоёв дальше.

Тогда при каждом изменении кода пакеты каждый раз будут подтягиваться заново, а в первом примере они будут кешироваться

  1. openjdk deprecated, из списка альтернатив личто я выбрал eclipse-temurin

  2. для работы Java приложению обычно достаточно JRE, образ ещё меньше

  3. dockerignore никак не влияет на размер итогового образа, только на время сборки, т.к. демону по умолчнию отправляется всё содержимое директории. Косвенно может влиять, если ненужные пути попадают в инструкции ADD/COPY

А сам список был выбран с той же страницы? Просто в нём только образы с отметкой official, что означает ровно те же грабли, когда размещением образов занимается кто-то из Docker Hub-а, а не непосредственно разработчики.

про какие грабли речь?
исходники сборок - на гитхабе, участие разработчиков продукта приветствуется

Хорошая статья, спасибо!

На мой взгляд, стоило ещё упомянуть про DOCKER_BUILDKIT=1, который не везде включён по умолчанию. Он позволяет выполнять некоторые шаги сборки параллельно.

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

Ну и много слоёв - это не всегда плохо. Если слои не генерируют никаких файлов, то их вклад в размер образа - сущие копейки. Таких слоёв можно делать много, если это поможет эффективнее использовать кэш. А удалять временные файлы нужно именно в том слое, где они созданы, иначе они всё равно останутся в финальном образе.

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

Если про новые фичи говорить, то Buildkit — бомба. Он не только значительно быстрее зачастую, но ещё и имеет более информативный лог. Ну и в плане новых фич можно ещё отметить docker-bake — некий аналог docker-compose для сборки.

По Java небольшой комментарий. Образ openjdk уже длительное время является deplrecated. На странице даже приведён список "official" образов на замену. Помимо размера стоит обращать пристальное внимание на безопасность. Вот например статья со сравнением размеров и результатов работы сканеров
https://thecattlecrew.net/2022/11/07/preparing-for-spring-boot-3-choose-the-right-java-17-base-image/
Помимо Alpine теперь есть и Alpaquita (https://hub.docker.com/r/bellsoft/alpaquita-linux-base), в ней кстати примечательно отсутствие уязвимостей при примерно том же размере. Ну и соответствующие минималистичные базовые образы вроде bellsoft/liberica-runtime-container:jdk-17-musl.

в ней кстати примечательно отсутствие уязвимостей

просто интересно, как это достигается?

В Alpine например по умолчанию libssl / libcrypto более низких мажорных версий. Кстати алпайн алпайну рознь. В базовых образах Либерики на alpine эти библиотеки были обновлены на более актуальную минорную версию. Ничего подобного в других образах не наблюдается. Из интересного, довольно неплохо в плане закрытия уязвимостей слоя ОС себя показывает Ubuntu, но обновления JDK они выпускают не слишком оперативно, и размер образа далеко не самый маленький.

Перечислять пакеты необходимо на нескольких строках, разделяя список символами .

В смысле "необходимо"?

Хорошая статья, но в идеале хотелось бы несколько конкретных и интересных примеров, а не Hello World. На той же Java можно собрать не просто alpine образ, а ещё и поиграться с jlink. Да возможно это уже не относится к Docker, но раз уж мы говорим про уменьшение образа, было бы неплохо упомянуть.

FROM gcr.io/distroless/base-debian10
WORKDIR / 
COPY --from=builder /hello_go_http /hello_go_http

Можно пояснить что это за магия и почему размер образа сокращается с 869 до 25.4 Мб?

Уже скомпилированную программу из образа, где весь инструментарий, копируют в образ, где нет ничего лишнего.

В статье не написано, что Мulti stage еще решает проблему ключей в образе. В первом шаге можно добавить ключи, пароли и т.п., собрать приложение, а потом во второй шаг передать чистый результат.

А может ли кто поделиться опытом, как multi-stage build использовать для сборки NodeJS приложения?

Есть package.json, в котором есть dependencies и devDependencies. При npm install в node_modules устанавливаются и те, и другие. А в итоговой сборке devDependencies не нужны, и копирования целиком всего node_modules в итоговый образ хотелось бы избежать.

npm install --omit=dev (пропустить dev)

Спасибо, но как же быть с тестами?
Имею в виду, тестовые зависимости - они в devDependencies, нужны для прогона тестов, но не нужны в итоговой сборке.

Делать прогон тестов и сборку в разных фазах (stages)?

Всех нюансов не знаю, но можно попробовать следующее:

сделать например 3 стадии сборки. В первой ставятся все зависимости и прогоняются юнит-тесты, на второй ставятся зависимости только для прода, которые копируются уже в последний шаг, потом это все отправляется в реестр контейнеров и потом следующим шагом уже CI/CD прогоняются автоматизированные интеграционные тесты (или этот образ разворачивается на тесте для отдела QA) и только потом этот же образ идет дальше в прод.

Sign up to leave a comment.