1. 为什么要在流水线里构建Dubbo服务
做过微服务交付的人都有一个共识:本地能跑通的服务,到了K8s集群里未必能跑通;本地能打出来的包,到了流水线上未必能构建成功。Dubbo服务尤其如此,它不像普通的Spring Boot Web服务那样“一个jar包扔进去就能跑”,Dubbo涉及到注册中心、协议端口、序列化方式、服务分组、版本号、超时重试策略等一堆配置项,任何一个环节在流水线里出了偏差,都会导致服务注册不上、消费者调用超时、甚至整个链路雪崩。
我所在的团队负责过多个微服务项目的K8s交付,从最初的“本地打jar包手动上传镜像”到后来的“全自动流水线构建+部署”,中间踩过的坑可以说能写一本小册子。这篇文章就是把这些经验整理出来,围绕K8s交付实战中流水线构建Dubbo服务这个核心场景,把整体设计思路、关键细节、实操步骤、常见问题排查全部讲透。
这篇文章适合谁看?如果你正在做微服务架构的K8s迁移,或者你所在的团队正在搭建CI/CD流水线来交付Dubbo服务,又或者你是一个对DevOps流程感兴趣的后端开发者,那这篇内容应该能给你不少参考。我会尽量用“说人话”的方式把每个环节讲清楚,包括为什么这么选、参数怎么算、坑在哪里、怎么绕过去。
整个方案的核心思路是:用流水线把Dubbo服务的构建、镜像打包、配置注入、部署发布全部串起来,做到代码提交后自动触发,人工只需审批和验证。听起来简单,但每个环节都有讲究,下面逐一拆解。
2. 整体方案设计与技术选型拆解
2.1 流水线架构的核心组成
先说一下我们最终落地的流水线架构。整个流程从代码提交开始,到服务在K8s集群中注册成功并对外提供调用结束,中间经过了以下几个核心阶段:
- 代码拉取与依赖解析:从代码仓库拉取最新代码,解析Maven依赖,确保所有依赖包都能正常下载。
- 单元测试与代码质量扫描:跑单元测试,同时做静态代码扫描,确保代码质量达标。
- Maven构建与打包:执行Maven打包命令,生成可执行的Dubbo服务jar包。
- 镜像构建与推送:基于Dockerfile构建镜像,推送到镜像仓库。
- 配置注入与环境适配:根据目标环境(开发/测试/预发/生产)注入对应的配置参数。
- K8s资源渲染与部署:渲染K8s Deployment、Service等资源清单,执行部署。
- 健康检查与注册验证:检查Pod状态,验证Dubbo服务是否成功注册到注册中心。
这七个阶段看起来是线性的,但实际上有些步骤可以并行执行,比如单元测试和代码扫描可以同时跑,镜像构建和配置准备也可以并行。我们在实际落地时,把整个流水线控制在8到12分钟之间,具体取决于项目规模和依赖数量。
注意:流水线阶段划分不是越细越好。阶段太多会导致流水线执行时间过长,阶段太少又不利于问题定位。一般建议控制在6到8个阶段,每个阶段有明确的输入和输出。
2.2 为什么选择这样的技术栈组合
技术选型这块,我们评估过多个方案,最终确定的组合是:Jenkins + Maven + Docker + Harbor + K8s + Nacos。下面说说每个选择的理由。
Jenkins作为流水线引擎,主要是因为它生态成熟、插件丰富、团队上手成本低。虽然现在有很多新的CI/CD工具,但Jenkins在灵活性和可扩展性上依然有优势,特别是对于需要自定义构建步骤的场景。我们用的Jenkins Pipeline(声明式)来定义整个流程,代码化管理,方便版本控制。
Maven作为构建工具,这个没什么争议,Dubbo服务基本都是Maven项目。关键是要配置好Maven的settings.xml,包括私服地址、镜像仓库、认证信息等。我们用的是公司内部的Maven私服,所有依赖都从私服拉取,避免直接访问外部仓库导致构建不稳定。
Docker作为镜像打包工具,这个也是标配。Dubbo服务的镜像构建有几个关键点:基础镜像的选择、JVM参数的配置、时区设置、日志目录挂载等。我们用的是OpenJDK的基础镜像,后来换成了更轻量的Alpine版本,镜像体积从600多MB降到了200MB左右。
Harbor作为镜像仓库,主要是因为它支持镜像扫描、权限管理、复制策略等功能,适合企业级使用。镜像推送时要注意tag的命名规范,我们用的是“服务名-分支名-构建号”的格式,方便追溯。
K8s作为部署平台,这个不用多说。Dubbo服务在K8s中部署有几个特殊点:Pod的 readiness probe 要配置好,确保服务真正就绪后才接入流量;Service的端口映射要跟Dubbo的协议端口一致;注册中心的地址要通过环境变量或配置中心注入。
Nacos作为注册中心,相比Zookeeper,Nacos在服务发现、配置管理、健康检查方面更现代化,而且跟K8s的集成也更友好。Dubbo 2.7之后的版本对Nacos的支持已经很成熟了。
2.3 方案选型背后的关键考量
为什么不用“本地构建+手动部署”的方式?因为这种方式在微服务数量多了之后完全不可维护。我们统计过,一个中等规模的微服务项目大概有20到30个Dubbo服务,如果每个服务都手动构建部署,一次全量发布需要至少两个人花一整天时间,而且极易出错。
为什么不用“Jenkinsfile + Kubernetes插件”直接部署?因为Dubbo服务的配置管理比较复杂,不同环境的注册中心地址、数据库连接、超时参数都不一样,如果全部写死在Jenkinsfile里,维护起来很痛苦。我们的做法是把配置抽离到配置中心(Nacos Config),流水线只负责注入环境标识,服务启动时自己去配置中心拉取对应环境的配置。
为什么镜像构建不放在K8s集群内部做?因为镜像构建需要Docker daemon,而在K8s集群内部跑Docker构建需要挂载宿主机的Docker socket,存在安全风险。我们的做法是在Jenkins Agent上构建镜像,推送到Harbor,然后K8s从Harbor拉取镜像。这样职责清晰,也安全。
实操心得:技术选型没有绝对的对错,关键是看团队的技术栈和运维能力。如果团队对Jenkins不熟悉,可以考虑用GitLab CI或者Tekton,但核心思路是一样的——把构建、打包、部署串成一条自动化流水线。
3. 核心细节解析与实操要点
3.1 Dubbo服务打包的关键配置
Dubbo服务的打包跟普通Spring Boot应用最大的区别在于:Dubbo的配置文件需要支持多环境动态替换。我们踩过的坑是:本地开发时用的是本地注册中心地址,打包时如果直接把这个配置打进去,到了K8s环境就注册不上了。
解决方案是在Maven打包时使用profile机制,或者用Maven的资源过滤功能,把配置文件中的占位符替换成实际值。具体做法是在pom.xml中配置多个profile,每个profile对应一个环境,打包时通过-P参数指定。
<profiles> <profile> <id>dev</id> <properties> <dubbo.registry.address>nacos://nacos-dev:8848</dubbo.registry.address> </properties> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <id>prod</id> <properties> <dubbo.registry.address>nacos://nacos-prod:8848</dubbo.registry.address> </properties> </profile> </profiles>然后在application.properties或dubbo.properties中使用${dubbo.registry.address}占位符。Maven打包时会自动替换。
但这种方式有个问题:每次新增环境都要改pom文件,而且配置分散在各个服务的pom里,不好统一管理。后来我们改成了配置中心方案:打包时只打一个通用的jar包,服务启动时通过环境变量SPRING_PROFILES_ACTIVE指定环境,然后从Nacos Config拉取对应环境的配置。
# 启动命令示例 java -jar dubbo-service.jar --spring.profiles.active=prod这样打包一次,所有环境通用,配置变更也不需要重新打包,只需要在Nacos Config里修改配置并发布即可。
3.2 Dockerfile的编写要点
Dubbo服务的Dockerfile有几个关键点需要注意:
基础镜像选择:我们用的是openjdk:8-jre-alpine,体积小,安全性也还可以。如果项目用的是JDK 11或17,对应换成openjdk:11-jre-slim或openjdk:17-jre-slim。不建议用latest标签,因为基础镜像更新后可能导致构建失败。
时区设置:Alpine镜像默认是UTC时区,如果不设置,日志时间会跟北京时间差8小时,排查问题时会很痛苦。需要在Dockerfile里加上时区设置。
JVM参数配置:Dubbo服务对内存比较敏感,需要根据Pod的资源限制来设置JVM的堆内存。我们一般设置-Xms和-Xmx为Pod内存限制的70%左右,留出空间给元空间和线程栈。
FROM openjdk:8-jre-alpine # 设置时区 RUN apk add --no-cache tzdata && \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone # 创建应用目录 WORKDIR /app # 复制jar包 COPY target/dubbo-service.jar /app/app.jar # 暴露Dubbo协议端口 EXPOSE 20880 # 启动命令 ENTRYPOINT ["java", \ "-Xms512m", "-Xmx512m", \ "-XX:MetaspaceSize=128m", \ "-XX:MaxMetaspaceSize=256m", \ "-Djava.security.egd=file:/dev/./urandom", \ "-jar", "/app/app.jar"]注意:
-Djava.security.egd=file:/dev/./urandom这个参数是为了加快随机数生成速度,避免Tomcat启动时因为熵池不足而卡住。这个坑我们在早期遇到过,服务启动要等好几分钟,加上这个参数后秒启。
3.3 K8s资源清单的关键配置
Dubbo服务在K8s中的资源清单主要包括Deployment和Service。Deployment负责管理Pod,Service负责网络暴露。这里重点说几个容易出问题的地方。
readiness probe配置:Dubbo服务启动后需要向注册中心注册,注册成功后才算真正就绪。如果readiness probe配置得太激进,Pod还没注册成功就被标记为就绪,流量打进来会报错。我们的做法是配置一个HTTP健康检查接口,这个接口会检查Dubbo服务是否已经注册成功。
readinessProbe: httpGet: path: /health/dubbo port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3Service端口配置:Dubbo默认使用20880端口,需要在Service中暴露出来。如果用的是Dubbo的QoS功能,还需要暴露22222端口。
apiVersion: v1 kind: Service metadata: name: dubbo-service spec: selector: app: dubbo-service ports: - name: dubbo port: 20880 targetPort: 20880 - name: http port: 8080 targetPort: 8080环境变量注入:注册中心地址、命名空间、服务分组等信息通过环境变量注入,方便不同环境复用同一份资源清单。
env: - name: SPRING_PROFILES_ACTIVE value: "prod" - name: DUBBO_REGISTRY_ADDRESS value: "nacos://nacos-prod:8848" - name: DUBBO_REGISTRY_NAMESPACE value: "production"实操心得:K8s资源清单建议用Helm管理,把可变部分抽成values.yaml,不同环境用不同的values文件。这样资源清单可以版本化,回滚也方便。
4. 流水线实操过程与核心环节实现
4.1 Jenkins Pipeline的完整定义
我们用的是Jenkins声明式Pipeline,整个Jenkinsfile大概200多行。下面把核心部分拆解出来,你可以直接参考这个结构来写自己的流水线。
pipeline { agent any environment { APP_NAME = 'dubbo-service' REGISTRY = 'harbor.internal.com' PROJECT = 'microservice' IMAGE_TAG = "${APP_NAME}-${BRANCH_NAME}-${BUILD_NUMBER}" } stages { stage('代码拉取') { steps { checkout scm } } stage('单元测试') { steps { sh 'mvn clean test -DskipTests=false' } post { always { junit 'target/surefire-reports/*.xml' } } } stage('代码扫描') { steps { sh 'mvn sonar:sonar -Dsonar.projectKey=${APP_NAME}' } } stage('Maven构建') { steps { sh 'mvn clean package -DskipTests -Pprod' } } stage('镜像构建与推送') { steps { script { docker.build("${REGISTRY}/${PROJECT}/${APP_NAME}:${IMAGE_TAG}") docker.withRegistry("https://${REGISTRY}", 'harbor-credentials') { docker.image("${REGISTRY}/${PROJECT}/${APP_NAME}:${IMAGE_TAG}").push() docker.image("${REGISTRY}/${PROJECT}/${APP_NAME}:${IMAGE_TAG}").push('latest') } } } } stage('部署到K8s') { steps { sh """ sed -i 's|IMAGE_PLACEHOLDER|${REGISTRY}/${PROJECT}/${APP_NAME}:${IMAGE_TAG}|g' k8s/deployment.yaml kubectl apply -f k8s/deployment.yaml -n ${NAMESPACE} kubectl apply -f k8s/service.yaml -n ${NAMESPACE} """ } } stage('健康检查') { steps { sh """ for i in \$(seq 1 30); do STATUS=\$(kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME} -o jsonpath='{.items[0].status.phase}') if [ "\$STATUS" == "Running" ]; then echo "Pod is running" exit 0 fi sleep 10 done echo "Pod failed to start" exit 1 """ } } } post { failure { sh 'echo "流水线执行失败,请检查日志"' } } }这个Pipeline有几个设计要点值得说明:
环境变量集中管理:APP_NAME、REGISTRY、PROJECT这些变量在environment块中统一定义,避免在脚本中硬编码。IMAGE_TAG的命名规则是“服务名-分支名-构建号”,这样每个镜像都能追溯到具体的代码分支和构建记录。
单元测试与代码扫描并行:这两个阶段其实可以并行执行,但为了简化Pipeline结构,我们暂时是串行的。如果构建时间紧张,可以用parallel块让它们并行跑。
镜像构建用Docker Pipeline插件:这个插件提供了docker.build和docker.withRegistry等便捷方法,比直接写shell命令更清晰。注意harbor-credentials需要在Jenkins的凭据管理中提前配置好。
部署阶段用sed替换镜像地址:这是一种简单粗暴但有效的方式。更好的做法是用Helm的--set参数或者Kustomize来管理镜像地址,但sed方式胜在简单直接,适合小团队快速落地。
4.2 多环境配置注入的实现
多环境配置注入是Dubbo服务流水线中最容易出问题的环节。我们的做法是:镜像只构建一次,配置在部署时注入。
具体来说,Dockerfile中不包含任何环境相关的配置,所有配置都通过环境变量或配置中心注入。K8s的Deployment中通过env字段注入环境标识,服务启动时根据环境标识去Nacos Config拉取对应配置。
# deployment.yaml 片段 spec: template: spec: containers: - name: dubbo-service image: IMAGE_PLACEHOLDER env: - name: SPRING_PROFILES_ACTIVE value: "prod" - name: NACOS_CONFIG_ADDR value: "nacos-prod:8848" - name: NACOS_CONFIG_NAMESPACE value: "production" - name: JAVA_OPTS value: "-Xms512m -Xmx512m -XX:MetaspaceSize=128m"然后在服务的bootstrap.properties中配置:
spring.application.name=dubbo-service spring.cloud.nacos.config.server-addr=${NACOS_CONFIG_ADDR} spring.cloud.nacos.config.namespace=${NACOS_CONFIG_NAMESPACE} spring.cloud.nacos.config.file-extension=yaml这样服务启动时会自动从Nacos Config拉取dubbo-service-prod.yaml的配置,里面包含了Dubbo的注册中心地址、协议配置、超时参数等。
注意:Nacos Config的namespace要用ID而不是名称,因为名称可能重复。在Nacos控制台创建namespace后,复制它的ID配置到环境变量中。
4.3 镜像构建的优化实践
镜像构建这块,我们经历过几个阶段的优化。最初是直接用mvn package打出来的jar包构建镜像,镜像体积大概600MB。后来做了以下优化,体积降到了200MB左右:
使用Alpine基础镜像:从openjdk:8-jre换成openjdk:8-jre-alpine,体积直接减少一半。
分层构建:把依赖下载和代码编译分开,利用Docker的缓存机制。不过对于Dubbo服务来说,最终镜像里只需要jar包,所以分层构建的收益不大。
清理不必要的文件:在Dockerfile中删除临时文件、日志文件、测试文件等。
FROM openjdk:8-jre-alpine RUN apk add --no-cache tzdata && \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone && \ apk del tzdata WORKDIR /app COPY target/dubbo-service.jar /app/app.jar # 清理不必要的文件 RUN rm -rf /tmp/* /var/cache/apk/* EXPOSE 20880 8080 ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]注意最后一行改成了sh -c的方式,这样可以在启动时通过JAVA_OPTS环境变量动态调整JVM参数,而不需要重新构建镜像。
4.4 部署策略与滚动更新
Dubbo服务的部署策略需要特别注意,因为服务注册和摘除不是瞬间完成的。如果滚动更新时新Pod还没注册成功就杀掉旧Pod,会导致调用方找不到服务提供者。
我们的做法是配置maxSurge和maxUnavailable参数,控制滚动更新的节奏:
strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0maxUnavailable: 0表示在更新过程中,不可用的Pod数量为0,也就是说必须先启动新Pod并确认就绪,才会杀掉旧Pod。maxSurge: 1表示最多可以比期望副本数多出1个Pod。
配合readiness probe使用,整个更新过程是:新Pod启动 -> 注册到Nacos -> readiness probe通过 -> 旧Pod被摘除 -> 旧Pod下线。这样对调用方完全无感知。
实操心得:Dubbo服务的优雅停机也很重要。在Pod被删除前,需要先向注册中心注销,然后等待一段时间让调用方刷新服务列表,最后才真正关闭进程。可以通过配置Dubbo的
shutdown.wait参数和K8s的preStop钩子来实现。
5. 常见问题与排查技巧实录
5.1 流水线构建失败的典型原因
在流水线构建Dubbo服务的过程中,我们遇到过各种各样的问题。下面整理了一个速查表,涵盖了最常见的失败原因和解决方法。
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| Maven依赖下载失败 | 私服地址配置错误或网络不通 | 检查settings.xml和网络连通性 | 修正私服地址,配置镜像仓库 |
| 单元测试失败 | 代码逻辑错误或测试环境问题 | 查看surefire报告 | 修复代码或调整测试配置 |
| 镜像构建失败 | Dockerfile语法错误或基础镜像拉取失败 | 查看docker build日志 | 修正Dockerfile,检查镜像仓库 |
| 镜像推送失败 | Harbor认证失败或磁盘空间不足 | 检查Jenkins凭据和Harbor状态 | 更新凭据,清理Harbor空间 |
| Pod启动失败 | 配置错误或资源不足 | kubectl describe pod查看事件 | 修正配置,调整资源限制 |
| 服务注册失败 | 注册中心地址错误或网络不通 | 查看服务启动日志 | 修正注册中心地址,检查网络策略 |
| 健康检查失败 | 接口未实现或端口配置错误 | 手动curl健康检查接口 | 实现健康检查接口,修正端口 |
这个表格看起来简单,但每一条都是我们实际踩过的坑。比如“Maven依赖下载失败”这个问题,我们遇到过一次是因为私服的证书过期了,导致所有构建都失败,排查了半天才发现是证书问题。
5.2 Dubbo服务注册失败的排查思路
Dubbo服务注册失败是最常见也最让人头疼的问题。服务启动日志看起来正常,但就是在注册中心看不到服务。我们的排查思路是这样的:
第一步:确认注册中心地址是否正确。检查环境变量DUBBO_REGISTRY_ADDRESS是否注入成功,可以在Pod里执行env | grep DUBBO查看。
第二步:确认网络是否连通。在Pod里执行telnet nacos-prod 8848或者curl nacos-prod:8848,看是否能通。如果不通,检查K8s的NetworkPolicy和Service配置。
第三步:确认命名空间和分组是否正确。Dubbo的注册中心可以配置namespace和group,如果服务提供者和消费者配置不一致,就会导致服务找不到。检查Nacos控制台中的服务列表,确认服务是否注册到了正确的命名空间和分组。
第四步:查看Dubbo的启动日志。Dubbo在启动时会打印注册中心的连接信息和注册结果。如果注册失败,日志中会有明显的错误信息。常见的错误包括“Failed to register service”和“No provider available”。
第五步:检查Dubbo的QoS端口。Dubbo 2.7之后默认开启了QoS功能,端口是22222。如果这个端口被占用或者被防火墙拦截,可能会影响服务注册。可以通过dubbo.application.qos-enable=false关闭QoS功能。
注意:Dubbo服务注册失败有时是因为服务启动时间太长,超过了readiness probe的initialDelaySeconds。可以适当调大这个值,或者优化服务启动速度。
5.3 镜像拉取失败的排查与解决
K8s拉取镜像失败也是常见问题,特别是在跨环境部署时。错误信息通常是ImagePullBackOff或ErrImagePull。
原因一:镜像地址错误。检查Deployment中的image字段是否跟实际推送的镜像地址一致。注意Harbor的项目路径和镜像tag是否正确。
原因二:Harbor认证失败。K8s拉取私有仓库的镜像需要配置imagePullSecrets。检查Secret是否存在,并且跟ServiceAccount关联正确。
# 创建imagePullSecret kubectl create secret docker-registry harbor-secret \ --docker-server=harbor.internal.com \ --docker-username=admin \ --docker-password=Harbor12345 \ -n production # 在Deployment中引用 spec: template: spec: imagePullSecrets: - name: harbor-secret原因三:网络不通。K8s节点无法访问Harbor仓库。检查节点的网络配置和DNS解析。
原因四:镜像不存在。流水线推送镜像失败,但部署阶段没有检查。可以在部署前加一个检查步骤,确认镜像已经推送成功。
5.4 服务调用超时的排查技巧
Dubbo服务调用超时是另一个高频问题。消费者调用提供者时抛出TimeoutException,但提供者日志显示请求已经处理完成。这种情况通常是网络延迟或者Dubbo的超时配置不合理导致的。
排查步骤:
检查Dubbo的超时配置。默认超时是1000ms,如果业务处理时间较长,需要调大这个值。可以在提供者端配置
dubbo.provider.timeout=5000,或者在消费者端配置dubbo.consumer.timeout=5000。检查网络延迟。在Pod里执行
ping或traceroute,看网络延迟是否正常。K8s集群内部的网络延迟一般在1ms以内。检查线程池配置。Dubbo的提供者默认使用固定大小的线程池(200个线程),如果并发请求量很大,线程池满了之后请求会排队,导致超时。可以通过
dubbo.provider.threads=500调大线程池。检查序列化方式。Dubbo默认使用Hessian2序列化,如果传输的对象很大,序列化时间会很长。可以考虑换成Kryo或FST等更高效的序列化方式。
实操心得:Dubbo的超时配置建议遵循“消费者超时 > 提供者超时”的原则。比如提供者配置3秒超时,消费者配置5秒超时。这样如果提供者处理超时,消费者还能收到提供者的超时异常,而不是直接抛出自己的超时异常,便于问题定位。
5.5 流水线执行时间过长的优化
流水线执行时间太长会影响开发效率。我们最初的流水线要跑20多分钟,后来优化到了8分钟左右。主要的优化手段包括:
并行执行:把单元测试和代码扫描并行跑,节省3到5分钟。
缓存Maven依赖:在Jenkins Agent上配置Maven本地仓库缓存,避免每次构建都重新下载依赖。可以用Jenkins的Cache插件或者直接挂载宿主机目录。
增量构建:对于多模块项目,只构建变更的模块。可以用Maven的-pl和-am参数指定模块。
镜像构建优化:使用Docker的缓存机制,把不经常变动的层放在前面。比如先复制pom.xml下载依赖,再复制源代码编译。
跳过不必要的步骤:比如在开发分支上跳过代码扫描,只在合并到主分支时执行。
stage('代码扫描') { when { branch 'main' } steps { sh 'mvn sonar:sonar' } }这个when条件表示只在main分支上执行代码扫描,其他分支跳过。这样开发分支的构建时间可以缩短不少。
6. 一些踩坑后的经验总结
流水线构建Dubbo服务这件事,说起来就是“构建-打包-部署”三个步骤,但每个步骤里都有大量的细节需要处理。我最大的体会是:不要追求一步到位的完美方案,而是先跑通最小闭环,再逐步优化。
我们最开始的时候,流水线只做了“拉代码-Maven构建-打镜像-推镜像”这四步,部署还是手动执行的。但就是这四步,已经帮我们节省了大量的时间,也避免了“本地能跑、线上报错”的问题。后来才慢慢加上了自动部署、健康检查、多环境配置注入等功能。
另一个体会是:日志和监控一定要做好。流水线执行失败时,如果日志不完整,排查起来非常痛苦。我们在每个阶段都加了详细的日志输出,包括执行的命令、环境变量、执行结果等。K8s那边也配置了Pod的日志收集,方便出问题时快速定位。
还有一个容易被忽视的点是镜像tag的管理。我们最开始用latest标签,结果每次部署都不知道用的是哪个版本的镜像。后来改成了“服务名-分支名-构建号”的格式,每次部署都能追溯到具体的代码提交,回滚也方便。
最后说一个关于Dubbo服务优雅停机的小技巧。在K8s的Deployment中配置preStop钩子,让Pod在删除前先执行一段脚本,向注册中心注销服务,然后等待一段时间再退出。
lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 30"]这个sleep 30秒的作用是给调用方足够的时间刷新服务列表。配合Dubbo的shutdown.wait参数使用,可以实现真正的优雅停机,调用方完全无感知。
这个方案后续还可以扩展的方向包括:接入Prometheus监控Dubbo的调用指标、用Istio做服务网格、用Argo CD做GitOps部署等。但那是另一个话题了,先把基础的流水线跑稳,再考虑这些高级特性。