手记

etcd client go 排坑记录

本文是我们在对 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。这些问题都需要各位开发者去解决。

1人推荐
随时随地看视频
慕课网APP