一個 Go 實用版本的 Github Actions 持續(xù)集成樣例

實現(xiàn) CI/CD 的工具平臺非常多,就個人使用過的工具就有:Jenkins、GitLab CI、Google Cloud Build 以及現(xiàn)在的 Github Actions 等等。
在 Github 開放個人私有倉庫功能之前,我的個人私有代碼是托管在 Google Cloud Source 上,加上 Google Cloud Build 以及 Google Cloud Registry 全家桶,使用起來還是很方便的。美中不足的就是 Google Cloud 上托管代碼,對于 Go 程序員而言,需要做一些輔助工作,因為 Google Cloud Source 上的代碼倉庫的路徑不像 github 倉庫路徑這樣標(biāo)準(zhǔn),必須搭建一個簡單的代理,才能實現(xiàn)常規(guī)的 Go Get 操作。
如今 Github 開始提供的免費的個人私有倉庫,所以打算將代碼遷移過來。既然要遷移代碼,必然涉及到持續(xù)集成(CI)的問題。
Google Cloud Build 與 Github Actions 的語法是非常不一樣的,但是,簡單遷移了一個代碼倉庫后,我發(fā)現(xiàn)整個過程并不痛苦。原因并不是因為 Github Actions 易于遷移,而是因為,當(dāng)初實現(xiàn)項目持續(xù)集成(CI)時,大部分構(gòu)建工作并不依賴于平臺,所以需要遷移的功能很少,僅僅是一個簡單觸發(fā)命令而已。
這就是我想分享的一點經(jīng)驗,CI/CD 盡量減少對于構(gòu)建平臺本身的依賴,盡可能多的實現(xiàn)本地化模擬。這樣做的好處非常明顯,既可以方便遷移,還可以減少對于不同平臺的學(xué)習(xí)成本。
本文通過一個簡單 Go 項目提供一個實用版本的 Github Actions 持續(xù)集成模板。方便自己和需要的同學(xué)使用。
在文中提供了關(guān)于如何設(shè)置 GitHub Actions 環(huán)境變量的章節(jié),算是對阮一峰那篇入門教程的補充。
持續(xù)集成與發(fā)布(CI/CD)對于程序員而言不是難事, 一般企業(yè)內(nèi)部都會提供現(xiàn)成的持續(xù)集成模板,特定的項目只需要修改相應(yīng)參數(shù)即可。這些現(xiàn)成的模板,很多時候就成我們學(xué)習(xí)CI/CD的最佳教程。本文即通過一個簡單 Go 項目提供一個實用版本的 Github Actions 持續(xù)集成模板。
同時,分享一下個人關(guān)于CI/CD以及Github Actions的簡單經(jīng)驗。
CI 與 CD
在容器概念出現(xiàn)以前,CI/CD之間的邊界比較模糊。有了容器概念以后,CI 與 CD 邊界變得非常明確,即 CI 負(fù)責(zé)構(gòu)建容器鏡像, CD 負(fù)責(zé)發(fā)布容器鏡像.有了 Kubernetes 之后,CI/CD 又再向前走一步,發(fā)展出了 Helm,CI 負(fù)責(zé)生成 Helm Chart, CD 負(fù)責(zé)發(fā)布 Helm Chart.
所以,CI/CD 是就其目的進行劃分的。
CI/CD 本地化
實現(xiàn) CI/CD 的工具平臺也非常多,就個人使用過的工具就有:Jenkins、GitLab CI、Google Cloud Build 以及現(xiàn)在的 Github Actions 等等。
在 Github 開放個人私有倉庫功能之前,我的個人私有代碼是托管在 Google Cloud Source 上,加上 Google Cloud Build 以及 Google Cloud Registry 全家桶,使用起來還是很方便的。美中不足的就是 Google Cloud 上托管代碼,對于 Go 程序員而言,需要做一些輔助工作,因為 Google Cloud Source 上的代碼倉庫的路徑不像 github 倉庫路徑這樣標(biāo)準(zhǔn),必須搭建一個簡單的代理,才能實現(xiàn)常規(guī)的 Go Get 操作。
如今 Github 開始提供的免費的個人私有倉庫,所以打算將代碼遷移過來。既然要遷移代碼,必然涉及到持續(xù)集成(CI)的問題。
Google Cloud Build 與 Github Actions 的語法是非常不一樣的,但是,簡單遷移了一個代碼倉庫后,我發(fā)現(xiàn)整個過程并不痛苦。原因并不是因為 Github Actions 易于遷移,而是因為,當(dāng)初實現(xiàn)項目持續(xù)集成(CI)時,大部分構(gòu)建工作并不依賴與平臺,所以需要遷移的功能很少,僅僅是一個簡單觸發(fā)命令而已。
這就是我想分享的一點經(jīng)驗,CI/CD 盡量減少對于構(gòu)建平臺本身的依賴,盡可能多的實現(xiàn)本地化模擬。這樣做的好處非常明顯,既可以方便遷移,還可以減少對于不同平臺的學(xué)習(xí)成本。
所以,在我寫的項目中會仍然保留 Makefile 文件。通過 Makefile 文件實現(xiàn) CI/CD 本地化。
Github Actions 實踐
在使用 Github Actions 功能之前,先看一下,Github 默認(rèn)提供給 Golang 項目的 WORKFLOW 定義.
name:?Go
on:
??push:
????branches:?[?master?]
??pull_request:
????branches:?[?master?]
jobs:
??build:
????name:?Build
????runs-on:?ubuntu-latest
????steps:
????-?name:?Set?up?Go?1.x
??????uses:?actions/setup-go@v2
??????with:
????????go-version:?^1.13
??????id:?go
????-?name:?Check?out?code?into?the?Go?module?directory
??????uses:?actions/checkout@v2
????-?name:?Get?dependencies
??????run:?|
????????go?get?-v?-t?-d?./...
????????if?[?-f?Gopkg.toml?];?then
????????????curl?https://raw.githubusercontent.com/golang/dep/master/install.sh?|?sh
????????????dep?ensure
????????fi
????-?name:?Build
??????run:?go?build?-v?.
????-?name:?Test
??????run:?go?test?-v?.
整個過程分為五步:
安裝 Go 環(huán)境; Checkout 代碼; 取項目依賴; 構(gòu)建項目; 項目測試。
這個過程沒什么問題,可以及時發(fā)現(xiàn)項目是否可以成功構(gòu)建并通過測試,但它并不實用。
首先, 它沒有完成一次真正的持續(xù)集成(CI)過程. 其次, 整個過程過度依賴 Github Actions 工作流。
所以,改造是必須的了。對于 Go 語言項目的CI過程,我一直推薦使用多階段容器構(gòu)建方式進行構(gòu)建.
既可以解決 Go 程序的跨平臺問題,又可以實現(xiàn)發(fā)布鏡像的最小化。
首先,通過提供Dockerfile的方式完成本地化構(gòu)建。
FROM?golang:1.14-alpine?AS?builder
#?按需安裝依賴包
#?RUN??apk?--update?--no-cache?add?gcc?libc-dev?ca-certificates??
#?設(shè)置Go編譯參數(shù)
ARG?VERSION
ARG?COMMIT
ARG?BUILDTIME
WORKDIR?/app
COPY?.?.
RUN?GOOS=linux?go?build?-o?main?-ldflags?"-X?github.com/x-mod/build.version=${VERSION}?-X?github.com/x-mod/build.commit=${COMMIT}?-X?github.com/x-mod/build.date=${BUILDTIME}"
#?第二階段
FROM??alpine
#?安裝必要的工具包
RUN??apk?--update?--no-cache?add?tzdata?ca-certificates?\
????&&?cp?/usr/share/zoneinfo/Asia/Shanghai?/etc/localtime
COPY?--from=builder?/app/main?/usr/local/bin
ENTRYPOINT?[?"main"?]
再通過 Makefile命令模擬CI觸發(fā):
...
image:
?docker?build?--build-arg?VERSION=${GITTAG}?--build-arg?COMMIT=${COMMIT}?--build-arg?BUILDTIME=${BUILD_TIME}?-t?${DOCKER_USER}/${PROJECT}:latest?.
完整的樣例,請參考看我寫的 Golang-Github-Action 模板項目。完成上面代碼后,其實整個CI過程就已經(jīng)本地化了。就可以通過
$:?make?image
驗證構(gòu)建過程。驗證通過后,我只需要將觸發(fā)過程遷移到 Github Actions 上即可。
現(xiàn)在就樣例項目寫一個 Github Actions 構(gòu)建模板:
#?This?is?a?basic?workflow?to?help?you?get?started?with?Actions?for?Golang?application
name:?CI
#?Controls?when?the?action?will?run.?Triggers?the?workflow?on?push?or?pull?request
#?events?but?only?for?the?master?branch
on:
??push:
????branches:
??????-?master
????tags:
??????-?"v*"
#?A?workflow?run?is?made?up?of?one?or?more?jobs?that?can?run?sequentially?or?in?parallel
jobs:
??#?This?workflow?contains?a?single?job?called?"build"
??build:
????#?The?type?of?runner?that?the?job?will?run?on
????runs-on:?ubuntu-latest
????#?Steps?represent?a?sequence?of?tasks?that?will?be?executed?as?part?of?the?job
????steps:
??????#?Checks-out?your?repository?under?$GITHUB_WORKSPACE,?so?your?job?can?access?it
??????-?uses:?actions/checkout@v2
??????-?name:?Define?variables
????????run:?|
??????????echo?::set-env?name=PROJECT::$(echo?"golang-github-action")
??????????echo?::set-env?name=VERSION::$(git?describe?--tags)
??????????echo?::set-env?name=COMMIT::$(git?rev-parse?HEAD)
??????????echo?::set-env?name=BUILDTIME::$(date?+%FT%T%z)
??????-?name:?Login?to?docker?hub
????????uses:?actions-hub/docker/login@master
????????env:
??????????DOCKER_USERNAME:?${{?secrets.DOCKER_USERNAME?}}
??????????DOCKER_PASSWORD:?${{?secrets.DOCKER_PASSWORD?}}
??????-?name:?docker?build
????????run:?docker?build?--build-arg?VERSION=${VERSION}?--build-arg?COMMIT=${COMMIT}?--build-arg?BUILDTIME=${BUILDTIME}?-t?${DOCKER_USERNAME}/${PROJECT}:${IMAGE_TAG}?.
????????env:
??????????DOCKER_USERNAME:?${{?secrets.DOCKER_USERNAME?}}
??????-?name:?Push?to?docker?hub
????????uses:?actions-hub/docker@master
????????with:
??????????args:?push?${DOCKER_USERNAME}/${PROJECT}:${IMAGE_TAG}
????????env:
??????????DOCKER_USERNAME:?${{?secrets.DOCKER_USERNAME?}}
這個過程分為 4 個步驟:
準(zhǔn)備環(huán)境變量 登錄 Docker Hub 構(gòu)建鏡像 發(fā)布鏡像到 Docker Hub
在第 3 步中,如果不是因為需要傳遞參數(shù),可以直接使用make image命令。
這樣一個構(gòu)建模板,其實整個過程是不依賴與特定開發(fā)語言的,因為項目的構(gòu)建過程已經(jīng)完全本地化了,即通過本地Dockerfile實現(xiàn)。所以,這個模板可以適用范圍是很廣的,完全與開發(fā)語言無關(guān)。
Github Actions 變量設(shè)置
關(guān)于 Github Actions 的基礎(chǔ)概念,我想阮一峰的這篇文章GitHub Actions 入門教程講的很清楚了,我就不在贅述了。
這里單獨將 Github Actions 環(huán)境變量的設(shè)置列出來說一下。
因為,當(dāng)我們拿到一個 CI 模板之后,首先遇到的就是如何修改參數(shù)或是變量值。
在樣例項目中,整個 CI 的 JOB 均是運行在同一臺 ubuntu-latest 的虛擬機上。所以可以通過設(shè)置系統(tǒng)的環(huán)境變量來設(shè)置相關(guān)參數(shù)。
如樣例中的第一步:
echo?::set-env?name=BUILDTIME::$(date?+%FT%T%z)
通過這個命令來設(shè)置環(huán)境變量。
這個命令的發(fā)現(xiàn)過程還挺有意思的。因為需要使用 Docker 發(fā)布,所以就找到了 actions-hub/docker 操作。
發(fā)現(xiàn)其中的參數(shù) ${IMAGE_TAG} 并非 GitHub Actions 默認(rèn)提供,所以就看了一下actions-hub/docker/login的實現(xiàn)代碼。
其中就有這樣一段代碼,
echo ::set-env name=IMAGE_TAG::${IMAGE_TAG}
echo ::set-env name=IMAGE_NAME::${IMAGE_NAME}
所以,也就依葫蘆畫瓢借來做自己的環(huán)境變量定義使用了。
除了自定義的環(huán)境變量以外,Github Actions 本身提供默認(rèn)變量可以參考該文檔using environment variables.
對于一些密鑰類的設(shè)置,則可以通過在項目中增加對應(yīng)密鑰變量的方式進行設(shè)置。官方參考文檔:creating-and-storing-encrypted-secrets.
樣例模板中
-?name:?Login?to?docker?hub
????????uses:?actions-hub/docker/login@master
????????env:
??????????DOCKER_USERNAME:?${{?secrets.DOCKER_USERNAME?}}
??????????DOCKER_PASSWORD:?${{?secrets.DOCKER_PASSWORD?}}
變量值DOCKER_USERNAME, DOCKER_USERNAME就來源于密鑰設(shè)置。這里要注意的是,在 step 中通過 env 定義的變量只能在該 step 中使用,不可以全局使用。
有了以上相關(guān)變量設(shè)置的知識,一些基本的 GitHub Actions 問題都可以迎刃而解了。
小結(jié)
樣例模板實現(xiàn)了每次master主分支推送以及tag推送觸發(fā)容器鏡像的構(gòu)建,需要的同學(xué)可以 clone 該樣例項目自行測試。
項目地址:https://github.com/liujianping/golang-github-action。
推薦閱讀
站長 polarisxu
自己的原創(chuàng)文章
不限于 Go 技術(shù)
職場和創(chuàng)業(yè)經(jīng)驗
Go語言中文網(wǎng)
每天為你
分享 Go 知識
Go愛好者值得關(guān)注
