首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
GitLab CI/CD从零到一:核心概念与实战配置指南
📅 2026/10/23 23:12:17
✍️ 爱科研究院
👁 阅读 3,247
直接跑题了扯概念没用先把GitLab怎么用跑通才是正事。这个系列写到第九篇前面的内容一直在讲持续集成、持续交付的理论从这篇开始落到具体工具上。我选GitLab作为第一个实操对象原因很直接它把代码仓库、CI/CD执行器、制品管理、环境管理全塞进了一个平台里中小团队想搭一套完整的持续交付链路GitLab几乎是上手成本最低的选择。这篇就围绕一个主题——“gitlab简单使用”把我自己从零开始接入GitLab CI/CD的完整过程、配置细节、踩过的坑整理出来适合刚接触CI/CD、想快速落地一套流水线的人参考。1. 先搞清楚GitLab在CICD链路里到底扮演什么角色1.1 为什么第一步是GitLab而不是别的工具很多人在做CI/CD选型时第一反应是Jenkins、GitHub Actions、CircleCI这些GitLab反而不在首选名单里。但如果你把整条链路拆开看GitLab的优势就非常明显了。它本身是一个代码托管平台天然拥有代码的“上游”——也就是仓库。代码从提交到触发构建再到跑测试、出产物、部署整个过程中的数据源、触发源都在GitLab手里捏着。这意味着你不需要像Jenkins方案那样单独部署一套服务、配置Webhook、维护节点和插件也不需要在不同平台之间来回切换上下文。我自己的经历很能说明问题。早前在某个小团队某创业公司里代码托管用的是某代码托管平台CI用Jenkins两者靠Webhook联动。听起来没什么问题但实际维护会发现很多碎片化的工作Webhook偶尔断掉不通知了、Jenkins凭证过期了、构建节点上的环境和开发机不一致……每一次排查都要在两个系统之间来回跳。后来换到GitLab一家人方案之后这些跨系统的问题直接消失了一半——不是问题不存在了而是被统一到了同一个平台、同一套权限体系、同一个配置模型里。对于团队规模不大、没有专职运维、又想迅速把CI/CD落地的场景GitLab这样的一体化平台是最省心的起点。这也是我把这一篇定位为“简单使用”的原因——它的核心使用门槛不在于功能复杂而在于你需要理解它那一套配置模型和几个关键概念理解之后日常场景覆盖足够了。1.2 从代码提交到部署数据是怎么流动的先把整条链路在脑子里过一遍。开发者在本地提交代码推送到GitLab仓库GitLab检测到分支上的新提交根据仓库根目录下的.gitlab-ci.yml文件内容GitLab创建一个Pipeline流水线流水线被分配给某个空闲的Runner执行器Runner去仓库拉取代码在隔离环境中按配置依次执行构建、测试、部署等任务任务执行结果和日志回传到GitLab显示在页面上。整个流程不需要你手动点任何按钮。这条链路里有一个关键点GitLab本身不执行任何构建任务它只负责“调度”和“展示”。真正干活的是RunnerRunner可以装在任何机器上——本机、服务器、容器、Kubernetes集群都可以。GitLab把任务通过HTTPS长连接推给RunnerRunner执行完再把结果上报回来。这个模型的灵活之处在于Runner是注册制的你可以注册多台Runner它们可以跑在不同环境里构建、测试、部署各用各的执行器完全解耦。理解了这条数据流后面所有配置都围绕一个核心文件展开——.gitlab-ci.yml。这个文件放在仓库根目录定义了在什么时机、用什么环境、执行哪些命令。接下来的章节全部围绕它展开。2. 上手前必须吃透的5个核心概念2.1 项目Project不只是代码仓库GitLab里的Project和你想的“一个代码仓库”不完全一样。它既有代码存储的功能也包含了Issues、Merge Request、Milestone、CI/CD配置入口、制品仓库等一系列配套设施。你可以把它理解为一个“软件交付单元”代码、协作、自动化构建都在同一个Project上下文里天然地拥有了统一的权限模型。这一点在实际使用中非常有用。比如你要限制谁可以推代码、谁可以合并、谁可以触发部署直接配置Project的成员角色就行。再比如部署环境变量、部署密钥、Runner绑定关系也都是挂在Project级别的。相比“代码仓库只管代码、CI平台另管一套权限”的模式这种设计会把很多安全问题前置解决掉。创建项目很简单登录GitLab后点“新建项目”选择“创建空白项目”填名称和可见级别就行。不过我建议从一开始就考虑好可见性。内部项目意味着公司内任何人可看可拉私有项目则只对指定成员开放。中小团队建议用内部项目避免后面因为权限问题反复调整。2.2 Runner真正干活的执行者Runner是整条流水线里唯一真正跑脚本的组件。它有三种作用域共享Runner整个GitLab实例中的所有项目可用、分组Runner某个分组下的项目可用、项目Runner只服务于指定项目。共享Runner适合个人折腾、统一维护生产环境建议优先用分组Runner或项目Runner隔离性更好。Runner本身需要安装在某个环境里安装后通过注册与GitLab服务器建立连接。注册时你会拿到一个URL和一个Token这个Token在GitLab项目页或管理员页面里可以找到后面实操章节我会细讲。Runner有几个重要的运行配置concurrent允许同时执行的任务数默认值是1意味着同一时间只跑一个任务其他任务排队。executor执行器类型决定了脚本在什么环境里跑常见的有shell直接在Runner宿主机上执行和docker每次拉镜像起容器执行。tagsRunner的标签注册时可以指定任务配置里写哪个标签就分配给带这个标签的Runner。这条Tag机制是整个调度里最容易踩坑的地方。任务写得再复杂Runner的tag对不上Pipeline就永远卡在pending状态。后面排查章节我会再强调一次。2.3 Pipeline与Stage流水线的骨架一次提交触发的完整流程叫Pipeline。Pipeline由若干Stage阶段组成Stage之间是串行关系前一个阶段全部成功后后一个阶段才开始。每个Stage内部可以并行执行多个Job任务。典型的阶段设计是build - test - deploy构建完成后才跑测试测试通过后才能部署。这种流水线设计本质上是在约束“顺序”。构建、测试、部署天然有先后依赖乱序执行没有意义。同时它也在提供“并行度”——一条流水线里同一个Stage的多个Job可以并行跑比如不同模块的单元测试可以同时执行节省大量时间。关于如何判断一个Pipeline是不是健康我的经验是看哪个Stage经常失败、哪个Job总是排队比看整体绿不绿更有价值。总绿色不代表没问题——有可能你压根没把关键测试放进去。2.4 Job最小的执行单元Pipeline里的每个Job是真正会去执行脚本的最小单元。Job必须包含至少一个script——也就是一串Shell命令。它还可以指定自己的imageDocker镜像、stage所属阶段、tags匹配的Runner标签、only/except执行条件、artifacts产物保存等属性。如果只用一句话记住Job的定义Job 一个名字 在哪个阶段执行 要跑什么命令 可选的其他行为限制。这就好比流水线上的一个工位名字是这个工位的编号阶段是这个工位位于第几道工序命令是这个工位具体做什么操作。理解了这一层gitlab-ci.yml里的配置就没有任何神秘感了。2.5.gitlab-ci.yml整个流水线的唯一入口GitLab的CI/CD配置全部集中在一个YAML文件里.gitlab-ci.yml。GitLab在代码提交后自动识别仓库根目录的这个文件根据文件内容生成对应的Pipeline。所以你在GitLab页面上能操作的一切CI相关动作本质上都在“编写/修改这个YAML文件”——页面操作只是结果展示。一个最小的完整示例如下stages: - build - test build-job: stage: build script: - echo Building... tags: - my-runner test-job: stage: test script: - echo Testing... tags: - my-runner这个配置声明了两个阶段、两个Job。提交后就能看到一条流水线先是build-job完成后再跑test-job。这里面最值得注意的一个细节是Stage串行同Stage的Job并行。也就是说如果test阶段有10个Job它们会一起排队并行执行而不是挨个跑完再跑下一个。3. 从零配置一套能跑的GitLab CI/CD3.1 安装Runner并完成注册Runner的安装可以分两种场景第一种是装在个人电脑或服务器上自给自足第二种是装进Kubernetes集群里按任务动态创建Pod。这里只讲最简单、最适合起步的shell执行器和docker执行器的安装注册过程。以Linux服务器为例安装Runner的步骤大致如下# 下载GitLab Runner二进制根据你的系统架构选择版本 sudo curl -L --output /usr/local/bin/gitlab-runner \ https://packages.gitlab.com/runner/gitlab-runner/packages/el/8/gitlab-runner-16.0.0-1.x86_64.rpm chmod x /usr/local/bin/gitlab-runner # 创建GitLab CI用户 sudo useradd --comment GitLab Runner --create-home gitlab-runner \ --shell /bin/bash # 安装并启动服务 sudo gitlab-runner install --usergitlab-runner sudo gitlab-runner start安装完成之后需要做一步至关重要的操作——注册。注册前要先去GitLab项目页面拿两个关键信息GitLab实例的URL和注册Token。路径是项目 → 设置 → CI/CD → Runner → 展开“项目Runner”区域。拿到之后执行注册命令sudo gitlab-runner register \ --url http://你的GitLab地址/ \ --token 粘贴你的注册Token \ --description my-first-runner \ --executor shell \ --tag-list my-runner执行完这个命令Runner就接入GitLab了。回到页面上刷新一下Runner状态会从灰色变成绿色说明连接正常、可以接收任务。这里有一个非常关键的细节执行器选择要慎重。shell执行器直接在Runner宿主机上执行命令适合初期练习但宿主机环境会被任务脚本污染——你装了Java另一个任务可能覆盖了Python版本跑依赖安装可能改了全局的包管理状态。docker执行器则每次拉取指定镜像、起一个干净容器、执行完销毁隔离性非常好。我个人的建议是如果机器上能装Docker就直接用docker执行器不要用shell执行器走弯路。docker执行器的注册只需要把--executor shell换成--executor docker再加一个--docker-image alpine指定默认镜像即可成本几乎一样换来的隔离性差别巨大。3.2 第一个Pipeline构建、测试、部署一条龙Runner就绪之后接下来就是写.gitlab-ci.yml了。一个标准的、可落地的三段式流水线我直接给出一份能跑通的参考配置stages: - build - test - deploy variables: APP_NAME: demo-app APP_ENV: staging build-job: stage: build image: node:18-alpine script: - npm ci - npm run build artifacts: paths: - dist/ tags: - my-runner test-job: stage: test image: node:18-alpine script: - npm ci - npm run test:unit tags: - my-runner deploy-job: stage: deploy image: alpine:latest before_script: - apk add --no-cache openssh-client script: - scp -r dist/ userserver:/var/www/html/ only: - main tags: - my-runner这份配置对应了三个关键思想一是依赖安装和构建命令都放在Docker容器里执行宿主机保持干净的运行环境二是artifacts参数把构建产物dist/保留并传给后续Job使用三是部署这个 Job 加了only: main也就是只有主分支提交才会触发部署日常功能分支不会误报——这是规范里非常重要的一个分支策略。部署命令里出现了before_script这个字段会在Job的script之前执行适合放一些准备工作比如安装SSH客户端、加载环境变量。整个Job的运行分三步先是拉镜像再执行before_script里的准备命令最后执行script里的核心命令。3.3 常用关键词和它们的行为边界配置写多了你会发现.gitlab-ci.yml的关键词其实就十几二十个但组合起来能表达的行为千变万化。我把自己日常使用频率最高的几个列出来配合说明和注意事项关键词作用注意事项stages定义所有阶段及执行顺序必须保持自上而下的依赖顺序scriptJob要执行的命令列表任意一条命令返回非0Job即失败before_script在script之前执行的准备命令常用于安装工具、加载密钥after_script即使Job失败也会执行常用于收尾清理、归档日志image指定执行任务时使用的Docker镜像仅在docker执行器下生效tags指定分配给带对应标签的Runnertag不匹配Job会一直pendingonly/except控制Job在何种条件下执行支持分支名、标签、合并请求等rulesonly/except的增强版支持更复杂的条件表达式与whenartifacts保存Job产物并传递给下游Job默认保留时间有限需按需设置cache缓存依赖目录加速后续Job和artifacts有区别容易混淆variables定义变量供所有Job使用支持API方式动态注入when控制Job执行结果的处理策略如on_success、on_failure、alwaysretry失败后自动重试指定次数适合偶发抖动型任务timeout限制Job最大执行时间防止任务卡住无限占用Runner这里重点讲两个最容易混淆的cache和artifacts。很多初学者分不清它们。简单说artifacts是“成品”——构建出来要传给下一个Job或者供下载的产物cache是“半成品”——为了下次构建更快把依赖目录临时缓存下来。它们都占用Runner机器的存储空间但生命周期不同。artifacts默认在Pipeline结束后保留几天cache则按缓存策略在后续Pipeline中复用。别把不该缓存的大文件放进cache磁盘会被撑爆的。rules是较后期版本引入的增强用法可以取代大部分only/except的场景。举个典型例子deploy-prod: stage: deploy script: - echo Deploy to prod rules: - if: $CI_COMMIT_BRANCH main when: manual - when: never这段配置的意思是只有主分支才会出现这个Job并且是需要手动点击执行其他分支根本不创建这个Job——用when: never直接跳过。这种写法在需要“人工确认再上生产环境”的流程里非常实用。实际部署场景里我几乎不会让代码推送到某个分支就直接自动上生产而是会加这样一步手动确认环节。4. 实测下来最常见的坑和排查手册4.1 Runner状态与Job排队的隐蔽问题最糟糕的情况不是任务失败而是任务压根没开始跑——Pipeline一直停留在pending状态。这是GitLab CI/CD新手遇到最多的问题没有之一。排查思路按优先级排列如下看Runner有没有绿色在线项目页面 → 设置 → CI/CD → Runner如果Runner是灰色说明它和GitLab的连接断开了去Runner所在机器看看服务是否存活。看Job用了什么tagsJob配置里写了tags: [xxx]但Runner注册时没给那个tag队列就永远匹配不上。这个错误在本地可以把Runner不设tags把Job的tags字段也去掉或者注册Runner时把tag写成一致的字符串。看并发满了没有Runner配置里concurrent 1之前有条任务卡住了还没结束后面所有任务都排队。可以临时把并发数调大或者去页面取消那些卡住的任务。看Runner是否处于暂停状态页面上的 Runner 如果带“暂停”标识需要恢复。多见于手动误操作或部署时的自动维护。我这个排查顺序是按照出现概率排的tags不匹配最多Runner离线其次并发占满第三。4.2 脚本执行环境的隐性不一致在本地跑得好好的命令到Runner上就报错。这个问题在shell执行器下尤其常见。根因是本地环境是开发机Runner环境是服务器两者的Node版本、Java版本、系统包管理工具、环境变量、当前用户权限全部可能有差异。一个典型的例子是本地用nvm管理Node版本终端里自动加载而Runner以gitlab-runner用户执行任务压根没有nvm环境node命令找不到。要解决这个问题有两个方向。一是统一镜像环境用image: node:18-alpine指定Docker镜像跑任务镜像里的环境是固定不变的从源头消除环境差异。二是让Runner用户环境干净化在Runner机器上为CI单独创建用户只装必要的工具不继承开发机上的任何默认配置。我强烈建议前者——一旦流水线完全容器化本地环境怎么折腾都不影响任务结果。还有一个隐蔽问题当前用户权限。Runner以独立用户运行时有些目录没有写权限。比如npm全局安装、写入/usr/local、读取/root/.ssh等都会失败。我的做法是给CI用户授予只针对构建目录的写权限部署阶段依赖的SSH密钥单独放到Runner用户自己的目录下不要把root用户的密钥复制过去。权限最小化原则同样适用于CI系统。4.3 部署任务里SSH认证失败部署场景里需要一个从Runner到目标服务器的SSH通道这个环节最容易出问题。先解释一个概念如果Runner是Docker执行器它每执行一次任务都是新起的容器容器里可能连SSH客户端都没有更不用说存放密钥。所以部署任务通常要做三件事安装SSH客户端、准备密钥文件、修改密钥权限。我在部署任务里用的是这样的组合deploy-job: stage: deploy image: alpine:latest before_script: - apk add --no-cache openssh-client - mkdir -p ~/.ssh - echo $SSH_PRIVATE_KEY ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - ssh-keyscan -H server-host ~/.ssh/known_hosts script: - scp -r dist/ userserver-host:/var/www/html/这段配置里三个细节值得说明。密钥来自$SSH_PRIVATE_KEY这个变量——你在GitLab项目的“设置 → CI/CD → 变量”里录入录入时勾选“保护”和“掩码”这样密钥内容不会在日志里直接暴露。chmod 600是必须的OpenSSH对密钥文件权限要求严格权限太松会直接拒绝使用。ssh-keyscan把目标服务器指纹写入known_hosts避免首次连接时的交互式确认——非交互环境里这个确认会直接导致任务失败。实际发生过刚配置好的密钥连接失败看日志报Permission denied (publickey)排查下来发现是变量里的密钥尾部多了个换行GitLab变量保存时有时候会带上看不见的字符。处理方式是录入密钥前先查看一下内容确保首尾没有多余空白行。这是真实踩过的坑。4.4 构建缓慢和缓存策略误区任务跑起来了也没报错就是慢。这时候大部分人第一时间想到加Runner机器配置但更常见的原因是依赖安装每次都重复执行、网络拉取慢、或者Docker镜像每次都重新拉取。我的判断顺序是检查是不是每次都重新拉了个很大的基础镜像。Docker执行器每次任务都要拉取image如果用的是node:18这种几百MB的镜像每一个Job都要消耗时间。可以在Runner配置里加pull_policy调整策略或者使用本地已有缓存。检查依赖缓存是否生效。npm ci比npm install更严格也更快。缓存方面给依赖目录配上cache可以明显加速cache: key: $CI_PROJECT_NAME-$CI_COMMIT_REF_SLUG paths: - node_modules/这里要注意一点这个cache的key改成带分支名的形式不同分支之间不会互相污染缓存。如果不加key默认情况下同一个项目所有Job共享缓存很容易出现“A分支改了依赖B分支受牵连”的怪问题。看网络阻塞点。Docker Hub在大陆的访问速度慢是老问题解决方案是配置镜像加速器或者提前把镜像拉到Runner所在机器上再改成pull_policy: if-not-present让Runner优先用本地镜像。4.5 生命周期结束的Job为什么还在跑还有一种很迷惑的现象在GitLab页面上手动点了Job的取消/停止但Runner机器的进程还在跑好像没收到停止信号。这通常是Runner版本和GitLab版本差异太大导致的兼容性问题。GitLab会尝试让Runner主动杀掉对应进程但如果Runner的版本太老实现不了这个信号机制。解决思路很简单保持Runner与GitLab版本接近。Runner的更新频率很高每次GitLab大版本升级后Runner也同步升一下级。我见过某团队长期不更新Runner结果某天升级GitLab后Runner直接连不上服务所有Pipeline报500错误。升级Runner很快一条命令的事sudo gitlab-runner stop sudo apt update sudo apt install --only-upgrade gitlab-runner sudo gitlab-runner start升级时间不要选在业务高峰期停Runner那几十秒内新的任务无法被调度正在执行的任务也会中断。升级前先在页面上把Runner暂停等所有排队Job清空后再停服务升级最后恢复上线。5. 一点实操心得GitLab CI/CD的“简单使用”可以总结成一句话先把Runner跑起来再写好.gitlab-ci.yml后面的细化都是锦上添花。但有几个原则是必须从一开始就立住的CI脚本永远给最小权限、Runner环境永远保持干净、密钥永远走GitLab变量而不是写死在仓库里。看似是技术规范背后其实是运维安全意识。每次遇到“本地可以怎么到Runner就不行”这类问题不急着怀疑GitLab先检查环境差异。环境不一致是CI里九成问题的根源。用Docker执行器把这些差异抹平多半就消停了。搭建这套流程时耐心点多观察几轮Pipeline日志很多问题在日志里写得明明白白比依赖记忆靠谱得多。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/23 23:12:17
containerd 1.7.23 安装配置与 CRI 排障指南
2026/10/23 23:12:17
Matlab多尺度法求解Duffing系统:从久期项消除到幅频响应与稳定性分析
2026/10/23 23:07:17
企业级代码准入白皮书:如何用确定性门禁扼杀大模型带来的隐性技术债务
2026/10/24 1:23:04
深度学习目标检测中的先验框解码:从偏移量到真实坐标
2026/10/24 1:23:04
STM32F722VE与PCA9422电源管理实战:动态调压与功耗优化
2026/10/24 1:23:04
QorIQ平台ONIE内核回移植:从4.14到5.15的驱动适配与设备树实战
2026/10/24 1:23:04
机器视觉镜头选型避坑指南:焦距、景深、接口与靶面匹配
2026/10/24 1:23:04
YOLO改进新思路:SCPP多尺度注意力模块替换SPPF提升小目标检测
2026/10/24 1:18:04
Matlab+CNN实现回归任务:从数据预处理到传感器寿命预测实践
2026/10/24 0:02:52
UDP协议实现局域网音量与关机控制:后台常驻与批量管理
2026/10/24 0:02:52
MATLAB缩进快捷键与批量格式化实战指南
2026/10/24 0:02:52
用Poco实现Qt风格信号与槽的跨平台C++方案
2026/10/23 0:52:23
OpenResearch实战:Claude Code、Codex、OpenCode、Cursor 工作流搭建与故障排查
2026/10/22 17:15:21
知识管理核弹:用Obsidian构建第二大脑的工程化方案(TaoToken 统一 Key 接入版)
2026/10/24 0:59:24
金融AI落地指南:从调研报告到技术选型与工程实践
2026/10/23 10:58:39
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/23 10:58:39
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/23 10:58:39
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)