GitLab部署与CI/CD实践:从Docker安装到自动化流水线
2026/9/15 8:02:52 网站建设 项目流程

1. 部署与安装:先把GitLab跑起来

1.1 部署方式选型:先想清楚再动手

聊GitLab之前,我猜你大概率是这两种情况之一:要么公司想搭一套内网的代码托管平台,把代码从第三方平台迁回来自建;要么你自己想搞一台服务器,把GitLab跑起来当个人代码仓库和CI/CD调度中心。

我接触GitLab的时间不算短,从最早公司自建到后来自己用Docker部署,踩过的坑能写满一个备忘录。老实说,GitLab不像某些开箱即用的小工具,它本质上是一套重量级研发协作平台,部署前如果不把方案想清楚,后面改起来会相当痛苦。

部署方式其实就三条路:直接用GitLab官方的SaaS服务、在自己的服务器上用Omnibus包安装、用Docker容器跑。官方SaaS解决的是“不想运维”的问题,适合个人或小团队,注册即用,不需要关心升级、备份、安全问题,但代码托管在别人那里,对于很多公司来说合规上过不去。

自己搭服务器则是完全不同的思路。大多数企业选它是因为要代码资产100%掌握在自己手里,同时利用GitLab自带的CI/CD、仓库管理、代码评审流程来统一研发流程。我见过很多团队一开始图省事用第三方平台,等到要接内网权限系统、自定义CI构建流程的时候才意识到自由度的价值,最后还是要折腾一套自建的。

Docker部署是我个人使用频率最高的方式,它有几个明显优势:升级方便、环境隔离干净、迁移简单。你只需要维护一份docker-compose配置,换服务器的时候几行命令就能把整个平台恢复起来。不过Docker方式也要注意,GitLab是一个多进程应用,如果宿主机配置不够,大团队使用会明显卡顿。我自己的经验是,5人以下小团队用2核4G的机器勉强能跑,10人以上至少4核8G起步,磁盘建议单独挂一块SSD,Git仓库的读写性能对磁盘IO非常敏感。

1.2 Docker方式安装GitLab:十分钟跑通第一步

Docker安装GitLab,我先给出一个我长期在用的最小化部署方案。这里用的镜像是gitlab/gitlab-ce,CE就是社区版,功能上对绝大多数团队完全够用。你不需要单独拉取数据库、Redis之类的容器,GitLab镜像内部已经集成了这些组件,开箱即用。

# 创建数据目录,目录结构建议固定下来,方便后续备份 mkdir -p /srv/gitlab/config mkdir -p /srv/gitlab/logs mkdir -p /srv/gitlab/data # 启动容器 docker run --detach \ --hostname gitlab.example.com \ --publish 8443:443 --publish 8080:80 --publish 2222:22 \ --name gitlab \ --restart always \ --volume /srv/gitlab/config:/etc/gitlab \ --volume /srv/gitlab/logs:/var/log/gitlab \ --volume /srv/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:latest

这里有几个参数我展开说一下,因为它们直接影响你后续的使用体验。

--hostname决定了GitLab生成的仓库URL地址。如果你不设置域名,建议直接填服务器的IP,比如--hostname 192.168.1.100,这样clone仓库的时候地址就是http://192.168.1.100/group/project.git,访问起来最省事。端口方面我这里做了自定义映射:宿主机8080映射容器80(HTTP),8443映射443(HTTPS),2222映射22(SSH)。注意SSH端口,宿主机22端口经常被系统自带的sshd占用,所以映射到2222,但这就意味着你后面配置SSH克隆地址时需要写成ssh://git@gitlab.example.com:2222/group/project.git,这也是很多人第一次配置SSH时死活连不上的原因之一。

启动之后,容器第一次启动会有一个初始化的过程,通常需要两三分钟。GitLab社区版默认会为管理员账号root生成一个随机密码,存放在容器内的/etc/gitlab/initial_root_password文件中。你需要执行下面这条命令来查看:

docker exec -it gitlab cat /etc/gitlab/initial_root_password

拿到的密码用于首次登录,登录后记得马上在用户设置里改成自己的密码。这个随机密码文件在首次登录后24小时会被GitLab自动删除,如果你忘了查看就得另外走重置流程,挺麻烦的。

另外我特别建议部署完之后立刻去Admin Area -> Settings -> Network里把“限制创建项目、限制注册”这些选项打开。GitLab默认是允许任何人注册账号的,如果部署在内网还好,一旦暴露到公网,扫描机器人分分钟给你注册一堆垃圾账号,这是我踩过最痛的坑之一。

1.3 离线环境部署GitLab:没网也能装

前面说过,很多企业内网和生产环境是物理隔离的,没法直接在线拉取镜像或安装包。这种情况下就需要走离线安装路线,我工作中也处理过不少这类需求,这里分享一下Linux环境下的离线部署思路。

第一步是在一台能上网的同系统环境下,去GitLab官方或清华镜像站下载对应系统版本的RPM包。GitLab官方下载页面提供了版本选择,注意要匹配你的系统发行版,比如CentOS 7对应的就是el7版本,Ubuntu 20.04对应的是focal版本。我用得比较多的是清华镜像源,速度比官方快很多,地址是https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/,版本号和企业版名要对应好。

下载好RPM包之后,拷贝到内网服务器上,执行安装:

# 先安装依赖,离线环境如果有本地yum源直接装,没有的话要提前把依赖包下载好 sudo yum install -y curl policycoreutils openssh-server openssh-clients postfix # 安装GitLab sudo rpm -i gitlab-ce-16.10.2-ce.0.el7.x86_64.rpm # 配置external_url,这一步很关键,不配置后面访问会有一堆奇怪问题 sudo vim /etc/gitlab/gitlab.rb # 找到 external_url 'http://gitlab.example.com',改成实际IP或域名 # 重新配置并启动 sudo gitlab-ctl reconfigure sudo gitlab-ctl status

离线安装最常见的坑是依赖缺失。GitLab依赖的组件不少,比如policycoreutils(SELinux相关)、openssh-serverpostfix(邮件通知),这些在最小化安装的Linux系统上经常没有。更麻烦的是,有些依赖本身还有依赖,纯离线情况下要把整棵依赖树都准备好。我的习惯是先准备一台和离线服务器同样系统版本、同样架构的机器,用yum downloadext --resolve把需要的RPM全部拉下来,再转移到内网批量安装,这样最保险。

external_url这个配置项特别值得多说一句。很多人在离线部署时图省事不配置,结果启动后页面上生成的仓库地址全是默认的gitlab.example.com,导致代码clone地址全部无法访问。我一般是部署完第一时间检查这个配置,要么改成服务器的实际IP,要么改成内网DNS能解析的域名。如果改完发现页面地址还是不对,需要执行gitlab-ctl reconfigure并重启服务,因为Omnibus包很多配置是启动时一次性写入的,不是动态生效。

2. 日常使用入门:注册、SSH与代码仓库操作

2.1 账号注册:真的不需要Visa卡

“GitLab注册需要Visa卡吗”这个问题我居然在高频搜索里看到过,那就在这里顺便辟个谣。无论你用的是GitLab官方SaaS还是自建的社区版,普通账号注册都只需要一个邮箱,跟信用卡没有任何关系。需要绑卡的情况只存在于企业版某些计费功能或高级服务中,日常使用完全用不上。

如果是自建的GitLab,管理员默认开启了注册功能,你直接去登录页点“Register now”填邮箱、用户名、密码就行。注册完之后,你还需要让管理员把你拉进对应的Group或项目里,并分配角色权限。GitLab的权限体系分五个级别:Guest(访客)、Reporter(报告者)、Developer(开发者)、Maintainer(维护者)、Owner(所有者)。实际使用中,普通研发给Developer就够了,可以push代码、创建分支、发起合并请求;Maintainer以上才有权限修改项目设置和管理成员。

2.2 配置SSH密钥:一次配置,长期使用

SSH密钥是GitLab日常使用中最基础也最影响体验的一环。很多新手第一次clone代码时提示权限失败,八成就是SSH密钥配置出了问题。我先把最标准的操作流程贴出来。

# 生成SSH密钥对,-t指定算法,-C填你的邮箱,-b指定长度 ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519 # 查看公钥内容,把这段内容复制到GitLab的SSH Keys页面 cat ~/.ssh/id_ed25519.pub

登录GitLab之后,点击右上角头像 -> Preferences -> SSH Keys,把刚才复制的公钥粘贴进去,起个容易辨识的标题,保存就完成了。GitLab支持同时添加多个公钥,比如一台办公电脑一台家用电脑,你可以分别生成各自的密钥对并添加,这样不管在哪台机器上都能正常访问仓库。

配置完成后,建议先做一次连接测试来验证是否真的通了:

ssh -T git@gitlab.example.com

如果看到Welcome to GitLab, @yourname!这段输出,说明SSH密钥配置成功,可以正常clone代码了。密钥算法我建议直接用ed25519,安全性比传统的RSA 2048更高,密钥长度更短,GitLab新版本完全支持。如果你用的还是老旧的RSA格式,建议尽快升级。

SSH还有个好处是无需频繁输入账号密码,在命令行操作时体验比HTTPS方式好很多。但如果你用的是局域网GitLab且经常换电脑,HTTPS方式配合Git的credential store也能做到类似效果,只是每次新增机器都要重新验证,我个人还是推荐SSH一条路走到底。

2.3 代码上传与拉取:从页面操作到命令行

代码上传这块,我见过两种完全不同的场景。一种是已经用Git很多年的老手,上来直接命令行初始化;另一种是偏测试或文档的同学,习惯在GitLab页面上传文件。两种方式我都说下。

命令行方式,以把一个已有的本地项目推送到新建的GitLab仓库为例:

# 在GitLab上新建空项目后,会得到一个仓库地址,比如: # http://gitlab.example.com/myteam/myproject.git cd myproject git init git add . git commit -m "init project" git remote add origin http://gitlab.example.com/myteam/myproject.git git branch -M main git push -u origin main

这里注意,git branch -M main是让默认分支名统一为main。GitLab默认分支名是main,如果你本地默认是master,不重命名直接push会出现远端有两个分支的情况,虽然不影响功能,但团队协作时分支不够规范容易混乱。

页面方式更简单,进入项目页面后点击+号选择Upload file,或者直接进入某个目录点Upload files,把本地文件拖拽进去即可。不过这种方式有大小限制,且每次只能操作少量文件,适合偶尔传个文档或配置文件。如果是批量代码,千万别用页面传,老老实实走命令行。

拉取代码这件事,本质上就是一个命令:git clone <仓库地址>。但实际工作中我更建议用git clone之前,先想好你要克隆的分支。比如你要拉取某个功能分支的代码,可以直接git clone -b feature/login http://gitlab.example.com/group/project.git,避免克隆完默认分支还要切换。另外,如果公司仓库特别大,历史提交很多,可以考虑--depth 1做浅克隆,只拉取最新一次提交,能节省大量时间和磁盘空间。不过浅克隆后续如果要看历史记录或切换分支,需要再git fetch --unshallow把历史补回来,各有利弊。

3. 分支管理与协作模型:别让Git变成“混乱之源”

3.1 分支策略:一套适合团队的Git Flow

代码托管只是GitLab的基本功能,真正让团队协作顺畅的是分支管理策略。我见过太多团队,所有人都在一个main分支上直接提交,发布的时候手忙脚乱,出了问题甚至不知道回滚到哪个提交。这种情况不是Git的问题,而是缺少一套明确的分支管理约定。

我个人比较推荐的是简化的Git Flow模型,它不需要像完整版那样重,但对于中小团队足够清晰。核心分支就两个:main(主干,始终保持可发布状态)和dev(日常集成开发分支)。功能开发时从dev拉出feature/xxx分支,开发完通过Merge Request合并回dev。准备发布时,从dev拉出release/x.x.x分支,只做修复不开发新功能,测试通过后合并到main并打上tag。

这个模型最大的好处是,main永远是干净的、可发布的,任何人任何时候拉取最新代码都不会拉到半成品。分支命名也建议规范化,比如feature/订单模块bugfix/登录异常hotfix/紧急修复,通过前缀一眼就能看出分支的用途。GitLab里我们还可以在项目设置里为受保护分支设置规则,比如main分支禁止直接push,只能通过Merge Request合入,这样从工具层面强制了代码评审流程。

3.2 如何查看某个分支是从哪个分支拉出来的

“GitLab如何查看某个分支是从哪个分支拉取的”这个热搜词出现的频率挺高,我一开始有点意外,后来想想确实是个很实际的痛点。你接手一个老项目,看到几十个分支,根本分不清谁是谁的爹。查分支来源,我常用的命令有这几条。

# 查看分支的最近共同祖先与合并基础 git merge-base --fork-point feature/login dev # 查看包含关系:dev分支是否已经包含feature分支的提交 git branch --merged dev # 最直观的方式:查看分支图 git log --graph --oneline --decorate --all

其中git merge-base --fork-point是我用得最多的,它可以直接找出两个分支是在哪个提交开始分叉的。举例来说,你执行git merge-base --fork-point feature/login dev,如果输出一个commit hash,再用git log -1 <hash>查看这个提交的信息,就能大致判断feature/login是从dev的哪个位置拉出来的。但说实话,Git本身并不会记录“某个分支是从哪个分支拉出来的”这个元信息,它只记录提交的父子关系和图结构。所以如果你真想做到可追溯,更靠谱的方式是在分支命名上就携带信息,或者在创建分支时用git push--set-upstream选项,让远端明确知道上游关系。

还有一个小技巧,适合查看分支生命周期中的分叉点:

# 显示两个分支从共同祖先以来的提交差异 git log --oneline --graph --left-right --cherry-pick --boundary main...feature/login

这个命令在评审Merge Request之前使用非常方便,能清晰看到feature分支相对于main做了哪些改动,哪些是cherry-pick过来的,哪些是冲突的。

3.3 合并请求与代码评审:把好质量关口

GitLab把Pull Request叫Merge Request,简称MR。MR机制不只是代码合并的工具,更是团队做代码评审的抓手。我强烈建议所有团队不管人多人少,都养成“改动必须走MR”的习惯,哪怕只有你自己一个开发,MR也能让你在合并前重新审视一遍diff,很多低级错误就是在这一关被拦住。

创建MR的入口很简单,推送分支之后GitLab会在项目页面提示你创建MR,或者点击Merge Requests -> New Merge Request选择源分支和目标分支。创建时建议把模板写好:背景说明、改动范围、测试情况、关联Issue。这些信息看起来繁琐,但三个月后你回看历史MR时就会感谢自己写清楚了。

GitLab内置的评审功能其实很全面。你可以在diff的每一行发起评论,作者可以回复或标记为已解决;评审人还可以在最后通过Approval功能正式批准合并。在项目设置中可以配置最少需要几个Approval才能合并,这对核心主干分支特别有用。我曾经的团队就设置成main分支必须至少2人Approval才能合并,虽然执行初期大家不太适应,但后面代码质量肉眼可见地提升了。

4. CI/CD流水线:从代码提交到自动部署

4.1 先理解CI/CD的整体结构

GitLab CI/CD是我认为它相对其他代码托管平台最有竞争力的功能。你只需要在仓库根目录放一个.gitlab-ci.yml文件,GitLab就会自动识别并调度构建任务,完全不需要额外的配置服务。

先理清几个基础概念。Runner是执行任务的代理程序,它可以是独立的服务器、虚拟机、容器甚至你的笔记本,GitLab将job任务分发给Runner执行,再把结果上报回GitLab。Pipeline是一整条流水线,由多个stage组成,比如常见的build -> test -> deploy;stage下面有具体的job,一个job就是一组命令。.gitlab-ci.yml的核心就是定义这些stage和job的关系。

Runner需要在每个项目中注册,注册时需要GitLab实例提供的注册token。你可以在Admin Area -> CI/CD -> Runners里看到这个token和注册命令。安装Runner也很简单,Linux下通常是一个RPM包加两行命令:

# 安装GitLab Runner sudo rpm -i gitlab-runner_16.10.2-1_amd64.rpm # 注册Runner,交互式问答模式 sudo gitlab-runner register

注册时长按你的环境选。shell执行器最简单,直接在Runner所在机器上执行命令;docker执行器更弹性和隔离,每个job都在独立的容器中运行,这也为后面Docker镜像构建提供了便利环境。我个人推荐团队用docker执行器,因为构建环境可以完全由Dockerfile定义,一次配置到处运行。

4.2 Docker镜像构建与自动化部署的完整实践

在GitLab CI/CD中构建Docker镜像并部署,是我觉得最有实际价值的一块内容,也是很多团队还没打通的关键环节。

先讲构建。我们知道在CI里执行docker build需要Docker环境,如果你的Runner用的是shell执行器,直接装Docker就行。但更标准的方式是用docker执行器,然后在job中挂载Docker的socket实现“DinD”(Docker in Docker),这样job内部就有独立的Docker环境了。

下面是一套我实际使用的.gitlab-ci.yml模板,实现了“代码推送后自动构建镜像并推送到镜像仓库,然后通过SSH登录服务器拉取镜像并重启容器”的完整链路:

stages: - build - deploy variables: IMAGE_NAME: registry.example.com/myteam/myapp IMAGE_TAG: $CI_COMMIT_SHORT_SHA build_image: stage: build image: docker:24.0.7 services: - docker:24.0.7-dind before_script: - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin registry.example.com script: - docker build -t $IMAGE_NAME:$IMAGE_TAG . - docker push $IMAGE_NAME:$IMAGE_TAG only: - main deploy: stage: deploy image: alpine:3.19 before_script: - apk add --no-cache openssh-client - eval $(ssh-agent -s) - echo "$DEPLOY_SERVER_PRIVATE_KEY" | ssh-add - script: - ssh -o StrictHostKeyChecking=no root@$DEPLOY_SERVER_HOST " docker pull $IMAGE_NAME:$IMAGE_TAG && docker stop myapp || true && docker rm myapp || true && docker run -d --name myapp -p 8080:80 $IMAGE_NAME:$IMAGE_TAG " only: - main when: manual

这套流程里几个细节我认为值得单独说明。$CI_COMMIT_SHORT_SHA是GitLab预定义变量,代表当前提交的短哈希,用它做镜像tag可以保证每个提交对应的镜像都是唯一的,部署之后也能精确定位是哪个代码版本在运行。when: manual让部署这一步需要人工点击执行而不是自动触发,这个策略适合生产环境,避免代码一推就自动上线导致来不及检查。如果你对自动化有信心,可以把when: manual去掉,改成自动执行,但部署前一定要有完善的回滚方案。

这个过程中最容易出问题的地方是Docker in Docker的共享资源冲突。多名开发者同时推送代码触发多个pipeline并行时,如果它们共享同一个dind服务,偶尔会有镜像层缓存冲突。解决办法是给每个job配置独立的service或者为每个pipeline使用独立的命名空间,GitLab在较新版本中对dind的支持已经好了很多,不再需要格外担心。

4.3 GitLab与Jenkins集成:经典组合不踩坑

虽然GitLab自带CI/CD已经很强,但很多公司旧的CI系统是Jenkins,短时间内不会完全迁移。于是“GitLab加Jenkins”就成了一个非常常见的过渡组合,大家在搜索引擎里频繁搜“jenkins配置gitlab connection”也印证了这一点。

Jenkins与GitLab集成,核心是两件事:一是Jenkins能拉取GitLab仓库的代码;二是GitLab能触发Jenkins任务。

拉取代码很简单,在Jenkins源码管理里填GitLab的仓库地址,再配置好凭据(Credentials)即可。复杂的是触发联动,通常我们通过GitLab的Webhook实现:在GitLab项目里创建Webhook,把Jenkins的地址(如http://jenkins.example.com/project/myjob)填进去,配置触发事件为Push或Merge Request,代码变化时GitLab就会通知Jenkins执行构建。

实际集成时,我踩过一个非常普遍的坑,报错信息是:

gitlab login failed. check api token or gitlab version. log in via git if the version is too old

这个报错基本集中在Jenkins的GitLab Plugin配置API Token那一环节。它翻译一下就是说:Jenkins尝试用API Token调用GitLab API时认证失败。常见原因有三个:Token配置错误、GitLab账号被禁用、GitLab版本过老导致API端点不兼容。解决办法是:在GitLab里创建一个专门的账号(或者用管理员账号),在用户设置中生成Personal Access Token,权限至少需要apiread_repository,把token完整复制到Jenkins的GitLab Connection配置里。如果GitLab版本确实很老(比如10.x之前),建议先升级GitLab,因为老版本的API接口已经不再被新版Jenkins插件兼容。

4.4 Runner异动与Pipeline卡住:几个急救手段

CI/CD用久了总会有各种意外,最常见的是Pipeline卡在pending状态迟迟不执行,这通常意味着没有可用的Runner,或者Runner的tag和job的tag不匹配。你可以在项目页面的CI/CD -> Runners里检查是哪些Runner处于在线状态。如果Runner是离线的,去Runner所在机器执行sudo gitlab-runner status查看服务是否正常,再查看/var/log/gitlab-runner日志定位原因。

另一个常见问题是构建环境缺依赖,导致每次跑都报同样的错。我的建议是尽量把所有环境准备都写进Dockerfile或runner镜像里,而不是在job里临时装。比如我经常让前端项目的job基于一个预装了node和pnpm的镜像,这样构建时间能大幅压缩,同时避免了公网软件源不稳定带来的概率性失败。

5. 周边工具联动:IDE、K8s与日常开发效率

5.1 设置kubectl配置文件访问Kubernetes集群

GitLab和Kubernetes的组合已经成为现代云原生部署的标准姿势。很多团队已经把GitLab CI/CD与K8s打通,实现代码提交后自动构建镜像并滚动更新集群内应用。而要在本地或CI环境中访问Kubernetes集群,第一步就是配置kubectl。

kubectl访问集群依赖一个config文件,默认路径是~/.kube/config。如果你用的是云厂商的托管K8s服务,一般可以在控制台直接下载kubeconfig。自建集群则通常由管理员生成并分发文件。这个文件里包含了集群API Server的地址、证书和用户凭据,本质上就是一把打开集群的钥匙,所以文件权限非常敏感,GitLab CI里使用它时要格外小心。

在GitLab的CI/CD流水线中使用kubectl的典型做法是把kubeconfig内容设置为项目的CI/CD变量,然后在job中动态生成配置文件:

deploy_to_k8s: stage: deploy image: bitnami/kubectl:latest script: - mkdir -p $HOME/.kube - echo "$KUBE_CONFIG" | base64 -d > $HOME/.kube/config - kubectl apply -f k8s/deployment.yaml - kubectl rollout status deployment/myapp -n production

这里把kubeconfig用base64编码后存到GitLab的变量中,job执行时再解码回文件。注意变量的类型要选FileVariable,且不要在日志中打印base64内容,否则等于把集群钥匙公开了。我见过不止一次有人把kubeconfig直接提交到仓库里,这种做法如果是个人的临时集群还好,团队集群的话基本等于直接送管理员权限,非常危险。

5.2 IDEA、VS Code与SourceTree的日常集成

日常开发工具上的GitLab集成,直接影响开发效率。现在主流的IDE都原生支持GitLab的仓库操作,包括clone项目、创建分支、提交代码、查看MR、甚至浏览CI流水线。

IDEA的配置很简单:Settings -> Version Control -> Git里配置好Git可执行文件路径,然后在Settings -> Version Control -> GitHub或直接通过File -> New -> Project from Version Control粘贴GitLab仓库地址完成clone。IDEA可以从GitLab拉取项目列表,需要安装GitLab插件并配置访问token。插件安装后在Settings -> Tools -> GitLab填入你的GitLab地址和Personal Access Token,就能直接在IDEA里浏览和clone你可见的仓库了。

VS Code同样支持GitLab扩展,配置方式类似,在扩展市场搜索GitLab Workflow,安装后在设置中填写GitLab实例地址和token,就可以在侧边栏浏览合并请求、GitLab CI流水线状态。

SourceTree是很多Windows用户的图形化Git客户端。用SSH方式连接局域网Git服务器时,重点在于SSH密钥的配置。SourceTree内置的SSH客户端可能和系统ssh-agent冲突,我建议在SourceTree设置里改为使用系统Git自带的OpenSSH,这样你的密钥配置一次,命令行和SourceTree都能共用。如果连接时提示SSH认证失败,多半是密钥格式不被识别,或者没有正确添加到Pageant(PuTTY的认证代理)。具体办法:把id_ed25519私钥导入Pageant,SourceTree的认证选择PuTTY,然后重新测试连接。

这里还要说一个实操细节:很多人在本地配置了多套SSH密钥,比如一个用于GitLab,一个用于个人仓库。SSH默认读取~/.ssh/id_rsaid_ed25519,如果密钥文件名不是默认的,需要通过~/.ssh/config文件显式指定:

Host gitlab.example.com HostName gitlab.example.com User git IdentityFile ~/.ssh/id_ed25519_work IdentitiesOnly yes

这样ssh在连接特定Host时会使用指定密钥,不用来回切换或反复覆盖默认密钥文件。这个问题看似简单,但困扰过不少同事,我还专门写了个小工具把常用Host的配置自动生成。

6. 安全加固与常见问题排查:维护一个稳定的GitLab

6.1 高危漏洞修复:升级是第一优先级

GitLab这类DevOps平台一旦有高危漏洞,影响面往往不只是代码托管本身,CI/CD的权限扩大、多项目项目管理等特性都方便了攻击者横向移动。近年来GitLab多次公开过高危漏洞,包括SSRF、任意文件读取、RCE等。修复方案里最重要的一条永远是:保持GitLab版本更新。

升级本身不需要过于担心。Omnibus包和Docker镜像都支持直接升级,但在升级前务必查看官方升级路径图,大版本跨版本升级时可能需要先升级到中间版本再继续,直接跨大版本跳着升级会报错甚至导致数据损坏。这是不少人踩过的坑。我自己的习惯是:每两个月看一次官方release notes,有小版本就顺手升,避免年久失修需要大跨度升级的尴尬。

除了升级版本,安全加固还应该包括:

  • 关闭开放注册(开启需要管理员审批,或限定企业域名注册)
  • 强制开启2FA(至少对管理员账号强制)
  • 为Runner设置专用的受限账号,不推荐用root跑job
  • 对公网暴露的GitLab配置HTTPS并定期更新证书
  • 设置备份策略,备份文件加密并异地存储

备份这块值得单独提醒一下。GitLab的备份命令是gitlab-backup create,Docker安装对应的命令是docker exec -t gitlab gitlab-backup create。备份只会备份数据库和Git仓库文件,配置文件(/etc/gitlab/gitlab.rb)和密钥(/etc/gitlab/trusted-certs)需要额外手动备份。也就是说,你只做备份命令是远远不够的,必须连配置文件一起打包保存,否则机器挂了之后恢复会非常痛苦。我一般会写一个定时任务,同时备份数据、配置和环境变量文件,放到远程存储上。

6.2 常见报错排查速查表:踩过的坑都在这里

日常使用GitLab报错不算少,而很多报错网上搜索出来的都是老版本的解决方案,不一定适配,这里把我自己实际踩过的常见问题整理成一个速查表,方便你对照处理:

报错或现象可能原因排查和解决方法
Host key verification failedSSH首次连接远程主机时没有确认指纹手动执行ssh-keyscan gitlab.example.com >> ~/.ssh/known_hosts,或临时加-o StrictHostKeyChecking=no
Permission denied (publickey)SSH私钥没被正确识别或公钥未添加到GitLab检查密钥文件名是否被ssh-agent加载:ssh-add -l;用ssh -T git@gitlab.example.com -v调试详细原因
fatal: repository not found项目地址不对或无权限确认仓库URL大小写完全一致;检查当前SSH key是否有该项目的访问权限
Email has already been taken注册时邮箱被占用先用该邮箱尝试登录,不记得密码就走找回密码流程;自建系统可联系管理员处理
GitLab is taking too much time to respond服务器负载过高或数据库异常查看gitlab-ctl status检查各组件状态;查看/var/log/gitlab/gitlab-rails/production.loggitlab-ctl tail
Pipeline一直pendingRunner离线或没有匹配tag去项目的Runners页面检查Runner在线状态,确认job的tag与Runner的tag匹配
docker: command not found执行器环境没有Docker如果用的docker执行器,确认job里写了image: docker:latest并启用了dind service;如果shell执行器,确认Runner主机的Docker已安装且当前用户有权限

我在实际操作中还有一个经验:连接GitLab报错时不要只看报错的第一行。比如fatal: unable to access 'http://...': The requested URL returned error: 502,这个报错背后可能是GitLab反向代理配置错误、unicorn/nginx崩溃、甚至是磁盘满了导致的写操作异常。所以排查务必朝两个方向铺开:一看GitLab自身的health check(访问服务器的/-/health路径),二看磁盘空间和内存占用,许多GitLab异常最终都跟资源不够有关。

6.3 记录一次真实的故障恢复:磁盘写满引发的连锁问题

想分享一次我印象很深的故障处理经历。有一次公司GitLab突然大面积报错,所有人都无法访问项目,首页转圈很久才打开,打开后提示500错误。

我第一反应是看服务状态,gitlab-ctl status显示各组件均正常,但gitlab-rails所在的端口迟迟没有响应。又继续看日志,结果发现/var/log/gitlab日志目录里有一堆磁盘写入失败的记录。再执行df -h才反应过来,根分区已经100%占满。

排查下去发现,罪魁祸首是CI流水线产生的构建缓存和日志文件,日积月累把磁盘吃满了。这次故障持续了大概40分钟,处理方式也很暴力:清理无用的容器镜像和构建缓存,删除旧的备份文件,再给日志目录配置了logrotate。之后我在CI中加入了定期清理任务,同时对磁盘监控加了告警,只要使用率超过85%就自动通知,从那之后再也没有出现过这类“突然挂了但不知道为什么”的情况。

这里也想提醒一下:GitLab跑久了,占空间的不只有Git仓库本身,还有CI缓存、构建产物、容器镜像、系统日志。很多人只关注仓库大小,忽视这些“隐藏胖子”,结果磁盘告警来得猝不及防。建议每隔一段时间用du -sh /srv/gitlab/*这类命令扫描一下各个目录的空间占用,把不需要的旧镜像和过期构建产物及时清掉。

写在最后:一点个人体会

经过这么多年的使用和部署维护,我认为GitLab最被低估的功能其实是它的“全流程集成能力”。很多人把它当成一个存代码的地方,但实际上它把代码托管、分支管理、代码评审、CI/CD、制品库、Kubernetes部署串联成了一个闭环。只要你愿意花时间去理解它的设计逻辑,工作效率的提升会非常明显。

如果你是从零开始接触GitLab,我的建议是分三步走:第一步,先不管CI/CD,把代码托管、SSH、分支管理这些基础功能用熟练,保证团队日常协作流畅;第二步,部署Runner,把最简单的构建任务跑起来,慢慢理解流水线的概念;第三步,再逐步尝试镜像构建、自动部署、K8s联动这些进阶玩法。不要想着一步到位,GitLab功能太多,一次装完反而容易迷失。

还有一点,如果你用的是Docker部署并长期使用,一定要尽早配置好监控。GitLab不像普通Web应用那样挂了重启就好,它的内部状态牵扯到数据库、Redis、Gitaly等组件,出问题以后排查成本不低。提前做好磁盘告警、探活检查和备份演练,是对自己负责,也是对整个团队负责。

如果你能把上面这些内容消化掉,再跟着操作走一遍,我相信你已经能从“听说GitLab”进化到“能独立维护一套GitLab平台”的程度了。后面如果再遇到具体问题,欢迎带着报错信息来找我交流,GitLab的坑确实多,但一个个踩过去,也就慢慢熟了。

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

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

立即咨询