本文是我们在对 etcd client v3 进行二次开发时,对 go module 引入包时出错的原因进行分析,最终解决问题的记录。
- 写作时间(初稿):
2020/12/04
- golang 版本:
go version go1.14.7 windows/amd64
- goproxy 配置:
https://goproxy.cn,direct
- etcd 最新版本(截至初稿,下同):
v3.4.14
- goproxy 缓存的最新版 etcd 库(截至初稿,以
https://pkg.go.dev
为准,下同):
pkg.go.dev/go.etcd.io/etcd (版本:v3.3.25+incompatible
)
pkg.go.dev/github.com/coreos/etcd (版本:v3.3.25+incompatible
)
pkg.go.dev/github.com/etcd-io/etcd (版本:v3.3.25+incompatible
) - bbolt 最新版本:
v1.3.5
- goproxy 缓存的最新版 bbolt 库:
pkg.go.dev/github.com/coreos/bbolt (版本:v1.3.3
,此为问题所在)
pkg.go.dev/go.etcd.io/bbolt (版本:v1.3.5
)
正文
由于项目需要,我们需要封装 etcd 的 client。以下面代码为例:
import "go.etcd.io/etcd/clientv3"
type Etcd struct {
client *clientv3.Client
}
在执行 go mod tidy 命令时,go mod 报了以下错误:
go: found google.golang.org/grpc/examples/helloworld/helloworld in google.golang.org/grpc/examples v0.0.0-20201203175230-2efef8fd1214
go: etcd-import-hole-filling imports
go.etcd.io/etcd/clientv3 tested by
go.etcd.io/etcd/clientv3.test imports
github.com/coreos/etcd/auth imports
github.com/coreos/etcd/mvcc/backend imports
github.com/coreos/bbolt: github.com/coreos/bbolt@v1.3.5: parsing go.mod:
module declares its path as: go.etcd.io/bbolt
but was required as: github.com/coreos/bbolt
查 etcd 的 GitHub issue 发现有人出现同样的问题:[issue 224] github.com/etcd-io/bbolt/issues/224 ,经过尝试各解决方法均以失败告终。
尝试使用 replace 解决
我们在 go.mod 文件中使用 replace,将依赖的 github.com/coreos/bbolt
修改为 go.etcd.io/bbolt
进行尝试。
replace github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.5
再次执行 go mod tidy 命令时,发现 bbolt 冲突已顺利解决,但出现了以下错误:
go: finding module for package google.golang.org/grpc/naming
etcd-import-hole-filling imports
go.etcd.io/etcd/clientv3 tested by
go.etcd.io/etcd/clientv3.test imports
github.com/coreos/etcd/integration imports
github.com/coreos/etcd/proxy/grpcproxy imports
google.golang.org/grpc/naming: module google.golang.org/grpc@latest found (v1.34.0), but does not contain package google.golang.org/grpc/naming
根据错误信息判断是 grpc 客户端版本问题。这是 etcd 的老问题了。在 pkg.go.dev 上查找哪个版本的 grpc 库包含 naming 模块,最终查到 v1.29.0,用 replace 替代:
replace google.golang.org/grpc => google.golang.org/grpc v1.29.0
替代后依然报错:
google.golang.org/grpc/examples/helloworld/helloworld: ambiguous import: found package google.golang.org/grpc/examples/helloworld/helloworld in multiple modules:
google.golang.org/grpc v1.34.0 (C:\Users\Administrator\go\pkg\mod\google.golang.org\grpc@v1.29.0\examples\helloworld\helloworld)
google.golang.org/grpc/examples v0.0.0-20201203175230-2efef8fd1214
注释掉 go.mod 中 require 的 google.golang.org/grpc/examples,终于顺利完成 go mod tidy。
然而没想到的是 go build 报错了:
github.com/coreos/etcd/clientv3/balancer/resolver/endpoint
..\..\pkg\mod\github.com\coreos\etcd@v3.3.25+incompatible\clientv3\balancer\resolver\endpoint\endpoint.go:114:78: undefined: resolver.BuildOption
..\..\pkg\mod\github.com\coreos\etcd@v3.3.25+incompatible\clientv3\balancer\resolver\endpoint\endpoint.go:182:31: undefined: resolver.ResolveNowOption
github.com/coreos/etcd/clientv3/balancer/picker
..\..\pkg\mod\github.com\coreos\etcd@v3.3.25+incompatible\clientv3\balancer\picker\err.go:37:44: undefined: balancer.PickOptions
..\..\pkg\mod\github.com\coreos\etcd@v3.3.25+incompatible\clientv3\balancer\picker\roundrobin_balanced.go:55:54: undefined: balancer.PickOptions
经过多方筛查,发现还是 grpc client 的问题。由于 naming 模块在 v1.30.0 后被弃用,解析名称的包更换为 resolver。而我们使用的是 v1.29.0 版本,无法兼容。
至此,该解决方案宣告失败:如果不替代 grpc 版本,则无法通过 go mod tidy,替代则无法通过 go build。
再次尝试
我们再次细查 etcd 的 GitHub 仓库,发现 master 分支中目录有修改(etcd 正准备发布 v3.5.0-pre 版本),将 clientv2 和 clientv3 两个路径合并为 client(分别修改为 client/v2 和 client/v3)。查询 pkg.go.dev 发现存在 go.etcd.io/etcd/client/v3 包。于是修改 import。
// import "go.etcd.io/etcd/clientv3"
import go.etcd.io/etcd/client/v3
go mod tidy 依然失败。相关信息见 [issue 12484] github.com/etcd-io/etcd/issues/12484 。
go: found go.etcd.io/etcd/client/v3 in go.etcd.io/etcd/client/v3 v3.0.0-20201204010209-ca866c02422f
go: go.etcd.io/etcd/client/v3@v3.0.0-20201204010209-ca866c02422f requires
go.etcd.io/etcd/api/v3@v3.5.0-pre: reading go.etcd.io/etcd/api/api/go.mod at revision api/v3.5.0-pre: unknown revision api/v3.5.0-pre
我们尝试了若干解决方法,过程省略,总之最后没有成功。
第三次尝试
我们发现,pkg.go.dev 上能找到的 etcd client 库最新版本都是 v3.3.25+incompatible,v3.4.0 以后的版本为什么没有我们不得而知。etcd 在 v3.3.25 版本以后,官方仓库从 GitHub 迁移到 go.etcd.io,并将coreos/etcd 仓库更名为 etcd-io/etcd。v3.4.0 后的版本一直沿用该仓库地址至今。
既然 go mod 能找到的最新版本只有v3.3.25,那么使用 github.com/coreos/etcd 作为依赖,不使用 go.etcd.io 会如何?
// import "go.etcd.io/etcd/clientv3"
import "github.com/coreos/etcd/clientv3"
运行结果:
go: etcd-import-hole-filling imports
github.com/coreos/etcd/clientv3 tested by
github.com/coreos/etcd/clientv3.test imports
github.com/coreos/etcd/auth imports
github.com/coreos/etcd/mvcc/backend imports
github.com/coreos/bbolt: github.com/coreos/bbolt@v1.3.5: parsing go.mod:
module declares its path as: go.etcd.io/bbolt
but was required as: github.com/coreos/bbolt
还是碰到 bbolt 包名冲突问题。这次我们不使用 go.etcd.io/bbolt 代替,而是查找 pkg.go.dev 上能最新的 github.com/coreos/bbolt 版本,发现最新版本号为 v1.3.3。再将 grpc 替代为 v1.26.0 版本,此版本较多人使用。
replace github.com/coreos/bbolt => github.com/coreos/bbolt v1.3.3
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0
执行 go mod tidy,成功!再执行 go build,也成功!问题终于解决!
总结
这个坑的形成有几方面原因:etcd 的迁移,和 go module 的机制是主要原因。如果不指定版本号,go module总是会尝试下载最新版的库。由于 bbolt 库地址的变化导致包名的变化,包含 bbolt 的项目的 go.mod 文件中 require 的包名与 bbolt 的 go.mod 中注册的包名不一致,导致出错。另外,grpc client 的问题也已经存在很长时间,目前通常还是使用 v1.26.0。这些问题都需要各位开发者去解决。