Pull to refresh

Рекомендации по работе с Docker для Golang-разработчиков (Multistage Building)

Reading time2 min
Views13K

Старайтесь всегда использовать многоэтапную сборку, для создания более компактных Docker образов. Давайте, рассмотрим на примере, как многоэтапная сборка позволяет значительно уменьшить размер Docker образа. В качестве примера, мы будем использовать простое веб-приложение на Golang:

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", HelloServer)
	fmt.Printf("Starting server at port 8080\n")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

func HelloServer(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello world")
}

Сначала, соберем Docker образ в один этап:

FROM golang:1.16-alpine
WORKDIR /build
COPY go.mod . # go.sum
RUN go mod download
COPY . .
RUN go build -o /main main.go
ENTRYPOINT ["/main"]

На выходе мы получили Docker образ, размер которого 308 MB. Давайте, теперь мы пересоберем тоже самое приложение, но с использованием многоэтапной сборки:

# Этап, на котором выполняется сборка приложения
FROM golang:1.16-alpine as builder
WORKDIR /build
COPY go.mod . # go.sum
RUN go mod download
COPY . .
RUN go build -o /main main.go
# Финальный этап, копируем собранное приложение
FROM alpine:3
COPY --from=builder main /bin/main
ENTRYPOINT ["/bin/main"]

Мы получили Docker образ, размер которого всего 11.8 MB. Неплохо, мы уменьшили образ в более, чем 25 раз. Но, за счет чего нам удалось добиться такого результата?

В первом случае, мы используем сборку в один этап, а следовательно размер финального Docker образа состоит:

  • из размера golang:1.16-alpine - 302 MB

  • размер исходников - 0.5 MB

  • размер скомпилированного приложения - 6.2 MB

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

  • размера alpine:3 - 5.59 MB

  • размер скомпилированного приложения - 6.2 MB

А можно ли еще уменьшить размер Docker образа?

Можно, но для этого в качестве финального образа, мы должны использовать docker scratch - это пустой образ в докере, размер которого - 0 MB.

FROM golang:1.16-alpine as builder
WORKDIR /build
COPY go.mod . # go.sum
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /main main.go
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder main /bin/main
ENTRYPOINT ["/bin/main"]

В итоге наш контейнер занимает всего лишь 6.34 MB (размер скомпилированного приложения). На что стоит обратить внимание?

Первое, нам пришлось изменить команду сборки, а именно добавить дополнительные флаги:

CGO_ENABLED - мы отключаем CGO, таким образом мы получаем скомпилированное Go-приложение вместе с статически связанными C-библиотеками, поэтому наш бинарник будет работать без каких-либо внешних зависимостей. Более подробно, можно почитать тут.

GOOS - мы указываем Linux в качестве ОС.

Второе, на что стоит обратить внимание - это SSL. Т.к. scratch пуст, то там просто нету рутовых SSL сертификатов, а значит их нужно добавить вручную.

Tags:
Hubs:
Total votes 13: ↑12 and ↓1+15
Comments13

Articles