使用 Jenkins 执行 Go 工程构建镜像
随着 Go 语言的流行,越来越多的公司和开发人员在工作中使用该语言开发项目,由于现有上线系统后端是基于 Jenkins + docker 执行任务的,那么是时候体验一下如何使用 Jenkins 执行 Go 工程构建镜像了。文章主要介绍了* 安装 Jenkins、安装 Go Plugin 插件并配置、配置 Jenkins Job 构建 Go 工程、使用 Golang 镜像执行编译、使用 Docker
文章目录
1、背景介绍
随着 Go 语言的流行,越来越多的公司和开发人员在工作中使用该语言开发项目,目前公司有的项目组也已经使用 Go 语言来开发一些项目,并运用到生产环境中,由于之前没有配套针对 Go 语言的上线流程,只能开发手动编译执行上线,上线效率很低,而且不容易回滚,所以迫切需要符合 Go 语言的项目上线流程。由于现有上线系统后端是基于 Jenkins + docker 执行任务的,那么是时候体验一下如何使用 Jenkins 执行 Go 工程构建镜像了。
2、环境、软件准备
本次演示环境,我是在本机 MAC OS 上操作,以下是安装的软件及版本:
- Docker: 17.09.0-ce
- Jenkins: v2.60.3
- Go Plugin: 1.2
- Go: 1.11
注意:因为演示需要进行镜像操作,所以本机需要安装好 Docker 环境,这里忽略 Docker 的安装过程,可以参考 docker 官网文档 , 这里着重介绍下 Jenkins 及其插件安装与构建操作。
3、安装 Jenkins
Jenkins 安装启动方式有两种,第一种是基于 Tomcat、Jdk 启动,第二种是基于 Docker 启动。 注意:因为下边我们需要演示使用 Golang 镜像执行编译以及多阶段构建,默认 Jenkins 镜像中是未安装 Docker 的,所以可以按照第一种方式启动。
3.1、基于 Tomcat、Jdk 启动
- 首先下载 Jenkins 最新的安装包,可以去官网下载最新版,点击 这里 下载。
- 启动 Jenkins 可以有两种方式
- 进入 war 包所在目录,直接执行
java -jar jenkins.war
- 将 war 包放在 Tomcat webapps 目录下,启动 Tomcat。
- 进入 war 包所在目录,直接执行
3.2、基于 Docker 启动
-
拉取 Jenkins 官方镜像
docker pull jenkins
-
启动 Jenkins 容器
docker run -p 8080:8080 -p 50000:50000 -v /Users/wanyang3/jenkins_home:/var/jenkins_home jenkins
启动完成之后,浏览器访问 http://localhost:8080
,第一次启动初始化稍慢一些,稍等一会就可开始 Jenkins 初始化配置。详细配置这里就不在赘述了,可以参照之前文章 初试 Jenkins2.0 Pipeline 持续集成 # 安装、启动并配置 jenkins 服务 详细配置。
4、安装 Go Plugin 插件并配置
Jenkins 配置完毕后,在正式执行 Go 工程编译前,我们需要安装一个 Go Plugin 插件,该插件主要完成以下几个功能:
- 提供各预编译版本 GO 安装包,方便 Jenkins 所在机器执行安装。
- 配置
GOROOT
环境变量,并指向安装的 Go 工具。 - 添加
$GOROOT/bin
到系统PATH
中,以方便构建时使用 GO 工具时可以直接使用。
说明一下,我们知道 GO 项目执行编译,需要指定好 GOROOT
以及配置 GOPATH
到环境变量中,这里插件直接帮我们配置好了,当然如果觉得默认配置路径不合适,我们也可以在执行构建时临时临时指定其他目录,下边会讲到。
安装该插件,点击 “系统管理” -> “管理插件” -> “可选插件” -> 选择 “Go Plugin” -> 点击最下边 “直接安装” 即可完成安装。
安装完毕后,我们进入到 “系统管理” -> “Global Tool Configuration” -> “Go” -> “新增 Go”,默认情况下,插件自动安装 “Install from golang.org”,我们直接选择 Go 版本以及配置别名即可,如下图。
But,由于国内网络的问题,想要直接从 golang.org
上下载安装包可不是那么随意的,那该怎么办呢?我们可以选择非自动安装,直接在机器上安装 Go,然后在这里指定 Go 安装目录即可。例如,这里我提前在机器 /var/jenkins_home/go
目录安装好了系统对应版本的 Go-1.11
版本的安装包,直接配置即可。Go 环境详细安装过程,可参考 Go 官网 文档.
安装完毕后,我们就可以使用 Go Plugin 插件啦!有两种方式使用该插件。
-
Freestyle 类型 Job
针对该类型的 Job,可以在 “构建环境” 项下选择 “Set up Go programming language tools”,然后在上边已配置的 “Go version” 下选择某一版本即可。
我们来测试一下这种方式,选择go 1.11
版本,然后输出一下GOROOT
、PATH
等环境变量。echo "GOROOT: ${GOROOT}" echo "PATH: ${PATH}" echo "GOPATH: ${GOPATH}" go version
看下日志输出,的确使用了
go 1.11
版本,并且自动设置了GOROOT
和添加了$GOROOT/bin
到系统PATH
中。...... 03:49:59 [go_build_test] $ /bin/sh -xe /tmp/jenkins6920182511078977565.sh 03:49:59 + echo GOROOT: /var/jenkins_home/go 03:49:59 GOROOT: /var/jenkins_home/go 03:49:59 + echo PATH: /var/jenkins_home/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 03:49:59 PATH: /var/jenkins_home/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 03:49:59 + echo GOPATH: 03:49:59 GOPATH: 03:49:59 + go version 03:49:59 go version go1.11 linux/amd64
-
Pipeline 类型 Job
该方式使用 Jenkins Pipeline 来运行该插件,可以使用 tool 工具来指定类型为 go,并指定 name 为上边配置的 Go 别名,配置一下 Go 运行环境,即可使用该版本 Go 环境啦!例如一个简单的 Pipeline Script 示例如下:
node { stage('show go version'){ def root = tool name: 'go 1.11', type: 'go' withEnv(["GOROOT=${root}", "PATH+GO=${root}/bin"]) { sh 'go version' } } }
5、配置 Jenkins Job 构建 Go 工程
插件调试完毕,接下来我们就可以配置构建 Go 工程,这里我以一个自己测试的简单的 Beego 框架搭建的项目 apiproject
为例,项目源码在 Github 这里 可以下载到。新建一个 Freestyle 的名称为 go_build_test
的 Job,并配置源码管理和构建脚本如下图:
说明一下:
- 源码管理处,我添加了
Check out to a sub-directory
并配置为$WORKSPACE/src/apiproject
,为什么要这样操作呢?我们知道,Go 运行需要指定 GOPATH 也即项目运行路径,默认情况下为$GOROOT/src
,跟我配置的不一致,这里我要指定当前 Job 的WORKSPACE
为项目构建路径,这样做的好处是:1、每个 Job 拥有自己工作空间下独立的构建路径,跟其他 Job 隔离开,避免不同项目对同一插件依赖的版本不一致导致的问题。2、每个 Job 在自己工作空间下,可以通过 Web 页面直接查看文件,方便排查问题。当然坏处就是:一旦我们清理了该 Job 的工作空间,那么下次执行时会重新拉取各个插件依赖,会耗时久一些。 - 同时还添加了 “Check out to a specific local branch”,并配置为
master
,这里是因为Check out to a sub-directory
操作会将当前分支变为一个游离分支,而下边go get -u -v
操作更新当前项目的时候是有要求的,首先远端仓库必须是 https 协议地址,其次本地分支必须跟远端已存在的相同分支关联。所以,这里我们可以将分支切换回 master 分支(因为上边配置的默认为 master 分支) - 构建脚本中,我执行了
export GOPATH=$WORKSPACE
和export PATH=$GOPATH:$PATH
目的是为了将当前 Job 工作空间当做项目构建目录。 - 构建脚本中,我执行了
git branch --set-upstream-to=origin/master master
这里是为了使本地分支与远端分支做关联,否则go get -u -v
操作不通过。
配置完毕后,执行立即构建,看下妥妥没问题。
可以看到工作空间里面,apiproject 工程成功构建。
6、使用 Golang 镜像执行编译
除了上边使用 Go Plugin 插件完成 Go 项目的编译之外,我们还可以是使用 Golang 官方镜像很容易来完成构建,我们来看下该如何实现,首先项目根目录新建一个 dockerfile
文件如下。
FROM golang:1.11
WORKDIR /go/src/apiproject
COPY . .
RUN go get -d -v && go install -v
CMD ["sh", "-c", "/go/bin/apiproject"]
简单说下,我们以 golang:1.11
作为基础镜像,因为该环境中已经安装好了 Go 环境,而且默认配置了 /go/src
为其构建路径,那么只需要将项目源码复制到该目录下(要注意项目名,不然源码中 import 包名会出错的哈),当然也可以使用挂载方式。最后执行拉取依赖和编译,启动编译后的文件即可。
接下来,制作镜像并启动一下看下。
$ docker build -t go-project/apiproject:v1.0.1 .
Sending build context to Docker daemon 2.655MB
Step 1/5 : FROM golang:1.11
---> fb7a47d8605b
Step 2/5 : WORKDIR /go/src/apiproject
Removing intermediate container 55586ebf0681
---> 5439da2e4f7d
Step 3/5 : COPY . .
---> 463d0a861f9d
Step 4/5 : RUN go get -d -v && go install -v
---> Running in c777400529e9
github.com/astaxie/beego (download)
github.com/go-sql-driver/mysql (download)
github.com/pkg/errors (download)
apiproject/bean
github.com/astaxie/beego/orm
github.com/pkg/errors
github.com/astaxie/beego/config
github.com/go-sql-driver/mysql
github.com/astaxie/beego/utils
github.com/astaxie/beego/vendor/gopkg.in/yaml.v2
github.com/astaxie/beego/session
github.com/astaxie/beego/logs
github.com/astaxie/beego/grace
github.com/astaxie/beego/toolbox
github.com/astaxie/beego/vendor/golang.org/x/crypto/acme
github.com/astaxie/beego/context
github.com/astaxie/beego/vendor/golang.org/x/crypto/acme/autocert
apiproject/models
github.com/astaxie/beego/context/param
github.com/astaxie/beego
apiproject/controllers
apiproject/routers
apiproject
Removing intermediate container c777400529e9
---> df774ffdeaff
Step 5/5 : CMD ["sh", "-c", "/go/bin/apiproject"]
---> Running in fff870787128
Removing intermediate container fff870787128
---> d7705fd98b5d
Successfully built d7705fd98b5d
Successfully tagged go-project/apiproject:v1.0.1
镜像制作完毕,启动一下试试看。
$ docker run -p 8089:8089 --name apiproject d7705fd98b5d
2018/09/29 10:00:35.117 [I] [parser.go:96] generate router from comments
2018/09/29 10:00:35.119 [I] [router.go:269] /go/src/apiproject/controllers no changed
2018/09/29 10:00:35.123 [I] [asm_amd64.s:1333] http server Running on http://:8089
2018/09/29 10:00:49.373 [D] [server.go:2741] | 172.17.0.1| 302 | 120.64µs| match| GET /swagger
2018/09/29 10:00:49.379 [D] [server.go:2741] | 172.17.0.1| 200 | 1.105248ms| match| GET /swagger/
2018/09/29 10:00:49.460 [D] [server.go:2741] | 172.17.0.1| 200 | 1.987006ms| match| GET /swagger/swagger-ui.css
2018/09/29 10:00:49.472 [D] [server.go:2741] | 172.17.0.1| 200 | 11.403196ms| match| GET /swagger/swagger-ui-standalone-preset.js
2018/09/29 10:00:49.474 [D] [server.go:2741] | 172.17.0.1| 200 | 16.388701ms| match| GET /swagger/swagger-ui-bundle.js
2018/09/29 10:00:50.044 [D] [server.go:2741] | 172.17.0.1| 200 | 2.538031ms| match| GET /swagger/swagger.json
2018/09/29 10:00:50.223 [D] [server.go:2741] | 172.17.0.1| 200 | 1.517141ms| match| GET /swagger/favicon-32x32.png
2018/09/29 10:00:50.276 [D] [server.go:2741] | 172.17.0.1| 200 | 2.631284ms| match| GET /swagger/favicon-16x16.png
日志显示启动成功,我们使用浏览器访问 http://127.0.0.1:8089/swagger/
,妥妥没有问题。
7、使用 Docker 多阶段构建镜像
Docker 17.05.0-ce 版本以后支持多阶段构建。使用多阶段构建,我们可以在 Dockerfile 中使用多个 FROM
语句,每条 FROM
指令可以使用不同的基础镜像,这样可以选择性地将服务组件从一个阶段 COPY
到另一个阶段,在最终镜像中只保留需要的内容。想想之前遇到镜像需要依赖另一个镜像运行后的服务组件,通常我们需要创建多个 Dockerfile
,然后通过挂载的方式将依赖的另一镜像的服务组件挂出,复制到最终镜像中,非常麻烦,例如我们典型的应用场景,一个编译镜像,一个运行镜像,运行镜像依赖编译镜像编译后的产物。
同时,上边我们使用 golang:1.11
镜像先编译后运行,最终的镜像大小达到了 813M,增加了磁盘使用量和下载时间,使得我们整个部署时间大大延长。接下来,演示一下使用 Docker 多阶段构建镜像方式,方便的制作出一个能够运行只包含 Go 编译后产物的镜像,而且镜像体积大大减小。
首先我们新建一个名称为 Multistage.dockerfile
的 dokerfile
文件如下。
# 编译镜像
FROM golang:1.11 AS builderImage
WORKDIR /go/src/apiproject
COPY . .
RUN go get -v && go install && mv /go/bin/apiproject /root
# 产物运行镜像
FROM scratch
WORKDIR /root
COPY --from=builderImage /root .
EXPOSE 8089
CMD ["/root/apiproject"]
简单说下,该 dockerfile
包含了两部分:编译镜像和产物运行镜像,编译镜像跟上边演示的 dockerfile
一样负责生成编译后产物,产物运行镜像通过 COPY --from
语句即可将编译后的产物复制到该镜像内部,最后运行该产物即可。这里提一下,产物运行镜像我采用了 scratch
作为基础镜像,scratch
是一个空镜像,只能用于构建其他镜像,因为它体积很小,能够最大限度的减小我们的产物运行镜像大小(当然也可以使用 busybox
、alpine
等小体积镜像)。
接下来,我们来执行 build 构建,看下执行过程吧!
$ docker build -t go-project/apiproject:v1.0.2 -f Multistage.dockerfile .
Sending build context to Docker daemon 2.656MB
Step 1/9 : FROM golang:1.11 AS builderImage
---> fb7a47d8605b
Step 2/9 : WORKDIR /go/src/apiproject
---> Using cache
---> 5439da2e4f7d
Step 3/9 : COPY . .
---> Using cache
---> f1c8116af400
Step 4/9 : RUN go get -v && go install && mv /go/bin/apiproject /root
---> Running in 7a5e5e04b61f
github.com/astaxie/beego (download)
github.com/go-sql-driver/mysql (download)
github.com/pkg/errors (download)
apiproject/bean
github.com/astaxie/beego/orm
github.com/pkg/errors
github.com/astaxie/beego/config
github.com/go-sql-driver/mysql
github.com/astaxie/beego/utils
github.com/astaxie/beego/vendor/gopkg.in/yaml.v2
github.com/astaxie/beego/session
github.com/astaxie/beego/logs
github.com/astaxie/beego/grace
github.com/astaxie/beego/toolbox
github.com/astaxie/beego/vendor/golang.org/x/crypto/acme
github.com/astaxie/beego/context
github.com/astaxie/beego/vendor/golang.org/x/crypto/acme/autocert
github.com/astaxie/beego/context/param
apiproject/models
github.com/astaxie/beego
apiproject/controllers
apiproject/routers
apiproject
Removing intermediate container 7a5e5e04b61f
---> 7c014b441b75
Step 5/9 : FROM scratch
--->
Step 6/9 : WORKDIR /root
Removing intermediate container e0fc1e337360
---> 07e1b208bdcc
Step 7/9 : COPY --from=builderImage /root .
---> be659716bed8
Step 8/9 : EXPOSE 8089
---> Running in 76059331c67a
Removing intermediate container 76059331c67a
---> 9756d11d714a
Step 9/9 : CMD ["/root/apiproject"]
---> Running in 44bfc2f11c31
Removing intermediate container 44bfc2f11c31
---> f7ef0d72e876
Successfully built f7ef0d72e876
Successfully tagged go-project/apiproject:v1.0.2
build 成功,来对比下两种不同方式 build 出来的镜像,我们会发现最后构建出的镜像远远小于使用 golang
镜像构建方式生成的镜像。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
go-project/apiproject v1.0.2 f7ef0d72e876 2 hours ago 22.9MB
<none> <none> 7c014b441b75 2 hours ago 813MB
go-project/apiproject v1.0.1 d7705fd98b5d 24 hours ago 813MB
说明一下,这里的 <none>
镜像是一个无用镜像,也即是 dockerfile
中的编译镜像 builderImage
,当编译完成后,它的使命就完成了。其大小刚好跟上边使用 golang
镜像构建方式生成的镜像一样大,因为他们是使用的同样的基础镜像和构建过程。
8、常见问题处理
-
问题一:执行 git clone 时缺少软件依赖
Jenkins 执行 shell 命令行 clone git 仓库时,报错如下:
Peer reports incompatible or unsupported git clone https://github.com/huwanyang/beeg... Cloning into 'beego-apiproject'... fatal: unable to access 'https://github.com/huwanyang/beeg...': Peer reports incompatible or unsupported protocol version.
出现这个问题的原因是由于 Jenkins 运行所在机器缺少一些软件依赖或者软件依赖版本太低,解决办法就是执行
yum update -y nss curl libcurl
(针对 Centos 系统) 执行更新。 -
问题二:安装 Go Plugin 时依赖插件导败失败
Jenkins 插件中心执行安装 Go Plugin 插件时显示失败,提示 Structs Plugin 插件版本低。这是因为 Go Plugin 依赖 Structs 插件版本需要 >= 1.7,当安装 Go Plugin 时会自动安装到最新版,但是需要重启 Jenkins 即可。
-
问题三:执行
go get -u -x
报错执行
go get -u -x
会每次都会更新当前项目和依赖插件,但是它要求本地分支必须跟远程分支做关联以及 https 协议地址,否则报错信息如下:09:09:34 cd /var/jenkins_home/workspace/go_build_test/src/apiproject; git pull --ff-only 09:09:34 There is no tracking information for the current branch. 09:09:34 Please specify which branch you want to merge with. 09:09:34 See git-pull(1) for details. 09:09:34 09:09:34 git pull <remote> <branch> 09:09:34 09:09:34 If you wish to set tracking information for this branch you can do so with: 09:09:34 09:09:34 git branch --set-upstream-to=origin/<branch> master
07:38:08 package probe: cannot download, http://xxx.xxx.xxx.com/huwanyang/apiproject.git uses insecure protocol
出现如上错误,是因为
go get
默认规则所致,那么解决方案为执行go get -u -x
时指定更新的插件,这样就忽略掉本身所在项目,或者只执行go get
即不执行更新。如果更新时非要本地分支跟远程分支做关联,那么执行前加上git branch --set-upstream-to=origin/<branch> master
也可以。
参考资料
更多推荐
所有评论(0)