正确使用Go DockerSDK(gomod)

1. 背景

1.1 go语言特性: 标准导入路径(canonical import path)

Go 1.4 introduces an annotation for package clauses in Go source that identify a canonical import path for the package. If an import is attempted using a path that is not canonical, the go command will refuse to compile the importing package.

https://golang.org/doc/go1.4#canonicalimports

go1.4 引入了一个特性,在package后可以使用注释声明导入这个包的标准路径, 叫做canonical import path。如果导入某个包的路径与这个canonical import path不同,则go命令会拒绝编译该包。

例如:

package pdf // import "rsc.io/pdf"

1.2 docker项目更名

docker详细开发现状参见: docker开发现状

这里要了解的是,docker曾在2017年将项目迁移到github.com/moby/moby

1.3 docker项目的依赖管理方式

go语言在1.11版本时发布了gomod特性来管理依赖,非常好用,但也有一些用起来不方便的地方,例如必须依赖于tag来管理版本。

docker未使用gomod管理依赖,使用的方法是在vendor中明细每个依赖的commit记录:

例如docker/cli/vendor.conf中导入了github.com/docker/docker的commit记录,这使得github.com/docker/docker即使没有发布更新的release记录,也能进行更新。

github.com/docker/docker                            a09e6e323e55e1a9b21df9c2c555f5668df3ac9b
github.com/docker/docker-credential-helpers         54f0238b6bf101fc3ad3b34114cb5520beb562f5 # v0.6.3
github.com/docker/go                                d30aec9fd63c35133f8f79c3412ad91a3b08be06 # Contains a customized version of canonical/json and is used by Notary. The package is periodically rebased on current Go versions.

2. 目的: 使用最新版本的docker

3. 问题与原因

3.1 docker/docker版本过低

根据官方库的指导,应该使用import github.com/docker/docker/client,而github.com/moby/moby/client是不存在的

3.2 Sirupsen/logrus更名冲突

go: finding github.com/docker/libtrust latest
go: finding github.com/vbatts/tar-split/tar/storage latest
go: finding github.com/vbatts/tar-split/tar/asm latest
go: finding github.com/vbatts/tar-split/tar latest
go: extracting github.com/Sirupsen/logrus v1.4.2
go: finding github.com/opencontainers/runtime-spec/specs-go latest
go: finding github.com/docker/distribution/digest latest
go: finding github.com/docker/docker/errdefs latest
go: github.com/Sirupsen/logrus@v1.4.2: parsing go.mod: unexpected module path "github.com/sirupsen/logrus"
go: error loading module requirements

4. 解决方案

因为历史原因,docker仓库从github.com/docker/docker更换到github.com/moby/moby, 而github.com/docker/docker已于release1.13.1停止更新,因此我们的项目中如果import path是github.com/docker/docker, 是只能获取到v1.13.1的。

而docker又不愿意更换import path, 所以才有此问题。(docker使用的custom import path)

参考这个issue, https://github.com/golang/go/issues/28489, 可以在gomod中替换仓库地址而不更改import path

在go.mod文件中添加replace语句

require (
	github.com/docker/docker v1.13.1
)

replace github.com/docker/docker => github.com/docker/engine v19.03.5
// 使用具体commit亦可
// replace github.com/docker/docker => github.com/docker/engine v0.0.0-20191113042239-ea84732a7725

其中docker-engine的最新版本可以在此地址查询:https://github.com/docker/engine/releases,但是这里不能使用latest,只能使用固定版本号

如果有多个docker库时,需要注意每个库版本的对应关系。例如docker/cli调用了docker/docker库,他们的对应关系可以在docker/cli/vendor.conf中查询到。我们可以如此调用:

require (
	github.com/docker/cli v0.0.0-20191220145525-ba63a92655c0
	github.com/docker/docker v1.13.1
	github.com/docker/swarmkit v1.12.0 // indirect
)

replace github.com/docker/cli => github.com/docker/cli v0.0.0-20191220145525-ba63a92655c0
replace github.com/docker/docker => github.com/docker/docker v0.7.3-0.20191025225506-a09e6e323e55
replace github.com/docker/swarmkit => github.com/docker/swarmkit v1.12.1-0.20190717134425-7dded76ec532

当然直接在require语句中写清楚commit记录也是可以的。

也可以使用在docker/engine中查询到tag记录,执行go mod tidy来获取对应的commit记录 require ( github.com/docker/docker v19.03.12 )

例如使用以上语句声明docker版本,执行go mod tidy命令时,会自动替换成commit时间-hash的形式。

$ go mod tidy
go: downloading github.com/docker/docker v0.0.0-20200618181300-9dc6525e6118