做微服务开发有个绕不开的痛:开发一时爽,部署火葬场。尤其是积木学堂(谷粒学院体系里非常经典的 SpringCloud 教育平台项目)这类工程,服务一拆就是六七个——用户、课程、订单、支付、统计、网关……每个服务独立打包、独立部署,手动操作一套下来,小半天就没了,而且纯重复劳动最容易出事故。我在这个项目上花了大概两周时间,把 SpringCloud + Jenkins + Docker 的持续部署链路完整打通,从流水线设计、镜像构建到目标机部署、回滚方案全都落到了实处。今天就把这套方案从头到尾拆开讲,配置和脚本都是可以直接抄走的。
这套东西本身不复杂,真正难的是三个地方:一是多模块 Maven 项目的构建顺序和依赖关系;二是 Jenkins 流水线怎么把“拉代码—编译打包—构建镜像—推送仓库—目标机部署”这些环节串成一条自动链路;三是容器网络、注册中心、配置中心之间的联动。任何一环没想明白,都会在发布日让你被迫加班到深夜。
这篇文章适合谁看?正在做 SpringCloud 微服务项目、想把手动部署升级成自动化流水线的开发;或者公司里没有专职运维、DevOps 需要自己动手搭一套的“全栈伪运维”。我把选型的理由、踩过的坑、能直接照抄的配置都写出来,照着做可以少走不少弯路。
1. 项目目标与整体方案选型
1.1 微服务部署到底难在哪
先从背景说起。积木学堂的业务模块划分是典型的 SpringCloud 教育平台架构:对外走 Gateway 网关统一入口,内部按业务拆成用户、课程、订单、支付、统计分析等服务,服务之间用 OpenFeign 调用,注册中心和配置中心用 Nacos 承接。这套架构在开发阶段确实舒服,每个团队各管各的服务,互不干扰。但到了部署环节,问题就全冒出来了。
第一个问题是服务数量多。两三个服务还能手动敲命令应付,七八个服务每个都要经历“打包、传包、停旧进程、启动新进程、看日志、确认注册”这一整套流程,纯手工搞一轮,运气好四十分钟,运气不好一个服务起不来,排查又是半小时起步。发布一次,人就是被绑在终端前面的。
第二个问题是环境差异。开发机、测试机、生产机的 JDK 版本、环境变量、配置文件不完全一样,经常出现“我本机跑得好好的,一到服务器就不行了”的灵异现象。这种问题最烦人,因为它不是代码逻辑错误,而是环境漂移(environment drift)造成的。Docker 之所以在部署环节是刚需,就是因为它把 JDK、依赖、运行参数全部固化进镜像的分层文件系统里,开发环境什么样,生产环境就是什么样,彻底堵住“我本机没问题”这种扯皮。
第三个问题是迭代频率高。教育平台业务,课程内容、营销活动更新频繁,高峰期一天要发两三次版本。手动部署一天最多扛一两次,而且人越忙越容易按错命令。说实话,部署这件事本身不需要多少“智能”,它需要的是标准化和自动化,而这正是 CI/CD 的用武之地。
1.2 技术选型:为什么是 Jenkins + Docker 这套组合
选型的时候我也对比过其他方案,比如 GitLab CI、Gitea Actions、Drone 之类,最后定的还是 Jenkins + Docker。理由很务实,不是因为它最先进,而是因为它最合适。
第一是生态成熟度。不管代码托管在 GitLab、Gitea 还是自建 Git 服务,Maven 私服用 Nexus 还是 Artifactory,镜像仓库用 Harbor 还是自建 Registry,Jenkins 都有对应的插件和大量现成的流水线示例。老工具不性感,但老工具在兼容性上真的省心。
第二是团队认知成本低。项目组平时写 Java、用 SpringCloud,对 JVM 工具链很熟,但不太愿意为 CI/CD 再去学一套新的编排语法。Jenkinsfile 用声明式流水线,分阶段展示执行状态,开发在 Web 界面就能看到哪一步挂了,理解成本非常低。让他们改一行 Groovy 还能应付,换个完全陌生的工具链,推行阻力会大很多。
第三是 Docker 在交付层面的收敛能力。把每个服务做成镜像后,“部署的变量”就压缩成了一条 docker run 命令。镜像分层、版本化、可回滚,配合 Registry 的 tag 管理,部署动作简化成“改 tag、拉镜像、重启容器”三步,这对没有专职运维的小团队来说是最可控的自动化边界。
当然这个组合也有缺点:Jenkins 界面不好看,配置管理偏传统,Pipeline 调试不如本地脚本直观。但在稳定性、可维护性、社区资料丰富程度上的优势,对一个十几人的技术团队自己搭 CI/CD 完全够用。没必要为了追逐新潮工具而制造新的学习成本,先把流水线稳定跑起来,比什么都强。
1.3 积木学堂的模块结构与部署拓扑
先看项目结构。积木学堂按 Maven 多模块组织,大致长这样:
jimu-academy ├── common // 公共依赖模块 │ ├── common-util // 工具类 │ ├── service-base // 服务基类、统一返回结构 │ └── spring-security // 安全认证模块 ├── infrastructure │ ├── api-gateway // SpringCloud Gateway 网关 │ └── service-oss // 文件存储服务 ├── service │ ├── service-user // 用户中心 │ ├── service-course // 课程服务 │ ├── service-order // 订单服务 │ ├── service-pay // 支付服务 │ └── service-statistics // 统计分析 ├── service-client // OpenFeign 客户端接口模块 └── pom.xml这种结构的部署顺序有讲究:必须先 install 公共模块 common,再 install service-client 和 infrastructure 里的基础服务,业务服务才能正常解析依赖。很多人第一次搭流水线,直接在最外层跑 mvn package,然后发现业务模块编译不过,就是没搞清楚这个依赖链。
部署拓扑上,我划分了三类节点角色:
- 构建节点:跑 Jenkins,负责拉代码、编译、构建镜像、推送镜像。
- 仓库节点:跑 Docker Registry 或 Harbor,存放镜像。小团队可以把它和 Jenkins 放在同一台机器上。
- 运行节点:跑各个微服务容器,通过 docker run 或 docker-compose 拉镜像并启动。
资源不充裕的话,这三类可以合并到一台 8C16G 服务器上,流量不大时够用。但生产环境建议至少拆成两台,避免构建时 CPU 被打满,导致正在运行的服务跟着抖动。
2. 环境准备:先把地基打牢
2.1 服务器规划与资源评估
先说我这边的实际规划,你可以按自己资源情况缩放。基础配置:一台 8C16G 的机器跑 Jenkins + Registry,一台 4C8G 跑网关,一台 8C16G 跑业务服务(用户、课程、订单、支付、统计)。Nacos、Redis、MySQL 这类中间件,我直接用 docker-compose 单独部署在运行节点上,没有塞进微服务的编排里。原因很简单:中间件和数据服务需要稳定,不应该跟着业务流水线一起频繁升级。
如果你预算少,中间件和业务服务可以共存,但内存规划要提前算好。SpringCloud 服务每个一般给 512m 到 1g 的堆内存,五六个业务服务加网关、再加中间件,16G 内存是底线。我之前在 8G 的机器上硬跑全部服务,结果容器频繁被操作系统 OOM Kill,日志里经常出现 Killed 字样,排查起来特别痛苦。后来把内存扩容、并把服务最小堆压到 256m,才稳定下来。
另一个容易被忽略的是磁盘。Docker 镜像、Maven 本地仓库、Jenkins 构建产物累积起来非常可观。我遇到过磁盘满了导致构建直接失败的情况,后面统一把 /var/lib/docker、Maven 仓库、Jenkins home 放到独立数据盘,写了定时清理脚本,保留最近 10 个构建产物和最近 5 个镜像 tag,仓库才算稳定下来。这一步看起来不起眼,但能帮你避开很多半夜报警。
2.2 Docker 安装与基础配置
Docker 安装本身不复杂,用官方脚本或系统包管理器都能装。但有几个配置必须做,不做后面准出事。
第一,配置镜像加速。国内网络直接拉 Docker Hub 的镜像非常慢,一个 openjdk 基础镜像能拉十几分钟。在/etc/docker/daemon.json里配上加速地址,实测构建时间能缩短一个量级。
第二,限制日志大小。微服务请求量一大,容器标准输出日志会疯狂膨胀。不限制的话,/var/lib/docker/containers目录会被 json 日志文件撑爆。我统一在 daemon.json 里加了:
{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }这个配置只对新容器生效,旧容器需要重建或者手动清理日志文件。上线前把这条配好,比事后天天清日志省心太多。
第三,想清楚 Jenkins 怎么调用 Docker。常见两种做法:Jenkins 容器内挂载 docker.sock(Docker Outside of Docker),或者 Jenkins 所在宿主机直接装 Docker,流水线通过 SSH 远程执行 docker 命令。我选的是后者——构建节点宿主机装 Docker,Jenkins 通过 SSH 远程执行 build 和 push。这样避免 Docker-in-Docker 的层级嵌套问题,权限控制也更直观。
2.3 Jenkins 初始化与必要插件
Jenkins 的安装我用 war 包配合 systemd 托管,Java 环境单独给 Jenkins 配一个,不要和项目的 JDK 挤在一起。项目如果是 Java 8,Jenkins 本身可以用 JDK 11 或更高版本,互不干扰。
首次初始化抓两个重点:插件安装和凭据配置。插件别贪多,按需装:
- Git:拉取代码
- Maven Integration:Maven 构建支持
- Docker Pipeline:流水线中调用 docker
- Pipeline、Pipeline: Stage View:流水线基础
- SSH Pipeline Steps 或 Publish Over SSH:远程执行部署命令
- Blue Ocean(可选):可视化流水线,展示更直观
我见过有人一口气装几十个插件,结果界面卡顿、插件版本冲突、Jenkins 重启后某个插件起不来,维护成本全上去了。插件越少越好,够用就行。
JDK 和 Maven 的配置,建议走 Jenkins 的 Global Tool Configuration,不要手动改系统环境变量。好处是每个任务可以指定用哪个 JDK、哪个 Maven,后续有其他项目、不同版本需求时切换成本极低。
2.4 Maven 私服与依赖缓存的必要性
国内做 Java 项目,Maven 中央仓库访问速度实在感人,强烈建议先搞定私服或镜像源。积木学堂这种多模块项目,每次构建要拉几十个依赖,没有私服的话,构建时间的很大一部分都浪费在网络请求上。
我这边用内部 Nexus 私服,Jenkins 构建机的 settings.xml 指向私服,私服里再代理中央仓库。第一次构建依赖慢,第二次开始基本走内网,速度差异非常明显。如果没有私服,用国内公有云厂商提供的 Maven 镜像也能救急,但要注意部分冷门依赖可能缺失,遇到就得手动补充依赖项。
多模块构建还有一个隐藏前提:Maven 默认只构建当前目录的项目。业务模块依赖的 common 模块还没 install 到本地仓库时,直接构建业务模块会报“找不到依赖”。所以流水线第一步要在根目录跑:
mvn clean install -DskipTests -pl common,service-client -am先装好基础模块,再进入具体业务模块打包。用-pl和-am参数精确指定,比每次全量 clean install 快很多,模块多了之后收益尤其明显。
3. 流水线核心实现:从代码到容器的完整链路
3.1 凭据管理与安全配置
Jenkins 流水线要操作代码仓库和镜像仓库,这是两个最基本的凭据。我的原则很简单:所有密文一律进 Jenkins 的凭据库(Credentials),绝对不写死在 Jenkinsfile 或 shell 命令里。这既是安全要求,也是维护性要求——密码换了,只需要在凭据库里改一次,不用翻一堆脚本。
创建凭据后在 Jenkinsfile 里通过 credentials() 引用:
environment { GIT_CRED = credentials('git-account') REGISTRY_CRED = credentials('registry-account') REGISTRY_URL = 'registry.internal:5000' }凭据 ID 是全局字符串,命名要有规矩。我习惯用“用途-环境”格式,比如 git-token-prod、registry-user-prod。项目多了以后,这种命名方式能省很多事。
还有一个细节:SSH 远程执行部署命令时,尽量用密钥而不是密码。把公钥配到运行节点,保证 Jenkins 到运行节点免密。否则密码一旦变化,流水线半夜断掉,又是爬起来手动处理的节奏。
3.2 多模块项目的 Maven 构建顺序与增量策略
积木学堂的构建,我按依赖关系分成几层:
- 基础模块:common 下的 common-util、service-base、spring-security。
- 接口模块:service-client 下各 OpenFeign 客户端接口,这是业务模块调用的契约。
- 基础设施模块:api-gateway、service-oss。
- 业务模块:service-user、service-course、service-order、service-pay、service-statistics。
- 最后统一打镜像、推镜像、部署。
为什么要分层?SpringCloud 项目里,业务服务之间通过 OpenFeign 调用,接口定义和实现是分离的。如果你只改了某个业务模块,理论上不需要重新构建所有服务。我最初是全量构建先跑通,链路稳定后再针对改动模块做增量构建,构建速度提升非常明显。
全量构建命令放根目录:
mvn clean install -DskipTests注意我跳过了单元测试。如果项目测试比较完善,想跑特定模块的测试可以针对性地打开。但全量构建时把所有测试跑一遍,流水线时间会翻倍,而且教育项目大量测试依赖数据库和 Redis,CI 环境里根本没有这些,强行跑只会收获一堆失败。持续部署和持续测试是两件事,先把交付链路打通,测试再设计独立环境补充。
3.3 Dockerfile 编写与镜像优化
每个服务的 Dockerfile 大同小异,以课程服务为例:
FROM openjdk:8-jdk-alpine ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} /app/service-course.jar EXPOSE 8002 ENTRYPOINT ["java", "-jar", "-Xms256m", "-Xmx512m", "/app/service-course.jar"]这里有几个经验点:
第一,时区必须写进镜像。国内服务器如果不设置 TZ,容器默认 UTC,日志时间比北京时间晚 8 小时。排查线上问题的时候时间对不上,非常耽误事。
第二,JVM 堆参数要固定。SpringCloud 服务默认按容器可见内存来设置堆大小,如果机器上还跑着别的服务,容易导致整机内存超配,触发 OOM。我给每个服务定好 -Xmx,避免服务之间互相抢内存。
第三,利用 Docker 层缓存。老手会把依赖层和业务层分开 COPY:先 COPY pom.xml,跑依赖下载,再 COPY 源码。这样依赖没变的时候,依赖层可以命中缓存,构建速度显著提升。多模块项目不一定能完全套用,因为公共模块一变依赖就要重拉,但思路值得理解。
3.4 面向多服务的 Jenkinsfile 设计
这是整个流水线的核心。我用的声明式 Pipeline(Declarative Pipeline),结构清晰,和 Blue Ocean 配合也好。核心思路是:一个构建任务覆盖多个服务,用参数选择目标服务,而不是为每个服务建一个几乎一模一样的任务。
关键脚本是这样的:
pipeline { agent any environment { REGISTRY_URL = 'registry.internal:5000' PROJECT = 'jimu-academy' DOCKER_TAG = "${env.BUILD_NUMBER}" } parameters { choice(name: 'SERVICE_NAME', choices: ['service-course', 'service-user', 'service-order', 'service-pay', 'service-statistics', 'api-gateway'], description: '请选择要构建的服务') } stages { stage('Checkout') { steps { checkout scm } } stage('Maven Package') { steps { sh "mvn clean package -DskipTests -pl ${params.SERVICE_NAME} -am" } } stage('Docker Build') { steps { sh "docker build -t ${REGISTRY_URL}/${PROJECT}/${params.SERVICE_NAME}:${DOCKER_TAG} ./${params.SERVICE_NAME}/" } } stage('Push Image') { steps { sh """ docker login ${REGISTRY_URL} -u ${REGISTRY_CRED_USR} -p ${REGISTRY_CRED_PSW} docker push ${REGISTRY_URL}/${PROJECT}/${params.SERVICE_NAME}:${DOCKER_TAG} """ } } stage('Deploy') { steps { sshPublisher(publishers: [ sshPublisherDesc(configName: 'prod-run-node', transfers: [], execCommand: "sh /opt/deploy/deploy.sh ${params.SERVICE_NAME} ${DOCKER_TAG}") ]) } } } }这样一个任务就覆盖了 6 个服务的构建部署,改动流水线逻辑只需要改一处。维护成本比 6 个重复任务低得多。
这里有个大坑:多模块项目里 Dockerfile 的构建上下文。上面脚本里./${params.SERVICE_NAME}/假设 Dockerfile 在子模块目录下,而且我们在根目录执行的 mvn package,产物 jar 在子模块的 target 里。你要确保 Dockerfile 里 COPY 的路径是相对于子模块根目录的,或者构建上下文切到子模块目录。我第一次没注意这一点,构建出来的镜像总是报错COPY failed: stat /var/lib/docker/tmp/... no such file or directory,卡了半天才发现是上下文路径的问题。
3.5 部署脚本与容器编排
目标服务器上放一个通用的 deploy.sh,这是所有服务的统一入口:
#!/bin/bash SERVICE=$1 TAG=$2 REGISTRY="registry.internal:5000" IMAGE="${REGISTRY}/jimu-academy/${SERVICE}:${TAG}" docker pull ${IMAGE} docker rm -f ${SERVICE} || true docker run -d \ --name ${SERVICE} \ --network jimu-net \ -e SPRING_PROFILES_ACTIVE=prod \ -e NACOS_ADDR="nacos:8848" \ -e TZ=Asia/Shanghai \ --restart unless-stopped \ ${IMAGE}几个要点:
容器网络统一用自定义 bridge 网络 jimu-net。容器之间通过服务名互相访问,比 IP 直连可靠,因为容器重建后 IP 会变,但服务名不变。
Spring 应用通过环境变量注入 profile 和注册中心地址,不要写死在镜像里。同一份镜像可以跑 test 和 prod 两种环境,只是环境变量不同,这才是“一次构建,处处运行”的正确打开方式。
--restart unless-stopped保证机器重启或进程异常退出后容器自动拉起。教育平台这种业务,大半夜没人在线盯着,这个策略能兜住大部分偶发故障。
服务多的话,可以用 docker-compose 管理运行节点,把所有 service 和中间件定义在一个 yml 里,部署回滚都方便。Kubernetes 当然更强大,但学习成本和运维成本对一个小团队来说偏高,没有必要盲目追求“先进”而上 K8s。
3.6 注册中心与配置中心的联动
SpringCloud 项目里,服务启动后会注册到 Nacos,还要从 Nacos 拉配置。和持续部署强相关的问题有三个。
第一是启动顺序。网关依赖下游服务都注册到 Nacos,业务服务依赖 MySQL、Redis 等中间件先启动。中间件最先启动,然后按业务依赖顺序启服务。deploy.sh 里要加健康检查:
for i in {1..30}; do if curl -s http://127.0.0.1:${PORT}/actuator/health | grep -q '"status":"UP"'; then echo "service ok" break fi sleep 3 done如果没有 actuator 健康检查,流水线显示部署成功,但服务实际还没注册上,请求全打 503。加了这个等待逻辑,流水线才算真正“部署成功”。
第二是配置变更和热更新。SpringCloud 的配置中心支持动态刷新,但如果你把环境配置写死在镜像里,那就等于“改配置必须重新构建镜像”。我建议所有环境相关配置都放到 Nacos,镜像里只留最基础的默认配置。线上调日志级别、调超时时间,直接在 Nacos 改,配合@RefreshScope注解实时生效,完全不用走 CI/CD。这个改动带来的灵活性非常大。
第三是优雅下线。新服务替换旧服务时,最好先在 Nacos 把旧实例下线,等新实例注册成功再摘除,否则可能出现短暂的 404、502。服务刚部署完的那几十秒,其他服务的本地缓存还没有刷新,偶发调用失败很难完全避免。业务侧加个重试机制兜底,比如 OpenFeign 的重试配置,比在运维层面死磕更见效。
4. 常见问题排查与实战避坑
4.1 构建阶段的经典报错
把这段时间排查遇到的问题汇总成速查表,都是实际踩过的:
| 症状 | 根本原因 | 解决方案 |
|---|---|---|
| 构建提示依赖找不到 | 公共模块未 install 到本地仓库 | 根目录先跑mvn install -pl common,service-client -am |
| 镜像构建 COPY 找不到 jar | Dockerfile 上下文路径不对 | 确认 COPY 路径相对子模块根目录,或用-f指定 Dockerfile |
| Maven 下载依赖卡死 | 中央仓库访问慢 | 配 Nexus 私服或国内镜像源 |
| Jenkins 提示 Docker 权限不足 | 用户不在 docker 组 | 将 Jenkins 用户加入 docker 组,或改用 SSH 方式执行 docker 命令 |
| 构建产物内容一直不变 | Maven 增量构建命中缓存 | 确认改动的代码是否在-pl指定的模块内 |
这些错误里,依赖找不到和 COPY 路径问题是我踩得最狠的。还有一个容易被忽略的细节:Jenkins 每个任务有独立的 workspace,流水线里不要依赖上一次构建留下的文件,所有中间产物必须在当前构建里完整生成,否则换节点、清 workspace 后必然翻车。
4.2 容器运行阶段的坑
运行时的问题比构建更隐蔽,因为表面上“容器起来了”,但服务实际上不可用。
第一个是时区问题。前面提到过,镜像里不配 TZ,日志时间和监控时间对不上。上生产之后,错误发生的时间和日志时间差 8 小时,很影响定位效率。这种低级问题一旦成为默认状态,团队里所有人都要跟着买单。
第二个是端口冲突。多个服务容器映射宿主机端口,一定要提前规划端口表。比如 8001 到 8005 依次给五个业务服务,网关用 8080,Nacos 用 8848,Redis 用 6379,MySQL 用 3306。把端口分配写进部署文档,新服务接入的时候先查表,别想当然。
第三个是健康检查的感知延迟。就算配了 actuator 健康检查,SpringCloud 服务注册到 Nacos 后,其他服务的本地服务列表不会立刻更新。刚部署完的一两分钟内,一部分请求会转发到旧实例或已下线实例。解决办法是业务侧重试加发布窗口容忍,同时开启 Nacos 的服务列表自动刷新。这些细节在压测和生产中会慢慢暴露,提前知道就能少挨几顿骂。
4.3 网络与配置联动的深坑
容器化之后,微服务之间、微服务与中间件之间的网络问题是重灾区。我遇到过这些情况:
- 容器 A 能 ping 通容器 B,但应用连不上 B 的 MySQL。原因通常是 MySQL 不在同一个自定义网络里,服务名解析不到。
- 容器里连宿主机上的 MySQL 失败。应用里配置的是 localhost,但容器里的 localhost 是容器自身,不是宿主机。要连宿主机得用宿主机 IP,不能想当然地认为容器和宿主机共享网络。
- 服务间 OpenFeign 调用 503。对方服务没注册到 Nacos,或者健康检查失败被摘除。先看 Nacos 服务列表,再逐个容器看日志。
排查容器网络问题,我一般不靠猜,直接进容器验证。docker exec -it <container> ping nacos,docker exec -it <container> telnet mysql 3306。如果 ping 都不通,大概率是网络没拉通,和业务代码无关,别在代码层面浪费时间。
还有一个坑是 Nacos 配置格式。如果配置 dataId 拼错、格式不对、配置项类型错误,服务启动会直接失败,报错信息还可能比较隐晦。我的建议是:本地开发环境先验证 Nacos 配置无误,再走 CI/CD 部署。把配置正确性验证前置到流水线前面,能避免把错误配置发布到生产。
4.4 回滚与故障恢复
自动化流水线也有发错版本的时候。我的回滚策略很简单:镜像 tag 直接用 Jenkins 构建号,所以回滚就是“把 tag 指回上一次构建号,重新跑一次 deploy”。deploy.sh 本身是幂等的:先 docker rm 旧容器,再 docker run 新容器,回滚也是同一套动作,不用额外写一套脚本。
实际操作中我遇到过回滚失败的情况:旧版本镜像因为清理策略被删了,想回滚却没有镜像可用。所以清理脚本必须保留最近 N 个 tag,N 至少大于你预计的“发现错误需要回滚”的时间窗口。我这边保留 5 个,从没出现过回滚时镜像失踪的尴尬。
另一个恢复手段是容器自愈。--restart unless-stopped配好之后,进程崩溃会自动重启。但要注意:如果服务是因为配置错误导致的崩溃,重启后只会陷入崩溃循环。这种时候先看容器日志确定根因,再决定回滚还是修配置。我经历过一次配置变更引发的崩溃循环,容器每隔几秒起来就挂,日志全是连接超时,最后回滚配置才安静下来。
5. 项目改造之外的几句真话
5.1 自动化不是万能药:先跑通再自动
这套持续部署搭建本身不算难,难的是持续维护的纪律。我最大的体会是:自动化流水线不会消除部署问题,它只是把问题提前到构建阶段暴露出来。
不要为了“自动化”而自动化。先把手动部署流程完整跑通,确认每一步都清楚,再写进流水线。自动化没有“也许”这个词,任何一步不确定,流水线就会成为新的故障来源。我见过团队连服务启动方式都没统一就急着上 Jenkins,结果流水线反而成了整个系统里最脆弱的一环,三天两头挂,最后废弃回手动。
5.2 维护流水线的三条纪律
第一条:所有环境差异必须收敛到配置和环境变量。镜像里不写死任何环境相关的值,数据库地址、密码、profile 全部由运行时环境变量或配置中心注入。这是容器化部署能否长期稳定的分水岭。
第二条:日志和监控要提前接入。自动化部署让发布变快了,但如果日志分散在各容器、没有统一查询入口,故障定位时间并不会缩短多少。我在部署完所有服务之后,花了大量时间接集中日志和告警,这件事比流水线本身更值得提前做。
第三条:变更要留痕。每次发布走了什么流程、改了哪个服务、用的哪个镜像 tag,都要有记录。Jenkins 构建历史和镜像 tag 天然就是这份记录,不需要额外开发。回滚时查一下历史就知道该回到哪个版本,这个习惯在关键时刻能救命。
5.3 后续扩展方向
顺着这条路,后面可以扩展的方向很清晰:给 Maven 私服和镜像仓库做完整备份策略,给 Nacos 配置加定时备份,给网关接入限流和灰度发布能力,甚至把流水线从单服务参数化升级成全服务一键发布。但这些都是建立在当前这套稳定链路之上的——先把地基打牢,再想楼盖多高。
如果你也在折腾 SpringCloud 的持续部署,直接在 Jenkins + Docker 这条路上走下去,别被各种新概念带偏。把上面这些配置和脚本跑通,你的项目就已经超过大多数还在手动部署的团队了。至少我现在发布积木学堂的服务,只需要在 Jenkins 上点一个按钮,选一下服务名,剩下的活全都自动完成。这种感觉,值得你亲手体验一次。