Go 部落格

2019 年的 Go 模組

羅素考克斯
2018 年 12 月 19 日

真是充實的一年!

對於 Go 生態系而言,2018 年是豐收的一年,其中我們的主要焦點之一是套件管理。2 月份,我們展開了一場全體社群的討論,主題是如何將套件管理直接整合到 Go 工具鏈中,而在 8 月份的 Go 1.11 中,我們發布了名為 Go 模組的第一個粗糙實作。從 GOPATH 轉移到 Go 模組的遷移將會是 Go 生態系自 Go 1 以來最有影響力的變革。讓整個生態系,包括程式碼、使用者、工具等等,從 GOPATH 轉移到模組,需要在許多不同領域中付出努力。對 Go 生態系而言,模組系統反過來能協助我們提供更好的驗證和建置速度。

這篇文章是對於 Go 團隊在 2019 年與模組有關規劃事項的預覽。

版本

於 2018 年 8 月發布的 Go 1.11 導入了 模組的初步支援。當前,模組支援與傳統的基於 GOPATH 的機制並行維護。當在 GOPATH/src 外的目錄結構中執行且目錄根目錄中標記有 go.mod 檔案時,go 指令預設為模組模式。可透過設定過渡期環境變數 $GO111MODULEonoff 來覆寫此設定;預設行為為 auto 模式。我們已在 Go 社群中看到模組獲得廣泛採用,也收到了許多有助於我們改善模組的有用建議和錯誤報告。

預計於 2019 年 2 月推出的 Go 1.12 將改善模組支援,但仍預設留在 auto 模式。除了許多錯誤修正及其他小改進之外,Go 1.12 中最顯著的變更可能是現在 go run x.gogo get rsc.io/2fa@v1.1.0 等指令可在沒有明確 go.mod 檔案時以 GO111MODULE=on 模式運作。

我們的目標是讓預計於 2019 年 8 月推出的 Go 1.13 預設啟用模組模式(即將預設從 auto 變更為 on),並將 GOPATH 模式設為不建議使用。為此,我們一直致力於改善工具支援以及對開源模組生態系的支援。

工具與 IDE 整合

在我們擁有 GOPATH 的八年期間,已經產生了大量假設 Go 原始碼儲存在 GOPATH 中的工具。移轉至模組需要更新所有做出此假設的程式碼。我們設計了一個新套件 golang.org/x/tools/go/packages,用來抽象化尋找和載入特定目標的 Go 原始碼資訊的操作。此新套件會自動適應 GOPATH 和模組模式,並且也可以擴充至工具特定的程式碼配置,例如 Bazel 所使用的配置。我們一直與 Go 社群中的工具作者合作,協助他們在工具中採用 golang.org/x/tools/go/packages。

在此努力的一環中,我們也一直致力於統一各種原始碼查詢工具,例如 gocode、godef 和 go-outline,將其化為單一工具,供從命令列使用,而且也相容現代 IDE 使用的 語言伺服器協定

過渡到模組和封包載入的變更也促使 Go 程式分析發生重大變革。為了支援模組而重新編寫 go vet 的一部分,我們推出了 Go 程式漸進分析的概化架構,會針對每個封包一次呼叫分析器。在此架構中,對一個封入的分析可以寫出讓匯入此封包的其他封包分析使用的資訊。例如,go vetlog 封包 的分析會判定並記錄 log.Printffmt.Printf 包裝器的資訊。接著,go vet 可以檢查其他呼叫 log.Printf 的封包中所使用的 printf 式樣格式字串。此架構應能讓開發人員使用許多新進且複雜的程式分析工具,更早找出錯誤並更深入瞭解程式碼。

模組索引

go get 原始設計最重要的部分之一是:它是「分散」的:我們當時認為(到現在都這麼認為)任何人應該都能在任何伺服器上發布他們的程式碼,這一點不同於 Perl 的 CPAN、Java 的 Maven 或 Node.js 的 NPM 等中央登錄。將網域名稱放在 go get 匯入空間的前面,可以重複使用現有的分散式系統,而不用重新解決決定誰可以使用哪些名稱的問題。公司也可以將私人伺服器上的程式碼匯入,搭配來自公用伺服器的程式碼。當我們切換到 Go 模組時,務必要保留這種分散性。

Go 相依性的分散性有很多好處,但也帶來了一些重大的缺點。第一個缺點是太難找到所有公開可用的 Go 封包。任何想要提供封包相關資訊的網站都必須自行爬取,否則就必須等到使用者詢問某個特定封包後再去取得。

我們正在開發一項新服務,也就是 Go 模組索引,這項服務會提供進入 Go 生態系統的封包公開記錄。像 godoc.org 和 goreportcard.com 之類的網站將可以觀看此記錄的新增項目,不用再各自行實作程式碼來尋找新封包。我們還希望這項服務支援使用簡易查詢來查詢封包,以便 goimports 為尚未下載到本機系統的封包新增匯入。

模組驗證

現在,go get 依賴於連線等級驗證(HTTPS 或 SSH)來查看是否與正確的伺服器連線以下載程式碼。不會針對程式碼本身執行額外檢查,這表示如果 HTTPS 或 SSH 機制遭到損害,可能會發生中間人攻擊。分散性表示組建的程式碼會從許多不同的伺服器取得,表示組建依賴於許多系統提供正確的程式碼。

Go 模組設計透過在每個模組中儲存 go.sum 檔案以改進程式碼驗證;該檔案會列出模組每個依賴項所預期的檔案樹的加密雜湊。在使用模組時,go 指令會使用 go.sum 來驗證依賴項是否在二進位元上與預期的版本完全相同,才會在建置中使用這些依賴項。但是,go.sum 檔案只會列出該模組使用的特定依賴項的雜湊。如果你新增一個新的依賴項,或者使用 go get -u 更新依賴項時,在 go.sum 中不會出現對應的條目,因此無法直接驗證已下載二進位元的位元。

對於公開的模組,我們計畫執行一個我們稱為「公證人」的服務,這個服務會遵循模組索引記錄,下載新的模組,並對「M 模組的 V 版本具有檔案樹雜湊 H」這類陳述進行加密簽署。公證人服務會在可查詢的「憑證透明度」格式的、防篡改記錄中,公開所有這些公證的雜湊,讓所有人都能驗證公證人是否正常運作。此記錄將用作公開的全球 go.sum 檔案,go get 能夠在新增或更新依賴項時使用此記錄驗證模組。

我們的目標是讓 go 指令在 Go 1.13 中開始檢查不在 go.sum 中的公開模組的公證雜湊。

模組鏡像

由於非集中的 go get 會從多個原始伺服器擷取程式碼,因此擷取程式碼的速度和可靠性會受到速度最慢、最不可靠的伺服器影響。在模組問世之前,唯一的防禦方法是將依賴項轉移到自己的儲存庫中。儘管轉移仍會持續受到支援,但我們偏好的是一種適用於所有模組的解決方案,而並非只適用於你已經在使用的模組,並且不需要將依賴項重複複製到每個使用該依賴項的儲存庫中。

Go 模組設計引入了「模組代理」的概念,這是一個伺服器,由 go 指令要求提供模組,而非原始伺服器提出要求。一個重要的代理類型為「模組鏡像」,它會透過從原始伺服器擷取模組,然後快取它們以供未來請求使用,來回應模組請求。即使某些原始伺服器已關閉,運作良好的鏡像也應該具有快速且可靠的特性。我們計畫於 2019 年推出公開模組的鏡像服務。其他專案,例如 GoCenter 和 Athens,也計畫推出鏡像服務。(我們預期各公司也會有多種選項來執行他們自己的內部鏡像服務,但這篇文章的重點在於公開的鏡像服務。)

鏡像的一個潛在問題是它們正是介於中間的伺服器,使其成為攻擊的自然目標。Go 開發人員需要一些保證,即鏡像提供與原始伺服器相同的位元組。我們在上一節中描述的公證程序正是解決了這個問題,它將適用於使用鏡像的下載,以及使用原始伺服器進行的下載。鏡像本身不必受信任。

我們的目標是讓 Google 執行的模組鏡像準備好在 Go 1.13 中的 `go` 指令中預設使用。使用備用鏡像或根本不使用鏡像是很簡單的設定。

模組偵測

最後,我們之前提到模組索引將使建置像 godoc.org 這樣的網站更容易。我們 2019 年的工作之一將會是大幅改造 godoc.org,使其對需要發現可用模組,然後決定是否依賴特定模組的開發人員更有用。

遠景

此圖顯示模組原始碼在本篇文章的設計中是如何流動的。

以前,所有 Go 原始碼的使用者(`go` 指令和任何像 godoc.org 這樣的網站),都是直接從每個程式碼主機取得程式碼。現在,他們可以從快速、可靠的鏡像取得快取程式碼,同時仍驗證下載的位元組是否正確。索引服務讓鏡像、godoc.org 和任何其他類似的網站都能輕鬆跟上每天新增到 Go 生態系的出色新程式碼。

我們對 2019 年 Go 模組的未來感到興奮,我們也希望你也是。新年快樂!

下一篇文章:Go 1.12 發布
上一篇文章:Go 2,來吧!
部落格首頁