持續集成
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,點擊旁邊的 ? 號