1. 背景javascript
分佈式技術的發展深入地改變了咱們編程的模式和思考軟件的模式。分佈式很好的解決性能擴展,可靠性,組件可用性等問題,可是單機轉變成分佈式卻加大了系統的複雜性,對於組件的開發,測試,部署,發佈都提出更高的要求。那麼,針對複雜的分佈式系統怎麼保證軟件質量和系統的穩定性?首先看下,傳統軟件產品活動的大體流程,簡化流程大概是 3 大塊:html
開發 -> QA -> 灰度上線
通常以下圖:
java
流程有個很大的問題,質量全靠 QA 測,對接全靠人力,溝通成本大,遺漏問題多,通常有幾個常見的問題:
node
QA 很難每次都測試全面,畢竟QA畢竟是人,人的主觀因素太大,有時候人爲判斷以爲簡單,不用測的地方極可能有漏了。或者以爲修改點太簡單,以爲不至於出問題,就再也不全面的測試。以致於可能會有基本功能問題;python
測試速度慢,效率過低,QA 資源浪費,若是每次 QA 都須要全量的測試,那麼重複工做太多,效率過低,效果也很差,對於這些重複工做,本能夠更好,更快的解決,不至於 QA 就爲了測試這麼點東西,而沒有精力去作更多的事情;nginx
甚至說編譯不過的代碼都有可能遺漏到 QA;git
閉環太慢,開發功能若是有問題,等到 QA 測出來,讓後再反饋到開發這個閉環就太慢了。更沒必要說,問題漏到線上,再反饋到開發人員,那麼戴江就更大了;github
多個開發人員並行開發的時候,工做可能相互影響,小問題越積越多,功能集成的時候可能很是耗時;golang
咱們提出的改進點:web
核心點之一:問題發現要早,發現越早,代價越小;
核心點之一:問題閉環要快,閉環越快,效率越高;
重複工做自動化,減小人的無效勞做;
多開發人員的時候,功能持續集成,問題拆小,提前發現;
最核心的一點就是:」自動化閉環問題「。
當前的複雜的軟件系統對質量和效率提出了更高的要求,因此響應的軟件活動必需要高度自動化才能達到要求。自動化觸發、自動化測試、自動化閉環、自動化發佈、自動化卡點等一系列的保證,一切可以事先預知且可固化的行爲都應該自動化,把效率和質量提高,而讓人去作更聰明的事情。
咱們的思考:近些年來,對於自動化有 Continuous Integration,Continuous Delivery,Continuous Deployment 的一些理論和實踐。這三者來講,突出「持續」二字,「持續」是爲了達到「快」的目的,「快速迭代」,「快速響應」,「快速閉環」,「快」是核心競爭力。通常你們的共識流程分類以下:
對於開發者來講,接觸到更多的是 Continuous Integration(持續集成),CI 把經過自動化,把流程固化下來,保證代碼集成的有序、可靠,確保版本可控,問題可追溯,代碼的活動中經過自動化,下降了人爲主觀的出錯率,提升速度,提升版本質量和效率。
2. CI 是什麼?
CI 便是持續集成(Continuous Integration),是當今軟件活動中相當重要的一環。CI 通常由開發人員遞交代碼改動所觸發,CI 在中間環境作自動化驗證,CI 驗證事後,即通過了基本質量保證,那麼就能夠容許下一步的軟件活動。
持續集成說白了就是一種軟件開發實踐,即團隊開發成員儘量的快的集成,每次集成經過自動化的構建(包括編譯,發佈,自動化測試)來驗證,從而儘早地發現集成錯誤。由於問題發現的越早,那麼問題解決的成本就越少。
通常來講,持續集成須要打通幾個環節:
代碼提交(git)
任務構建(jenkins)
部署測試(ansible,shell,puppet)
劃重點:CI 過程由代碼活動觸發。
代碼活動關注兩個時間點:
Pre-Merge :代碼改動合入主幹分支前夕觸發。集成的對象是代碼改動與主幹最新代碼 Merge 以後的代碼,目的是驗證代碼改動是否可以合入主幹;
Post-Merge :代碼改動合入主幹分支以後觸發。集成對象就是最新的主幹分支代碼,目的是驗證合入改動代碼以後主幹是否可以正常工做;
Pre-Merge 和 Post-Merge 關注點不一樣,缺一不可。差別在哪裏?若是隻有一個開發者,那麼 Pre-Merge 和 Post-Merge 的測試對象是相同的。在多個開發者遞交代碼的時候,Pre-Merge 和 Post-Merge 就會呈現差別,他們的 CI 測試對象不一樣。
換句話說,Pre-Merge 是並行的,每一個開發分支想要合入主幹都會觸發Pre-Merge CI,CI 的測試對象是 <開發分支+主幹分支>, Post-Merge 是串行的,測試對象永遠都是最新的主幹分支代碼。
3. CI 的四個思考
3.1 CI 怎麼觸發?
代碼活動觸發:
通常有兩個觸發點,Pre-Merge,Post-Merge ,分別是代碼合入主幹以前,主幹代碼合入以後。
定時觸發。
3.2 CI 觸發以後作什麼?
CI 觸發了以後作什麼?說白了就是構建任務作了啥,通常有幾個流程:
Checkout,Pre-Merge 代碼——校驗 MR 合入是否合法;
代碼編譯 —— 校驗代碼編譯是否合法;
靜態檢查 —— 校驗靜態語法是否合法;
單元測試 —— 迴歸測試函數單元合法;
冒煙測試 —— 簡單測試系統是否正常;
接口測試 —— 測試用戶接口是否正常;
性能基準測試 —— 測試性能是否符合預期;
3.3 怎麼閉環問題?
先思考可能會有什麼問題:
Pre-Merge 代碼衝突;
代碼編譯失敗;
靜態檢查失敗;
單元測試迴歸測試不經過;
冒煙測試步經過,接口測試失敗。。。;
遞交一個代碼 MR 遞交可能遇到以上問題,那麼怎麼才能快速閉環這個問題呢?
首先,得有手段通知到開發者
解決:
MR 的 comment,CI 活動失敗以後,直接以評論的方式自動添加到 MR;
郵件,觸發的一次 MR ,失敗了以郵件的方式發送到相關人;
再者,得有手段讓開發人員知道問題
解決:
開發者知道本身的 MR 觸發 CI 失敗以後,得知道怎麼去排查問題 —— 測試報告,
好比單元測試失敗,要有單元測試報告,接口測試失敗要有接口測試報告;
每次構建任務保留歸檔線索,以便排查;
3.4 CI 構建活動輸出什麼?
單元測試報告;
接口測試報告;
代碼覆蓋率報告;
接口覆蓋率報告;
構建版本包(持續化部署須要);
4. CI 平臺選型
通常對多數的公司來講,不須要本身研發一個 CI 平臺,有不少優秀的開源 CI 平臺工具,工具之間並無絕對的差別優點,這裏就不進行選型了,咱們以一個 Jenkins 完整示例來講明 CI 的使用方法和技巧。代碼倉庫咱們使用 Gitlab,CI 平臺咱們使用開源的 Jenkins 做爲演示。一步步完成咱們須要得幾大模塊功能。
平臺選型:代碼平臺 Gitlab,CI 平臺 Jenkins;
5. CI 的流程實踐
CI 主要把關的是代碼活動,通常有兩個觸發點:
代碼合入主幹前,觸發 CI 測試,目的是校驗本次合入是否符合質量預期,若是不符合,那麼不許代碼合入主幹;
代碼合入主幹後,觸發 CI 測試,目的是校驗最新的主幹分支是否符合質量預期;
Pre-Merge 觸發過程:
開發代碼遞交 Merge Request ( github 上習慣叫作 PR,gitlab 上習慣叫作 MR )
MR 自動觸發 CI 構建事件
運行 靜態檢查,Merge 檢查,單元測試,冒煙測試,集成測試,所有經過以後,代碼才容許 Merge 合入主幹分支;
進行下一步軟件活動
Post-Merge 觸發過程:
管理員審覈 Merge Request 經過,代碼合入主幹,觸發 Post-Merge 事件;
CI 平臺收到事件,自動進行 CI 構建;
構建完成,進行下一步軟件活動;
6. Jenkins 平臺構建
6.1 Jenkins 平臺搭建
jenkins 是 java 程序開發的,安裝是很是方便的,去官網上下載一個 war 包,而後後臺拉起運行便可。運行命令以下:
啓動
nohup /usr/bin/java -jar jenkins.war --httpPort=8888 jenkins.log 2>&1 &
這樣 jenkins 平臺就拉起來了,超簡單。
6.1.1 初始化平臺
初始密鑰
平臺第一次搭建須要作一些配置,在日誌裏找到一個「初始密鑰」注意一下提示:
***************************************************************************************************************************************************************************************
Jenkins initial setup is required. An admin user has been created and a password generated.Please use the following password to proceed to installation:
5ddce31f4a0d48b4b7d6d71ff41d94a8
This may also be found at: /root/software/jenkins/workhome/secrets/initialAdminPassword
***************************************************************************************************************************************************************************************
這個是最初始的超級用戶密碼,待會配置的 jenkins 時候會用上,因此趕忙拿個小本本記下。
登陸網頁
如今打開瀏覽器,登陸 jenkins 的網頁,咱們下面作 jenkins 的初始化:
點擊 「Continue」,進行下一步,下一步就是定製一些插件安裝了。
第一次定製插件安裝
這個是可選的,這個根據本身需求選擇插件,爲了省事,選第一種就好,以後也很方便在平臺上下載插件。
安裝插件過程(jenkins 幾乎徹底由插件組裝起功能):
更新成功的就會顯示「綠色」。插件安裝完以後,下一步就是配置第一個超級用戶了。
配置完成以後,點擊 "Save and Continue" ,最後一步,配置 url ,點擊 finish 便可:
如今基本配置已經完成,能夠開始愉快的使用 jenkins 了。
6.1.2 使用小技巧
中文化配置
jenkins 平臺搭建好以後,默認的是英文的,在國內的話可能不必,咱們能夠安裝中文化插件來更友好的展現咱們的 jenkins。分兩個步驟:
步驟一:安裝插件:」Locale「:
"Manage Jenkins" -> "Manage Plugins" -> "Available"
步驟二:安裝完以後,配置 Configure
6.2 Jenkins 插件安裝
6.2.1 插件更新地址
這裏推薦國內的插件源地址,由於官網的網絡訪問不是很穩定。好比如下是清華的鏡像源。
https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
6.2.2 必備插件安裝
jenkins 功能都是由插件提供,有些插件是必須配備的,才能提供完備的 CI 功能,好比流水線 Pipeline 。這裏列舉幾大關鍵插件的使用方法。
pipeline
jenkins 必備插件,流水線插件,可否很是方便的讓你定義流程,調度節點,分配資源,處理結果等。
blue ocean
pipeline 的可視化插件,pipeline 仍是聲明式代碼編寫,若是要能讓人更方便的使用,那麼須要一個可視化的工具,blue ocean 就是爲此而生。
junit
測試報告的一個解析插件,這也是一種較爲通用的測試報告格式。
Cobertura Plugin
覆蓋率展現的一個插件。單測跑完,須要有手段知道覆蓋率的狀況,而且須要能方便的閉環處理。
顯示覆蓋率的狀況;
代碼的覆蓋詳情,方便開發人員閉環處理(細化到每一行代碼);
GitLab
咱們的演示以 Gitlab 做爲例子,須要和 GitLab 進行交互,因此須要安裝插件用來接受 GitLab 事件,並反饋 CI 結果。
6.3 Jenkins 任務建立
6.3.1 建立任務(item)
item 就是 CI 的項目,item 由管理員靜態建立配置好,觸發起來就是 job 了。每觸發一次,job 編號都是遞增的。點擊 New Item 建立一個」流水線「 的項目。
6.3.2 建立視圖(view)
View 是什麼概念?View 能夠把一些有業務意義的任務概括起來,在一個列表中顯示。能夠點擊 」New View「 進行建立。
視圖會展現 item ,你能夠選擇性的勾選。
6.4 Jenkins 流水線
流水線框架是經過 Pipeline 腳原本描述流程,Pipeline 有兩種建立方式,兩種語法:
聲明式流水線語法
腳本化流水線語法
如今官方推薦的是聲明式流水線語法。那麼,聲明式的語法是什麼樣子的?聲明式語法特色之一:頂層必須以 pipeline {}
開始。
7. Jenkins 和 Gitlab 交互
這一步就是最關鍵的東西,Jenkins 搭建好了以後,若是隻是一個孤島平臺,那麼沒有任何意義,它必須參與到軟件開發流程中去才能發揮效果。交互示意圖以下:
咱們看到,Gitlab 的代碼活動須要以事件的形式觸發 Jenkins,Jenkins 執行完一系列活動以後,須要把結果反饋到 Gitlab,而且可以影響到 Gitlab 的下一步活動,因此 Gitlab 和 Jenkins 須要相互配置關聯。
7.1 Gitlab 配置
爲何須要配置 gitlab ?由於須要打通 gitlab 到 jenkins 的路。gitlab 做爲代碼倉庫,主要產生項目代碼相關的事件,好比 Merge Request,Push Commit 等,當 gitlab 產生這些事件的時候,須要自動把這個事件推送給 jenkins ,這樣就打通了觸發交互。
7.1.1 配置 Web Hook 事件
操做步驟:
打開代碼倉庫
點擊 setting -> integrations
填入 URL
填入 Secret Token
勾選 Trigger 事件
URL 和 Secret Token 怎麼來的?這個是對應到 item 。
在 Jenkins 平臺上,打開對應的 item,打開 Configure ,勾選 Build Triggers ,找到 」Build when a change is pushed to GitLab「 ,就是這個了。
再往下有一個 Secret token ,點擊 Generate 。
把這兩個正確填寫好,那麼就能打通第一個環節了:Gitlab 到 Jenkins 的觸發。填寫完以後,能夠由 GitLab 發個測試事件測試下。
返回 200 便是成功了。
7.1.2 配置 Pre-Merge 卡點
代碼 Pre-Merge CI 沒過不讓合入主幹 這個功能怎麼實現?關鍵是 GitLab 要支持代碼 Merge 前夕的 Hook 行爲。
首先,咱們約定一個行爲規範:全部合入主幹的代碼必須遞交 MR,MR CI 測試經過才能夠合入主幹;
其次,勾選 Settings -> General -> Merge requests ,把 」Only allow merge requests to be merged if the pipeline succeeds「 勾選上;
7.2 Jenkins 配置
Jenkins 主要看 Pipeline 的配置,Pipeline 配置打開 Configure 以下:
看一個完整的 pipeline 架子定義各個階段(能夠直接把這個拷貝,運行看下效果):
pipeline { agent any stages { { steps { echo "------------" } } stage ("靜態檢查") { steps { echo "------------" } } stage ("代碼編譯") { steps { echo "------------" } } stage ("單元測試") { steps { echo "------------" } } stage ("打包") { steps { echo "------------" } } stage ("冒煙測試") { steps { echo "------------" } } stage ("集成測試") { steps { echo "------------" } } stage ("基準性能測試") { steps { echo "------------" } } } post { always { echo "------------" } success { echo "------------" } failure { echo "------------" } unstable { echo "------------" } }}
跑出來的效果:
Blue Ocean 的效果:
接下來,咱們拆解幾個關鍵的階段來分析。
7.2.1 代碼 checkout
Checkout 的代碼也就是咱們的測試對象,這個對於 Pre-merge 和 Post-merge 是不一樣的,Pre-merge 卡點由 Merge Request 事件觸發,咱們須要 Checkout 出 」代碼修改「 + 」最新主幹分支「 的代碼。Post-merge 相對簡單,咱們只須要 Checkout 出最新的主幹分支便可。怎麼作?
pipeline 直接支持這個使用。
stage('代碼checkout') { steps { dir(path: "${你想要放置的代碼路徑}") { checkout changelog: true, poll: true, scm: [ $class: 'GitSCM', branches: [[name: "*/${env.gitlabSourceBranch}"]], doGenerateSubmoduleConfigurations: false, extensions: [ [$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: 'origin', mergeStrategy: 'DEFAULT', mergeTarget: "${env.gitlabTargetBranch}" ]], [ $class: 'UserIdentity', email: "${env.gitlabUserEmail}", name: "${env.gitlabUserName}" ] ], submoduleCfg: [], userRemoteConfigs: [[ credentialsId: "${env.OCS_GITLAB_CredentialsId}", url: "${env.gitlabSourceRepoHttpUrl}" ]] ] } } }
這裏指明:
代碼 checkout 下來放置的路徑(dir path 配置);
指明 checkout 的分支,MR 事件觸發 CI 的時候 env.gitlabSourceBranch 是會自動設置上的;
指明 checkout 的行爲,PreBuildMerge 行爲(其實 Post-Merge 觸發 PUSH 事件的時候,也適用於上面的寫法);
指明 gitlab 認證憑證(credentialsId);
上面的語法同時適用於 Pre-merge 和 Post-merge 。
7.2.2 靜態檢查
靜態檢查主要是對代碼語法作一些靜態檢查,好比 golang ,可使用自帶的 go vet ,或者 go fmt 等檢查,通過這一輪檢查就能保證你們的代碼消除了最基本的語法和格式錯誤。
7.2.3 單元測試
對最小的函數作單元測試是必要的,通過單元測試能夠拿到項目的一個覆蓋率狀況。這個階段咱們得到兩個東西:
單元測試案例報告
覆蓋率報告
測試報告怎麼拿?以 golang 爲例,跑單測的時候,把覆蓋率的開關打開,標準輸出到一個文件:
go test -cover -coverprofile=cover.output xxx | tee ut.output
這裏會產生兩個文件:
ut.output :用來生成單元測試報告的文件;
cover.output :用來生成覆蓋率報告的文件;
單測報告生成
首先解析這個文件成 xml 格式的文件,而後用 junit 上報給 jenkins 展現。
sh "go-junit-report < ut.output > ut.xml"junit 'ut.xml'
go-junit-report 哪裏來的?這是個開源的工具,就是專門用來作單元測試解析的。
在 jenkins 上展現的效果以下:
覆蓋率報告生成
解析覆蓋率輸出文件,生成一個xml 文件:
gocov convert cover.output | gocov-xml > cover.xml
上報這個 xml 文件,用於 jenkins 平臺展現:
step([ $class: 'CoberturaPublisher', autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: '**/cover.xml', failUnhealthy: false, failUnstable: false, maxNumberOfBuilds: 0, onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false ])
點擊進文件,能夠看到代碼覆蓋的詳情:
7.2.4 接口測試
對完整的系統作一些接口級別的測試,好比模擬用戶行爲,測試用戶調用的接口,這樣能保證最基本的功能。報告輸出也能夠用junit 格式,能夠上報給jenkins,解析如圖:
7.2.5 郵件發送
測試經過或者失敗,須要發送有結果郵件。
configFileProvider([configFile(fileId: '5f1e288d-71ee-4d29-855f-f3b22eee376c', targetLocation: 'email.html', variable: 'content')]) { script { template = readFile encoding: 'UTF-8', file: "${content}" emailext( subject: "CI構建結果: ${currentBuild.result?: 'Unknow'}", to: "test@test.com", from: "push@test.com", body: """${template}""" ) }}
7.2.6 Gitlab 狀態交互
// 定義 Gitlab 流程options { gitLabConnection('test-gitlab') gitlabBuilds(builds: ['jenkinsCI'])}
// 觸發gitlab pipeline updateGitlabCommitStatus name: 'jenkinsCI', state: 'success' addGitLabMRComment comment: """**CI Jenkins自動構建詳情**\n| 條目 | 值 || ------ | ------ || 結果 | ${currentBuild.result?: 'Unknow'} || MR LastCommit | ${env.gitlabMergeRequestLastCommit} | | MR id | ${env.gitlabMergeRequestIid} || Message Title | ${env.gitlabMergeRequestTitle} || 構建任務ID | ${env.BUILD_NUMBER} || 構建詳情連接 | [${env.RUN_DISPLAY_URL}](${env.RUN_DISPLAY_URL})"""
CI 成功或失敗,都須要把這個狀態給到 gitlab,咱們以一個 Comment 展現結果,而且附上 jenkins 任務的跳轉連接,這樣能夠最快的幫助開發人員閉環。
成功才容許合入 :
Gitlab CI
7.2.7 構建歸檔
打包日誌:
// 先打一個 tar 包sh "tar -czvf log.tar.gz ${SERVICEDIR}/run/*.log"// jenkins 進行歸檔archiveArtifacts allowEmptyArchive: true, artifacts: "log.tar.gz", followSymlinks: false
8. Jenkins 高級技巧
8.1 資源互斥
有時候多個任務跑的時候,可能會併發使用到某個資源,而若是這個資源有限,那麼可能須要用到一些互斥手段來保證。好比,兩個任務可能都用到了 mongodb,而 mongodb 若是隻有一套,那麼就必須讓多個任務串行執行才行,否則就會跑錯了邏輯。怎麼作?
這個能夠在 「Configure System」->「Lockable Resources Manager」 定義好鎖資源:
而後再 Pipeline 腳本里使用這個鎖資源:
stage ("單元測試") { steps { lock(resource: "UT_TEST", quantity:1) { echo "====== 單元測試 ============" echo "====== 單元測試完成 ============" } }}
而且還能夠在界面上( Dashboard
-> Lockable Resources
)看到哪些資源被哪些任務佔用:
經過合理定義鎖資源,咱們就能作到任務能夠併發,可是關鍵的競態資源作互斥,這樣 CI 構建任務更靈活,更有效率(這個能夠類比成代碼裏面鎖粒度的一個影響,若是你不用 Lock Resource 這種方式,那麼極可能只能配置成 node 併發度爲1才能保護到競態資源)。
8.2 節點調度
jenkins 容許你調度指定的任務到合適的節點。當有多個節點的時候,可能會想要任務 A 固定到 node1 上執行,那麼可使用 agent 命令指定。
定義節點的時候每一個節點都會賦有一個 label 名稱,而後運行的時候,就能夠指定節點了:
agent { label "slave_node_1" }
8.3 節點間文件傳輸
咱們使用 stash
, unstash
來實現, 下面的例子就是把 build/ 目錄在 node1 和 node2 直接作無損傳輸:
stage ("打包") { agent { label "slave_node_1" } steps { // 節點1上,把 Build 目錄下的都打包; stash (name: "buildPkg", includes: "build/**/*") }}
stage ("冒煙部署") { agent { label "slave_node_2" } steps { // 節點2上,解包 unstash ("buildPkg") }}
8.4 節點的後置清理
在流水線多節點切換的時候,須要注意下你所在的節點是哪一個,千萬別暈頭了。
pipeline { agent { label "master" } stages { stage ("測試") { agent { label "slave_node_1" } steps { } 階段後置 post { always { 清理 slave_node_1 的構建空間 cleanWs() } } }
} 流水線總後置 post { always { 只清理 master 節點的構建空間 cleanWs() } }}
多節點的時候,必定要記得分別清理節點。
9. 總結
經過使用合理的技術平臺,把人與事合理的關聯,現代軟件開發活動中,CI 是必不可少的流程,開發人員身在其中,CI 以代碼活動爲起點,構建結果能能快速響應到對應人,並提供手段讓對應人快速解決,最後提供直觀的報告。咱們經過 Jenkins(CI平臺) + Gitlab(倉庫)來演示完整搭建流程,展現一個可實踐的過程。一切都是爲了軟件開發效率和版本質量。
☆ END ☆
OPPO互聯網雲平臺團隊招聘一大波崗位,涵蓋Java、容器、Linux內核開發、產品經理、項目經理等多個方向,請在公衆號後臺回覆關鍵詞「雲招聘」查看查詳細信息。
更多技術乾貨
掃碼關注
OPPO互聯網技術
本文分享自微信公衆號 - OPPO互聯網技術(OPPO_tech)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。