基於Jenkins Pipeline的ASP.NET Core持續集成實踐

最近在公司實踐持續集成,使用到了Jenkins的Pipeline來提升團隊基於ASP.NET Core API服務的集成與部署效率,所以這裏總結一下。html

1、關於持續集成與Jenkins Pipeline

1.1 持續集成相關概念

  互聯網軟件的開發和發佈,已經造成了一套標準流程,最重要的組成部分就是持續集成(Continuous integration,簡稱 CI) 。 git

  持續集成指的是,頻繁地 (一天屢次) 將代碼集成到主幹web

  它的好處主要有兩個:docker

(1)快速發現錯誤。每完成一點更新,就集成到主幹,能夠快速發現錯誤,定位錯誤也比較容易。shell

(2)防止分支大幅偏離主幹。若是不是常常集成,主幹又在不斷更新,會致使之後集成的難度變大,甚至難以集成。windows

  持續集成的目的,就是讓產品能夠快速迭代,同時還能保持高質量服務器

Martin Fowler 說:「 持續集成並不能消除 Bug,而是讓它們很是容易發現和改正。」  app

  與持續集成相關的,還有持續交付和持續部署。框架

  持續交付指的是:頻繁地將軟件的新版本,交付給質量團隊或者用戶,以供評審。若是評審經過,代碼就進入生產階段。它強調的是,無論怎麼更新,軟件是隨時隨地能夠交付的ssh

  持續部署是持續交付的下一步,指的是代碼經過評審之後,自動部署到生產環境。它強調的是代碼在任什麼時候刻都是可部署的,能夠進入生產階段

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提供。

2、準備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服務器》。

3、配置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") } } }
View Code

  (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' } } } }
View Code

  這裏因爲個人測試環境分爲兩個,一個是開發人員聯調環境190,另外一個是集成測試環境175,統一在一個Pipeline任務中進行發佈。

  對於Master分支,咱們還能夠將Web系統的發佈也集成到同一個Pipeline任務中,實現一個一條龍的發佈流水線任務,因爲各個Web系統的實現技術不同,這裏就再也不貼腳本了。

4、效果演示

  (1)持續集成示例

  (2)持續發佈示例

  (3)構建失敗告警

  (4)構建大屏顯示

  再來一張投屏到工做區域電視屏幕中的效果,你們擡頭就能夠看到構建結果,是綠了仍是紅了?固然,咱們都喜歡「綠」的,呼呼。

5、小結

  藉助持續集成和持續發佈,咱們開發人員能夠節省不少質量保證和發佈部署的時間,從而減小不少由於人爲QA和Deploy形成的失誤影響,從另外一個層面上,它也可使咱們避免996(好吧,雖然關聯有點牽強)。後續,我還會探索K8S,到時候但願可以分享一個ASP.NET Core on K8S的系列文章,敬請期待。

參考資料

大寶魚,《玩轉Jenkins Pipeline

李志強,《Jenkins高級用法 - Pipeline 安裝

李志強,《Jenkins高級用法 - Jenkinsfile 介紹及實戰經驗

三隻松鼠,《jenkins + pipeline構建自動化部署

ofnhkb1,《.NET項目從CI到CD-Jenkins_Pipeline的應用

 

做者:周旭龍

出處:http://edisonchou.cnblogs.com

本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接。