1. 为什么选择Docker部署GitLab
1.1 传统部署方式踩过的坑
早些年我在物理机和虚拟机上部署GitLab,每次都是一场硬仗。GitLab的组件非常多,包含Nginx、PostgreSQL、Redis、Gitaly、Sidekiq、Puma等,手动安装需要逐个配置依赖关系,光是Ruby环境、数据库版本匹配就能耗掉大半天。更麻烦的是升级,跨版本升级GitLab有严格的路径要求,不能跳版本,一旦中间某个版本出问题,回滚极其痛苦。
后来我开始用Docker部署,最大的感受就是环境隔离和版本管理变得非常清晰。镜像里已经打包好了所有依赖,我只需要关注数据持久化和性能参数。升级的时候直接拉新镜像、停旧容器、起新容器,数据卷挂载不变,整个过程几分钟搞定。这也是我写这个系列第四篇的初衷——把Docker部署GitLab这件事讲透,尤其是性能调优部分,因为默认配置下的GitLab在Docker里跑,性能往往不尽如人意。
1.2 Docker部署GitLab的适用场景
不是所有场景都适合用Docker跑GitLab。我总结了几种典型情况:
- 中小团队自建代码托管平台:十到五十人的研发团队,需要私有化部署,又不想在运维上投入太多精力,Docker方案非常合适。
- 需要快速搭建测试环境:比如做CI/CD流水线验证、演示环境搭建,用Docker可以在半小时内从零到可用。
- 资源有限的单机部署:一台配置还不错的服务器,同时跑GitLab和其他服务,Docker的资源隔离能力能帮你控制GitLab的资源占用。
- 需要频繁迁移或备份:数据卷挂载的方式让迁移变得简单,打包数据目录拷走就行。
但如果你需要GitLab的高可用集群、需要跑GitLab Geo多地域部署,那Docker单机方案就不太够了,得考虑Kubernetes或者官方推荐的云原生方案。
1.3 核心关键词与版本选择
目前GitLab的Docker镜像主要有两个来源:官方社区版(gitlab/gitlab-ce)和企业版(gitlab/gitlab-ee)。社区版免费,功能对于大多数团队已经足够;企业版多了一些高级功能如审计日志、合规性管理等,需要授权。
版本选择上,我建议不要盲目追最新版。GitLab的版本迭代很快,每个月都有新版本,但新版本偶尔会引入一些回归问题。我的经验是选择当前稳定的大版本,比如16.x或17.x的某个小版本,观察一两周没有严重bug再升级。另外要注意,GitLab的Docker镜像标签有latest、具体版本号(如16.11.3-ce.0)等,生产环境一定要用具体版本号,避免自动拉到不兼容的新版本。
提示:拉取镜像前先确认服务器架构,x86_64和ARM的镜像不通用。可以用
uname -m查看。
2. 部署前的环境准备与资源规划
2.1 硬件资源的最低要求与推荐配置
GitLab是个资源大户,官方给出的最低配置是2核4G,但那是“能跑起来”的底线。实际使用中,4G内存的机器跑GitLab,打开网页都卡。我实测下来的推荐配置如下:
| 团队规模 | CPU | 内存 | 磁盘 | 说明 |
|---|---|---|---|---|
| 1-5人 | 2核 | 4GB | 50GB SSD | 勉强可用,需关闭部分功能 |
| 5-20人 | 4核 | 8GB | 100GB SSD | 基本流畅,推荐起步配置 |
| 20-50人 | 8核 | 16GB | 200GB SSD | 舒适运行,可开CI/CD |
| 50人以上 | 16核 | 32GB+ | 500GB SSD+ | 需要进一步调优和分离组件 |
磁盘一定要用SSD,GitLab的Git仓库读写、数据库操作对IOPS很敏感,机械硬盘会让整个系统响应变慢。另外,磁盘空间要留足余量,GitLab的日志、备份、CI构建产物都会占空间,建议使用率不超过70%。
2.2 Docker与Docker Compose的安装
假设你用的是主流的Linux发行版,安装Docker的步骤大同小异。以常见的包管理方式为例:
# 卸载旧版本(如果有) sudo apt remove docker docker-engine docker.io containerd runc # 安装依赖 sudo apt update sudo apt install -y ca-certificates curl gnupg lsb-release # 添加Docker官方GPG密钥 sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg # 添加软件源 echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 安装Docker Engine sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin # 验证安装 sudo docker version安装完成后,把当前用户加入docker组,避免每次都要sudo:
sudo usermod -aG docker $USER newgrp dockerDocker Compose现在以插件形式提供,命令是docker compose(注意中间是空格),不再是老的docker-compose。这个变化很多人容易搞混,写脚本的时候要注意。
2.3 系统内核参数调优
在跑GitLab之前,有几个系统层面的参数需要调整,否则容器里可能会出现连接数不够、文件句柄不足等问题。
# 增加文件句柄数 sudo tee -a /etc/security/limits.conf << EOF * soft nofile 65536 * hard nofile 65536 EOF # 调整内核参数 sudo tee -a /etc/sysctl.conf << EOF net.core.somaxconn = 1024 net.ipv4.tcp_max_syn_backlog = 2048 vm.overcommit_memory = 1 vm.swappiness = 10 fs.file-max = 1000000 EOF sudo sysctl -pvm.swappiness=10是让系统尽量少用交换分区,因为GitLab对内存延迟敏感,一旦用到swap,性能会断崖式下降。vm.overcommit_memory=1是Redis要求的,避免后台保存时fork失败。
注意:修改limits.conf后需要重新登录才生效,或者重启相关服务。如果是通过systemd管理的服务,还需要在service文件里单独配置LimitNOFILE。
3. Docker Compose编排GitLab完整配置
3.1 目录结构与数据持久化设计
我习惯把GitLab的所有数据放在一个统一目录下,方便备份和迁移。目录结构如下:
/opt/gitlab/ ├── config/ # GitLab配置文件 ├── logs/ # 日志 ├── data/ # 数据(Git仓库、数据库、上传文件等) └── docker-compose.yml创建目录并设置权限:
sudo mkdir -p /opt/gitlab/{config,logs,data} sudo chmod -R 755 /opt/gitlab为什么要这样分?因为GitLab容器内的/etc/gitlab、/var/log/gitlab、/var/opt/gitlab分别对应配置、日志和数据。把它们挂载到宿主机,容器删了数据还在,升级时只需要替换镜像,数据卷不动。
3.2 docker-compose.yml核心配置详解
下面是我经过多次调整后稳定运行的compose配置:
version: '3.8' services: gitlab: image: gitlab/gitlab-ce:16.11.3-ce.0 container_name: gitlab restart: always hostname: 'gitlab.example.com' environment: GITLAB_OMNIBUS_CONFIG: | # 外部访问地址 external_url 'http://gitlab.example.com' # SSH克隆地址 gitlab_rails['gitlab_shell_ssh_port'] = 2222 # 时区 gitlab_rails['time_zone'] = 'Asia/Shanghai' # 关闭不必要的监控 prometheus_monitoring['enable'] = false # Puma工作进程数 puma['worker_processes'] = 4 # Puma最大线程数 puma['max_threads'] = 8 # Sidekiq并发数 sidekiq['concurrency'] = 10 # PostgreSQL共享缓冲区 postgresql['shared_buffers'] = "2GB" # PostgreSQL最大工作进程 postgresql['max_worker_processes'] = 8 # Redis最大内存 redis['maxmemory'] = "512mb" redis['maxmemory_policy'] = "allkeys-lru" # Gitaly并发限制 gitaly['concurrency'] = [ { 'rpc' => "/gitaly.SmartHTTPService/PostReceivePack", 'max_per_repo' => 20 }, { 'rpc' => "/gitaly.SmartHTTPService/PostUploadPack", 'max_per_repo' => 20 } ] ports: - '80:80' - '443:443' - '2222:22' volumes: - '/opt/gitlab/config:/etc/gitlab' - '/opt/gitlab/logs:/var/log/gitlab' - '/opt/gitlab/data:/var/opt/gitlab' shm_size: '256m' deploy: resources: limits: memory: 12G这份配置里有几个关键点需要展开说。
GITLAB_OMNIBUS_CONFIG是GitLab Omnibus包的核心配置入口,所有gitlab.rb里的配置都可以写在这里。用多行字符串的方式写,容器启动时会自动生成gitlab.rb。这样做的好处是配置和compose文件在一起,版本管理方便。
puma['worker_processes']控制Puma应用服务器的工作进程数。官方建议是CPU核数加1,但实际要看内存。每个worker大概占300-500MB内存,4个worker就是1.5-2GB。如果内存紧张,可以减到2-3个。
postgresql['shared_buffers']是PostgreSQL最重要的性能参数,一般设置为可用内存的25%。比如给GitLab分配8G内存,数据库大概能用2G左右,shared_buffers设2GB比较合适。设太大反而会因为内存交换导致性能下降。
shm_size这个参数很多人会忽略。GitLab的PostgreSQL和Redis在容器内运行时会用到共享内存,默认Docker的/dev/shm只有64MB,不够用会导致数据库启动失败或者运行不稳定。设成256MB以上比较保险。
3.3 端口映射与访问配置
端口映射这里有个细节:GitLab容器内的SSH服务默认监听22端口,但宿主机22端口通常已经被系统SSH占用了。所以我把容器22映射到宿主机2222,然后在配置里通过gitlab_shell_ssh_port告诉GitLab对外暴露的SSH端口是2222。这样用户克隆代码时用的地址就是ssh://git@gitlab.example.com:2222/group/project.git。
HTTP和HTTPS端口映射到宿主机的80和443。如果宿主机上已经有Nginx占用了80,可以改成其他端口,比如8080:80,然后通过外部Nginx做反向代理。
external_url要填用户实际访问的地址。如果通过域名访问就填域名,如果通过IP访问就填IP。这个配置影响GitLab生成的克隆地址、邮件里的链接等,填错了会导致各种链接不可用。
3.4 启动与初始化过程
配置写好后,启动命令很简单:
cd /opt/gitlab docker compose up -d第一次启动会比较慢,因为GitLab要初始化数据库、编译资源、启动所有组件。我实测在4核8G的机器上,从启动到可以访问大概需要5-8分钟。这期间可以用以下命令观察日志:
docker compose logs -f gitlab看到类似gitlab Reconfigured!的日志,说明初始化完成。然后浏览器访问配置的地址,会要求设置root密码。设置完成后用root账号登录。
注意:初始root密码也可以从容器里读取,命令是
docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password。这个文件在24小时后会自动删除,所以要么及时修改密码,要么记下来。
4. 性能调优实战:从能跑到跑得好
4.1 内存分配与Swap策略
GitLab在Docker里跑,内存是最容易出问题的地方。默认配置下,GitLab会尽可能多地占用内存,如果宿主机内存不够,容器可能被OOM Killer干掉。
我的做法是给容器设置内存限制,同时调整各个组件的内存参数。在compose里通过deploy.resources.limits.memory限制容器最大内存,然后在GITLAB_OMNIBUS_CONFIG里细化各组件的内存使用。
具体分配逻辑是这样的:假设给GitLab容器12G内存,那么PostgreSQL的shared_buffers设2-3G,Puma的worker数控制在4-6个(每个约400MB),Sidekiq并发10-15(每个约200MB),Redis限制在512MB-1GB,剩下的留给Gitaly和系统开销。
关于Swap,我建议在宿主机上保留一定的Swap空间,但设置vm.swappiness=10,让系统只在内存极度紧张时才用Swap。完全禁用Swap在内存不足时会导致进程直接被杀死,反而更糟。
4.2 Puma与Sidekiq并发参数调整
Puma是GitLab的Web应用服务器,负责处理HTTP请求。它的性能直接影响页面响应速度。worker_processes和max_threads是两个核心参数。
worker_processes是进程数,每个进程独立处理请求,进程间不共享内存。max_threads是每个进程内的线程数,线程共享进程内存,但受Ruby GVL限制,同一时刻只有一个线程在执行Ruby代码。所以Puma的并发能力约等于worker_processes * max_threads,但实际受CPU核数限制。
我的经验是:worker_processes设为CPU核数,max_threads设为4-8。比如4核机器,设4个worker、每个8线程,理论并发32,实际能跑到20左右就不错了。
Sidekiq负责后台任务,比如推送代码后的CI触发、邮件发送、仓库索引等。concurrency参数控制同时执行的任务数。设太小任务积压,设太大内存吃不消。一般设10-20比较合适,内存充足可以到25。
调整这些参数后需要重新配置GitLab:
docker exec -it gitlab gitlab-ctl reconfigurereconfigure过程会重新生成配置并重启相关服务,大概需要1-2分钟。期间GitLab不可用,建议在低峰期操作。
4.3 PostgreSQL数据库调优
GitLab用PostgreSQL存储元数据,包括用户、项目、Issue、合并请求等。数据库性能直接影响页面加载速度。
除了前面提到的shared_buffers,还有几个参数值得关注:
work_mem:每个查询操作可用的内存,默认4MB。复杂查询(如大项目的提交历史)可能需要更多。设成16-32MB可以明显改善查询性能,但设太大在并发高时会耗尽内存。maintenance_work_mem:维护操作(如VACUUM、CREATE INDEX)用的内存,设256MB-512MB。effective_cache_size:告诉PostgreSQL操作系统有多少内存可用于缓存,一般设为可用内存的50-75%。random_page_cost:SSD上设为1.1,机械硬盘保持默认4.0。这个参数影响查询计划,SSD上设低一些会让数据库更倾向于用索引。
在GITLAB_OMNIBUS_CONFIG里加上:
postgresql['shared_buffers'] = "2GB" postgresql['work_mem'] = "16MB" postgresql['maintenance_work_mem'] = "256MB" postgresql['effective_cache_size'] = "4GB" postgresql['random_page_cost'] = 1.14.4 Gitaly与Git操作性能优化
Gitaly是GitLab的Git服务层,所有Git操作(clone、push、fetch)都经过它。大仓库操作慢,往往是Gitaly的问题。
Gitaly的并发限制可以防止单个仓库的大操作拖垮整个系统。前面配置里的concurrency就是干这个的。max_per_repo限制单个仓库同时进行的操作数,设20意味着一个仓库最多同时处理20个clone/push请求。
另外,Gitaly的缓存也值得调整。gitaly['ruby_max_rss']限制Ruby进程内存,gitaly['ruby_restart_delay_seconds']控制重启延迟。这些参数在内存紧张时有用。
对于大仓库,还可以开启pack_objects_cache,缓存Git打包对象,加速clone操作:
gitaly['pack_objects_cache_enabled'] = true gitaly['pack_objects_cache_max_age'] = '24h'4.5 关闭非必要组件节省资源
GitLab默认开启了很多监控组件,比如Prometheus、Grafana、Alertmanager、Node Exporter等。如果你有自己的监控体系,这些完全可以关掉,能省下不少内存和CPU。
prometheus_monitoring['enable'] = false grafana['enable'] = false alertmanager['enable'] = false node_exporter['enable'] = false redis_exporter['enable'] = false postgres_exporter['enable'] = false gitlab_exporter['enable'] = false关掉这些后,内存能省出1-2GB。对于小内存机器来说,这是最立竿见影的优化。
另外,如果不需要CI/CD的代码质量分析、安全扫描等功能,也可以在配置里关闭对应的Runner和组件。
5. 常见问题排查与避坑指南
5.1 容器启动失败与日志分析
GitLab容器启动失败的原因很多,最常见的是内存不足和配置错误。排查第一步永远是看日志:
docker compose logs --tail=200 gitlab如果日志里出现OOMKilled,说明内存不够,需要调大内存限制或减少组件。如果出现Configuration Error,说明GITLAB_OMNIBUS_CONFIG里有语法错误,仔细检查Ruby语法。
还有一种情况是容器一直处于starting状态,这通常是GitLab在初始化,耐心等待即可。如果超过15分钟还没好,可能是某个组件卡住了,可以进容器手动查看:
docker exec -it gitlab gitlab-ctl status这个命令会显示所有组件的运行状态,哪个是down或者starting一目了然。
5.2 502错误与Puma超时处理
访问GitLab时遇到502 Bad Gateway,通常是Puma没起来或者响应超时。先检查Puma状态:
docker exec -it gitlab gitlab-ctl status puma如果Puma是down,看日志:
docker exec -it gitlab tail -f /var/log/gitlab/puma/puma_stderr.log常见原因是内存不足导致Puma worker启动失败,或者worker_processes设得太大。可以尝试减少worker数,或者增加容器内存。
如果是间歇性502,可能是Puma处理请求超时。GitLab默认的请求超时是60秒,大仓库的某些操作可能超过这个时间。可以在配置里调整:
gitlab_rails['webhook_timeout'] = 60 puma['worker_timeout'] = 1205.3 磁盘空间告警与清理策略
GitLab用久了磁盘会越来越满,主要有几个来源:Git仓库、CI构建产物、日志、备份文件、上传的附件。
定期清理的策略:
- 日志:配置logrotate,或者用
gitlab-ctl的日志清理功能。日志目录/var/log/gitlab下每个组件都有日志,可以设置保留天数。 - CI构建产物:在GitLab管理后台设置构建产物的过期时间,默认是30天,可以缩短到7天。
- 备份文件:备份文件默认存在
/var/opt/gitlab/backups,如果备份到远程存储,本地可以只保留最近几份。 - 容器日志:Docker的容器日志也会占空间,可以在compose里配置日志轮转:
logging: driver: "json-file" options: max-size: "100m" max-file: "3"5.4 升级过程中的数据安全
GitLab升级有严格的版本路径要求,不能跨大版本直接升。比如从15.x升到17.x,需要先升到16.x的最新小版本,再升到17.x。具体路径可以在GitLab官方文档的升级路径工具里查。
升级前必须备份:
docker exec -it gitlab gitlab-backup create备份文件会生成在/var/opt/gitlab/backups,对应宿主机的/opt/gitlab/data/backups。把备份文件拷到安全的地方,然后再操作升级。
升级步骤:
# 修改compose里的镜像版本 # 然后 docker compose pull docker compose up -d容器启动后会自动执行数据库迁移,这个过程可能比较久,大版本升级时尤其如此。迁移期间不要中断容器,否则可能导致数据库不一致。
提示:升级前先在测试环境验证一遍,确认没问题再在生产环境操作。我吃过这个亏,有一次跨版本升级后某些API不兼容,导致CI流水线全部失败,回滚又花了不少时间。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 容器启动后立即退出 | 配置语法错误 | 查看容器日志 | 检查GITLAB_OMNIBUS_CONFIG语法 |
| 页面502 | Puma未启动或超时 | gitlab-ctl status puma | 增加内存或减少worker数 |
| 数据库连接失败 | shared_buffers过大 | 查看PostgreSQL日志 | 调小shared_buffers |
| Git操作慢 | Gitaly并发限制 | 查看Gitaly日志 | 调整concurrency参数 |
| 磁盘满 | 日志/备份/构建产物堆积 | du -sh /opt/gitlab/* | 清理旧文件,配置轮转 |
| 内存持续增长 | 内存泄漏或缓存过大 | docker stats | 限制容器内存,调整组件参数 |
| SSH克隆失败 | 端口映射错误 | 检查compose端口配置 | 确认gitlab_shell_ssh_port与实际映射一致 |
6. 日常运维与监控建议
6.1 备份策略的制定与验证
备份不是做了就行,关键时候能恢复才是真的。我建议采用“本地+远程”双备份策略:本地保留最近7天的备份,远程存储保留最近30天。备份频率根据团队活跃度来定,一般每天一次,活跃团队可以每天两次。
备份命令:
docker exec -it gitlab gitlab-backup create这个命令会备份数据库、Git仓库、上传文件等,但不包括配置文件。配置文件在/opt/gitlab/config,需要单独备份。我习惯把配置目录也纳入版本管理,每次修改都提交,这样配置变更也有记录。
备份完成后一定要验证。验证方法是在测试环境恢复一次,确认数据完整。恢复命令:
# 先停掉相关服务 docker exec -it gitlab gitlab-ctl stop puma sidekiq # 恢复 docker exec -it gitlab gitlab-backup restore BACKUP=备份文件名 # 重启 docker exec -it gitlab gitlab-ctl restart6.2 资源监控与告警设置
Docker环境下监控GitLab,我主要关注几个指标:容器CPU使用率、内存使用率、磁盘IO、磁盘空间、GitLab各组件的响应时间。
简单的监控可以用docker stats:
docker stats gitlab --no-stream更完善的监控可以部署cAdvisor加Prometheus加Grafana,但前面我们关掉了GitLab自带的Prometheus,所以需要自己搭一套。如果不想太复杂,用简单的Shell脚本定时采集指标,写入日志,再用日志分析工具做告警也够用。
告警阈值建议:内存使用率超过85%告警,磁盘使用率超过80%告警,容器重启次数超过3次告警。
6.3 定期维护清单
GitLab需要定期做一些维护操作,我整理了一个清单,按周期执行:
- 每日:检查备份是否成功,查看容器状态和资源使用。
- 每周:清理过期构建产物,检查日志有无异常,确认磁盘空间充足。
- 每月:执行一次完整的备份恢复演练,检查GitLab版本和安全更新。
- 每季度:评估资源使用趋势,必要时扩容;审查用户权限和项目访问控制。
这些操作看起来琐碎,但坚持下来能避免很多突发故障。我有一次因为没及时清理构建产物,磁盘满了导致GitLab无法写入,整个团队停工了半天,教训深刻。
6.4 性能调优的持续迭代
性能调优不是一次性的工作,随着团队规模增长、项目数量增加,原来的参数可能就不够用了。我建议每季度做一次性能评估,关注几个关键指标:页面平均响应时间、Git操作平均耗时、CI流水线排队时间、数据库慢查询数量。
如果发现页面响应变慢,先看Puma的worker是否够用;如果Git操作慢,看Gitaly的并发和缓存;如果CI排队严重,看Sidekiq的并发和Runner数量。根据瓶颈点有针对性地调整,而不是盲目加资源。
另外,GitLab每个大版本都会带来性能改进,保持版本更新也是提升性能的一种方式。但升级前一定要看Release Notes里的性能相关说明和已知问题。
我个人在实际操作中的体会是,Docker部署GitLab最大的优势是灵活性和可复现性。同样的compose文件,换一台服务器就能跑起来,配置参数都在一个文件里,迁移和重建都很方便。性能调优的核心思路是“了解每个组件的作用,根据实际负载分配资源”,没有一套参数是万能的,需要根据自己的硬件和团队使用习惯不断调整。最后再分享一个小技巧:如果你不确定某个参数该设多少,可以先设一个保守值,然后逐步增加,观察性能变化,找到最适合你环境的平衡点。