GitLab Workhorse
上一回介绍了 GitLab 的基础功能和架构,但还没具体讲解用户的请求是怎么被处理的,只是将各个组件的功能职责介绍了一遍,本节将简单介绍 gitlab-workhorse 的功能
首先回顾一下:GitLab 利用 Nginx 将前端的 http/https 请求代理至 gitlab-workhorse,gitlab-workhorse 再将请求转发至 Unicorn Web 服务器。默认情况下 gitlab-workhorse 与前端之间的通信是使用 unix domain socket 进行的,但也支持 TCP 转发请求;GitLab 使用 Unicorn Web 服务器提供动态网页和 API 接口
1. Nginx 入口
从架构图可以看出,HTTP/HTTPS 请求进入 GitLab 的第一站是 nginx
下载 GitLab-ce 官方源码后,进入 ${gitlab-ce根目录}/lib/support/nginx
,打开 gitlab-ssl
可以看到 nginx 的配置
GitLab 默认将 http 请求重定向至 https 请求
## Redirects all HTTP traffic to the HTTPS host
server {
listen 0.0.0.0:80;
listen [::]:80 ipv6only=on default_server;
server_name YOUR_SERVER_FQDN;
server_tokens off;
return 301 https://$http_host$request_uri;
access_log /var/log/nginx/gitlab_access.log gitlab_ssl_access;
error_log /var/log/nginx/gitlab_error.log;
}
复制代码
https 的设置相对比较复杂,这里仅提一个亮点:location: /
下面的 proxy_pass http://gitlab-workhorse;
,说明了 除了某些静态页面,nginx 几乎将所有的 http/https 请求传递给了 gitlab-workhorse 组件处理(使用 unix socket 通信)
Unix Socket 是一种 socket 方式实现的进程间通信功能,它不需要进行复杂的数据打包拆包、校验和计算验证,不需要走网络协议栈,安全可靠。Unix Socket 其实是 socket 的其中一种 AF_UNIX 或 AF_LOCAL 的类型,成为 unix domain socket,是为了进行本地通信,也就是为了实现 IPC,所以构造函数不需要IP和端口,取而代之的是文件路径
upstream gitlab-workhorse {
# GitLab socket file,
# for Omnibus this would be: unix:/var/opt/gitlab/gitlab-workhorse/socket
server unix:/home/git/gitlab/tmp/sockets/gitlab-workhorse.socket fail_timeout=0;
}
...
## HTTPS host
server {
listen 0.0.0.0:443 ssl;
listen [::]:443 ipv6only=on ssl default_server;
server_name YOUR_SERVER_FQDN;
server_tokens off;
ssl on;
ssl_certificate /etc/nginx/ssl/gitlab.crt;
ssl_certificate_key /etc/nginx/ssl/gitlab.key;
ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 5m;
real_ip_recursive off; ## If you enable 'on'
access_log /var/log/nginx/gitlab_access.log gitlab_ssl_access;
error_log /var/log/nginx/gitlab_error.log;
location / {
client_max_body_size 0;
gzip off;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade_gitlab_ssl;
proxy_pass http://gitlab-workhorse;
}
error_page 404 /404.html;
error_page 422 /422.html;
error_page 500 /500.html;
error_page 502 /502.html;
error_page 503 /503.html;
location ~ ^/(404|422|500|502|503)\.html$ {
root /home/git/gitlab/public;
internal;
}
}
复制代码
2. GitLab-workhorse
那么 GitLab-workhorse 是什么呢?官方解释称它是 GitLab 的智能反向代理服务器,用于处理负载量较大的 HTTP 请求,诸如文件上传/下载、Git push/pull 以及 Git 归档下载等。而实际上可能比较复杂
+-------+ +------------------+ +---------+
| | | | | |
| NGINX +->| gitlab-workhorse +->| Unicorn |
| | | | | |
+-------+ +------------------+ +---------+
复制代码
下述的 Rails 组件是运行在 Unicorn Web 服务器的:
- workhorse 能处理一些无需调用 Rails 组件的请求,例如静态的 js/css 资源文件
- workhorse 能修改 Rails 组件发来的响应。例如:假设你的 Rails 组件使用
send_file
,那么 gitlab-workhorse 将会打开磁盘中的文件然后把文件内容作为响应体返回给客户端 - workhorse 能接管向 Rails 组件询问操作权限后的请求,例如 处理
git clone
之前得确认当前客户的权限,在向 Rails 组件询问确认后 workhorse 将继续接管git clone
的请求 - workhorse 能修改发送给 Rails 组件之前的请求信息。例如:当处理 Git LFS 上传时,workhorse 首先向 Rails 组件询问当前用户是否有执行权限,然后它将请求体储存在一个临时文件里,接着它将修改过后的包含此临时文件路径的请求体发送给 Rails 组件
- workhorse 能管理与 Rails 组件通信的长时间存活的 websocket 连接
- workhorse 无法直接连接数据库,只能与 Rails 组件和 Redis 组件(可选)通信
- 所有到达 workhorse 的请求都是由上游代理服务器(nginx)转发而来
- workhorse 不接受 https 连接
- workhorse 不会清除空闲客户端连接
- 所有对 Rails 组件的请求都得经过 workhorse
比如 Unicorn 处理静态资源文件效率相对较低,那就交给 workhorse 处理。由于文章篇幅的原因,这里仅挑一个例子进行讲解:gzip 资源文件
在 ${gitlab-workhorse根目录}/internal/staticpages/servefile.go
的函数 func (s *Static) ServeExisting
内,定义了 workhorse 对静态资源文件的处理方式
假设我们得请求相对 URL 为 /assets/locale/zh_CN/app-3396bd500e53f89d971d8c31ba7275f1c9ae2899062d4a7aeef14339084f44bd.js
,因为带有 assets
前缀,所以 workhorse 将采用处理静态资源文件的方式处理请求,如代码所示
// ${gitlab-workhorse根目录}/internal/upstream/routes.go
// Serve assets
route(
"", `^/assets/`,
static.ServeExisting(
u.URLPrefix,
staticpages.CacheExpireMax,
NotFoundUnless(u.DevelopmentMode, proxy),
),
withoutTracing(), // Tracing on assets is very noisy
),
复制代码
下图对 js 静态资源文件的请求 /assets/locale/zh_CN/app-3396bd500e53f89d971d8c31ba7275f1c9ae2899062d4a7aeef14339084f44bd.js
,就是使用 gzip 的方式
如果用户请求头带 Accept-Encoding: gzip
的话,workhorse 将读取相应请求静态资源的 gzip 文件(服务器预压缩的静态资源文件),并向浏览器传送(并非直接传送,还得经过 nginx 服务器)压缩过的内容,浏览器接收到服务器响应之后判断内容是否被压缩,如果被压缩则进行解压缩;当然如果用户请求头不指示使用 gzip ,那 workhorse 就会读取源文件
// ${gitlab-workhorse根目录}/internal/staticpages/servefile.go
// ...省略部分代码
file := filepath.Join(s.DocumentRoot, prefix.Strip(r.URL.Path))
// ...省略部分代码
// Serve pre-gzipped assets
if acceptEncoding := r.Header.Get("Accept-Encoding"); strings.Contains(acceptEncoding, "gzip") {
content, fi, err = helper.OpenFile(file + ".gz")
if err == nil {
w.Header().Set("Content-Encoding", "gzip")
}
}
复制代码
从下图感受一下,压缩后的 gz 文件比源文件的 1/3 还小,可以说是大大节省服务器的网络带宽了
同时我们也该注意到,当访问静态资源文件的时候,请求并没有转发到 Unicorn Web 服务器,而是 workhorse 亲自处理了,这就是 workhorse 组件存在的最大意义,就是弥补 Unicorn Web 服务器存在的缺陷。这就得联想一个名言了:
Any problem in computer science can be solved by another layer of indirection
计算机科学领域的任何问题都可以以增加一个中间层来搞定,原来都是满满的套路啊
最后做个总结吧:workhorse 组件最初是为了解决 git-over-http/https 超时的问题,或者换句话说,workhorse 组件解决的是 unicorn 服务器不擅长处理的请求,而诸如动态页面渲染等请求将由 workhorse 帮忙代理至 unicorn 服务器处理,因为 unicorn 服务器擅长处理这类请求。至于最前端的 nginx 服务器则主要用于 https 配置等目的
附录
参考链接
转载于:https://juejin.im/post/5cf680f86fb9a07ed5248cda