系列索引
为什么以及什么
项目,依赖项和Gopls
最小版本选择
镜像,校验和和Athens
Gopls改进
vendor的使用
介绍
当我第一次学习模块时,我遇到的一个长期问题是模块镜像,校验和以及Athens如何工作。Go团队已经撰写了大量有关模块镜像和校验和的文章,但是我希望在这里将最重要的信息整合在一起。在本文中,我提供了这些系统的用途,拥有的不同配置选项,并使用示例程序展示了这些系统的实际作用。
模块镜像
模块镜像是在2019年八月推出,是Go1.13版本获取模块的默认系统。模块镜像作为VCS环境之前的代理服务器,以帮助加快构建本地应用程序所需的模块的下载。代理服务器实现了基于REST的API,并根据Go工具的需求进行了设计。
模块镜像将缓存已请求的模块及其特定版本,从而可以更快地检索将来的请求。一旦代码被获取并缓存在模块镜像中,那么它也可以将其快速提供给世界各地的用户。模块镜像还允许用户继续从其原始VCS位置获取不再可用的源代码。这可以避免类似于一个Node开发者2016年的经历 。
校验和数据库
校验和数据库也于2019八月推出,是模块起源的散列码防篡改日志,可以用来验证不可信的代理。校验和数据库以服务的方式实现,可以被Go工具使用于模块的验证。它可以验证任何给定的特定版本的模块的代码是否相同,而不管它是谁,从哪里通过什么途径获取的。它还解决了其他依赖项管理系统尚未解决的其他安全问题(如上面的链接中所述)。Google拥有唯一的校验和数据库,但是可以通过私有模块镜像对其进行缓存,稍后会讲述。
模块索引
索引服务为那些想在google模块镜像列表后新增模块的开发者提供服务。比如pkg.go.dev之类的网站使用索引来检索和发布有关模块的信息。
你的隐私
如隐私权政策中所述,Go团队已构建了这些服务,以保留尽可能少的使用信息,同时仍确保它们能够检测并修复问题。但是,可识别个人身份的信息(例如IP地址)最多保留30天。如果这对你或你的公司来说是个问题,那么你可能不希望使用Go团队的服务来满足模块的获取和验证需求。
Athens
Athens是一个模块镜像,你可以搭建在自己的私人环境中。使用私有模块镜像的一个原因是允许缓存公共模块镜像无法访问的私有模块。出色的是Athens项目提供了一个在Docker Hub上发布的Docker容器,因此不需要特殊的安装。
代码1
docker run -p '3000:3000' gomods/athens:latest
代码1显示了如何使用Docker运行本地Athens服务器。我们稍后将使用它来查看Go工具的运行情况,并监视所有Go工具Web调用的Athens日志。请注意,默认情况下,Athens Docker镜像使用临时磁盘存储启动,因此当你关闭正在运行的容器时,所有内容都会消失。
Athens还具有代理校验和数据库的能力。当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可以在vendor或模块缓存被保护的环境中构建项目时使用。
GOSUMDB:校验和数据库的名称,用于验证给定模块/版本的代码未随时间变化。此名称用于形成一个适当的URL,该URL告诉Go工具去哪里检索校验和数据库。该URL可以指向Google拥有的校验和数据库,也可以指向支持对校验和数据库缓存或代理的本地模块镜像。如果你不需要Go工具验证并添加到go.sum文件中的给定模块/版本的哈希码,也可以将其设置为off。只有将新的go.sum行添加到go.sum文件之前,才检索校验和数据库。
GONOPROXY:一组基于URL的模块路径,不使用模块镜像来获取,而直接在VCS位置上获取。
GONOSUMDB:一组基于URL的模块路径,这些模块的哈希码不在校验和数据库中查找。
GOPRIVATE:一个方便的变量,用于将GONOPROXY和GONOSUMDB设置为相同的默认值。
隐私语义
在考虑隐私和项目所依赖的模块时,需要考虑几件事。尤其是你不希望任何人知道的私有模块。下表提供了隐私选项。再强调一次,需要为每个开发人员或构建环境配置成计算机级别设置。
代码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"
完全的私密性:直接从VCS服务器获取代码,并且不会从校验和数据库中查找哈希码或添加到go.sum
文件。
内部隐私:通过像Athens这样的私有模块镜像获取代码,针对GONOSUMDB
下面列出的指定URL路径,并且不会在校验和数据库中查找哈希码并添加到go.sum
文件中。如有必要,不在GONOSUMDB
中列出的模块将会去校验和数据库中检索。
没有隐私:通过Google或JFrog的公共服务器等公共模块镜像获取代码。在这种情况下,你依赖的所有模块都必须是公共模块,并且可以通过你选择的公共模块镜像进行访问。这些公共模块镜像将记录你的请求以及其中包含的详细信息。还将记录对Google拥有的校验和数据库的访问记录。所有记录的信息会遵守隐私政策。
从来不会出现要在校验和数据库中查找私有模块的哈希码的情况,因为在校验和数据库中永远不会列出这些模块。私有模块不会允许公用模块镜像访问,因此不会生成并存储哈希码。对于私有模块,你需要依靠内部策略和实践来保证模块/版本的代码保持一致。但是,如果私有模块/版本的代码确实发生了变化,那么当首次在新机器上获取并缓存模块/版本时,Go工具仍会发现差异。
每当将模块/版本添加到计算机上的本地缓存中并且在go.sum文件中记录,就将go.sum文件中的哈希码与刚刚从缓存中获取的哈希码进行比较。如果哈希码不匹配,则说明已更改。关于此工作流程的最佳部分是不需要依赖校验和数据库查找,这样仍可以验证任何给定版本的私有模块,而不会有隐私泄露的问题。显然,这完全取决于你何时首次获取私有模块/版本,对于存储在校验和数据库中的公共模块/版本来说,这是同样的问题。
当使用Athens搭建模块镜像服务时,也要考虑Athens的配置选项。
代码4
GlobalEndpoint = "https://"
NoSumPatterns = ["github.com/mycompany/*]
代码4中的这些设置来自Athens文档,它们很重要。默认情况下,Athens将直接从不同的VCS服务器获取模块。这样可以保持你的私有环境的隐私级别为最高级别。但是,你可以通过设置GlobalEnpoint的URL,将Athens指向其他模块镜像。这将使你在获取新的公共模块时有更好的性能,但是将失去隐私的保护。
另一个设置NoSumPatterns,它用于帮助开发人员验证构建环境是否已正确配置。开发人员需要将添加到GONOSUMDB的同一组路径也添加到NoSumPatterns中。每当校验和数据库请求到Athens寻找与该路径匹配的模块时,它将返回状态码,该状态代码会使Go工具失败。这表明开发人员的设置错误。换句话说,如果发出请求的机器配置正确,则该请求永远不会首先到达Athens。
Vendoring
我相信每个项目都应提供依赖项管理,直到这样做不合理或实际不需要为止。诸如Docker和Kubernetes之类的项目无法供应其依赖项,因为vendor的依赖项太多了。但是,对于我们大多数人而言,情况并非如此。在v1.14版本中,对vendor和模块提供了强大的支持。我将在另一篇文章中讨论。
我提出Vendoring很重要的一点是。我听说有人使用Athens或专用模块镜像代替Vendoring。我认为这是一个错误,一个与另一个无关。你可能会争辩说模块镜像vendor的依赖性,因为模块的代码是持久的,但是代码与依赖它的项目仍然相去甚远。即使你相信模块镜像的可用性,我依然相信一个自身拥有所需所有源代码的项目是无可替代的,这样仅依靠项目本身来构建代码就可以了。
实用工具
有了所有这些背景知识之后,就该开始实践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显示了运行命令后在磁盘上创建的项目结构,初始化项目的模块以及使VS Code运行。
代码7
https://play.golang.org/p/h23opcp5qd0
01 package main
02
03 import (
04 "context"
05 "log"
06
07 "github.com/Bhinneka/golib"
08 db "gopkg.in/rethinkdb/rethinkdb-go.v5"
09 )
10
11 func main() {
12 c, err := db.NewCluster([]db.Host{{Name: "localhost", Port: 3000}}, nil)
13 if err != nil {
14 log.Fatalln(err)
15 }
16
17 if _, err = c.Query(context.Background(), db.Query{}); err != nil {
18 log.Fatalln(err)
19 }
20
21 golib.CreateDBConnection("")
22 }
代码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
time="2020-01-21T21:12:35Z" level=info msg="Exporter not specified. Traces won't be exported"
2020-01-21 21:12:35.937604 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服务的更重要的输出。如果查看go.mod和go.sum文件,将看到列出了用于构建和验证项目的所有内容。
场景2-Athens 模块镜像 / GitHub 模块直接获取
在这种场景下,我不希望通过模块镜像获取托管在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为例,如果你运行一个本地VCS像GitLab,这种配置也可以完美符合你的需求。这将允许你直接获取私有模块。
代码13
handler: GET /gopkg.in/@v/list [404]
handler: GET /gopkg.in/rethinkdb/rethinkdb-go.v5/@v/list [200]
代码13显示了运行go mod tidy后Athens服务的更重要的输出。这次,Athens只显示了对位于gopkg.in的两个模块的请求。Athens服务不再请求位于github.com的模块。
方案3-模块镜像404
在这种情况下,我将使用自己的模块镜像,该模块镜像将为每个模块请求返回404。当模块镜像返回410(消失)或404(未找到)时,Go工具将继续以逗号分隔的GOPROXY
变量列表中列出的其他镜像集。
代码14
https://play.golang.org/p/uEH4_b6QrAO
01 package main
02
03 import (
04 "log"
05 "net/http"
06 )
07
08 func main() {
09 h := func(w http.ResponseWriter, r *http.Request) {
10 log.Printf("%s %s -> %s\n", r.Method, r.URL.Path, r.RemoteAddr)
11 w.WriteHeader(http.StatusNotFound)
12 }
13 http.ListenAndServe(":3000", http.HandlerFunc(h))
14 }
代码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
之前将其direct
从GOPROXY
中删除。
代码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
变量中呢?
代码17
$ 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非常友好,并为人们遇到的问题提供解决方案。获取依赖时的Go工具期望使用基于https
的协议。在需要对VCS进行身份验证的环境中,这可能是个问题。Athens可以解决这个问题,但是如果你要确保直接调用不会失败,Erdem为全局git配置文件提供了这些设置。
代码19
[url "[email protected]:"]
insteadOf = "https://github.com"
pushInsteadOf = "github:"
pushInsteadOf = "git://github.com/"
结论
当你开始在自己的项目中使用模块时,请确保提前决定要使用的模块镜像。如果你使用私有VCS或隐私安全是一个大问题,那么使用私有模块镜像是你的最佳选择。这将提供你所需的所有安全性,用于获取模块的更好性能以及最高级别的隐私保护。Athens是运行私有模块镜像的理想选择,因为它提供了模块缓存和校验和数据库代理。
如果要检查Go工具是否遵循你的配置,并且所选的模块镜像是否正确代理了校验和数据库,Go工具提供一个名为go mod verify
的命令。此命令可以检查下载的依赖项是否未被修改。它将检查本地模块缓存中的内容或即将在1.15版中发布的内容,该命令可以检查vendor文件夹。
试用这些配置项,找到能满足你需求的最佳解决方案。