本文以又拍云团队私有化模块处理的实践案例为基础,介绍如何使用私有化模块,以及 go get 工具背后的细节,其中包括如何让 go 正确的源获私有化 gitlab 上源代码以及认证等问题。文章根据又拍云资深开发工程师刘云鹏在 Open Talk 公开课直播分享进行整理,回放视频请下拉文末点击“阅读原文”。html
关于 Open Talk:由又拍云发起的综合性技术沙龙,秉承又拍云“让创业更简单”的初衷,以全干货的形式为技术开发者提供包括技术、运维、产品、创业等多维度的知识分享,帮助企业成员提高专业技能,推进企业更好更快地发展。git
GO 在 1.11 版本开始引入 Module 的特性;1.13 版本引入 Module 校验和检查,增强了 Module 的安全性;如今的 1.16 版本已经默认使用 Module 模式。日前 GO 团队在博客上代表,将在 1.17 版本时删除对 GOPAHT 的支持,若是如今尚未使用 GO MODULE,赶忙抓紧时间试试 GOMDULE 吧。github
GOMODULE 和 GOPATH 的主要区别在于私有化模块的使用。公有化模块使用是相同的,都是经过 go get 直接获取模块。对于私有化模块 GOPAHT 能够直接将模块代码丢在 GOPAHT 目录下,而 GO Module 不行,它有本身的代码管理方式,下面咱们简单介绍下。golang
GO 获取模块一般是使用 go get 工具获取模块,当前 go get 支持两种方式:正则表达式
第一种是经过传统的 VCS 去代码托管平台上拉取代码,以 git 为主,还支持 svn、hg、等其余平台。数据库
第二种是经过 1.12 版本开始支持的 GOPROXY 协议,go 在 GOPROXY 服务器上获取代码归档文件。apache
从 1.13 起 GO 还使用校验和检查—— GO SUM ,全部模块下载后都会检查其校验和。它会将下载模块的哈希值与 Google 线上数据库中的哈希值进行比对,防止模块被篡改,只有验证经过后的模块才能正常安装使用。windows
GO 支持不少的版本管理工具。首先须要判断使用什么版本管理工具去获取模块。判断方式大体分红三类,不依赖其余的两种静态匹配方式和一种动态匹配方式。缓存
静态匹配方式安全
前缀匹配:好比 github 、谷歌的 bitbuket 和 apache、openstack 等代码托管平台,会内制在 go get 的工具链中,会去判断模块的前缀当前缀匹配上则使用对应的版本管理工具。图中左方的一例子,github.com/eamaple/pkg 模块会匹配前缀,并与 github 相匹配,同时能知道 github 使用 git 工具。
正则匹配:正则的方式是给模块加上后缀,后缀名能够是前文介绍的五种版本管理工具( git,svn ,hg ,bzr,fossil )之一的后缀。后缀的匹配是经过正则表达式实现的。上图中两个例子都是以 .git 做为后缀,经过正则表达式的匹配会获得里面的子分组,即 VCS 子分组会匹配到模块是使用 git 进行管理的。
动态匹配方式
当前缀和正则表达式都匹配不上,则会采用动态判断的方式。go get 会发送一个 HTTP 请求,URL为模块带上协议头和参数( go-get=1 )。go get 期待服务器返回模块相应信息来帮助go get 进一步的操做。GO 默认会发送 HTTPS 请求,若是服务器想用 HTTP 协议,能够经过环境变量 GOINSECURE 来处理,当 GOINSECURE 为 1 时,GO 就会使用 HTTP 协议。
Go get 预期的返回体是一个 HTML 文档,其中对 GO 有意义的是要带 name="go-import" 属性的 meta 标签。该 meta 标签会经过 content 属性告诉 GO 怎么去获取模块。
content 的内容有三部分:第一部分 root-path,指模块的名字;第二部分 vcs 表明须要使用的管理工具,好比说 git、svn。;第三部分 repo-url 指的是模块原代码存放在哪一个仓库下面,该仓库就须要是协议加仓库地址的形式。
上图以 GO 的子包为例,经过 curl 模拟发送 go get 请求,golang.org/x/net 服务器返回了一个 html 文档,文档有用的是红圈框起来的部分,里面是 meta 标记,content 第一部分是 GO 模块名称 golang.org/x/net ;第二部分是 git,表明须要使用 git 来获取原码;第三部分是模块托管的地址,表示托管在模块包的地址 googlesource.com/net 上。须要注意 meta 标记只能放在 head 里面,go get 解析会从头开始,当遇到 head 的结束标签或者 body 的开始标签时中止解析。
git 支持 HTTP 协议和 SSH 协议,GO调用 git 时默认只使用 HTTP 协议,调用过程当中会禁用 git 的交互过程。例如 git 使用 HTTP 协议去克隆私有仓库须要输入用户名和密码,可是 GO 调用 git 时不能经过交互输入用户名和密码会致使获取模块失败。交互是经过环境变量 GIT_TERMINAL_PROMPT 控制,若是手动将变量强行更改成 1,就能够启用交互从而手动输入用户名和密码。
那么该怎样将用户名和密码无感知的传递给 git 呢?事实上在 git 里,若是是使用 HTTP 协议均可以经过 netrc 文件来传递用户名和密码,该文件在 HOME 目录下,有两种文件格式:
如上图所示,第一条中配置了 gitlab.com ,用户名为 root,密码是 admin。经过 git 去克隆 gitlab 的私有仓库时,能够把用户名 root 和 admin 传递给 git,让 git 无感知获取到用户名和密码,从而就不会再要求输入密码了。第二条中经过 default 给全部的服务器都设置默认的用户名和密码,设置的用户名为 guest,密码是 123456,表示除了 gitlab.com 以外的全部服务器须要认证时,都会把将 guest 和 123456 做为用户名和密码传递给须要的程序。
go 调用 git 时也支持 SSH 协议,但默认不会使用。只有在动态获取的时候显示指定,才可使用 SSH 协议。若是经过静态匹配方式(前缀匹配或正则匹配),能匹配上使用的模块信息,都只能使用 HTTPS 协议。
上图中的模块是 example.com/pkg,仓库地址是 gitlab.example.com/example/pkg。meta 标记的content 里包含了完整的模块信息,首先 第一部分是模块的名字,这和前面 module 的名字定义是相同的;紧接着是 git,表明使用 git 去获取代码,最后一部分是仓库地址这里就显示指定了 SSH 协议,同时还有 git 的用户名和服务器 SSH 服务端口号。
Git ssh 认证是基于密钥对实现的,若是没有密钥对,能够经过 SSH 工具套件 ssh-keygen 生成密钥。上图中列举了经常使用的参数 -t,该参数能够指定密钥的类型。其中 RSA 密钥多是最经常使用的,而本人比较喜欢使用 ED25519,它有个明显的优势就是密钥长度很是短,公钥和私钥都只有 32 字节,安全性也能够和 RSA 密钥 3000 位左右的相媲美,可以保证安全性,密钥长度又短,所以会常用 ED25519 做为密钥。
当生成秘钥对以后,会在 HOME 下的 .ssh 文件夹中生成密钥队的文件码,包含私钥和公钥。".pub" j结尾的文件是公钥文件,须要把公钥文件配置在 gitlab 或 github 等代码托管平台上。右边是 gitlab 的截图,图中使用的密钥就是 ED25519 格式的密钥,能够看到长度真的很是短。
GO 支持经过 GOPROXY 协议获取 GO 模块。模块是基于 HTTP 协议的,只会使用 HTTP 的 get 请求,而且使用标准的 HTTP 状态码进行调用。当使用的公共 GOPROXY 协议,其 GOPROXY 代理服务器默认都是没有用户名和密码的。但实际上若是须要搭建私有的,是能够支持 HTTP 基础受权,方式与前面同样,经过 .netrc 文件去配置用户名和密码。另外 GOPROXY 还有两点特性:
GOPROXY 的配置是经过 GOPROXY 环境变量来控制,配置的是代理服务器URL。代理服务器 URL 能够配置多个,经过逗号和管道符来进行分割,管道符和逗号的区别后面会举例讲解。
经过固定的字符串 off 和 direct 能够代替 URL。off 禁止从任何来源去下载模块,把 GOPROXY 设置为 off 会禁止下载模块,只能使用本地模块,不管从 gitlab、github或其余地方的模块都不能下载。direct 表明直接从VCS上拉取,通常会做为备选方案。
图中展现了两个例子:
GOPROXY 的实现很简单,官方定义只有五个接口。
URL 中的三个变量意义以下:
大小写编码问题
在 HTTP 的 URL 定义上是不区分大小写的,当 module 或 version 出现大写字母时,在某些系统中可能会出现混淆的问题。为了不此问题,须要进行大小写的编码,把大写字母转换成感叹号加小写字母的编码。
上图是 list 接口示例, proxy.golang.org 是代理服务器的地址, golang.org/x/text 是要获取的模块名字,@v 为固定的字符串,list 是要调用的 list 接口。能够看到该接口返回了 text 包的全部版本,图中 GO 获取了全部版本后能够经过版本语义推断出模块的最新版本。
如上图所示, INFO 接口和 LATEST 接口返回的内容是同样的。 Version:固定版本字符串的版本号, Time 是 fc3339 时间格式的字符串,为可选项,表明版本的提交时间。
最后是 MOD 和 ZIP 接口。MOD 接口就是返回指定版本的 mod 文件,上图示例中获取了最新版本的 mod 文件, text 包只依赖了 tools 模块。ZIP 文件接口就是获取模块指定版本的 ZIP 文件,当它把版本的全部原文件打包成 ZIP 文件,go get 最终经过接口去下载的就是这个版本的模块。
前面提到经过 GOPROXY 去获取源代码会比经过 VCS 获取要更快,经过 zip 去下载只会下载当前版本的全部文件不会包含历史的版本信息,若是是经过 VCS 好比 git 去克隆仓库,就会获取全部的历史版本信息;所以经过 GOPROXY zip 接口获取文件的体积会更小,下载也会更快,须要注意的是 GOPROXY 定义了模块 zip 文件的大小和其全部文件的未压缩总限制为 500 MiB,go.mod 文件和 LICENSE 文件大小限制为 16 MiB。
Go1.13 版本开始加入模块 SUM 验证机制,默认全部 go 模块下载后都会验证其 hash 是否与线上( 默认:sum.golang.org 国内:sum.golang.google.cn)记录的一致。
验证的过程能够经过环境变量 GONOSUMDB 和 GOSUMDB 来控制:首先来看 GOSUMDB 的配置,它指定了须要使用的线上数据库地址。由于默认使用的 sum.golang.org 在国内没法访问,上图中配置使用的是 google 搭建的国内镜像,还能够配置为 off,表明禁用校验,即下载模块不进行哈希值的校验,完全抛弃这个过程。使用中我不建议这样作,可使用 GONOSUMDB 的环境变量去配置不须要验证的模块,好比私有模块确定是不能经过验证的。GONOSUMDE 是经过前缀匹配的方式运行的。图中配置了 gitlab.com,那么全部以 gitlab.com 开头的包都不会进行 GO 的校验和检查。
下面来梳理下常见的变量:
私有包的使用
下面介绍一下如何使用私有模块。通常公司内使用较多的是私有化搭建的 gitlab 服务,gitlab 自己是支持响应 go get 的 HTTP 请求。经过 go get 获取包时,客户端会发送 HTTP 请求到 gitlab 服务器上,服务器收到请求后会返回响应中包含 meta 标记。该标记会告诉客户端,模块使用 git 经过 HTTP 协议获取原代码。gitlab 默认使用 HTTPS 协议。客户端收到 gitlab 服务器响应结果后,能正确的使用 git 去拉取模块的源代码。模块下载经过后,一样会有校验和检查的过程,能够在 GOPRIVATE 变量加上 gitlab.com,告知 go gitlabc.com 相关的模块都是私有模块跳过校验和检查。
在又拍云内部实践中,状况有些不一样,又拍云内部全部使用的 HTTP 服务都须要通过 google 的二次验证。全部发往内部 gitlab 服务器的请求都会预先检查是否有 google 受权的 head,若是没有会被直接拦截掉并返回403错误。这样会致使全部的简单 HTTP 请求都不能到达 gitlab 服务器直接被拦截。go 发送的 HTTP 请求一样也会被拦截掉,将致使 go 不能正确的获取模块信息。这时虽然能够直接通 ssh 协议 clone 服务器上原代码,但因为 go get 没有这些信息,致使请求失败。所以下图中灰线表示的请求其实是发不出来的。
那么该如何解决呢?方法是采用额外的 http 服务来处理 go get 的 HTTP 请求。额外 HTTP 服务没有验证过程,请求经过后会 go get能正确的获取到须要的 meta 信息。meta 中必须指定使用 ssh 协议,由于 gitlab http 服务有二次认证,没有认证的请求都不能经过,所以只能使用 ssh 协议。权限认证能够由 SSH 密钥对完成,进行无感知进行受权。go get 引导 http 服务不会管理受权相关问题,全部的受权处理都交给 gitlab。做为私有模块,若是没有对应的响应程序,受权认证都交给 gitlab 处理。
go get 请求指引
采用额外的服务去引导 go get 是怎么作的呢?这须要对模块包的命名进行修改,须要基于 gitlab 命名的规则修改。
gitlab.com/lyp256/pkg
域名 仓库名
一个完整的模块有几部分组成,首先是域名 gitlab.com,lyp256 是全部者,pkg 是模块的项目名字。对于单个 gitlab平台重要的是后面两段,也就是指定这个模块全部者和项目名,域名确定是固定的能够忽略。
基于这样的规则我实现了一个简单的小服务,来解决 go get http 请求的处理。代码以下:
Gitlab CI 实践
Gitlab CI 时会起一个空的容器,图中示例使用的是 golang alpine 的镜像。这个镜像里除了 golang 没有其余的东西。咱们须要安装相关依赖和注入 SSH 认证相关内容。script 中定义以下:
第一步: 使用 mikdir -p,在 cache 下建立目录,这个目录是咱们 CI 机器上的缓存挂载的是物理盘上的一块空间能够保留数据,用来缓存 go mod 减小模块下载。
第二步:安装基础环境、工具软件包等。图中示例安装了 git 和 g++,g++ 是 go 编译所须要的依赖,openssh 是 ssh 的工具链 git 须要用到。
第三步:处理 SSH 秘钥。这里有两步,信任 gitlab 服务器秘钥和导入认证私钥。私钥是经过环境变量 $DEPLOY_SSH_KEY 导入,只须要保留该环境变量中的内容到对应的秘钥文件就能够了。gitlab 服务器秘钥使用 ssh-keyscan 来获取并保存到 known_hosts 文件。经过 gitlab SI 的配置把能访问 git 项目的私钥放在环境变量 $DEPLOY_SSH_KEY 里面,把私钥放在相应的 ssh 私钥文件而且授予正确的权限。
最后还须要配置 GOPRIVATE 变量定义全部 go.holdcloud.com 相关的模块为 PRIVATE 模块不要使用代理和检验和检查。
到此已基本完成全部准备工做,后面的 go test 是正常的 ci 测验逻辑,能够根据实际状况来写。