尝试使用Docker 17.05的多阶段构建,将镜像大小缩小到原来的30分之一
引子
Kubernetes最佳实践:如何以及为何构建小型容器镜像,我实际尝试了一些减小Docker镜像大小的技巧。
我认为在 Kubernetes 中,小型的镜像大小对性能有所帮助,但在其他用例中,我也认为小型镜像的好处很多。
因为我会很快忘记,所以基本上是为了自己的备忘录而保存。
在这之前,多阶段构建是什么意思?
Docker文档中详细描述了相关内容。
从Docker 17.05版本开始引入的功能是能够在一个Dockerfile中使用多个基础镜像,并最终创建一个镜像。这样可以保持维护性的同时,进行Dockerfile的优化等操作。
简单地说,可以通过不使用 Builder-Pattern 来减小 Docker 镜像的尺寸。
本次为了方便起见,我们将创建一个名为 single-stage 的 Dockerfile,并从单一的基础映像构建。
这次的构成
Docker 17.09.0-ce(Mac版Docker)
.
├── multi-stage
│ ├── Dockerfile
│ └── main.go
└── single-stage
├── Dockerfile
└── main.go
我将本次构建的配置放在了这个链接中:https://github.com/ight-reco/go-docker-multistage。
试试看实际操作一下
单阶段
我会试着从单阶段的Docker镜像开始制作。
# golang app build & 実行用
FROM golang:alpine
WORKDIR /app
ADD . /app
RUN cd /app && go build -o goapp
EXPOSE 8080
ENTRYPOINT ./goapp
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, World")
});
fmt.Println("Running http://localhost:8080");
http.ListenAndServe(":8080", nil);
}
$ docker build -t single-stage ./single-stage
...
Successfully tagged single-stage:latest
$ docker images --format "table {{.Repository}}\t{{.Size}}"
REPOSITORY SIZE
single-stage 382MB
...
我成功构建了。即使使用了golang:alpine,大小也是大约382MB。 我会预先执行一下,以确保。
$ docker run -it -p 8080:8080 builder-pattern
Running http://localhost:8080
$ curl localhost:8080
Hello, World
好像一切顺利进行!
多阶段的 (duō jiē de)
我们将创建一个多阶段的镜像,同样地需要出现两次 FROM,由于 main.go 和 single 相同,所以我们将省略它。
# golang app build 用
FROM golang:alpine AS build-env
WORKDIR /app
ADD . /app
RUN cd /app && go build -o goapp
# golang app 実行用 (ここのベース image は最小限に)
FROM alpine
WORKDIR /app
# build した goapp を実行用に COPY
COPY --from=build-env /app/goapp /app
EXPOSE 8080
ENTRYPOINT ./goapp
$ docker build -t multi-stage ./multi-stage
...
Successfully tagged multi-stage:latest
$ docker images --format "table {{.Repository}}\t{{.Size}}"
REPOSITORY SIZE
multi-stage 10.8MB
single-stage 382MB
...
10.8MB变得非常小了!
为什么会那样呢?
在Golang的情况下,进行build后会生成一个单一的可执行文件,因此基本上只要有该编译好的二进制文件,就可以运行。(在一些情况下,比如HTTPS通信等,这个说法不一定适用。)
因此,可以通过在构建时不包含仅在构建时需要的文件来降低镜像的大小。
总结
我认为我们可以采用多段构建的方式来实现,而不需要使用Multi-stage构建功能,这种方式被称为Builder-Pattern,它将构建和执行分开。然而,由于增加了复杂性,我认为还是采用Multi-stage构建比较好!
由于这是一个非常极端的例子,所以我认为实际上图像尺寸可能会更大。