news 2026/9/5 8:31:22

从提交到部署:用GitLab CI/CD构建自动化交付流水线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从提交到部署:用GitLab CI/CD构建自动化交付流水线

“团队正以前所未有的速度推进。”最近在需求评审会上听到这句话时,第一反应不是兴奋,而是压力。业务侧的需求在高速增长,排期在压缩;但研发这边,发布流程还是老套路:本地打包、上传服务器、手动重启、人工验证。

这是一个很典型的“业务提速、交付滞后”矛盾。团队推进速度快,如果想支撑这种速度,就必须把持续集成、持续交付真正落到项目的每个分支、每次提交和每次发布上。这也是很多团队从一天发一次版变成一天发几十次版的关键:不是人变多了,而是流程自动化了。

这篇文章会以“团队推进速度特别快时,研发如何构建一套自动化交付链路”为主线,从 CI/CD 概念说起,以 GitLab CI/CD 为例,完成从代码提交到自动构建、镜像推送、环境部署的完整流程。无论你是后端研发、测试工程师,还是维护 CI/CD 的 DevOps 同学,都可以按这篇文章的思路直接迁移到项目里。

1. 背景与核心概念

1.1 为什么团队推进速度会“卡”住

先说一个常见场景。业务需求排期被压缩到两周甚至一周,产品经理每个迭代都能拿出一整屏需求;研发这边的代码提交频率确实上来了,但发布环节没有跟上。每次发布仍然需要有人手动去服务器上执行命令,靠“操作手册”和“老员工的记忆”完成任务。

这带来的第一个问题是不可复现。线上环境、测试环境、本地环境之间的差异,会让明明已经验证通过的版本在发布后马上报错。第二个问题是反馈慢:代码提交后,编译和测试的结果要很久才能反馈到开发者手里,导致问题在人越来越多的时候集中爆发。第三个问题是发布窗口难以控制:一旦业务方说“这周必须上线”,发布就变成高风险操作,谁都怕碰。

这些问题的本质,是项目已经进入了“快节奏迭代”阶段,但交付链路仍是手工驱动。开发速度与发布速度不匹配,最终会反过来限制产品的推进速度。因此第一步不是继续给团队加需求,而是把“代码提交到远程仓库”到“应用运行在目标环境”的全部过程,变成一条可自动执行的流水线。

1.2 CI 与 CD 是什么

先看两个缩写。

CI,Continuous Integration,持续集成。核心动作是:开发者把代码推送到共享仓库后,由系统自动完成代码检查、编译、单元测试,并快速给出结果。它强调的是“频繁集成、尽早反馈”,避免几个人各写各的,最后合并时出现大量冲突和低级错误。

CD,Continuous Delivery / Continuous Deployment,持续交付/持续部署。持续交付的意思是:代码通过测试后,构建产物已经处于“随时可以发布”的状态,由人工决定什么时候点发布按钮。持续部署更进一步:流水线全自动,代码通过所有关卡后直接部署到生产环境。

很多刚接触这个概念的人容易把 CI 和 CD 当成一回事。它们的边界其实是:CI 解决“能不能集成、能不能通过测试”,CD 解决“能不能交付、能不能上线”。一个完整的 CI/CD 管道,通常包含代码提交、并行测试、构建镜像、推送镜像仓库、部署到环境等多个阶段。在团队推进速度很快时,这两部分必须都自动化,否则速度只能停留在表面。

1.3 为什么要用 GitLab CI/CD

实现 CI/CD 的工具不少:Jenkins、GitHub Actions、Azure Pipelines、GitLab CI/CD 都有各自的生态。GitLab CI/CD 比较适合很多企业内部已经在使用 GitLab 管理代码的场景,因为它把“代码仓库、合并请求、CI 流水线、镜像仓库、部署环境”放在同一个平台上,配置也只需在仓库里维护一个.gitlab-ci.yml文件,不需要单独维护一套 Jenkins Job。

本文使用 GitLab CI/CD 作为示例,主要流程是:代码推送到 GitLab → 自动触发流水线 → 执行测试与构建 → 生成 Docker 镜像并推送到镜像仓库 → 在目标服务器上部署运行。理解这一条链路后,迁移到 Jenkins 或 GitHub Actions 也很快,因为核心思路是通用的:阶段划分、任务定义、产物传递、环境变量管理。

2. 环境准备与版本说明

2.1 本文示例环境

技术教程最容易踩坑的地方是环境版本。不同版本的 GitLab Runner、Docker、JDK,会导致同样的配置出现完全不同的结果。下面给出本文示例所用的环境,实际使用时请以你项目的版本为准。

软件/组件版本或说明
GitLabGitLab CE 15+,自建或 GitLab.com 均可
GitLab Runner与 GitLab 版本保持兼容,建议使用官方最新版
Docker20.10+,用于本地构建镜像和 Runner 执行环境
JDK17(示例 Spring Boot 项目使用)
Maven3.9+
目标服务器Linux,安装 Docker,可访问 GitLab 镜像仓库

如果你的团队使用的是企业内部的托管 GitLab 或多云平台,版本可能更旧。遇到旧版本时,.gitlab-ci.yml中部分关键字支持情况会有差异,这一点在阅读下文时要注意。

2.2 示例项目结构

为了演示效果更直观,本文会创建一个简单的order-service服务,基于 Spring Boot 构建。项目结构如下:

order-service/ ├── .gitlab-ci.yml # GitLab 流水线配置 ├── Dockerfile # 镜像构建文件 ├── pom.xml # Maven 工程配置 └── src/ └── main/ └── java/ └── com/example/order/ └── OrderServiceApplication.java

实际项目会比这个复杂,但不必担心。流水线关心的只是“如何构建”和“如何部署”,业务代码结构不会影响核心配置逻辑。

2.3 开始前的准备清单

在动手之前,先检查以下几项是否就绪:

  1. GitLab 仓库已经创建,并且本地代码可以正常推送到远程。
  2. 需要执行 CI/CD 的 Runner 已经安装,并且能注册到 GitLab(安装方式见下文)。
  3. 目标服务器安装了 Docker,并有权限拉取镜像仓库中的镜像。
  4. 如果使用私有镜像仓库,准备好仓库地址、用户名和密码,后续需要在 GitLab 中配置为变量。

这些条件不具备时,可以先使用 Docker Desktop 或远程服务器搭建一个临时环境,不需要一开始就申请生产权限。

3. GitLab CI/CD 核心配置拆解

3.1 .gitlab-ci.yml 是什么

.gitlab-ci.yml是 GitLab Runner 读取的流水线定义文件。它使用 YAML 格式描述流水线要执行哪些阶段、每个阶段里有哪些任务、任务用什么镜像和命令、产物要如何保存。文件放在项目仓库根目录,提交后 GitLab 会自动检测并触发流水线。

一个最小化的示例是:

hello-job: script: - echo "Hello GitLab CI/CD"

这个文件定义了一个名为hello-job的 Job,里面只有一条命令。提交后,Runner 会执行这个脚本,并在流水线页面展示执行日志。对这个文件的理解可以稍后再深入,先建立“流水线 = YAML 中的 Job 列表”这个概念。

3.2 stages、jobs 和 script

正式项目中,一个流水线通常分为多个阶段,每个阶段里包含多个 Job。stages用来声明阶段顺序,例如:

stages: - test - build - deploy

默认情况下,同一阶段里的 Job 可以并行执行,不同阶段之间按顺序执行。只有当test阶段的 Job 全部成功,build阶段才会开始;deploy阶段则依赖build成功。这种依赖关系非常适合“先测试、再构建、最后部署”的开发流程。

每个 Job 的核心参数是script,即要执行的实际命令。你可以写多行命令,每个-项对应一行 shell 命令。需要注意的是,Runner 默认使用非交互式 shell 执行脚本,所以像docker login这种需要交互输入的命令,一定要通过参数或环境变量传入,否则会因为等待输入而卡住。这个细节在实操阶段很容易踩坑。

3.3 常用关键字说明

为了让后面的实战代码不显得突兀,这里先把最常用、最关键的关键字列出来。

关键字作用说明
stages定义流水线阶段,决定 Job 执行顺序
scriptJob 要执行的 shell 命令
before_script每个 Job 开始前执行的公共命令,常用来配置环境
after_scriptJob 结束后执行的脚本,无论成败都会执行
imageJob 运行的 Docker 镜像
servicesJob 额外的 Docker 服务,例如 docker:dind
artifacts构建产物,可用于传递到后续 Job
cache缓存依赖目录,例如 Maven 的.m2,加速后续构建
rules动态控制 Job 什么时候执行,是更现代的替代only/except的方式
environment声明部署环境,可为部署操作提供手动确认与回滚支持

其中rules是新手最容易写错的地方。它基于变量、分支名、提交信息等条件判断 Job 是否运行。示例:

deploy: script: "./deploy.sh" rules: - if: '$CI_COMMIT_BRANCH == "main"' when: manual - when: never

上面的代码表示:只有分支是main时,deployJob 才出现,并且需要人工点击执行。其他情况一律不运行。相比直接在script里写 if 判断,用rules会让配置更清晰,也更利于团队评审。

3.4 镜像构建与 Docker in Docker

在 CI 环境中构建 Docker 镜像,最常用的方案是 Docker in Docker,简称 DinD。Runner 本身运行一个 Docker 容器,里面通过docker命令访问一个 Docker 守护进程,进行镜像构建和推送。

.gitlab-ci.yml中,使用 DinD 的典型写法是:

image-demo: image: docker:24.0 services: - docker:24.0-dind variables: DOCKER_TLS_CERTDIR: "/certs" script: - docker info

这里image: docker:24.0表示 Job 运行的镜像里包含 Docker CLI;docker:24.0-dind提供 Docker 守护进程服务。DOCKER_TLS_CERTDIR是很多 GitLab 示例都会配置的变量,用于解决 Docker 客户端与守护进程之间的 TLS 通信问题。

如果你使用共享 Runner 或 Kubernetes Runner,可能不需要自己配置 DinD,直接使用 Runner 提供的 Docker 能力即可。但理解这一层机制,对排查“权限不足”“daemon 无法连接”类问题很有帮助。

4. 完整实战案例:从提交到自动部署

接下来是本文的核心部分。我们会从零搭建一个 Spring Boot 项目,并让它通过 GitLab CI/CD 自动完成测试、构建、镜像推送和环境部署。

4.1 创建项目结构

在本地新建目录并初始化 Maven 项目。下面是需要创建的目录结构:

orderservice/ .gitlab-ci.yml Dockerfile pom.xml src/main/java/com/example/order/OrderServiceApplication.java

如果你使用 IDEA 或 Eclipse,可以直接创建一个 Spring Boot 项目,只需要保证pom.xml中的打包方式是jar,并提供启动类即可。

4.2 编写 Spring Boot 启动类

创建src/main/java/com/example/order/OrderServiceApplication.java,代码如下:

package com.example.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } @GetMapping("/health") public String health() { return "ok"; } }

这个启动类包含一个/health接口,方便部署后验证服务是否正常运行。实际项目中,这里可以是任何 Spring Boot 业务应用。

4.3 编写 Dockerfile

镜像构建方式会直接影响流水线的速度和产物稳定性。这里采用多阶段构建:第一阶段用 Maven 镜像完成编译,第二阶段把编译好的jar放到更小的 JRE 镜像里,减少最终镜像体积。

# 阶段一:编译 FROM maven:3.9-eclipse-temurin-17 AS build WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn package -DskipTests -B # 阶段二:运行 FROM eclipse-temurin:17-jre WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

mvn dependency:go-offline会在构建前把需要用到的依赖下载到本地,后续真正打包时速度会快很多。-DskipTests表示跳过测试,因为流水线的测试阶段已经执行过单元测试;如果希望一次构建把所有事情完成,也可以去掉这个参数。

4.4 编写 .gitlab-ci.yml

接下来是整篇文章的关键,编写流水线配置。假设镜像仓库地址是registry.example.com/team-demo/order-service,你可以按实际环境修改。

stages: - test - build - image - deploy variables: DOCKER_IMAGE: registry.example.com/team-demo/order-service DOCKER_TLS_CERTDIR: "/certs" cache: paths: - .m2/ # 阶段一:单元测试 test: stage: test image: maven:3.9-eclipse-temurin-17 script: - mvn test rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event" || $CI_COMMIT_BRANCH' tags: - maven-runner # 阶段二:打 jar 包 build: stage: build image: maven:3.9-eclipse-temurin-17 script: - mvn package -DskipTests -B artifacts: paths: - target/*.jar rules: - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_TAG' # 阶段三:构建 Docker 镜像并推送 image: stage: image image: docker:24.0 services: - docker:24.0-dind script: - docker login -u "$REGISTRY_USER" -p "$REGISTRY_PASSWORD" "$REGISTRY_HOST" - docker build -t "$DOCKER_IMAGE:$CI_COMMIT_SHA" . - docker push "$DOCKER_IMAGE:$CI_COMMIT_SHA" rules: - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_TAG' # 阶段四:部署到服务器 deploy: stage: deploy image: docker:24.0 script: - docker login -u "$REGISTRY_USER" -p "$REGISTRY_PASSWORD" "$REGISTRY_HOST" - docker pull "$DOCKER_IMAGE:$CI_COMMIT_SHA" - docker stop order-service || true - docker rm order-service || true - docker run -d --name order-service -p 8080:8080 "$DOCKER_IMAGE:$CI_COMMIT_SHA" environment: name: production rules: - if: '$CI_COMMIT_BRANCH == "main"' when: manual tags: - deploy-runner

这个配置里有几个地方需要重点说明。

第一,tags字段绑定了 Runner。如果你的 GitLab 中注册了多个 Runner,建议给 Maven 构建和部署分别打不同 tag,避免部署 Job 被一个没有 Docker 权限的 Runner 执行。

第二,artifactsbuildJob 生成的 jar 文件能被后续 Job 使用。不过这里最终并不会直接使用 jar,因为镜像阶段是通过 Dockerfile 重新构建镜像的。你仍然需要保留build阶段的产物,方便在流水线页面下载 jar 做人工排查。

第三,deploy阶段被设置为when: manual,并且只对main分支生效。这样做的好处是:生产部署需要人工确认,避免每次提交代码都直接触发生产变更。若你的团队希望做到全自动持续部署,去掉when: manual即可,但需要确保测试覆盖和回滚机制足够完善。

4.5 注册 GitLab Runner

前面的配置使用了两类 Runner:maven-runnerdeploy-runner。这里以 Docker executor 为例,演示如何注册一个 Runner。

在安装 GitLab Runner 的服务器上执行:

gitlab-runner register \ --non-interactive \ --url http://gitlab.example.com \ --registration-token YOUR_REGISTRATION_TOKEN \ --executor docker \ --docker-image docker:24.0 \ --docker-privileged true \ --tag-list maven-runner

其中--url是 GitLab 服务地址,--registration-token是项目或群组设置的 Runner 注册令牌。不同 GitLab 版本注册方式略有差异,新版更推荐使用 Runner 认证 token 注册。如果你拿到的是页面提供的短令牌,直接替换即可。

注册完成后,在 GitLab 项目页面的 Settings → CI/CD → Runners 中就能看到这个 Runner。部署用的 Runner 可以再注册一个实例,并设置不同的 tag。注意:用 Docker executor 的 Runner 跑docker build时,通常需要加--docker-privileged true,否则 DinD 服务可能没有足够权限。

4.6 运行与验证

配置完成后,将代码推送到 GitLab 仓库:

git add . git commit -m "feat: 初始化 order-service 项目" git push origin main

推送后,进入 GitLab 项目页面,找到 CI/CD → Pipelines,可以看到一条新的流水线正在执行。正常情况下会依次经过test → build → image → deploy四个阶段。testbuild会自动执行,deploy需要人工点击“播放”按钮。

部署成功后,在目标服务器上可以用下面的命令检查服务状态:

curl http://<服务器IP>:8080/health

如果返回ok,说明整个 CI/CD 链路已经完全打通:代码提交后自动测试、自动构建、自动生成镜像,并且可以一键部署到生产环境。

5. 常见问题与排查思路

自动化流水线不会一次就完全顺畅。这里把实操中高频出现的问题整理成一张表,并给出两个比较典型的排查案例。

5.1 常见问题汇总

问题现象常见原因解决思路
流水线一直卡在 pendingRunner 未上线,或 tags 不匹配查看 Runner 状态,确认 Job 的 tags 与 Runner tags 一致
docker: command not foundJob 镜像里没有安装 Docker CLI使用image: docker:24.0,或切换到支持 Docker 的 Runner
permission denied while trying to connect to the Docker daemon socket没有配置 DinD,或 Runner 未开启 privilege 模式添加docker:dind服务,或为 Runner 增加--docker-privileged true
docker login失败镜像仓库地址、用户名或密码错误检查 GitLab 变量是否配置正确,确认仓库地址可访问
Maven 构建时依赖下载慢第一次构建没有缓存,或网络受限配置 Maven 镜像源,使用cache缓存.m2目录
部署后服务访问不通端口映射错误,或容器启动失败查看容器日志:docker logs order-service

5.2 现场排查案例一:Docker daemon 连接失败

有同学在配置完成后,发现image阶段一直报错,日志类似:

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

这个报错说明 Job 里执行docker build时,找不到可用的 Docker 守护进程。可能原因只有一个:Job 定义里只写了image: docker:24.0,但没有添加docker:dind服务。Docker CLI 和 Docker daemon 是两回事:CLI 负责接管用户输入,daemon 才是真正构建和运行容器的地方。

解决办法是给 Job 的services增加docker:24.0-dind,并在变量里设置DOCKER_TLS_CERTDIR: "/certs"。修改后重新提交代码,流水线就会自动使用 DinD 服务。如果问题仍然存在,再检查 Runner 是否开启了 privileged 模式。

5.3 现场排查案例二:流水线一直 pending

另一种常见情况是:流水线创建了,但 Job 一直处于 pending 状态,不开始执行。这时优先检查两处。

第一,Runner 是否在线。在 GitLab 的 Runners 页面查看 Runner 状态,如果显示灰色,说明 Runner 没有成功注册或网络不通。

第二,Job 和 Runner 的 tags 是否匹配。.gitlab-ci.yml中如果写了tags: [ maven-runner ],那么必须有一个 Runner 注册了相同 tag。如果没有,Runner 会认为没有可用的执行器,流水线就会一直 pending。这是新手最容易忽略的问题。

6. 最佳实践与工程建议

流程跑通之后,下一步是让流水线变得健壮、安全、可维护。这里给出几个在正式团队里比较重要的原则。

6.1 流水线设计:阶段要小,反馈要快

流水线不是越大越好。一个 Job 里塞了十几条命令,虽然也能跑,但在定位问题时会非常痛苦。建议把阶段拆小:测试、打包、构建镜像、部署各自独立。这样某个环节失败时,团队能立刻看到是哪一步出了问题,避免从头看日志。

同时要考虑反馈速度。每次提交都跑完整部署链路并不现实。可以在开发分支只执行testbuild,只在main分支或发布 tag 上执行镜像和部署。rules就是用来做这件事的。合理的阶段划分会让团队在保持速度的同时,不被大量无意义的构建结果淹没。

6.2 敏感信息不要写在 YAML 里

镜像仓库的用户名、密码、服务器私钥,一律不要写在.gitlab-ci.yml中。GitLab 提供了变量机制,在项目或群组的 Settings → CI/CD → Variables 中配置。配置时可以勾选 Masked 和 Protected,让敏感信息在日志中被隐藏,并且只在受保护的分支或

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 8:12:05

SpringBoot电商项目实战:服装销售平台架构设计与核心模块实现

简介&#xff1a;这是一套完整的基于SpringBoot的服装销售平台毕业设计项目源码&#xff0c;面向Java初学者与高校计算机专业学生&#xff0c;解决电商类系统开发学习中缺乏全栈实战案例的问题。资源包含862个文件&#xff0c;涵盖146个Java后端逻辑文件、52个Vue前端组件、153…

作者头像 李华
网站建设 2026/9/4 8:32:15

原句法庭与认知免疫:逻辑优先、证据资格及权力化宣称的递归批判

原句法庭与认知免疫&#xff1a;逻辑优先、证据资格及权力化宣称的递归批判 摘要 本文提出一套以“原句逻辑审查优先”为总纲的认知批判框架。本文所谓“宣称”&#xff0c;不是泛指一切表达、主张或判断&#xff0c;而是指一个人、机构或技术系统在命题自身的逻辑结构尚未成…

作者头像 李华
网站建设 2026/9/4 8:34:54

STM32+电容触控+环境光接近传感器协同设计实战

简介&#xff1a;这是一份面向嵌入式硬件工程师与STM32初学者的显示控制板参考设计资源&#xff0c;聚焦于多芯片协同驱动的实用场景——以STM32F103C8T6为主控&#xff0c;集成Cypress CY8CMBR3108电容触摸控制器与ROHM BU9796 LED背光驱动芯片&#xff0c;解决中小尺寸LCD模组…

作者头像 李华
网站建设 2026/9/4 14:03:14

Python数据分析可视化实战:构建空气污染数据可视化分析系统

简介&#xff1a;本资源是一套完整的Python数据分析与可视化课程设计项目&#xff0c;面向计算机、数据科学及环境类专业本科生&#xff0c;解决空气污染数据探索性分析与交互式可视化呈现的实际教学需求。资源包共288个文件&#xff0c;含42个CSV空气质量原始数据集、15个Jupy…

作者头像 李华
网站建设 2026/9/4 14:03:08

向量加法:第一个并行 Kernel

从主机内存&#xff08;Host Memory&#xff09;到设备内存&#xff08;Device Memory&#xff09;&#xff0c;第一次走通 CUDA 数据闭环。核心判断&#xff1a;向量加法真正教给你的不是 c[i] a[i] b[i]&#xff0c;而是 CUDA 的第一条完整数据链路&#xff1a;Host 数据如…

作者头像 李华