跳转至

Git 服务

主要作者

@taoky

本文编写中

本文讨论在镜像站场景下,为不特定用户提供基于 HTTP(S) 协议的 Git 拉取服务(Git over HTTP(S))相关的知识与优化技巧。本文涉及以下主题:

  • 交互式的 Git 服务,例如 GitWebGitLabForgojo
  • 通过 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 支持使用 FastCGISCGI 模块,它们在 CGI 的基础上自定义了优化的协议。为了运行我们的 CGI 程序,这里安装 fcgiwrap 包。fcgiwrap 可以将 CGI 程序包装为 FastCGI 接口,以此对接 Nginx 的 ngx_http_fastcgi_module 模块。

在启动 fcgiwrap.socketfcgiwrap.service 后,对应的 Nginx 配置类似如下:

/etc/nginx/snippets/git-http
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_bufferingNO_BUFFERING 参数,分别关闭 Nginx fastcgi 模块 和 fcgiwrap 的缓冲区,使得客户端可以更快看到 Git 操作的进度。
    • 需要注意的是,NO_BUFFERING 需要特殊的补丁。Debian 打包的版本包含这个修改。
  • fcgiwrap 会接受 SCRIPT_FILENAME 参数决定需要启动的 CGI 程序(优先级高于 DOCUMENT_ROOTSCRIPT_NAME)。
  • git-http-backend 会根据 PATH_INFOGIT_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 作为环境变量文件:

Environment=DAEMON_OPTS=-f
EnvironmentFile=-/etc/default/fcgiwrap

因此可以修改此文件来设置最大总并发数:

/etc/default/fcgiwrap
# 预先 fork 128 个 fcgiwrap 进程
DAEMON_OPTS="-f -c 128"

调试 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

Git over HTTP(S) 协议介绍

Dumb Protocol

Smart Protocol

Git 服务的优化