| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 开发工具 -> 当你 git push 时,极狐GitLab上发生了什么? -> 正文阅读 |
|
[开发工具]当你 git push 时,极狐GitLab上发生了什么? |
勇士,你可曾好奇过 Git 和极狐GitLab 是如何工作的?现在,拿起你心爱的 IDE,和我们一起踏上探索之旅吧! 基础知识在开始旅程之前,我们需要做三分钟的知识储备,计时开始! Git仓库内幕使用了 Git 的项目都会在其根目录有个?.git?文件夹(隐藏),它承载了 Git 保存的所有信息,下面是我们这次关注的部分:
图:Pro Git on git-scm.com 图注:红色部分由 refs 提供,其余部分全部由 objects 提供,commit 对象(黄色)指向保存文件结构的 tree 对象(蓝色),后者再指向各个文件对象(灰色) Git 服务端只会存储?.git?文件夹内的信息(称为?bare repository,裸仓库),git clone?是从远端拉取这些信息到本地再重建仓库位于?HEAD?的状态的操作,而?git push?是把本地的 ref 及其相关 commit 对象、tree 对象和文件对象发送到远端的操作。 Git 在通过网络传输对象时会将其压缩,压缩后的对象称为?packfile。 Git 传输协议让我们按时间先后顺序理理?git push?时发生了什么: 1.?用户在客户端上运行?git push 2.?客户端的 Git 的?git-send-pack?服务带上仓库标识符,调用服务端的?git-receive-pack?服务 3.?服务端返回目前服务端仓库各个 ref 所处的 commit hash,每个 hash 记为 40 位 hex 编码的文本,它们长这样:
我们可以看到,服务端的?main?分支位于?229859bcc73cdab4db2b70ed681077a5885f80134(忽略前面的协议内容)。 4.?客户端根据返回的 ref 情况,找出那些自己有但是服务端没有的 commit,把即将变更的 ref 告知服务端:
上面这个例子中,我们正在推送一个新分支?feat/hello-world,它现在指向?8fa91ae7af0341e6524d1bc2ea067c99dff65f1c,由于它是个新分支,以前的指向记为0000000000000000000000000000000000000000。 5.?客户端将相关 commit 及其 tree 对象、文件对象打包压缩为 packfile,发送到服务端,packfile 是二进制:
6.?服务端解包 packfile,更新 ref,返回处理结果:
Git 传输协议可以由 SSH 或者 HTTP(S) 承载。 还是挺直接的,对吧? 极狐GitLab 的组成部分极狐GitLab 是一个常用的 Git 代码托管服务,同时支持协作开发、任务跟踪、CI/CD 等功能。 极狐GitLab 的服务并不是一个单体,我们以大版本 15 为例,和 git push 有关的组件有下面这些:
简明的极狐GitLab 组件关系 图/极狐GitLab architecture overview on docs.gitlab.cn 开始 git push!三分钟过得真快!现在你已经掌握了基础,让我们开始征途吧! 你喜欢SSH?如果你的远端地址是?git@jihulab.example.com:user/repo.git?这样的,那么你在用 SSH 与 极狐GitLab 进行通讯。在你执行 git push 时,本质上,你的 Git 客户端的 upload-pack 服务在执行下列命令:
这里面有挺多问题值得说道的:
这两个问题由极狐GitLab Shell 的 gitlab-sshd 来解决。它是个定制化的 SSH Daemon,和一般的 sshd 讲同样的 SSH 协议,客户端没法分清它们。客户端在做 SSH 握手时会提供自己的公钥,gitlab-sshd 会调用 Rails 的内部 API?GET /api/v4/internal/authorized_keys?查询公钥是否在极狐GitLab 注册过并返回对应公钥 ID(可定位到用户),同时校验 SSH 握手的签名是否由同一份公钥对应的私钥生成。 另外,gitlab-sshd 限制了客户端可以运行的命令,其实,它在使用用户运行的命令来匹配自己应该运行哪个方法,没有对应方法的命令都会被拒绝。 可惜,看来我们是没法通过 SSH 在极狐GitLab 的服务器上运行?bash?或者?rm -rf /?了。┑( ̄Д  ̄)┍ 说点有趣的,早期极狐GitLab 当真使用 sshd 来响应 Git 请求。为了解决上面这两个问题,他们这么写?authorized_keys:
对,你没猜错,整个极狐GitLab 的用户公钥都会被放到这个文件里,它可能会上百 MB 的大小!朴实无华! Command?参数覆盖了每次 SSH 客户端想运行的命令,让 sshd 启动 gitlab-shell,启动参数是公钥 ID. gitlab-shell 可以在由 sshd 设定的环境变量?SSH_ORIGINAL_COMMAND?获取到客户端原本想执行的命令,进而运行相关方法。 由于 sshd 在匹配?authorized_keys?时用的是线性检索,在?authorized_keys?很大时,先注册的用户(公钥在文件的前面)的匹配优先级会被后注册的用户高很多,换句话说,老用户的 SSH 鉴权要比新用户的快,而且是可察觉的快。(真·老用户福利) ?黄金老用户的特别福利——超长git push时间。图/xkcd-excuse.com 如今 gitlab-sshd 依赖的 Rails API 背后是 Postgres 索引,这个 bug(feature?)不复存在。 通过用户身份验证后,gitlab-sshd 会检查用户对目标仓库是否有写权限(POST /api/v4/internal/allowed),同时获知这个仓库在哪一个 Gitaly 实例,以及用户 ID 和仓库信息。 最后,gitlab-sshd 会调用对应的 Gitaly 实例的?SSHReceivePack?方法,在 Git 客户端(SSH)与 Gitaly(GRPC)之间作为中继和翻译。 最后两步 gitlab-shell 的行为和 gitlab-sshd 是一样的。 从宏观视角看,经由 SSH 的 git push 是这样的: 1.?用户执行?git push; 2.?Git 客户端通过 SSH 链接到 gitlab-shell; 3.?gitlab-shell 使用客户端公钥调用?GET /api/v4/internal/authorized_keys?获得公钥 ID,进行 SSH 握手; 4.?gitlab-shell 使用公钥 ID 和仓库地址调用?POST /api/v4/internal/allowed,确认用户有到仓库的写权限; 5.?API 返回:Gitaly 地址和鉴权 token、repo 对象、钩子回调信息(逻辑用户名?GL_ID、逻辑项目名?GL_REPOSITORY); 6.?gitlab-shell 用上列信息调用 Gitaly 的?SSHReceivePack?方法,成为客户端和 Gitaly 的中继; 7.?Gitaly 在适当的工作目录运行?git-receive-pack,并且预先设定好环境变量?GITALY_HOOKS_PAYLOAD,其中包含?GL_ID,?GL_REPOSITORY?等; 8.?服务端 Git 尝试更新 refs,运行 Git hooks; 9.?完成。 Gitaly 和 refs 更新我们稍后会聊到。 你更喜欢HTTP(S)?HTTP(S)的远端地址形如?https://jihulab.example.com/user/repo.git.?和 SSH 不一样,HTTP 请求是无状态的,而且总是一问一答。在你执行 git push 时,Git 客户端会按顺序和两个接口打交道:
上述两个请求会被 Workhorse 截获,每次它都做这两件事:
总结一下,经由 HTTP(S) 的 git push 是这样的: 1.?用户执行 git push; 2.?Git客户端调用GET https://jihulab.example.com/user/repo.git/info/refs?service=git-receive-pack,带上对应的authorization header; 3.?Workhorse 截获请求,原样发送请求到 Rails,获得鉴权结果、用户 ID、仓库对应 Gitaly 实例信息; 4.?Workhorse 根据上一步 Rails 的返回信息,调用 Gitaly 的 GRPC 服务?InfoRefsReceivePack,在客户端和 Gitaly 之间充当中继; 5.?Gitaly 在适当的工作目录运行?git-receive-pack,返回 refs 信息; 6.?Git客户端调用POST https://jihulab.example.com/user/repo.git/git-receive-pack; 7.?Workhorse ?截获请求,原样发送请求到 Rails,获得鉴权结果、用户 ID、仓库对应 Gitaly 实例信息; 8.?Workhorse 根据上一步 Rails 的返回信息,调用 Gitaly 的 GRPC 服务?PostReceivePack,在客户端和 Gitaly 之间充当中继; 9.?Gitaly 在适当的工作目录运行?git-receive-pack,并且预先设定好环境变量?GITALY_HOOKS_PAYLOAD,其中包含?GL_ID,?GL_REPOSITORY?等; 10.?服务端 Git 尝试更新 refs,运行 Git hooks; 11.?完成。 Gitaly 和 Git Hooks呼…说完了前面的连接层和权限控制,我们终于得以接近极狐GitLab 的 Git 核心,Gitaly。 Gitaly Logo 图/Gitaly Gitaly 这个名字其实是在玩梗,致敬了 Git 和俄罗斯小镇 Aly,后者在 2010 年俄罗斯人口普查中得出的常住人口是 0,Gitaly 的工程师希望 Gitaly 的大部分操作的磁盘 IO 也是 0。 软件工程师的梗实在是太生硬了,一般人恐怕吃不下…… Gitaly 负责极狐GitLab 仓库的存储和操作,它通过 fork/exec 运行本地的 Git 二进制程序,采用 cgroups 防止单个 Git 吃掉太多 CPU 和内存。仓库存储在本地,路径形如/var/opt/gitlab/git-data/repositories/@hashed/b1/7e/b17ef6d19c7a5b1ee83b907c595526dcb1eb06db8227d650d5dda0a9f4ce8cd9.git,早期极狐GitLab/Gitaly 也使用?#{namespace}/#{project_name}.git?的形式,但是?namespace?和?project_name?都可以被用户修改,这带来了额外的运行开销。 git push 对应 Gitaly 的?SSHReceivePack(SSH)和?PostReceivePack(HTTPS)方法,它们的底部都是 Git 的 git-receive-pack,也就是说,最核心的 refs 和 object 更新由 Git 二进制来完成。git-receive-pack 提供了钩子使得这个过程能够被 Gitaly 介入,这里面还牵扯 Rails,一个单边的请求(不含返回)流程大概像下面这样: Gitaly 在启动 git-receive-pack 时会通过环境变量?GITALY_HOOKS_PAYLOAD?传入一个 Base64 编码的 JSON,其中有仓库信息、Gitaly Unix Socket 地址和链接 token、用户信息、要执行的哪些 Hook(对于 git push,总是下面这几个),并且设定 Git 的?core.hooksPath?参数到 Gitaly 自己在程序启动时准备好的一个临时文件夹,那里的所有 Hook 文件都符号链接到了 gitaly-hooks上。 gitaly-hooks 在被 git-receive-pack 启动后从环境变量读取?GITALY_HOOKS_PAYLOAD,通过 Unix Socket 和 GRPC 连接回 Gitaly,告知 Gitaly 目前执行的 Hook,以及 Git 提供给 Hook 的参数。 pre-receive hook这个钩子会在 Git 收到 git push 时触发一次,在调用 gitlab-hooks 时,Git 会向其标准输入中写入变更信息,即“某个 ref 想从 commit hash A 更新到 commit hash B”,一行一个:
其中?SP?是空格,LF?是换行符。 上述信息回到 Gitaly 之后,Gitaly 会依次调用 Rails 的两个接口:
如果?POST /api/v4/internal/allowed?返回错误,Gitaly 会将错误返回给 gitaly-hooks,gitaly-hooks 会在标准错误中写入错误信息并且退出,退出码非 0. 错误信息会被 git-receive-pack 收集后再写入到标准错误,gitaly-hooks 非 0 的退出码会使得 git-receive-pack 停止处理当前的 git push 而退出,退出码同样非 0,控制权回到 Gitaly,后者收集 git-receive-pack 的标准错误输出,回复 GRPC 响应到 Workhorse/Gitlab-Shell. 细心的同学可能会问,Hooks 在运行的时候,相关的 object 肯定已经上传到服务端了,这时停下来这部分悬空的 object 如何处理呢? 其实没有处理完的 git push 对应的 object 会被先写入到隔离环境中,它们独立存储在?objects?下的一个子文件夹,形如?incoming-8G4u9v,这样如果 Hooks 认为这个 push 有问题,相关的资源就能容易地得到清理了。 update hook这个钩子会在 Git 实际更新 ref 的前一刻触发,每个 ref 触发一次,入参从命令行参数传入:要更新的 ref、旧 commit hash、新 commit hash。目前这个钩子不会与 Rails 互动。 极狐GitLab 同时支持自定义 Git Hooks,pre-receive hook, update hook 和 post-receive hook 都支持,这个操作在 gitlab-hooks 通知 Gitaly 钩子运行时在 Gitaly 中完成。此刻就是触发自定义 update hook 的时候。 图/Vishal Jadhav on Unsplash 图中的这个钩子和计算机科学有着历史悠久的联系……咳咳,好吧我编不下去了,我只是担心你看到这里已经要睡着了,找张图片让你放松一下~ post-receive hook在所有 refs 都得到更新后,Git 会执行一次 post-receive 钩子,它获得的参数与pre-receive钩子相同。 Gitaly 收到 gitaly-hooks 的提醒后,会调用 Rails 的?POST /api/v4/internal/post_receive,Rails 会在这时干很多事:
其中有的操作是异步的,被交给 SideKiq 调度。 结语现在,你已经从客户端到服务端走完了 git push 全程,真是一次伟大的旅程! 勇士,下图就是你的通关宝藏! ? 参考资料如果你想继续深入了解相关内容,下面的资料会是不错的起点: |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/25 21:13:34- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |