Git 服务¶
主要作者
本文编写中
本文讨论在镜像站场景下,为不特定用户提供基于 HTTP(S) 协议的 Git 拉取服务(Git over HTTP(S))相关的知识与优化技巧。本文不涉及以下主题:
- 交互式的 Git 服务,例如 GitWeb、GitLab、Forgojo
- 通过 Git 协议(TCP 9418)或 SSH 协议拉取数据
- Git 与用户认证
- 需要用户向服务器推送(push)变更的场景
建议在阅读网络服务实践的 Nginx 服务器与版本管理与合作后再阅读本部分。
其他实现:JGit
以下介绍的是基于 Git 官方的 C 实现的内容,不过这不是唯一的选择。Google 的 Gerrit 平台使用的就是基于 Java 的 JGit,其实现了完整的与 Git 服务有关的功能。
搭建服务¶
Git over HTTP(S) 有两种传输协议:Dumb protocol 和 Smart protocol,前者不需要运行专门的服务,而后者需要。Smart protocol 处理用户请求的组件是 git-http-backend,这是一个 CGI 程序。
CGI
CGI(Common Gateway Interface)是一种传统的 Web 服务器与用户程序交互的接口:对需要给程序处理的每个 HTTP 连接,Web 服务器读取用户请求的头之后,启动用户程序,将请求头信息(例如请求方法 REQUEST_METHOD、请求路径 PATH_INFO)放在环境变量中,请求的 body 通过标准输入提供给用户程序,而程序的标准输出则会在 Web 服务器处理后作为返回给用户的响应。可参考 RFC 3875 了解相关标准。
由于 CGI 程序每个请求都有创建与销毁进程的开销,因此如今大部分网络应用都会直接处理 HTTP 请求,Web 服务器通过反代(proxy_pass)的方式将请求转发给对应的网络应用。
Nginx 支持使用 FastCGI 或 SCGI 模块,它们在 CGI 的基础上自定义了优化的协议。为了运行我们的 CGI 程序,这里安装 fcgiwrap 包。fcgiwrap 可以将 CGI 程序包装为 FastCGI 接口,以此对接 Nginx 的 ngx_http_fastcgi_module 模块。
在启动 fcgiwrap.socket 或 fcgiwrap.service 后,对应的 Nginx 配置类似如下:
fastcgi_read_timeout 5m;
fastcgi_pass unix:/var/run/fcgiwrap.socket;
fastcgi_buffering off;
fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
fastcgi_param PATH_INFO $uri;
fastcgi_param NO_BUFFERING "";
fastcgi_param GIT_PROJECT_ROOT /srv/git;
fastcgi_param GIT_HTTP_EXPORT_ALL "";
fastcgi_param GIT_PROTOCOL $http_git_protocol;
include fastcgi_params;
其中的一些重要参数:
- 配置
fastcgi_buffering与NO_BUFFERING参数,分别关闭 Nginx fastcgi 模块 和 fcgiwrap 的缓冲区,使得客户端可以更快看到 Git 操作的进度。- 需要注意的是,
NO_BUFFERING需要特殊的补丁。Debian 打包的版本包含这个修改。
- 需要注意的是,
- fcgiwrap 会接受
SCRIPT_FILENAME参数决定需要启动的 CGI 程序(优先级高于DOCUMENT_ROOT和SCRIPT_NAME)。 git-http-backend会根据PATH_INFO和GIT_PROJECT_ROOT选择合适的仓库。这里假设所有的仓库都在/srv/git下方(仓库目录可以是指向其他目录的软链接)。- 默认情况下,
git-http-backend会检查仓库.git下是否包含git-daemon-export-ok这个文件。GIT_HTTP_EXPORT_ALL参数可以解除这个限制。 - Smart protocol 有 v1 和 v2 两个版本。
GIT_PROTOCOL参数可以传递 HTTP 头中客户端传递的版本信息,在允许的情况下升级到 v2 协议。 /etc/nginx/fastcgi_params为 Nginx 打包自带提供的文件,设置了诸如REQUEST_METHOD等必要的参数,直接include即可。
之后对需要提供服务的仓库,对应的 location 中 include snippets/git-http 即可:
# 假设仓库在 `/srv/git/xxx.git`
location ~ ^/.+?\.git/(info/refs|git-upload-pack) {
include snippets/git-http;
}
并发数量注意事项
服务器可以同时处理的 Git 操作的硬上限由 fcgiwrap 的进程数量决定。在服务启动时,fcgiwrap 会根据 -c 参数的值,预先 fork 出对应的进程数。由于 fcgiwrap.service 设置了 /etc/default/fcgiwrap 作为环境变量文件:
因此可以修改此文件来设置最大总并发数:
调试 git-http-backend
如果遇到请求缓慢、出现错误,甚至 git 崩溃的情况,你可能会希望能够调试 git 在接受请求时的行为。可以用类似 CGI 的形式调用 git-http-backend。在抓包后,将对应需要排查的请求的 HTTP body 存储在文件中,然后根据 HTTP 头的信息,设置对应的环境变量,调用即可,例如:
GIT_PROTOCOL=version=2 CONTENT_TYPE=application/x-git-upload-pack-request GIT_HTTP_EXPORT_ALL="" GIT_PROJECT_ROOT=/srv/git/ PATH_INFO=/crates.io-index/git-upload-pack REQUEST_METHOD=POST git http-backend < input.txt > output.txt
同时 git 也提供了一些用于调试的环境变量,例如设置 GIT_TRACE2=1 后,git 会将其 trace 信息输出到标准错误,有关调试环境变量的信息,可参考 git。