在部署 Go 应用时,我们通常会使用 Docker 镜像来部署,那么如何构建一个 Go 应用的 Docker 镜像呢?镜像构建过程中有没有什么最佳实践呢?
这正是本文想要讲解的内容。总的来说,本文会包含 Dockerfile 编写、镜像构建、多阶段构建、交叉编译以及使用 Makefile 简化构建流程等知识点。
创建一个简单的 Go 应用
为了说明整个镜像构建流程,让我们先从一个简单的 Go REST 应用开始。
该应用主要有以下功能:
/Hello, Docker! <3/ping{"Status":"OK"}
应用源码地址在 https://github.com/nodejh/docker-go-server-ping ,你可以直接下载使用,也可以按照下面的步骤从零开始编写代码。
方式一:下载源码
安装依赖模块:
方式二:从零编写 Go 应用
docker-go-server-ping
安装 Echo 模块:
main.go
go mod tidygo.mod
测试 Go 应用
go run
http://localhost:8080/curl
可以看到应用正常返回了,正如开头描述的那样。
确定服务器正在运行并且可以访问后,我们就可以继续针对应用构建 Docker 镜像了。
为 Go 应用创建一个 Dockerfile
docker build
从零创建 Dockerfile
让我们先来看一下创建 Dockerfile 的详细过程。
在项目根目录中创建一个名为 Dockerfile 的文件并在编辑器中打开。
# syntaxdocker/dockerfile:11
接下来在 Dockerfile 中再添加一行,告诉 Docker 我们的应用使用什么基础镜像:
1.16-alpine
为了更好地在镜像中管理我们的应用程序,让我们在镜像中创建一个工作目录,之后源码或编译产物都存放在该目录中:
接下来我们就需要在镜像中编译 Go 应用,这样做是为了保证编译和最终运行的环境一致。
go.modgo.sum
COPY./WORKDIR/app
RUNgo mod download
此时我们已经有了一个基于 Go 1.16 的镜像,并安装了 Go 应用所需的依赖。下一步要做的就是把源码复制到镜像中:
.go
RUN
docker-go-server-ping
现在,剩下要做的就是告诉 Docker 当我们用该镜像来启动容器时要执行什么命令,这时可以使用 CMD 命令:
完整的 Dockerfile
构建镜像
docker build
构建镜像
--tag<镜像名称>:<标签>
docker image lsdocker images
为镜像设置标签
docker image tag
docker image ls
docker image rmdocker rmi
v1.0.0latest
单元测试
既然本文主要将 Go 的 Docker 镜像,这样就顺便简单说明如何使用 dockertest 对 Go 应用进行单元测试。
dockertest 可以在 Docker 容器中启动 Go 应用镜像并执行测试用例。
相关测试用例可以参考 main_test.go 。
docker-go-server-ping:latest
go testdocker-go-server-ping:latest
多阶段构建
可能你已经注意到了,docker-go-server-ping 镜像的大小有 407MB,这着实有点大,并且镜像中还有全套的 Go 工具、Go 应用的依赖等,但实际我们应用运行时不需要这些文件,只需要编译后的二进制文件。那么能不能减小镜像的体积呢?
要减小镜像体积,我们可以使用多阶段构建。Docker 在 17.05 版本以后,新增了多阶段构建功能。多阶段构建实际上是允许一个 Dockerfile 中出现多个 FROM 指令。通常我们使用多阶段构建时,会先使用一个(或多个)镜像来构建一些中间制品,然后再将中间制品放入另一个最新且更小的镜像中,这个最新的镜像就只包含最终需要的构建制品。
多阶段构建 Dockerfile
Dockerfile.multistagemultistage
Dockerfile.multistagegolang:1.16-alpinebuildscratch
在 Go 应用中,多阶段构建非常常见,可以减小镜像的体积、节省大量的存储空间。
Dockerfile.multistageRUN
交叉编译
交叉编译是指在一个平台上生成另一个平台的可执行程序。
在其他编程语言中进行交叉编译可能要借助第三方工具,但 Go 内置了交叉编译工具,使用起来非常方便,通常设置 CGO_ENABLED、GOOS 和 GOARCH 这几个环境变量就够了。
CGO_ENABLED
1
CGO_ENABLED=1CGO_ENABLED=0
所以交叉编译时,我们需要将 CGO_ENABLED 设置为 0。
GOOS 和 GOARCH
GOOS 是目标平台的操作系统,如 linux、windows,注意 macOS 的值是 darwin。默认是当前操作系统。
GOARCH 是目标平台的 CPU 架构,如 amd64、arm、386 等。默认值是当前平台的 CPU 架构。
Go 支持的所有操作系统和 CPU 架构可以查看 syslist.go 。
go env
Dockerfile.multistage
构建镜像
由于我们现在有两个 Dockerfile,所以我们必须告诉 Docker 我们要使用新的 Dockerfile 进行构建。
构建完成后,你会发现 docker-go-server-ping:multistage 只有不到 8MB,比 docker-go-server-ping:latest 小了几十倍。
运行 Go 镜像
现在我们有了 Go 应用的镜像,接下来就可以运行 Go 镜像查看应用程序是否正常运行。
docker run
可以看到 Go 应用成功启动了。
让我们再打开一个新的终端,通过 curl 向 Go 服务器发起一个请求:
使用 Makefile 简化构建流程
在前面的步骤中,我们使用到了非常多的命令,维护起来非常麻烦,这时我们就可以使用 make 来简化构建流程。
make 是一个自动化构建工具,会在根据当前目录下名为 Makefile(或 makefile)的文件来执行相应的构建任务。
所以让我们先创建一个 Makefile 文件,内容如下:
make
例如:
makeallmake testmake build-dockerbuild-docker-multistage
当然你也可以在 Makefile 中定义其他命令。
总结
在本文中,我们首先开发了一个简单的 Go REST 服务应用,然后针对该应用详细讲解了如何构建 Docker 镜像。要构建镜像首先需要编写 Dockerfile,但基础的 Dockerfile 体积过大,所以我们又学习了如何通过多阶段构建减小镜像体积。在多阶段构建时,由于构建机和部署服务器可能存在操作系统和 CPU 架构的差异,又学习了如何通过交叉编译构建出可在其他平台直接使用的二进制文件。最后由于整个构建流程涉及命令比较多,真实 Go 项目可能构建流程会更复杂,所以学习了如何通过 Makefile 简化构建流程。
最后感谢你的阅读,希望本文的内容能让你有所收获。