开倒车 🚗 ?
年后这几周花了两周左右的时间将我司的 GitHub 代码迁移到内部的 Gitlab。影响最大的就是我们产品的发布流水线,需要适配 Gitlab 和内网环境的一些服务。基本上整个产品打包发布的流水线代码全部重写了一遍,可累坏咱了🥺。当时心里还认为代码迁移至 Gitlab 纯属倒车行为😅,不过等到所有的适配修改完毕后忽然发现 Gitlab 真香!
归根结底内网的 Gitlab 网络状况十倍百倍与 GitHub 不止。众所周知在学习墙国,GitHub 直连的速度和稳定性差的一批。也正因此之前在 GitHub 上的流水线经常会被网络抖动所干扰,有时侯 fetch 一个 repo 十几二十分钟!迁移到内网 Gitlab 之后,那速度简直飞起!以往最少十几分钟的流水线现在只需要不到五分钟就能完成😂。
于是今天就写篇博客记录一下当时折腾 Gitlab 时收获的一点人生经验👓
我今天是作为一个长者给你们讲的,我不是新闻工作者,但是我见得太多了,我有这个必要告诉你们一点人生的经验
Gitlab
在折腾的过程中使用到的有关 Gitlab 的文档和工具:
- Gitlab workflow:了解一下 Gitlab 的工作流,不同于 GitHub 的 PR,在 Gitlab 中使用的是 MR 的方式;
- Gitlab API:Gitlab API 的官方文档,了解它在使用下下面这些工具时候会得心应手。
- python-gitlab API client:使用 Python 实现的 Gitlab API client,用它来完成一些特定需求工具的开发,比如根据 tag 或者 branch获取 repo 中指定的文件或目录;
- python-gitlab CLI:基于 python-gitlab API client 封装成的命令行工具,因为是 CLI 工具所以可以很方便地集成在一些流水线的脚本中;
- go-gitlab API client:使用 Golang 实现的 Gitlab API client。由于发布流水线中的一个阶段就是根据一个 list 来收集其他 repo 中的特定文件和目录,使用的工具是 golang 写的,为了减少代码修改量就使用了 go-gitlab 而不是 python-gitlab。
Gitlab workflow
PR
在 GitHub 上我们一般使用 PR 的方式来完成代码合并工作,流程如下:
- 成员 Fork 原始仓库,将 Fork 出来的仓库 clone 到本地
- 在本地创建新分支,并基于新分支进行修改和提交,推送新分支到 Fork 的仓库
- 基于 Fork 仓库中的新分支向原始仓库的目标分支发起 Pull Request
- 在 PR 的评论中 @ 审查者,请求 review 修改
- 审查者收到请求邮件,审查代码,并在建议处直接做出评论
- 提交者根据建议,继续提交改动,并对意见作出回应
- 审查者无异议后,在 PR 的评论中 @ 管理员,请求合入代码,管理员接受 PR,代码合入主分支
MR
但是到了 Gitlab 之后我们就使用 MR 的方式来完成代码合并工作,流程如下:
- 成员 Clone 原始仓库到本地,基于要修改的分支,创建新的分支
- 本地修改和提交,推送新分支到原始仓库
- 在原始仓库中基于新分支向目标保护分支发起 Merge Request
- 审核者 review 代码,管理员 Merge 代码
相比来讲 MR 和方式更适合团队内部的协作开发,PR 的方式适合开源项目的协作开发。
Gitlab API
The main GitLab API is a REST API. Because of this, the documentation in this section assumes that you’re familiar with REST concepts.
参照官方文档 API resources 可知,共有 Projects 、Groups、Standalone 这三种 API 分组。
- Projects: 对应的就是与 repo 相关的 API ,比如 tag、commit、branch 、MR、Issue 这一类型;
- Groups: 对应类似于 GitHub 上的 Organizations,一般来讲公司里的 repo 都会按照团队来划分组织,同一团队里的 repo 会放在 gitlab 同一个 Groups 下,而不是以个人为单位存放 repo;
- Standalone:则是除了 Projects 和 Groups 之外的 API 资源,如 user
而我们多数情况下使用的是 Projects 相关的 API,通过它来对 repo 进行增删改查。简单介绍完 Gitlab API 类型之后,本文会介绍几种使用 Gitlab API 的工具。在使用这些工具的过程中,如果遇到一些错误可以通过 Status codes API 返回状态码来排查问题。
python-gitlab CLI
这是一个使用 python-gitlab API client 封装好的 gitlab 命令行工具,可以使用它来完成绝大多数 Gitlab API 所支持的操作。因为之前的流水线中有很多操作是访问的 GitHub,比如提交 PR、获取 repo tag、查看 PR labels 等,都是写在 Jenkinsfile 调用各种脚本和工具来完成的。 切换到了 Gitlab,自然也需要一个工具来完成上述操作了。那么 python-gitlab CLI 这个工具无疑是不二之选,甚至比之前的工具更方便。因为迄今为止还没有见到过 GitHub 能有像 python-gitlab 这样的工具。总之,对于使用 Gitlab 的人来讲,要对 repo 完成一些自动化处理的工作,强烈推荐使用这个 CLI 工具,它可以很方便地集成在流水线中。
安装
python-gitlab CLI 依赖 Python 2.7 或者 3.4+,2021 年啦,就不要使用 Python2.7 啦😊。本地安装好 python3 和 pip3 后使用如下命令安装即可。
1 | # 在这里使用清华的 pypi 源来加速安装,毕竟是墙🧱国 |
由于使用这个工具的场景大多数是在 Jenkins 所创建的 slave pod 中执行的,所以也可以构建一个 docker 镜像, Dockerfile
如下
1 | FROM debian:buster |
配置
Gitlab CLI 工具需要使用一个 python-gitlab.cfg
配置文件用于连接 Gitlab 服务器以及完成一些鉴权认证,配置文件格式为 ini
如下:
1 | [global] |
- 全局的连接参数
Option | Possible values | Description |
---|---|---|
ssl_verify |
True 或 False |
是否开启 SSL 加密验证 |
timeout |
证书 | 连接超时时间 |
api_version |
4 |
API 的版本,默认为 4 即可,参考 API V3 to API V4 |
per_page |
1 ~ 100 | 每次返回的元素数量,Gitlab 的最大限制为 100。可以通过 --all 参数获取所有的元素 |
- 自定义 GitLab server 参数
Option | Description |
---|---|
url |
GitLab server 的 URL |
private_token |
通过访问 gitlab 服务器的 -/profile/personal_access_tokens 来生成 token |
oauth_token |
|
job_token |
|
api_version |
API 的版本,默认为 4 即可,也可以不用定义,使用全局参数 |
http_username |
Gitlab 用户名,不推荐使用它来连接 Gitlab 服务器 |
http_password |
Gitlab 密码,不推荐使用它来连接 Gitlab 服务器 |
将文件保存在 ~/.python-gitlab.cfg
或者 /etc/python-gitlab.cfg
,也可以使用环境变量 PYTHON_GITLAB_CFG
或者 --config-file
执行配置文件的路径,为了省事儿还是将它放到 ~/.python-gitlab.cfg
下。
配置完成之后,可以使用 gitlab current-user get
命令测试连接是否正常,如果有返回值且正确的用户名说明配置成功了。
1 | $ gitlab current-user get |
基本使用
gitlab 命令行工具主要是对 Gitlab 服务器上的各种对象如:user, project, file, repo, mr, tag, commit 等进行增删改查(get、list、create、delete、update)。使用的命令行格式方式如下:
1 | $ gitlab <option> [object] [action] <option> |
一般来讲只需要 4 种参数:
第一个参数是紧接着 gitlab 命令后面的参数,它是 gitlab 命令行的输出参数和配置参数,如
-o
参数指定输出结果的格式;-f
参数将输出结果存放到文件中;-g
参数执行连接哪个 Gitlab 服务器。第二个参数则是用来指定所要操作的对象,比如 project-merge-request,project-tag 等,所支持的对象有很多,基本上涵盖了所有 Gitlab API 所支持的操作对象,如下:
1 | $ gitlab -h |
- 第三个参数则是 action 参数,即用于指定对所操作的对象进行何种操作,一般来讲都会支持增删改查操作(get, list, create, update, delete)
1 | $ gitlab project-tag |
- 第三个参数则是 object + action 所依赖的参数,比如指定 project id
1 | $ gitlab project-tag list |
Project-ID:是 gitlab 上唯一表示该 repo 的 ID,可分为两种,一种是
group/project
的形式,其中/
要转译成%2F
如:muzi502%2Fkubespray
;另一种则是数字的形式,在该 repo 的 web 页面上可以看到,推荐使用第二种。
1 | # 也可以使用gitlab 命令获取 repo 的 id |
- 在流水线中可以根据 token 获取用户的用户名和邮箱,用于配置流水线中的 repo git 信息,避免因为 CLA 无法通过。
1 | gitlab -o json current-user get | jq '.id' |
project
- 获取 repo ssh url 地址
由于 Jenkins 流水线中 clone 的 repo url 是使用 token+ https 的方式,在流水线中如果要 push 代码到 repo 需要修改为 ssh 的方式,可使用如下方式根据 project id 来获取该 repo 的 ssh url。
1 | $ gitlab -o json project get --id ${PROJECT_ID} | jq -r '.ssh_url_to_repo' |
file
对于 repo 中文件的操作使用 project-file
1 | $ gitlab project-file |
- 获取文件,通过
project-file
对象的 get 操作
1 | $ gitlab -o json project-file get --project-id ${PROJECT_ID} --file-path .gitignore --ref master | jq '.' |
- 通过 project-file 的 raw 方法可以获取文件的原始内容,无须 base64 解码
1 | $ gitlab project-file get --project-id ${PROJECT_ID} --file-path .gitignore --ref master |
创建文件,对文件的增删改都是通过提交 commit 来完成的,因此需要指定所要操作的分支以及 commit-message 的信息。另外如果操作的文件是 master 分支获取其他保护分支,要确保当前用户有写入的权限,不然会提示如下错误:
1
2gitlab project-file update
Impossible to update object (400: You are not allowed to push into this branch)
之前在 GitHub 上有一套 CI 并不能适用于 Gitlab,因此需要为所有的分支创建 CI 流水线,用于检查代码是否符合规范,可以通过如下方法批量量地向所有分支创建文件。
1 | ID=123456 |
- 更新文件
比如批量更新所有分支的 Makefile
中 github.com
为 gitlab.com
1 | ID=123456 |
- 删除文件
1 | $ gitlab project-file delete --project-id ${PROJECT_ID} --file-path .gitignore --ref master \ |
MR
- 创建 MR,指定 source branch 和 target branch 以及 mr 的 title 这三个参数。前面最好加上 -o json 参数用户获取 mr 的 iid,可通过此 iid 来对这个 mr 进行增删改查。
1 | $ gitlab -o json project-merge-request create --project-id ${PROJECT_ID} --source-branch --target-branch ${BASE_BRANCH} --title "${MR_TITLE}" |
通过 -o json 参数会返回此 mr 的信息,其中 iid
就是该 mr 在此 repo 中的唯一标示
1 | $ gitlab -g gitlab -o json project-merge-request create --project-id 25099880 --source-branch release-2.14 --target-branch master --title "mr create test" |
- 合并 MR
1 | $ gitlab project-merge-request merge --project-id ${PROJECT_ID} --iid @mr_iid |
- 查看 MR 状态
1 | $ gitlab -o json project-merge-request get --project-id ${PROJECT_ID} --iid @mr_iid | jq -r ".state" |
- 集成在 Jenkinsfile 中完成创建 MR、合并 MR、检查 MR
调用用它的时候只需要传入 SOURCE_BRANCH, TARGET_BRANCH, MR_TITLE 这三个参数即可。
1 | def makeMR(SOURCE_BRANCH, TARGET_BRANCH, MR_TITLE) { |
Tag
- 列出 repo tag
在这里还是推荐使用 git tag 的方式获取 repo tag ,因为 Gitlab API 的限制,每次请求最多只能返回 100 个值,可以加上 --all
参数来返回所有的值。
1 | $ gitlab -o json project-tag list --project-id ${ID} | jq -r '.[].name' |
- 创建 tag
1 | $ gitlab project-tag create --project-id ${ID} --tag-name v1.0.0-rc.2 --ref master |
- 删除 tag
upstream 上的 repo tag 只能通过在 Gitlab 上删除,在本地 repo 下是无法删除的,因此可以使用如下命令删除 Gitlab repo tag,注意:受保护的 repo tag 如果没有权限的话是无法删除的。
1 | $ gitlab project-tag delete --project-id ${ID} --tag-name v1.0.0-rc.2 |
- 创建受保护的 repo tag
由于我们流水线任务依赖于 repo tag 来做版本的对于,因此需要保护每一个 repo tag,但有特殊情况下又要覆盖 repo tag,所以受保护的 repo tag 目前还没有找到合适的方法,只能先手动创建了,等到需要删除的时候再删除它。可以使用如下命令批量创建受保护的 repo tag。
1 | $ git tag | xargs -L1 -I {} gitlab project-protected-tag create --project-id ${ID} --name {} |
Lint 流水线
迁移到了 Gitlab 之后原有的流水线在内网的 Gitlab 上也就无法使用了,为了减少维护成本就使用了 Gitlab 自带的 CI 。我所维护的 repo 使用 Gitlab CI 只是做一些 lint 的检查内容,因此 CI 配置起来也特别简单。如 kubespray 的 CI 配置:
.gitlab-ci.yml
1 |
|
- 使用 gitlab CLI 工具给所有分支添加
.gitlab-ci.yml
文件
1 | ID=123456 |
其他
- repo 迁移
由于内部的 Gitlab 不支持导入 git url 的方式,所以只能手动地将 GitHub 上的 repo clone 到本地再 push 到 Gitlab 上。使用 git clone 的方式本地只会有一个 master 分支,要把 GitHub 上 repo 的所有分支都 track 一遍,然后再 push 到 Gitlab 上。
1 | # 使用 git clone 下来的 repo 默认为 master 分支 |