1. 程式人生 > >golang在gitlab中的工作流

golang在gitlab中的工作流

註入 ebo pac href 步驟 ant ren 定義 span

在敏捷開發的時代, 快速的編碼, code review, 測試, 部署, 是提升程序員效率的關鍵。 同時在基礎工具完備的如今, 我們甚至無需過多的操作就可以輕松實現上述步驟, 本文就以gitlab為例, 分享一下golang項目結合gitlab如何實現自動化CI。

在gitlab中執行CI, 需要在項目根目錄下增加.gitlab-ci.yml文件, 定義執行CI任務的步驟及方式, 例如典型的操作:執行代碼檢測, 編譯, 測試, 發布。 gitlab會在每次commit或push的時候調用runner來執行該文件中所定義的操作。runner是一個執行具體CI任務的工具, 可以運行在任意實體機, docker或k8s集群, 在項目中配置好後, gitlab就會自動調用runner並將結果返回。

配置gitlab runner

runner可以為某個項目單獨配置, 也可以由gitlab管理員預先定義一些公共使用的runner, 後者無需開發人員過多操作, 主要講解一下前者, 如何單獨配置一個runner。首先安裝gitlab runner, 參照官方安裝教程, 安裝好之後需要註冊該runner到項目中, 本文使用的是docker作為executer, 也就是用docker執行pipeline, 在setting->pipeline下獲取到項目的token和url, 通過gitlab-runner register來註冊該runner,
其中DOCKER_IMAGE 是如果未在.gitlab-ci.yml文件中指定image時默認的image。

sudo gitlab-runner register -n -u $CI_SERVER_URL  -r $REGISTRATION_TOKEN  --description "test-docker-runner"  --executor docker  --docker-image $DOCKER_IMAGE  --docker-privileged

編寫ci文件

上述如此就安裝並關聯好了gitlab-runner, 接下來就是編寫.gitlab-ci.yml文件了:

image: cr.registry.name/groupname/project-name-builder

stages:
- test - build - release cache: paths: - bin before_script: - echo "Git Branch is ${CI_COMMIT_REF_NAME}" testcase: stage: test script: - echo "begin run test" - make test build_bin: stage: build script: - make release_bin: stage: release script: - echo "begin release rpm pkg" - make push_rpm

image運行pipeline的鏡像, 可以直接使用golang這些基礎鏡像, 也可以是自己寫的一個Budiler鏡像, 本例中筆者使用的是一個自己寫好的builder鏡像, 因為在後面的ci操作中, 筆者會打包rpm包並上傳到文件服務器上以便後續的發布, 這些client都需要在鏡像中準備好, 基礎鏡像無法滿足條件, 所有筆者就自己打包了一個鏡像。
接下來是stage的定義, stage就是定義ci任務的各個階段, 本例中是執行測試, 編譯, 發布三個階段(因為go test的實現並不是分析編譯後binary文件, 所以可以看到測試在編譯之前), 各個stage串行執行, 只有前一階段執行完畢才會執行後面的階段, 如果哪個stage失敗則整個pipeline失敗。下面的testcase, build_bin, release_bin分別是各個stage的具體定義, 稱為job, 各個job的script中可以執行任意的命令, 需要註意script中的命令必須在上面指定的image中存在, 筆者的script只是調用了項目中的makefil, makefile中寫好了具體執行的操作, 如果一個stage中定義了多個job的話, 他們會並行執行。before_script定義了在各個job的script執行之前執行的操作, 筆者是打印出執行pipeline的branch, 其中CI_COMMIT_REF_NAME是gitlab內置變量, 表示當前的branch, 如果需要使用自己的變量, 可以通過variables來定義, 但是程序中使用到的敏感的token, key等信息, 不要直接定義在variables中, 可以通過setting->pipeline->Secret variables中設置, 使用的時候會自動作為環境變量註入進去。cache定義了各個stage直接可以保存下來供其他stage使用的數據, 筆者這裏是項目下的bin文件, 因為在build階段生成了binary可執行文件之後, 在release階段就可以直接使用。需要註意的是, cache並不能保證每次都會存在, 所以在release中需要有其他的邏輯保證沒找到cache也可以正常的工作。
筆者上述只是三個stage, 在一個成熟的項目中應該包括很多這樣的stage, 依次進行lint, unit tests, data race, memory sanitizer, code coverage, build, release。幸運的是對於這些步驟中的大部分go都直接提供了相應的工具來使用, 例如goline, go test等, 其中代碼測試覆蓋率這塊有個小問題需要單獨拿出來講一下。

golang 項目的 test coverage

golang項目的測試覆蓋率可以直接用go test -cover ${package}輸出
也可以通過go test -coverprofile生成coverage profile, 該文件中保存了一些收集到的測試信息, 然後將這些信息提供給go tool cover使用來輸出測試覆蓋率。

go test -coverprofile "profile.cov" ${package_name}
go tool cover -func="profile.cov"

但是上面的測試覆蓋率只是針對一個package, 對於一個項目中有多個package, 需要依次調用go test -coverprofile產生每個package的profile, 然後將其合並在一起, 再調用go tool cover產生整個項目的coverage例如:

#!/bin/bash
#
# Code coverage generation
COVERAGE_DIR="${COVERAGE_DIR:-coverage}"
PKG_LIST=$(go list ./... | grep -v /vendor/)
# Create the coverage files directory
mkdir -p "$COVERAGE_DIR";
# Create a coverage file for each package
for package in ${PKG_LIST}; do
    go test -covermode=count -coverprofile "${COVERAGE_DIR}/${package##*/}.cov" "$package" ;
done ;
# Merge the coverage profile files
echo ‘mode: count‘ > "${COVERAGE_DIR}"/coverage.cov ;
tail -q -n +2 "${COVERAGE_DIR}"/*.cov >> "${COVERAGE_DIR}"/coverage.cov ;
# Display the global code coverage
go tool cover -func="${COVERAGE_DIR}"/coverage.cov ;
# Remove the coverage files directory
rm -rf "$COVERAGE_DIR";

在golang 1.10中已經支持了go test後面的多個package共同生成一個profile, 無需手動合並多個profile, go test $(go list ./... | grep -v vendor) -v -coverprofile .testCoverage.txt
所以現在整個項目的coverage的輸出方式為:go test $(go list ./...) -v -coverprofile .testCoverage.txt && go tool cover --func=.testCoverage.txt 如果你需要一步生成coverage的話記得升級你的golang版本。
如果項目中某個package不含有測試文件, 則這個package的coverage應該為0%, 但是目前go tes對於不含有測試文件的package就不會在profile中產生對應的記錄, 在算項目的coverage的時候也就不會包括該package。如此整個項目的coverage其實比實際值大, 因為少算了coverage為0的package, 這個目前還沒有一種簡單的解決方式, 只能是在每個package中都增加測試文件, 其實這也是我們應該做到的。

徽章(Badges)

有了測試覆蓋率的數據就可以在項目的首頁展示這些數據了, 如下圖所示:
技術分享圖片

在項目的Setting-> pipeline 最下面可以找到對應的MarkDown格式的輸出, 在首頁的ReadMe中添加上即可, 形如

[![Build Status](https://gitlab.com/pantomath-io/demo-tools/badges/master/build.svg)](https://gitlab.com/pantomath-io/demo-tools/commits/master)   

對於測試覆蓋率還需要在項目中配置正則表達式, 來匹配pipeline的輸出中的測試覆蓋率, 如果用上面的命令則正則表達式為: total:\s+\(statements\)\s+(\d+.\d+\%)
如果想要添加其他的badege, 查閱相關資料即可。如果想要使用go report card badges 而有不想公開公司內網的代碼則需要自己搭建go report 服務器了。

gitlab與golang 的其他要點

  1. 項目中每次改代碼都應該在一個新的branch上進行編碼操作, 每次push代碼都會觸發提前設置好的pipeline自動執行一些編譯測試等操作, pipeline結束後會生成代碼的測試覆蓋率, code small等數據, 如果數據不符合預先的規範, 需要繼續修改直到數據達標。開發完成之後然後提交merge request, 然後指派給同事進行code review, 如何需要多人review的話就在description裏cc @somebody 艾特其他人就可以了。 gitlab本身的code review效果還不錯, 可以選擇在任意兩個commit 之間進行diff。 如果本次提交是為了解決issue的話, 在commit message中直接通過#issueID來鏈接或關閉對應的issue。最後merge request通過之後代碼合並至master, master中的pipeline與dev分支的有些不同, 可以在master中執行CD進行自動發布。
    對於所有出現的bug都應該反應在一次issue裏, 後續的進展都應該及時更新在issue裏。 issue可以通過設置tag, milestone 來管理, 這樣做更加清晰明了, 方便管理, 回溯。
  2. 項目中必須包含makefile, 任何編譯, 測試, 發布, 都通過make subcommand 一條命令解決, 無需額外設置GOPATH的位置, git submodule, go package的下載等, 這樣才足夠敏捷, .gitlab-ci.yml文件中也無需寫冗長的script, 將來如果編譯方式變化也可以只修改makefile就可以了, 同時對於新人剛接觸項目也更友好。
  3. 項目中推薦寫一個builder鏡像, 取名為Dockerfile.build以區分真正的業務鏡像, 這樣的好處是對開發環境依賴較少, 只需要有docker就行, golang, rpm制作工具等都打包在builder鏡像之中, 使用的時候直接docker run -v ``pwd``:/project cr.registry.name/groupname/project-name-builder make即可。尤其是在gitlab ci的時候, 可能ci過程中需要執行代碼質量檢查等, 需要用到golint等工具, 如果只使用基礎鏡像, 則每次ci任務都需要在去下載, 這樣費時費力, 不如將這些工具打包在builder鏡像中, 然後在yaml文件的image中指定該builder鏡像即可。

reference

Go tools and GitLab: How to do continuous integration like a boss

golang在gitlab中的工作流