wordpress菜单背景,沈阳网站的优化,99到家微网站什么做的,wordpress换主题Go 包操作之如何拉取私有的Go Module
在前面#xff0c;我们已经了解了GO 项目依赖包管理与Go Module常规操作#xff0c;Go Module 构建模式已经成为了 Go 语言的依赖管理与构建的标准。
在平时使用Go Module 时候#xff0c;可能会遇到以下问题#xff1a;
在某 modul…Go 包操作之如何拉取私有的Go Module
在前面我们已经了解了GO 项目依赖包管理与Go Module常规操作Go Module 构建模式已经成为了 Go 语言的依赖管理与构建的标准。
在平时使用Go Module 时候可能会遇到以下问题
在某 module 尚未发布到类似GitHub 或 Gitee 这样的网站前如何 import 这个本地的 module如何拉取私有 module 文章目录 Go 包操作之如何拉取私有的Go Module一、导入本地 module1.1 依赖本地尚未发布的 module1.2 Go Module 开发中本地导入两种方式1.2.1 使用 replace 指令1.2.2 使用工作区模式 二、拉取私有 module 的需求与参考方案2.1 方案一通过直连组织公司内部的私有 Go Module 服务器拉取2.2 方案二将外部 Go Module 与私有 Go Module 都交给内部统一的 GOPROXY 服务去处理 三、统一 Goproxy 方案的实现思路与步骤3.1 goproxy 服务搭建3.2 自定义包导入路径并将其映射到内部的 vcs 仓库3.3 开发机 (客户端) 的设置3.4 方案的“不足”3.4.1 第一点开发者还是需要额外配置 GONOSUMDB 变量3.4.2 第二点新增私有 Go Modulevanity.yaml 需要手工同步更新3.4.3 第三点无法划分权限 一、导入本地 module
1.1 依赖本地尚未发布的 module
如果我们的项目依赖的是本地正在开发、尚未发布到公共站点上的 Go Module那么我们应该如何做呢
例如假设有个hello-module的项目你的main包中依赖了moduleA,代码如下:
package mainimport gitee.com/tao-xiaoxin/study-basic-go/hello-module/moduleAfunc main() {moduleA.ModuleA()
}并且这个项目中的moduleA 依赖 moduleB,此时此刻module A 和 moduleB 还没有发布到gitee公共托管站点上它的源码还在你的开发机器上。也就是说Go 命令无法在gitee.com/user/上找到并拉取 module A 和 module B这时使用go mod tidy命令就会收到类似下面这样的报错信息
$go mod tidy
go: finding module for package gitee.com/user/moduleB
go: finding module for package gitee.com/user/moduleA
go: gitee.com/tao-xiaoxin/study-basic-go importsgitee.com/user/moduleA: module gitee.com/user: git ls-remote -q origin in /Users/thinkook/go/pkg/mod/cache/vcs/ff424152e6f6be73e07b96e5d8e06c6cd9f86dc9903058919a7b8737718a8418: exit status 128:致命错误仓库 https://gitee.com/user/ 未找到
go: gitee.com/tao-xiaoxin/study-basic-go/moduleA importsgitee.com/user/moduleB: module gitee.com/user: git ls-remote -q origin in /Users/thinkook/go/pkg/mod/cache/vcs/ff424152e6f6be73e07b96e5d8e06c6cd9f86dc9903058919a7b8737718a8418: exit status 128:致命错误仓库 https://gitee.com/user/ 未找到所以Go提供了两种方式可以导入本地正在开发的 Go Module
1.2 Go Module 开发中本地导入两种方式
1.2.1 使用 replace 指令
介绍 使用replace指令可以替代远程依赖模块的路径将其指向本地的模块路径便于本地开发和测试。
基本使用 下面是一个示例replace指令的使用方式
replace example.com/module版本号 你的本地Module路径可以使用相对路径或者绝对路径接着我们继续回到上面的举例中首先我们需要在 module a 的 go.mod 中的 require 块中手工加上这一条并且替换为本地路径上的module A和moduleB:
replace (gitee.com/user/moduleA v1.0.0 ../moduleAgitee.com/user/moduleB v1.0.0 ../moduleB
)这里的v1.0.0版本号是一个“假版本号”目的是满足go.mod中require块的语法要求。
或者使用go mod edit 命令编辑 go.mod 文件:
go mod edit -replacegitee.com/user/moduleAv1.0.0../moduleA -replacegitee.com/user/moduleBv1.0.0../moduleB这样修改之后Go 命令就会让module A依赖你本地正在开发、尚未发布到代码托管网站的module B的源码了并且main函数依赖你本地正在开发、尚未发布到代码托管网站的module B的源码了。
虽然虽然这个方案可以解决上述问题但是在平时开发过程中go.mod 文件通常需要上传到代码服务器上这意味着另一个开发人员下载了这份代码后很可能无法成功编译。在这个方法中require指示符将gitee.com/user/moduleA v1.0.0替换为一个本地路径下的module A的源码版本但这个本地路径因开发者环境而异。为了成功编译module A和主程序该开发人员必须将replace后面的本地路径更改为适应自己的环境路径。
于是每当开发人员 pull 代码后第一件事就是要修改go.mod中的replace块。每次上传代码前可能还要将replace路径还原这是一个很繁琐的事情。于是Go开发团队在Go 1.18 版本中加入了 Go 工作区Go workspace也译作 Go 工作空间辅助构建机制。
上述举例代码仓库地址:点我进入
1.2.2 使用工作区模式
**介绍**Go 工作区模式是 Go 语言 1.18 版本引入的新功能允许开发者将多个本地路径放入同一个工作区中这样在这个工作区下各个模块的构建将优先使用工作区下的模块的源码。工作区模式具有以下优势
可以将多个本地模块放入同一个工作区中方便开发者管理。可以解决“伪造 go.mod”方案带来的那些问题。可以提高模块构建的性能。
常用命令
Go 工具提供了以下命令来帮助开发者使用工作区模式
go work edit提供了用于修改go.work的命令行接口主要是给工具或脚本使用。go work init初始化工作区文件 go.workgo work use将模块添加到工作区文件go work sync把go.work文件里的依赖同步到workspace包含的Module的go.mod文件中。
基本使用
首先我们初始化 Go workspace 使用命令go work init命令如下
go work init [moddirs]moddirs是Go Module所在的本地目录。如果有多个Go Module就用空格分开。如果go work init后面没有参数会创建一个空的workspace。
执行go work init后会生成一个go.work文件go.work里列出了该workspace需要用到的Go Module所在的目录workspace目录不需要包含你当前正在开发的Go Module代码。
如果要给workspace新增Go Module可以使用如下命令
go work use [-r] moddir如果带有-r参数会递归查找-r后面的路径参数下的所有子目录把所有包含go.mod文件的子目录都添加到go work文件中。 如果要同步依赖到workspace包含的Module的go.mod文件中可以使用如下命令 go work sync介绍完之后,我们回到上面的例子中现在我们进入 gowork下面然后通过下面命令初始化一个go.work:
go work init .我们看到go work init命令创建了一个go.work文件使用go env GOWORK命令查看go.work所在位置
$go env GOWORK
~/workspace/GolandProjects/study-basic-go/syntax/gowork/go.work接着我们在 module a 的go.work 中的 use 块中替换为本地路径上的module A和moduleB:
go 1.21.1use (../moduleA./moduleB
)支持replace指示符go.work还支持replace指示符,使用方法和上面一样
上面的代码地址:点我
二、拉取私有 module 的需求与参考方案
自从 Go 1.11 版本引入 Go Module 构建模式后通过 Go 命令拉取项目依赖的公共 Go Module已不再是一个“痛点”。现在我们只需要在每个开发机上设置环境变量 GOPROXY配置一个高效且可靠的公共 GOPROXY 服务就可以轻松地拉取所有公共 Go Module 了。 但随着公司内 Go 使用者和 Go 项目的增多“重造轮子”的问题就出现了。抽取公共代码放入一个独立的、可被复用的内部私有仓库成为了必然这样我们就有了拉取私有 Go Module 的需求。
一些公司或组织的所有代码都放在公共 vcs 托管服务商那里比如 github.com私有 Go Module 则直接放在对应的公共 vcs 服务的 private repository私有仓库中。如果你的公司也是这样那么拉取托管在公共 vcs 私有仓库中的私有 Go Module也很容易见下图 也就是说只要我们在每个开发机上配置公共 GOPROXY 服务拉取公共 Go Module同时将私有仓库配置到 GOPRIVATE 环境变量就可以了。这样所有私有模块的拉取都将直接连接到代码托管服务器不会通过 GOPROXY 代理服务并且不会向 GOSUMDB 服务器发出 Go 包的哈希值校验请求。
当然这个方案有一个前提那就是每个开发人员都需要具有访问公共 vcs 服务上的私有 Go Module 仓库的权限凭证的形式不限可以是 basic auth 的 user 和 password也可以是 personal access token类似 GitHub 那种只要按照公共 vcs 的身份认证要求提供就可以了。
不过更多的公司 / 组织可能会将私有 Go Module 放在公司 / 组织内部的 vcs代码版本控制服务器上就像下面图中所示 那么这种情况我们该如何让 Go 命令自动拉取内部服务器上的私有 Go Module 呢这里给出两个参考方案。
2.1 方案一通过直连组织公司内部的私有 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。
2.2 方案二将外部 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 方案的实现思路与步骤
3.1 goproxy 服务搭建
Go module proxy 协议规范发布后Go 社区出现了很多成熟的 Goproxy 开源实现比如最初的 Athens还有国内的两个优秀的开源实现goproxy.cn 和 goproxy.io 等。其中goproxy.io 在官方站点给出了企业内部部署的方法所以今天我们将基于 goproxy.io 来实现我们的方案。
我们在上图中的 in-house goproxy 节点上执行这几个步骤安装 goproxy
$mkdir ~/.bin/goproxy
$cd ~/.bin/goproxy
$git clone https://github.com/goproxyio/goproxy.git
$cd goproxy
$make编译后我们会在当前的 bin 目录~/.bin/goproxy/goproxy/bin下看到名为 goproxy 的可执行文件。
然后我们建立 goproxy cache 目录
$mkdir /root/.bin/goproxy/goproxy/bin/cache再启动 goproxy
$./goproxy -listen0.0.0.0:8081 -cacheDir/root/.bin/goproxy/goproxy/bin/cache -proxy https://goproxy.io
goproxy.io: ProxyHost https://goproxy.io启动后goproxy 会在 8081 端口上监听即便不指定goproxy 的默认端口也是 8081指定的上游 goproxy 服务为 goproxy.io。
不过要注意下goproxy 的这个启动参数并不是最终版本的这里我仅仅想验证一下 goproxy 是否能按预期工作。我们现在就来实际验证一下。
首先我们在开发机上配置 GOPROXY 环境变量指向 10.10.20.20:8081
// .bashrc
export GOPROXYhttp://10.10.20.20:8081生效环境变量后执行下面命令
$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在 goproxy 的 cache 目录下我们也看到了下载并缓存的 github.com/pkg/errors 包
$cd /root/.bin/goproxy/goproxy/bin/cache
$tree
.
└── pkg└── mod└── cache└── download└── github.com└── pkg└── errors└── v└── list8 directories, 1 file这就标志着我们的 goproxy 服务搭建成功并可以正常运作了。
3.2 自定义包导入路径并将其映射到内部的 vcs 仓库
一般公司可能没有为 VCS 服务器分配域名我们也不能在 Go 私有包的导入路径中放入 IP 地址因此我们需要给我们的私有 Go Module 自定义一个路径比如mycompany.com/go/module1。我们统一将私有 Go Module 放在 mycompany.com/go 下面的代码仓库中。
那么接下来的问题就是当 goproxy 去拉取 mycompany.com/go/module1 时应该得到 mycompany.com/go/module1 对应的内部 VCS 上 module1 仓库的地址这样goproxy 才能从内部 VCS 代码服务器上下载 module1 对应的代码具体的过程如下 那么我们如何实现为私有 module 自定义包导入路径并将它映射到内部的 vcs 仓库呢
其实方案不止一种这里我使用了 Google 云开源的一个名为 govanityurls 的工具来为私有 module 自定义包导入路径。然后结合 govanityurls 和 Nginx我们就可以将私有 Go Module 的导入路径映射为其在 VCS 上的代码仓库的真实地址。具体原理你可以看一下这张图 首先goproxy 要想不把收到的拉取私有 Go Modulemycompany.com/go/module1的请求转发给公共代理需要在其启动参数上做一些手脚比如下面这个就是修改后的 goproxy 启动命令
$./goproxy -listen0.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 的“源站”。
而上面这张图中要做的就是将这个“源站”的地址转换为企业内部 VCS 服务中的一个仓库地址。然后我们假设 mycompany.com 这个域名并不存在很多小公司没有内部域名解析能力从图中我们可以看到我们会在 goproxy 所在节点的 /etc/hosts 中添加这样一条记录
127.0.0.1 mycompany.com这样做了后goproxy 发出的到 mycompany.com 的请求实际上是发向了本机。而上面这图中显示监听本机 80 端口的正是 nginxnginx 关于 mycompany.com 这一主机的配置如下
// /etc/nginx/conf.d/gomodule.confserver {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;}
}我们看到对于路径为 mycompany.com/go/xxx 的请求nginx 将请求转发给了 127.0.0.1:8080而这个服务地址恰恰就是 govanityurls 工具监听的地址。
govanityurls 这个工具是前 Go 核心开发团队成员 Jaana B. Dogan 开源的一个工具这个工具可以帮助 Gopher 快速实现自定义 Go 包的 go get 导入路径。
govanityurls 本身就好比一个“导航”服务器。当 go 命令向自定义包地址发起请求时实际上是将请求发送给了 govanityurls 服务之后govanityurls 会将请求中的包所在仓库的真实地址从 vanity.yaml 配置文件中读取返回给 go 命令后续 go 命令再从真实的仓库地址获取包数据。 注govanityurls 的安装方法很简单直接 go install/go get github.com/GoogleCloudPlatform/govanityurls 就可以了。在我们的示例中vanity.yaml 的配置如下 host: mycompany.compaths:/go/module1:repo: ssh://admin10.10.30.30/module1vcs: git也就是说当 govanityurls 收到 nginx 转发的请求后会将请求与 vanity.yaml 中配置的 module 路径相匹配如果匹配 OK就会将该 module 的真实 repo 地址通过 go 命令期望的应答格式返回。在这里我们看到module1 对应的真实 VCS 上的仓库地址为ssh://admin10.10.30.30/module1。
所以goproxy 会收到这个地址并再次向这个真实地址发起请求并最终将 module1 缓存到本地 cache 并返回给客户端。
3.3 开发机 (客户端) 的设置
前面示例中我们已经将开发机的 GOPROXY 环境变量设置为 goproxy 的服务地址。但我们说过凡是通过 GOPROXY 拉取的 Go Modulego 命令都会默认把它的 sum 值放到公共 GOSUM 服务器上去校验。
但我们实质上拉取的是私有 Go ModuleGOSUM 服务器上并没有我们的 Go Module 的 sum 数据。这样就会导致 go build 命令报错无法继续构建过程。
因此开发机客户端还需要将 mycompany.com/go作为一个值设置到 GONOSUMDB 环境变量中
export GONOSUMDBmycompany.com/go这个环境变量配置一旦生效就相当于告诉 go 命令凡是与 mycompany.com/go 匹配的 Go Module都不需要再做 sum 校验了。
到这里我们就实现了拉取私有 Go Module 的方案。
3.4 方案的“不足”
3.4.1 第一点开发者还是需要额外配置 GONOSUMDB 变量
由于 Go 命令默认会对从 GOPROXY 拉取的 Go Module 进行 sum 校验因此我们需要将私有 Go Module 配置到 GONOSUMDB 环境变量中这就给开发者带来了一个小小的“负担”。
对于这个问题我的解决建议是公司内部可以将私有 Go 项目都放在一个特定域名下这样就不需要为每个 Go 私有项目单独增加 GONOSUMDB 配置了只需要配置一次就可以了。
3.4.2 第二点新增私有 Go Modulevanity.yaml 需要手工同步更新
这是这个方案最不灵活的地方了由于目前 govanityurls 功能有限针对每个私有 Go Module我们可能都需要单独配置它对应的 VCS 仓库地址以及获取方式git、svn 或 hg。
关于这一点我的建议是在一个 VCS 仓库中管理多个私有 Go Module。相比于最初 Go 官方建议的一个 repo 只管理一个 module新版本的 Go 在一个 repo 下管理多个 Go Module 方面已经有了长足的进步我们已经可以通过 repo 的 tag 来区别同一个 repo 下的不同 Go Module。
不过对于一个公司或组织来说这点额外工作与得到的收益相比应该也不算什么
3.4.3 第三点无法划分权限
在讲解上面的方案的时候我们也提到过goproxy 所在节点需要具备访问所有私有 Go Module 所在 VCS repo 的权限但又无法对 Go 开发者端做出有差别授权。这样只要是 goproxy 能拉取到的私有 Go ModuleGo 开发者都能拉取到。
不过对于多数公司而言内部所有源码原则上都是企业内部公开的这个问题似乎也不大。如果觉得这是个问题那么只能使用前面提到的第一个方案也就是直连私有 Go Module 的源码服务器的方案了。
参考链接
小厂内部私有Go module拉取方案3Go 1.18新特性前瞻Go工作区模式