微服务项目一旦模块数量增多,靠本地手动编译再拷贝发布的方式基本走不通了。一个稍具规模的后端系统往往包含网关、用户服务、订单服务、消息服务等多个可独立部署的单元,每次发版都要逐个编译、打镜像、推仓库、更新集群配置,稍有疏忽就会出现版本不一致的故障。用 Jenkins 把这一整套动作固化成流水线,是 .NET 团队比较常见也是比较成熟的选择。本文从零开始搭建一条覆盖编译、测试、打包、部署的 .NET 微服务流水线,并顺带聊几个实战中容易踩的坑。

环境准备:让 Jenkins 能跑 .NET 构建
Jenkins 本身是一个 Java 应用,跟 .NET 没有直接绑定关系,真正执行构建的是它调度的执行节点。所以第一步要保证执行节点上具备 .NET SDK。如果你的服务还停留在 .NET Framework,需要节点是 Windows 机器并安装对应的 MSBuild 工具链;如果是 .NET 6 或 .NET 8 这类新版本,直接安装 SDK 即可,跨平台部署会更省心。
推荐用容器方式跑 Jenkins,这样升级和环境迁移都方便。用下面的命令启动一个 Jenkins 实例,同时把宿主机的 Docker socket 挂进去,方便后续在流水线里执行 docker 命令打包镜像:
docker run -d --name jenkins \ -p 8080:8080 -p 50000:50000 \ -v jenkins_home:/var/jenkins_home \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $(which docker):/usr/bin/docker \ --restart=always jenkins/jenkins:lts
启动后进入容器装好 .NET SDK,或者干脆自己基于 Jenkins 镜像构建一个包含 SDK 的自定义镜像。需要安装的插件主要有 Pipeline、Git、Docker Pipeline、Credentials Binding 这几个,都是配置多阶段流水线和镜像操作的基础。另外别忘了在系统配置里添加全局工具,比如 dotnet 的路径,后面在 Jenkinsfile 里可以直接引用。
编写 Jenkinsfile:声明式流水线覆盖核心阶段
声明式流水线结构清晰,可读性强,适合作为团队的统一规范。一条完整的 .NET 微服务流水线通常包含拉取代码、还原依赖、编译、单元测试、发布、镜像构建推送这几个阶段。下面是一个可以直接落地的模板:
pipeline {
agent any
environment {
REGISTRY = 'registry.ipipp.com/myteam'
IMAGE_TAG = "${env.BUILD_NUMBER}"
SERVICE = 'order-service'
}
stages {
stage('检出代码') {
steps {
checkout scm
}
}
stage('还原与编译') {
steps {
sh 'dotnet restore src/${SERVICE}/${SERVICE}.csproj'
sh 'dotnet build src/${SERVICE}/${SERVICE}.csproj -c Release --no-restore'
}
}
stage('单元测试') {
steps {
sh 'dotnet test tests/${SERVICE}.Tests -c Release --logger trx'
}
}
stage('发布与打镜像') {
steps {
sh 'dotnet publish src/${SERVICE}/${SERVICE}.csproj -c Release -o publish'
script {
docker.withRegistry("https://${REGISTRY}", 'docker-hub-cred') {
def img = docker.build("${REGISTRY}/${SERVICE}:${IMAGE_TAG}", 'publish')
img.push()
img.push('latest')
}
}
}
}
}
post {
failure {
emailext subject: "构建失败:${SERVICE}",
body: '请查看流水线日志排查问题',
to: 'dev-team@ipipp.com'
}
}
}几个细节值得注意。第一,restore 和 build 拆开执行并加上 --no-restore,可以让每个阶段的职责更明确,日志也更容易定位问题。第二,镜像标签不要只用 latest,用构建号加 Git 提交短哈希组合(比如 42-a1b2c3d)能保证每次发布的镜像都可追溯,回滚时直接指定历史标签即可。第三,单元测试失败会中断流水线,这是刻意为之的行为,带伤上线的代价远大于跳过测试省下的几分钟。
凭据管理是新手常犯错误的地方。镜像仓库的账号密码、数据库连接串这类敏感信息,一定要放在 Jenkins 的 Credentials 里,通过 withCredentials 或环境注入的方式使用,绝对不要明文写进 Jenkinsfile 提交到代码仓库。一旦仓库泄露,明文凭据等于把生产环境的钥匙拱手送人。
多模块微服务的并行构建与缓存加速
当服务数量超过五六个,串行构建的耗时会明显拖慢发版节奏。声明式流水线支持用矩阵或并行 stage 处理这个问题。可以把服务列表定义成数组,再用 parallel 块同时触发多条构建链:
def services = ['gateway', 'user-service', 'order-service', 'notify-service']
pipeline {
agent any
stages {
stage('并行构建') {
steps {
script {
def branches = [:]
for (svc in services) {
def s = svc
branches[s] = {
stage("${s} 编译测试") {
sh "dotnet test src/${s} -c Release"
}
stage("${s} 打镜像") {
sh "docker build -t registry.ipipp.com/myteam/${s}:${BUILD_NUMBER} src/${s}"
}
}
}
parallel branches
}
}
}
}
}注意循环里那行 def s = svc 不是多余的,Groovy 闭包捕获变量的方式决定了不这样做的话,所有并行分支引用的可能是同一个值。另外并行度要结合节点资源控制,每个 dotnet 编译进程占用内存不小,四核节点同时跑六七个编译任务反而可能触发 OOM,合理做法是配置多个执行节点或者限制并发数。
加速方面还有两个实用手段。一是利用 NuGet 缓存,把 ~/.nuget/packages 目录持久化到宿主机或者挂载卷,依赖包不用每次全量下载,大项目能省两三分钟。二是对 Docker 镜像做分层优化,把 csproj 文件单独拷贝并先执行 restore,利用构建缓存让依赖层在代码不变时直接复用:
FROM mcr.microsoft.com/dotnet/aspnet:8.0 WORKDIR /app COPY src/OrderService/*.csproj ./ RUN dotnet restore COPY src/OrderService/ ./ RUN dotnet publish -c Release -o out --no-restore ENTRYPOINT ["dotnet", "out/OrderService.dll"]
对接部署环节与常见坑位总结
构建出镜像只是流水线的一半,部署环节同样要纳入自动化。如果团队用 Kubernetes,推荐在 Jenkinsfile 末尾加一个部署阶段,通过 kubectl set image 或者 kubectl apply 更新工作负载镜像。更规范的做法是用 Helm 管理每个服务的部署模板,流水线只需执行 helm upgrade 并传入新的镜像标签,配置变更和版本变更都走同一套通道,可审计性更好。开发测试环境可以配置成提交即部署,生产环境则建议加一个手动审批节点,用 input 步骤让负责人确认后再继续。
实践中几个高频坑位提前说一下。Jenkins 容器内跑 docker 命令涉及 socket 权限,最简单的处理是给 socket 文件加读写权限,生产环境更推荐 DinD 或独立的构建节点。Git 大仓库拉取慢可以配置浅克隆,在检出步骤里指定浅克隆深度为一即可。还有 dotnet test 生成的 trx 结果文件,配合 Jenkins 的 xUnit 插件可以在构建页面直接看到测试报告,比翻日志直观得多。把这些问题提前处理好,一条稳定跑几个月不用操心的 .NET 微服务流水线就算真正落地了。