使用Docker进行golang工程应用编译
一、准备
考虑如下的golang工程,包含一个Client和一个Server端,其中:
- 客户端是用于编写、发送和 接收消息。
- 服务器接收来自客户端的消息,翻译它们, 并将它们发送回客户端
源码
├── Dockerfile
├── cmd
│ ├── client
│ │ ├── main.go
│ │ ├── request.go
│ │ └── ui.go
│ └── server
│ ├── main.go
│ └── translate.go
├── go.mod
└── go.sum
Dockerfile
# syntax=docker/dockerfile:1
FROM golang:1.20-alpine # 使用golang官方镜像的版本
WORKDIR /src # 创建并指定工作目录
COPY . . # 复制文件到工作目录
RUN go mod download # 下载依赖
RUN go build -o /bin/client ./cmd/client # 编译client
RUN go build -o /bin/server ./cmd/server # 编译server
ENTRYPOINT [ "/bin/server" ] # 启动server
其中:
# syntax=docker/dockerfile:1
此注释是 Dockerfile 解析器指令。 它指定要使用的 Dockerfile 语法版本。此文件使用 最佳实践的语法:它确保您拥有访问最新的 Docker 构建功能。可以参考此文了解这个解析器的功能:Custom Dockerfile syntax | Docker Documentation
编译
docker build -t test:v1 .
二、Layer
dockerfile的每一条指令都会被记录为一个镜像层。通常情况下,docker引擎使用文件系统Unionfs
来存储这些image layer。
From: http://docs.docker.com
运行build构建时,构建器会尝试重用早期构建中的层。 如果映像的某个layer未更改,则构建器将从构建缓存中选取该层。 如果自上次构建以来某个层发生了更改,则必须重新构建该层以及随后的所有层。
在企业的内部流水线中,可以使用docker镜像层缓存的特性,来加速流水线的构建速度。
三、多级构建(Multi-Stage)
在上述案例中,我们把整个golang环境、源码、依赖都打包到了 test:v1 这个镜像。当build结束时,我们发现这个镜像的大小达到了415MB。
臃肿的镜像会为构建效率、制品分发和环境资源带来不少的麻烦,并且存在源码泄露的风险。在实际的使用中,通常使用Docker的多级构建(Multi-Stage)特性,来解决上述问题。
添加Stage
使用多级构建(Multi-Stage),可以选择为构建和运行时环境使用不同的基本映像,将打包结果从Build Stage复制到Runtime Stage。
Dockerfile可以改为:
# Build Stage
# syntax=docker/dockerfile:1
FROM golang:1.20-alpine
WORKDIR /src
COPY . .
RUN go env -w GOPROXY=https://goproxy.cn,direct
RUN go mod download
RUN go build -o /bin/client ./cmd/client
RUN go build -o /bin/server ./cmd/server
# 运行时 Stage
FROM scratch
COPY --from=0 /bin/client /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
通过上述dockerfile构建,可以看到 test:v2 镜像大小显著减小至 15.8MB
并行化构建(Parallelism)
通过多级构建,已经达到了减小镜像大小的目的。但是我们分析Dockerfile,发现在Build Stage中,client和server是串行执行的,这使得镜像构建时,本来就漫长的构建环节雪上加霜(不夸张哈,虽然这个demo挺快的,真实生产环境可不一定,CICD环境流程每提升1s都能节约不少资源)。
所以,我们可以进一步去优化编译的过程,使其并行化执行。
结合第二章的多级构建,Dockerfile可以改为:
# syntax=docker/dockerfile:1
FROM golang:1.20-alpine AS base
WORKDIR /src
COPY . .
RUN go env -w GOPROXY=https://goproxy.cn,direct
RUN go mod download -x
FROM base AS build-client
RUN go build -o /bin/client ./cmd/client
FROM base AS build-server
RUN go build -o /bin/server ./cmd/server
FROM scratch AS client
COPY --from=build-client /bin/client /bin/
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
可以看到,编译client和server步骤合并为一个,且同时运行
分目标(Target)构建
尽管已经通过多级构建和并行化来提升效率,同时降低镜像体积。但是,镜像还存在一个问题,client和server在同一个镜像内。在生产情况下,client和server需要分别部署,所以上面的流程明显是不合理的。需要将target拆分开来。
Dockerfile可以改为:
# syntax=docker/dockerfile:1
FROM golang:1.20-alpine AS base
WORKDIR /src
COPY . .
RUN go env -w GOPROXY=https://goproxy.cn,direct
RUN go mod download -x
FROM base AS build-client
RUN go build -o /bin/client ./cmd/client
FROM base AS build-server
RUN go build -o /bin/server ./cmd/server
FROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]
FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
构建命令:
# 构建client
docker build --tag=client:v1 --target=client .
# 构建server
docker build --tag=server:v1 --target=server .
拆分后,镜像大小进一步降低。
四、本文总结
本文通过介绍Layer,一步步探索多级构建(Multi-Stage)的各种特性,实现对Docker的构建效率、制品体积的有效控制。