LOADING

Follow me

Golang漫谈之mod详解
10月 17 2019|

Golang漫谈之mod详解

除了《Kubernetes GO》系列之外,对于golang相关知识,同时准备了《Golang 漫谈》以增雅趣,不足之处,万望海涵,在此特别感谢雨痕的Golang 源码剖析

Golang 1.13.1已在9月26日正式发布,主要修复CVE-2019-16276,当然docker等相关组件也同时做了update

Go modules是Golang官方在1.11版本推出的包管理工具,在此之前,周边社区提供多种包管理方案,在讨论此类方案之前,可以先回顾一个golang关于包管理的发展。该文章主要介绍,以下内容:

  • mod简介及入门
  • mod特性
  • 社区动向

一、mod简介

1.1 包管理历史发展

官方对于Golang的包管理前期一直未释出官方版本,这同样也是被人所诟病的一大缺陷,下面我们看看包管理的历史发展:

  • 在1.5版本前,所有的依赖都在GOPATH之下,没有版本控制,更别谈其他特性,该方式无法实现包的多版本依赖控制,如project A和B依赖不同的project C版本
  • 1.15推出vendor机制,即每个项目的跟目录下可以有一个vendor目录,里面存放该项目的依赖库,类似nodejs,在执行go build时会先查找vendor目录
  • 1.9版本推出dep、vgo,dep被废弃
  • 1.11版本推出modules机制,基于vgo实现,1.11.2得到增强,并在1.12中默认开启

除此之外,社区周边还活跃着以下几种包管理工具:

  • godep 废弃
  • glide 不再维护
  • govendor 维护
1.2 环境介绍

Golang版本: 1.12.7,在1.12之前,启用mod需要设置环境变量GO111MODULE=on,若需要关闭该特性使用GOPATH,则GO111MODULE=off,说明如下:

  • GO111MODULE=off:关闭mod特性,使用GOPATH
  • GO111MODULE=on:启用mod特性
  • GO111MODULE=auto:自动检测是否使用
1
2
➜  kubernetes git:(master) go version
go version go1.12.7 darwin/amd64
1.3 项目体验
项目初始化

mod的初始化工作:

  • 创建项目目录,如项目:indagate,该目录需要在GOPATH之外
  • 切换到项目目录,如/Users/zoues/Code/indagate
  • 执行初始化目录:go mod init <项目名称>,如 go mod init github.com/ustackq/indagate
1
2
3
4
5
6
7
8
9
10
11
➜  mkdir indagate
➜ cd indagate
go mod init github.com/ustackq/indagate
go: creating new go.mod: module github.com/ustackq/indagate
➜ indagate ll
total 8
-rw------- 1 zoues staff 25B 10 17 17:00 go.mod
➜ indagate cat go.mod
module github.com/ustackq/indagate

go 1.12

使用init命令,会自动生成go.mod文件,在该文件中,存储项目的第三方包的依赖版本,需要注意的是,只有在run/test或者go mod tidy时,才会触发依赖解析,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
➜  indagate git:(master) ✗ go mod tidy
➜ indagate git:(master) ✗ cat go.mod
module github.com/ustackq/indagate

go 1.12

require (
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0
...
)

➜ indagate git:(master) ✗ ls|grep go.
go.mod
go.sum

此时,可以发现上述目录多了go.sum文件,其用于记录每个包的版本及hash。go.mod 文件正常情况会包含 module 和 require 模块,除此之外还可以包含 replace 和 exclude等模块。

上传代码仓库

然后上传代码至代码仓库,如:

1
2
3
4
git init
git add *
git commit -am "First commit"
git push -u origin master

在上传完毕后,可以通过go get -v github.com/ustackq/indagate拉取进行测试,该命令获取的是master分支最新代码,这种方式不好之处就是无法对其进行版本管理。

在构建项目时,可以为代码库添加tag:

1
2
git tag v1.0.0
git push --tags

如果不打算维护小版本,可以简化成如下操作:

1
2
git checkout -b v1
git push -u origin v1
修改代码

项目在v1.0.1上新增功能,并且在v1修复某一个bug,操作如下:

1
2
3
git commit -m "Emphasize our friendliness" testmod.go
git tag v1.0.1
git push --tags origin v1
依赖升级

golang提供包依赖查询命令,go help list,如下所示:

1
2
3
4
5
6
7
8
9
10
➜  indagate git:(master) ✗ go list -m -u all
go: finding github.com/go-openapi/validate v0.19.4
go: finding gopkg.in/gomail.v2 latest
go: finding github.com/modern-go/concurrent latest
go: finding github.com/nfnt/resize latest
go: finding github.com/coreos/pkg latest
go: finding github.com/go-openapi/runtime v0.19.7
go: finding golang.org/x/sync latest
go: finding gopkg.in/check.v1 latest
...

若需要升/降某个包时,可以执行 go help get,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
go get -v <包>@<版本号>
# 该处为将validate v0.19.4降级到0.19.3
➜ indagate git:(master) ✗ go get -v github.com/go-openapi/[email protected].19.3
Fetching https://goproxy.io/github.com/go-openapi/validate/@v/list
github.com/go-openapi/validate
➜ indagate git:(master) ✗ cat go.sum|grep v0.19.3
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/loads v0.19.3 h1:jwIoahqCmaA5OBoc/B+1+Mu2L0Gr8xYQnbeyQEo/7b0=
github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI=
github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/strfmt v0.19.3 h1:eRfyY5SkaNJCAwmmMcADjY31ow9+N7MCLW7oRkbsINA=
github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
github.com/go-openapi/validate v0.19.3 h1:PAH/2DylwWcIU1s0Y7k3yNmeAgWOcKrNE2Q7Ww/kCg4=
github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo=
➜ indagate git:(master) ✗ cat go.mod|grep v0.19.3
github.com/go-openapi/loads v0.19.3
github.com/go-openapi/spec v0.19.3
github.com/go-openapi/strfmt v0.19.3
github.com/go-openapi/validate v0.19.3

需要注意的是,在 modules 模式开启和关闭的情况下,go get 的使用方式不是完全相同的。在 modules 模式开启的情况下,可以通过在 package 后面添加 @version 来表明要升级(降级)到某个版本。如果没有指明 version 的情况下,则默认先下载打了 tag 的 release 版本,比如 v0.4.5 或者 v1.2.3;如果没有 release 版本,则下载最新的 pre release 版本,比如 v0.0.1-pre1。如果还没有则下载最新的 commit。包的的tag需要遵循semver规范,否则modules 是无法管理的。version 的格式为 v(major).(minor).(patch) 即vX.Y.Z,更多信息可以参考参考链接 。

此外,在 modules 开启的模式下,go get 还支持 version 模糊查询,比如>v1.0.0表示大于v1.0.0的可使用版本;< v1.12.0 表示小于 v1.12.0 版本下最近可用的版本。版本的比较规按照semver规范的各个字段来进行。

vendor依赖

Go 1.5 推出了 vendor 机制,go mod 也可以支持 vendor 机制,将依赖包拷贝到 vendor 目录。但是像一些 test case 里面的依赖包并不会拷贝的 vendor 目录中。

1
2
3
4
5
6
7
8
9
➜  indagate git:(master) ✗ go help mod vendor
usage: go mod vendor [-v]

Vendor resets the main module's vendor directory to include all packages
needed to build and test all the main module's packages.
It does not include test code for vendored packages.

The -v flag causes vendor to print the names of vendored
modules and packages to standard error.
build

在处理完项目的初步功能,需要编译时,执行以下步骤

  • 清理不需要的依赖,下载所缺依赖:go mod tidy
  • 使用依赖编译执行文件:go build -mod vendor main.go

2 mod特性

上面介绍了 go modules 的简单使用方法,但是 modules 的一些更高级的特性没有介绍,将在下面进行展开。

  1. GoProxy
  2. Replace
GoProxy

proxy 顾名思义,代理服务器。众所周知,有些 Golang 的 package 在国内是无法直接 go get 的。在之前,我们解决这个问题,一般都是通过设置 http_proxy/https_proxy 来解决。GoProxy 相当于官方提供了一种 proxy 的方式让用户来进行包下载。要使用 GoProxy 只需要设置环境变量 GOPROXY 即可。目前公开的 GOPROXY 有:

  • goproxy.io
  • goproxy.cn: 由七牛云提供,参考 github repo

当然你也可以实现自己的 GoProxy 服务,比如项目中的依赖包含外部依赖和内部依赖的时候,那么只需要实现 module proxy protocal 协议即可。

值得注意的是,在最新 release 的 Go 1.13 版本中默认将 GOPROXY 设置为 https://proxy.golang.org,这个对于国内的开发者是无法直接使用的。所以如果升级了 Go 1.13 版本一定要把 GOPROXY 手动改掉。

Replace

replace 主要有以下典型应用:

  • 为了解决某些包发生改名的问题
  • 解决包名不规范问题,如github.com/sirupsen/Logrus当初的不规范问题,现在已经改名为github.com/sirupsen/logrus
  • 比如对于有些 golang.org/x/ 下面的包由于某些原因在国内是下载不了的,但是对应的包在 github 上面是有一份拷贝的,这个时候我们就可以将 go.mod 中的包进行 replace 操作

比较典型的是kubernetes中go.mod 的replace使用。

1
2
3
4
5
replace (
bitbucket.org/bertimus9/systemstat => bitbucket.org/bertimus9/systemstat v0.0.0-20180207000608-0eeff89b0690
cloud.google.com/go => cloud.google.com/go v0.38.0
...
)
SubCommand

modules 支持的 subcommand 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
➜  indagate git:(master) ✗ go help mod
Go mod provides access to operations on modules.

Note that support for modules is built into all the go commands,
not just 'go mod'. For example, day-to-day adding, removing, upgrading,
and downgrading of dependencies should be done using 'go get'.
See 'go help modules' for an overview of module functionality.

Usage:

go mod <command> [arguments]

The commands are:

download download modules to local cache
edit edit go.mod from tools or scripts
graph print module requirement graph
init initialize new module in current directory
tidy add missing and remove unused modules
vendor make vendored copy of dependencies
verify verify dependencies have expected content
why explain why packages or modules are needed

Use "go help mod <command>" for more information about a command.

对应解释如下:

  • download: 下载 modules 到本地缓存
  • edit: 提供一种命令行交互修改 go.mod 的方式
  • graph: 将 module 的依赖图在命令行打印出来,其实并不是很直观
  • init: 初始化 modules,会生成一个 go.mod 文件
  • tidy: 清理 go.mod 中的依赖,会添加缺失的依赖,同时移除没有用到的依赖
  • vendor: 将依赖包打包拷贝到项目的 vendor 目录下,值得注意的是并不会将 test code 中的依赖包打包到 vendor 中。这种设计在社区也引起过几次争论,但是并没有达成一致。
  • verify: verify 用来检测依赖包自下载之后是否被改动过。
  • why: 解释为什么 package 或者 module 是需要,但是看上去解释的理由并不是非常的直观。

4 社区动向

上面在讨论 GoProxy 的时候提到了 Go 1.13 默认设置环境变量 GOPROXY 的值,除此之外 Go 1.13 对 modules 还有哪些值得注意的改动呢?

特性

modules 在 Go 1.13 的版本下是默认开启的。

私库

前面也说到对于一些内部的 package,GoProxy 并不能很好的处理,Go 1.13 推出了 GOPRIVATE 机制。只需要设置这个环境变量,然后标识出哪些 package 是 private 的,那么对于这个 package 的处理将不会从 proxy 下载。GOPRIVATE 的值是一个以逗号分隔的列表,支持正则(正则语法遵守 Golang 的 包 path.Match)。下面是一个 GOPRIVATE 的示例:

1
GOPRIVATE=*.xxx.com,yy.zz/private
校验

GOSUMDB 的全称为 Go CheckSum Database,用来下载的包的安全性校验问题。包的安全性在使用 GoProxy 之后更容易出现,比如我们引用了一个不安全的 GoProxy 之后然后下载了一个不安全的包,这个时候就出现了安全性问题。对于这种情况,可以通过 GOSUMDB 来对包的哈希值进行校验。当然如果想要关闭哈希校验,可以将 GOSUMDB 设置为 off;如果要对部分包关闭哈希校验,则可以将包的前缀设置到环境变量中 GONOSUMDB 中,设置规则类似 GOPRIVATE。

关于 GOSUMDB 的配置格式为:<db_name>+<publickey>+<url>

1
2
3
GOSUMDB="sum.golang.org"
GOSUMDB="sum.golang.org+<publickey>"
GOSUMDB="sum.golang.org+<publickey> https://sum.golang.org"

上面三种配置都是合理的,因为对于 sum.golang.org,Go 自己知道其对应的 publickey 和 url,所以我们只要配置一个名字即可,对于另外一个 sum.golang.google.cn 也是一样。除此之外的,都需要指明 publickey,url 默认是 https://<db_name>


后续

**请关注后续golang内置数据结构漫谈
Golang 包管理历经多个版本,目前来看并没有一个完全让开发者满意的方案,比如类似 Java 的 maven 的包管理方式。很多开发者也表示 modules 的管理方式也不是很直观。其实 Golang 的 package 使用 git 的管理方式来管理其实是一个很好的管理方式,我们最需要的其实如何能让普通开发者获取到任何 package 的心智负担降到最低。值得欣慰的是我们确实有看到大家在这个方向上的努力,比如 modules 的 GoProxy。


参考资料

zouyee wechat

微信扫一扫
关注该公众号

0%