使用 Jenkins 執行 Go 工程構建映象
文章目錄
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
也可以。
參考資料