Skip to content
SlippinDylan
Go back

GitLab EE 17.11 部署方案:N100 小主机 + Traefik 反代 + Cloudflare

为什么是 N100

N100 是 Intel 2023 年发布的低功耗处理器,4 核 4 线程,TDP 仅 6W,但单核性能比树莓派强一大截。配上 16GB 内存和 NVMe SSD,价格在国内大约 600-800 元出头,是目前 Homelab 性价比最高的方案之一。

GitLab 的官方最低要求是 4 核 + 4GB,推荐生产配置是 8 核 + 16GB。N100 的 4 核 16GB 正好卡在”能跑、但要调”的位置——开箱默认配置跑不好,调完之后两人用完全没问题。

这篇文章的所有配置都针对这个硬件规格。

机器配置:


网络架构

在家部署面临两个现实问题:

  1. 国内宽带的 80/443 端口被运营商封锁,无法直接在标准 HTTPS 端口上对外提供服务
  2. 家庭公网 IP 通常是动态的,需要一个稳定的域名指向

解法是这条链路:

flowchart LR
  CF["Cloudflare\ngitlab.example.com\nDNS Only 灰云"] --> OW["OpenWrt\n端口转发\n自定义端口 → 内网"]
  OW --> TR["Traefik\n反向代理\nTLS 终结"]
  TR -->|"127.0.0.1:8181"| GW["GitLab Workhorse"]
  GW --> GL["GitLab Rails\nPuma / Sidekiq\nPostgreSQL / Redis / Gitaly"]

每一跳的职责:


GitLab EE 还是 CE

GitLab 有两个版本:社区版(CE)和企业版(EE)。

EE 听起来要收费,但实际上 EE 免费功能的范围已经够 Homelab 使用了。EE 的收费功能需要购买 License 才能解锁,不买 License 的话 EE 和 CE 的功能几乎一样。

选 EE 的原因很实际:如果以后想试某个 EE 专属功能,不需要重新部署,直接加 License 就行。CE 想换 EE 需要走升级流程,麻烦。


为什么用 Omnibus 而不是 Docker

GitLab 提供两种主流安装方式:Omnibus 安装包和 Docker 镜像。

Omnibus 是 GitLab 官方打包的 .deb 安装包,把所有组件(Rails、Puma、Sidekiq、PostgreSQL、Redis、Gitaly、NGINX……)打成一个包统一安装,所有配置集中在 /etc/gitlab/gitlab.rb 一个文件里。

Docker 方式则把这些组件分别放在容器里,或者用一个大的 GitLab 官方镜像跑全部组件,配置通过环境变量和卷挂载传入。

两种方式都能跑,选 Omnibus 的理由是:


安装前的准备

域名规划

一个主域名就够用了,其他按需开启:

功能推荐域名是否必需
GitLab 主站gitlab.example.com✅ 必需
Container Registryregistry.example.com可选
GitLab Pagespages.example.com可选

Container Registry 虽然可以挂在主域名子路径下,但 Docker 客户端对非标端口的支持历来一言难尽,强烈建议单独子域名 + 标准 443 端口,省掉很多奇怪的问题。

部署前检查

# 设置时区(GitLab 的日志和备份命名都依赖系统时间)
sudo timedatectl set-timezone Asia/Shanghai

# 检查磁盘(GitLab 本身就需要几十 GB,再加上仓库和制品,建议预留 100GB 以上)
df -h

# 检查 SSH 端口是否占用了 22(GitLab 的 Git SSH 也默认用 22,两个抢同一个端口会冲突)
sudo netstat -tlnp | grep sshd

SSH 端口冲突是第一个容易踩的坑。GitLab 的 Git over SSH 功能和系统 SSH 服务都想监听 22 端口,装之前要么把系统 SSH 改到其他端口,要么提前配好 GitLab 用别的端口。


安装

# 安装基础依赖
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl openssh-server ca-certificates tzdata perl

# 添加 GitLab 官方仓库
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ee/script.deb.sh | sudo bash

# 查看可用的 17.11.x 版本
apt-cache madison gitlab-ee | grep 17.11

# 安装(不在这里设置 EXTERNAL_URL,后续在 gitlab.rb 里配)
sudo apt install gitlab-ee=17.11.*

为什么要锁版本:

sudo apt-mark hold gitlab-ee

这一步不是可选的。GitLab 的大版本升级有严格的顺序要求,比如从 16.x 升到 17.x,必须先升到 16.11,再升到 17.0,然后才能到 17.11,不能跳版本。如果不锁版本,某次 apt upgrade 悄悄给你升了几个大版本,数据库 migration 会失败,回滚非常麻烦。锁住版本,升级时按照 GitLab 官方升级路径文档一步一步来。


配置

配置文件是 /etc/gitlab/gitlab.rb,修改完之后运行 sudo gitlab-ctl reconfigure 应用。

gitlab.rb 是 Ruby DSL,不是 YAML,语法错误不会在保存时报,只在 reconfigure 时才炸。改完先检查语法:

sudo gitlab-ctl check-config

最常见的错误是中文引号混入、漏掉逗号、字符串没加引号。

完整配置

# ============================================
# 基础配置
# ============================================

external_url 'https://gitlab.example.com'

# ============================================
# 禁用内置 NGINX,改用 Traefik 做反代
# ============================================

# GitLab Omnibus 内置了一个 NGINX 作为 Web 入口
# 我们已经有 Traefik 了,两个 Web 服务器叠在一起没有意义,关掉
nginx['enable'] = false

# Workhorse 只监听本地回环地址
# 不对外暴露,只让 Traefik 能访问到
gitlab_workhorse['listen_network'] = "tcp"
gitlab_workhorse['listen_addr'] = "127.0.0.1:8181"

# 告诉 GitLab 信任来自这些地址的代理请求头
# 不配这个,所有请求经 Traefik 转发后,GitLab 看到的客户端 IP 全是 127.0.0.1
# 审计日志、登录地理位置、暴力破解检测全部失效
gitlab_rails['trusted_proxies'] = ['127.0.0.1', '::1', '10.0.0.0/8']

# ============================================
# Puma(Web 应用服务器)
# ============================================

# Puma 是跑 GitLab Rails 应用的进程
# worker_processes 是工作进程数,每个进程独立处理请求
# 每个 worker 常驻内存约 1-1.5GB,所以不能开太多
# 4 核机器开 3 个 worker,留一个核给其他组件
puma['worker_processes'] = 3
puma['min_threads'] = 4
puma['max_threads'] = 4
puma['worker_timeout'] = 60

# 内存超过这个阈值就重启 worker(注意:这是触发重启的阈值,不是硬限制)
# 实际高负载下 worker 可能跑到 1.5GB 才触发回收,属于正常现象
puma['per_worker_max_memory_mb'] = 1200

# ============================================
# Sidekiq(后台任务)
# ============================================

# Sidekiq 处理所有异步任务:发邮件、pipeline 调度、镜像同步等
# concurrency 是并发线程数,20 对两人团队足够
sidekiq['max_concurrency'] = 20

# ============================================
# PostgreSQL
# ============================================

# shared_buffers:PostgreSQL 用于缓存数据的内存
# 通常设为系统内存的 25%,16GB 机器设 4GB
postgresql['shared_buffers'] = "4GB"

# work_mem:每个查询操作(排序、哈希)可用的内存
# 注意这是「每个操作」而非「每个连接」,高并发下实际占用可能很高
postgresql['work_mem'] = "32MB"

# maintenance_work_mem:VACUUM、CREATE INDEX 等维护操作的内存上限
postgresql['maintenance_work_mem'] = "512MB"

# effective_cache_size:告诉查询规划器系统有多少内存可用于缓存
# 这个参数不实际分配内存,只影响查询计划的选择
postgresql['effective_cache_size'] = "10GB"

postgresql['max_connections'] = 300

# ============================================
# Gitaly(Git 仓库服务)
# ============================================

# Gitaly 处理所有 Git 操作,仓库多了内存消耗会很明显
# 限制单个 RPC 的最大并发,防止某个操作把 Gitaly 资源耗尽
gitaly['concurrency'] = [
  { 'rpc' => "/gitaly.SmartHTTPService/PostReceivePack", 'max_per_repo' => 3 },
  { 'rpc' => "/gitaly.SSHService/SSHUploadPack", 'max_per_repo' => 3 }
]

# jemalloc 内存分配器调优,减少 Ruby 进程的内存碎片
gitlab_rails['env'] = {
  'MALLOC_CONF' => 'dirty_decay_ms:1000,muzzy_decay_ms:1000'
}

# ============================================
# 关闭不需要的组件,节省内存
# ============================================

# 监控栈(Prometheus + Grafana):两人团队暂时不需要,省出几百 MB
prometheus_monitoring['enable'] = false
prometheus['enable'] = false
alertmanager['enable'] = false
node_exporter['enable'] = false
redis_exporter['enable'] = false
postgres_exporter['enable'] = false
gitlab_exporter['enable'] = false
grafana['enable'] = false

# 不用的功能组件
mattermost['enable'] = false
pages_nginx['enable'] = false
gitlab_pages['enable'] = false
registry['enable'] = false
registry_nginx['enable'] = false

# ============================================
# 存储和备份
# ============================================

git_data_dirs({
  "default" => {
    "path" => "/var/opt/gitlab/git-data"
  }
})

gitlab_rails['backup_path'] = "/var/opt/gitlab/backups"
gitlab_rails['backup_keep_time'] = 604800  # 保留 7 天,单位秒

实际内存占用参考

组件预计占用
Puma(3 workers)≈ 3.6 GB(实测高负载下可到 4.5GB)
PostgreSQL≈ 5 GB(shared_buffers + 其他)
Sidekiq + Redis + Gitaly≈ 2-3 GB
系统 + 其他≈ 1 GB
合计≈ 11-13 GB,16GB 机器有 3-5GB buffer

应用配置并启动

# 应用配置(首次大约需要 5-10 分钟,不要中途 Ctrl+C)
sudo gitlab-ctl reconfigure

# 验证所有服务状态
sudo gitlab-ctl status

# 确认 Workhorse 在监听 8181
sudo netstat -tlnp | grep 8181

# 获取初始 root 密码(这个文件 24 小时后会自动删除)
sudo cat /etc/gitlab/initial_root_password

浏览器访问 https://gitlab.example.com,用 root 和上面拿到的密码登录。

首次加载会很慢,不是配置有问题。GitLab 在跑数据库初始化和资源编译任务,等 2-3 分钟再刷新。

登录后立刻做这三件事:

  1. 修改 root 密码(初始密码文件 24 小时后消失)
  2. 创建普通用户账号(日常操作不要用 root,出问题审计日志会乱)
  3. 配置 SSH 公钥,跑一次 git push 验证整条链路

踩过的坑

Cloudflare 橙云 + 非标端口 = 流量直接消失

Cloudflare 的代理模式(橙云)只支持有限的端口列表,80、443、8080 等,其他端口的流量会被 Cloudflare 丢弃,不会有任何有意义的报错,请求就是发出去没有回应。

用非标端口必须设为 DNS Only(灰云),让流量直接打到家庭公网 IP,Cloudflare 只做域名解析,不碰流量。

判断方法:访问 https://gitlab.example.com 超时,但直接访问 公网IP:端口 正常,八九不离十是橙云的问题。

trusted_proxies 不配,IP 全是 127.0.0.1

所有请求经 Traefik 转发后,带着 X-Forwarded-For 头进来,但 GitLab 默认不信任任何代理,这个头会被忽略,记录下来的客户端 IP 全是 127.0.0.1

后果是:审计日志没有真实 IP,登录异常检测失效,暴力破解防护形同虚设。必须在 gitlab.rb 里把 Traefik 所在的地址段加到 trusted_proxies

reconfigure 中途被打断

reconfigure 是一个完整的 Chef 收敛过程,中途 Ctrl+C 会让 GitLab 的内部状态停在一半,某些组件初始化了,某些没有,各种奇怪的服务起不来。

遇到这种情况:不要试图局部修复,直接重跑完整的 sudo gitlab-ctl reconfigure。这个命令是幂等的,重跑不会有副作用,会从断点恢复。

Puma worker 内存会超出设定值

per_worker_max_memory_mb = 1200 是触发 worker 重启的阈值,不是硬内存限制。Puma 不会在内存达到这个值时立刻杀掉 worker,而是等当前请求处理完再重启。

高负载下,每个 worker 轻松跑到 1.5GB 才触发回收。16GB 机器开 3 个 worker 是合理上限,不要贪心开 4 个,留足 buffer,否则系统开始 swap,性能会断崖式下跌。

gitlab.rb 语法错误只在 reconfigure 时才暴露

gitlab.rb 是 Ruby DSL,保存文件时不会做语法检查,只有跑 reconfigure 时才会解析。写错了,reconfigure 中途报错退出,GitLab 可能处于半配置状态。

养成习惯,改完配置先跑:

sudo gitlab-ctl check-config

常见错误:全角引号混进来、字符串结尾漏引号、数组最后多了逗号。


常用管理命令

sudo gitlab-ctl status              # 查看所有组件运行状态
sudo gitlab-ctl restart             # 重启所有服务
sudo gitlab-ctl reconfigure         # 应用 gitlab.rb 的配置变更
sudo gitlab-ctl tail puma           # 实时查看 Puma 日志
sudo gitlab-ctl tail postgresql     # 实时查看 PostgreSQL 日志
sudo gitlab-rake gitlab:check       # 环境自检,排查问题用
sudo gitlab-rake gitlab:env:info    # 查看 GitLab 版本和环境信息

备份

GitLab 的备份不包括配置文件,需要分开备份。

# 备份数据(仓库、数据库、上传的文件等)
sudo gitlab-backup create

# 配置文件单独备份(包含密钥,妥善保管)
sudo cp /etc/gitlab/gitlab.rb /backup/gitlab.rb
sudo cp /etc/gitlab/gitlab-secrets.json /backup/gitlab-secrets.json

配置定时备份:

sudo crontab -e
# 每天凌晨 2 点自动备份
# 0 2 * * * /opt/gitlab/bin/gitlab-backup create CRON=1

CRON=1 是告诉 GitLab 这是定时任务触发的,只有在出错时才输出日志,避免每天凌晨塞满 crontab 日志。

恢复时有一个关键细节:备份文件的 GitLab 版本必须和当前安装版本完全一致,否则恢复会失败。这也是版本锁定的另一个原因——随意升级之后,旧备份可能无法用于恢复。


附录:参考链接


Share this post on:

Previous Post
微信支付选型避坑:从分账到商家转账,一个产品经理的踩坑记录
Next Post
生活服务平台的订单,不是换了商品的电商