拉取私有module
# 拉取私有 module
背景:随着内部 Go 使用者和 Go 项目的增多,出现“重造轮子”的问题。抽取公共代码放入一个独立的、可被复用的内部私有仓库成为了必然,这样就有了拉取私有 Go Module 的需求。
配置公共 GOPROXY 服务,拉取所有公共 Go Module:
拉取托管在公共 vcs 私有仓库中的私有 Go Module:
在每个开发机上,配置公共 GOPROXY 服务拉取公共 Go Module,同时再把私有仓库配置到 GOPRIVATE 环境变量,就可以了。这样,所有私有 module 的拉取,都会直连代码托管服务器,不会走 GOPROXY 代理服务,也不会去 GOSUMDB 服务器做 Go 包的 hash 值校验。
拉取公司 / 组织内部的 vcs(代码版本控制)服务器的私有 Go Module:
两个参考方案:
第一个方案是通过直连组织公司内部的私有 Go Module 服务器拉取。
公司内部会搭建一个内部 goproxy 服务(也就是上图中的 in-house goproxy)。
两个目的:
- 一是,为那些无法直接访问外网的开发机器,以及 ci 机器提供拉取外部 Go Module 的途径;
- 二来,由于 in-house goproxy 的 cache 的存在,这样做还可以加速公共 Go Module 的拉取效率。
对于私有 Go Module,开发机只需要将它配置到 GOPRIVATE 环境变量中就可以了,这样,Go 命令在拉取私有 Go Module 时,就不会再走 GOPROXY,而会采用直接访问 vcs(如上图中的 git.yourcompany.com)的方式拉取私有 Go Module。
这个方案十分适合内部有完备 IT 基础设施的公司。这类型的公司内部的 vcs 服务器都可以通过域名访问(比如 git.yourcompany.com/user/repo),因此,公司内部员工可以像访问公共 vcs 服务那样,访问内部 vcs 服务器上的私有 Go Module。
第二种方案,是将外部 Go Module 与私有 Go Module 都交给内部统一的 GOPROXY 服务去处理。
开发者只需要把 GOPROXY 配置为 in-house goproxy,就可以统一拉取外部 Go Module 与私有 Go Module。
但由于 go 命令默认会对所有通过 goproxy 拉取的 Go Module,进行 sum 校验(默认到 sum.golang.org),而私有 Go Module 在公共 sum 验证 server 中又没有数据记录。因此,开发者需要将私有 Go Module 填到 GONOSUMDB 环境变量中,这样,go 命令就不会对其进行 sum 校验了。
注意:in-house goproxy 需要拥有对所有 private module 所在 repo 的访问权限,才能保证每个私有 Go Module 都拉取成功。
推荐第二个方案。在第二个方案中,可以将所有复杂性都交给 in-house goproxy 这个节点,开发人员可以无差别地拉取公共 module 与私有 module,心智负担降到最低。
统一 Goproxy 方案可行的实现思路与具体步骤:
选择一个 GOPROXY 实现
Go module proxy 协议规范发布后,Go 社区出现了很多成熟的 Goproxy 开源实现,比如有最初的athens (opens new window),还有国内的两个优秀的开源实现:goproxy.cn (opens new window)和goproxy.io (opens new window)等。其中,goproxy.io 在官方站点给出了企业内部部署 (opens new window)的方法,所以就基于 goproxy.io 来实现方案。
在 in-house goproxy 节点上执行这几个步骤安装 goproxy:
$mkdir ~/.bin/goproxy
$cd ~/.bin/goproxy
$git clone https://github.com/goproxyio/goproxy.git
$cd goproxy
$make
2
3
4
5
生成名为 goproxy 的可执行文件。
然后,建立 goproxy cache 目录:
$mkdir /root/.bin/goproxy/goproxy/bin/cache
再启动 goproxy:
$./goproxy -listen=0.0.0.0:8081 -cacheDir=/root/.bin/goproxy/goproxy/bin/cache -proxy https://goproxy.io
goproxy.io: ProxyHost https://goproxy.io
2
启动后,goproxy 会在 8081 端口上监听(即便不指定,goproxy 的默认端口也是 8081),指定的上游 goproxy 服务为 goproxy.io。
注意:goproxy 的这个启动参数并不是最终版本的,这里仅仅验证一下 goproxy 是否能按预期工作。
验证:
首先,在开发机上配置 GOPROXY 环境变量指向 10.10.20.20:8081:
// .bashrc
export GOPROXY=http://10.10.20.20:8081
2
生效环境变量后,执行下面命令:
$go get github.com/pkg/errors
结果和预期的一致,开发机顺利下载了 github.com/pkg/errors 包。可以在 goproxy 侧,看到了相应的日志:
goproxy.io: ------ --- /github.com/pkg/@v/list [proxy]
goproxy.io: ------ --- /github.com/pkg/errors/@v/list [proxy]
goproxy.io: ------ --- /github.com/@v/list [proxy]
goproxy.io: 0.146s 404 /github.com/@v/list
goproxy.io: 0.156s 404 /github.com/pkg/@v/list
goproxy.io: 0.157s 200 /github.com/pkg/errors/@v/list
2
3
4
5
6
在 goproxy 的 cache 目录下,也看到了下载并缓存的 github.com/pkg/errors 包:
$cd /root/.bin/goproxy/goproxy/bin/cache
$tree
.
└── pkg
└── mod
└── cache
└── download
└── github.com
└── pkg
└── errors
└── @v
└── list
8 directories, 1 file
2
3
4
5
6
7
8
9
10
11
12
13
14
这就标志着 goproxy 服务搭建成功,并可以正常运作了。
自定义包导入路径并将其映射到内部的 vcs 仓库
一般公司可能没有为 vcs 服务器分配域名,也不能在 Go 私有包的导入路径中放入 ip 地址,因此需要给私有 Go Module 自定义一个路径,并映射到内部的vcs仓库路径
使用 Google 云开源的一个名为 govanityurls (opens new window) 的工具,来为私有 module 自定义包导入路径。然后,结合 govanityurls 和 nginx,就可以将私有 Go Module 的导入路径映射为其在 vcs 上的代码仓库的真实地址。具体原理:
首先,goproxy 修改启动命令参数,把收到的拉取私有 Go Module(mycompany.com/go/module1)的请求转发给公共代理:
$./goproxy -listen=0.0.0.0:8081 -cacheDir=/root/.bin/goproxy/goproxy/bin/cache -proxy https://goproxy.io -exclude "mycompany.com/go"
这样,凡是与 -exclude 后面的值匹配的 Go Module 拉取请求,goproxy 都不会转给 goproxy.io,而是直接请求 Go Module 的“源站”。
然后,govanityurls 将这个“源站”的地址,转换为企业内部 vcs 服务中的一个仓库地址。假设 mycompany.com 这个域名并不存在(很多小公司没有内部域名解析能力),在 goproxy 所在节点的 /etc/hosts 中加上这样一条记录:
127.0.0.1 mycompany.com
goproxy 发出的到 mycompany.com 的请求实际上是发向了本机,监听本机 80 端口的nginx。
nginx 关于 mycompany.com 这一主机的配置如下:
// /etc/nginx/conf.d/gomodule.conf
server {
listen 80;
server_name mycompany.com;
location /go {
proxy_pass http://127.0.0.1:8080;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
对于路径为 mycompany.com/go/xxx 的请求,nginx 将请求转发给了 127.0.0.1:8080,而这个服务地址恰恰就是 govanityurls 工具监听的地址。
govanityurls 可以帮助 Gopher 快速实现自定义 Go 包的 go get 导入路径。
它是前 Go 核心开发团队成员Jaana B.Dogan开源的一个工具,govanityurls 本身,就好比一个“导航”服务器。当 go 命令向自定义包地址发起请求时,实际上是将请求发送给了 govanityurls 服务,之后,govanityurls 会将请求中的包所在仓库的真实地址(从 vanity.yaml 配置文件中读取)返回给 go 命令,后续 go 命令再从真实的仓库地址获取包数据。
注:govanityurls 的安装方法很简单,直接 go install/go get github.com/GoogleCloudPlatform/govanityurls 就可以了。
vanity.yaml 的配置如下:
host: mycompany.com
paths:
/go/module1:
repo: ssh://admin@10.10.30.30/module1
vcs: git
2
3
4
5
6
当 govanityurls 收到 nginx 转发的请求后,会将请求与 vanity.yaml 中配置的 module 路径相匹配,如果匹配 ok,就会将该 module 的真实 repo 地址,通过 go 命令期望的应答格式返回。在这里 module1 对应的真实 vcs 上的仓库地址为:ssh://admin@10.10.30.30/module1。所以,goproxy 会收到这个地址,并再次向这个真实地址发起请求,并最终将 module1 缓存到本地 cache 并返回给客户端。
开发机 (客户端) 的设置
凡是通过 GOPROXY 拉取的 Go Module,go 命令都会默认把它的 sum 值放到公共 GOSUM 服务器上去校验。
因为拉取的是私有 Go Module,GOSUM 服务器上并没有 Go Module 的 sum 数据。这样就会导致 go build 命令报错,无法继续构建过程。需要对开发机客户端做配置。
将 mycompany.com/go,作为一个值设置到 GONOSUMDB 环境变量中:
export GONOSUMDB=mycompany.com/go
相当于告诉 go 命令,凡是与 mycompany.com/go 匹配的 Go Module,都不需要在做 sum 校验了。
到这里,就实现了拉取私有 Go Module 的方案。
方案的“不足”
第一点:开发者还是需要额外配置 GONOSUMDB 变量。
解决建议:公司内部可以将私有 go 项目都放在一个特定域名下,这样就不需要为每个 go 私有项目单独增加 GONOSUMDB 配置了,只需要配置一次就可以了。
第二点:新增私有 Go Module,vanity.yaml 需要手工同步更新。
建议:在一个 vcs 仓库中管理多个私有 Go Module。相比于最初 go 官方建议的一个 repo 只管理一个 module,新版本的 go 在一个 repo 下管理多个 Go Module (opens new window) 方面,已经可以通过 repo 的 tag 来区别同一个 repo 下的不同 Go Module。
第三点:无法划分权限。
goproxy 所在节点需要具备访问所有私有 Go Module 所在 vcs repo 的权限,但又无法对 go 开发者端做出有差别授权,这样,只要是 goproxy 能拉取到的私有 Go Module,go 开发者都能拉取到。
如果觉得这是个问题,那么只能使用前面提到的第一个方案,也就是直连私有 Go Module 的源码服务器的方案了。