Go Module 教程:鏡像、校驗和以及 Athens

注意,該教程基於 Go1.13。最新版本可能會有所不同。

引言

當我第一次學習模塊時遇到的一個長期問題是模塊鏡像、校驗和數據庫以及 Athens 是如何工作的。Go 團隊已經寫了大量關於模塊鏡像和校驗和數據庫的內容,但我希望在這裏合併最重要的信息。在這篇文章中,我提供了這些系統的用途,你可以控制的不同的配置選項,並用示例程序展示了這些系統的運行情況。

模塊鏡像(Mirror)

模塊鏡像 [1] 是 2019 年 8 月發佈的,是 Go 版本 1.13 中用於抓取模塊的默認系統。模塊鏡像是作爲一個代理服務器實現的,它面向 VCS 環境,幫助加速獲取構建應用程序所需的本地模塊。代理服務器實現了一個基於 REST 的 API,並且是圍繞 Go 工具的需求而設計的。

模塊鏡像緩存模塊及其被請求的特定版本,這樣可以更快地檢索未來的請求。一旦獲取代碼並將其緩存到模塊鏡像中,就可以快速地將其提供給世界各地的客戶機。模塊鏡像還允許用戶繼續獲取原始 VCS 位置不再可用的源代碼。這可以防止像 Node 開發者在 2016 年遇到的問題 [2]。

校驗和數據庫

校驗和數據庫 [3] 也於 2019 年 8 月推出,是一個防篡改的模塊散列碼日誌,可用於驗證不可信代理或來源。校驗和數據庫是作爲服務實現的,Go 工具使用它來驗證模塊。它驗證了特定版本的任何給定模塊的代碼是否是相同的,不管是誰、是什麼、在哪裏以及如何獲取。它還解決了其他依賴管理系統尚未解決的其他安全問題(如上面的鏈接所述)。Google 擁有現存唯一的校驗和數據庫,但是它可以被私有的模塊鏡像緩存。

模塊索引

該索引服務 [4] 是爲那些希望跟蹤添加到 Google 模塊鏡像的新模塊的模塊列表的開發人員提供的。像 pkg.go.dev[5] 這樣的網站使用索引來檢索和發佈模塊的信息。

你的隱私

正如隱私政策 [6] 中記錄的那樣,Go 團隊構建這些服務是爲了儘可能少地保留關於使用情況的信息,同時仍然確保它們能夠檢測和修復問題。然而,像 IP 地址這樣的個人身份信息可以保存 30 天。如果這對您或您的公司是一個問題,您可能不希望使用 Go 團隊的服務來獲取和驗證模塊。

Athens

Athens[7] 是一個模塊鏡像,你可以搭建你的私人環境。使用私有模塊鏡像的一個原因是允許緩存公共模塊鏡像無法訪問的私有模塊。最棒的是 Athens 項目提供了一個 Docker 容器,發佈在 Docker Hub 上,所以不需要特殊安裝。

GCTT 注:還有 goproxy.io 和 goproxy.cn,這兩個都是國人開發的

清單 1

docker run -p '3000:3000' gomods/athens:latest

清單 1 顯示瞭如何使用 Docker 運行本地 Athens 服務器。稍後我們將使用這個工具來查看 Go 工具的運行情況,並監視所有 Go 工具 Web 調用的 Athens 日誌。要知道,Athens Docker 映像默認啓動了臨時磁盤存儲,所以當你關閉運行的容器時,所有內容都將被清空。

Athens 有能代理 [8] 校驗和數據庫。當 Go 工具被配置爲使用一個像 Athens 一樣的私有模塊鏡像時,當需要從校驗和數據庫中查找散列碼時,Go 工具將嘗試使用相同的私有模塊鏡像。如果你正在使用的私有模塊鏡像不支持代理校驗和數據庫,那麼將直接訪問校驗和數據庫,除非它被手動關閉。

清單 2

http://localhost:3000/sumdb/sum.golang.org/latest

go.sum database tree
756113
k9nFMBuXq8uk+9SQNxs/Vadri2XDkaoo96u4uMa0qE0=

— sum.golang.org Az3grgIHxiDLRpsKUElIX5vJMlFS79SqfQDSgHQmON922lNdJ5zxF8SSPPcah3jhIkpG8LSKNaWXiy7IldOSDCt4Pwk=

清單 2 顯示了 Athens 如何成爲校驗和數據庫代理。第一行中列出的 URL 要求本地運行的 Athens 服務從校驗和數據庫中檢索有關最新簽名樹的信息。你可以瞭解爲什麼 GOSUMDB 配置爲名稱而不是 URL。

環境變量

有幾個環境變量控制 Go 工具的行爲,因爲它與模塊鏡像和校驗和數據庫相關。需要在每個開發人員或構建環境的計算機級別上設置這些變量。

GOPROXY:一組指向模塊鏡像的 URL,用於抓取模塊。如果你希望 Go 工具只從 VCS 位置直接獲取模塊,那麼可以將其設置爲 direct。如果你將此設置爲 off,那麼模塊將不會被下載。使用 off 可以用在保留 vendoring 或模塊緩存的構建環境中。

GOSUMDB:用於驗證給定模塊 / 版本的代碼的校驗和數據庫的名稱沒有隨時間變化。此名稱用於形成一個正確的 URL,該 URL 告訴 Go 工具在哪裏執行這些校驗和數據庫查找。這個 URL 可以指向 Google 擁有的校驗和數據庫,或者指向支持緩存或代理校驗和數據庫的本地模塊鏡像。如果你不希望 Go 工具驗證添加到 go.sum 文件中的給定模塊 / 版本的哈希代碼,也可以將其設置爲 off。只有在向 go.sum 文件添加任何新的 go.sum 行之前,纔會查詢校驗和數據庫。

GONOPROXY:模塊的一組基於 URL 的模塊路徑,不應該使用模塊鏡像獲取,而是直接從 VCS 位置獲取。

GOPRIVATE:一個便捷的變量,用於設置具有相同默認值的 GONOPROXY 和 GONOSUMDB。

GCTT 注:這些環境變量的幫助文檔可以通過 go help environment 獲得

隱私語義學(Privacy Semantics)

在考慮隱私和項目所依賴的模塊時,需要考慮以下幾點。特別是那些你不想讓別人知道的私人模塊。下面的圖表嘗試提供隱私選項。同樣,需要在每個開發人員或構建環境的計算機級別上設置此配置。

清單 3

Option            : Fetch New Modules     : Validate New Checksums
-----------------------------------------------------------------------------------------
Complete Privacy  : GOPROXY="direct"      : GOSUMDB="off"
Internal Privacy  : GOPROXY="Private_URL" : GOSUMDB="sum.golang.org"
                                            GONOSUMDB="github.com/mycompany/*,gitlab.com/*"
No Privacy        : GOPROXY="Public_URL"  : GOSUMDB="sum.golang.org"

Complete Privacy:代碼直接從 VCS 服務器獲取,沒有生成並添加到 go.sum 文件的哈希代碼,並且它們也不會從校驗和數據庫中查找。

Internal Privacy:代碼是通過一個像 Athens[9] 這樣的私有模塊鏡像獲取的,並且不會在校驗和數據庫中查找生成和添加到 go.sum 文件中的,GONOSUMDB 下列出的指定 URL 路徑的哈希代碼。如果需要,將在校驗和數據庫中查找不屬於 GONOSUMDB 中列出的路徑的模塊。

No Privacy:代碼是通過像 Google[10] 或 Goproxy.CN[11] 公共服務器這樣的公共模塊鏡像獲取的。在這種情況下,你所依賴的所有模塊都需要是公共模塊,並可由你選擇的公共模塊進行訪問。這些公共模塊鏡像將記錄你的請求和其中包含的詳細信息。訪問谷歌擁有的校驗和數據庫也將被記錄。所記錄的信息受各自的隱私策略控制。

從來沒有理由在校驗和數據庫中查找私有模塊的哈希代碼,因爲校驗和數據庫中永遠不會有這些模塊的列表。公共模塊鏡像不能訪問私有模塊,因此不能生成和存儲哈希代碼。對於私有模塊,你需要依靠內部策略和實踐來保持給定模塊 / 版本的代碼一致。但是,如果私有模塊 / 版本的代碼確實發生了更改,那麼當第一次在新機器上獲取並緩存模塊 / 版本時,Go 工具仍然可以發現差異。

任何時候,當模塊 / 版本被添加到機器上的本地緩存中,並且 go.sum 文件中已經有一個條目時,go.sum 文件中的哈希碼都會與剛纔在緩存中獲取的內容進行比較。如果哈希代碼不匹配,就說明發生了變化。這個工作流程最好的部分是不需要校驗和數據庫查找,因此任何給定版本的私有模塊仍然可以在不損失隱私的情況下進行驗證。顯然,這完全取決於你第一次獲取私有模塊 / 版本的時間,這對於存儲在校驗和數據庫中的公共模塊 / 版本來說也是同樣的問題。

當使用 Athens 作爲模塊鏡像,需要考慮 Athens 配置選項。

清單 4

GlobalEndpoint = "https://<url_to_upstream>"
NoSumPatterns = ["github.com/mycompany/*]

清單 4 中的這些設置來自 Athens 的文檔,它們很重要。默認情況下,Athens 將直接從不同的 VCS 服務器獲取模塊。這將爲你的環境保持最高級別的隱私。但是,可以通過將 GlobalEnpoint 設置爲該模塊鏡像的 URL,將 Athens 指向另一個模塊鏡像。這將使你在獲取新的公共模塊時獲得更好的性能,但是你將失去隱私。

另一個設置稱爲 NoSumPatterns,它有助於驗證開發人員和構建環境的正確配置。開發人員向 GONOSUMDB 添加的相同路徑集應該添加到 NoSumPatterns 中。當檢查和數據庫請求訪問 Athens 以獲取與路徑匹配的模塊時,它將返回一個狀態代碼,該狀態代碼將導致 Go 工具失敗。這表明開發人員的設置是錯誤的。換句話說,如果機器配置正確,那麼這個請求從一開始就不應該到達 Athens 。

Vendoring

我相信每個項目都應該提供他們的依賴關係,或者認爲這樣做不合理或者不切實際。像 Docker 和 Kubernetes 這樣的項目不能提供他們的依賴項,因爲依賴項太多了。然而,對於我們大多數人來說,情況並非如此。在 v1.14 版本中,對 vendoring 和模塊有很好的支持。我將在另一篇文章中討論這個問題。

我提到 vendoring 有一個重要的原因。我聽說有人用 Athens 或者私有模塊鏡像代替 vendoring。我認爲這是個錯誤。這兩者沒有任何關係。您可以爭辯模塊鏡像 vendoring 的依賴關係,因爲模塊的代碼是持久化的,但是代碼仍然遠離依賴它的項目。即使你相信你的模塊鏡像的彈性,我也相信沒有什麼可以替代你的項目擁有它所需要的所有源代碼,除了項目本身來構建代碼之外不依賴其他任何東西。

工具的使用

有了所有這些背景和知識,是時候看看 Go 工具是如何工作的了。爲了瞭解環境變量如何影響 Go 工具,我將運行幾個不同的場景。在開始之前,可以通過運行 go env 命令來了解默認值。

清單 5

$ go env
GONOPROXY=""
GONOSUMDB=""
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOSUMDB="sum.golang.org"

清單 5 顯示了告訴 Go 工具使用 Google 模塊鏡像和 Google 校驗和數據庫的默認值。如果你需要的所有代碼都可以通過這些 Google 服務訪問,那麼這是推薦的配置。如果 Google 模塊鏡像碰巧響應了 410(已消失)或 404(未找到),那麼使用 direct(這是 GOPROXY 配置的一部分)將允許 Go 工具改變方向並直接從 VCS 位置獲取模塊 / 版本。任何其他狀態代碼(比如 500)都會導致 Go 工具失敗。

如果 Google 模塊鏡像碰巧對給定模塊 / 版本響應了 410 或 404,那是因爲它不在緩存中,可能不能緩存,而私有模塊就是這種情況。在這種情況下,校驗和數據庫中很可能也沒有列表。即使 Go 工具可以成功地直接獲取模塊 / 版本,但是查找將會失敗,而 Go 工具仍然會失敗。使用私有模塊時需要注意的一些事項。

因爲我不能顯示任何來自 Google 模塊鏡像的日誌,所以我將使用 Athens 運行一個本地模塊鏡像。這將允許你看到 Go 工具和模塊鏡像在運行。最後,Athens 實現了相同的語義和工作流。

項目

要創建項目,請啓動終端會話並創建項目結構。

清單 6

cd $HOME
$ mkdir app
$ mkdir app/cmd
$ mkdir app/cmd/db
$ touch app/cmd/db/main.go
$ cd app
$ go mod init app
$ code .

清單 6 顯示了爲在磁盤上創建項目結構、爲模塊初始化項目和運行 VSCode 而運行的命令。

清單 7

https://play.studygolang.com/p/TtbuNj_IAwL

package main

import (
 "context"
 "log"

 "github.com/Bhinneka/golib"
 db "gopkg.in/rethinkdb/rethinkdb-go.v5"
)

func main() {
 c, err := db.NewCluster([]db.Host{{Name: "localhost", Port: 3000}}, nil)
 if err != nil {
  log.Fatalln(err)
 }

 if _, err = c.Query(context.Background(), db.Query{}); err != nil {
  log.Fatalln(err)
 }

 golib.CreateDBConnection("")
}

清單 7 顯示了 main.go 的代碼。隨着項目的設置和主要功能的到位,我將在項目中運行三個不同的場景,以更好地理解環境變量和 Go 工具。

場景 1:Athens 模塊鏡像

在這個場景中,我將使用 Athens 作爲私有模塊鏡像替代 Google 模塊鏡像。

清單 8

GONOSUMDB=""
GONOPROXY=""
GOSUMDB="sum.golang.org"
GOPROXY="http://localhost:3000,direct"

清單 8 顯示,我要求 Go 工具對模塊鏡像使用在端口 3000 上本地運行的 Athens 服務。如果模塊鏡像以 410(已消失)或 404(未找到)響應,則嘗試直接拉出模塊。默認情況下,如果需要,Go 工具現在將使用 Athens 來訪問校驗和數據庫。

接下來,爲運行 Athens 啓動一個新的終端會話。

清單 9

$ docker run -p '3000:3000' -e ATHENS_LOG_LEVEL=debug -e GO_ENV=development gomods/athens:latest

INFO[10:15AM]: Exporter not specified. Traces won't be exported
2021-09-05 10:15:08.464666 I | Starting application at port :3000

清單 9 在第一行顯示了在一個新的終端會話中運行的命令,該命令使用額外的調試日誌記錄啓動並運行 Athens 服務。確保你本機啓動了 Docker。一旦 Athens 啓動,你應該會看到清單中的輸出。

要查看 Go 工具使用 Athens 服務的情況,請在用於創建項目的原始終端會話中運行以下命令。

清單 10

export GOPROXY="http://localhost:3000,direct"
$ rm go.*
$ go mod init app
$ go mod tidy

清單 10 顯示了將 GOPROXY 變量設置爲使用 Athens 服務、刪除模塊文件和重新初始化應用程序的命令。最後的命令 go mod tidy 將使 Go 工具與 Athens 服務通信,以獲取構建這個項目所需的模塊。

清單 11

handler: GET /github.com/!bhinneka/@v/list [404]
handler: GET /github.com/@v/list [404]
handler: GET /github.com/!bhinneka/golib/@v/list [200]
handler: GET /gopkg.in/@v/list [404]
handler: GET /github.com/!bhinneka/golib/@latest [200]
handler: GET /gopkg.in/rethinkdb/rethinkdb-go.v5/@v/list [200]
handler: GET /github.com/bitly/@v/list [404]
handler: GET /github.com/bmizerany/@v/list [404]
handler: GET /github.com/bmizerany/assert/@v/list [200]
handler: GET /github.com/bitly/go-hostpool/@v/list [200]
handler: GET /github.com/bmizerany/assert/@latest [200]

清單 11 顯示了來自 Athens Service 的重要輸出。如果查看 go.mod 和 go.sum 文件,你將看到構建和驗證項目所需的所有內容。

GCTT 注:你看到的信息可能會有所不同,因爲 Athens 可能升級了,日誌輸出方式變了

場景 2:Athens 模塊鏡像 / 直接從 GitHub Modules 獲取

在這個場景中,我不希望從模塊鏡像獲取任何託管在 GitHub 上的模塊。我希望這些模塊可以直接從 GitHub 獲取。

清單 12

export GONOPROXY="github.com"export GOPROXY="http://localhost:3000,direct"
$ rm go.*
$ go mod init app
$ go mod tidy

清單 12 顯示了在這個場景中如何設置 GONOPROXY 變量。現在 GONOPROXY 告訴 Go 工具直接獲取任何名稱以 github. com 開頭的模塊。不要使用 GOPROXY 變量定義的模塊鏡像。雖然我使用 GitHub 來展示這一點,但是如果你運行一個像 GitLab 這樣的本地 VCS,這個配置是完美的。這將允許你直接獲取私有模塊。

清單 13

handler: GET /gopkg.in/@v/list [404]
handler: GET /gopkg.in/rethinkdb/rethinkdb-go.v5/@v/list [200]

清單 13 顯示了運行 go mod tidy 之後從 Athens Service 得到的更重要的輸出。這次 Athens 只顯示對位於 gopk.in 的兩個模塊的請求。位於 github. com 的模塊不再需要 Athens 的服務。

場景 3:Module Mirror 404

在這個場景中,我將使用自己的模塊鏡像,它將爲每個模塊請求返回一個 404。當模塊鏡像返回 410(已消失)或 404(未找到)時,Go 工具將沿着 GOPROXY 變量中列出的逗號分隔的其他鏡像集繼續。

清單 14

https://play.studygolang.com/p/uEH4_b6QrAO

package main

import (
 "log"
 "net/http"
)

func main() {
 h := func(w http.ResponseWriter, r *http.Request) {
  log.Printf("%s %s -> %s\n", r.Method, r.URL.Path, r.RemoteAddr)
  w.WriteHeader(http.StatusNotFound)
 }
 http.ListenAndServe(":3000", http.HandlerFunc(h))
}

清單 14 顯示了我的模塊鏡像的代碼。它能夠記錄每個請求的跟蹤並返回 http.StatusNotFound,即 404。

清單 15

unset GONOPROXY
$ export GOPROXY="http://localhost:3000"
$ rm go.*
$ go mod init app
$ go mod tidy

清單 15 顯示瞭如何將 GONOPROXY 變量恢復爲空,以及再次運行 go mod tidy 之前如何從 GOPROXY 中刪除 direct。

清單 16

app/cmd/db imports
 github.com/Bhinneka/golib: cannot find module providing package github.com/Bhinneka/golib
app/cmd/db imports
 gopkg.in/rethinkdb/rethinkdb-go.v5: cannot find module providing package gopkg.in/rethinkdb/rethinkdb-go.v5

清單 16 顯示了運行 go mod tidy 時來自 Go 工具的輸出。你可以看到調用失敗,因爲 Go 工具找不到模塊。

如果我將 direct 放回 GOPROXY 變量中會怎樣?

unset GONOPROXY
$ export GOPROXY="http://localhost:3000,direct"
$ rm go.*
$ go mod init app
$ go mod tidy

清單 17 顯示瞭如何再次將 direct 用於 GOPROXY 變量。

清單 18

go: finding github.com/Bhinneka/golib latest
go: finding gopkg.in/rethinkdb/rethinkdb-go.v5 v5.0.1
go: downloading gopkg.in/rethinkdb/rethinkdb-go.v5 v5.0.1
go: extracting gopkg.in/rethinkdb/rethinkdb-go.v5 v5.0.1

清單 18 顯示了 Go 工具是如何再次工作的,並直接到每個 VCS 系統去獲取模塊。記住,如果返回任何其他狀態代碼(在 200、410 或 404 之外),Go 工具將失敗。

其他場景

我決定不再繼續使用其他只會導致 Go 工具失敗的場景。如果你使用的是私有模塊,那麼你需要一個私有模塊鏡像,每個開發人員和構建機器上的配置都很重要,並且需要保持一致。私有模塊鏡像的配置需要與開發人員配置的內容相匹配,構建計算機也是如此。然後使用 GONOPROXY 和 GONOSUMDB 環境變量防止將私有模塊的請求發送到任何 Google 服務器。如果你正在使用 Athens,它有特殊的配置選項來查找任何開發人員或構建計算機上的配置差異。

VCS 認證問題

在回顧這篇文章的時候,Erdem Aslan[12] 非常友好地爲人們遇到的問題提供了一個解決方案。獲取依賴項時的 Go 工具直接期望使用基於 https 的協議。在需要 VCS 認證的環境中,這可能是一個問題。Athens 可以幫助解決這個問題,但是如果你想確保直接調用不會失敗,Erdem 爲你的全局 git 配置文件提供了這些設置。

清單 19

[url "git@github.com:"]
insteadOf = "https://github.com"
  pushInsteadOf = "github:"
  pushInsteadOf = "git://github.com/"

總結

當你開始在自己的項目中使用模塊時,請確保儘早決定使用哪個模塊鏡像。如果你有一個私有的 VCS 或者如果隱私是一個大問題,那麼使用一個私有的模塊鏡像是你最好的選擇。這將提供你需要的所有安全性、更好的抓取模塊性能和最高級別的隱私。Athens 是運行私有模塊鏡像的好選擇,因爲它提供了模塊緩存和校驗和數據庫代理。

如果你想檢查 Go 工具是否遵守你的配置,並且所選擇的模塊鏡像是否正確地代理了校驗和數據庫,那麼 Go 工具有一個名爲 go mod verify 的命令。此命令檢查依賴項在下載後是否未被修改。它將檢查本地模塊緩存中的內容,在 1.15 版本,該命令可以檢查 vendor 件夾 [13]。

嘗試這些配置,並找到最符合你需要的解決方案。

via: https://www.ardanlabs.com/blog/2020/02/modules-04-mirros-checksums-athens.html

作者:William Kennedy[14] 譯者:polaris1119[15] 校對:polaris1119[16]

本文由 GCTT[17] 原創編譯,Go 中文網 [18] 榮譽推出,發佈在 Go 語言中文網公衆號,轉載請聯繫我們授權。

參考資料

[1]

模塊鏡像: https://blog.golang.org/module-mirror-launch

[2]

遇到的問題: https://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/

[3]

校驗和數據庫: https://go.googlesource.com/proposal/+/master/design/25530-sumdb.md

[4]

索引服務: https://index.golang.org/

[5]

pkg.go.dev: https://pkg.go.dev/

[6]

隱私政策: https://sum.golang.org/privacy

[7]

Athens: https://docs.gomods.io/

[8]

代理: https://docs.gomods.io/configuration/sumdb

[9]

Athens: https://docs.gomods.io/

[10]

Google: https://proxy.golang.org/

[11]

Goproxy.CN: https://goproxy.cn/

[12]

Erdem Aslan: https://twitter.com/Gladmir

[13]

vendor 件夾: https://github.com/golang/go/issues/27348

[14]

William Kennedy: https://www.ardanlabs.com/

[15]

polaris1119: https://github.com/polaris1119

[16]

polaris1119: https://github.com/polaris1119

[17]

GCTT: https://github.com/studygolang/GCTT

[18]

Go 中文網: https://studygolang.com/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/b1mTUOKCs-L9Tt9TdzJpQw