持續整合
1. 概述
持續整合(Continuous integration,簡稱 CI)指的是,頻繁地(一天多次)將程式碼整合到主幹
持續整合的目的,就是讓產品可以快速迭代,同時還能保持高質量。它的核心措施是,程式碼整合到主幹之前,必須通過自動化測試。只要有一個測試用例失敗,就不能整合
通過持續整合,團隊可以快速的從一個功能到另一個功能,簡而言之,敏捷軟體開發很大一部分都要歸功於持續整合
根據持續整合的設計,程式碼從提交到生產,整個過程有以下幾步:
-
提交
流程的第一步,是開發者向程式碼倉庫提交程式碼,所有後面的步驟都始於原生程式碼的一次提交
-
測試(第一輪)
程式碼倉庫對提交操作配置了鉤子,只要提交程式碼或者合併進主幹,就會跑自動化測試
-
構建
通過第一輪測試,程式碼就可以合併進主幹,就算可以交付了
-
測試(第二輪)
構建完成,就要進行第二輪測試。如果第一輪已經涵蓋了所有測試內容,第二輪可以省略,當然,這時構建步驟也要移到第一輪測試前面
-
部署
過了第二輪測試,當前程式碼就是一個可以直接部署的版本。將這個版本的所有檔案打包存檔,發到生產伺服器
-
回滾
一旦當前版本發生問題,就要回滾到上一個版本的構建結果。最簡單的做法就是修改一下符號連結,指向上一個版本的目錄
2. 組成要素
1、一個自動構建過程,從檢出程式碼、編譯構建、執行測試、結果記錄、測試統計等都是自動完成的,無需人工干預
2、一個程式碼儲存庫,即需要版本控制軟體來保障程式碼的可維護性,同時作為構建過程的素材庫,一般使用 SVN 或 Git
3、一個持續整合伺服器, Jenkins 就是一個配置簡單和使用方便的持續整合伺服器
3. 持續整合的好處
1、降低風險,由於持續整合不斷去構建,編譯和測試,可以很早期發現問題,所以修復的代價就少;
2、對系統健康持續檢查,減少釋出風險帶來的問題;
3、減少重複性工作;
4、持續部署,提供可部署單元包;
5、持續交付可供使用的版本;
6、增強團隊信心
4. 持續整合流程說明
1)開發人員每天進行程式碼提交,提交到 Git 倉庫
2)然後,Jenkins 作為持續整合工具,使用 Git 工具或者 Git 倉庫拉取程式碼到整合伺服器,再配合 JDK、Maven 等軟體完成程式碼編譯、程式碼測試與審查、測試、打包等工作,在這個過程中有一步出錯,都要重新執行一次流程
3)最後,Jenkins 把生成的包分發到測試伺服器或生產伺服器
Gitlab 程式碼託管伺服器
GitLab 是一個用於倉庫管理系統的開源專案,使用 Git 作為程式碼管理工具,並在此基礎上搭建起來的 web 服務
GitLab 和 GitHub 一樣屬於第三方基於 Git 開發的作品,免費且開源。不同的是,GitLab 可以部署到自己的伺服器上,資料庫等一切資訊都掌握在自己手上,適合團隊內部協作開發
以 centos 為例,安裝步驟如下:
-
安裝相關依賴
yum -y install policycoreutils openssh-server openssh-clients postfix
-
啟動 ssh 服務 & 設定為開機啟動
systemctl enable sshd && sudo systemctl start sshd
-
設定 postfix 開機自啟,並啟動,postfix 支援 gitlab 發信功能
systemctl enable postfix && systemctl start postfix
-
開放 ssh 以及 http 服務,然後重新載入防火牆列表
firewall-cmd --add-service=ssh --permanent firewall-cmd --add-service=http --permanent firewall-cmd --reload
如果關閉防火牆就不需要做以上配置
-
下載 gitlab 包,並且安裝線上下載安裝包
wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el6/gitlab-ce-12.4.2-ce.0.el6.x 86_64.rpm](https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el6/gitlab-ce-12.4.2-ce.0.el6.x86_64.rpm
-
修改 gitlab 配置
vi /etc/gitlab/gitlab.rb
修改 gitlab 訪問地址和埠,預設為 80,我們改為 82
external_url ‘http://192.168.66.100:82’ nginx[‘listen_port’] = 82
-
過載配置及啟動
gitlab gitlab-ctl reconfigure gitlab-ctl restart
-
把埠新增到防火牆
firewall-cmd --zone=public --add-port=82/tcp --permanent firewall-cmd --reload
Jenkins
Jenkins 是一款流行的開源持續整合(Continuous Integration)工具,廣泛用於專案開發,具有自動化構建、測試和部署等功能
1. Jenkins 安裝
-
獲取 Jenkins 安裝包,下載頁面:https://jenkins.io/zh/download/
進行安裝:
rpm -ivh jenkins-2.190.3-1.1.noarch.rpm
-
修改 Jenkins 配置
vi /etc/syscofig/jenkins
修改內容如下:
JENKINS_USER="root" JENKINS_PORT="8888"
-
啟動 Jenkins
systemctl start jenkins
-
開啟瀏覽器訪問 http://localhost:8888
-
獲取並輸入 admin 賬戶密碼
cat /var/lib/jenkins/secrets/initialAdminPassword
2. Jenkins 外掛管理
Jenkins 本身不提供很多功能,我們可以通過使用外掛來滿足我們的使用。例如從Gitlab拉取程式碼,使用Maven構建專案等功能需要依靠外掛完成
Jenkins 國外官方外掛地址下載速度非常慢,可以修改為國內外掛地址:Jenkins - Manage Jenkins - Manage Plugins,點選 Available
這樣做是為了把 Jenkins 官方的外掛列表下載到本地,接著修改地址檔案,替換為國內外掛地址
cd /var/lib/jenkins/updates
sed -i 's/http:\/\/updates.jenkinsci.org\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' default.json && sed -i
's/http:\/\/www.google.com/https:\/\/www.baidu.com/g' default.json
最後,Manage Plugins 點選 Advanced,把 Update Site 改為國內外掛下載地址 https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
Sumbit 後,在瀏覽器輸入:http://localhost:8888/restart,重啟 Jenkins,下載中文漢化外掛
Jenkins - Manage Jenkins - Manage Plugins,點選 Available,搜尋 "Chinese",勾選並安裝。重啟 Jenkins 後,就看到 Jenkins 漢化了
3. Jenkins 使用者許可權管理
我們可以利用 Role-based Authorization Strategy 外掛來管理 Jenkins 使用者許可權,安裝外掛,點選 Manage Jenkins,選擇 Configure Global Security,授權策略切換為 Role-Based Strategy,儲存
在系統管理頁面進入 Manage and Assign Roles,點選 Manage Roles,可建立角色
- Global roles(全域性角色):管理員等高階使用者可以建立基於全域性的角色
- Project roles(專案角色):針對某個或者某些專案的角色
- Slave roles(奴隸角色):節點相關的許可權
在系統管理頁面進入 Manage Users,建立使用者。接下來是為使用者分配角色,系統管理頁面進入 Manage and Assign Roles,點選 Assign Roles,為使用者分配角色
4. Jenkins 憑證管理
憑據可以用來儲存需要密文保護的資料庫密碼、Gitlab 密碼資訊、Docker 私有倉庫密碼等,以便 Jenkins 可以和這些第三方的應用進行互動
要在 Jenkins 使用憑證管理功能,需要安裝 Credentials Binding 外掛。安裝外掛後,會多出一個憑證選單,在這裡管理所有憑證
可以新增的憑證有五種:
- Username with password:使用者名稱和密碼
- SSH Username with private key:使用 SSH 使用者和金鑰
- Secret file:需要保密的文字檔案,使用時 Jenkins 會將檔案複製到一個臨時目錄中,再將檔案路徑設定到一個變數中,等構建結束後,所複製的 Secret file 就會被刪除
- Secret text:需要儲存的一個加密的文字串,如釘釘機器人或 Github 的 api token
- Certificate:通過上傳證照檔案的方式
5. 整合 Maven
-
Jenkins 關聯 JDK 和 MAVEN
Jenkins - Global Tool Configuration - JDK,新增 JDK,配置指定 JDK 的 JAVA_HOME
Jenkins - Global Tool Configuration - Maven,新增 Maven,配置指定 MAVEN 的 MAVEN_HOME
-
新增 Jenkins 全域性變數
Manage Jenkins - Configure System - Global Properties,新增三個全域性變數 JAVA_HOME、M2_HOME、PATH+EXTRA
我們也可以在拉取程式碼時完成構建,選擇 構建 - 增加構建步驟 - Execute Shell,輸入:mvn clean package
儲存配置後,選擇專案,點選構建 Build Now 開始構建專案
檢視 linux 的 /var/lib/jenkins/workspace/**目錄,會生成一個 target 目錄,裡面有相應的包生成
Jenkins 專案構建型別
Jenkins 中自動構建專案的型別有很多,常用的有以下三種:
- 自由風格軟體專案(FreeStyle Project)
- Maven 專案(Maven Project)
- 流水線專案(Pipeline Project)
每種型別的構建都可以完成一樣的構建過程與結果,只是在操作方式、靈活度等方面有所區別,在實際開發中,可以根據自己的需求和習慣來選擇
1. 自由風格專案構建
一個自由風格專案來完成專案的整合過程:拉取程式碼 - 編譯 - 打包 - 部署
-
建立專案
-
配置原始碼管理,從 gitlab 拉取程式碼
-
編譯打包
構建 - 新增構建步驟 - Executor Shell
echo "開始編譯和打包" mvn clean package echo "編譯和打包結束"
-
部署,把專案部署到遠端的 Tomcat
Jenkins 部署專案到 Tomcat 伺服器,需要用到 Tomcat 的使用者,所以修改 tomcat 以下配置,新增使用者及許可權
vi /opt/tomcat/conf/tomcat-users.xml
內容如下:
<tomcat-users> <role rolename="tomcat"/> <role rolename="role1"/> <role rolename="manager-script"/> <role rolename="manager-gui"/> <role rolename="manager-status"/> <role rolename="admin-gui"/> <role rolename="admin-script"/> <user username="tomcat" password="tomcat" roles="manager-gui,managerscript,tomcat,admin-gui,admin-script"/> </tomcat-users>
為了能夠剛才配置的使用者登入到 Tomcat,還需要修改以下配置
vi /opt/tomcat/webapps/manager/META-INF/context.xml
把下面內容註釋
<!-- <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" /> -->
重啟 Tomcat
/opt/tomcat/bin/shutdown.sh 停止 /opt/tomcat/bin/startup.sh 啟動
訪問:http://localhost:8080/manager/html ,輸入 tomcat 和 tomcat,看到以下頁面代表成功
Jenkins 本身無法實現遠端部署到 Tomcat 的功能,需要安裝 Deploy to container 外掛實現
新增 Tomcat 使用者憑證,新增構建後操作,選擇 Deploy war/ear to a container,部署到容器(遠端 tomcat)
改動程式碼後的持續整合
- 原始碼修改並提交到 gitlab
- 在 Jenkins 中專案重新構建
- 訪問 Tomcat
2. Maven 專案構建
使用 Maven 專案構建需要安裝 Maven Integration 外掛,拉取程式碼和遠端部署的過程和自由風格專案一樣,只是構建部分不同。之前是通過 shell 來指定編譯後的行為,現在則是在 Build 操作介面輸入指定的 pom.xml 檔案路徑,輸入 maven 指令
3. Pipeline 流水線專案構建
3.1 Pipeline 簡介
Pipeline,簡單來說,就是一套執行在 Jenkins 上的工作流框架,將原來獨立執行於單個或者多個節點的任務連線起來,實現單個任務難以完成的複雜流程編排和視覺化的工作
Pipeline 指令碼是由 Groovy 語言實現的,支援兩種語法:Declarative(宣告式)和 Scripted Pipeline(指令碼式)語法
Pipeline 也有兩種建立方法:
- 可以直接在 Jenkins 的 Web UI 介面中輸入指令碼
- 也可以通過建立一個 Jenkinsfile 指令碼檔案放入專案原始碼庫中(推薦在 Jenkins 中直接從原始碼控制 SCM 中直接載入 Jenkinsfile Pipeline 這種方法)
要使用 Pipeline,需安裝 Pipeline 外掛,Manage Jenkins - Manage Plugins - 可選外掛 – 安裝 Pipeline,安裝外掛後,建立專案的時候多了流水線型別
3.2 Pipeline 語法快速入門
-
Declarative 宣告式 Pipeline
流水線 - 選擇 Declarative Pipeline - 選擇 HelloWorld 模板,生成內容如下:
pipeline { agent any stages { stage('Hello') { steps { echo 'Hello World' } } } }
- stages:代表整個流水線的所有執行階段,通常 stages 只有一個,裡面包含多個 stage
- stage:代表流水線中的某個階段,可能出現多個,一般分為拉取程式碼,編譯構建,部署等階段
- steps:代表一個階段內需要執行的邏輯,steps 裡面是 shell 指令碼,git 拉取程式碼,ssh 遠端釋出等任意內容
編寫一個簡單的宣告式 Pipeline:
pipeline { agent any stages { stage('拉取程式碼') { steps { echo '拉取程式碼' } } stage('編譯構建') { steps { echo '編譯構建' } } stage('專案部署') { steps { echo '專案部署' } } } }
點選構建,可以看到整個構建過程
我們可以在流水線語法裡選擇片段生成器,快速生成 Pipeline 程式碼:
-
生成一個 pull stage
選擇
checkout from version controller
,拉取程式碼,選擇型別為 git,填寫好 git 專案地址,填寫拉取分支名字,生成流水線指令碼,指令碼里就包含了憑證資訊 -
生成一個構建 stage
選擇
sh:shell script
,輸入mvc clean package
,點選生成指令碼 -
生成一個部署 stage
選擇
deploy
,填寫WAR files:targer/*.war
,選擇 tomcat 遠端,然後填寫 tomcat 的地址就可遠端部署,可以同時部署多臺 tomcat
-
Scripted 指令碼式 Pipeline
流水線 - 選擇 Scripted Pipeline,編寫一個簡單的指令碼式 Pipeline:
node { def mvnHome stage('拉取程式碼') { // for display purposes echo '拉取程式碼' } stage('編譯構建') { echo '編譯構建' } stage('專案部署') { echo '專案部署' } }
- Node:節點,一個 Node 就是一個 Jenkins 節點,Master 或者 Agent,是執行 Step 的具體執行環境
- Stage:階段,一個 Pipeline 可以劃分為若干個 Stage,每個 Stage 代表一組操作,比如:Build、Test、Deploy,Stage 是一個邏輯分組的概念
- Step:步驟,Step 是最基本的操作單元,可以是列印一句話,也可以是構建一個 Docker 映象,由各類 Jenkins 外掛提供,比如命令:
sh 'make'
,就相當於我們平時 shell 終端中執行 make 命令一樣
完整程式碼如下:
pipeline{ agentanystages{ stage('拉取程式碼'){ steps{ checkout([ $class: 'GitSCM', branches: [ [name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [ ], submoduleCfg: [ ], userRemoteConfigs: [ [ credentialsId: '68f2087f-a034-4d39-a9ff-1f776dd3dfa8', url: 'git@192.168.66.100: itheima_group/web_demo.git' ] ] ]) } }stage('編譯構建'){ steps{ shlabel: '', script: 'mvncleanpackage' } }stage('專案部署'){ steps{ deployadapters: [ tomcat8(credentialsId: 'afc43e5e-4a4e-4de6-984fb1d5a254e434', path: '', url: 'http: //192.168.66.102: 8080') ], contextPath: null, war: 'target/*.war' } } } }
-
Pipeline Script from SCM
之前我們都是直接在 Jenkins 的 UI 介面編寫 Pipeline 程式碼,這樣不方便指令碼維護,建議把 Pipeline 指令碼放在專案中,一起進行版本控制
-
在專案根目錄建立 Jenkinsfile 檔案,編寫指令碼內容,把檔案上傳到 Gitlab
-
在專案中引用該檔案
-
點選構建,就開始拉取,拉取後拿到 Jenkins 後操作
-
Jenkins 構建觸發器
Jenkins 內建了四種構建觸發器:
- 遠端觸發構建
- 其他工程構建後觸發(Build after other projects are build)
- 定時構建(Build periodically)
- 輪詢SCM(Poll SCM)
1. 遠端觸發構建
在 Jenkins 工程下點選配置,然後構建觸發器,其他系統傳送 URL 請求,就可以讓 Jenkins 開始構建(觸發構建)
觸發構建url:http://192.168.66.101:8888/job/web_demo_pipeline/build?token=6666
2. 其他工程構建後觸發
該觸發器的需求是:當前專案需要前一個專案構建完成後才能觸發
-
建立 pre_job 流水線工程,該工程構建完成後觸發當前專案
-
配置需要觸發的工程
3. 定時構建
選擇 Build periodically,輸入定時字串表示式,即可定時構建
下面是一些定時表示式的例子:
每30分鐘構建一次:H代表形參 H/30 * * * * 10:02 10:32
每2個小時構建一次: H H/2 * * *
每天的8點,12點,22點,一天構建3次: (多個時間點中間用逗號隔開) 0 8,12,22 * * *
每天中午12點定時構建一次 H 12 * * *
每天下午18點定時構建一次 H 18 * * *
在每個小時的前半個小時內的每10分鐘 H(0-29)/10 * * * *
每兩小時一次,每個工作日上午9點到下午5點(也許是上午10:38,下午12:38,下午2:38,下午4:38) H H(9-16)/2 * * 1-5
4. 輪詢 SCM
輪詢 SCM,是指定時掃描原生程式碼倉庫的程式碼是否有變更,如果程式碼有變更就觸發專案構建
Jenkins 會定時掃描本地整個專案的程式碼,增大系統的開銷,不建議使用輪詢 SCM
5. Git hook 自動觸發構建
利用 Gitlab 的 webhook 實現程式碼 push 到倉庫,立即觸發專案自動構建,需要安裝兩個外掛:Gitlab Hook 和 GitLab
需要把生成的 webhook URL 配置到 Gitlab 中:
- 使用 root 賬戶登入到後臺,點選 Admin Area - Settings - Network,勾選
Allow requests to the local network from web hooks and services
讓網路鉤子允許請求本地網路 - 點選專案 - Settings - Integrations,在專案新增 webhook
在 Jenkins 中,Manage Jenkins - Configure System,取消勾選 Enable authentication for '/project' end-point GitLab connections
Jenkins 引數化構建
有時候在專案構建的過程中,我們需要根據使用者的輸入動態傳入引數,從而影響整個構建結果,比如:我們希望根據使用者傳入的引數,部署不同的分支,這時我們可以使用引數化構建
在 Jenkins 新增字串型別引數
改動 pipeline 流水線程式碼
點選 Build with Parameters,就用指定引數開始了構建
Jenkins 配置郵箱伺服器
安裝 Email Extension 外掛 template,Jenkins 設定郵箱相關引數:Manage Jenkins - Configure System,
設定 Jenkins 預設郵箱資訊
在專案根目錄編寫 email.html,並把檔案推送到 Gitlab,內容如下:
<!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: Tahoma, Arial, Helvetica, sansserif">
<tr>
<td>(本郵件是程式自動下發的,請勿回覆!)</td>
</tr>
<tr>
<td><h2>
<font color="#0000FF">構建結果 - ${BUILD_STATUS}</font>
</h2></td>
</tr>
<tr>
<td><br />
<b><font color="#0B610B">構建資訊</font></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>構建日誌: <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>
<tr>
<td><b><font color="#0B610B">Changes Since Last
Successful Build:</font></b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td>
<ul>
<li>歷史變更記錄 : <a
href="${PROJECT_URL}changes">${PROJECT_URL}changes</a></li>
</ul> ${CHANGES_SINCE_LAST_SUCCESS,reverse=true, format="Changes for
Build #%n:<br />%c<br />",showPaths=true,changesFormat="<pre>[%a]<br
/>%m</pre>",pathFormat=" %p"}
</td>
</tr>
<tr>
<td><b>Failed Test Results</b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td><pre
style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica,
sans-serif">$FAILED_TESTS</pre>
<br /></td>
</tr>
<tr>
<td><b><font color="#0B610B">構建日誌 (最後 100行):</font></b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td><textarea cols="80" rows="30" readonly="readonly"
style="font-family: Courier New">${BUILD_LOG,
maxLines=100}</textarea>
</td>
</tr>
</table>
</body>
</html>
編寫 Jenkinsfile 新增構建後傳送郵件的 Pipeline 程式碼,這個 post 可以到宣告式指令碼生成器裡選擇 post,選擇對應的 conditions,比如選擇永遠都執行等等,他和 stage 是分開的
pipeline {
agent any
stages {
stage('拉取程式碼') {
steps {
checkout([$class: 'GitSCM',
branches: [[name: '*/master']],
doGenerateSubmoduleConfigurations: false,
extensions: [],
submoduleCfg: [],
userRemoteConfigs: [[credentialsId: '68f2087f-a034-4d39-a9ff-1f776dd3dfa8',
url: 'git@192.168.66.100:itheima_group/web_demo.git']]])
}
}
stage('編譯構建') {
steps {
sh label: '', script: 'mvn clean package'
}
}
stage('專案部署') {
steps {
deploy adapters: [tomcat8(credentialsId: 'afc43e5e-4a4e-4de6-984fb1d5a254e434',
path: '',
url: 'http://192.168.66.102:8080')],
contextPath: null,
war: 'target/*.war'
}
}
}
post { # 主要看這就行
always {
emailext(
subject: '構建通知:${PROJECT_NAME} - Build # ${BUILD_NUMBER} -${BUILD_STATUS}!',
body: '${FILE,path="email.html"}',
to: 'xxx@qq.com'
)
}
}
}
郵件相關全域性引數參考列表:系統設定 - Extended E-mail Notification - Content Token Reference,點選旁邊的 ? 號