五分钟学会使用 go modules

五分钟学会使用 go modules

导读go modules 是 golang 1.11 新加的特性。如今 1.13 都已经发布了第 7 个小版本了,几乎所有大项目均已开始使用,这自然也包括 Kubernetes 生态中的众多项目。但modules 的各项功能看似简单,却并没有那么好用,于是便想给大家分享一下使用心得,希望大家也能在最短时间内学会 modules 的使用,避免踩坑。

modules 是什么?

简单说就是包管理,Golang 的包管理素来以混乱著称,以前是依赖 $GOPATH,只要你的代码放在指定路径下就好了,完全没有“包管理”的概念。被社区吐槽了很久以后开始搞 vendor 机制,简单来说就是代码不光是可以放到指定路径,还可以放在项目自身路径的 vendor 文件夹。这个解决的问题是:你引用的代码包上游变更不会直接影响你的项目,这显然是开始关心“包版本”了。遗憾的是依旧没有解决包管理的问题,比如不同的包依赖了同一个包的不同版本怎么办?版本间代码冲突怎么办?vendor 机制并没有解决,于是围绕 vendor/ 社区就出了几十个包管理工具,一时间百花齐放、百家争鸣、各有所长,导致 golang 的包管理生态变得有些混乱。对这段历史感兴趣的可以阅读下《Go 包管理的前世今生》。

更有意思的是,在 go 官方社区看到包管理工具的乱象后,也做了个功能类似的工具 dep,原理与其他各类依靠vendor/ 机制的包管理工具类似,准备对包管理做统一。当大家对 dep 工具报以期望并纷纷开始切换到 dep 工具管理依赖包的时候,go 官方又发布了现在的 modules 机制,完全放弃了之前的 dep 工具与 vendor 机制。这样的操作在社区引起了巨大的争议,modules 与 go getgo 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 getgo buildgo run 都会自动查找并在go.mod 里面更新依赖。

所以按照 Go 官方团队的意思,一般情况下,你根本不用关心包管理的问题了。( 当然,这真的纯粹只是官方在 YY )

所以到这里你就已经学会 go modules 了,没超过5分钟吧?

FAQ

实际因为 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 都找不到的时候,直接访问,代理访问不到的私有仓库就可以正常使用了。

这个功能基本上是在家远程办公的必备工具了。

公司通过 gitlab 搭建了私有库,二方依赖库下载不下来怎么办?

这个几乎是最常见的问题,比较简单的解决方案是 hack 一下 git 配置:

git config --global url."[email protected]:/.git".insteadOf "https://gitlab.your-company.com//.git"

这个方案依赖你本地的 ~/.ssh/id_rsa, 这样你就可以正常 go get 了。

Dockerfile 中构建镜像怎么解决私有库的依赖包问题?

  • 方案一:上述方式通过修改 git config ,却依赖你本地的 ~/.ssh/id_rsa,在构建时可以通过 multistage-build 把私钥 add 到 stage 0 里面 build,然后用后面新的 stage 生成镜像,这样构建的镜像就不会包含私钥;
  • 方案二: 更为安全的方式是,在每次构建 Docker 镜像之前,先在本地用 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 里还有一些其他功能,也在此列举,方便大家查阅:

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(解释为什么需要依赖)

你可能感兴趣的:(Go)