私有化仓库的 GO 模块使用实践
本文以又拍云團(tuán)隊(duì)私有化模塊處理的實(shí)踐案例為基礎(chǔ),介紹如何使用私有化模塊,以及 go get 工具背后的細(xì)節(jié),其中包括如何讓 go 正確的源獲私有化 gitlab 上源代碼以及認(rèn)證等問題。文章根據(jù)又拍云資深開發(fā)工程師劉云鵬在 Open Talk 公開課直播分享進(jìn)行整理,回放視頻請(qǐng)下拉文末點(diǎn)擊“閱讀原文”。
關(guān)于 Open Talk:由又拍云發(fā)起的綜合性技術(shù)沙龍,秉承又拍云“讓創(chuàng)業(yè)更簡單”的初衷,以全干貨的形式為技術(shù)開發(fā)者提供包括技術(shù)、運(yùn)維、產(chǎn)品、創(chuàng)業(yè)等多維度的知識(shí)分享,幫助企業(yè)成員提升專業(yè)技能,推動(dòng)企業(yè)更好更快地發(fā)展。
研發(fā)背景
GO 在 1.11 版本開始引入 Module 的特性;1.13 版本引入 Module 校驗(yàn)和檢查,加強(qiáng)了 Module 的安全性;現(xiàn)在的 1.16 版本已經(jīng)默認(rèn)使用 Module 模式。日前 GO 團(tuán)隊(duì)在博客上表明,將在 1.17 版本時(shí)刪除對(duì) GOPAHT 的支持,如果現(xiàn)在還沒有使用 GO MODULE,趕緊抓緊時(shí)間試試 GOMDULE 吧。
GOMODULE 和 GOPATH 的主要區(qū)別在于私有化模塊的使用。公有化模塊使用是相同的,都是通過 go get 直接獲取模塊。對(duì)于私有化模塊 GOPAHT 可以直接將模塊代碼丟在 GOPAHT 目錄下,而 GO Module 不行,它有自己的代碼管理方式,下面我們簡單介紹下。
GO 如何獲取 Module
GO 獲取模塊通常是使用 go get 工具獲取模塊,當(dāng)前 go get 支持兩種方式:
第一種是通過傳統(tǒng)的 VCS 去代碼托管平臺(tái)上拉取代碼,以 git 為主,還支持 svn、hg、等其他平臺(tái)。
第二種是通過 1.12 版本開始支持的 GOPROXY 協(xié)議,go 在 GOPROXY 服務(wù)器上獲取代碼歸檔文件。
從 1.13 起 GO 還使用校驗(yàn)和檢查—— GO SUM ,所有模塊下載后都會(huì)檢查其校驗(yàn)和。它會(huì)將下載模塊的哈希值與 Google 線上數(shù)據(jù)庫中的哈希值進(jìn)行比對(duì),防止模塊被篡改,只有驗(yàn)證通過后的模塊才能正常安裝使用。
VCS 獲取模塊的方式
GO 支持很多的版本管理工具。首先需要判斷使用什么版本管理工具去獲取模塊。判斷方式大致分成三類,不依賴其他的兩種靜態(tài)匹配方式和一種動(dòng)態(tài)匹配方式。
靜態(tài)匹配方式
**前綴匹配:**比如 github 、谷歌的 bitbuket 和 apache、openstack 等代碼托管平臺(tái),會(huì)內(nèi)制在 go get 的工具鏈中,會(huì)去判斷模塊的前綴當(dāng)前綴匹配上則使用對(duì)應(yīng)的版本管理工具。圖中左方的一例子,github.com/eamaple/pkg 模塊會(huì)匹配前綴,并與 github 相匹配,同時(shí)能知道 github 使用 git 工具。
**正則匹配:**正則的方式是給模塊加上后綴,后綴名可以是前文介紹的五種版本管理工具( git,svn ,hg ,bzr,fossil )之一的后綴。后綴的匹配是通過正則表達(dá)式實(shí)現(xiàn)的。上圖中兩個(gè)例子都是以 .git 作為后綴,通過正則表達(dá)式的匹配會(huì)得到里面的子分組,即 VCS 子分組會(huì)匹配到模塊是使用 git 進(jìn)行管理的。
動(dòng)態(tài)匹配方式
當(dāng)前綴和正則表達(dá)式都匹配不上,則會(huì)采用動(dòng)態(tài)判斷的方式。go get 會(huì)發(fā)送一個(gè) HTTP 請(qǐng)求,URL為模塊帶上協(xié)議頭和參數(shù)( go-get=1 )。go get 期待服務(wù)器返回模塊相應(yīng)信息來幫助go get 進(jìn)一步的操作。GO 默認(rèn)會(huì)發(fā)送 HTTPS 請(qǐng)求,如果服務(wù)器想用 HTTP 協(xié)議,可以通過環(huán)境變量 GOINSECURE 來處理,當(dāng) GOINSECURE 為 1 時(shí),GO 就會(huì)使用 HTTP 協(xié)議。
Go get 預(yù)期的返回體是一個(gè) HTML 文檔,其中對(duì) GO 有意義的是要帶 name=“go-import” 屬性的 meta 標(biāo)簽。該 meta 標(biāo)簽會(huì)通過 content 屬性告訴 GO 怎么去獲取模塊。
content 的內(nèi)容有三部分:第一部分 root-path,指模塊的名字;第二部分 vcs 代表需要使用的管理工具,比如說 git、svn。;第三部分 repo-url 指的是模塊原代碼存放在哪個(gè)倉庫下面,該倉庫就需要是協(xié)議加倉庫地址的形式。
上圖以 GO 的子包為例,通過 curl 模擬發(fā)送 go get 請(qǐng)求,golang.org/x/net 服務(wù)器返回了一個(gè) html 文檔,文檔有用的是紅圈框起來的部分,里面是 meta 標(biāo)記,content 第一部分是 GO 模塊名稱 golang.org/x/net ;第二部分是 git,代表需要使用 git 來獲取原碼;第三部分是模塊托管的地址,表示托管在模塊包的地址 googlesource.com/net 上。需要注意 meta 標(biāo)記只能放在 head 里面,go get 解析會(huì)從頭開始,當(dāng)遇到 head 的結(jié)束標(biāo)簽或者 body 的開始標(biāo)簽時(shí)停止解析。
GIT 在 GO GET 中的應(yīng)用
git 支持 HTTP 協(xié)議和 SSH 協(xié)議,GO調(diào)用 git 時(shí)默認(rèn)只使用 HTTP 協(xié)議,調(diào)用過程中會(huì)禁用 git 的交互過程。例如 git 使用 HTTP 協(xié)議去克隆私有倉庫需要輸入用戶名和密碼,但是 GO 調(diào)用 git 時(shí)不能通過交互輸入用戶名和密碼會(huì)導(dǎo)致獲取模塊失敗。交互是通過環(huán)境變量 GIT_TERMINAL_PROMPT 控制,如果手動(dòng)將變量強(qiáng)行更改為 1,就可以啟用交互從而手動(dòng)輸入用戶名和密碼。
那么該怎樣將用戶名和密碼無感知的傳遞給 git 呢?事實(shí)上在 git 里,如果是使用 HTTP 協(xié)議都可以通過 netrc 文件來傳遞用戶名和密碼,該文件在 HOME 目錄下,有兩種文件格式:
-
第一種:通過服務(wù)器名和用戶名密碼的方式去定義服務(wù)器的用戶名和密碼;
-
第二種:不指定服務(wù)器,把所有的服務(wù)器都指定相同的用戶名和密碼。
如上圖所示,第一條中配置了 gitlab.com ,用戶名為 root,密碼是 admin。通過 git 去克隆 gitlab 的私有倉庫時(shí),可以把用戶名 root 和 admin 傳遞給 git,讓 git 無感知獲取到用戶名和密碼,從而就不會(huì)再要求輸入密碼了。第二條中通過 default 給所有的服務(wù)器都設(shè)置默認(rèn)的用戶名和密碼,設(shè)置的用戶名為 guest,密碼是 123456,表示除了 gitlab.com 之外的所有服務(wù)器需要認(rèn)證時(shí),都會(huì)把將 guest 和 123456 作為用戶名和密碼傳遞給需要的程序。
go 調(diào)用 git 時(shí)也支持 SSH 協(xié)議,但默認(rèn)不會(huì)使用。只有在動(dòng)態(tài)獲取的時(shí)候顯示指定,才可以使用 SSH 協(xié)議。如果通過靜態(tài)匹配方式(前綴匹配或正則匹配),能匹配上使用的模塊信息,都只能使用 HTTPS 協(xié)議。
上圖中的模塊是 example.com/pkg,倉庫地址是 gitlab.example.com/example/pkg。meta 標(biāo)記的content 里包含了完整的模塊信息,首先 第一部分是模塊的名字,這和前面 module 的名字定義是相同的;緊接著是 git,代表使用 git 去獲取代碼,最后一部分是倉庫地址這里就顯示指定了 SSH 協(xié)議,同時(shí)還有 git 的用戶名和服務(wù)器 SSH 服務(wù)端口號(hào)。
Git ssh 認(rèn)證是基于密鑰對(duì)實(shí)現(xiàn)的,如果沒有密鑰對(duì),可以通過 SSH 工具套件 ssh-keygen 生成密鑰。上圖中列舉了常用的參數(shù) -t,該參數(shù)可以指定密鑰的類型。其中 RSA 密鑰可能是最常用的,而本人比較喜歡使用 ED25519,它有個(gè)明顯的優(yōu)點(diǎn)就是密鑰長度非常短,公鑰和私鑰都只有 32 字節(jié),安全性也可以和 RSA 密鑰 3000 位左右的相媲美,能夠保證安全性,密鑰長度又短,因此會(huì)經(jīng)常使用 ED25519 作為密鑰。
當(dāng)生成秘鑰對(duì)之后,會(huì)在 HOME 下的 .ssh 文件夾中生成密鑰隊(duì)的文件碼,包含私鑰和公鑰。".pub" j結(jié)尾的文件是公鑰文件,需要把公鑰文件配置在 gitlab 或 github 等代碼托管平臺(tái)上。右邊是 gitlab 的截圖,圖中使用的密鑰就是 ED25519 格式的密鑰,可以看到長度真的非常短。
GOPROXY 獲取模塊
GO 支持通過 GOPROXY 協(xié)議獲取 GO 模塊。模塊是基于 HTTP 協(xié)議的,只會(huì)使用 HTTP 的 get 請(qǐng)求,并且使用標(biāo)準(zhǔn)的 HTTP 狀態(tài)碼進(jìn)行調(diào)用。當(dāng)使用的公共 GOPROXY 協(xié)議,其 GOPROXY 代理服務(wù)器默認(rèn)都是沒有用戶名和密碼的。但實(shí)際上如果需要搭建私有的,是可以支持 HTTP 基礎(chǔ)授權(quán),方式與前面一樣,通過 .netrc 文件去配置用戶名和密碼。另外 GOPROXY 還有兩點(diǎn)特性:
-
第一:比起使用 VCS 方式直接去克隆,GOPROXY 獲取模塊的速度會(huì)更快,原因后面會(huì)詳細(xì)說明。
-
第二:可以解決模塊不能訪問的問題,比如 Golang 域名訪問不了等問題,通過第三方搭建好的代理服務(wù)器即可訪問下載到這些模塊。
GOPROXY 使用
GOPROXY 的配置是通過 GOPROXY 環(huán)境變量來控制,配置的是代理服務(wù)器URL。代理服務(wù)器 URL 可以配置多個(gè),通過逗號(hào)和管道符來進(jìn)行分割,管道符和逗號(hào)的區(qū)別后面會(huì)舉例講解。
通過固定的字符串 off 和 direct 可以代替 URL。off 禁止從任何來源去下載模塊,把 GOPROXY 設(shè)置為 off 會(huì)禁止下載模塊,只能使用本地模塊,無論從 gitlab、github或其他地方的模塊都不能下載。direct 代表直接從VCS上拉取,一般會(huì)作為備選方案。
圖中展示了兩個(gè)例子:
-
第一個(gè)是 Linux 環(huán)境變量的語法,通過 export 來設(shè)置環(huán)境變量。前面配置了proxy.golang.org,這是 google 官方的 goproxy 的服務(wù)器,逗號(hào)之后指定了備選方案 direct。在 GOPROXY 服務(wù)器返回403和410狀態(tài)碼時(shí),表示找不到模塊。以逗號(hào)為分隔指定備選方案時(shí)只有當(dāng)服務(wù)器返回了403或410狀態(tài)碼時(shí),go get 會(huì)嘗試使用備選方案,這里是從版本管理平臺(tái)上去下載代碼。
-
第二個(gè)使用了另一種語法配置,go env -w 語法是 GO 自帶的,GO1.13版本開始支持。它是可以跨平臺(tái)使用的,通過這種語法,沒有操作系統(tǒng)的差異,在 windows、Linux、max 上面,都可以通過該方式去配置 GO 相關(guān)的環(huán)境變量。示例中設(shè)置成了國內(nèi)常用的 proxy 的地址:goproxy.cn。這里使用了管道符指定備選方案,管道符的意義是無論代理服務(wù)器返回了什么錯(cuò)誤,即便不是 HTTP 的錯(cuò)誤,如 GOproxy 服務(wù)器掛了返回500的錯(cuò)誤,或者網(wǎng)絡(luò)錯(cuò)誤。都會(huì)嘗試使用備選方案去下載模塊。
GOPROXY 實(shí)現(xiàn)
GOPROXY 的實(shí)現(xiàn)很簡單,官方定義只有五個(gè)接口。
URL 中的三個(gè)變量意義如下:
-
base 代表是 GOPROXY 服務(wù)器的 URL 地址;
-
module 表示需要需獲取模塊的名字;
-
version 是模塊的版本。
大小寫編碼問題
在 HTTP 的 URL 定義上是不區(qū)分大小寫的,當(dāng) module 或 version 出現(xiàn)大寫字母時(shí),在某些系統(tǒng)中可能會(huì)出現(xiàn)混淆的問題。為了避免此問題,需要進(jìn)行大小寫的編碼,把大寫字母轉(zhuǎn)換成感嘆號(hào)加小寫字母的編碼。
-
第一個(gè)接口是獲取所有版本列表;
-
第二個(gè)接口是獲取指定版本的信息;
-
第三個(gè)接口是獲取指定模塊,指定版本的 mod 文件;
-
第四個(gè)接口是獲取模塊的最新版本。這是可選的接口,不提供與實(shí)現(xiàn)該接口,GOPROXY 仍然可以正常工作;
-
最后一個(gè)接口是下載模塊指定版本的 zip 文件。
上圖是 list 接口示例, proxy.golang.org 是代理服務(wù)器的地址, golang.org/x/text 是要獲取的模塊名字,@v 為固定的字符串,list 是要調(diào)用的 list 接口。可以看到該接口返回了 text 包的所有版本,圖中 GO 獲取了所有版本后可以通過版本語義推斷出模塊的最新版本。
如上圖所示, INFO 接口和 LATEST 接口返回的內(nèi)容是一樣的。 Version:固定版本字符串的版本號(hào), Time 是 fc3339 時(shí)間格式的字符串,為可選項(xiàng),代表版本的提交時(shí)間。
最后是 MOD 和 ZIP 接口。MOD 接口就是返回指定版本的 mod 文件,上圖示例中獲取了最新版本的 mod 文件, text 包只依賴了 tools 模塊。ZIP 文件接口就是獲取模塊指定版本的 ZIP 文件,當(dāng)它把版本的所有原文件打包成 ZIP 文件,go get 最終通過接口去下載的就是這個(gè)版本的模塊。
前面提到通過 GOPROXY 去獲取源代碼會(huì)比通過 VCS 獲取要更快,通過 zip 去下載只會(huì)下載當(dāng)前版本的所有文件不會(huì)包含歷史的版本信息,如果是通過 VCS 比如 git 去克隆倉庫,就會(huì)獲取所有的歷史版本信息;因此通過 GOPROXY zip 接口獲取文件的體積會(huì)更小,下載也會(huì)更快,需要注意的是 GOPROXY 定義了模塊 zip 文件的大小和其所有文件的未壓縮總限制為 500 MiB,go.mod 文件和 LICENSE 文件大小限制為 16 MiB。
module 驗(yàn)證
Go1.13 版本開始加入模塊 SUM 驗(yàn)證機(jī)制,默認(rèn)所有 go 模塊下載后都會(huì)驗(yàn)證其 hash 是否與線上( 默認(rèn):sum.golang.org 國內(nèi):sum.golang.google.cn)記錄的一致。
驗(yàn)證的過程可以通過環(huán)境變量 GONOSUMDB 和 GOSUMDB 來控制:首先來看 GOSUMDB 的配置,它指定了需要使用的線上數(shù)據(jù)庫地址。因?yàn)槟J(rèn)使用的 sum.golang.org 在國內(nèi)無法訪問,上圖中配置使用的是 google 搭建的國內(nèi)鏡像,還可以配置為 off,代表禁用校驗(yàn),即下載模塊不進(jìn)行哈希值的校驗(yàn),徹底拋棄這個(gè)過程。使用中我不建議這樣做,可以使用 GONOSUMDB 的環(huán)境變量去配置不需要驗(yàn)證的模塊,比如私有模塊肯定是不能通過驗(yàn)證的。GONOSUMDE 是通過前綴匹配的方式運(yùn)行的。圖中配置了 gitlab.com,那么所有以 gitlab.com 開頭的包都不會(huì)進(jìn)行 GO 的校驗(yàn)和檢查。
下面來梳理下常見的變量:
-
GONOPROXY,基于前綴的匹配方式運(yùn)行,上圖中指定了gitlab.com,也就是所有 gitlab.com 上的代碼,不從 GOPROXY 服務(wù)器上去獲取,全部通過傳統(tǒng) VCS 方式,直接去原代碼服務(wù)器上拉取;
-
GONOSUMDB,可以讓前綴匹配上的模塊跳過安全性檢查;
-
GOPRIVATE,相當(dāng)于前面兩個(gè)環(huán)境變量的集合,配置了 GOPRIVATE 就相當(dāng)于把前面的兩個(gè)環(huán)境變量一起配置了;
-
GOVCS ,這是 GO1.16 版本才添加的,其主要作用是指定哪些模塊使用哪些 VCS。
又拍云的業(yè)務(wù)實(shí)踐
私有包的使用
下面介紹一下如何使用私有模塊。一般公司內(nèi)使用較多的是私有化搭建的 gitlab 服務(wù),gitlab 本身是支持響應(yīng) go get 的 HTTP 請(qǐng)求。通過 go get 獲取包時(shí),客戶端會(huì)發(fā)送 HTTP 請(qǐng)求到 gitlab 服務(wù)器上,服務(wù)器收到請(qǐng)求后會(huì)返回響應(yīng)中包含 meta 標(biāo)記。該標(biāo)記會(huì)告訴客戶端,模塊使用 git 通過 HTTP 協(xié)議獲取原代碼。gitlab 默認(rèn)使用 HTTPS 協(xié)議。客戶端收到 gitlab 服務(wù)器響應(yīng)結(jié)果后,能正確的使用 git 去拉取模塊的源代碼。模塊下載通過后,同樣會(huì)有校驗(yàn)和檢查的過程,可以在 GOPRIVATE 變量加上 gitlab.com,告知 go gitlabc.com 相關(guān)的模塊都是私有模塊跳過校驗(yàn)和檢查。
在又拍云內(nèi)部實(shí)踐中,情況有些不同,又拍云內(nèi)部所有使用的 HTTP 服務(wù)都需要經(jīng)過 google 的二次驗(yàn)證。所有發(fā)往內(nèi)部 gitlab 服務(wù)器的請(qǐng)求都會(huì)預(yù)先檢查是否有 google 授權(quán)的 head,如果沒有會(huì)被直接攔截掉并返回403錯(cuò)誤。這樣會(huì)導(dǎo)致所有的簡單 HTTP 請(qǐng)求都不能到達(dá) gitlab 服務(wù)器直接被攔截。go 發(fā)送的 HTTP 請(qǐng)求同樣也會(huì)被攔截掉,將導(dǎo)致 go 不能正確的獲取模塊信息。這時(shí)雖然可以直接通 ssh 協(xié)議 clone 服務(wù)器上原代碼,但由于 go get 沒有這些信息,導(dǎo)致請(qǐng)求失敗。因此下圖中灰線表示的請(qǐng)求實(shí)際上是發(fā)不出來的。
那么該如何解決呢?方法是采用額外的 http 服務(wù)來處理 go get 的 HTTP 請(qǐng)求。額外 HTTP 服務(wù)沒有驗(yàn)證過程,請(qǐng)求通過后會(huì) go get能正確的獲取到需要的 meta 信息。meta 中必須指定使用 ssh 協(xié)議,因?yàn)?gitlab http 服務(wù)有二次認(rèn)證,沒有認(rèn)證的請(qǐng)求都不能通過,因此只能使用 ssh 協(xié)議。權(quán)限認(rèn)證可以由 SSH 密鑰對(duì)完成,進(jìn)行無感知進(jìn)行授權(quán)。go get 引導(dǎo) http 服務(wù)不會(huì)管理授權(quán)相關(guān)問題,所有的授權(quán)處理都交給 gitlab。作為私有模塊,如果沒有對(duì)應(yīng)的響應(yīng)程序,授權(quán)認(rèn)證都交給 gitlab 處理。
go get 請(qǐng)求指引
采用額外的服務(wù)去引導(dǎo) go get 是怎么做的呢?這需要對(duì)模塊包的命名進(jìn)行修改,需要基于 gitlab 命名的規(guī)則修改。
gitlab.com/lyp256/pkg
域名 倉庫名
一個(gè)完整的模塊有幾部分組成,首先是域名 gitlab.com,lyp256 是所有者,pkg 是模塊的項(xiàng)目名字。對(duì)于單個(gè) gitlab平臺(tái)重要的是后面兩段,也就是指定這個(gè)模塊所有者和項(xiàng)目名,域名肯定是固定的可以忽略。
基于這樣的規(guī)則我實(shí)現(xiàn)了一個(gè)簡單的小服務(wù),來解決 go get http 請(qǐng)求的處理。代碼如下:
Gitlab CI 實(shí)踐
Gitlab CI 時(shí)會(huì)起一個(gè)空的容器,圖中示例使用的是 golang alpine 的鏡像。這個(gè)鏡像里除了 golang 沒有其他的東西。我們需要安裝相關(guān)依賴和注入 SSH 認(rèn)證相關(guān)內(nèi)容。script 中定義如下:
第一步: 使用 mikdir -p,在 cache 下創(chuàng)建目錄,這個(gè)目錄是我們 CI 機(jī)器上的緩存掛載的是物理盤上的一塊空間可以保留數(shù)據(jù),用來緩存 go mod 減少模塊下載。
第二步:安裝基礎(chǔ)環(huán)境、工具軟件包等。圖中示例安裝了 git 和 g++,g++ 是 go 編譯所需要的依賴,openssh 是 ssh 的工具鏈 git 需要用到。
第三步:處理 SSH 秘鑰。這里有兩步,信任 gitlab 服務(wù)器秘鑰和導(dǎo)入認(rèn)證私鑰。私鑰是通過環(huán)境變量 $DEPLOY_SSH_KEY 導(dǎo)入,只需要保留該環(huán)境變量中的內(nèi)容到對(duì)應(yīng)的秘鑰文件就可以了。gitlab 服務(wù)器秘鑰使用 ssh-keyscan 來獲取并保存到 known_hosts 文件。通過 gitlab SI 的配置把能訪問 git 項(xiàng)目的私鑰放在環(huán)境變量 $DEPLOY_SSH_KEY 里面,把私鑰放在相應(yīng)的 ssh 私鑰文件并且授予正確的權(quán)限。
最后還需要配置 GOPRIVATE 變量定義所有 go.holdcloud.com 相關(guān)的模塊為 PRIVATE 模塊不要使用代理和檢驗(yàn)和檢查。
到此已基本完成所有準(zhǔn)備工作,后面的 go test 是正常的 ci 測驗(yàn)邏輯,可以根據(jù)實(shí)際情況來寫。
總結(jié)
-
GO 在 1.17 版本會(huì)刪除對(duì) GOPATH 的支持,建議盡快遷移至 GOMDULE;
-
GO 的校驗(yàn)和檢查可以感知到代碼的變化提高安全與可用性,建議不要關(guān)閉;
-
建議保留 vendor ,防止依賴模塊被刪除。
總結(jié)
以上是生活随笔為你收集整理的私有化仓库的 GO 模块使用实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开箱即用的微服务框架 Go-zero(进
- 下一篇: Ansible 快速入门