一、羅里吧嗦
最近遷移了服務器,順道完善下服役了一兩年的Jenkins服務,主要是把Slave搭建起來,還有等等。本文只是我對Jenkins Pipeline的一些自己的理解與應用,歡迎指出錯誤,歡迎交流高級應用
二、運行環境
Jenkins:
- master:阿里雲Windows_2016_x64
- Slave1:京東雲Windows_2008_r2_x64
- Slave2:阿里雲Windows_2008_r2_x86
版本管理器:自建的git服務器,使用gogs
.NET項目:使用VS2017新建的一個web mvc項目與一個windows service項目,項目上傳至git服務器
一些輔助工具:
- 7-zip:作為壓縮 解壓
- ossutil:阿里雲oss服務工具
- nuget:還原解決方案引用包
- MSBuild:編譯項目
三、開始
首先新建.NET項目,新建一個web項目與windows service項目,步驟略
其次,在自行安裝Jenkins,步驟略
新建Jenkins項目,類型選擇Pipeline,命名為JenkinsPipelineProject
整體流程如下
start->檢出代碼->還原引用包->編譯->打包->上傳OSS->分發slave->發布web->發布Service->end
各步驟:
檢出代碼:使用內置的工具進行代碼的檢出,如我使用的是git
還原引用包:使用nuget.exe對解決方案進行引用包還原,包源可選國內節點,國內節點下載速度框
編譯:此處進行了兩次編譯,一次編譯web,一次編譯Service
打包:並行進行,對編譯步驟得到的文件進行打包(使用7zip),存放於本地路徑上,打包時,會刪除相關配置文件,配置文件為手動更新
上傳OSS:對剛打包好的更新包進行上傳,因兩台服務器處於阿里雲內網,所以采用阿里雲的OSS,更新速度快
分發Slave:根據配置的節點,進行更新web和service操作
發布web:首先從OSS下載文件下來, 停止站點(非停止IIS),使用7zip進行解壓文件,更新文件,更新完畢后啟動站點,如有多台服務器需要更新,則並行執行,互不干擾
發布Service:首先從OSS下載文件下來,停止對應的windows服務,卸載對應的windows服務,如若失敗,則進行強制刪除windows服務,之后使用7zip進行文件的解壓更新,更新完畢后安裝服務,並啟動服務
以下為具體的Pipeline代碼
注:
- 代碼中所需的配置為我自己本身項目需要,如若更改,可根據自己項目進行定制
- 代碼中一些敏感的配置已用xxxx代替
- 僅用於參考
//編譯服務器設置start def buildNodeSettings = [:] buildNodeSettings.node = '阿里雲Windows_2008_r2_x86'//編譯服務器節點設置 buildNodeSettings.gitUrl = 'https://xxx/JenkinsPipelineProject.git'//git地址 buildNodeSettings.gitBarnches = '*/master' //分支 buildNodeSettings.slnFile = 'JenkinsPipelineProject.sln' //Nuget還原解決方案名 buildNodeSettings.buildFileForWeb ='JenkinsPipelineProjectWeb\\JenkinsPipelineProjectWeb.csproj' //msbulid編譯文件名 web buildNodeSettings.msbuildArgForWeb = '/t:Rebuild /p:Configuration=Release;PublishProfile=FolderProfile;DeployOnBuild=true' //msbulid參數 web buildNodeSettings.publishOutputForWeb = '\\JenkinsPipelineProjectWeb\\bin\\Release\\PublishOutput' //編譯后發布的路徑 web buildNodeSettings.publishFileNameForWeb = env.JOB_NAME + '/Build-Web-' +env.BUILD_NUMBER + '.7z' //文件名 buildNodeSettings.delFilesForWeb = ["Web.config","Web.Debug.config","Web.Release.config"] as String[] //需要刪除的文件 buildNodeSettings.buildFileForService ='JenkinsPipelineProject.sln' //msbulid編譯文件名 Service buildNodeSettings.msbuildArgForService = '/t:Rebuild /p:Configuration=Release' //msbulid參數 Service buildNodeSettings.publishOutputForService = '\\JenkinsPipelineProjectWindowsService\\bin\\Release' //編譯后發布的路徑 Service buildNodeSettings.publishFileNameForService = env.JOB_NAME + '/Build-Service-' +env.BUILD_NUMBER + '.7z' //文件名 buildNodeSettings.delFilesForService = ["*.config"] as String[] //需要刪除的文件 buildNodeSettings.updateServerPath = 'D:\\WebRoot\\update\\public_html\\'//更新服務器存放包地址 //編譯服務器設置end def webNodeSetting = [:] webNodeSetting.node = '阿里雲Windows_2008_r2_x86' //Web服務器節點 webNodeSetting.downloadPath = 'C:\\Jenkins\\download\\'//更新包下載地址 webNodeSetting.publishPath = 'D:\\WebRoot\\JenkinsPipelinePorject\\Web' //web服務器網站根目錄 webNodeSetting.webApplicationName = 'JenkinsPipelinePorject'//web站點名稱 def webNodeSetting2 = [:] webNodeSetting2.node = 'master' //Web服務器節點 webNodeSetting2.downloadPath = 'C:\\JenkinsDownload\\'//更新包下載地址 webNodeSetting2.publishPath = 'D:\\WebRoot\\JenkinsPipelinePorject\\Web' //web服務器網站根目錄 webNodeSetting2.webApplicationName = 'JenkinsPipelinePorject'//web站點名稱 def webNodeSetting3 = [:] webNodeSetting3.node = '京東雲Windows_2008_r2_x64' //Web服務器節點 webNodeSetting3.downloadPath = 'C:\\Jenkins\\download\\'//更新包下載地址 webNodeSetting3.publishPath = 'C:\\WebRoot\\JenkinsPipelinePorject\\Web' //web服務器網站根目錄 webNodeSetting3.webApplicationName = 'JenkinsPipelinePorject'//web站點名稱 def serviceNodeSetting = [:] serviceNodeSetting.node = '阿里雲Windows_2008_r2_x86' serviceNodeSetting.downloadPath = 'C:\\Jenkins\\download\\'//更新包下載地址 serviceNodeSetting.publishPath = 'D:\\WebRoot\\JenkinsPipelinePorject\\Service' //Service Windows Service存放路徑 serviceNodeSetting.serviceName = 'JenkinsPipelineProject'//服務名稱 serviceNodeSetting.serviceFileName = 'JenkinsPipelineProjectWindowsService.exe' //服務的文件名,相對publishPath的路徑 def serviceNodeSetting2 = [:] serviceNodeSetting2.node = 'master' serviceNodeSetting2.downloadPath = 'C:\\Jenkins\\download\\'//更新包下載地址 serviceNodeSetting2.publishPath = 'D:\\WebRoot\\JenkinsPipelinePorject\\Service' //Service Windows Service存放路徑 serviceNodeSetting2.serviceName = 'JenkinsPipelineProject'//服務名稱 serviceNodeSetting2.serviceFileName = 'JenkinsPipelineProjectWindowsService.exe' //服務的文件名,相對publishPath的路徑 def serviceNodeSetting3 = [:] serviceNodeSetting3.node = '京東雲Windows_2008_r2_x64' serviceNodeSetting3.downloadPath = 'C:\\Jenkins\\download\\'//更新包下載地址 serviceNodeSetting3.publishPath = 'C:\\WebRoot\\JenkinsPipelinePorject\\Service' //Service Windows Service存放路徑 serviceNodeSetting3.serviceName = 'JenkinsPipelineProject'//服務名稱 serviceNodeSetting3.serviceFileName = 'JenkinsPipelineProjectWindowsService.exe' //服務的文件名,相對publishPath的路徑 node(buildNodeSettings.node) { def msbuild=tool name: 'MSBuildTool V14.0', type: 'msbuild' //編譯工具名稱與地址 buildNodeSettings.publishOutputForWeb = env.WORKSPACE + buildNodeSettings.publishOutputForWeb buildNodeSettings.publishOutputForService = env.WORKSPACE + buildNodeSettings.publishOutputForService stage('Check Out') { echo '檢出項目' checkout([$class: 'GitSCM', branches: [[name: buildNodeSettings.gitBarnches]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'xxxxxx', url: buildNodeSettings.gitUrl]]]) } stage('Nuget Restore') { echo ' 還原nuget ' echo '${env.nuget} restore "' + env.WORKSPACE + '/' + buildNodeSettings.slnFile + '" -ConfigFile "' + env.config + '" -NoCache' bat env.nuget + ' restore "' + env.WORKSPACE + '/' + buildNodeSettings.slnFile + '" -ConfigFile "' + env.config + '" -NoCache' } stage('Bulid') { echo ' 編譯項目' echo 'Bulid Web' bat '"' + msbuild + '" ' + buildNodeSettings.msbuildArgForWeb + ' "' + env.WORKSPACE + '/' + buildNodeSettings.buildFileForWeb + '"' echo 'Bulid Service' bat '"' + msbuild + '" ' + buildNodeSettings.msbuildArgForService + ' "' + env.WORKSPACE + '/' + buildNodeSettings.buildFileForService + '"' } stage('Pack') { parallel PackWeb:{ echo '刪除相關配置文件' buildNodeSettings.delFilesForWeb.each{ echo '刪除文件:' + it def filepath ='"' + buildNodeSettings.publishOutputForWeb.replace("/","\\") + '\\' + it + '"' bat 'if exist '+ filepath +' del ' + filepath } echo ' 發布到更新系統' bat 'if not exist "' + buildNodeSettings.updateServerPath + env.JOB_NAME + '" md "' + buildNodeSettings.updateServerPath + env.JOB_NAME + '"' bat '"' + env.zip + '"'+ ' a -r "' + buildNodeSettings.updateServerPath + buildNodeSettings.publishFileNameForWeb + '" "' + buildNodeSettings.publishOutputForWeb + '\\*"' echo '壓縮完成' echo '上傳oss' bat env.oss + ' -c ' + env.ossconfig + ' cp "' + buildNodeSettings.updateServerPath + buildNodeSettings.publishFileNameForWeb + '" "oss://xxxx/' + buildNodeSettings.publishFileNameForWeb +'"' }, PackService:{ echo '刪除相關配置文件' buildNodeSettings.delFilesForService.each{ echo '刪除文件:' + it def filepath ='"' + buildNodeSettings.publishOutputForService.replace("/","\\") + '\\' + it + '"' bat 'if exist '+ filepath +' del ' + filepath } echo ' 發布到更新系統' bat 'if not exist "' + buildNodeSettings.updateServerPath + env.JOB_NAME + '" md "' + buildNodeSettings.updateServerPath + env.JOB_NAME + '"' bat '"' + env.zip + '"'+ ' a -r "' + buildNodeSettings.updateServerPath + buildNodeSettings.publishFileNameForService + '" "' + buildNodeSettings.publishOutputForService + '\\*"' echo '壓縮完成' echo '上傳oss' bat env.oss + ' -c ' + env.ossconfig + ' cp "' + buildNodeSettings.updateServerPath + buildNodeSettings.publishFileNameForService + '" "oss://xxxx/' + buildNodeSettings.publishFileNameForService +'"' } } stage('Clear') { echo '清理工作目錄' deleteDir() } } stage('Publish Web') { parallel publishWeb1:{ node(webNodeSetting.node) { echo '發布web' echo '更新文件' echo '更新文件下載地址為:http://xxxx/' + buildNodeSettings.publishFileNameForWeb echo '下載文件' bat env.oss + ' -c ' + env.ossconfig + ' cp "oss://xxxx/' + buildNodeSettings.publishFileNameForWeb + '" ' + webNodeSetting.downloadPath echo '文件下載完成' echo '停止站點' bat 'C:\\Windows\\System32\\inetsrv\\appcmd.exe stop site "' + webNodeSetting.webApplicationName + '"' bat '"' + env.zip + '" x "'+ webNodeSetting.downloadPath + buildNodeSettings.publishFileNameForWeb + '" -y -o"' + webNodeSetting.publishPath + '"' echo '啟動站點' bat 'C:\\Windows\\System32\\inetsrv\\appcmd.exe start site "' + webNodeSetting.webApplicationName+ '"' } }, publishWeb2:{ node(webNodeSetting2.node) { echo '發布web' echo '更新文件' echo '更新文件下載地址為:http://xxxx/' + buildNodeSettings.publishFileNameForWeb echo '下載文件' bat env.oss + ' -c ' + env.ossconfig + ' cp "oss://xxxx/' + buildNodeSettings.publishFileNameForWeb + '" ' + webNodeSetting2.downloadPath echo '文件下載完成' echo '停止站點' bat 'C:\\Windows\\System32\\inetsrv\\appcmd.exe stop site "' + webNodeSetting2.webApplicationName + '"' bat '"' + env.zip + '" x "'+ webNodeSetting2.downloadPath + buildNodeSettings.publishFileNameForWeb + '" -y -o"' + webNodeSetting2.publishPath + '"' echo '啟動站點' bat 'C:\\Windows\\System32\\inetsrv\\appcmd.exe start site "' + webNodeSetting2.webApplicationName+ '"' } }, publishWeb3:{ node(webNodeSetting3.node) { withEnv(['oss=C:\\Tools\\oss\\ossutil.exe', 'ossconfig=C:\\Tools\\oss\\config']) {//需要手動設置變量 echo '發布web' echo '更新文件' echo '更新文件下載地址為:http://xxxx/' + buildNodeSettings.publishFileNameForWeb echo '下載文件' bat env.oss + ' -c ' + env.ossconfig + ' cp "oss://xxxx/' + buildNodeSettings.publishFileNameForWeb + '" ' + webNodeSetting3.downloadPath echo '文件下載完成' echo '停止站點' bat 'C:\\Windows\\System32\\inetsrv\\appcmd.exe stop site "' + webNodeSetting3.webApplicationName + '"' bat '"' + env.zip + '" x "'+ webNodeSetting3.downloadPath + buildNodeSettings.publishFileNameForWeb + '" -y -o"' + webNodeSetting3.publishPath + '"' echo '啟動站點' bat 'C:\\Windows\\System32\\inetsrv\\appcmd.exe start site "' + webNodeSetting3.webApplicationName+ '"' } } } } stage('Publish Service') { parallel publishService1: { node(serviceNodeSetting.node){ //發布windows service echo '發布Service' echo '下載文件' bat env.oss + ' -c ' + env.ossconfig + ' cp "oss://xxxx/' + buildNodeSettings.publishFileNameForService + '" ' + serviceNodeSetting.downloadPath echo '卸載Windows Services' try{ bat 'net stop ' + serviceNodeSetting.serviceName bat env.InstallUtil + ' -u ' + serviceNodeSetting.serviceName }catch(ex) { echo '卸載失敗:' + ex try{ bat 'sc delete ' + serviceNodeSetting.serviceName }catch(ex2) { echo '強制刪除失敗:' +ex2 } } echo '解壓文件' bat '"' + env.zip + '" x "'+ serviceNodeSetting.downloadPath + buildNodeSettings.publishFileNameForService + '" -y -o"' + serviceNodeSetting.publishPath + '"' echo '服務安裝' bat env.InstallUtil + ' ' + serviceNodeSetting.publishPath + '\\' + serviceNodeSetting.serviceFileName + ' /name='+ serviceNodeSetting.serviceName + ' /display=' + serviceNodeSetting.serviceName + ' /desc=' + serviceNodeSetting.serviceName echo '啟動服務' bat 'net start ' + serviceNodeSetting.serviceName } }, publishService2: { node(serviceNodeSetting2.node){ //發布windows service echo '發布Service' echo '下載文件' bat env.oss + ' -c ' + env.ossconfig + ' cp "oss://xxxx/' + buildNodeSettings.publishFileNameForService + '" ' + serviceNodeSetting2.downloadPath echo '卸載Windows Services' try{ bat 'net stop ' + serviceNodeSetting2.serviceName bat env.InstallUtil + ' -u ' + serviceNodeSetting2.serviceName }catch(ex) { echo '卸載失敗:' + ex try{ bat 'sc delete ' + serviceNodeSetting2.serviceName }catch(ex2) { echo '強制刪除失敗:' +ex2 } } echo '解壓文件' bat '"' + env.zip + '" x "'+ serviceNodeSetting2.downloadPath + buildNodeSettings.publishFileNameForService + '" -y -o"' + serviceNodeSetting2.publishPath + '"' echo '服務安裝' bat env.InstallUtil + ' ' + serviceNodeSetting2.publishPath + '\\' + serviceNodeSetting2.serviceFileName + ' /name='+ serviceNodeSetting2.serviceName + ' /display=' + serviceNodeSetting2.serviceName + ' /desc=' + serviceNodeSetting2.serviceName echo '啟動服務' bat 'net start ' + serviceNodeSetting2.serviceName } }, publishService3: { node(serviceNodeSetting3.node){ withEnv(['oss=C:\\Tools\\oss\\ossutil.exe', 'ossconfig=C:\\Tools\\oss\\config']) {//需要手動設置變量 //發布windows service echo '發布Service' echo '下載文件' bat env.oss + ' -c ' + env.ossconfig + ' cp "oss://xxxx/' + buildNodeSettings.publishFileNameForService + '" ' + serviceNodeSetting3.downloadPath echo '卸載Windows Services' try{ bat 'net stop ' + serviceNodeSetting3.serviceName bat env.InstallUtil + ' -u ' + serviceNodeSetting3.serviceName }catch(ex) { echo '卸載失敗:' + ex try{ bat 'sc delete ' + serviceNodeSetting3.serviceName }catch(ex2) { echo '強制刪除失敗:' +ex2 } } echo '解壓文件' bat '"' + env.zip + '" x "'+ serviceNodeSetting3.downloadPath + buildNodeSettings.publishFileNameForService + '" -y -o"' + serviceNodeSetting3.publishPath + '"' echo '服務安裝' bat env.InstallUtil + ' ' + serviceNodeSetting3.publishPath + '\\' + serviceNodeSetting3.serviceFileName + ' /name='+ serviceNodeSetting3.serviceName + ' /display=' + serviceNodeSetting3.serviceName + ' /desc=' + serviceNodeSetting3.serviceName echo '啟動服務' bat 'net start ' + serviceNodeSetting3.serviceName } } } }
以上代碼對三台服務器上的web和service進行了更新操作,兩台阿里雲內網,一台京東雲
代碼說明:
四、看看效果
我們開始構建剛才新建的項目
從gif可以看出,整個流程只耗費了一分鍾不到,我們去看看這三台服務器
三台服務器的文件都已更新,並且服務已經啟動,證明我們的pipeline代碼是可行的
五、補充和改進
- 這篇文章的代碼量過大,語言組織能力有待改進
- Slave節點的環境變量不能正確讀取到,目前只能使用withEnv進行更改環境變量,具體情況publishService3
- 項目的耦合性太高了,目前編譯、打包、發布都在同一個項目中,需要進行項目的拆分
- 沒有加重試機制,一旦某一階段失敗,只能重新運行,有待改進
- 失敗郵件通知,這個目前沒有加入
如若有寫的不好的地方,請指出
如若有更好的方案,歡迎一起交流
如若有不懂,歡迎咨詢,我會告訴你我知道的
本文已同步個人博客,歡迎轉載