导读:go modules
是 golang 1.11 新加的特性。如今 1.13 都已经发布了第 7 个小版本了,几乎所有大项目均已开始使用,这自然也包括 Kubernetes 生态中的众多项目。但modules 的各项功能看似简单,却并没有那么好用,于是便想给大家分享一下使用心得,希望大家也能在最短时间内学会 modules 的使用,避免踩坑。
简单说就是包管理,Golang 的包管理素来以混乱著称,以前是依赖 $GOPATH
,只要你的代码放在指定路径下就好了,完全没有“包管理”的概念。被社区吐槽了很久以后开始搞 vendor
机制,简单来说就是代码不光是可以放到指定路径,还可以放在项目自身路径的 vendor 文件夹。这个解决的问题是:你引用的代码包上游变更不会直接影响你的项目,这显然是开始关心“包版本”了。遗憾的是依旧没有解决包管理的问题,比如不同的包依赖了同一个包的不同版本怎么办?版本间代码冲突怎么办?vendor
机制并没有解决,于是围绕 vendor/
社区就出了几十个包管理工具,一时间百花齐放、百家争鸣、各有所长,导致 golang 的包管理生态变得有些混乱。对这段历史感兴趣的可以阅读下《Go 包管理的前世今生》。
更有意思的是,在 go 官方社区看到包管理工具的乱象后,也做了个功能类似的工具 dep
,原理与其他各类依靠vendor/
机制的包管理工具类似,准备对包管理做统一。当大家对 dep
工具报以期望并纷纷开始切换到 dep
工具管理依赖包的时候,go 官方又发布了现在的 modules 机制,完全放弃了之前的 dep
工具与 vendor
机制。这样的操作在社区引起了巨大的争议,modules 与 go get
、go build
等官方工具生态有很好的集成,官方的意图自然是希望抛开原有的历史包袱,通过全新的方式拯救世界。然而实际体验下来,却依旧不尽如人意。
总的来说大趋势已经是用 modules
,go1.13 也对 modules 机制做了不少工作。
言归正传,本文的目标是希望能用 5~10 分钟时间带您学会使用 go modules,然后通过 QA 的形式,描述一些常见的问题。如果希望详细理解相关内容,也可以参考官方文档。
modules 机制在 go 里面的子命令是 go mod
。
modules 机制是 go 1.11 才引入的,所以开始用之前先检查下自己的 go 版本 go version
,建议使用最新的 1.13 版本,涵盖了 module 机制相关的较多更新和功能。
在保证 go 版本至少在 1.11 或以上之后,就要开启一下环境变量 GO111MODULE,启用 module 机制。
export GO111MODULE=on
或者设置为 auto 模式,这样在 GOPATH 路径下的项目可以不使用 module 机制。
export GO111MODULE=auto
建议加到 ~/.bashrc
、~/.zshrc
等配置中启动便自动生效。
如果您的项目之前已经用 modules 管理了,那么到此为止你本地的环境已经完成初始化了。
如果项目里之前没有使用 modules,切换过来也很简单,删除原先的 vendor
文件夹(保险起见可以移动到项目之外的地方),在项目里执行一下初始化命令即可。
go mod init [module名称]
包名跟以前一样,还是跟 go path 强关联的,比如我们的项目一般是在 http://github.com/oam-dev/oam-go-sdk
,那么你的包名就是这个了。
go mod init github.com/oam-dev/oam-go-sdk
初始化完成后就会看见项目里有个 go.mod
文件。
然后通过 go mod download
就可以下载所有原先 vendor
中的依赖。
使用了 go module
以后,你的许多命令就会与包管理集成,比如 go get
、go build
、go run
都会自动查找并在go.mod
里面更新依赖。
所以按照 Go 官方团队的意思,一般情况下,你根本不用关心包管理的问题了。( 当然,这真的纯粹只是官方在 YY )
所以到这里你就已经学会 go modules 了,没超过5分钟吧?
实际因为 go 官方对包管理重视的太晚,各种包都没有版本的概念,随随便便就会出现各种冲突,而且由于 go modules 实在是太自动了,所以就算你学会了怎么用 modules,最后还是会比较头疼。
下面我们就以 FAQ 的形式回答项目中遇到各种问题怎么办。
Go module 加入了代理的机制,只要设置一个代理地址,就可以提供代理访问。阿里云就提供了这样一个 go 的代理 ,是完全免费的服务。公共代理仓库会代理并缓存go模块,你可以利用该代理来避免 DNS 污染或其他问题导致的模块拉取缓慢或失败的问题,加速你的项目构建。
设置的方式非常简单,只需要设置如下环境变量即可,执行命令:
export GOPROXY=https://mirrors.aliyun.com/goproxy/
go1.13 加入了 mirror 机制,可以通过 go env -w
设置 mirror,其实就是之前的 GOPROXY 做的更到位一些,执行命令:
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
逗号后面可以增加多个 proxy,最后的 direct 则是在所有 proxy 都找不到的时候,直接访问,代理访问不到的私有仓库就可以正常使用了。
这个功能基本上是在家远程办公的必备工具了。
这个几乎是最常见的问题,比较简单的解决方案是 hack 一下 git 配置:
git config --global url."[email protected]:/.git".insteadOf "https://gitlab.your-company.com//.git"
这个方案依赖你本地的 ~/.ssh/id_rsa
, 这样你就可以正常 go get
了。
git config
,却依赖你本地的 ~/.ssh/id_rsa
,在构建时可以通过 multistage-build 把私钥 add 到 stage 0
里面 build,然后用后面新的 stage 生成镜像,这样构建的镜像就不会包含私钥;go mod vendor
把包缓存下来,在 Dockerfile 构建镜像过程中还是用 GOPATH 和 Vendor 机制来管理依赖。先查看版本:
$ go list -m -versions rsc.io/sampler
rsc.io/sampler v1.0.0 v1.2.0 v1.2.1 v1.3.0 v1.3.1 v1.99.99
再更新:
$ go get rsc.io/[email protected]
go: finding rsc.io/sampler v1.3.1
go: downloading rsc.io/sampler v1.3.1
go: extracting rsc.io/sampler v1.3.1
$ go test
PASS
ok example.com/hello 0.022s
go 的依赖与项目名直接相关,这就导致如果我们使用了 github 上的项目,然后项目的维护人员突发奇想改个项目名称,就会导致所有依赖它的项目都无法找到依赖。
还好有 replace 的机制:
replace golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
这就要梳理版本了,是最没有捷径的。一个比较简单的办法是把所有 go.mod
里不需要指定版本的包全部删掉,仅指定必要的包版本,然后通过 go build
让项目自动构建依赖包的版本。
通过 go mod graph 可以查看具体依赖路径:
$ go mod graph
github.com/oam-dev/oam-go-sdk github.com/go-logr/[email protected]
github.com/oam-dev/oam-go-sdk github.com/onsi/[email protected]
github.com/oam-dev/oam-go-sdk github.com/onsi/[email protected]
github.com/oam-dev/oam-go-sdk github.com/stretchr/[email protected]
github.com/oam-dev/oam-go-sdk golang.org/x/[email protected]
github.com/oam-dev/oam-go-sdk k8s.io/[email protected]
github.com/oam-dev/oam-go-sdk k8s.io/[email protected]
github.com/oam-dev/oam-go-sdk k8s.io/[email protected]
github.com/oam-dev/oam-go-sdk sigs.k8s.io/[email protected]
...
左边是项目包,右边是被依赖的包和版本。
如果确实存在两个需要指定版本的包互相冲突,那就要做取舍,修改代码,升级或降级某个包了。
如果在代码调试过程中,涉及到修改其他依赖项目代码,这时候就要引用本地包,也可以采用 replace 机制:
require (
golang.org/x/crypto v0.0.0
)
replace golang.org/x/crypto v0.0.0 => ../crypto
后面这个就是个相对项目路径的本地依赖所在路径。
解决了上面的这些问题,基本上你就可以愉快的使用 module 功能啦。
go mod 里还有一些其他功能,也在此列举,方便大家查阅:
go mod子命令 | 功能 |
---|---|
download | download modules to local cache(下载依赖包) |
edit | edit go.mod from tools or scripts(编辑 go.mod) |
graph | print module requirement graph(打印模块依赖图) |
init | initialize new module in current directory(在当前目录初始化mod) |
tidy | add missing and remove unused modules(拉取缺少的模块,移除不用的模块) |
vendor | make vendored copy of dependencies(将依赖复制到 vendor下) |
verify | verify dependencies have expected content(验证依赖是否正确) |
why | explain why packages or modules are needed(解释为什么需要依赖) |