Comments 27
Немного оффтоп, но:
Мulti stage сборка позволяет использовать несколько инструкций FROM, и как итог используется сразу 2 базовых образа
Насколько я помню, данный подход не позволяет полноценно совместить два образа, только использовать один как первый этап другого. А если действительно нужен образ, в котором, например, одновременно и python, и node.js? Есть какой-то способ добиться этого без лишних усилий?
Как вариант, можно использовать готовый образ, где уже предустановлен и python, и nodejs https://hub.docker.com/r/nikolaik/python-nodejs
Использовать готовый образ может быть опасно. Почему вы должны доверять автору?
Я обычно беру официальный образ 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 всё подтянется само.
Или я просто не понимаю тонкостей (
Тогда при каждом изменении кода пакеты каждый раз будут подтягиваться заново, а в первом примере они будут кешироваться
openjdk deprecated, из списка альтернатив личто я выбрал eclipse-temurin
для работы Java приложению обычно достаточно JRE, образ ещё меньше
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) и только потом этот же образ идет дальше в прод.
Оптимизация Dockerfile для уменьшения размера и быстрой сборки образов