Создайте минимальный образ Docker для приложения Golang.

Go

Я обычно использую docker для запуска своих программ golang, здесь я делюсь своим опытом создания образов docker. Я создаю образы докеров, чтобы оптимизировать не только размер сборки, но и скорость сборки.

Образец заявления

Сначала опубликуйте пример кода, предположим, что мы хотим создать http-сервис.

package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

func main() {
	fmt.Println("Server Ready")
	router := gin.Default()
	router.GET("/", func(c *gin.Context) {
		c.String(200, "hello world, this time is: "+time.Now().Format(time.RFC1123Z))
	})
	router.GET("/github", func(c *gin.Context) {
		_, err := http.Get("https://api.github.com/")
		if err != nil {
			c.String(500, err.Error())
			return
		}
		c.String(200, "access github api ok")
	})

	if err := router.Run(":9900"); err != nil {
		panic(err)
	}
}


инструкция:

  • Gin выбран в качестве примера здесь, чтобы продемонстрировать, что нам нужно оптимизировать скорость сборки при условии, что у нас есть сторонние пакеты.
  • Основная функция основной функции печатает строку, чтобы показать яму, возникшую после запуска
  • Печать маршрутизации со временем, чтобы продемонстрировать яму около часового пояса, встречаемого позже
  • Маршрут github, чтобы попытаться получить доступhttps://api.github.com, чтобы продемонстрировать обнаруженную позже яму сертификата

Здесь мы можем сначала попробовать объем собранного пакета

$ go build -o server
$ ls -alh | grep server
-rwxrwxrwx 1 eyas eyas  14.6M May 29 10:26 server

14,6 МБ, это привет, мир http-сервиса, конечно, это потому, что используется джин, поэтому он немного великоват, если вы используете стандартные пакетыnet/httpНаписано hello world, объем около 7 мб

Эволюция Dockerfile

Версия 1, предварительная оптимизация

Взгляните на первую версию

FROM golang:1.14-alpine as builder
WORKDIR /usr/src/app
ENV GOPROXY=https://goproxy.cn
COPY ./go.mod ./
COPY ./go.sum ./
RUN go mod download
COPY . .
RUN go build -ldflags "-s -w" -o server

FROM scratch as runner
COPY --from=builder /usr/src/app/server /opt/app/
CMD ["/opt/app/server"]

инструкция:

  • выберитеgolang:1.14-alpineВ качестве среды компиляции, потому что это самая маленькая среда компиляции golang.
  • GOPROXY настроен на повышение скорости сборки
  • скопируй сначалаgo.modиgo.sum,Потомgo mod download, чтобы предотвратить повторную загрузку пакета зависимостей для каждой сборки и использовать кеш сборки докера для повышения скорости сборки.
  • добавляйте когда идете строить-ldflags "-s -w"Удалите отладочную информацию пакета сборки и уменьшите размер программы после сборки go, который, вероятно, можно уменьшить1/4Бар
  • Используется многоступенчатая сборка, т.е.FROM XXX as xxx, при сборке пакета используйте образ со средой компиляции для сборки, и на самом деле вообще не нужна среда компиляции go при запуске, поэтому используйте пустой образ докера на этапе выполненияscratchБежать.这部是减小镜像体积最有效的方法了。

Хорошо, давайте начнем создавать изображение ниже

$ docker build -t server .
...
Successfully built 8d3b91210721
Successfully tagged server:latest

На данный момент сборка прошла успешно, посмотрите на размер образа

$ docker images
server          latest         8d3b91210721      1 minutes ago        11MB

11MB, ладно, запускай сейчас

$ docker run -p 9900:9900 server
standard_init_linux.go:211: exec user process caused "no such file or directory"

Было обнаружено, что при запуске сообщалось об ошибке, а первая строка операторов печати в основной функции не появлялась, поэтому вся программа вообще не запускалась. Причиной ошибки является отсутствие файла зависимостей библиотеки. На самом деле это встроенная программа go, которая также зависит от базового файла библиотеки so.Если вы мне не верите, вы можете проверить ее зависимости после компиляции физической машины.

$ go build -o server
$ ldd server
        linux-vdso.so.1 (0x00007ffcfb775000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f9a8dc47000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9a8d856000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f9a8de66000)

Разве это не немного отличается от нашего познания?Говорят, что нет никаких зависимостей, но есть еще несколько зависимых библиотечных файлов.Хотя эти зависимости являются самым низким уровнем, общая операционная система будет иметь их, но кто сказал нам, что они избраныscratch, в этом образе действительно ничего нет, кроме ядра linux.

Это потому, что go build по умолчанию включает CGO.Если вы мне не верите, вы можете попробовать эту командуgo env CGO_ENABLED,При включении CGO,независимо от того,использует код CGO или нет,будут библиотечно-зависимые файлы.Решение тоже очень простое.Можно вручную указать закрывать CGO,и размер пакета не увеличится,а уменьшится .

$ CGO_ENABLED=0 go build -o server
$ ldd server
        not a dynamic executable

Версия 2, решить ошибку времени выполнения

FROM golang:1.14-alpine as builder
WORKDIR /usr/src/app
ENV GOPROXY=https://goproxy.cn
COPY ./go.mod ./
COPY ./go.sum ./
RUN go mod download
COPY . .
-RUN go build -ldflags "-s -w" -o server
+RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server

FROM scratch as runner
COPY --from=builder /usr/src/app/server /opt/app/
CMD ["/opt/app/server"]

Измененный пункт: добавлено перед сборкойCGO_ENABLED=0

$ docker build -t server .
...
Successfully built a81385160e25
Successfully tagged server:latest
$ docker run -p 9900:9900 server
[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
[GIN-debug] GET    /github                   --> main.main.func2 (3 handlers)
[GIN-debug] Listening and serving HTTP on :9900

Запустилось нормально, давайте зайдем и попробуем.Перед посещением проверьте текущее время.

$ date
Fri May 29 13:11:28 CST 2020

$ curl http://localhost:9900       
hello world, this time is: Fri, 29 May 2020 05:18:28 +0000

$ curl http://localhost:9900/github
Get "https://api.github.com/": x509: certificate signed by unknown authority

нашел проблему

  • Текущее системное время13:11:28, но по времени, показанному05:11:53, на самом деле часовой пояс в докер-контейнере указан неправильно, по умолчанию 0 часовой пояс, но наша страна восточная 8-я зона
  • попытаться получить доступhttps://api.github.com/Это https-сайт, и сообщается об ошибке сертификата

Решать проблему

  • Поместите корневой сертификат в контейнер
  • Установите часовой пояс контейнера

Версия 3, решить проблемы с часовым поясом и сертификатом среды выполнения.

FROM golang:1.14-alpine as builder
WORKDIR /usr/src/app
ENV GOPROXY=https://goproxy.cn
+RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
+  apk add --no-cache ca-certificates tzdata
COPY ./go.mod ./
COPY ./go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server

FROM scratch as runner
+COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/src/app/server /opt/app/
CMD ["/opt/app/server"]

На этапе билдера устанавливаются две библиотеки ca-сертификатов tzdata, а на этапе запуска копируется конфигурация часового пояса и корневой сертификат

$ docker build -t server .
...
Successfully built e0825838043d
Successfully tagged server:latest
$ docker run -p 9900:9900 server
[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
[GIN-debug] GET    /github                   --> main.main.func2 (3 handlers)
[GIN-debug] Listening and serving HTTP on :9900

посетите и попробуйте

$ date
Fri May 29 13:27:16 CST 2020

$ curl http://localhost:9900       
hello world, this time is: Fri, 29 May 2020 13:27:16 +0800

$ curl http://localhost:9900/github
access github api ok

Все в норме, посмотрите на текущий размер изображения

$ docker images
server          latest         e0825838043d      9 minutes ago        11.3MB

Он всего 11,3Мб, что уже очень мало, но можно еще меньше, то есть еще раз сжать собранный пакет

Версия 4, дальнейшее уменьшение размера

FROM golang:1.14-alpine as builder
WORKDIR /usr/src/app
ENV GOPROXY=https://goproxy.cn
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
-  apk add --no-cache ca-certificates tzdata
+  apk add --no-cache upx ca-certificates tzdata
COPY ./go.mod ./
COPY ./go.sum ./
RUN go mod download
COPY . .
-RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server
+RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server &&\
+ upx --best server -o _upx_server && \
+ mv -f _upx_server server

FROM scratch as runner
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/src/app/server /opt/app/
CMD ["/opt/app/server"]

На этапе сборки устанавливается upx, и после завершения сборки используйте upx для его сжатия, выполните сборку, вы обнаружите, что время сборки увеличивается, это связано с тем, что параметры, которые я установил для upx,--best, то есть максимальный уровень сжатия, чтобы он был как можно меньше после сжатия.Если он слишком медленный, можно уменьшить уровень сжатия от-1прибыть-9, чем выше число, тем выше уровень сжатия и медленнее. я использую--bestВзгляните на объем образа после завершения сборки.

$ docker build -t server .
...
Successfully built 80c3f3cde1f7
Successfully tagged server:latest
$ docker images
server          latest         80c3f3cde1f7      1 minutes ago        4.26MB

В этот раз он маленький, всего 4,26Мб, а тут попробуй два интерфейса, все нормально. На этом оптимизация заканчивается.

Окончательный файл Docker

FROM golang:1.14-alpine as builder
WORKDIR /usr/src/app
ENV GOPROXY=https://goproxy.cn
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
  apk add --no-cache upx ca-certificates tzdata
COPY ./go.mod ./
COPY ./go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o server &&\
  upx --best server -o _upx_server && \
  mv -f _upx_server server

FROM scratch as runner
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/src/app/server /opt/app/
CMD ["/opt/app/server"]

Суммировать

Чтобы уменьшить зеркальный объем, первая многоступенчатая конструкция которого очень важно, так что вы можете отделить среду сборки и среды выполнения.

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

Рекомендуемый выборalpine, размер изображения альпийского5.61MBЭтот размер на самом деле является размером распакованного образа.На самом деле при скачивании образа вам нужно всего лишь скачать2.68 MB. Кроме того, все тома образа, о которых я упоминал выше, относятся к объему распакованного образа, который отличается от фактического объема загрузки и загрузки.Докер сам сожмет образ один раз, а затем передаст образ.

Еще одно маленькое зеркалоbusybox, его объем1.22MB,скачать705.6 KB, доступно большинство команд linux, но операционная среда все еще очень примитивна, вы можете попробовать, если вам интересно

Будь то alpine или busybox, у них будут вышеуказанные проблемы с часовым поясом и сертификатом, которые также можно решить в соответствии с описанным выше методом.Переключение на alpine или busybox также очень просто, просто нужно изменить базовый образ бегуна.

-FROM scratch as runner
+FROM alpine as runner

или

-FROM scratch as runner
+FROM busybox as runne