11.22 Docker+Jenkins+Pipeline實現持續集成 java項目構建

插件推薦

插件名作用

Blue OceanJenkins2.7以後可安裝,是Jenkins的一種新視圖,能夠通過圖形化的界面創建和編輯Jenkinsfile,實現pipeline as codePipeline Maven Integration Plugin在pipeline中集成maven,即可使用withMaven{}命令Config File Provider Plugin可創建並管理Maven的settings文件及其他配置文件JUnit Attachments Plugin可以對單元測試生成的測試結果在Jenkins中進行展示Task Scanner Plugin跟蹤項目中TODO和FIXTURERancher Plugin可以在Rancher1.*上創建及更新服務HTTP Request Plugin可以發送HTTP請求


創建pipeline項目

在Jenkins首頁點擊“新建”進入項目的創建頁面,輸入任務名後,選擇“流水線”(或"Pipeline"),點擊“確定”,即可創建一個新的pipeline項目,如下圖所示:

創建pipeline項目

編寫pipeline腳本

在項目配置頁面,找到"流水線"一項,選擇"Pipeline/>下面首先介紹腳本式pipeline的基本結構,以及如何使用Jenkins自帶的語法生成器:

pipeline的基本結構

pipeline語法分為了腳本式pipeline和聲明式pipeline,以下均為腳本式pipeline。腳本式Pipeline本質是一個groovy腳本,可以直接使用Groovy提供的大多數功能,執行的順序是從頂部開始的順序執行。整個Pipeline使用node塊結構,每一個階段的執行用

stage表示,如下是Jenkins提供的GitHub+Maven的模板(在腳本右上方處try sample Pipeline選擇GitHub+Maven即會自動生成):

 node {
def mvnHome
stage('Preparation') { // for display purposes
// Get some code from a GitHub repository
git 'https://github.com/jglick/simple-maven-project-with-tests.git'
// Get the Maven tool.
// ** NOTE: This 'M3' Maven tool must be configured
// ** in the global configuration.
mvnHome = tool 'M3'
}
stage('Build') {
// Run the maven build
if (isUnix()) {
sh "'${mvnHome}/bin/mvn' -Dmaven.test.failure.ignore clean package"
} else {
bat(/"${mvnHome}\\bin\\mvn" -Dmaven.test.failure.ignore clean package/)
}
}
stage('Results') {
junit '**/target/surefire-reports/TEST-*.xml'
archive 'target/*.jar'
}
}

語法生成

Jenkins提供了語法生成器,可以幫助我們編寫pipeline流程。點擊腳本下的Pipeline Syntax可進入pipiline-syntax頁面,如下圖所示:

語法生成器


在pipiline-syntax頁面中選擇需要執行的構建步驟,填寫參數,然後點擊“Generate Pipeline Script”即可生成需要的語法,如下圖所示:

語法生成器使用


點擊pipeline-syntax頁面右方的Global Variables Reference,頁面會展示pipeline腳本中可直接使用的全局變量,有docker、env、currentBuild等。當需要在腳本中使用全局變量,則使用"."連接全局變量和屬性/方法,例如,使用docker變量構建鏡像的方法為docker.build(your_imagename),獲取當前構建結果的屬性為currentBuild.result

Java項目的pipeline編寫

這裡我們以java項目為例編寫腳本式pipeline,這個pipeline進行了打包構建、生成docker鏡像、並將鏡像推送到docker倉庫中,最後實現在Rancher上的自動服務部署)。

node {

stage('Preparation') {
}

stage('Build') {
}

stage('DockerBuild') {
}

stage('Rancher') {
}

}

gitlab代碼拉取

Jenkins提供了有兩種獲取源代碼的語法git和checkout,推薦使用checkout的方式,因為其更強大,可以配置工作目錄、選擇是否使用鉤子等等,如下為兩種語法的使用模板:

checkout scm: [$class: 'GitSCM', branches: [[name: "*/${repoBranch}"]], doGenerateSubmoduleConfigurations: false, userRemoteConfigs: [[credentialsId: "${gitCredentialsId}", url: "${repoUrl}"]]]

git branch: "${repoBranch}", credentialsId: "${gitCredentialsId}", url: "${repoUrl}"

checkout的語法生成配置如下,需填寫倉庫url、branch等:

checkout語法生成


可以看到上圖中選擇了拉取gitlab代碼的認證權限為”demo-ssh“,該認證需要在Credentials中進行配置;如果沒有,也可點擊Add進行添加,如下圖為配置ssh密鑰(對應公鑰已添加在git倉庫中):

添加認證


此時倉庫認證成功,點擊下方的GeneratePipelineScript生成語法,如下:

checkout語法生成


將該語法粘貼到Pipeline腳本中,如下:

stage('Preparation') {
checkout([$class: 'GitSCM', branches: [[name: '*/jenkins-test']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'c26f36ef-031d-4dbd-9d71-95be6a59e0f6', url: 'git@****.git']]])
}

maven構建

接下來需要添加代碼構建階段“Build”,通過mvn命令實現代碼構建,在這之前我們需要在Jenkins添加一些配置。


(1)下載插件Pipeline Maven Integration Plugin、Jenkins Config File Provicer Plugin,輔助插件JUnit Attachments Plugin、Task Scanner Plugin
(2)配置maven工具,進入系統管理——全局工具配置,找到Maven項,點擊新增Maven,即可配置安裝一個新的Maven工具,如下圖所示:

maven工具安裝


(3)對於一些java項目,需要定製化的使用settings.xml文件(例如添加maven鏡像和倉庫)。進入系統管理——Managed files,點擊Add a new Config後,選擇Maven settings.xml,點擊Submit,如下圖:

添加settings.xml文件


然後在配置頁面編寫settings.xml文件。
然後pipeline-syntax頁面,找到withMaven語法,進行maven的配置,包括選擇maven工具、settings文件等等,如下圖:

withMaven


最後將語法添加在Pipeline腳本中,並在withMaven裡使用批處理命令來執行mvn的構建命令,如下:

stage('Build') {
withMaven(
maven: 'maven3.5.2',
mavenSettingsConfig: '9e88adc5-8b36-4f00-b6f6-fdb15e9286ae') {
sh 'mvn -U clean package -Dmaven.test.skip=true'
}
}

(4)輔助插件可幫助我們查看JUnit執行情況、追蹤項目的TODO和FIXME等等

docker鏡像生成和發佈

在上篇中,我們介紹瞭如何在Jenkins容器裡使用Docker命令,此時,我們可以直接利用全局變量docker進行docker的各種操作(具體見Global Variables)。
我們首先將Build中生成的jar包複製到Dockerfile所在目錄下,然後使用方法docker.build()生成docker鏡像,且該方法會返回一個Image的對象,然後使用Image.push()方法即可將該鏡像推送到遠程倉庫,如下:

stage('DockerBuild') {
sh """
rm -f src/docker/*.jar
cp target/*.jar src/docker/*.jar
"""

dir ("src/docker/") {
def image = docker.build("*****/demo:1.0.0")
image.push()
}
}

注:由於當前我們使用root用戶運行Jenkins容器,因此無法直接利用docker.withRegistry()或withDockerRegistry連接dockerhub倉庫(該語句會生成倉庫的config文件,該文件存放在/var/jenkins_home文件夾下,但docker命令會直接從/root下讀取配置文件,導致配置文件無效)。因此,我們可以將config文件提前打包到Jenkins容器中,或者直接在命令行中進行登錄。

Rancher1.X上的服務部署

我們的服務都使用了Rancher進行部署運行。當docker鏡像已經推送到dockerhub倉庫後,需要做的就是向Rancher發送請求,使其用最新構建的鏡像進行服務更新。
(1)下載安裝Rancher Plugin插件
(2)需要在Rancher中添加Environment Api Key,具體步驟為進入需部署服務的Rancher環境,點擊API下的Keys,點擊打開ADVANCEDOPTIONS,點擊AddEnvironmentAPIKeys,如下圖:

Rancher添加Api Key


(3)然後在彈出的對話框中填寫Name、Description,點擊Create,記錄創建的Access Key和Secret Key,如下圖:

記錄Key


(4)在Jenkins的Credentials中添加一個類型為Username with password的認證,username和password分別對應於上一步生成的Access Key和Secret Key,如下圖

添加Rancher認證


(5)然後在語法生成器中,找到rancher進行如下圖的配置:

Rancher語法生成


(6)最後的Rancher服務部署階段的pipeline腳本流程如下

stage('Rancher') {
rancher confirm: false, credentialId: 'b56bd9b2-3277-4072-baae-08d73aa26549', endpoint: 'https://*******.com/v2-beta', environmentId: '1a226', environments: '', image: '*/demo:1.0.0', ports: '', service: 'jenkins/demo', timeout: 50
}

Rancher2.X上的服務部署

在編寫文檔時,尚未有插件支持Rancher2.X的服務自動部署,因此採取了直接向Rancher的Api發送請求的方式實現容器的更新。
(1)下載插件HTTP Request Plugin
(2)類似Rancher1.X,首先需要在Rancher上新增一個Api Key。進入需要部署服務的Rancher環境,選擇右上角用戶頭像下的API & Keys進入配置頁面,點擊Add Key,填寫描述和過期時間,進入API Key Created頁面後,記錄Endpoint、Access Key和Secret Key,如下圖:

Rancher2.X添加API Key


(3)類似Rancher1.X,然後在Jenkins的Credentials中添加一個類型為Username with password的認證,username和password分別對應於上一步生成的Access Key和Secret Key,如下圖

配置Rancher2認證


(4)不同與Rancher1.X,Rancher2.X尚未有插件支持,因此需要用http請求的方式調用Rancher的API。在實踐過程中,我主要用到了4個Rancher API,分別是:

GET https://<rancher>/v3/project/<project>/workloads/deployment:<rancher>:<rancher> # 獲取一個服務的詳細信息
GET https://<rancher>/v3/project/<project>/pods/?workloadId=deployment:<rancher>:<rancher> # 獲取服務的所有容器信息


DELETE https://<rancher>/v3/project/<project>/pods/<rancher>:<container> # 根據容器名刪除容器
PUT https://<rancher>/v3/project/<project>/workloads/deployment:<rancher>:<rancher> # 更新服務/<rancher>/<rancher>/<project>/<rancher>/<container>/<rancher>/<project>/<rancher>/<rancher>/<rancher>/<project>/<rancher>/<rancher>/<rancher>/<project>/<rancher>

具體的腳本如下:

// 查詢服務信息
def response = httpRequest acceptType: 'APPLICATION_JSON', authentication: "${RANCHER_API_KEY}", contentType: 'APPLICATION_JSON', httpMode: 'GET', responseHandle: 'LEAVE_OPEN', timeout: 10, url: "${rancherUrl}/workloads/deployment:${rancherNamespace}:${rancherService}"
def serviceInfo = new JsonSlurperClassic().parseText(response.content)
response.close()

def dockerImage = imageName+":"+imageTag
if (dockerImage.equals(serviceInfo.containers[0].image)) {
// 如果鏡像名未改變,直接刪除原容器
// 查詢容器名稱
response = httpRequest acceptType: 'APPLICATION_JSON', authentication: "${RANCHER_API_KEY}", contentType: 'APPLICATION_JSON', httpMode: 'GET', responseHandle: 'LEAVE_OPEN', timeout: 10, url: "${rancherUrl}/pods/?workloadId=deployment:${rancherNamespace}:${rancherService}"
def podsInfo = new JsonSlurperClassic().parseText(response.content)
def containerName = podsInfo.data[0].name
response.close()
// 刪除容器
httpRequest acceptType: 'APPLICATION_JSON', authentication: "${RANCHER_API_KEY}", contentType: 'APPLICATION_JSON', httpMode: 'DELETE', responseHandle: 'NONE', timeout: 10, url: "${rancherUrl}/pods/${rancherNamespace}:${containerName}"

} else {
// 如果鏡像名改變,使用新鏡像名更新容器
serviceInfo.containers[0].image = dockerImage
// 更新
def updateJson = new JsonOutput().toJson(serviceInfo)
httpRequest acceptType: 'APPLICATION_JSON', authentication: "${RANCHER_API_KEY}", contentType: 'APPLICATION_JSON', httpMode: 'PUT', requestBody: "${updateJson}", responseHandle: 'NONE', timeout: 10, url: "${rancherUrl}/workloads/deployment:${rancherNamespace}:${rancherService}"
}

注:如果Rancher1.X的部署不能通過插件滿足,也可以採取調用API的方式實現,例如一個服務有多個容器的情況。

執行構建

腳本編寫完成後,點擊保存進入項目頁面。點擊項目的“立即構建”,會手動觸發構建過程,項目的構建歷史會在右側顯示,同時每個階段的執行狀態及日誌會以表格的形式在Stage View中展示

Docker+Jenkins+Pipeline實現持續集成 java項目構建


分享到:


相關文章: