ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GitLab CI/CD 配置实战:从环境准备到流水线落地

GitLab CI/CD 配置实战:从环境准备到流水线落地 一个项目从代码提交到上线中间隔着编译、测试、打包、部署这一大堆环节。以前靠人肉盯着提交一次代码就得手动跑一遍命令碰上多环境部署更是折磨。我最初接触CI/CD就是被这种重复劳动逼的后来花了两周时间把整套流水线从零搭起来才体会到什么叫“配置一次长期受益”。这篇内容就是围绕CI/CD配置展开的把从环境准备到流水线落地的完整过程拆开讲适合刚接触持续集成、想自己搭一套自动化构建流程的开发者参考。先说清楚一件事 CI/CD 不是某个单一工具而是一套把“源代码变成可运行服务”的自动化机制。CI持续集成解决的是“代码提交后能不能自动编译、自动测试”的问题CD持续部署/交付解决的是“编译好的产物能不能自动发布到目标环境”的问题。配置流水线的本质就是把原来需要手动执行的命令按照一定规则编排成自动化任务。理解了这个前提后面所有配置项才有意义。1. 内容整体设计与思路拆解1.1 先想清楚你要哪种流水线形态我在网上看到很多人一上来就搜“CI/CD 配置教程”然后照着某篇博客抄一个.gitlab-ci.yml或 Jenkinsfile结果换了个项目就跑不通。原因很简单不同类型的项目、不同规模的团队对流水线的需求是完全不一样的。第一类是你一个人开发项目结构简单比如一个前端单页应用或一个小型后端服务。这种场景不需要把流水线做得太复杂通常一条流水线包含“安装依赖 → 运行测试 → 构建产物 → 部署到测试服务器”就足够了。重点是快、稳、容易排查问题。第二类是团队协作多人频繁提交代码需要保证每次合入主干的代码都能通过自动化检查。这时候要在流水线里加入代码规范检查lint、单元测试覆盖率门禁、多环境部署策略。配置的复杂度会明显上升但换来的收益是“主干永远是绿的”谁也不会把坏代码合进去。第三类是复杂微服务架构涉及多个仓库、多个服务、依赖关系复杂。这种场景通常需要引入更高级的配置方式比如流水线模板化、多项目触发器、制品版本管理。说白了这时候 CI/CD 配置已经从“写脚本”变成了“设计一套工程体系”。我个人的建议是开始配置之前先花半小时回答三个问题——项目构建需要哪些步骤、哪些步骤可以并行、发布目标环境有哪几个。把这几个问题想透后面的配置文件基本就是按顺序填空。1.2 为什么选择自托管 CI/CD 而不是云服务现在市面上有现成的 CI/CD 云服务比如 GitHub Actions、CircleCI 等免费额度对个人项目基本够用。但为什么还有大量团队选择自己搭一套 GitLab CI/CD我的实际体验是三个字可掌控。云服务的优势是零维护push 代码就自动跑。但短板也很明显其一构建环境是黑盒你没法完全控制操作系统版本、预装软件出了问题排查起来很被动其二企业内网环境往往有安全策略代码和数据不允许传到外部平台其三并发构建数量受套餐限制团队一多就排队。自托管 GitLab Runner 搭配 Docker executor 是我推荐的首选方式原因也很纯粹几乎所有依赖都能通过镜像搞定Node.js、Java、Python 这些环境用官方镜像一键拉取版本随便切换。比如我今天用 Node 20 构建明天升级到 Node 22只需要改一行镜像标签完全不需要在宿主机上折腾环境变量。自托管还有一个隐形收益构建机和服务器在同一内网时部署延迟极低。云服务跑完流水线后要通过公网把产物传到你的服务器那个速度和服务稳定性用过的都知道有多痛。2. 环境准备把 CI/CD 的地基打牢2.1 Git 安装配置与代码仓库初始化整个 CI/CD 的起点不是 Runner而是代码仓库。我们常说的“触发流水线”本质上就是 Git 服务器收到 push 事件后发出的信号。所以先把 Git 环境配好是第一件正事。以 Windows 环境为例主流做法是下载 Git for Windows 安装包安装时基本一路默认即可。这里有一个很多人忽略的细节安装时“调整 PATH 环境变量”这一步建议选择 “Use Git and optional Unix tools from the Command Prompt”这样后续在 PowerShell 或 CMD 里直接用git命令不会报“无法识别”。装完之后要配置用户信息这是提交代码时用来标识身份的git config --global user.name 你的名字 git config --global user.email 你的邮箱还有一个重要的点是配置 SSH 密钥。因为 CI/CD 流水线中 Runner 需要拉取代码如果用 HTTPS 方式每次都得输入账号密码流水线跑不起来。正确做法是生成一对 SSH 密钥把公钥添加到 Git 平台的 SSH Keys 列表里ssh-keygen -t ed25519 -C your_emailexample.com生成后把~/.ssh/id_ed25519.pub的内容复制到平台的 SSH 设置页面即可。测试连接ssh -T gitgithub.com如果返回Hi username! Youve successfully authenticated说明配置成功。2.2 JDK / Maven / Node.js 环境配置要点虽然使用 Docker executor 后宿主机上装不装 JDK、Maven 都不影响流水线运行但说实话你总要在本地开发调试吧而且 Runner 宿主机上安装了这些工具后面做shell executor方式的流水线时会非常方便。我建议还是把基础开发环境装好。以 JDK 17 Maven 3.9 为例下载对应安装包后Windows 上需要设置三个环境变量JAVA_HOMEC:\Program Files\Java\jdk-17 MAVEN_HOMED:\apache-maven-3.9.6 PATH%JAVA_HOME%\bin;%MAVEN_HOME%\bin验证是否成功java -version mvn -v这里有个容易踩坑的点Maven 默认从中央仓库下载依赖国内的网络状况你们懂的慢得让人想摔键盘。解决办法是在 Maven 的settings.xml里配置阿里云镜像mirrors mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror /mirrors配完之后Maven 下载速度会有质的飞跃。这个配置同样适用于流水线内的mvn命令只要把settings.xml挂载或复制到构建环境里就能生效。Node.js 的安装相对简单去官网下载 LTS 版本安装包安装时勾选“Add to PATH”。装完之后验证node -v npm -v要注意的是 Node 版本和构建工具的兼容性。比如近几年很多项目要求 Node 18如果你的流水线用旧版本的node:16镜像构建大概率会在安装依赖时报错。这也是为什么我一直强调用 Docker 镜像管理构建版本而不是在宿主机上装一个固定版本。2.3 Docker Engine 安装与 GitLab Runner 注册Runner 是真正执行流水线任务的代理程序而 Docker 则是 Runner 用来隔离构建环境的工具。最推荐的方式是宿主机安装 Docker EngineRunner 注册为dockerexecutor。这样每次流水线都会拉起一个全新的容器来跑任务彻底杜绝环境残留。Docker Engine 的安装可以直接参考官方文档Ubuntu 系统用 apt 源安装即可。装完记得配置国内镜像加速否则后面拉取镜像时速度感人。修改/etc/docker/daemon.json{ registry-mirrors: [https://docker.m.daocloud.io] }然后重启 Docker 服务sudo systemctl restart dockerGitLab Runner 的安装分两步。第一步是安装 Runner 程序本身GitLab 官方提供了包管理器方式curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash sudo apt-get install gitlab-runner第二步是注册 Runner。注册时需要从 GitLab 项目的Settings → CI/CD → Runners页面拿到注册 token然后执行sudo gitlab-runner register交互式配置过程会依次问你 GitLab 地址、注册 token、Runner 名称、执行器类型等选择docker默认镜像可以填docker:20.10.16。注册成功后再看 GitLab 的 Runner 列表会看到这个 Runner 已经处于在线状态。有一个细节值得注意如果 GitLab 有多个项目可以给 Runner 打 tag然后在流水线里通过tags关键字指定用哪个 Runner 跑。比如你有两台构建机一台负责前端一台负责后端各自打上frontend、backend的标签流水线就能精确分流。3. 核心配置实操写出一份真正能跑的 GitLab CI/CD 配置3.1 流水线的骨架stages 与 jobs一份.gitlab-ci.yml文件放在项目根目录GitLab 收到 push 后就会按这个文件的内容执行流水线。最核心的构成要素是两个stages定义流水线阶段jobs定义具体任务。一个典型的三阶段流水线长这样stages: - build - test - deploy每个 job 必须至少包含script字段它是这个任务要执行的 shell 命令。一个简单的示例compile: stage: build script: - echo 开始编译... - mvn compile这里 job 的名字叫compile它属于build阶段。流水线的执行规则是同一阶段里的 job 默认并行执行不同阶段的 job 按顺序执行。这个设计让流水线的编排变得非常灵活。有些初学者会困惑为什么我的 job 没有按我写在文件里的顺序执行其实原因就在于阶段。如果两个 job 都属于同一个 stage它们就是并行的GitLab 默认不做先后约束。想要串行就分到不同 stage。3.2 完整示例一个 Java Spring Boot 项目的流水线用实际例子来看才有感觉。假设我们的项目是基于 Maven 的 Spring Boot 后端服务目标是将构建好的 jar 包部署到一台内网服务器上。我一开始写这份配置时踩过不少坑最终稳定运行的版本长这样stages: - build - test - package - deploy variables: MAVEN_OPTS: -Dmaven.repo.local/cache/.m2/repository DOCKER_REGISTRY: registry.cn-hangzhou.aliyuncs.com IMAGE_NAME: $DOCKER_REGISTRY/mynamespace/demo:$CI_COMMIT_SHORT_SHA cache: key: $CI_COMMIT_REF_NAME paths: - .m2/ build-job: stage: build tags: - backend image: maven:3.9-eclipse-temurin-17 script: - mvn compile only: - merge_requests - main test-job: stage: test tags: - backend image: maven:3.9-eclipse-temurin-17 script: - mvn test artifacts: paths: - target/surefire-reports/ expire_in: 7 days package-job: stage: package tags: - backend image: maven:3.9-eclipse-temurin-17 script: - mvn package -DskipTests artifacts: paths: - target/*.jar expire_in: 3 days only: - main deploy-job: stage: deploy tags: - backend image: docker:20.10.16 before_script: - docker info script: - docker build -t $IMAGE_NAME . - docker push $IMAGE_NAME only: - main这段配置里有几个关键设计逐一解释下。首先是cache配置。Maven 构建最耗时的部分就是下载依赖如果没有缓存每次流水线都从零下载跑一次要十分钟团队规模一大根本受不了。用cache关键字把.m2目录缓存下来只要依赖没变化后续流水线会直接复用本地仓库的 jar 包速度能提升一个量级。其次是artifacts配置。它把 job 产生的文件保存到 GitLab 服务器上可以在流水线页面直接下载。surefire-reports是测试报告target/*.jar是构建产物设置expire_in是为了防止 GitLab 服务器磁盘被历史产物撑爆。再就是only关键字。它控制 job 在哪些分支或事件下触发。merge_requests表示合并请求时跑编译和测试main表示主干分支推送时跑完整流程。这样设计的好处是日常开发推送到功能分支时不需要每次都构建镜像并部署节省了资源和时间。最后是deploy阶段的镜像选择。这里用了docker:20.10.16而不是 Maven 镜像因为部署阶段要执行docker build和docker push必须有一个带 Docker CLI 的环境。同时也意味着Runner 所在的宿主机上必须要能看到这个容器。所以通常我们会把 Runner 的 Docker socket 挂载到构建容器内让容器里的 docker 命令直接操作宿主机上的 Docker 引擎。3.3 挂载 Docker socket 的配置方式这是容器里使用 docker 命令的关键一步。修改 Runner 的config.toml在对应的 runner 配置中添加[[runners]] name backend-runner url https://gitlab.example.com token xxxxxxxx executor docker [runners.docker] image docker:20.10.16 volumes [/var/run/docker.sock:/var/run/docker.sock, /cache]注意第一个 volume 把宿主机的 Docker socket 映射到容器内部。这样容器里的 docker 命令能直接和宿主机的 Docker 引擎通信。第二个 volume 是缓存目录配合前面配置文件里的 cache 路径使用。但这里有个安全隐患值得提醒凡是能通过这个 Runner 跑流水线的人都相当于有了宿主机 root 权限。如果项目里混入了不可信的.gitlab-ci.yml完全可以在脚本里执行docker run -v /etc:/host/etc这样的命令直接读到宿主机的所有文件。所以多项目共享 Runner 一定要谨慎尽量使用项目隔离的 Runner或者在注册时打开 Runner 的 token 保护。3.4 前端项目流水线的配置差异前端项目和后端项目的流水线配置差异很大核心原因是构建工具和产物形态不同。一个 Vue 3 Vite 项目的流水线会和 Java 项目形成鲜明对比stages: - install - lint - build - deploy install-job: stage: install image: node:22.3.0 cache: key: $CI_COMMIT_REF_NAME paths: - node_modules/ script: - npm config set registry https://registry.npmmirror.com - npm install lint-job: stage: lint image: node:22.3.0 cache: key: $CI_COMMIT_REF_NAME paths: - node_modules/ script: - npm run lint needs: [install-job] build-job: stage: build image: node:22.3.0 cache: key: $CI_COMMIT_REF_NAME paths: - node_modules/ - dist/ script: - npm run build artifacts: paths: - dist/ needs: [install-job] deploy-job: stage: deploy image: alpine:3.20 script: - apk add --no-cache rsync openssh - rsync -avz --delete -e ssh dist/ useryour-server:/var/www/html/ needs: [build-job] only: - main environment: name: production url: https://example.com有几个地方值得注意。install-job和lint-job、build-job都使用了同一个node_modules缓存目录因为 Node 项目的依赖安装是整个流水线中最耗时的环节。配合needs关键字lint-job和build-job可以不用等待install-job完全跑完再启动GitLab 会尽量在它们之间做 DAG 调度。当然实际执行时lint-job还是会等install-job成功毕竟没有依赖包 eslint 根本跑不起来。部署这一步前端项目通常是“构建产物 rsync 同步”到 Nginx 服务器目录。这里我特意用了alpine镜像而不是完整版 Linux因为 rsync openssh 在 alpine 上只需要安装两个包整个镜像下载体积很小部署任务启动速度会快很多。还有一个换环境部署的技巧多个环境可以这样写deploy-staging: stage: deploy script: - rsync -avz dist/ userstaging-server:/var/www/html/ environment: name: staging only: - develop deploy-production: stage: deploy script: - rsync -avz dist/ userprod-server:/var/www/html/ environment: name: production only: - main同一个部署逻辑通过only分支控制走哪个环境清晰明了。4. 常见问题与排查技巧实录4.1 Runner 显示离线该怎么办Runner 离线是流水线跑不起来的头号原因也是我见过最多人踩坑的地方。排查思路从下往上走。第一先在宿主机上检查 Runner 服务是否在运行sudo systemctl status gitlab-runner如果显示 inactive 或 failed重启一次sudo systemctl restart gitlab-runner第二如果服务正常但 GitLab 页面仍显示离线检查 Runner 与 GitLab 之间的网络连通性。GitLab 服务器地址如果是 https 自签名证书Runner 注册和通信时都要对应配置tls-ca-file。有些人在 Runner 注册时问是否跳过 SSL 校验选了“是”结果注册成功但后续 SSL 校验失败。这种情况比较少见但一旦出现就非常隐蔽排查时容易忽略。第三看 Runner 日志sudo journalctl -u gitlab-runner -f通常日志里会直接打印出错误原因比如“Failed to connect to GitLab server”、访问超时等。看到什么就对症处理。4.2 Docker executor 拉取镜像超时用 Docker executor 时如果网络不定时电源索引很容易出现镜像拉取超时流水线直接失败。最典型的表现是 job 日志里一堆 “failed to pull image” 或 “timeout” 字样。处理方法有两个方向。第一是配置 Docker Registry 国内镜像加速这一点前面已经提过加在/etc/docker/daemon.json里第二是对 CPU 占用较高的镜像尽量使用体积小的 alpine 版本或直接优化基础镜像减少拉取时长。还有一个小技巧可以在 Runner 的config.toml里设置pull_policy if-not-present这样本地已有镜像就直接使用只有本地没有时才去远程仓库拉取。频繁跑流水线时能明显加快速度缺点是如果镜像真是旧版本你不会第一时间感知到。所以这个策略只适合稳定镜像不建议用于latest标签的开发镜像。4.3 Maven 依赖下载慢或构建失败流水线里 Maven 构建失败十有八九是仓库访问问题。Maven 官方中央仓库在国内访问很慢有时还会断连。我之前项目组就遇到过流水线跑到一半Maven 依赖下载失败整个 job 直接红色报警然后集体验证很被动。后来统一在流水线里为 maven 镜像挂载自定义的settings.xml里面配好阿里云镜像和公司内部私有仓库问题就彻底解决了。具体写法是把settings.xml放到项目根目录下的.gitlab-ci文件夹中然后在流水线脚本里通过-s参数指定mvn compile -s .gitlab-ci/settings.xml或者把settings.xml直接复制到镜像内的 Maven 配置目录mkdir -p /root/.m2 cp .gitlab-ci/settings.xml /root/.m2/settings.xml mvn compile4.4 缓存失效和 job 之间取不到产物这种情况也比较经典。你配了 cache也配了 artifacts但下一个 job 就是找不到上一个 job 生成的文件。先理解两者的区别cache是给某些依赖目录加速用的比如 Maven 的.m2目录、Node 的node_modules目录它不承诺把文件传递给下一个 jobartifacts才是 job 与 job 之间传递产物的机制在testjob 里生成的文件要进入packagejob 使用必须用artifacts声明。所以排查思路很简单检查你想要的文件是否在artifacts.paths列表里。另外artifacts 默认只在同一流水线内传递如果跨流水线需要下载历史产物就得通过 API 或专门的上传步骤解决。4.5 分支名称导致流水线不执行新手经常犯的一个错误是配置了only或rules但分支名没对上导致 push 之后流水线压根不触发。比如你只想在main分支上运行 deploy但实际开发分支叫master配置里的only: - main自然就拦住了所有执行。日志中会显示流水线被跳过skipped这时候回头检查分支名是否正确以及 Git 默认分支名称是否被修改过。5. 避坑速查表与个人经验总结到这里配置 CI/CD 的核心链路已经走通了。我把实际运维中反复踩过的坑做了一张速查表留给有需要的朋友随时对照。问题常见原因解决方向流水线完全不触发.gitlab-ci.yml文件名拼错、分支配置不匹配检查文件命名、检查only/rules分支条件Runner 离线服务未启动、网络不通、SSL 证书问题重启服务、检查网络、配置证书镜像拉取太慢Docker 源网络受限配置镜像加速、设置pull_policy if-not-presentMaven 依赖下载慢中央仓库访问慢配置阿里云镜像挂载settings.xml部署时 docker 命令权限不足容器内 docker 无法连接宿主机挂载 Docker socket注意权限安全构建成功但部署失败artifacts 路径配置不对检查artifacts.paths确认产物存在某一步骤莫名跳过when: manual或rules条件触发检查配置逻辑查看流水线日志中的跳过原因Node 依赖安装失败镜像中 Node 版本与项目要求不匹配更换 node 镜像版本或使用.nvmrc约束最后再分享一个我在实际运维中的体会CI/CD 流水线本质上是把团队的流程规范固化成代码既然它是代码就应该有版本管理、有注释、有评审。很多人只把.gitlab-ci.yml当成一个“跑构建的脚本”随便堆命令今天加一行明天删一行时间一长谁都不敢动它。我后期给自己的项目做的一个重要调整就是把流水线文件里每段逻辑都加了注释把每个 job 的职责、触发条件、产物去向写清楚新同事接手时不再靠猜。另外一个建议是不要把 Docker 镜像、依赖包版本、流水线配置混在一起不复盘。镜像 tag 建议用$CI_COMMIT_SHORT_SHA这样的 Git commit 哈希而不是latest。这样每次构建的镜像和代码版本是一一对应的出问题回滚时能精确找到“是哪一次提交产生的镜像”。这个习惯帮我节约过太多排查时间。如果你正准备从零搭建流水线先别急着抄大而全的配置用一个最简单的项目把 Runner、Docker executor、stages 跑通再逐步加测试、加部署这种渐进式的配置路径试错成本最低。等走过一遍完整的流程之后你会发现所谓 CI/CD 配置其实并不神秘它只是把“人肉维护的构建部署过程”变成了“代码化的可重复执行过程”而这份代码化的过程就是团队工程化能力的地基。
返回列表