自建 GitLab 仓库:Docker 部署、权限规划与 CI/CD 实践
2026/9/18 8:04:30 网站建设 项目流程

1. 先想清楚:自建 GitLab 仓库到底解决什么问题

很多人第一次听到"在 GitLab 上建一个自己的仓库",脑子里浮现的是一串命令行,其实真正的分水岭在动手之前:你到底是在官方托管站点上点一个 New Project,还是在自己的一台机器上把整套 GitLab 服务跑起来。这两件事的技术含量差了整整一个数量级,踩坑位置也完全不一样。我做过后一种,也帮别人收拾过前者留下的烂摊子,所以这篇东西会偏向自建场景,顺带把托管场景里那些容易忽略的按钮选项讲透。

仓库这个词在开发日常里其实被混用了。Git 仓库指的是一个带版本历史的代码目录;GitLab 仓库是托管平台上的一个项目实体,里面装着 Git 仓库、议题、合并请求、流水线、制品等一堆附属物;而 Docker 仓库、Maven 仓库指的是存储构建产物的地方。这三者在真实项目里经常被一起提到,配置的时候也容易串味。这篇内容的主线是第二类,也就是 GitLab 项目仓库的建立,同时会在后面专门拿出一节,把制品仓库和代码仓库的边界讲清楚,免得你把该放 Nexus 的东西硬塞进 Git 里。

适合读这篇的人大致有三类。一类是团队里被指派"搞个内网代码托管"的人,手里有一台服务器和一点 Docker 基础,但从没装过 GitLab;一类是刚进公司、需要在新环境里建自己的仓库并推第一版代码的新人,卡在 SSH 密钥或者权限上;还有一类是已经用着 GitLab,但仓库越建越乱、分支命名五花八门、备份从来没做过的人。三类人的关注点不同,我会尽量把每一步都拆到能照着敲的程度,同时把"为什么要这么做"讲明白。

一个基础判断先摆在前面:如果你的仓库里只有公开的学习代码,托管服务完全够用,注册账号、点几下鼠标、推上去就完事,自建纯属给自己找活干。真正需要自建的情况通常是这几条中的一条或多条:代码不能出内网、团队规模大到需要统一账号体系、需要跟内部的其他系统做深度集成、或者网络出口不稳定导致拉取经常失败。判断清楚了再往下走,能省掉大量返工。

1.1 托管服务与自建部署的取舍逻辑

托管服务最大的优势不是免费,而是你不用管升级、不用管磁盘、不用管证书过期。它的代价在于可定制性受限,以及数据物理位置不在自己手里。自建则完全反过来:所有东西都在你掌控之中,但运维责任也一并落到你头上——备份、升级、容量、安全补丁,全是你的活。

我在做选型的时候习惯列一张责任清单,把"谁负责什么"写清楚。这张表看起来很笨,但它能有效阻止那种"装完了就没人管"的经典局面。

维度使用托管服务自建部署
初始成本几乎为零一台服务器加半天到两天时间
持续运维平台方承担自己承担升级、备份、监控
数据位置平台机房自己的机房或云主机
定制能力受平台功能限制可改配置、可接内部系统
网络依赖依赖公网质量内网访问,速度快且稳定
适合规模个人、小团队中大型团队、合规要求高的场景

实际决策中还有一个容易被忽略的变量:人数增长曲线。三五个人用托管很舒服,三十个人开始出现权限管理需求,三百个人的时候账号生命周期管理、审计日志、单点登录这些事会变成硬需求,而这些恰好是自建方案的强项。所以如果你的团队在一年内可能翻几倍,自建的早期投入是划算的。

1.2 三种部署形态的适用场景对照

确定自建之后,装法也有讲究,主流就三条路:容器化部署、系统包直接安装、离线环境的手工部署。它们不是新旧关系,而是适配不同约束条件。

容器化部署是我最推荐的入门方式。镜像封装好了所有依赖,升级就是换个 tag 重启,回滚同理,配置文件集中在一个 gitlab.rb 里,出问题好定位。它的短板在于对宿主机内核版本有要求,且容器与宿主机的资源共享关系需要理解,比如内存限制设得太死会导致服务被系统直接干掉。

系统包安装(也就是官方提供的 Omnibus 包)在物理机上更常见,好处是跟系统服务管理集成得自然,开机自启、日志轮转这些不用额外操心。坏处是升级路径有时候需要按版本逐个跳,跨大版本升级容易出岔子。

离线部署主要出现在内网隔离环境。做法是提前把安装包和所有依赖下载好,用本地源的方式安装,过程中不能有联网动作。这种场景下最容易翻车的地方是时间同步和证书,装完之后 clone 一直报错,查半天发现是服务器时间偏差太大导致令牌校验失败。

三种方式的共同点是:GitLab 是个相当吃资源的常驻服务,无论怎么装,预留的资源都要给足,后面会具体算。

1.3 账号、分组、权限的前置规划

我见过太多团队把 GitLab 装好之后,所有人用 root 账号干活,仓库全建在 root 名下。等到需要交接或者有人离职的时候,才发现根本理不清哪些仓库跟谁有关。这个问题在项目启动的第一天解决成本几乎为零,拖到半年后就要动大手术。

推荐的起点是这样:root 账号只用于管理,日常开发另建个人账号,然后在顶层建一个或几个 Group,项目全部挂在 Group 下而不是个人命名空间下。这样做的好处很直接——人员变动时只需要调整组成员,仓库本身的所有权不用动,URL 也不会因为用户名变化而失效。

权限层次上,GitLab 预设了 Guest、Reporter、Developer、Maintainer、Owner 五档。给权限的原则是够用就好:只读代码的人给 Reporter,日常提交的给 Developer,负责发布和分支保护的给 Maintainer,Owner 一般只留给团队负责人。这个划分听着啰嗦,但它能防住"手滑删掉主分支"这类事故。

Group 还有一层嵌套的能力,可以按部门建父组、按项目建子组,权限在父组层面统一授一次,子组自动继承。团队规模超过二十人的时候,这套结构能省掉大量重复配置。

2. 服务器准备与 GitLab 部署实操

服务器这一关是自建路上淘汰率最高的环节,绝大多数失败案例都源于资源给得不够。GitLab 是个 Rails 应用加一堆后台服务(PostgreSQL、Redis、Gitaly、Sidekiq、Nginx)的组合体,启动完成后常驻内存占用就不低,编译资源、跑流水线的时候还会再往上冲。

2.1 资源规格怎么算才不踩坑

内存是最关键的一项。官方给出的最低门槛是 4GB,但那是"能起来"的标准,不是"能用"的标准。我自己的经验值是这样:纯代码托管、十几个人用,8GB 内存是舒适线;如果要在上面跑 CI 流水线,尤其是构建 Docker 镜像,建议直接上 16GB。另外 Swap 一定要开,2GB 到 4GB 都行,它能在内存峰值时保命,避免 OOM Killer 把 PostgreSQL 直接杀掉。

CPU 方面,2 核能跑,但页面响应会明显迟钝;4 核是性价比较高的选择。GitLab 的很多操作是单线程瓶颈,堆更多核不一定线性提升,4 到 8 核之间比较合理。

磁盘要算得细一点。系统加 GitLab 本体大约占 10GB 到 15GB;代码仓库本身的体积取决于项目,纯文本代码通常很小,但如果仓库里混进了二进制文件、设计稿、数据集,体积会迅速膨胀。我的算法是:预留 50GB 起步,然后按"代码总量乘以 3"来估算实际占用,因为 Git 是完整保存历史的,加上 GitLab 的制品、日志、备份,实际占用通常是原始代码的好几倍。另外强烈建议上 SSD,GitLab 的随机读写非常密集,机械盘上的体验会让人怀疑人生。

还有一项容易忘:备份空间。备份文件默认放在数据目录下,如果不做清理,日积月累能把磁盘吃干净。建议把备份直接往外部存储或者另一台机器上推。

2.2 Docker 方式部署 GitLab 完整流程

下面这套流程我用了很多次,稳定可靠。前提是服务器上已经装好 Docker 和 Docker Compose,并且宿主机的 80、443、22 端口没有被占用(22 端口如果被 SSH 占用,就把 GitLab 的 SSH 映射到别的端口,比如 2222)。

先建目录,把配置、日志、数据三块分开挂载,这样升级的时候直接换容器就行,数据不受影响:

sudo mkdir -p /srv/gitlab/config /srv/gitlab/logs /srv/gitlab/data

然后用 docker compose 管理,比一长串 docker run 好维护得多:

services: gitlab: image: gitlab/gitlab-ce:17.4.1-ce.0 container_name: gitlab restart: always hostname: git.example.com environment: TZ: Asia/Shanghai GITLAB_OMNIBUS_CONFIG: | external_url 'https://git.example.com' gitlab_rails['gitlab_shell_ssh_port'] = 2222 gitlab_rails['time_zone'] = 'Asia/Shanghai' nginx['redirect_http_to_https'] = true letsencrypt['enable'] = false puma['worker_processes'] = 2 postgresql['shared_buffers'] = '1GB' ports: - "80:80" - "443:443" - "2222:22" volumes: - /srv/gitlab/config:/etc/gitlab - /srv/gitlab/logs:/var/log/gitlab - /srv/gitlab/data:/var/opt/gitlab shm_size: "256m"

这里有几个参数值得单独说明。external_url决定 GitLab 生成的所有链接和 clone 地址,必须在第一次启动前就定好,中途改会带来一堆麻烦。gitlab_shell_ssh_port是给页面显示的 SSH 地址打补丁的,如果不设,页面上给出的 clone 地址会带着容器的 22 端口,而宿主机的 22 被 SSH 占着,用户照着复制就会连不上。shm_size关系到 PostgreSQL 的共享内存,默认 64MB 偏小,设成 256m 能减少一些偶发的数据库报错。puma['worker_processes']控制应用进程数,内存吃紧的机器上把它调小能省下几百 MB。

启动命令很简单:

docker compose up -d docker compose logs -f gitlab

启动过程会持续三五分钟,期间 Nginx、PostgreSQL、Gitaly 依次就绪。看到日志里出现 "gitlab Reconfigured" 之类的字样,基本就算完成了。这个过程千万别中途Ctrl+C,重新配置会被打断,留下半成品状态。

2.3 首次登录与管理员初始化

服务起来之后,第一件事是拿初始密码。GitLab 会在首次启动时生成一个随机 root 密码,写在容器内的文件里:

docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password

这个文件有个坑:它会在 24 小时后被自动删除。所以拿到密码第一件事就是登录改掉,改完之后把新密码存进团队的密码管理工具,而不是留在某个聊天记录里。

登录之后建议立刻做几件事。第一,关掉公开注册。管理后台的"设置 - 通用 - 注册限制"里可以关闭开放注册,改为管理员审核或仅邀请,这一步能挡掉大量扫描注册的机器人。第二,配置发信服务。GitLab 默认不发邮件,导致密码找回、通知、合并请求提醒全部失效,体验会差一大截;配好 SMTP 之后这些功能才完整。第三,调整默认的项目可见性。默认是 Private 还是 Internal 要根据团队习惯定,企业内部一般选 Private,避免有人手滑把内部代码建成公开项目。

还有一个界面上的细节:进入管理后台之后,先看一眼"概览"里的组件状态,确认各个服务都是绿色。如果有服务显示异常,多半是资源不够或者端口冲突,这时候排查比用起来之后再排查容易得多。

2.4 让 clone 地址显示域名而不是机器 ID

这个问题的表现形式是:项目页面上显示的 clone 地址形如http://a1b2c3d4e5f6/group/project.git,中间那串是容器 ID 或者主机名,别人复制去用根本连不上。

根因在于 GitLab 不知道自己对外的正式地址是什么。它生成 URL 时优先使用配置里的external_url,如果这个值没设或者设成了主机名,页面就会把内部标识暴露出来。解决办法就是在部署时把external_url写成完整的对外域名,比如https://git.example.com,改完之后执行:

docker exec -it gitlab gitlab-ctl reconfigure

如果前面挂了反向代理,还要多做两步。一是确认代理转发时带上了正确的主机头,二是在 GitLab 配置里打开对代理头的信任:

nginx['listen_port'] = 80 nginx['listen_https'] = false nginx['proxy_set_header'] = { "Host" => "$http_host", "X-Forwarded-Proto" => "https", "X-Forwarded-Ssl" => "on" } gitlab_rails['trusted_proxies'] = ['172.16.0.0/12']

改完同样要 reconfigure。做完这几步,页面上的 clone 地址就会稳定显示成你的域名,HTTP 和 SSH 两种方式都会同步更新。

3. 创建你的第一个仓库并完成代码推送

服务跑起来、管理员账号能用之后,建仓库这件事本身只要一分钟,但里面有几个选项会长期影响使用体验,值得逐个看。

3.1 页面上新建仓库时那几个选项逐个拆

点新建项目之后,GitLab 会问你是从空白建、从模板建,还是导入已有仓库。空白项目自不必说;模板适合快速起一个带 README 和基础流水线配置的项目;导入适合从别的平台迁移,后面单独讲。

接着是命名空间的选择,这一步是重点。选择个人命名空间还是某个 Group,决定了仓库的 URL 前缀和权限继承关系。前面提过,团队项目一律挂 Group 下,个人试验性质的可以放自己名下。

项目路径(Project slug)会自动根据名称生成,建议手动确认一下,因为路径一旦确定就很难改,改了之后所有本地仓库的 remote 地址都会失效。命名习惯上我推荐用小写加连字符,比如order-service,避免空格、大写和特殊字符,可以减少跨系统集成时的转义麻烦。

可见性三档是 Private、Internal、Public。Internal 的含义是"任何登录用户可见",在企业内网环境里这是个容易被误用的选项——你以为它是私有的,实际上全公司只要有账号就能看到。真正需要限制就选 Private,然后在项目成员里逐个加人。

初始化选项里有一个"用 README 初始化仓库"。如果你本地已经有一个带历史的项目要推上去,就别勾,否则远端先有一个提交,本地推的时候会因为历史不一致被拒。反过来,如果你想直接在页面上传几个文件,那勾上更省事。

3.2 SSH 密钥配置与排错

用 SSH 方式访问仓库比 HTTPS 舒服得多,不用反复输账号密码,也不用折腾令牌。配置过程本身很简单,坑都在细节里。

先生成密钥对,推荐用 ed25519,比 RSA 更短更安全:

ssh-keygen -t ed25519 -C "your.name@example.com"

一路回车,默认落在~/.ssh/id_ed25519.pub两个文件。然后把.pub里的内容整段复制,粘到 GitLab 的"用户设置 - SSH 密钥"里。注意是 pub 那个,不是私钥,这一点每年都有无数人搞错。

端口不是 22 的情况下(比如我们前面映射到 2222),需要在~/.ssh/config里加一段:

Host git.example.com HostName git.example.com Port 2222 User git IdentityFile ~/.ssh/id_ed25519 IdentitiesOnly yes

配完先用这条命令验证:

ssh -T git@git.example.com

返回一句带你用户名的欢迎语就说明通了。如果报Permission denied (publickey),按这个顺序查:密钥有没有粘错、ssh-add -l看 agent 里有没有加载、文件权限是不是 600、config 里的 Host 别名有没有和实际使用的一致。我遇到最多的情况是服务器换了新密钥但本地 agent 里还缓存着旧的那把,ssh-add -D清一下再重新加就好。

3.3 本地仓库从零推到远端

远端仓库建好、SSH 通了之后,本地这段流程就是标准动作。假设你本地已经有一个写好的项目目录:

cd your-project git init git add . git commit -m "chore: 初始化项目" git branch -M main git remote add origin git@git.example.com:group/your-project.git git push -u origin main

这里有几个细节值得说。git branch -M main是把默认分支名统一成 main,过去默认是 master,现在两边混用的团队很多,统一一下能省掉后续的混乱。-u参数把本地 main 和远端 main 关联起来,之后直接git push就行。

如果你本地已经有一段时间的提交历史,推送前先看一眼git log,确认没有把不该提交的东西写进去。文件体积也很关键,超过 100MB 的单个文件在推送时会被拒绝,而且一旦进了历史,即使后来删掉,仓库体积也不会自动缩小,清理起来相当麻烦。初始化的时候就把.gitignore写对,比事后补救轻松十倍。

从别的地方复制过来的项目,还可能带着旧仓库的.git目录。这种情况要么直接复用它的历史,要么删掉.git重新 init。想保留部分历史又想去掉某些敏感提交,就得上git filter-repo这类工具,属于进阶操作,不在第一次建仓的范围内。

3.4 第一次推送失败的高频原因

第一次推送失败太正常了,我把遇到过的原因整理成一张表,按出现频率排序:

报错信息关键字常见原因处理办法
Permission denied (publickey)公钥没上传或 agent 没加载重新粘贴公钥,ssh-add加载私钥
remote: HTTP Basic: Access deniedHTTPS 方式用了账号密码改用访问令牌或个人访问令牌
! [rejected] main -> main (fetch first)远端已有提交,历史不一致git pull --rebase再推
fatal: remote origin already exists重复添加远端git remote set-url origin 新地址
RPC failed; HTTP 413反代请求体限制过小调大代理的 body 尺寸或改用 SSH
Please make sure you have the correct access rights项目路径写错或没权限核对 URL,确认自己是项目成员
login failed. check api token or gitlab version令牌权限不足或版本不兼容重新生成带 api 范围的令牌

最后那条尤其值得展开。这个报错常见于 IDE 插件或者第三方工具连接 GitLab 的场景,本质是工具在调 API 的时候带了无效令牌,或者令牌缺少必要的 scope(比如只有 read_repository 却要执行写操作)。排查方法是先确认令牌还在有效期内、再确认勾选的权限范围够用、最后确认工具版本对 API v4 的支持没问题。老版本的插件有时候会用已经废弃的接口,这种情况只能升级工具。

还有一个环境相关的坑:某些网络环境对 HTTP 大请求做了限制,推送一个稍大的仓库就会中断。这种时候用 SSH 协议往往能绕开,因为限制通常施加在 HTTP 层。判断方法很简单,同一个小仓库用 HTTPS 能推、用 HTTPS 推大仓库失败、换 SSH 成功,基本就能确认。

4. 分支模型、合并请求与团队协作规范

仓库建起来只是开始,真正决定长期可维护性的是分支怎么切、代码怎么合。这一块没有唯一正确答案,但有几个组合在实践中反复被证明有效。

4.1 一个够用又不折腾的分支模型

小团队最忌讳上重型流程。我的建议是从一个极简模型起步,随着团队扩大再逐步加规则。

主干分支只有一个,叫 main,始终保持随时可发布的状態。功能开发在 feature 分支上做,命名用feature/简短描述的形式,比如feature/order-export。修复线上问题用hotfix/前缀,发布准备用release/前缀。前缀的作用是让分支列表一眼能看出用途,尤其在分支数量上百之后,这个习惯能救命。

分支的生命周期要短。一个功能分支从创建到合并最好控制在一周以内,超过两周的分支会开始积累冲突,合并成本急剧上升。我在实践中会定期清理已经合并的远端分支,GitLab 支持在合并请求里勾选"合并后删除源分支",打开这个开关能自动完成大部分清理工作。

命名上还有一个细节:用连字符而不是下划线或空格。因为分支名会出现在 URL 里,也会出现在命令行里,连字符在所有这些环境里都不需要转义。

4.2 保护分支与合并请求的必填项

main 分支一定要设为受保护分支,这是整个协作规范里性价比最高的一条规则。保护之后,普通开发者不能直接推送,只能通过合并请求提交,从机制上杜绝了误操作。

保护分支的配置项里有几个开关值得细看。"允许合并"决定谁能点合并按钮,一般设为 Maintainer;"允许推送"建议关掉,改成"不允许任何人直接推送",包括管理员也走合并请求流程,这样审计记录才完整。"允许强制推送"必须关闭,否则保护形同虚设。

合并请求本身也有必填项可以配置。我一般会打开三条:至少一个人批准、所有讨论必须解决、流水线必须通过。第一条防止自己批自己,第二条防止带着未解决的评审意见合并,第三条防止把编译不过的代码合进主干。

合并方式上有三种选择:普通合并会保留完整历史,合并提交会多一个节点;快进合并保持线性历史,看着干净但丢失了分支信息;压缩合并把整个分支的提交压成一个,适合那种提交信息很随意的场景。我们团队的默认是压缩合并,因为大家本地提交往往很零碎,压成一个语义完整的提交后再进主干,历史读起来舒服很多。

4.3 标签、里程碑与仓库内文档

标签(Tag)用于标记发布节点,配合语义化版本号,比如v1.2.0。打标签的时候建议附上注释,用git tag -a v1.2.0 -m "发布说明"的形式,这样标签对象里会保存打标签的人和说明,比轻量标签信息更完整。

里程碑适合管理有明确时间点的版本目标,把相关的议题和合并请求挂上去,进度一目了然。这个功能很多人没用起来,其实对"这个版本要做哪些事"这种问题,它给出的答案比翻聊天记录可靠得多。

仓库内的文档从第一天就建。README 说清楚项目是干什么的、怎么跑起来;CONTRIBUTING 说清楚分支怎么起、提交信息怎么写;CHANGELOG 记录每次发布的变更。这三份文档加起来可能就几百字,但它能省掉后来者大量的问人时间。写文档这件事有个规律:越早写越省事,因为项目还小的时候你脑子里什么都清楚,写起来很快;等半年后你自己都记不清某个配置为什么那样设了。

5. 仓库日常维护:备份、迁移与安全加固

仓库不是建完就完事的,它跟所有有状态的服务一样,需要定期照看。这一章讲的三件事——备份、迁移、升级——我在实际项目里都遇到过因为没做而导致严重后果的情况。

5.1 备份策略与恢复演练

GitLab 的备份分两部分,很多人只知道第一部分。第一部分是数据备份,用这条命令:

docker exec -it gitlab gitlab-backup create

生成的备份包默认在/var/opt/gitlab/backups下,里面包含数据库、仓库、制品等。第二部分是配置文件,也就是/etc/gitlab/gitlab.rb/etc/gitlab/gitlab-secrets.json。这两个文件必须单独备份,因为密钥文件里存着数据库加密密钥、CI 变量密钥这些内容,缺了它,数据恢复了也解不开。

恢复演练比备份本身更重要。我见过备份脚本跑了两年从没验证过,真出事的时候才发现备份包是空的——原因是备份任务和某个维护窗口撞车,进程被中断了。演练的频率建议每季度一次,在测试环境上实际恢复一遍,确认能起来、能登录、仓库内容完整。

备份的存放位置也有讲究。默认路径在数据目录里,磁盘一旦写满,备份和线上数据一起遭殃。建议改到独立磁盘或者远端存储:

gitlab_rails['backup_path'] = "/mnt/backup/gitlab" gitlab_rails['backup_keep_time'] = 604800

backup_keep_time单位是秒,604800 就是保留七天。加上定时任务自动清理,能避免备份把自己的磁盘撑爆。

5.2 从外部仓库迁移项目

迁移的需求很常见,比如团队从别的平台整体搬迁过来。GitLab 提供了按 URL 导入的功能,在新建项目时选择"导入项目",填入源仓库地址和凭证,它会自动把代码、议题、合并请求一起拉过来。

实际做的时候有几个坑。一是源平台的 API 速率限制,仓库多的时候要分批导入,不然会被限流中断。二是议题和评论里的用户映射,源平台的用户在新平台可能不存在,导入后会显示成一串匿名标识,需要事后手工对应。三是大仓库的导入非常慢,一个几 GB 的仓库可能要跑几个小时,中途不要刷新页面。

如果源平台不支持标准导入接口,退而求其次的做法是纯 Git 迁移:在源端git clone --mirror,然后在新端推送。这样能完整保留所有分支和标签,但议题、合并请求这些元数据就带不走了,只适合纯代码迁移的场景。

还有一种情况是从自建的旧服务迁移过来,旧服务可能在另一台机器上。做法是先停掉旧服务的写入,做一次全量同步,然后切换 DNS 或者修改客户端的 remote 地址。切换前一定要在所有开发者的机器上确认一遍 remote 地址已经更新,否则会有人在旧地址上继续推代码,造成分叉。

5.3 版本升级与安全补丁的跟进节奏

GitLab 的版本节奏很快,每个月一个小版本,这带来一个现实问题:不升级会积累安全风险,升级太频繁又会消耗运维精力。

我的做法是制定一个固定的升级窗口,比如每季度一次,跟随当前稳定分支的小版本前进。升级前必做三件事:完整备份、查阅官方的升级路径说明、在测试环境先跑一遍。升级路径特别重要,跨大版本有时候需要按顺序逐级升,直接跳过去会导致数据库迁移失败。

小版本的补丁发布通常包含安全修复,这类更新建议及时跟进。关注官方的安全公告渠道,看到涉及自己使用版本的通知就评估影响范围。评估维度包括:漏洞是否需要认证才能触发、是否需要特定配置才会命中、内部网络是否已经隔离了外部访问。综合判断之后再决定是立即升级还是等下一个窗口。

升级本身的操作很简单,容器方式就是改 compose 文件里的镜像 tag,然后docker compose up -d

docker compose pull docker compose up -d docker exec -it gitlab gitlab-ctl reconfigure

重新配置的过程可能持续几分钟,期间服务不可用,最好安排在低峰期。跑完之后检查一遍组件状态和日志,确认没有报错再收工。

5.4 容量治理与历史仓库瘦身

仓库体积是慢慢长起来的,等到磁盘告警的时候通常已经麻烦了。日常可以关注这几项占用:Git 仓库本体、CI 产生的制品、容器镜像缓存、日志文件。

制品是最容易失控的一块。流水线每次运行都可能产生编译产物、测试报告、打包文件,默认保留时间很长。在项目设置里可以配置制品和流水线的过期时间,比如保留最近 30 天,超过的自动清理。对于已经积累的大量历史制品,可以用清理命令批量处理:

docker exec -it gitlab gitlab-rake gitlab:cleanup:orphan_job_artifact_files

日志方面,GitLab 的日志文件增长也不容小觑,尤其是访问日志。在配置里开启日志轮转和保留天数限制,能省下不少空间。

至于 Git 仓库本体的瘦身,属于比较重的操作。如果历史里混进了大文件,常规做法是用git filter-repo重写历史,然后强制推送。这个操作会改变所有提交的哈希,所有协作者都必须重新克隆,所以一定要提前通知到位,并且选在大家都不提交的时间段做。做完之后在服务端触发一次垃圾回收,空间才会真正释放:

docker exec -it gitlab gitlab-rake gitlab:housekeeping

6. 把 CI/CD 和镜像仓库串起来

代码托管只是 GitLab 的一半价值,另一半在于它内置的流水线能力。仓库建好之后,很自然的下一步就是让提交自动触发构建和部署。

6.1 Runner 注册的两种方式

流水线要跑起来,得有 Runner 执行任务。Runner 可以装在项目级别、组级别,也可以作为整个实例的共享 Runner。我的建议是:通用能力(比如构建基础镜像)用共享 Runner,特殊环境(比如需要访问内部网络的部署任务)用项目专属 Runner。

容器方式装 Runner 很方便:

docker run -d --name gitlab-runner --restart always \ -v /srv/gitlab-runner/config:/etc/gitlab-runner \ -v /var/run/docker.sock:/var/run/docker.sock \ gitlab/gitlab-runner:v17.4.0

注册命令需要从 GitLab 界面上拿注册令牌,路径在"管理后台 - CI/CD - Runner"或者具体项目的设置里:

docker exec -it gitlab-runner gitlab-runner register \ --url https://git.example.com \ --token glrt-xxxxxxxxxxxx \ --executor docker \ --docker-image alpine:latest \ --description "docker-runner"

--executor docker表示每个任务跑在一个临时容器里,隔离性好,环境干净。代价是每次都要拉镜像,首次执行会慢一些。如果宿主机上有现成的构建环境,也可以选 shell 执行器,速度更快但隔离性差,容易受宿主机环境影响。

注册完成后,在界面上能看到 Runner 处于在线状态,标签(tag)可以用来精确调度任务。给 Runner 打标签是个好习惯,比如docker-builderdeploy-prod,在流水线里指定标签,避免任务被随机调度到不具备条件的机器上。

6.2 构建镜像并推送到私有镜像仓库

下面这份配置是我在多个项目里用过的模板,作用是构建镜像并推送到内部镜像仓库:

stages: - build - deploy variables: IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA build_image: stage: build image: docker:24.0 services: - docker:24.0-dind variables: DOCKER_TLS_CERTDIR: "/certs" script: - docker login -u "$HARBOR_USER" -p "$HARBOR_PASS" registry.example.com - docker build -t registry.example.com/team/app:$CI_COMMIT_SHORT_SHA . - docker push registry.example.com/team/app:$CI_COMMIT_SHORT_SHA only: - main - tags deploy_prod: stage: deploy image: bitnami/kubectl:latest script: - kubectl set image deployment/app app=registry.example.com/team/app:$CI_COMMIT_SHORT_SHA when: manual only: - main

几点说明。$HARBOR_USER$HARBOR_PASS是放在项目 CI/CD 变量里的,不要写进文件。变量可以设置为"受保护",这样就只有受保护分支的流水线才能读到,安全性更好。DOCKER_TLS_CERTDIR是 docker-in-docker 的必要配置,不设的话客户端和服务端通信会失败。when: manual让部署任务变成手动触发,给线上变更留一道确认环节。

用提交的短哈希作为镜像标签,好处是每个构建产物唯一且可追溯,出问题的时候能精确定位到某次提交。同时可以再打一个latest标签方便本地测试,但生产环境请始终用具体标签,不要用 latest。

6.3 依赖仓库与制品仓库的分工

这一节解决一个常见混淆:代码仓库和制品仓库到底各管什么。

Git 擅长管文本,它对每一行变更都做增量存储和差异计算,这是它的强项。但二进制文件、依赖包、构建产物属于大块头且几乎每次都在整体变化,放进 Git 会让仓库体积迅速膨胀,而且拉取速度直线下降。所以这些东西应该放在制品仓库里,Git 仓库里只保留引用它们的配置文件。

常见组合是这样的:源码放在 GitLab;编译出的二进制包、Docker 镜像放在私有制品仓库(如 Nexus、Harbor 等);依赖的三方库通过私服代理缓存,比如 Maven 项目在settings.xml里配置镜像,把中央仓库的请求转发到内部私服,既加速又可控:

<mirror> <id>internal-repo</id> <mirrorOf>central</mirrorOf> <name>internal mirror</name> <url>https://repo.example.com/repository/maven-public/</url> </mirror>

私服还可以配置多个上游仓库的代理顺序,当某个上游不可达时自动回退到下一个,这对构建稳定性帮助很大。类似的机制在其他生态里也存在,比如前端包管理器的私有源配置、鸿蒙生态的包仓库配置,思路完全一致:内部缓存优先,外部兜底。

需要提醒的是,不要把制品仓库的地址和凭证硬编码进代码。放在环境变量或者 CI 变量里,本地开发用一份独立的配置,避免开发者的个人配置影响到流水线。

7. 常见问题速查与踩坑记录

前面几章零散提了不少坑,这一章把高频问题集中整理一遍,方便出问题时按图索骥。

7.1 连接与认证类问题

认证问题占了日常求助的一大半。除了前面表格里列的那些,还有几个场景值得单独说。

页面能打开但 clone 一直超时,通常是端口没通。GitLab 网页走 443,SSH 走另一个端口,很多防火墙只开了 443,导致 HTTPS 能拉、SSH 拉不动。排查顺序是先在本地telnet 域名 端口看通不通,再看服务器防火墙规则,最后确认容器端口映射是否正确。

还有一种情况是 HTTPS 方式推送时反复要求输入密码。这通常意味着服务端开启了双重认证,或者必须用访问令牌代替密码。正确做法是在"用户设置 - 访问令牌"里生成一个带write_repository范围的令牌,然后在提示密码时粘贴这个令牌。

浏览器层面也可能出问题。如果登录后莫名其妙被踢出、或者页面报 CSRF 错误,先检查系统时间是否准确,时间偏差过大时令牌校验会失败。这个问题在虚拟机和容器环境里特别常见。

7.2 部署与性能类问题

页面加载慢、推送超时、流水线卡住,这些多半跟资源有关。按这个顺序排查:先用docker stats看容器内存和 CPU 使用,再用free -h看宿主机整体情况,最后看 GitLab 自身的组件状态。

内存不足最典型的表现是 GitLab 运行一段时间后某个服务自动退出,日志里有 OOM 相关记录。这种情况下要么加内存,要么调小puma的进程数、sidekiq的并发数。调小并发会牺牲一些响应速度,但能换来稳定性,取舍看实际负载。

磁盘满的表现更直接,页面报 500,日志里一堆写入失败。这时候可以先做一次清理:清掉过期的流水线制品、清理日志、删除无用的大型附件,能快速腾出空间。长期方案还是要把备份和制品往外挪。

流水线卡住大部分情况是 Runner 的问题。先确认 Runner 在线,再看它的标签和任务的标签是否匹配,最后看 Runner 所在机器的资源是否被占满。如果用的是 docker 执行器,还要注意并发任务数,默认配置下同一个 Runner 可能同时跑多个任务,资源竞争会导致全部变慢。

7.3 一些没人写在文档里的经验

最后这部分是我自己踩出来的,官方文档里不会写,但实际很有用。

养成先建空仓库再推代码的习惯。很多人喜欢在页面上勾选"用 README 初始化",然后本地推的时候遇到历史冲突,再花时间解决。先建空仓库,本地推首次提交,流程最顺。

.gitignore要一次写全。我见过把node_modules、构建产物、本地配置文件全提交进去的项目,仓库体积几十兆,克隆要等半天。建仓的第一件事就是根据项目技术栈选一份完整的忽略规则,宁多勿少。

提交信息写清楚。这条听起来像废话,但在需要回溯问题的时候,一句"fix bug"和一句"修复订单导出时金额字段精度丢失"的价值差距是巨大的。团队里可以在合并请求模板里加一条提醒,慢慢就形成习惯了。

给仓库加个描述和头像。项目多了之后,列表页里一堆名字相似的仓库,有个描述和图标能省很多辨认时间。这个成本极低,收益却很持续。

定期看一眼管理后台的审计日志。异常登录、权限变更、大量克隆,这些行为在日志里都有记录。平时不看,出事的时候连追溯的线索都没有。设置一个每周花五分钟扫一遍的习惯,性价比很高。

我个人在这件事上最大的体会是:仓库的规范程度几乎完全取决于建仓那一个小时的决策。命名、分组、权限、忽略规则、分支保护,这些在项目刚开始时设置只需要几分钟,等项目跑起来再补,就要协调所有人配合,成本高得离谱。所以如果你正准备建自己的第一个 GitLab 仓库,不妨在点下"创建"之前多花十分钟,把命名空间、可见性、保护规则这三件事想清楚,后面会省掉非常多的麻烦。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询