FEATURED · 精选文章

Day54 | 企业级 CI/CD 流水线设计:Jenkins + GitLab + Docker,别让“手动部署”毁掉你的职业生涯

发布时间 / 2026/8/20 23:27:09
来源 / 创域科博编辑部
栏目 / 资讯中心
Day54 | 企业级 CI/CD 流水线设计:Jenkins + GitLab + Docker,别让“手动部署”毁掉你的职业生涯 一、为什么手动部署迟早会出生产事故把代码从提交变成线上服务最危险的环节往往不是写代码而是人肉上线——scp传 jar、kill -9重启、nohup拉起。手动部署有三个绕不开的坑不可重复两次人肉步骤永远不可能完全一致、无法追溯谁、何时、部署了哪个版本一问三不知、反馈极慢编译测试打包上线全串行一个小 bug 修半天才能验证。本文给出一套中小厂可直接落地的企业级 CI/CD 方案GitLab 托管源码 Jenkins 编排流水线 Docker 提供不可变交付物 SonarQube 守住质量门禁。从 Jenkinsfile 全链路代码、分层 Dockerfile、质量门禁配置到一键回滚全部是可跑的实操落地成本不高、见效快。二、为什么 2~5 年的 Java 开发者必须认真搞懂 CI/CD很多兄弟觉得 CI/CD 是运维的活。错了。在中小厂DevOps 往往没有专门团队后端开发就是 pipeline 的第一责任人。你写的代码能不能稳定、快速地到达用户手里直接取决于你会不会设计一条靠谱的流水线。手动部署的三大原罪不可重复环境差异、人为疏忽、漏执行步骤都是事故的温床。无法追溯谁、什么时候、部署了什么版本、回滚到哪一版一问三不知。反馈极慢编译、测试、打包、上线全串行一个小 bug 修复也要半天才能验证。CI/CD 不是炫技而是工程化的底线。它把“构建→测试→打包→部署”固化成代码让每一次发布都可重复、可审计、可回滚。今天这套方案是我带多个团队验证过的GitLab 托管源码 Jenkins 编排流水线 Docker 提供不可变交付物。成本不高落地很快适合国内大多数中小企业的私有化场景。三、整体架构一条流水线到底经过哪些环节先上个全景图后面的代码都围绕这张图展开。核心理念就一句话代码提交后除了最后一步“批准上生产”其他全部自动跑完。四、环境准备别急着写 Jenkinsfile先把路铺好4.1 基础设施清单组件用途版本建议GitLab源码托管、MR 审批、Webhook16.x 社区版Jenkins流水线编排2.440 LTSMavenJava 构建3.9.xJDK编译运行17 或 21SonarQube代码质量门禁10.x 社区版Harbor私有 Docker 镜像仓库2.10部署机运行容器任意 Linux4.2 Jenkins 必装插件GitLab Plugin接收 GitLab Webhook。Pipeline Plugin运行 Jenkinsfile。Docker Pipeline / Docker Commons在 pipeline 里调用 docker 命令。SonarQube Scanner for Jenkins质量门禁。Credentials Binding安全注入密码、私钥。SSH Agent远程登录部署机。安装完插件后先在 Jenkins 里把这几个全局凭证配好gitlab-tokenGitLab Personal Access Token用于拉取代码。harbor-credHarbor 的账号密码。deploy-ssh-key部署机的私钥用于 SSH 登录。凭证一定要走 Jenkins Credentials不要硬编码在代码里。这是底线。五、Jenkinsfile把整个流水线写成代码下面是我在一个 Spring Boot 3 项目里真实使用过的Jenkinsfile已经做过脱敏处理。建议直接放到项目根目录文件名叫Jenkinsfile。// Jenkinsfile - Declarative Pipeline // 适用Spring Boot 3 Maven Docker SonarQube // Jenkins 要求安装Pipeline、Docker Pipeline、SonarQube Scanner、SSH Agent、Credentials Binding ​ pipeline { agent any ​ tools { maven Maven-3.9.6 // 与 Jenkins 全局工具配置中的名称保持一致 jdk JDK-21 // 同上 } ​ environment { // 镜像命名规范仓库/项目/服务名 IMAGE_NAME harbor.example.com/java-ai/order-service // Git 短提交 ID作为镜像 tag保证可追溯 GIT_SHORT sh(returnStdout: true, script: git rev-parse --short HEAD).trim() // 分支名中的 / 替换为 -避免镜像 tag 不合法 BRANCH_NAME_SAFE ${env.BRANCH_NAME.replaceAll(/, -)} IMAGE_TAG ${BRANCH_NAME_SAFE}-${env.BUILD_NUMBER}-${GIT_SHORT} } ​ options { timeout(time: 30, unit: MINUTES) // 防止异常任务无限挂起 buildDiscarder(logRotator(numToKeepStr: 20)) disableConcurrentBuilds() // 同一分支别同时跑两个构建 } ​ stages { stage(Checkout) { steps { // 显式清理工作区避免 Maven 目标目录污染 cleanWs() checkout scm echo 当前构建分支${env.BRANCH_NAME}镜像 tag${IMAGE_TAG} } } ​ stage(Compile Unit Test) { steps { // -B 批处理模式避免输出乱码跳过 IT 测试后面单独跑 sh mvn -B clean compile test -DskipITstrue } post { always { // 收集测试报告和覆盖率需配合 jacoco 插件 junit **/target/surefire-reports/*.xml } } } ​ stage(SonarQube Quality Gate) { steps { // 必须先在 Jenkins 系统配置里配好 SonarQube Server 名称 withSonarQubeEnv(SonarQube-LTS) { sh mvn -B sonar:sonar \ -Dsonar.projectKeyorder-service \ -Dsonar.projectNameOrder Service \ -Dsonar.host.url\${SONAR_HOST_URL} \ -Dsonar.login\${SONAR_AUTH_TOKEN} } // 阻塞直到质量门禁结果返回超时 5 分钟 timeout(time: 5, unit: MINUTES) { waitForQualityGate abortPipeline: true } } } ​ stage(Package Build Docker Image) { steps { // 打包成 jarjar 里已经包含依赖 sh mvn -B package -DskipTests ​ // 使用 Spring Boot 分层 jar 的 Dockerfile后面会贴 script { docker.withRegistry(https://harbor.example.com, harbor-cred) { def image docker.build(${IMAGE_NAME}:${IMAGE_TAG}) image.push() // 同步打一个 latest 标签方便非生产环境快速拉取 image.push(latest) } } } } ​ stage(Deploy to Test) { when { branch develop } steps { sshagent([deploy-ssh-key]) { sh ssh -o StrictHostKeyCheckingno deploytest-server \ cd /opt/apps/order-service \ sed -i s|image: .*|image: ${IMAGE_NAME}:${IMAGE_TAG}| docker-compose.yml \ docker-compose pull \ docker-compose up -d \ sleep 10 \ docker-compose ps } } } ​ stage(Smoke Test) { when { branch develop } steps { // 简单冒烟 actuator health 接口返回 UP sh for i in \$(seq 1 12); do STATUS\$(curl -s -o /dev/null -w %{http_code} http://test-server:8080/actuator/health || echo 000) if [ \$STATUS 200 ]; then echo Smoke test passed exit 0 fi echo Waiting for service... \$i/12 sleep 5 done echo Smoke test failed exit 1 } } } ​ post { failure { // 构建失败时通知负责人CSDN 读者可换成企业微信/钉钉/邮件 echo 构建失败请检查${env.BUILD_URL}console } always { // 清理本地镜像避免磁盘无限膨胀 sh docker rmi ${IMAGE_NAME}:${IMAGE_TAG} || true } } }这段 pipeline 里我埋了几个“坑后智慧”镜像 tag 一定要包含 Git 短 ID出了问题一眼就能定位到具体代码版本。waitForQualityGate abortPipeline: true质量门禁不通过绝不允许继续部署。别让“下次再修”成为口头禅。分支隔离when { branch develop }保证不会误把 feature 分支推到测试环境。post 里清理镜像构建节点磁盘爆满是 Jenkins 运维的高频事故必须自动清理。六、Dockerfile构建可重复的交付物CI/CD 的交付物必须是不可变的。Spring Boot 的可执行 jar 很好但如果环境里有不同版本的 JDK一样会出问题。所以最终交付物是 Docker 镜像。# Dockerfile - Spring Boot 3 分层 jar最大化构建缓存命中率 # 基础镜像Eclipse Temurin JRE 21体积小且经过长期维护验证 FROM eclipse-temurin:21-jre-alpine AS builder ​ WORKDIR /application ​ # 第一步只复制 jar 并解压分层 ARG JAR_FILEtarget/*.jar COPY ${JAR_FILE} application.jar ​ # Spring Boot 2.3 支持的分层 jar把依赖、外部库、应用代码分开 RUN java -Djarmodelayertools -jar application.jar extract ​ # 第二阶段生产运行时镜像 FROM eclipse-temurin:21-jre-alpine ​ WORKDIR /application ​ # 时区和中文支持避免日志时间错乱 ENV TZAsia/Shanghai RUN apk add --no-cache tzdata \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ echo Asia/Shanghai /etc/timezone ​ # 创建非 root 用户运行防止容器被攻破后直接拿到 root RUN addgroup -S appgroup adduser -S appuser -G appgroup ​ # 按分层顺序复制依赖层变化最慢放在最前面缓存命中率最高 COPY --frombuilder /application/dependencies/ ./ COPY --frombuilder /application/spring-boot-loader/ ./ COPY --frombuilder /application/snapshot-dependencies/ ./ COPY --frombuilder /application/application/ ./ ​ USER appuser ​ # 暴露端口 EXPOSE 8080 ​ # Spring Boot 分层 jar 推荐启动方式 ENTRYPOINT [java, org.springframework.boot.loader.launch.JarLauncher]注意Spring Boot 3.x 的分层启动器类路径是org.springframework.boot.loader.launch.JarLauncherSpring Boot 2.x 是org.springframework.boot.loader.JarLauncher别抄错。七、SonarQube 质量门禁让“烂代码”上不了线代码审查不能全靠人眼SonarQube 就是那个 24 小时不睡觉的守门员。在pom.xml里加入 JaCoCo 和 Sonar 配置properties java.version21/java.version !-- SonarQube 项目标识与 Jenkinsfile 中保持一致 -- sonar.projectKeyorder-service/sonar.projectKey sonar.host.urlhttp://sonarqube.example.com:9000/sonar.host.url !-- 代码覆盖率阈值可根据团队成熟度调整 -- sonar.coverage.exclusions**/config/**,**/dto/**/sonar.coverage.exclusions /properties ​ build plugins !-- JaCoCo生成单元测试覆盖率报告 -- plugin groupIdorg.jacoco/groupId artifactIdjacoco-maven-plugin/artifactId version0.8.12/version executions execution idprepare-agent/id goals goalprepare-agent/goal /goals /execution execution idreport/id phasetest/phase goals goalreport/goal /goals /execution /executions /plugin /plugins /build在 SonarQube 后台给项目设置质量门禁Quality Gate我常用的底线规则新代码覆盖率 ≥ 60%新代码无阻断BlockerBug新代码无严重CriticalBug代码重复率 ≤ 5%达不到流水线直接红色失败谁也合不进去。八、部署机的 docker-compose最后一步也要可版本化很多兄弟把docker-compose.yml随手扔到部署机上美其名曰“灵活”。我劝你别这么干。部署配置也要进 Git 仓库和代码一起版本化# docker-compose.yml - 测试环境部署配置 version: 3.8 ​ services: order-service: # 这个 image 行由 Jenkins pipeline 自动替换 image: harbor.example.com/java-ai/order-service:latest container_name: order-service restart: unless-stopped ports: - 8080:8080 environment: - SPRING_PROFILES_ACTIVEtest - JAVA_OPTS-Xms512m -Xmx1024m -XX:UseG1GC healthcheck: test: [CMD, wget, -q, --spider, http://localhost:8080/actuator/health] interval: 15s timeout: 5s retries: 5 start_period: 30s # 日志限制防止磁盘被日志打爆 logging: driver: json-file options: max-size: 50m max-file: 3healthcheck 一定要配。容器up不代表服务可用只有 actuator/actuator/health返回 UP才能认为启动成功。九、回滚策略上线不翻车翻车能回来再稳的流水线也可能遇到“代码没问题但业务逻辑想错了”的情况。所以必须有一键回滚。最简单的方案保留最近 5 个镜像 tag回滚时把docker-compose.yml里的 image 改成上一个 tag 重新 up。#!/bin/bash # rollback.sh - 回滚到上一个镜像版本 set -e ​ APP_DIR/opt/apps/order-service cd $APP_DIR ​ CURRENT_TAG$(grep image: docker-compose.yml | awk -F: {print $3}) echo 当前运行镜像 tag$CURRENT_TAG ​ # 从 Harbor 拉取最近 5 个 tag列出供选择 # 实际生产可结合 Jenkins 参数化构建实现 Web 页面点选回滚更高级的做法是用Blue-Green 部署或K8s Rolling Update。但对于大多数还在用 Jenkins Docker 的团队“保留历史 tag 快速替换 docker-compose 镜像”已经能覆盖 90% 的回滚场景。十、建议1. 把“构建产物”和“部署配置”分开管代码仓库存Jenkinsfile、Dockerfile、src部署配置单独放一个deploy仓库或目录。这样生产环境的连接串、密钥、域名不会因为源码泄露而全部暴露。2. 永远不要让 SonarQube 只是“参考”见过太多团队把 SonarQube 当装饰品dashboard 上全是红色照样上线。我的做法是在 GitLab MR 里强制要求流水线通过并且用 SonarQube 的分支分析做质量门禁。不绿不能合并。3. 流水线也要被监控CI/CD 本身也是系统也会挂。Jenkins 节点磁盘满了、Harbor 证书过期、GitLab webhook 失效这些都会导致“代码提交了半天没反应”。建议给 Jenkins 配一个简单的健康看板最近 24 小时构建失败率平均构建时长趋势各阶段失败次数分布Checkout / Test / Sonar / Docker / Deploy这些数据用 Jenkins API Prometheus 很容易拉出来。“手动部署不是慢是不可控CI/CD 不是快是把失控变成可预期。”今天我们把 GitLab、Jenkins、Docker、SonarQube 串成了一条完整的流水线。但这只是开始。代码进了仓库之后我们还要回答另一个问题代码质量怎么持续保证下一篇Day55《代码质量自动化Checkstyle / SpotBugs / SonarQube 三剑客》我会把代码规范检查、潜在 Bug 扫描、技术债务量化串成一套“质量门禁组合拳”。没有规矩不成方圆没有门禁CI/CD 只是跑得快的裸奔。老规矩代码仓库和更多实战脚本会同步更新到专栏配套仓库评论区见。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻