在开始这项工作之前大家可以先去看一下docker官方给出关于空镜像scratch的说明,采用官方简单的一句话就是:scratch是一个明确的空图像,特别是对于“从头开始”构建图像。
分阶段构建镜像就会用到scratch这个空镜像,这样的好处是可以大大节约服务器资源,比如用普通的镜像(golang:1.18-alpine)来说,它在构建完之后大约占300MB左右,那么我们通过分阶段构建的话可能只需要20MB左右(与你的程序文件以及系统安装的文件内容有关,总之会比原镜像小很多),这里我在想scratch既然是个空镜像干净的环境那么它系统加载的内容也会非常少,在一定程度上是不是也存在提升了程序的性能,这里懂的原理的同学可以交流。
接下来是分阶段构建镜像所用到的Dockerfile和docker-compose.yml以及相关文件的演示:
Dockerfile:
FROM golang:1.18-alpine as builder
# 设置必要的环境变量
ENV GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64 \
GOPROXY=https://goproxy.cn,direct
RUN set -ex \
&& sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
&& apk --update add tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& apk --no-cache add ca-certificates
# 移动到工作目录:/build
WORKDIR /build
# 将代码复制到容器中
COPY . .
RUN go mod download && go mod tidy -v && go build -o execute .
# 运行阶段指定scratch作为基础镜像
FROM scratch
WORKDIR /app
# 拷贝二进制可执行文件
COPY --from=builder /build/execute buildExecute
# 下载时区包
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# 设置当前时区
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# https ssl证书
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# 需要运行的命令
ENTRYPOINT ["./buildExecute"]
docker-compose.yml
version: "3"
services:
my-golang:
build: .
image: golang-1.18-scratch
container_name: my-golang
ports:
- "8888:8888"
restart: always
编写测试程序main.go
package main
import (
"github.com/gin-gonic/gin"
)
func InitRoute() *gin.Engine {
gin.DisableConsoleColor()
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "hello go!",
})
})
return r
}
func main() {
r := InitRoute()
err := r.Run(":8888") //启动监听8888端口
if err != nil {
panic(any(err))
}
}
当前目录结构及文件:

这两个文件都放在项目的根目录之后执行:
docker-compose up -d --build
这时候我们查看执行完毕后的docker容器和镜像信息:


可以看到原镜像golang:1.18-alpine需要328MB空间,而我们通过分阶段构建出来的镜像golang-1.18-scratch仅需要11.4MB,由于我们的测试go程序相对简单,所以占用的空间也是很少的,节省了近30倍的空间,很香吧!
接下来我们检测一下程序是否正常运行:

我们在宿主机curl容器映射出来的8888端口,这个时候收到正确的响应数据,说明我们构建完的镜像已经在正常运行了。
PS:在我们上面的查看执行完毕后的docker容器和镜像信息这一步执行:

为什么出现了这么多none的镜像呢?经过查阅相关资料得知这叫做docker的“悬空镜像”,至于为什么大家可以查询一下相关的资料或者阅读这篇文章《docker的虚悬镜像是什么?》。