Оптимизация образов Docker с помощью многоэтапных сборок и бездистрибутивного подхода
Когда мы перевели наше приложение с Node.js (Express) на Go, нам удалось уменьшить размер образа Docker с 2,8 ГБ до 400 МБ, что изначально нас устраивало. Однако, стремясь к более эффективному использованию ресурсов и более быстрому развертыванию, мы поняли, что все еще есть возможности для дальнейшего уменьшения размера изображения, поэтому мы решили попробовать новый подход.
Возможно, это знакомая территория для инженеров с опытом работы в DevOps, но для новичков я поделюсь улучшениями, которые мы внесли.
Исходный Dockerfile
Наш исходный файл Dockerfile использовал базовый образ golang:1.21.0
и включал шаги по загрузке модулей Go, созданию приложения и его выполнению. Однако этот подход означал, что все файлы, необходимые для среды разработки, были включены в образ, что приводило к увеличению конечного размера образа.
# Base the image on the official Go image
FROM golang:1.21.0
# Create the application directory
WORKDIR /app
# Enable Go modules
ENV GO111MODULE=on
# Copy and download dependencies
COPY go.mod .
COPY go.sum .
RUN go mod download
# Copy the application source
COPY . .
# Build the application
RUN go build -o main .
# Expose the port
EXPOSE 8081
# Execute the application command
CMD ["./main"]
Улучшенный Dockerfile
В улучшенном файле Dockerfile мы внедрили многоступенчатые сборки. Многоступенчатые сборки определяют несколько этапов сборки в одном файле Dockerfile, и в конечный образ включаются только необходимые файлы. На первом этапе создается приложение, а на следующем этапе только созданный исполняемый файл копируется в облегченный образ alpine
. Это значительно уменьшает конечный размер изображения за счет включения только необходимых файлов.
# Base the image on the official Go image
FROM golang:1.21.0-alpine as builder
# Create the application directory
WORKDIR /app
# Enable Go modules
ENV GO111MODULE=on
# Copy and download dependencies
COPY go.mod .
COPY go.sum .
RUN go mod download
# Copy the application source
COPY . .
# Build the application
RUN go build -o main .
# Execution stage
FROM alpine:3.19
WORKDIR /root/
# Copy the built binary
COPY --from=builder /app/main .
# Expose the port
EXPOSE 8081
# Execute the application command
CMD ["./main"]
Сравнение результатов
При использовании оригинального Dockerfile размер образа составлял около 400 МБ. Однако после применения многоэтапной сборки размер образа сократился всего до 10 МБ. Без многоэтапной сборки, но с использованием golang:1.21.0-alpine
в качестве базового образа размер составил около 160 МБ.
Об образах без дистрибутива
Для этого проекта мы выбрали образ Alpine с оболочкой для целей отладки, но мы также рассматривали возможность использования образов Distroless от Google. Образы без дистрибутива известны как очень легкие и безопасные среды, содержащие только минимальное количество необходимых файлов, без удаленных оболочек и ненужных пакетов.
При использовании образов Distroless приложениям, написанным на Go, требуются статически связанные двоичные файлы, которые не зависят от внешних библиотек C. Это связано с тем, что Distroless предоставляет минимальную среду без внешних библиотек и оболочек. Поэтому необходимо установить CGO_ENABLED=0
, чтобы отключить CGO (интерфейс между языками C и Go), создав автономный двоичный файл, включающий все зависимости.
Dockerfile с использованием Distroless будет выглядеть так:
# Build stage
FROM golang:1.21.0 as builder
# Set the application directory
WORKDIR /app
# Enable Go modules
ENV GO111MODULE=on
# Copy and download dependencies
COPY go.mod .
COPY go.sum .
RUN go mod download
# Copy the application source
COPY . .
# Build the application
RUN CGO_ENABLED=0 go build -o main .
# Execution stage
FROM gcr.io/distroless/base-debian10
# Copy the built binary
COPY --from=builder /app/main /
# Execute the application
CMD ["/main"]
Заключение
Подводя итог, наш опыт оптимизации образов Docker для приложения Go преподнес нам ценные уроки об эффективности и безопасности в контейнерных средах. Внедрив многоэтапные сборки, мы смогли значительно уменьшить размер образа, тем самым повысив скорость развертывания и минимизировав использование ресурсов. Такой подход не только способствует более упорядоченному и экономически эффективному процессу развертывания, но также хорошо согласуется с принципами современной практики DevOps.
Более того, изучение таких вариантов, как образы Distroless, открыло нам глаза на важность безопасности в средах Docker. Хотя мы выбрали Alpine из-за нашей срочной потребности в оболочке для отладки, Distroless представляет собой привлекательную альтернативу для производственных сред, где безопасность и минимализм имеют первостепенное значение.
Это мероприятие является свидетельством непрерывного развития в области разработки и внедрения программного обеспечения. Это подчеркивает важность того, чтобы оставаться адаптируемыми и всегда искать лучшие и более эффективные решения. По мере развития технологий развиваются и инструменты и методы, позволяющие нам создавать и развертывать приложения, которые являются не только мощными, но также эффективными и безопасными.
Как для опытных профессионалов DevOps, так и для новичков эти результаты подчеркивают важность оптимизации образов Docker и влияние, которое она может оказать на общий жизненный цикл поставки программного обеспечения. По мере нашего продвижения вперед становится ясно, что внедрение таких оптимизаций будет иметь ключевое значение для повышения операционной эффективности и предоставления программного обеспечения в темпах и масштабах, требуемых современной цифровой средой.