私有化倉庫的 GO 模塊使用實踐

本文以又拍雲團隊私有化模塊處理的實踐案例爲基礎,介紹如何使用私有化模塊,以及 go get 工具背後的細節,其中包括如何讓 go 正確的源獲私有化 gitlab 上源代碼以及認證等問題。文章根據又拍雲資深開發工程師劉雲鵬在 Open Talk 公開課直播分享進行整理,回放視頻請下拉文末點擊「閱讀原文」。html

關於 Open Talk:由又拍雲發起的綜合性技術沙龍,秉承又拍雲「讓創業更簡單」的初衷,以全乾貨的形式爲技術開發者提供包括技術、運維、產品、創業等多維度的知識分享,幫助企業成員提高專業技能,推進企業更好更快地發展。git

研發背景

GO 在 1.11 版本開始引入 Module 的特性;1.13 版本引入 Module 校驗和檢查,增強了 Module 的安全性;如今的 1.16 版本已經默認使用 Module 模式。日前 GO 團隊在博客上代表,將在 1.17 版本時刪除對 GOPAHT 的支持,若是如今尚未使用 GO MODULE,趕忙抓緊時間試試 GOMDULE 吧。github

GOMODULE 和 GOPATH 的主要區別在於私有化模塊的使用。公有化模塊使用是相同的,都是經過 go get 直接獲取模塊。對於私有化模塊 GOPAHT 能夠直接將模塊代碼丟在 GOPAHT 目錄下,而 GO Module 不行,它有本身的代碼管理方式,下面咱們簡單介紹下。golang

GO 如何獲取 Module

GO 獲取模塊一般是使用 go get 工具獲取模塊,當前 go get 支持兩種方式:正則表達式

第一種是經過傳統的 VCS 去代碼託管平臺上拉取代碼,以 git 爲主,還支持 svn、hg、等其餘平臺。數據庫

第二種是經過 1.12 版本開始支持的 GOPROXY 協議,go 在 GOPROXY 服務器上獲取代碼歸檔文件。apache

從 1.13 起 GO 還使用校驗和檢查—— GO SUM ,全部模塊下載後都會檢查其校驗和。它會將下載模塊的哈希值與 Google 線上數據庫中的哈希值進行比對,防止模塊被篡改,只有驗證經過後的模塊才能正常安裝使用。windows

VCS 獲取模塊的方式

GO 支持不少的版本管理工具。首先須要判斷使用什麼版本管理工具去獲取模塊。判斷方式大體分紅三類,不依賴其餘的兩種靜態匹配方式和一種動態匹配方式。緩存

靜態匹配方式安全

前綴匹配:好比 github 、谷歌的 bitbuket 和 apache、openstack 等代碼託管平臺,會內製在 go get 的工具鏈中,會去判斷模塊的前綴當前綴匹配上則使用對應的版本管理工具。圖中左方的一例子,github.com/eamaple/pkg 模塊會匹配前綴,並與 github 相匹配,同時能知道 github 使用 git 工具。

正則匹配:正則的方式是給模塊加上後綴,後綴名能夠是前文介紹的五種版本管理工具( git,svn ,hg ,bzr,fossil )之一的後綴。後綴的匹配是經過正則表達式實現的。上圖中兩個例子都是以 .git 做爲後綴,經過正則表達式的匹配會獲得裏面的子分組,即 VCS 子分組會匹配到模塊是使用 git 進行管理的。

動態匹配方式

當前綴和正則表達式都匹配不上,則會採用動態判斷的方式。go get 會發送一個 HTTP 請求,URL爲模塊帶上協議頭和參數( go-get=1 )。go get 期待服務器返回模塊相應信息來幫助go get 進一步的操做。GO 默認會發送 HTTPS 請求,若是服務器想用 HTTP 協議,能夠經過環境變量 GOINSECURE 來處理,當 GOINSECURE 爲 1 時,GO 就會使用 HTTP 協議。

Go get 預期的返回體是一個 HTML 文檔,其中對 GO 有意義的是要帶 name="go-import" 屬性的 meta 標籤。該 meta 標籤會經過 content 屬性告訴 GO 怎麼去獲取模塊。

content 的內容有三部分:第一部分 root-path,指模塊的名字;第二部分 vcs 表明須要使用的管理工具,好比說 git、svn。;第三部分 repo-url 指的是模塊原代碼存放在哪一個倉庫下面,該倉庫就須要是協議加倉庫地址的形式。

上圖以 GO 的子包爲例,經過 curl 模擬發送 go get 請求,golang.org/x/net 服務器返回了一個 html 文檔,文檔有用的是紅圈框起來的部分,裏面是 meta 標記,content 第一部分是 GO 模塊名稱 golang.org/x/net ;第二部分是 git,表明須要使用 git 來獲取原碼;第三部分是模塊託管的地址,表示託管在模塊包的地址 googlesource.com/net 上。須要注意 meta 標記只能放在 head 裏面,go get 解析會從頭開始,當遇到 head 的結束標籤或者 body 的開始標籤時中止解析。

GIT 在 GO GET 中的應用

git 支持 HTTP 協議和 SSH 協議,GO調用 git 時默認只使用 HTTP 協議,調用過程當中會禁用 git 的交互過程。例如 git 使用 HTTP 協議去克隆私有倉庫須要輸入用戶名和密碼,可是 GO 調用 git 時不能經過交互輸入用戶名和密碼會致使獲取模塊失敗。交互是經過環境變量 GIT_TERMINAL_PROMPT 控制,若是手動將變量強行更改成 1,就能夠啓用交互從而手動輸入用戶名和密碼。

那麼該怎樣將用戶名和密碼無感知的傳遞給 git 呢?事實上在 git 裏,若是是使用 HTTP 協議均可以經過 netrc 文件來傳遞用戶名和密碼,該文件在 HOME 目錄下,有兩種文件格式:

  • 第一種:經過服務器名和用戶名密碼的方式去定義服務器的用戶名和密碼;
  • 第二種:不指定服務器,把全部的服務器都指定相同的用戶名和密碼。

配置文件示例

如上圖所示,第一條中配置了 gitlab.com ,用戶名爲 root,密碼是 admin。經過 git 去克隆 gitlab 的私有倉庫時,能夠把用戶名 root 和 admin 傳遞給 git,讓 git 無感知獲取到用戶名和密碼,從而就不會再要求輸入密碼了。第二條中經過 default 給全部的服務器都設置默認的用戶名和密碼,設置的用戶名爲 guest,密碼是 123456,表示除了 gitlab.com 以外的全部服務器須要認證時,都會把將 guest 和 123456 做爲用戶名和密碼傳遞給須要的程序。

go 調用 git 時也支持 SSH 協議,但默認不會使用。只有在動態獲取的時候顯示指定,纔可使用 SSH 協議。若是經過靜態匹配方式(前綴匹配或正則匹配),能匹配上使用的模塊信息,都只能使用 HTTPS 協議。

上圖中的模塊是 example.com/pkg,倉庫地址是 gitlab.example.com/example/pkg。meta 標記的content 裏包含了完整的模塊信息,首先 第一部分是模塊的名字,這和前面 module 的名字定義是相同的;緊接着是 git,表明使用 git 去獲取代碼,最後一部分是倉庫地址這裏就顯示指定了 SSH 協議,同時還有 git 的用戶名和服務器 SSH 服務端口號。

Git ssh 認證是基於密鑰對實現的,若是沒有密鑰對,能夠經過 SSH 工具套件 ssh-keygen 生成密鑰。上圖中列舉了經常使用的參數 -t,該參數能夠指定密鑰的類型。其中 RSA 密鑰多是最經常使用的,而本人比較喜歡使用 ED25519,它有個明顯的優勢就是密鑰長度很是短,公鑰和私鑰都只有 32 字節,安全性也能夠和 RSA 密鑰 3000 位左右的相媲美,可以保證安全性,密鑰長度又短,所以會常用 ED25519 做爲密鑰。

當生成祕鑰對以後,會在 HOME 下的 .ssh 文件夾中生成密鑰隊的文件碼,包含私鑰和公鑰。".pub" j結尾的文件是公鑰文件,須要把公鑰文件配置在 gitlab 或 github 等代碼託管平臺上。右邊是 gitlab 的截圖,圖中使用的密鑰就是 ED25519 格式的密鑰,能夠看到長度真的很是短。

GOPROXY 獲取模塊

GO 支持經過 GOPROXY 協議獲取 GO 模塊。模塊是基於 HTTP 協議的,只會使用 HTTP 的 get 請求,而且使用標準的 HTTP 狀態碼進行調用。當使用的公共 GOPROXY 協議,其 GOPROXY 代理服務器默認都是沒有用戶名和密碼的。但實際上若是須要搭建私有的,是能夠支持 HTTP 基礎受權,方式與前面同樣,經過 .netrc 文件去配置用戶名和密碼。另外 GOPROXY 還有兩點特性:

  • 第一:比起使用 VCS 方式直接去克隆,GOPROXY 獲取模塊的速度會更快,緣由後面會詳細說明。
  • 第二:能夠解決模塊不能訪問的問題,好比 Golang 域名訪問不了等問題,經過第三方搭建好的代理服務器便可訪問下載到這些模塊。

GOPROXY 使用

GOPROXY 的配置是經過 GOPROXY 環境變量來控制,配置的是代理服務器URL。代理服務器 URL 能夠配置多個,經過逗號和管道符來進行分割,管道符和逗號的區別後面會舉例講解。

經過固定的字符串 off 和 direct 能夠代替 URL。off 禁止從任何來源去下載模塊,把 GOPROXY 設置爲 off 會禁止下載模塊,只能使用本地模塊,不管從 gitlab、github或其餘地方的模塊都不能下載。direct 表明直接從VCS上拉取,通常會做爲備選方案。

圖中展現了兩個例子:

  • 第一個是 Linux 環境變量的語法,經過 export 來設置環境變量。前面配置了proxy.golang.org,這是 google 官方的 goproxy 的服務器,逗號以後指定了備選方案 direct。在 GOPROXY 服務器返回403和410狀態碼時,表示找不到模塊。以逗號爲分隔指定備選方案時只有當服務器返回了403或410狀態碼時,go get 會嘗試使用備選方案,這裏是從版本管理平臺上去下載代碼。
  • 第二個使用了另外一種語法配置,go env -w 語法是 GO 自帶的,GO1.13版本開始支持。它是能夠跨平臺使用的,經過這種語法,沒有操做系統的差別,在 windows、Linux、max 上面,均可以經過該方式去配置 GO 相關的環境變量。示例中設置成了國內經常使用的 proxy 的地址:goproxy.cn。這裏使用了管道符指定備選方案,管道符的意義是不管代理服務器返回了什麼錯誤,即使不是 HTTP 的錯誤,如 GOproxy 服務器掛了返回500的錯誤,或者網絡錯誤。都會嘗試使用備選方案去下載模塊。

GOPROXY 實現

GOPROXY 的實現很簡單,官方定義只有五個接口。

URL 中的三個變量意義以下:

  • base 表明是 GOPROXY 服務器的 URL 地址;
  • module 表示須要需獲取模塊的名字;
  • version 是模塊的版本。

大小寫編碼問題

在 HTTP 的 URL 定義上是不區分大小寫的,當 module 或 version 出現大寫字母時,在某些系統中可能會出現混淆的問題。爲了不此問題,須要進行大小寫的編碼,把大寫字母轉換成感嘆號加小寫字母的編碼。

  • 第一個接口是獲取全部版本列表;
  • 第二個接口是獲取指定版本的信息;
  • 第三個接口是獲取指定模塊,指定版本的 mod 文件;
  • 第四個接口是獲取模塊的最新版本。這是可選的接口,不提供與實現該接口,GOPROXY 仍然能夠正常工做;
  • 最後一個接口是下載模塊指定版本的 zip 文件。

GOPROXY LIST

上圖是 list 接口示例, proxy.golang.org 是代理服務器的地址, golang.org/x/text 是要獲取的模塊名字,@v 爲固定的字符串,list 是要調用的 list 接口。能夠看到該接口返回了 text 包的全部版本,圖中 GO 獲取了全部版本後能夠經過版本語義推斷出模塊的最新版本。

GOPROXY INFO & LATEST

如上圖所示, INFO 接口和 LATEST 接口返回的內容是同樣的。 Version:固定版本字符串的版本號, Time 是 fc3339 時間格式的字符串,爲可選項,表明版本的提交時間。

GOPROXY MOD & ZIP

最後是 MOD 和 ZIP 接口。MOD 接口就是返回指定版本的 mod 文件,上圖示例中獲取了最新版本的 mod 文件, text 包只依賴了 tools 模塊。ZIP 文件接口就是獲取模塊指定版本的 ZIP 文件,當它把版本的全部原文件打包成 ZIP 文件,go get 最終經過接口去下載的就是這個版本的模塊。

前面提到經過 GOPROXY 去獲取源代碼會比經過 VCS 獲取要更快,經過 zip 去下載只會下載當前版本的全部文件不會包含歷史的版本信息,若是是經過 VCS 好比 git 去克隆倉庫,就會獲取全部的歷史版本信息;所以經過 GOPROXY zip 接口獲取文件的體積會更小,下載也會更快,須要注意的是 GOPROXY 定義了模塊 zip 文件的大小和其全部文件的未壓縮總限制爲 500 MiB,go.mod 文件和 LICENSE 文件大小限制爲 16 MiB。

module 驗證

Go1.13 版本開始加入模塊 SUM 驗證機制,默認全部 go 模塊下載後都會驗證其 hash 是否與線上( 默認:sum.golang.org 國內:sum.golang.google.cn)記錄的一致。

驗證的過程能夠經過環境變量 GONOSUMDB 和 GOSUMDB 來控制:首先來看 GOSUMDB 的配置,它指定了須要使用的線上數據庫地址。由於默認使用的 sum.golang.org 在國內沒法訪問,上圖中配置使用的是 google 搭建的國內鏡像,還能夠配置爲 off,表明禁用校驗,即下載模塊不進行哈希值的校驗,完全拋棄這個過程。使用中我不建議這樣作,可使用 GONOSUMDB 的環境變量去配置不須要驗證的模塊,好比私有模塊確定是不能經過驗證的。GONOSUMDE 是經過前綴匹配的方式運行的。圖中配置了 gitlab.com,那麼全部以 gitlab.com 開頭的包都不會進行 GO 的校驗和檢查。

下面來梳理下常見的變量:

  • GONOPROXY,基於前綴的匹配方式運行,上圖中指定了gitlab.com,也就是全部 gitlab.com 上的代碼,不從 GOPROXY 服務器上去獲取,所有經過傳統 VCS 方式,直接去原代碼服務器上拉取;
  • GONOSUMDB,可讓前綴匹配上的模塊跳過安全性檢查;
  • GOPRIVATE,至關於前面兩個環境變量的集合,配置了 GOPRIVATE 就至關於把前面的兩個環境變量一塊兒配置了;
  • GOVCS ,這是 GO1.16 版本才添加的,其主要做用是指定哪些模塊使用哪些 VCS。

又拍雲的業務實踐

私有包的使用

下面介紹一下如何使用私有模塊。通常公司內使用較多的是私有化搭建的 gitlab 服務,gitlab 自己是支持響應 go get 的 HTTP 請求。經過 go get 獲取包時,客戶端會發送 HTTP 請求到 gitlab 服務器上,服務器收到請求後會返回響應中包含 meta 標記。該標記會告訴客戶端,模塊使用 git 經過 HTTP 協議獲取原代碼。gitlab 默認使用 HTTPS 協議。客戶端收到 gitlab 服務器響應結果後,能正確的使用 git 去拉取模塊的源代碼。模塊下載經過後,一樣會有校驗和檢查的過程,能夠在 GOPRIVATE 變量加上 gitlab.com,告知 go gitlabc.com 相關的模塊都是私有模塊跳過校驗和檢查。

在又拍雲內部實踐中,狀況有些不一樣,又拍雲內部全部使用的 HTTP 服務都須要通過 google 的二次驗證。全部發往內部 gitlab 服務器的請求都會預先檢查是否有 google 受權的 head,若是沒有會被直接攔截掉並返回403錯誤。這樣會致使全部的簡單 HTTP 請求都不能到達 gitlab 服務器直接被攔截。go 發送的 HTTP 請求一樣也會被攔截掉,將致使 go 不能正確的獲取模塊信息。這時雖然能夠直接通 ssh 協議 clone 服務器上原代碼,但因爲 go get 沒有這些信息,致使請求失敗。所以下圖中灰線表示的請求其實是發不出來的。

那麼該如何解決呢?方法是採用額外的 http 服務來處理 go get 的 HTTP 請求。額外 HTTP 服務沒有驗證過程,請求經過後會 go get能正確的獲取到須要的 meta 信息。meta 中必須指定使用 ssh 協議,由於 gitlab http 服務有二次認證,沒有認證的請求都不能經過,所以只能使用 ssh 協議。權限認證能夠由 SSH 密鑰對完成,進行無感知進行受權。go get 引導 http 服務不會管理受權相關問題,全部的受權處理都交給 gitlab。做爲私有模塊,若是沒有對應的響應程序,受權認證都交給 gitlab 處理。

go get 請求指引

採用額外的服務去引導 go get 是怎麼作的呢?這須要對模塊包的命名進行修改,須要基於 gitlab 命名的規則修改。

gitlab.com/lyp256/pkg

域名 倉庫名

一個完整的模塊有幾部分組成,首先是域名 gitlab.com,lyp256 是全部者,pkg 是模塊的項目名字。對於單個 gitlab平臺重要的是後面兩段,也就是指定這個模塊全部者和項目名,域名確定是固定的能夠忽略。

基於這樣的規則我實現了一個簡單的小服務,來解決 go get http 請求的處理。代碼以下:

Gitlab CI 實踐

Gitlab CI 時會起一個空的容器,圖中示例使用的是 golang alpine 的鏡像。這個鏡像裏除了 golang 沒有其餘的東西。咱們須要安裝相關依賴和注入 SSH 認證相關內容。script 中定義以下:

第一步: 使用 mikdir -p,在 cache 下建立目錄,這個目錄是咱們 CI 機器上的緩存掛載的是物理盤上的一塊空間能夠保留數據,用來緩存 go mod 減小模塊下載。

第二步:安裝基礎環境、工具軟件包等。圖中示例安裝了 git 和 g++,g++ 是 go 編譯所須要的依賴,openssh 是 ssh 的工具鏈 git 須要用到。

第三步:處理 SSH 祕鑰。這裏有兩步,信任 gitlab 服務器祕鑰和導入認證私鑰。私鑰是經過環境變量 $DEPLOY_SSH_KEY 導入,只須要保留該環境變量中的內容到對應的祕鑰文件就能夠了。gitlab 服務器祕鑰使用 ssh-keyscan 來獲取並保存到 known_hosts 文件。經過 gitlab SI 的配置把能訪問 git 項目的私鑰放在環境變量 $DEPLOY_SSH_KEY 裏面,把私鑰放在相應的 ssh 私鑰文件而且授予正確的權限。

最後還須要配置 GOPRIVATE 變量定義全部 go.holdcloud.com 相關的模塊爲 PRIVATE 模塊不要使用代理和檢驗和檢查。

到此已基本完成全部準備工做,後面的 go test 是正常的 ci 測驗邏輯,能夠根據實際狀況來寫。

總結

  • GO 在 1.17 版本會刪除對 GOPATH 的支持,建議儘快遷移至 GOMDULE;
  • GO 的校驗和檢查能夠感知到代碼的變化提升安全與可用性,建議不要關閉;
  • 建議保留 vendor ,防止依賴模塊被刪除。

推薦閱讀

實操筆記:爲 NSQ 配置監控服務的心路歷程

告別DNS劫持,一文讀懂DoH

相關文章
相關標籤/搜索