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

от автора

Старайтесь всегда использовать многоэтапную сборку, для создания более компактных 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 RUN mkdir /build WORKDIR /build COPY go.mod . #COPY 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 RUN mkdir /build WORKDIR /build COPY go.mod . #COPY 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 RUN mkdir /build WORKDIR /build COPY go.mod . #COPY 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 сертификатов, а значит их нужно добавить вручную.


ссылка на оригинал статьи https://habr.com/ru/post/647255/