构造能离线编译golang项目的docker镜像
Contents
缘起
在现代化的开发流程 DevOps 中,经常需要让 gitlab 在提交代码后自动进行单元测试、构建甚至发布。这时,通常会让一个 gitlab-runner 执行 gitlab-ci 脚本。执行时,一般使用一个 docker 镜像。
在 gitlab-ci 脚本中,会需要对这个 go 项目进行单元测试(go test ./...
)或者构建(go build .
)。因此这个 docker 镜像必须预装 go 语言。
然而有一个问题。
一方面,golang 是一个高度依赖互联网连接的语言,其依赖包都是来自网上,甚至编译过程中还会下载所需要的包。
另一方面,很多企业为了安全,gitlab-runner 运行在不联网的服务器上。
这使得 gitlab-runner 执行go build
(或者在那之前的go mod tidy
)时,会因为网络不通而失败。
问题分析
无法编译的原因在于,编译过程所依赖的第三方包,由于网络原因无法下载到本地(上面情况就是下载到 docker 容器里)。
那么解决方案有三种。
- 设法让 gitlab-runner 可以联网。
- 将项目依赖的第三方包保存到项目内部的 vendor 目录下。
- 直接让编译器镜像自带所需要的第三方包。
本文专注第三种方案。
解决方案
准备动作:我使用 windows 命令行。需要安装 Docker Desktop。
Step 1: 拉取最新 golang 基础镜像
由于项目很可能在go mod init
的时候生成了一个基于最新 go 版本的go.mod
文件,因此编译环境必须尽可能最新。
命令:
|
|
效果:
|
|
确认:
|
|
Step 2: 将所有依赖包都打包压缩成一个 tar.gz
虽然理论上只需要将当前项目所需的第三方包打包到编译器镜像中,但为了让这个镜像通用于其他项目的编译,并且(更主要的)为了方便操作,干脆把所有本地的第三方包都打包。
确认第三方包位置:
|
|
进入该目录,可以看到里面有 bin 和 pkg 两个目录。我们需要打包的是整个 pkg 文件夹。打包成单个 pkg.tar.gz 文件。
Windows 标准命令行(cmd)没有提供 tar 命令,不过 PowerShell 有提供。所以用 PowerShell 可以完成打包压缩(也可以切换到 WSL 来使用 linux 的 tar 工具)。
|
|
Step 3: 编写编译器镜像的 DockerFile
基本上,应该用 DockerFile 来规定 Docker 镜像的生成规则。
随便找个目录,在里面放上纯文本的 DockerFile 文件,内容是:
|
|
上面这个 DockerFile 将打包后的 pkg.tar.gz 添加到镜像中。注意,虽然添加的是 pkg.tar.gz 但添加时会自动解压缩。
将这个 DockerFile 和 pkg.tar.gz 放在同一个目录下。
|
|
进入这个目录,执行docker build -t <TAG>
。
|
|
这里 gobuilder 是镜像名称,0.11 是镜像版本号。均可随意改动。
确认编译结果:
|
|
Step 4: 将新镜像上传到 docker 镜像仓库
基本上,docker 镜像要上传到哪个镜像仓库,是由镜像标题决定的。只需要给已有镜像打上一个「带有镜像仓库路径」的tag,然后推送就行了。推送的时候会自动从路径识别出目标镜像仓库。
|
|
Step 5: 在待编译项目的 .gitlab-ci.yml 里指定使用新编译器镜像
直接在开头或者某个job里使用image
命令即可。比如image: your.company.com/myteamname/gobuilder:0.56
。
日常维护建议
golang 官方会时不时地更新golang:latest
的版本。各第三方库也可能时常更新版本。所以要定期更新gobuilder
(暂定名),方便使用。
总结
这个方案并不完美。
理想情况下,应该是在已有镜像的基础上动态更新,这样可以节约 docker 镜像仓库的空间。不过由于golang:latest
作为基础镜像,每次更新gobuilder
都需要重新构建整个镜像。
考虑到各模块对编译环境的依赖,或许应该让 gitlab-ci 里依赖your.company.com/myteamname/gobuilder:latest
更好。
Author seedjyh
LastMod 2025-07-09