前不久分享了关于最新版本的 GitLab 的试用体验,《试用 GitLab 14 以及中国发行版:极狐》

但是众所周知,GitLab 在 v10 版本之后,不断增加功能,逐渐调整重心为一站式平台,产品趋于面向公司和组织,导致其对于服务器资源的依赖与日俱增,从最初的 1GB 左右内存的资源就能流畅运行,膨胀到了目前至少需要 6~7 个GB内存才能够顺滑运行。

对于开发者和小团队而言,如何相对克制和轻量的使用它变成了一个有挑战的事情。所以本篇文章就来试着针对 GitLab 进行配置调整,让其能够以相对低的资源占用提供服务。

写在前面

如果你追求绝对的资源占用,只希望拥有一个轻量的代码仓库,对于项目管理相关功能并不介意,时至今日,GitLab 不论如何优化都难以达到其他聚焦于代码仓库功能的项目,推荐你使用“Gitea”这个轻量的程序,之前有几篇文章有提如何安装部署,以及搭配 CI 使用,相信聪明的你,几分钟就能跑起来这套服务。

但如果你希望拥有类似 GitHub 的项目管理体验,并有私有化部署要求,GitLab 会是不二之选。

为了方便测试安装,我们使用上篇中提到的方式来快速初始化容器环境。(代码仓库 https://github.com/soulteary/linux-scripts):

curl -o- https://raw.githubusercontent.com/soulteary/linux-scripts/main/docker-with-mirror.sh | bash

curl -o- https://raw.githubusercontent.com/soulteary/linux-scripts/main/docker-compose.sh | bash

在优化之前,我们先来看看应用在默认配置启动后的表现如何。

观察默认配置启动的应用

在安装完毕 Docker 环境后,可以使用下面的配置,在不进行任何应用设置的情况下,启动应用:

version: "3"

services:

  gitlab:
    image: gitlab/gitlab-ce:14.0.5-ce.0
    container_name: gitlab
    hostname: gitlab.soulteary.com
    ports:
      - "8080:80"

将上面的内容保存为 docker-compose.yml,然后使用 docker-compose up -d 启动应用。

稍等片刻,待应用 Web 界面正常提供服务后,使用 docker stats 查看初始资源消耗:

CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O       PIDS
4af8ead479b2   gitlab    5.82%     3.197GiB / 7.774GiB   41.13%    10.3kB / 33.5kB   135MB / 224MB   416

可以看到 GitLab 一上来,还未使用的情况下,便吃掉了 3个GB 的内存,如果你持续观察,可以看到 CPU 占用在一直跳动,最低 5% 左右,不时 20% 一下。

进入容器,查看进程树,可以看到类似下面这样壮观的景象。

wrapper─┬─gitaly─┬─2*[ruby───38*[{ruby}]]
        │        └─15*[{gitaly}]
        ├─gitlab-ctl───omnibus-ctl───sh───xargs───tail
        └─runsvdir─┬─runsv─┬─sshd
                   │       └─svlogd
                   ├─runsv─┬─gitlab-logrotat───sleep
                   │       └─svlogd
                   ├─runsv─┬─redis-server───4*[{redis-server}]
                   │       └─svlogd
                   ├─runsv─┬─gitaly-wrapper───8*[{gitaly-wrapper}]
                   │       └─svlogd
                   ├─runsv─┬─postgres───17*[postgres]
                   │       └─svlogd
                   ├─runsv─┬─bundle─┬─2*[bundle───14*[{bundle}]]
                   │       │        ├─4*[bundle───13*[{bundle}]]
                   │       │        └─4*[{bundle}]
                   │       └─svlogd
                   ├─runsv─┬─ruby─┬─bundle───72*[{bundle}]
                   │       │      └─{ruby}
                   │       └─svlogd
                   ├─runsv─┬─gitlab-workhors───11*[{gitlab-workhors}]
                   │       └─svlogd
                   ├─runsv─┬─nginx───9*[nginx]
                   │       └─svlogd
                   ├─runsv─┬─gitlab-exporter───4*[{gitlab-exporter}]
                   │       └─svlogd
                   ├─runsv─┬─redis_exporter───10*[{redis_exporter}]
                   │       └─svlogd
                   ├─runsv─┬─prometheus───12*[{prometheus}]
                   │       └─svlogd
                   ├─runsv─┬─alertmanager───10*[{alertmanager}]
                   │       └─svlogd
                   ├─runsv─┬─postgres_export───9*[{postgres_export}]
                   │       └─svlogd
                   └─runsv─┬─grafana-server───12*[{grafana-server}]
                           └─svlogd

调整配置尝试轻量运行

在了解完默认配置下的程序初始表现后,我们来看看调整配置后的容器内进程树。

wrapper─┬─gitaly─┬─2*[ruby───38*[{ruby}]]
        │        └─12*[{gitaly}]
        ├─gitlab-ctl───omnibus-ctl───sh───xargs───tail
        └─runsvdir─┬─runsv─┬─sshd
                   │       └─svlogd
                   ├─runsv─┬─gitlab-logrotat───sleep
                   │       └─svlogd
                   ├─runsv─┬─redis-server───4*[{redis-server}]
                   │       └─svlogd
                   ├─runsv─┬─gitaly-wrapper───7*[{gitaly-wrapper}]
                   │       └─svlogd
                   ├─runsv─┬─postgres───11*[postgres]
                   │       └─svlogd
                   ├─runsv─┬─bundle───9*[{bundle}]
                   │       └─svlogd
                   ├─runsv─┬─ruby─┬─bundle───18*[{bundle}]
                   │       │      └─{ruby}
                   │       └─svlogd
                   ├─runsv─┬─gitlab-workhors───10*[{gitlab-workhors}]
                   │       └─svlogd
                   └─runsv─┬─nginx───9*[nginx]
                           └─svlogd

可以看到整个树精简了不少,和上文一样,在等待应用 Web 界面正常提供服务后,使用 docker stats 查看初始资源消耗:

CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O        BLOCK I/O       PIDS
9a14fb16a437   gitlab    1.21%     1.967GiB / 7.774GiB   25.30%    19MB / 345kB   254kB / 178MB   200

如果持续观察,可以看到应用 CPU 资源在 1~3% 徘徊,内存稳定在 2GB 的边缘。因为剥离了各种比较重的服务,经过测试,在数小时运行后,程序资源消耗依然非常稳定。

此时的 GitLab 保留的功能有:代码管理、项目管理、Wiki 文档、在线 IDE,各种集成能力。在给出配置之前,先来看看精简掉了哪些功能吧。

关闭不需要的服务

建议根据自己情况进行选择处理,下面我将按照如何节约资源的角度去进行描述。

GitLab 默认提供了软件包仓库、容器仓库、软件依赖管理,这些可以使用我之前分享过的 《使用容器搭建简单可靠的容器仓库》一文中的 distribution,或者 Nexus 进行替代。前者更加轻量高效、后者更加专业。通过设置一些配置项,可以将 GitLab 中这些功能进行关闭。

# 关闭容器仓库功能
gitlab_rails['gitlab_default_projects_features_container_registry'] = false
gitlab_rails['registry_enabled'] = false
registry['enable'] = false
registry_nginx['enable'] = false

# 包仓库、依赖管理
gitlab_rails['packages_enabled'] = false
gitlab_rails['dependency_proxy_enabled'] = false

GitHub Pages 服务为开源项目提供了一个不错的文档、Demo 的展示方式,GitLab 虽说也是如此,但在私有化部署场景,可以使用 CI 结合其他更高效的工具,完成这个功能,比如 Hugo(golang) / MdBook(rust) 。如果你翻阅资料,会发现在 GitLab 内部,Pages 是一个相对复杂的服务,可选择的配置项非常多。同样可以调整配置对其进行关闭。

# GitLab Pages
gitlab_pages['enable'] = false
pages_nginx['enable'] = false

上篇文章中提到过,GitLab 14 中对于监控功能有着非常多的加强,甚至在应用初始化后,会创建一个项目专门用于监控 GitLab 本体的运行状况。对于个人使用场景,由于没有复杂负载压力,做好备份即可保障数据安全,所以监控和性能基准相关的功能都可以考虑关闭。

# 关闭监控和性能基准相关功能
prometheus_monitoring['enable'] = false
alertmanager['enable'] = false
node_exporter['enable'] = false
redis_exporter['enable'] = false
postgres_exporter['enable'] = false
pgbouncer_exporter['enable'] = false
gitlab_exporter['enable'] = false
grafana['enable'] = false
sidekiq['metrics_enabled'] = false

此外,针对应用的性能分析和上报,也可以直接关闭。

# Usage Statistics
gitlab_rails['usage_ping_enabled'] = false
gitlab_rails['sentry_enabled'] = false
grafana['reporting_enabled'] = false

对于个人场景,如果你没有混合云/公有云场景,或不需要使用 GitLab 进行 CD 管理,那么 KAS 和 Terraform 完全可以关闭。而 Kerberos 和 Sentinel 在文档中看起来是企业版软件的功能,为了节约资源,这里一并调整配置,显式声明关闭功能。Mattermost 虽然是一个好用聊天的应用,在单人场景下、或者有熟悉的的IM场景下,这个功能也可以进行关闭。

# GitLab KAS
gitlab_kas['enable'] = false
gitlab_rails['gitlab_kas_enabled'] = false
# Terraform
gitlab_rails['terraform_state_enabled'] = false
# Kerberos 文档说EE only,但是默认值为 true
gitlab_rails['kerberos_enabled'] = false
# Sentinel
sentinel['enable'] = false
# Mattermost
mattermost['enable'] = false
mattermost_nginx['enable'] = false

默认情况下,GitLab 会创建大量 Puma 进程来提供 Web 服务能力。我们可以适当对其进行调整和设置,够用就行。此外管理调度的 sidekiq 也可以调低并发,避免不必要的资源浪费。至于 Gitaly ,实测之后,不建议进行调整,一来节约资源非常非常有限,二来 Gitaly 运行数量如果被过分限制会直接影响使用体验,详见下文。(集群化部署的时候,为了保障体验,我们甚至需要独立部署 Gitaly 服务)

# 禁用 PUMA 集群模式
puma['worker_processes'] = 0
puma['min_threads'] = 1
puma['max_threads'] = 2

# 降低后台守护进程并发数
sidekiq['max_concurrency'] = 5

此外,如果不需要电子邮件相关功能,也可以进行关闭。

# 关闭电子邮件相关功能
gitlab_rails['smtp_enable'] = false
gitlab_rails['gitlab_email_enabled'] = false
gitlab_rails['incoming_email_enabled'] = false

最后,还记得前文中提到 GitLab CPU 占用波动频繁的问题吗?如果你愿意使用轻量的 Drone 对其进行替换的话,可以考虑将默认的 CI 功能关闭,可以将 CPU 资源消耗降低到非常低的数值。

gitlab_ci['gitlab_ci_all_broken_builds'] = false
gitlab_ci['gitlab_ci_add_pusher'] = false

完整配置

将上面的内容进行合并,更新到配置文件中,完整配置如下:

version: "3"


services:
  gitlab:
    restart: always
    image: gitlab/gitlab-ce:14.0.5-ce.0
    container_name: gitlab
    hostname: gitlab.soulteary.com
    ports:
      - "8080:80"
      - "2222:22"
    volumes:
      - ./config:/etc/gitlab
      - ./data:/var/opt/gitlab
    environment:
      TZ: Asia/Shanghai
      GITLAB_OMNIBUS_CONFIG: |
        external_url 'http://gitlab.soulteary.com'
        gitlab_rails['time_zone'] = 'Asia/Shanghai'

        # 关闭电子邮件相关功能
        gitlab_rails['smtp_enable'] = false
        gitlab_rails['gitlab_email_enabled'] = false
        gitlab_rails['incoming_email_enabled'] = false

        # Terraform
        gitlab_rails['terraform_state_enabled'] = false

        # Usage Statistics
        gitlab_rails['usage_ping_enabled'] = false
        gitlab_rails['sentry_enabled'] = false
        grafana['reporting_enabled'] = false

        # 关闭容器仓库功能
        gitlab_rails['gitlab_default_projects_features_container_registry'] = false
        gitlab_rails['registry_enabled'] = false
        registry['enable'] = false
        registry_nginx['enable'] = false

        # 包仓库
        gitlab_rails['packages_enabled'] = false
        gitlab_rails['dependency_proxy_enabled'] = false

        # GitLab KAS
        gitlab_kas['enable'] = false
        gitlab_rails['gitlab_kas_enabled'] = false

        # Mattermost
        mattermost['enable'] = false
        mattermost_nginx['enable'] = false

        # Kerberos
        gitlab_rails['kerberos_enabled'] = false
        sentinel['enable'] = false

        # GitLab Pages
        gitlab_pages['enable'] = false
        pages_nginx['enable'] = false

        # 禁用 PUMA 集群模式
        puma['worker_processes'] = 0
        puma['min_threads'] = 1
        puma['max_threads'] = 2

        # 降低后台守护进程并发数
        sidekiq['max_concurrency'] = 5

        gitlab_ci['gitlab_ci_all_broken_builds'] = false
        gitlab_ci['gitlab_ci_add_pusher'] = false

        # 关闭监控
        prometheus_monitoring['enable'] = false
        alertmanager['enable'] = false
        node_exporter['enable'] = false
        redis_exporter['enable'] = false
        postgres_exporter['enable'] = false
        pgbouncer_exporter['enable'] = false
        gitlab_exporter['enable'] = false
        grafana['enable'] = false
        sidekiq['metrics_enabled'] = false        

使用方式依旧是将上面的内容保存为 docker-compose.yml,使用 docker-compose up -d 启动。至此,你就能够以相对轻量的资源拥有一个具备项目管理和仓库存储、以及有良好体验的在线编辑器的“代码仓库服务”啦。

如果你需要配合 HTTPS 使用,可以参考之前到文章《如何配置 GitLab 使用 HTTPS》结合 Traefik 进行配置调整;如果你有数据备份还原需求,可以阅读《GitLab 简明维护指南(v2020.05)》进行了解。

隐藏界面中不需要的功能

在 GitLab 官方社区中,曾经有用户提到过这样一个问题

需要一个选项能够关闭界面中的 “ Security and Operations ” 选择卡

用户A:Operations选项占用了太多空间,但是并非所有项目都需要这些。例如,我们有一些项目只使用Issues和Wiki 功能,而不包含代码。 用户B:“Security & Compliance” 是付费选项,作为免费用户,它对我来说毫无用处。

这个问题至今还是打开状态,未被官方人员回复,但是其实解决方案也很简单。先将运行容器中菜单相关代码复制到宿主机:

docker cp gitlab:/opt/gitlab/embedded/service/gitlab-rails/lib/sidebars/projects/menus menus

随便打开一个菜单文件,比如 menus/monitor_menu.rb

# frozen_string_literal: true

module Sidebars
  module Projects
    module Menus
      class MonitorMenu < ::Sidebars::Menu
        override :configure_menu_items
        def configure_menu_items
          return false unless context.project.feature_available?(:operations, context.current_user)

          add_item(metrics_dashboard_menu_item)
          add_item(logs_menu_item)
          add_item(tracing_menu_item)
          add_item(error_tracking_menu_item)
          add_item(alert_management_menu_item)
          add_item(incidents_menu_item)
          add_item(serverless_menu_item)
          add_item(terraform_menu_item)
          add_item(kubernetes_menu_item)
          add_item(environments_menu_item)
          add_item(feature_flags_menu_item)
          add_item(product_analytics_menu_item)

          true
        end

...

如果我们想将界面中的内容隐藏,只需要将 def configure_menu_items 函数中的内容清空,替换成 false 即可,如:

# frozen_string_literal: true

module Sidebars
  module Projects
    module Menus
      class MonitorMenu < ::Sidebars::Menu
        override :configure_menu_items
        def configure_menu_items
          false
        end

...

GitLab 将忽略这个菜单程序的初始化,界面中也就看不到类似的按钮啦。当然,修改后的内容要记得映射到容器中,或者重新封装一个属于你的镜像。

...
    volumes:
      - ./config:/etc/gitlab
      - ./data:/var/opt/gitlab
      - ./menus:/opt/gitlab/embedded/service/gitlab-rails/lib/sidebars/projects/menus
...

不推荐调整的配置:Gitaly

前文提到不推荐对 Gitaly 服务进行配置调整,因为这个服务对于环境变量的获取和判断使用在逻辑上有一些小问题。

gitaly['ruby_num_workers'] = 3

即使我们只配置 worker 数量,不进行并发数,不设置 cgroups 限制,也会得到类似下面的错误信息,整个应用会一直重启,但是无法提供正常的服务。

gitlab    | time="2021-07-14T06:49:31Z" level=info msg="Starting GitalyversionGitaly, version 14.0.5"
gitlab    | time="2021-07-14T06:49:31Z" level=warning msg="git path not configured. Using default path resolution" resolvedPath=/opt/gitlab/embedded/bin/git
gitlab    | time="2021-07-14T06:49:31Z" level=fatal msg="load config: config_path \"/var/opt/gitlab/gitaly/config.toml\": cgroups mountpoint cannot be empty"
gitlab    | {"gitaly":4639,"level":"warning","msg":"forwarding signal","signal":17,"time":"2021-07-14T06:49:31.415Z","wrapper":4633}
gitlab    | {"error":"os: process already finished","gitaly":4639,"level":"error","msg":"can't forward the signal","signal":17,"time":"2021-07-14T06:49:31.415Z","wrapper":4633}

观察资源使用,会发现资源消耗所差无几,那么折腾这个服务的意义也就不是很大了。

CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O       BLOCK I/O     PIDS
526b770574af   gitlab    0.94%     1.985GiB / 7.774GiB   25.53%    1.52kB / 0B   0B / 5.27MB   238

此外,官方网站的多篇文档、GitLab 默认配置模版中,对于这个服务的资料和默认值存在多处冲突和错误,以及存在未文档声明的配置,处于一个“黑盒状态”。所以非常不推荐配置折腾这个服务。

其他

还记得前文中,我曾提到“时至今日,GitLab 不论如何优化都难以达到其他聚焦于代码仓库功能的项目”吗?

在早些时候的《容器方式使用轻量的 GitLab 低版本》 一文中,我分享过如何使用低版本的 GitLab ,那篇文章中的 GitLab 的进程树状况如下。

wrapper─┬─gitlab-ctl───omnibus-ctl───sh───xargs───tail
        └─runsvdir─┬─runsv─┬─sshd
                   │       └─svlogd
                   ├─runsv─┬─postgres───8*[postgres]
                   │       └─svlogd
                   ├─runsv─┬─bundle─┬─bundle───7*[{bundle}]
                   │       │        └─3*[{bundle}]
                   │       └─svlogd
                   ├─runsv─┬─bundle───17*[{bundle}]
                   │       └─svlogd
                   ├─runsv─┬─gitlab-workhors───18*[{gitlab-workhors}]
                   │       └─svlogd
                   ├─runsv─┬─nginx───2*[nginx]
                   │       └─svlogd
                   └─runsv─┬─gitlab-logrotat───sleep
                           └─svlogd

不论是和本文中默认配置运行的 GitLab 相比较,还是和调整配置后的 GitLab 相比较,你会发现 GitLab 服务臃肿已经是必然事实。这也是我们常听到的 GitLab “比较重”的本质原因。

在产品越来越面向 B 端用户,有盈利压力的背景下,相比较开发效率,性能永远是最末端需要考虑的事情。

最后

本篇内容,针对之前在群里的讨论做了实践尝试。一番折腾下来,个人使用场景还是更推荐使用 Gitea,舍弃项目管理功能、舍弃内置在线 Web IDE 功能,可以快速拥有一个稳定轻量的仓库服务。

而团队使用场景,GitLab 依旧值得投入一些硬件资源去深入使用。不过,是否使用 GitLab 深入的管理项目,以及结合 GitLab 改变团的工作模式,可能落地难度还是比较大的。这块官方或许也发现了,所以出现了越来越多的最佳实践分享和培训课程。

或许短时间内 GitLab v12/v13 是更稳妥的选择。

–EOF