云原生小技巧 : 如何自動(dòng)化發(fā)布 CLI 工具?
在云原生時(shí)代,CLI 工具已成為開發(fā)者日常工作中不可或缺的一部分。然而,將開發(fā)好的 CLI 工具分享給大家使用,如果僅依賴手動(dòng)發(fā)布,不僅效率低,且易出錯(cuò),特別是在處理多架構(gòu)和多平臺兼容性時(shí)尤為明顯。
那么,我們?nèi)绾尾拍軐?shí)現(xiàn) CLI 工具的自動(dòng)化發(fā)布呢?本文旨在探討這一問題,并提出一套實(shí)用的解決方案。
在接下來的分享中,我將主要以 Golang 舉例。需要指出的是,我們將討論的自動(dòng)化構(gòu)建和發(fā)布的原則是通用的,適用于所有編程語言。因此,無論大家使用哪種語言編寫工具,這些實(shí)踐都將具有重要的參考價(jià)值。
編寫構(gòu)建腳本
在自動(dòng)化構(gòu)建的世界中,編寫一個(gè)穩(wěn)定且跨平臺兼容的構(gòu)建腳本是關(guān)鍵。Golang 提供了強(qiáng)大的跨平臺構(gòu)建能力,而 go build 命令是實(shí)現(xiàn)這一目標(biāo)的核心。例如:
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o fooctl-darwin-amd64 -v
這個(gè)命令示例突顯了 Golang 在生成特定平臺和架構(gòu)二進(jìn)制文件方面的靈活性。在構(gòu)建腳本中,我們需要進(jìn)一步擴(kuò)展這種靈活性,以支持多平臺構(gòu)建需求。
- 參數(shù)化和默認(rèn)值設(shè)置:
OUTPUT_DIR 和 BINARY_NAME 的設(shè)定允許用戶自定義輸出目錄和二進(jìn)制文件的名稱,加強(qiáng)了腳本的通用性。
BUILDPATH 變量用于指定構(gòu)建路徑,是腳本運(yùn)行的必需參數(shù),保證了構(gòu)建過程的穩(wěn)定性。
- 跨平臺和架構(gòu)支持:
- 通過設(shè)置 BUILD_GOOS 和 BUILD_GOARCH 變量,腳本能夠靈活地處理不同操作系統(tǒng)和架構(gòu)的構(gòu)建需求,增加了適用性。
- 這些變量的默認(rèn)值通過 go env 獲取,但也可以通過參數(shù)覆蓋,提供了靈活性。
- 動(dòng)態(tài)輸出路徑:
- OUT 變量根據(jù)是否為發(fā)布版(IS_RELEASE),動(dòng)態(tài)調(diào)整輸出文件的命名和路徑。這樣的設(shè)計(jì)使得腳本能夠根據(jù)不同的使用場景(如開發(fā)測試或正式發(fā)布)輸出不同格式的文件名。
- 特殊情況處理:
- 對 Windows 平臺的特殊處理(.exe 擴(kuò)展名)是必要的,因?yàn)?Windows 系統(tǒng)下的可執(zhí)行文件通常需要這個(gè)擴(kuò)展名。
gobuild.sh 腳本
下面的 gobuild.sh 腳本是對上述原則的實(shí)踐,將跨平臺構(gòu)建的復(fù)雜性轉(zhuǎn)化為簡單的命令行操作:
OUTPUT_DIR=${4:-"bin"}
BINARY_NAME=$(basename ${1})
BUILDPATH=./${1:?"path to build"}
BUILD_GOOS=${GOOS:-$(go env GOOS)}
BUILD_GOARCH=${GOARCH:-$(go env GOARCH)}
GOBINARY=${GOBINARY:-go}
LDFLAGS=$(version::ldflags)
if [ $# -ge 2 ] && [ -n $2 ]; then
BUILD_GOOS=$2
fi
if [ $# -ge 3 ] && [ -n $3 ]; then
BUILD_GOARCH=$3
fi
OUT=${OUTPUT_DIR}/${1:?"output path"}
if [ "${IS_RELEASE:-0}" == "1" ]; then
OUT="${OUTPUT_DIR}/${BINARY_NAME}-${BUILD_GOOS}-${BUILD_GOARCH}"
if [ "${BUILD_GOOS}" == "windows" ]; then
OUT="${OUTPUT_DIR}/${BINARY_NAME}-${BUILD_GOOS}-${BUILD_GOARCH}.exe"
fi
fi
CGO_ENABLED=0 GOOS=${BUILD_GOOS} GOARCH=${BUILD_GOARCH}${GOBINARY} build \
-ldflags="${LDFLAGS}" \
-o "${OUT}" \
"${BUILDPATH}"
這個(gè)腳本不僅適應(yīng)了多平臺和多架構(gòu)的需要,還提供了足夠的靈活性和可配置性,以適應(yīng)不同的構(gòu)建場景。
配合 Makefile 實(shí)現(xiàn)全自動(dòng)化構(gòu)建
進(jìn)一步的,結(jié)合 Makefile 可以將構(gòu)建過程自動(dòng)化,提升效率:
.PHONY: build-binaries
BUILD_SCRIPT_PATH := ./hack/gobuild/gobuild.sh
# 列出了需要構(gòu)建的所有二進(jìn)制文件,可管理多個(gè)項(xiàng)目的構(gòu)建過程
BINARIES := cmd/fooctl cmd/barctl
# 通過 ALLPLATFORMS 變量,我們定義了一系列目標(biāo)平臺和架構(gòu)組合
ALLPLATFORMS := linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64
# 構(gòu)建所有組合
build-binaries: $(foreach bin,$(BINARIES),$(foreach plat,$(ALLPLATFORMS),build-$(bin)-$(plat)))
# 構(gòu)建規(guī)則模板
# 這個(gè)模板可以生成特定于每個(gè)二進(jìn)制文件和平臺組合的構(gòu)建規(guī)則。
define BUILD_template
build-$(1)-$(2):
IS_RELEASE=1 $$(BUILD_SCRIPT_PATH) $(1) $$(subst /, ,$$(word 1,$$(subst -, ,$(2)))) $$(subst /, ,$$(word 2,$$(subst -, ,$(2))))
endef
# 生成構(gòu)建規(guī)則
# 我們自動(dòng)為每個(gè)二進(jìn)制文件和平臺組合生成了具體的構(gòu)建規(guī)則。
$(foreach bin,$(BINARIES),$(foreach plat,$(ALLPLATFORMS),$(eval $(call BUILD_template,$(bin),$(plat)))))
通過這個(gè) Makefile,即使同時(shí)構(gòu)建 fooctl 和 barctl 這兩個(gè) CLI Tool 也變得異常簡單。一條簡單的命令 make build-binaries 就能觸發(fā)整個(gè)構(gòu)建流程,大大減少了人工干預(yù),確保了構(gòu)建過程的一致性和可靠性。
小結(jié)
通過上述詳細(xì)的構(gòu)建腳本和 Makefile 配置,我們可以看到,現(xiàn)代軟件開發(fā)中自動(dòng)化構(gòu)建的強(qiáng)大功能和必要性。這種方法不僅提升了構(gòu)建效率,也增強(qiáng)了軟件的質(zhì)量和穩(wěn)定性。在云原生時(shí)代,自動(dòng)化構(gòu)建已成為提高開發(fā)團(tuán)隊(duì)效率和產(chǎn)品可靠性的關(guān)鍵策略。
Release CLI tool on GitLab CI/CD
在構(gòu)建腳本準(zhǔn)備完畢后,接下來我們就可以將其集成到 CI 系統(tǒng)了,下面我以 GitLab CI/CD 為例。
在 GitLab CI/CD 的核心,是一系列定義明確的作業(yè)(Jobs),它們在代碼提交時(shí)自動(dòng)執(zhí)行。對于完整的持續(xù)集成來說,這些作業(yè)通常包括構(gòu)建(build)、測試(test)、代碼審查(lint)等步驟。但在本文中,我們將重點(diǎn)關(guān)注自動(dòng)發(fā)布流程。
觸發(fā)自動(dòng)發(fā)布的條件
自動(dòng)發(fā)布流程是基于 Git 標(biāo)簽創(chuàng)建的。當(dāng)開發(fā)者推送一個(gè)新標(biāo)簽到倉庫時(shí),GitLab CI/CD 會(huì)捕捉到這一事件,并啟動(dòng)預(yù)定義的發(fā)布流程。
rules:
- if: $CI_COMMIT_TAG
這個(gè)條件確保只有在創(chuàng)建新標(biāo)簽時(shí),才會(huì)啟動(dòng)后續(xù)的構(gòu)建、上傳和發(fā)布作業(yè)。
Release Jobs
步驟一:構(gòu)建二進(jìn)制文件,在 build-binaries 階段,CI 會(huì)構(gòu)建針對不同平臺和架構(gòu)的 CLI 工具二進(jìn)制文件,確保構(gòu)建過程的一致性和可重復(fù)性。
步驟二:上傳構(gòu)建產(chǎn)物,待構(gòu)建完成后,upload 階段負(fù)責(zé)將二進(jìn)制文件上傳到 GitLab 的包管理器或其他存儲(chǔ)位置。這為后續(xù)的發(fā)布提供了必要的資源。
步驟三: 發(fā)布到 GitLab,最后,在 release 階段,CI 使用 release-cli 工具自動(dòng)創(chuàng)建發(fā)布,并將構(gòu)建的二進(jìn)制文件作為發(fā)布的資產(chǎn)。
Create releases from .gitlab-ci.yml
下面的 .gitlab-ci.yml 腳本是對上述發(fā)布流程的實(shí)踐:
stages:
...
- build-binaries
- upload
- release
build-binaries:
stage: build-binaries
image: golang:1.21.1
rules:
- if: $CI_COMMIT_TAG
script:
- echo "Building binaries for all platforms and architectures..."
- make build-binaries
artifacts:
paths:
- bin
upload:
stage: upload
image: curlimages/curl:latest
rules:
- if: $CI_COMMIT_TAG
script:
- echo "Uploading binaries..."
- >
for binary in ./bin/*; do
curl --header "JOB-TOKEN: $CI_JOB_TOKEN" \
--upload-file $binary \
"${PACKAGE_REGISTRY_URL}/$(basename $binary)";
done
release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
rules:
- if: $CI_COMMIT_TAG
script:
- echo "Creating a release for $CI_COMMIT_TAG"
- |
ASSET_LINKS=""
for binary in ./bin/*; do
LINK="{\"name\":\"$(basename $binary)\", \"url\":\"${PACKAGE_REGISTRY_URL}/$(basename $binary)\"}"
ASSET_LINKS="${ASSET_LINKS},${LINK}"
done
ASSET_LINKS="[${ASSET_LINKS:1}]"
- >
release-cli create \
--name "Release $CI_COMMIT_TAG" \
--tag-name $CI_COMMIT_TAG \
--description "Created using the release-cli: $CI_COMMIT_REF_NAME-$CI_JOB_ID" \
--ref $CI_COMMIT_SHA \
--assets-link "$ASSET_LINKS"
以上示例將會(huì)構(gòu)建 CLI 工具二進(jìn)制文件,并將其上傳到 Gitlab Release 頁面。用戶從 Gitlab Release 頁面查找并下載適合其平臺的二進(jìn)制包即可。
圖片
有關(guān)詳細(xì)的 GitLab CI 流程,可以參考項(xiàng)目:https://gitlab.com/lqshow/clireleaseautomator
小結(jié)
這個(gè)流程大大簡化了 CLI 工具的發(fā)布過程,使得開發(fā)者能夠?qū)W⒂诖a開發(fā),而不是后續(xù)的構(gòu)建和發(fā)布環(huán)節(jié)。自動(dòng)化這些步驟意味著每次發(fā)布都是快速、一致且無誤的,從而提高了軟件的整體質(zhì)量和可靠性。
Release CLI tool use GoReleaser
不難發(fā)現(xiàn),上述整個(gè)流程相對來說還是比較繁瑣的,準(zhǔn)備腳本的過程也比較復(fù)雜,現(xiàn)在我將介紹一個(gè)讓這個(gè)過程不那么痛苦的工具 GoReleaser[1]。
它是一個(gè)變革性的工具,特別是對于以 Golang 編寫的項(xiàng)目。相比于傳統(tǒng)的手動(dòng)配置和腳本編寫,GoReleaser 提供了一種更高效和簡潔的自動(dòng)化發(fā)布方法。
GoReleaser 的優(yōu)勢
GoReleaser 的設(shè)計(jì)理念是“一次配置,處處運(yùn)行”,它通過一個(gè)單一的配置文件,即可控制整個(gè)發(fā)布流程。這個(gè)配置文件定義了如何構(gòu)建二進(jìn)制文件、如何打包它們、如何處理版本信息以及如何發(fā)布到各種平臺。具體來說,GoReleaser 的優(yōu)勢包括:
- 簡化的構(gòu)建過程:通過預(yù)定義的模板,GoReleaser 能夠自動(dòng)構(gòu)建針對不同平臺和架構(gòu)的二進(jìn)制文件,無需編寫復(fù)雜的腳本。
- 靈活的打包和發(fā)布:支持多種格式的打包選項(xiàng),以及與主要代碼托管平臺的無縫集成。
- 高度可配置:從構(gòu)建選項(xiàng)到發(fā)布設(shè)置,GoReleaser 允許高度定制化,以滿足不同項(xiàng)目的需求。
配置和使用 GoReleaser
使用 GoReleaser 的第一步是在項(xiàng)目的根目錄下創(chuàng)建 .goreleaser.yml 配置文件。通過 goreleaser init 命令可快速生成初始配置。這個(gè)文件涵蓋了構(gòu)建、打包和發(fā)布的全過程。
在配置好 .goreleaser.yml 之后,我們需要調(diào)整 .gitignore 加上 dist,因?yàn)?goreleaser 會(huì)默認(rèn)把編譯編譯好的文件輸出到 dist 目錄中。
接下來我們看個(gè)例子:
# .goreleaser.yml 示例
builds:
- id: fooctl
binary: fooctl
main: ./cmd/fooctl
ldflags:
- -s -w
- -X gitlab.com/lqshow/clireleaseautomator-with-goreleaser/version.gitVersinotallow={{.Version}}
- -X gitlab.com/lqshow/clireleaseautomator-with-goreleaser/version.gitCommit={{.ShortCommit}}
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm64
- id: barctl
binary: barctl
main: ./cmd/barctl
ldflags:
- -s -w
- -X gitlab.com/lqshow/clireleaseautomator-with-goreleaser/version.gitVersinotallow={{.Version}}
- -X gitlab.com/lqshow/clireleaseautomator-with-goreleaser/version.gitCommit={{.ShortCommit}}
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm64
這個(gè)簡單清晰的配置文件,事實(shí)上包含了我之前介紹的兩個(gè)模塊,相當(dāng)于省去了寫 shell 腳本和 Makefile 文件,使整個(gè)過程更加靈活和高效。
GitLab CI 中的 GoReleaser 集成
在 .gitlab-ci.yml 文件中,我們只需要定義一個(gè)簡單的 release 作業(yè),對了,別忘了還需要在 CI/CD 里配置一個(gè) GTILAB_TOKEN 變量:
stages:
- release
release:
stage: release
image:
name: goreleaser/goreleaser
entrypoint: ['']
only:
- tags
variables:
GIT_DEPTH: 0
GITLAB_TOKEN: $GITLAB_TOKEN
script:
- goreleaserrelease--clean
只要查看運(yùn)行日志,其實(shí)我們就會(huì)發(fā)現(xiàn),GoReleaser 自動(dòng)執(zhí)行后,它包含了構(gòu)建、上傳和發(fā)布的整個(gè)流程。
具體詳情:https://gitlab.com/lqshow/clireleaseautomator-with-goreleaser/-/jobs/5669211977