基於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")
        }
    }
}
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系統的實現技術不一樣,這里就不再貼腳本了。

四、效果演示

  (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的應用

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM