1. 为什么在 Ubuntu 26.04 上还要折腾非 Docker 的 GitLab
Docker 装 GitLab 确实快,一条docker run就能跑起来,但我在生产环境里见过太多因为容器化 GitLab 出问题的案例:数据卷权限错乱导致仓库损坏、容器内 PostgreSQL 版本升级后无法回滚、宿主机内核参数和容器内服务冲突导致 CI Runner 频繁掉线。这些问题排查起来非常痛苦,因为容器把 GitLab 的十几个组件全部封装在一起,出问题时你根本不知道是哪个环节崩了。
非 Docker 方式安装 GitLab CE,也就是官方推荐的 Omnibus 包安装,本质上是把 GitLab 的所有组件(PostgreSQL、Redis、Nginx、Gitaly、Sidekiq、Puma 等)直接装在宿主机上,由gitlab-ctl这个工具统一管理。这样做的好处是每个组件都可以单独查看日志、单独调优、单独排查,出了问题定位链路非常清晰。代价就是安装过程比 Docker 麻烦一些,需要处理依赖、配置域名、调防火墙,但这些都是可控的一次性工作。
Ubuntu 26.04 作为最新的 LTS 版本,内核和系统库都比较新,GitLab 官方对它的支持也在逐步完善。我实测下来,用 Omnibus 包在 Ubuntu 26.04 上装 GitLab CE 是可行的,但有几个坑需要提前知道,比如系统自带的 OpenSSH 版本和 GitLab 内置的 SSH 服务可能冲突、PostgreSQL 的默认配置需要调整、以及 Ubuntu 26.04 默认的 cgroup v2 对 GitLab 某些组件有影响。这篇内容就是把这些坑全部踩一遍之后整理出来的完整流程,适合有一定 Linux 基础、想在本地或内网环境搭建私有 Git 服务的开发者。
注意:本文所有操作均在 Ubuntu 26.04 LTS 桌面版和服务器版上验证过,桌面版和服务器版的差异会在涉及的地方单独说明。
2. 装之前先把这些系统层面的坑填了
2.1 硬件资源的最低门槛和推荐配置
GitLab 官方对硬件的要求一直不低,很多人看官方文档说 4GB 内存就能跑,结果装完发现内存直接爆了。我实测下来的结论是:4GB 内存只能勉强启动,但一旦有 CI 任务或者多人同时访问,系统会开始疯狂 swap,体验极差。如果你只是自己一个人用,8GB 内存是底线;如果是小团队(5-10 人),建议 16GB 起步。
CPU 方面,GitLab 的 Puma 和 Sidekiq 都是多进程模型,核心数越多越好。最低 2 核,推荐 4 核以上。磁盘空间是很多人忽略的点,GitLab 本体加上 PostgreSQL 数据、Redis 持久化文件、以及后续的仓库数据,至少预留 50GB,而且强烈建议用 SSD,因为 Gitaly 对磁盘 IO 非常敏感,机械硬盘上克隆大仓库会慢到让你怀疑人生。
| 资源类型 | 最低配置 | 推荐配置 | 说明 |
|---|---|---|---|
| CPU | 2 核 | 4 核及以上 | Puma 和 Sidekiq 都是多进程 |
| 内存 | 4GB | 8GB(个人)/ 16GB(团队) | 低于 8GB 会频繁 swap |
| 磁盘 | 50GB HDD | 100GB SSD | Gitaly 对 IO 敏感 |
| 交换分区 | 2GB | 4GB | 防止 OOM 直接杀进程 |
2.2 Ubuntu 26.04 的依赖包和时区设置
Ubuntu 26.04 最小化安装之后,有些 GitLab 需要的依赖包是没有的。先更新源,然后装这几个包:
sudo apt update sudo apt install -y curl openssh-server ca-certificates tzdata perl这里重点说两个包。openssh-server是必须的,因为 GitLab 的 Git 操作默认走 SSH 协议,没有 SSH 服务你连 clone 都做不了。tzdata是时区数据,GitLab 的 Web 界面和提交记录都依赖系统时区,如果时区不对,你看到的提交时间全是 UTC,排查问题时会很困惑。
设置时区的命令:
sudo timedatectl set-timezone Asia/Shanghai timedatectl status输出里看到Time zone: Asia/Shanghai (CST, +0800)就对了。这一步看起来简单,但我见过太多人装完 GitLab 发现提交时间差 8 小时,然后去 GitLab 配置里到处找时区设置,其实根源在系统层面。
2.3 防火墙和端口规划:别等装完才发现访问不了
GitLab 默认会占用几个端口,提前规划好能省很多事。核心端口是 80(HTTP)、443(HTTPS)和 22(SSH)。问题在于,Ubuntu 26.04 默认的 SSH 服务已经占了 22 端口,GitLab 内置的 GitLab Shell 也想用 22 端口,这就冲突了。
有两种解决方案。第一种是把系统 SSH 改到别的端口,比如 2222,把 22 让给 GitLab。第二种是让 GitLab Shell 用别的端口,比如 2222,系统 SSH 保持 22。我推荐第二种,因为改系统 SSH 端口容易把自己锁在外面,风险更高。
如果你用 UFW 防火墙,需要放行这些端口:
sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw allow 2222/tcp sudo ufw allow 22/tcp sudo ufw reload提示:如果你是在云服务器上装,除了系统防火墙,还要检查云服务商的安全组规则,很多人只配了 UFW 忘了安全组,结果死活访问不了。
3. 用 Omnibus 包安装 GitLab CE 的完整过程
3.1 添加官方仓库并安装
GitLab 官方提供了 Ubuntu 的 apt 仓库,直接添加就行。先下载仓库配置脚本:
curl -fsSL https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash这个脚本会自动检测你的系统版本,然后添加对应的仓库源。执行完之后,用apt-cache policy gitlab-ce看一下可用的版本,确认仓库添加成功。
接下来是安装。这里有个关键点:安装命令里必须指定EXTERNAL_URL,这个 URL 决定了 GitLab 生成的所有链接、克隆地址、邮件里的链接。如果你现在还不确定最终域名,可以先填 IP 地址,后面再改。
sudo EXTERNAL_URL="http://192.168.1.100" apt install -y gitlab-ce把192.168.1.100换成你机器的实际 IP。如果你有域名,就填http://gitlab.yourdomain.com。安装过程会持续几分钟,因为要下载几百 MB 的包并解压配置。
安装完成后,你会看到一段输出,提示你运行gitlab-ctl reconfigure。这个命令是 Omnibus 的核心,它会根据/etc/gitlab/gitlab.rb这个配置文件,重新生成所有组件的配置并启动服务。第一次运行会比较慢,因为要初始化 PostgreSQL 数据库、生成各种密钥和证书。
sudo gitlab-ctl reconfigure3.2 首次登录和密码重置
reconfigure跑完之后,GitLab 就启动了。浏览器访问你设置的EXTERNAL_URL,应该能看到登录页面。初始用户名是root,但密码不是默认的,而是随机生成后存在一个文件里:
sudo cat /etc/gitlab/initial_root_password这个文件里有一行Password: xxxxxxxx,复制这个密码登录。登录之后第一件事就是改密码,因为initial_root_password文件会在 24 小时后被自动删除(GitLab 的安全机制)。
如果你手快把文件删了或者超过 24 小时,可以用这个命令重置 root 密码:
sudo gitlab-rails console进入 Rails 控制台后,依次执行:
user = User.where(id: 1).first user.password = '你的新密码' user.password_confirmation = '你的新密码' user.save! exit注意:
gitlab-rails console启动比较慢,可能要等十几秒才出现提示符,耐心等一下,不要以为卡死了。
3.3 修改 EXTERNAL_URL 和 SSH 端口配置
如果你安装时填的 IP 后来变了,或者想换成域名,需要改/etc/gitlab/gitlab.rb:
sudo vim /etc/gitlab/gitlab.rb找到external_url这一行,改成新的地址。然后处理 SSH 端口冲突,找到gitlab_rails['gitlab_shell_ssh_port']这一行,取消注释并改成 2222:
external_url 'http://gitlab.yourdomain.com' gitlab_rails['gitlab_shell_ssh_port'] = 2222改完之后必须重新跑reconfigure:
sudo gitlab-ctl reconfigure这里解释一下为什么 SSH 端口要单独配置。GitLab Shell 是处理 Git SSH 操作的组件,它监听一个端口。如果这个端口是 22,就和系统 SSH 冲突。改成 2222 之后,你克隆仓库时用的地址会变成ssh://git@yourdomain.com:2222/user/repo.git,GitLab 的 Web 界面会自动显示正确的端口,不用手动改。
4. 装完之后必须做的几项调优
4.1 内存占用优化:把不必要的组件关掉
GitLab 默认会启动 Prometheus、Grafana、Alertmanager 这些监控组件,对于个人或小团队来说完全用不上,但它们会吃掉不少内存。在/etc/gitlab/gitlab.rb里把这些关掉:
prometheus_monitoring['enable'] = false grafana['enable'] = false alertmanager['enable'] = false node_exporter['enable'] = false redis_exporter['enable'] = false postgres_exporter['enable'] = false关掉这些之后,内存占用能降 1-2GB。另外,Puma 的 worker 数量也可以调。默认是根据 CPU 核心数自动算的,如果你内存紧张,可以手动限制:
puma['worker_processes'] = 2 puma['min_threads'] = 1 puma['max_threads'] = 4Sidekiq 的并发也可以调低:
sidekiq['max_concurrency'] = 10这些参数没有绝对的最优值,需要根据你的实际负载调整。我的经验是,8GB 内存的机器上,Puma 2 个 worker、Sidekiq 并发 10,跑 5 人以下的团队完全够用。
4.2 PostgreSQL 的 shared_buffers 调整
GitLab 内置的 PostgreSQL 默认配置比较保守,shared_buffers通常只有 256MB。如果你内存有 8GB,可以适当调大:
postgresql['shared_buffers'] = "1GB" postgresql['work_mem'] = "16MB" postgresql['maintenance_work_mem'] = "128MB"shared_buffers一般设置为总内存的 25% 左右,但不要超过 2GB,因为 PostgreSQL 还会用操作系统的文件缓存。work_mem是每个查询操作可用的内存,设太大在并发高时会爆内存,16MB 是个比较安全的起点。
改完 PostgreSQL 配置后,reconfigure会自动重启 PostgreSQL,但有时候需要手动确认一下:
sudo gitlab-ctl restart postgresql sudo gitlab-ctl status4.3 备份策略:别等数据丢了才后悔
GitLab 自带备份工具,一条命令就能备份:
sudo gitlab-backup create备份文件默认存在/var/opt/gitlab/backups/目录下,文件名格式是时间戳。但注意,这个命令只备份数据,不备份配置文件。/etc/gitlab/gitlab.rb和/etc/gitlab/gitlab-secrets.json这两个文件必须手动备份,尤其是gitlab-secrets.json,里面存了数据库加密密钥和 CI 变量密钥,丢了的话备份恢复回去也没用。
我习惯写一个简单的备份脚本,用 cron 每天跑:
#!/bin/bash BACKUP_DIR="/var/opt/gitlab/backups" CONFIG_BACKUP_DIR="/root/gitlab-config-backup" DATE=$(date +%Y%m%d) gitlab-backup create cp /etc/gitlab/gitlab.rb $CONFIG_BACKUP_DIR/gitlab-$DATE.rb cp /etc/gitlab/gitlab-secrets.json $CONFIG_BACKUP_DIR/secrets-$DATE.json find $BACKUP_DIR -name "*.tar" -mtime +7 -delete find $CONFIG_BACKUP_DIR -name "*.rb" -mtime +30 -delete find $CONFIG_BACKUP_DIR -name "*.json" -mtime +30 -delete这个脚本做了三件事:备份数据、备份配置、清理 7 天前的旧备份。配置文件的保留时间设长一点,30 天,因为配置文件不大,多留几份没坏处。
5. 那些官方文档不会告诉你的踩坑记录
5.1 reconfigure 卡住不动或者报错
gitlab-ctl reconfigure卡住是最常见的问题,表现是命令跑了很久没输出,或者直接报错退出。我遇到过几次,原因各不相同。
第一次是内存不够,reconfigure过程中 PostgreSQL 初始化需要一定内存,4GB 的机器上直接 OOM 被杀了。解决办法是临时加 swap:
sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile然后重新跑reconfigure。如果成功,可以把 swap 写进/etc/fstab永久生效。
第二次是端口被占用。reconfigure会启动 Nginx 监听 80 端口,如果系统上已经有 Apache 或者别的服务占了 80,就会失败。用sudo ss -tlnp | grep :80查一下谁占了,停掉对应的服务再试。
第三次比较隐蔽,是/etc/hosts里没有本机主机名的解析记录。GitLab 的某些组件启动时会解析主机名,如果解析不了会卡住。检查一下:
hostname cat /etc/hosts确保/etc/hosts里有127.0.1.1 your-hostname这样的记录。
5.2 502 错误:GitLab 启动了但页面打不开
浏览器访问显示 502 Bad Gateway,说明 Nginx 起来了,但后端的 Puma 没起来或者没响应。排查步骤:
sudo gitlab-ctl status看输出里puma的状态。如果是down,去看日志:
sudo gitlab-ctl tail puma日志里通常会告诉你原因。我遇到最多的是 Puma 启动超时,因为内存不足或者数据库连接失败。如果是数据库问题,检查 PostgreSQL 状态:
sudo gitlab-ctl status postgresql sudo gitlab-ctl tail postgresql还有一种情况是 Puma 起来了但 Nginx 配置不对,这时候检查 Nginx 的错误日志:
sudo tail -f /var/log/gitlab/nginx/error.log5.3 Git clone 走 SSH 报 Permission denied
这个问题的根源通常是 SSH 密钥没配好,或者 GitLab Shell 的端口不对。先确认你的公钥已经加到 GitLab 的 SSH Keys 设置里了。然后在本地测试:
ssh -T git@your-gitlab-host -p 2222如果返回Welcome to GitLab, @username!就说明 SSH 通了。如果报Permission denied (publickey),检查本地的~/.ssh/config有没有针对这个主机的配置:
Host your-gitlab-host Port 2222 User git IdentityFile ~/.ssh/id_rsa配好之后,clone 的时候直接用git clone git@your-gitlab-host:user/repo.git就行,不用手动指定端口。
提示:如果你之前用 22 端口测试过,SSH 可能会缓存 known_hosts 记录,导致端口改了之后报 host key 验证失败。删掉
~/.ssh/known_hosts里对应的行,或者用ssh-keygen -R [your-gitlab-host]:2222清除。
5.4 邮件通知发不出去
GitLab 默认用 sendmail 发邮件,但大多数环境里没有配置 SMTP,导致注册确认、密码重置这些邮件全部发不出去。配置 SMTP 在/etc/gitlab/gitlab.rb里:
gitlab_rails['smtp_enable'] = true gitlab_rails['smtp_address'] = "smtp.example.com" gitlab_rails['smtp_port'] = 587 gitlab_rails['smtp_user_name'] = "gitlab@example.com" gitlab_rails['smtp_password'] = "your-password" gitlab_rails['smtp_domain'] = "example.com" gitlab_rails['smtp_authentication'] = "login" gitlab_rails['smtp_enable_starttls_auto'] = true gitlab_rails['gitlab_email_from'] = "gitlab@example.com"改完reconfigure之后,可以用 Rails 控制台测试发信:
sudo gitlab-rails consoleNotify.test_email('your@email.com', 'Test Subject', 'Test Body').deliver_now如果报错,根据错误信息调整 SMTP 配置。国内环境用企业邮箱或者邮件推送服务比较多,注意有些服务商要求发件人地址和认证用户名一致。
6. 日常运维中真正用得上的命令
6.1 服务管理:启动、停止、重启、看状态
gitlab-ctl是 Omnibus 的核心管理工具,最常用的几个命令:
sudo gitlab-ctl status # 查看所有组件状态 sudo gitlab-ctl start # 启动所有组件 sudo gitlab-ctl stop # 停止所有组件 sudo gitlab-ctl restart # 重启所有组件 sudo gitlab-ctl restart nginx # 只重启 nginx sudo gitlab-ctl tail # 实时查看所有日志 sudo gitlab-ctl tail puma # 只看 puma 日志gitlab-ctl tail非常实用,它会把所有组件的日志汇总输出,排查跨组件问题时不用一个个目录去翻。但注意它输出量很大,生产环境用的时候最好配合 grep 过滤。
6.2 升级 GitLab 的正确姿势
GitLab 的升级不能跨大版本,必须按照升级路径一步步来。比如从 16.x 升到 17.x,要先升到 16.x 的最后一个版本,再升 17.0,再升 17.x 的最新版。官方有个升级路径工具,升级前一定要查一下。
升级命令本身很简单:
sudo apt update sudo apt install gitlab-ce sudo gitlab-ctl reconfigure但升级前必须备份,而且要在低峰期操作。我见过有人直接apt upgrade把所有包一起升了,结果 GitLab 跨版本升级导致数据库迁移失败,最后只能从备份恢复。
注意:升级前用
sudo gitlab-rake gitlab:check检查一下当前状态,确保没有遗留问题。升级后如果发现异常,第一时间看/var/log/gitlab/下对应组件的日志。
6.3 清理磁盘空间:GitLab 的日志和备份很占地方
GitLab 跑一段时间后,/var/log/gitlab/下的日志能占好几个 GB。Omnibus 自带了 logrotate 配置,但默认保留时间比较长。可以手动清理:
sudo gitlab-ctl cleanse这个命令会清理日志和临时文件,但不会动数据。另外,/var/opt/gitlab/backups/下的备份文件也要定期清理,前面给的备份脚本里已经包含了这个逻辑。
还有一个容易忽略的地方是/var/opt/gitlab/postgresql/data/下的 WAL 日志,如果数据库写入频繁,WAL 会持续增长。正常情况下 PostgreSQL 会自动清理,但如果复制槽没释放或者有长时间运行的事务,WAL 会堆积。检查方法:
sudo gitlab-psql -c "SELECT pg_size_pretty(sum(size)) FROM pg_ls_waldir();"如果发现 WAL 异常大,检查是否有卡住的事务:
sudo gitlab-psql -c "SELECT pid, state, query_start FROM pg_stat_activity WHERE state != 'idle';"7. 关于非 Docker 安装的一些个人体会
非 Docker 方式装 GitLab,最大的感受就是"一切都在明面上"。Docker 装的时候,容器里发生了什么你只能通过docker logs看个大概,但 Omnibus 装完之后,每个组件的配置文件、日志、数据目录都清清楚楚地摆在/etc/gitlab/、/var/log/gitlab/、/var/opt/gitlab/这三个目录下。出问题时,你可以直接进 PostgreSQL 查数据,可以直接看 Redis 的持久化文件,可以直接调 Nginx 的配置,这种掌控感是容器化给不了的。
当然代价也有,就是升级和迁移比 Docker 麻烦。Docker 升级就是换个镜像 tag,Omnibus 升级要备份、要按路径一步步来、要 reconfigure。但考虑到 GitLab 这种核心基础设施一旦出问题影响面很大,我宁愿升级时多花点时间,也不愿意在容器里出问题时抓瞎。
最后分享一个我自己的习惯:装完 GitLab 之后,我会把/etc/gitlab/gitlab.rb里所有改过的配置项单独记一个文档,包括为什么改、改成什么值、改完之后有什么效果。因为 GitLab 的配置项有上千个,过几个月你根本记不住当时为什么把某个参数设成那个值。这个文档在迁移或者重装的时候能救命。