目录
单阶段构建
多阶段构建镜像
使用UPX构建更小的Docker镜像
们将使用以下镜像构建新的镜像:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
golang 1.10.3 d0e7a411e3da 6 weeks ago 794MB
alpine 3.8 11cd0b38bc3c 8 weeks ago 4.41MB
Dockerfile
FROM golang:1.10.3
WORKDIR /go/src/test
RUN go get github.com/gin-gonic/gin
COPY src src
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main src/main.go
CMD ["./main"]
Build
$ docker build -t zev/test:1.0.0 .
Sending build context to Docker daemon 17.41kB
Step 1/6 : FROM golang:1.10.3
---> d0e7a411e3da
Step 2/6 : WORKDIR /go/src/test
---> Running in 94d1ede51e17
Removing intermediate container 94d1ede51e17
---> 2b643ce8b3cf
Step 3/6 : RUN go get github.com/gin-gonic/gin
---> Running in de5e9adb7c10
Removing intermediate container de5e9adb7c10
---> ff970f45de1e
Step 4/6 : COPY src src
---> 6b79fef06e45
Step 5/6 : RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main src/main.go
---> Running in 6d4ef8c0b580
Removing intermediate container 6d4ef8c0b580
---> 59678a3ab4d8
Step 6/6 : CMD ["./main"]
---> Running in a5cea54f2ccb
Removing intermediate container a5cea54f2ccb
---> a253cfcddd6a
Successfully built a253cfcddd6a
Successfully tagged zev/test:1.0.0
RUN
$ docker run -it -p 8080:8080 zev/test:1.0.0
[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :8080
Images
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
zev/test 1.0.0 a253cfcddd6a 4 minutes ago 857MB
image的size为857MB,内部包含了整个golang环境,这么大的文件在传输中绝对是个灾难,接下来我们用多阶段构建一个相对比较小的image。
Docker镜像的每一层只记录文件变更,在容器启动时,Docker会将镜像的各个层进行计算,最后生成一个文件系统,这个被称为 联合挂载。
Docker的各个层是有相关性的,在联合挂载的过程中,系统需要知道在什么样的基础上再增加新的文件。那么这就要求一个Docker镜像只能有一个起始层,只能有一个根。所以,老版本Docker的Dockerfile中,就只允许一个 FROM指令。因为多个 FROM 指令会造成多根,则是无法实现的。
Docker 17.05 版本以后允许 Dockerfile支持多个 FROM 指令了。多个 FROM 指令并不是为了生成多根的层关系,最后生成的镜像,仍以最后一条 FROM 为准,之前的 FROM 会被抛弃;每一条 FROM 指令都是一个构建阶段,多条 FROM 就是多阶段构建,虽然最后生成的镜像只能是最后一个阶段的结果,但是,能够将前置阶段中的文件拷贝到后边的阶段中,这就是多阶段构建的最大意义。
多阶段构建最大的使用场景是将编译环境和运行环境分离:
DockerFile
FROM golang:1.10.3 as builder
WORKDIR /go/src/test
RUN go get github.com/gin-gonic/gin
COPY src src
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main src/main.go
FROM alpine:3.8
WORKDIR /root
COPY --from=builder /go/src/test/main .
CMD ["./main"]
Build
$ docker build -t zev/test:1.0.1 .
Sending build context to Docker daemon 17.41kB
Step 1/9 : FROM golang:1.10.3 as builder
---> d0e7a411e3da
Step 2/9 : WORKDIR /go/src/test
---> Using cache
---> 2b643ce8b3cf
Step 3/9 : RUN go get github.com/gin-gonic/gin
---> Using cache
---> ff970f45de1e
Step 4/9 : COPY src src
---> Using cache
---> 6b79fef06e45
Step 5/9 : RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main src/main.go
---> Using cache
---> 59678a3ab4d8
Step 6/9 : FROM alpine:3.8
---> 11cd0b38bc3c
Step 7/9 : WORKDIR /root
---> Running in 1640c71479d6
Removing intermediate container 1640c71479d6
---> ec68dc839562
Step 8/9 : COPY --from=builder /go/src/test/main .
---> 5bb444c91aff
Step 9/9 : CMD ["./main"]
---> Running in a80305feba6e
Removing intermediate container a80305feba6e
---> 5923597f59c2
Successfully built 5923597f59c2
Successfully tagged zev/test:1.0.1
RUN
$ docker run -it -p 8080:8080 zev/test:1.0.1
[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :8080
Images
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
zev/test 1.0.1 5923597f59c2 2 minutes ago 19.8MB
多阶段构建让images缩小了40倍,19.8M的size无论在测试环境还是生产环境都能很好的工作了,但是这样就结束了吗?
当然不是了,我们的目的是让image变得更小,下面看我们的操作。
Dockerfile
FROM golang:1.10.3 as builder
RUN apt-get update && apt-get install -y xz-utils && rm -rf /var/lib/apt/lists/*
ADD https://github.com/upx/upx/releases/download/v3.95/upx-3.95-amd64_linux.tar.xz /usr/local
RUN xz -d -c /usr/local/upx-3.95-amd64_linux.tar.xz | tar -xOf - upx-3.95-amd64_linux/upx > /bin/upx && chmod a+x /bin/upx
WORKDIR /go/src/test
RUN go get github.com/gin-gonic/gin
COPY src src
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main src/main.go
RUN strip --strip-unneeded main
RUN upx main
FROM alpine:3.8
WORKDIR /root
COPY --from=builder /go/src/test/main .
CMD ["./main"]
Build
$ docker build -t zev/test:1.0.2 .
Sending build context to Docker daemon 17.92kB
Step 1/14 : FROM golang:1.10.3 as builder
---> d0e7a411e3da
Step 2/14 : RUN apt-get update && apt-get install -y xz-utils && rm -rf /var/lib/apt/lists/*
---> Running in 65772cb8fdab
Ign:1 http://deb.debian.org/debian stretch InRelease
Get:2 http://security.debian.org/debian-security stretch/updates InRelease [94.3 kB]
Get:3 http://deb.debian.org/debian stretch-updates InRelease [91.0 kB]
Get:4 http://security.debian.org/debian-security stretch/updates/main amd64 Packages [392 kB]
.....此处省略
Step 10/14 : RUN upx main
---> Running in d802406ee44a
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2018
UPX 3.95 Markus Oberhumer, Laszlo Molnar & John Reiser Aug 26th 2018
File size Ratio Format Name
-------------------- ------ ----------- -----------
9848136 -> 2945384 29.91% linux/amd64 main
Packed 1 file.
Removing intermediate container d802406ee44a
---> 0c29f4b2272d
Step 11/14 : FROM alpine:3.8
---> 11cd0b38bc3c
Step 12/14 : WORKDIR /root
---> Using cache
---> ec68dc839562
Step 13/14 : COPY --from=builder /go/src/test/main .
---> a2c265cc9aff
Step 14/14 : CMD ["./main"]
---> Running in 7e350a4620ee
Removing intermediate container 7e350a4620ee
---> a4d7753c8112
Successfully built a4d7753c8112
Successfully tagged zev/test:1.0.2
Run
$ docker run -it -p 8080:8080 zev/test:1.0.2
[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :8080
Images
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
zev/test 1.0.2 a4d7753c8112 4 minutes ago 7.36MB
到现在我们已经看到image的size已经缩小到7.36MB,这已经非常的小了,换算过来我们的程序只有2.95M。
alpine的size已经固定了,能让image变得更小的入手点只能是可执行文件,利用UPX的加壳技术可以压缩main可执行程序,可以把main体积缩小50%-70%。