基于Jenkins Pipeline的ASP.NET Core持续集成实践
最近在公司实践持续集成,使用到了Jenkins的Pipeline来提高团队基于ASP.NET Core API服务的集成与部署效率,因此这里总结一下。
一、关于持续集成与Jenkins Pipeline
1.1 持续集成相关概念

互联网软件的开发和发布,已经形成了一套标准流程,最重要的组成部分就是持续集成(Continuous integration,简称 CI) 。
持续集成指的是,频繁地 (一天多次) 将代码集成到主干。
它的好处主要有两个:
(1)快速发现错误。每完成一点更新,就集成到主干,可以快速发现错误,定位错误也比较容易。
(2)防止分支大幅偏离主干。如果不是经常集成,主干又在不断更新,会导致以后集成的难度变大,甚至难以集成。
持续集成的目的,就是让产品可以快速迭代,同时还能保持高质量。
Martin Fowler 说:“ 持续集成并不能消除 Bug,而是让它们非常容易发现和改正。”
与持续集成相关的,还有持续交付和持续部署。
持续交付指的是:频繁地将软件的新版本,交付给质量团队或者用户,以供评审。如果评审通过,代码就进入生产阶段。它强调的是,不管怎么更新,软件是随时随地可以交付的。

持续部署是持续交付的下一步,指的是代码通过评审以后,自动部署到生产环境。它强调的是代码在任何时刻都是可部署的,可以进入生产阶段。

1.2 Jenkins Pipeline

Jenkins 是一款流行的开源持续集成(CI)与持续部署(CD)工具,广泛用于项目开发,具有自动化构建、测试和部署等功能。有关Jenkins的安装,可以参考我的这一篇文章进行安装。
相信很多童鞋都已经在使用Jenkins或者计划使用Jenkins来代替传统的人工发布流程了,因此我们创建了很多自由风格(Free Style)的构建任务用于多个Job,而我们经常会听到说流水线任务,那么流水线是什么呢?
流水线Pipeline是一套运行于Jenkins上的工作流框架,将原本独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂流程编排与可视化。下图是一个Jenkins Pipeline的实例效果:

Pipeline :Build => Test => Deploy
这里涉及到Pipeline中的几个重要概念,需要了解一下:
- Stage: 阶段,一个Pipeline可以划分为若干个Stage,每个Stage代表一组操作。注意,Stage是一个逻辑分组的概念,可以跨多个Node。如上图所示,Build,Test和Deploy就是Stage,代表了三个不同的阶段:编译、测试和部署。
- Node: 节点,一个Node就是一个Jenkins节点,或者是Master,或者是Slave,是执行Step的具体运行期环境。
- Step: 步骤,Step是最基本的操作单元,小到创建一个目录,大到构建一个Docker镜像,由各类Jenkins Plugin提供。
二、准备ASP.NET Core Docker环境
2.1 安装Docker环境

可以参考我的这一篇《.NET Core微服务之ASP.NET Core on Docker》来安装和配置Docker环境,建议在Linux环境下配置。
2.2 安装SFTP服务
在Linux下,SSH服务默认会安装,而在Windows Server下,需要单独安装,可以借助FreeSSHD这个免费工具来实现。由于我的物理机都是Windows Server,物理机上的VM是Linux(Docker运行环境),所以需要给物理机配置FreeSSHD,用来实现从CI服务器发布Release到物理服务器中的VM。
至于如何安装配置FreeSSHD,可以参考这一篇《freeSSHD在windows环境下搭建SFTP服务器》。
三、配置Jenkins Pipeline流水线任务
3.1 总体目标
(1)持续集成:实现编译+单元测试的自动运行

这里我要实现的目标是:当有人push代码到git server中(这里我使用的git server是Gogs,需要给Gogs设置一个Webhook,如下图所示,需要注意的是设置的密钥文本要和在Pipeline中填写的一致,否则Jenkins无法正确接收Web钩子),git server会触发一个webhook发送一个post的请求给CI server,CI server会触发Pipeline任务的构建,一路pull代码+编译+单元测试。

(2)持续发布:实现编译+发布到具体的测试环境

由于在开发阶段,我不需要每次Push都进行发布,因此我这里设置的是手动在Jenkins中触发发布任务来实现自动化发布。
3.2 全局设置
首先,肯定是Jenkins的插件安装了。
(1)Generic WebHook Trigger => 触发WebHook必备
(2)Gogs Plugin => 因为我使用的Git Server是Gogs搭建的
(3)MSBuild Plugin => 进行sln、csproj项目文件的编译
(4)MSTest & xUnit => 进行基于MSTest或基于xUnit的单元测试
(5)Nuget Plugin => 拉取Nuget包必备
(6)Pipeline => 实现Pipeline任务必备,建议将Pipeline相关插件都安装上
(7)Powershell Plugin => 如果你的CI服务器是基于Windows的,那么安装一下Powershell插件来执行命令吧
(8)Publish Over SSH => 远程发布Release必备
(9)WallDisplay => 电视投屏构建任务列表必备
其次,为了提示邮件,也要Email插件(Email Extension)的支持,并进行以下配置:
(1)第一处:Jenkins Location

(2)第二处:Email扩展插件全局变量设置

这里主要是需要设置Subject和Content,就可以在各个Pipeline中使用了。因此,这里贴出我的Default Content内容:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
</head> <body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4"
offset="0">
<table width="95%" cellpadding="0" cellspacing="0" style="font-size: 11pt; font-family: Microsoft YaHei, Tahoma, Arial, Helvetica">
<tr>
<td>各位同事,大家好,以下为 ${PROJECT_NAME } 构建任务信息</td>
</tr>
<tr>
<td><br />
<b style="font-weight:bold; color:#66cc00">构建信息</b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td>
<ul>
<li>任务名称 : ${PROJECT_NAME}</li>
<li>构建编号 : 第${BUILD_NUMBER}次构建</li>
<li>触发原因: ${CAUSE}</li>
<li>构建状态: <span style="font-weight:bold; color:#FF0000">${BUILD_STATUS}</span></li>
<li>构建日志: <a href="${BUILD_URL}console">${BUILD_URL}console</a></li>
<li>构建 Url : <a href="${BUILD_URL}">${BUILD_URL}</a></li>
<li>工作目录 : <a href="${PROJECT_URL}ws">${PROJECT_URL}ws</a></li>
<li>项目 Url : <a href="${PROJECT_URL}">${PROJECT_URL}</a></li>
</ul>
</td>
</tr>
</table>
</body>
</html>

为了能够发给更多的人,建议勾选以上两个选项。

这里是Email通知必填的SMTP服务器配置。
最后,是SSH服务器的声明,指定可以进行SSH发布的服务器有哪些,IP又是多少:

3.3 新增Pipeline脚本
(1)持续集成Pipeline
首先,填写Webhook的密钥文本:

其次,Build Triggers的时机选择“Build when a change is pushed to Gogs”,即有人push代码到仓库就触发。当然,这里需要提前在Gogs设置Webhook。

其次,编写Pipeline脚本,各个Stage写清楚职责:

具体的Pipeline脚本在下边:
pipeline{
agent any
stages {
stage('XDP Core Services Checkout') {
steps{
checkout([$class: 'GitSCM', branches: [[name: '*/dev-xds']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '35b9890b-2338-45e2-8a1a-78e9bbe1d3e2', url: 'http://192.168.18.150:3000/EDC.ITC.XDP.Core/EDC.XDP.Core.git']]])
echo 'Core Services Checkout Done'
}
}
stage('XDP Core Services Build') {
steps{
bat '''cd "D:\\Jenkins\\workspace\\XDS.Dev.CI.Pipeline\\src\\services\\EDC.XDP.Core\\"
dotnet build EDC.XDP.Core-All.sln'''
echo 'Core Services Build Done'
}
}
stage('Core Delivery Service Unit Test') {
steps{
bat '''cd "D:\\Jenkins\\workspace\\XDS.Dev.CI.Pipeline\\src\\services\\EDC.XDP.Core\\Services\\EDC.XDP.Core.Delivery.UnitTest"
dotnet test -v n --no-build EDC.XDP.Core.Delivery.UnitTest.csproj'''
echo 'Core Delivery Service Unit Test Done'
}
}
stage('XDS Delivery Service Checkout') {
steps{
checkout([$class: 'GitSCM', branches: [[name: '*/dev-service']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '35b9890b-2338-45e2-8a1a-78e9bbe1d3e2', url: 'http://192.168.18.150:3000/EDC.ITC.XDP.XDS/EDC.XDP.XDS.git']]])
echo 'Core Delivery Service Checkout Done'
}
}
stage('XDS Delivery Service Build') {
steps{
bat '''cd "D:\\Jenkins\\workspace\\XDS.Dev.CI.Pipeline\\src\\services\\EDC.XDP.XDS"
dotnet build EDC.XDP.XDS.sln'''
echo 'XDS Service Build Done'
}
}
stage('XDS Delivery Service Unit Test') {
steps{
bat '''cd "D:\\Jenkins\\workspace\\XDS.Dev.CI.Pipeline\\src\\services\\EDC.XDP.XDS\\EDC.XDP.XDS.Delivery.UnitTest"
dotnet test -v n --no-build EDC.XDP.XDS.Delivery.UnitTest.csproj'''
echo 'XDS Service Unit Test Done'
}
}
}
post{
failure {
emailext (
subject: '${DEFAULT_SUBJECT}',
body: '${DEFAULT_CONTENT}',
to: "edisonchou@qq.com,xxxxx@qq.com")
}
}
}
(2)持续发布Pipeline
持续发布Pipeline与持续集成Pipeline类似,只是在脚本处有所不同:
pipeline{
agent any
stages {
stage('Core Delivery Service Checkout') {
steps{
checkout([$class: 'GitSCM', branches: [[name: '*/dev-xds']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '35b9890b-2338-45e2-8a1a-78e9bbe1d3e2', url: 'http://192.168.18.150:3000/EDC.ITC.XDP.Core/EDC.XDP.Core.git']]])
echo 'Core Delivery Service Dev Branch Checkout Done'
}
}
stage('Core Delivery Service Build & Publish') {
steps{
bat '''cd "D:\\Jenkins\\workspace\\XDS.API.Dev.CD.Pipeline\\src\\services\\EDC.XDP.Core"
dotnet build EDC.XDP.Core-DataServices.sln
dotnet publish "%WORKSPACE%\\src\\services\\EDC.XDP.Core\\Services\\EDC.XDP.Core.Delivery.API\\EDC.XDP.Core.Delivery.API.csproj" -o "%WORKSPACE%\\EDC.XDP.Core.Delivery.API/publish" --framework netcoreapp2.1
'''
echo 'Core Delivery Service Build & Publish Done'
}
}
stage('Core Delivery Service Deploy To 190 Server') {
steps{
sshPublisher(publishers: [sshPublisherDesc(configName: 'XDP-DEV-Server', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''docker stop xdp_core_deliveryservice; docker rm xdp_core_deliveryservice; docker run --ulimit core=0 --restart=always -v /etc/localtime:/etc/localtime -d -e ASPNETCORE_ENVIRONMENT=dev --privileged=true --name=xdp_core_deliveryservice -p 8010:80 -v /XiLife/publish/EDC.XDP.Core.Delivery.API/:/app -w /app xdp_service_runtime:latest dotnet EDC.XDP.Core.Delivery.API.dll''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'EDC.XDP.Core.Delivery.API/', remoteDirectorySDF: false, removePrefix: 'EDC.XDP.Core.Delivery.API/publish/', sourceFiles: 'EDC.XDP.Core.Delivery.API/publish/**')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
echo 'Delivery Service Deploy To 190 Done'
}
}
stage('Core Delivery Service Deploy To 175 Server') {
steps{
sshPublisher(publishers: [sshPublisherDesc(configName: 'XDP-DEV-MT-Server', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''docker stop xdp_core_deliveryservice; docker rm xdp_core_deliveryservice; docker run --ulimit core=0 --restart=always -v /etc/localtime:/etc/localtime -d -e ASPNETCORE_ENVIRONMENT=devmt --privileged=true --name=xdp_core_deliveryservice -p 8010:80 -v /XiLife/publish/EDC.XDP.Core.Delivery.API/:/app -w /app xdp_service_runtime:latest dotnet EDC.XDP.Core.Delivery.API.dll''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'EDC.XDP.Core.Delivery.API/', remoteDirectorySDF: false, removePrefix: 'EDC.XDP.Core.Delivery.API/publish/', sourceFiles: 'EDC.XDP.Core.Delivery.API/publish/**')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
echo 'Delivery Service Deploy To 175 Done'
}
}
stage('XDS Delivery Service Checkout') {
steps{
checkout([$class: 'GitSCM', branches: [[name: '*/dev-service']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '35b9890b-2338-45e2-8a1a-78e9bbe1d3e2', url: 'http://192.168.18.150:3000/EDC.ITC.XDP.XDS/EDC.XDP.XDS.git']]])
echo 'XDS Delivery Service Checkout Done'
}
}
stage('XDS Delivery Service Build & Publish') {
steps{
bat '''cd "D:\\Jenkins\\workspace\\XDS.API.Dev.CD.Pipeline\\src\\services\\EDC.XDP.XDS"
dotnet build EDC.XDP.XDS.sln
dotnet publish "%WORKSPACE%\\src\\services\\EDC.XDP.XDS\\EDC.XDP.XDS.Delivery.API\\EDC.XDP.XDS.Delivery.API.csproj" -o "%WORKSPACE%\\EDC.XDP.XDS.Delivery.API/publish" --framework netcoreapp2.1
'''
echo 'XDS Delivery Service Build & Publish Done'
}
}
stage('XDS Delivery Service Deploy To 190 Server') {
steps{
sshPublisher(publishers: [sshPublisherDesc(configName: 'XDP-DEV-Server', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''docker stop xdp_xds_delivery_service;docker rm xdp_xds_delivery_service; docker run --ulimit core=0 --restart=always -v /etc/localtime:/etc/localtime -d -e ASPNETCORE_ENVIRONMENT=dev --privileged=true --name=xdp_xds_delivery_service -p 9020:80 -v /XiLife/publish/EDC.XDP.XDS.Delivery.API/:/app -w /app xdp_service_runtime:latest dotnet EDC.XDP.XDS.Delivery.API.dll''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'EDC.XDP.XDS.Delivery.API/', remoteDirectorySDF: false, removePrefix: 'EDC.XDP.XDS.Delivery.API/publish/', sourceFiles: 'EDC.XDP.XDS.Delivery.API/publish/**')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
echo 'XDS Delivery Service Deploy to 190 Done'
}
}
stage('XDS Delivery Service Deploy To 175 Server') {
steps{
sshPublisher(publishers: [sshPublisherDesc(configName: 'XDP-DEV-MT-Server', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''docker stop xdp_xds_delivery_service;docker rm xdp_xds_delivery_service; docker run --ulimit core=0 --restart=always -v /etc/localtime:/etc/localtime -d -e ASPNETCORE_ENVIRONMENT=devmt --privileged=true --name=xdp_xds_delivery_service -p 9020:80 -v /XiLife/publish/EDC.XDP.XDS.Delivery.API/:/app -w /app xdp_service_runtime:latest dotnet EDC.XDP.XDS.Delivery.API.dll''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'EDC.XDP.XDS.Delivery.API/', remoteDirectorySDF: false, removePrefix: 'EDC.XDP.XDS.Delivery.API/publish/', sourceFiles: 'EDC.XDP.XDS.Delivery.API/publish/**')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
echo 'XDS Delivery Service Deploy to 175 Done'
}
}
}
}
这里由于我的测试环境分为两个,一个是开发人员联调环境190,另一个是集成测试环境175,统一在一个Pipeline任务中进行发布。
对于Master分支,我们还可以将Web系统的发布也集成到同一个Pipeline任务中,实现一个一条龙的发布流水线任务,由于各个Web系统的实现技术不一样,这里就不再贴脚本了。
四、效果演示
(1)持续集成示例

(2)持续发布示例

(3)构建失败告警

(4)构建大屏显示

再来一张投屏到工作区域电视屏幕中的效果,大家抬头就可以看到构建结果,是绿了还是红了?当然,我们都喜欢“绿”的,呼呼。

五、小结
借助持续集成和持续发布,我们开发人员可以节省很多质量保证和发布部署的时间,从而减少很多因为人为QA和Deploy造成的失误影响,从另一个层面上,它也可以使我们避免996(好吧,虽然关联有点牵强)。后续,我还会探索K8S,到时候希望能够分享一个ASP.NET Core on K8S的系列文章,敬请期待。
参考资料
大宝鱼,《玩转Jenkins Pipeline》
李志强,《Jenkins高级用法 - Pipeline 安装》
李志强,《Jenkins高级用法 - Jenkinsfile 介绍及实战经验》
三只松鼠,《jenkins + pipeline构建自动化部署》
ofnhkb1,《.NET项目从CI到CD-Jenkins_Pipeline的应用》
基于Jenkins Pipeline的ASP.NET Core持续集成实践的更多相关文章
- 基于Jenkins的开发测试全流程持续集成实践
今年一直在公司实践CI,本文将近半年来的一些实践总结一下,可能不太完善或优美,但的确初步解决了我目前所在项目组的一些痛点.当然这仅是一家之言也不够完整,后续还会深入实践和引入Kubernetes进行容 ...
- 初探gitlab & gitlab-runner & asp.net core持续集成
文章简介 gitlab & gitlab-runner 简介 基于gitlab & gitlab-runner 的asp.net core webapi 极简持续集成实践 gitla ...
- docker结合jenkins、gitlab实现.netcore的持续集成实践
本文的目标是实现下图基于ASP NET Core的实践 运行环境 Cent OS 7 vs code .net core cmder 运行docker,设置docker镜像加速器,不然国内下载imag ...
- 基于Microsoft Azure、ASP.NET Core和Docker的博客系统
欢迎阅读daxnet的新博客:一个基于Microsoft Azure.ASP.NET Core和Docker的博客系统 2008年11月,我在博客园开通了个人帐号,并在博客园发表了自己的第一篇博客 ...
- Jenkins +Maven+Tomcat+SVN +Apache项目持续集成构建
详解Jenkins +Maven+Tomcat+SVN +Apache项目持续集成 一:前言 1. Jenkins jenkins版本大全http://mirrors.jenkins-ci.org/ ...
- 「持续集成实践系列」Jenkins 2.x 搭建CI需要掌握的硬核要点
1. 前言 随着互联网软件行业快速发展,为了抢占市场先机,企业不得不持续提高软件的交付效率.特别是现在国内越来越多企业已经在逐步引入DevOps研发模式的变迁,在这些背景催促之下,对于企业研发团队所需 ...
- 「持续集成实践系列 」Jenkins 2.x 构建CI自动化流水线常见技巧
在上一篇文章中,我们介绍了Jenkins 2.x实现流水线的两种语法,以及在实际工作中该如何选择脚本式语法或声明式语法.原文可查阅:「持续集成实践系列」Jenkins 2.x 搭建CI需要掌握的硬核要 ...
- 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生
[转].NET(C#):浅谈程序集清单资源和RESX资源 目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...
- 使用jenkins配置.net mvc网站进行持续集成二
上一篇使用jenkins配置.net mvc网站进行持续集成一只是简单介绍了jenkins构建站点到本地服务器,这一篇,就来讲解如何部署站点到指定的服务器上面. 1.IIS远程发布配置 1.在服务器管 ...
随机推荐
- C#文件和字节流的转换方法
1.读取文件,并转换为字节流 FileStream fs = new FileStream(filename,FileMode.Open,FileAccess.Read); byte[] infbyt ...
- Android之淘宝商品列表长按遮罩效果
先来看看淘宝.唯品会长按商品的效果,以及简单Demo的效果: 首先分析一下场景: 长按条目时,弹出遮罩的效果遮挡在原来的条目布局上: 页面滑动或点击其他的条目,上一个正在遮罩的条目遮罩消 ...
- c++11线程池
#pragma once #include <future> #include <vector> #include <atomic> #include <qu ...
- Unity pdb2mdb错误
错误: D:\sandbox\sandbox_art\sandbox_artprj\Assets\Plugins\Sandbox\Editor>"C:\ProgramFiles/Uni ...
- birt4.6部署到tomcat及启动服务报错解决方法
一.下载birt-runtime-4.6.0-20160607.zip包 解压后birt-runtime-4.6.0-20160607\WebViewerExample将WebViewerExampl ...
- zfs文件系统简单使用
关于ubuntu下zfs的使用参考:https://github.com/zfsonlinux/zfs/wiki/Ubuntu%2016.04%20Root%20on%20ZFS 安装zfs: 启动z ...
- JavaScript(三、DOM文档对象模型)
一.什么是DOM DOM 是 Document Object Model(文档对象模型)的缩写. DOM 是 W3C(万维网联盟)的标准. DOM 定义了访问 HTML 和 XML 文档的标准: &q ...
- Linux环境安装配置JDK
本文安装环境为Ubuntu14 64位,jdk版本为jdk1.6.0_38,安装文件名为jdk-6u38-linux-x64.bin(根据系统不同,下载不同的安装文件) 下载地址:http://www ...
- Git分支合并冲突解决(续)
接Git分支合并冲突解决,在使用rebase合并冲突情况下,如果不小心,执行完add后执行了commit,此时本地仓库HEAD处于游离态(即HEAD指向未知的分支),如何解决? 解决方法 (1)此时, ...
- dubbo-admin和dubbo-monitor的安装
一.安装dubbo-admin 去这里 http://download.csdn.net/download/u013081610/10044744 下载dubbo-admin.war 部署dubbo- ...