基于docker multi-stage分离Golang编译与运行时镜像

在没有docker技术之前,我们利用Jenkins实现CI/CD的时候,代码是在Jenkins宿主机上完成编译的。

这样不同的Jenkins构建任务之间就缺乏良好的隔离性,可能A项目需要Golang1.12版本,而B项目需要Golang1.13版本,这样宿主机就很不方便同时满足两者。

docker带来的改变

在docker技术出现之后,给隔离带来了便捷性。

我们完全可以为每一个构建任务启动一个专属容器,根据环境需要使用不同版本的Golang镜像完成代码编译。

我们可以直接把编译go程序的容器打包成镜像直接用于发布,但是会面临一个问题:

编译程序的容器内残留了golang编译的依赖,当然也包含了golang语言本身。而实际golang编译产生的二进制是不需要golang环境的,可以直接独立运行的。

借助multi-stage实现优化

出于镜像体积的考虑,如果我们可以把编译好的go二进制copy到另外一个纯净的镜像里发布,那么PAAS集群下载镜像的体积就比较小,分发速度就可以得到保证。

上述思路需要基于docker提供的multi-stage功能实现,它允许在1个Dockerfile中定义连续构建多个镜像,并且允许在镜像之间进行文件Copy。

因此,golang程序的Docker打包过程就变成了这样:

  • 构建第1个镜像,在内部完成依赖下载与程序编译,产生二进制。
  • 构建第2个镜像,从第1个镜像仅copy二进制到第2个镜像内,最终发布第2个镜像提供服务。

演示项目地址:https://github.com/owenliang/docker-multi-stage

下面简单讲解一下项目。

写一个Go程序

采用go module管理依赖,程序只有1个文件:https://github.com/owenliang/docker-multi-stage/blob/master/cmd/main.go

引用了第三方的库实现定时任务。

Dockerfile的stage1

接下来编写Dockerfile来实现上述程序的编译与打包。

完整Dockerfile见:https://github.com/owenliang/docker-multi-stage/blob/master/Dockerfile

这里使用了docker的multi-stage功能,因此第1个stage就是为了编译go程序:

忽略一些细节的话,大家应该可以理解这个镜像的构造过程:

  • FROM:基于dockerhub上的官方golang镜像作为base image,版本是通过ARG传参进来的。
  • COPY:把cmd目录、go.sum、go.mod等项目文件统统copy到容器内。
  • ENV:配置golang走代理下载依赖。
  • RUN:在容器内进入到.go文件目录,执行go build自动完成依赖下载和程序编译,产生二进制main。

比较陌生的细节有2个:

  • ARG:执行docker build的时候,可以传参给Dockerfile,以便实现灵活控制;这里就是支持命令行传参GOLANG_VER,用来指定要使用哪个版本的golang镜像用于程序编译。
  • as BINARY:这相当于给stage起了一个名,叫做BINARY;因为后一个stage中我们要从该stage构建的镜像中把main二进制copy过去,因此需要给第1个stage起一个名字。

实际命令行会这样执行:

至于另外一个APP_LOCATION则是用于我们第2个stage使用的参数,稍后会看到。

Dockerfile的stage2

stage2的目的是把stage1的main二进制拷贝过来,输出的镜像就是用于发布用途的运行时镜像了。

golang的二进制可以直接运行,不依赖于go环境,所以我这里采用centos作为基础镜像。

这里需要注意的包含2点:

  • ENV与CMD:APP_LOCATION参数是为了控制将main二进制移动到什么路径下;但是CMD部分并不能访问到ARG APP_LOCATION,因为CMD命令是容器拉起时才会解释执行的,所以必须通过ENV保存APP_LOCATION,这样CMD在执行时才会取到值。
  • COPY:这里用了–from=BINARY指定了从哪个stage的镜像中进行文件copy。

根据之前docker build命令可知,stage2构建的镜像最终命名为test,接下来做一个测试。

测试image

先启动容器到后台,容器的入口CMD命令就是执行了go的二进制程序:

然后通过bash进入容器,确认go二进制正常运行:

可以看到/go/myapp已经拉起运行。

最后

其实无论是什么语言,基于docker multi-stage均可以实现编译环境与运行环境的隔离,实现灵活的CI/CD控制。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~