現(xiàn)有混合云平臺的場景下惯吕,即有線下和線上的環(huán)境庭砍,又有測試與正式的場景,而且結(jié)合了Docker混埠,導(dǎo)致打包內(nèi)容有所區(qū)分怠缸,且服務(wù)的發(fā)布流程復(fù)雜起來,手工打包需要在編譯階段就要根據(jù)環(huán)境到處更改配置钳宪,因此純手工發(fā)布增加了實(shí)施的難度揭北,需要一個(gè)統(tǒng)一的適應(yīng)各種環(huán)境部署的方案。
基于微服務(wù)的發(fā)布流程
手動/自動構(gòu)建 -> Jenkins 調(diào)度 K8S API ->動態(tài)生成 Jenkins Slave pod -> Slave pod 拉取 Git 代碼/編譯/打包鏡像 ->推送到鏡像倉庫 Harbor -> Slave工作完成吏颖,Pod 自動銷毀 ->部署到測試或生產(chǎn) Kubernetes(K8S)平臺搔体。
上面是理想狀況下的將服務(wù)編譯打包成鏡像上傳到鏡像庫后部署到Kubernetes平臺的一個(gè)流程,但問題是:
我們有線上線下平臺半醉,代碼在線下GitLab疚俱,是出不了外網(wǎng)的,因此線上K8S集群無法拉取代碼編譯缩多。
Jenkins的master所在服務(wù)器是CentOS6.5呆奕,沒有Docker環(huán)境,也沒有在K8S集群服務(wù)器內(nèi)衬吆,因此無法直接執(zhí)行docker build鏡像和 kubectl apply 發(fā)布服務(wù)到K8S集群梁钾。
Jenkins的slave節(jié)點(diǎn)都是無法訪問外網(wǎng)的,
線上服務(wù)需要Pinpoint而線下環(huán)境暫時(shí)不需要啟用Pinpoint逊抡,否則一直報(bào)錯(cuò)姆泻,因此需要根據(jù)選擇的環(huán)境動態(tài)的構(gòu)建Dockerfile,而且要求整個(gè)發(fā)布流程可選擇冒嫡。
就上面現(xiàn)實(shí)問題拇勃,我們將發(fā)布流程簡化:
關(guān)鍵點(diǎn):
Docker鏡像的打包使用com.spotify的docker-maven-plugin插件結(jié)合Dockerfile,調(diào)用遠(yuǎn)程服務(wù)器的Docker環(huán)境生成鏡像孝凌。
K8S服務(wù)部署采用的是ssh方式方咆,將Deployment文件上傳到K8S集群服務(wù)器,然后執(zhí)行部署命令胎许。
如何利用Dockerfile打包鏡像
之前也是用com.spotify的docker-maven-plugin插件來打包鏡像并推送到私有鏡像倉庫峻呛,但問題是無法根據(jù)環(huán)境寫條件判斷,如動態(tài)選擇是否需要啟動pinpoint辜窑,線上線下庫地址動態(tài)更換钩述,導(dǎo)致鏡像名前綴也是要?jiǎng)討B(tài)變化的,此時(shí)直接配置無法滿足穆碎,需要結(jié)合Dockerfile來實(shí)現(xiàn)牙勘。
先更改pom文件,指定本項(xiàng)目的Dockerfile文件地址所禀,默認(rèn)是放在項(xiàng)目根目錄下:
<plugin><groupId>com.spotify</groupId><artifactId>docker-maven-plugin</artifactId><version>1.2.0</version><configuration><!--覆蓋相同標(biāo)簽鏡像--><forceTags>true</forceTags><!-- 與maven配置文件settings.xml一致 --><serverId>nexus-releases</serverId><!--私有倉庫地址 --><registryUrl>https://${docker.repostory}</registryUrl><!--遠(yuǎn)程Docker地址 --><dockerHost>http://10.3.87.210:2375</dockerHost><!-- 注意imageName一定要是符合正則[a-z0-9-_.]的方面,否則構(gòu)建不會成功 --><!--指定鏡像名稱 倉庫/鏡像名:標(biāo)簽--><imageName>${docker.repostory}/${project.artifactId}:${project.version}</imageName><dockerDirectory>${project.basedir}</dockerDirectory><resources><resource><!-- 指定要復(fù)制的目錄路徑,這里是當(dāng)前目錄 --><!-- 將打包文件放入dockerDirectory指定的位置 --><targetPath>/app/</targetPath><!-- 指定要復(fù)制的根目錄色徘,這里是target目錄 --><directory>${project.build.directory}</directory><!-- 指定需要拷貝的文件恭金,這里指最后生成的jar包 --><include>${project.build.finalName}.jar</include></resource></resources></configuration></plugin>
<registryUrl>https://${docker.repostory}</registryUrl>
指定遠(yuǎn)程倉庫地址,在主項(xiàng)目的<properties>中指定褂策,這里默認(rèn)線上倉庫<docker.repostory>39.95.40.97:5000</docker.repostory>
<dockerHost>http://10.3.87.210:2375</dockerHost>
指定Docker鏡像打包服務(wù)器横腿,這里指定線下服務(wù)器。
<imageName>${docker.repostory}/${project.artifactId}:${project.version}</imageName>
指定鏡像名稱 倉庫/鏡像名:標(biāo)簽
<dockerDirectory>${project.basedir}</dockerDirectory>
指定Dockerfile文件地址斤寂,此處指定項(xiàng)目根目錄
Dockerfile內(nèi)容
FROM join:0.2
MAINTAINER {description} Join
ADD /app/{artifactId}-{version}.jar /app/
ENTRYPOINT ["java", "-Xmx512m","-Dspring.profiles.active={active}",{jarparam} "-jar", "/app/{artifactId}-{version}.jar"]
基礎(chǔ)鏡像用join:0.2耿焊,里面包含了pinpoint和監(jiān)控jvm的promethus客戶端包。
Jarparam會在Jenkins中動態(tài)替換運(yùn)行時(shí)參數(shù)遍搞,active 指定當(dāng)前運(yùn)行環(huán)境罗侯,這里可能有人提議根據(jù)項(xiàng)目yml文件中指定內(nèi)容自動匹配,因?yàn)橐紤]到如果自動匹配 更換線上線下環(huán)境就需要更改yml配置文件后又要上傳到gitlab溪猿,如此沒有必要多做一步钩杰,直接在Jenkins中當(dāng)作參數(shù)指定最為便捷。
此處Dockerfile是通用模板诊县,如果有特殊內(nèi)容添加榜苫,可自行更改,此時(shí)的模板需要在Jenkins運(yùn)行時(shí)替換參數(shù)后才有用翎冲,如果想直接在本機(jī)運(yùn)行打包垂睬,可手動替換參數(shù)內(nèi)容后運(yùn)行:
clean package -DskipTests docker:build
推送
clean package -DskipTests docker:build -DpushImage
Jenkins發(fā)布流程
利用Jenkins的pipeline構(gòu)建流水線
Pipeline也就是構(gòu)建流水線,對于程序員來說抗悍,最好的解釋是:使用代碼來控制項(xiàng)目的構(gòu)建驹饺、測試、部署等缴渊。使用它的好處有很多赏壹,包括但不限于:
l? 使用Pipeline可以非常靈活的控制整個(gè)構(gòu)建過程;
l? 可以清楚的知道每個(gè)構(gòu)建階段使用的時(shí)間衔沼,方便構(gòu)建的優(yōu)化蝌借;
l? 構(gòu)建出錯(cuò)昔瞧,使用stageView可以快速定位出錯(cuò)的階段;
l? 一個(gè)job可以搞定整個(gè)構(gòu)建菩佑,方便管理和維護(hù)等自晰。
Pipeline 支持兩種語法,聲明式和腳本式稍坯。這兩種方法都支持構(gòu)建持續(xù)交付流水線酬荞,都可以通過 web UI 或 Jenkinsfile 文件來定義 Pipeline(通常認(rèn)為創(chuàng)建 Jenkinsfile 文件并上傳到源代碼控制倉庫是最佳實(shí)踐)
Jenkinsfile 就是一個(gè)包含對 Jenkins Pipeline 定義的文本文件,會上傳到版本控制中瞧哟。下面的 Pipeline 實(shí)現(xiàn)了基本的 3 段持續(xù)交付流水線混巧。
聲明式 Pipeline:
// Jenkinsfile (Declarative Pipeline)
pipeline {
??? agent any
??? stages {
??????? stage('Build') {
??????????? steps {
??????????????? echo 'Building..'
??????????? }
??????? }
??????? stage('Test') {
??????????? steps {
??????????????? echo 'Testing..'
??????????? }
??????? }
??????? stage('Deploy') {
??????????? steps {
??????????????? echo 'Deploying....'
??????????? }
??????? }
??? }
}
對應(yīng)的腳本式 Pipeline:
// Jenkinsfile (Scripted Pipeline)
node {
??? stage('Build') {
??????? echo 'Building....'
??? }
??? stage('Test') {
??????? echo 'Building....'
??? }
??? stage('Deploy') {
??????? echo 'Deploying....'
??? }
}
注意,所有的 Pipeline 都會有這三個(gè)相同的 stage勤揩,可以在所有項(xiàng)目的一開始就定義好它們咧党。下面演示在 Jenkins 的測試安裝中創(chuàng)建和執(zhí)行一個(gè)簡單的 Pipeline。
假設(shè)項(xiàng)目已經(jīng)設(shè)置好了源代碼控制倉庫陨亡,并且已經(jīng)按照入門章節(jié)的描述在 Jenkins 中定義好了 Pipeline凿傅。
使用文本編輯器(最好支持 Groovy 語法高亮顯示),在項(xiàng)目根目錄中創(chuàng)建 Jenkinsfile数苫。
上面的聲明式 Pipeline 示例包含了實(shí)現(xiàn)一個(gè)持續(xù)交付流水線所需的最少步驟聪舒。必選指令?agent?指示 Jenkins 為 Pipeline 分配執(zhí)行程序和工作空間。沒有 agent 指令的話虐急,聲明式 Pipeline 無效箱残,無法做任何工作!默認(rèn)情況下 agent 指令會確保源代碼倉庫已經(jīng)檢出止吁,并且可用于后續(xù)步驟被辑。
stage 和 step 指令在聲明式 Pipeline 中也是必須的,用于指示 Jenkins 執(zhí)行什么及在哪個(gè) stage 中執(zhí)行敬惦。
對于腳本式 Pipeline 的更高級用法盼理,上面的示例節(jié)點(diǎn)是至關(guān)重要的第一步,因?yàn)樗鼮?Pipeline 分配了一個(gè)執(zhí)行程序和工作空間俄删。如果沒有 node宏怔,Pipeline 不能做任何工作!在 node 內(nèi)畴椰,業(yè)務(wù)的第一階段是檢出此項(xiàng)目的源代碼臊诊。由于 Jenkinsfile 是直接從源代碼控制中提取的,因此 Pipeline 提供了一種快速簡單的方法來訪問源代碼的正確版本:
// Jenkinsfile (Scripted Pipeline)
node {
??? checkout scm
??? /* .. snip .. */
}
這個(gè) checkout 步驟會從源代碼控制中檢查代碼斜脂,scm 是特殊變量抓艳,它指示運(yùn)行檢出步驟,復(fù)制觸發(fā)了這次 Pipeline 運(yùn)行的指定版本帚戳。
最終的流程樣式:
一般用聲明式來構(gòu)建流水玷或,實(shí)際操作過程中還是發(fā)現(xiàn)腳本式構(gòu)建更順手儡首,而且Groovy語言更方便查資料,因此下面以腳本構(gòu)建為主演示一個(gè)流程偏友。
1.新建任務(wù)
2.填寫任務(wù)名和描述蔬胯,由于防止構(gòu)建歷史太多,只保留3個(gè)约谈。
3.添加構(gòu)建時(shí)全局構(gòu)建參數(shù),用來構(gòu)建流程動態(tài)選擇環(huán)境犁钟,這里有兩種方式棱诱,一種是直接在頁面上添加笼吟,如下圖放妈,一種是在Jenkinsfile中添加(第一次構(gòu)建時(shí)不會出現(xiàn)選項(xiàng)娘赴,第二次構(gòu)建才會出現(xiàn)狸捕,因此首次構(gòu)建需要試構(gòu)建熔吗,暫停再刷新頁面才會有選擇框)凤价,兩種最張效果一樣便斥,這里為了方便采用Jenkinsfile來添加全局參數(shù)国夜。
Jenkinsfile中添加
properties([
? ? ? ? parameters([string(name: 'PORT', defaultValue: '7082', description: '程序運(yùn)行端口'),choice(name: 'ACTIVE_TYPE', choices: ['dev', 'prd', 'local'], description: '程序打包環(huán)境'),choice(name: 'ENV_TYPE', choices: ['online', 'offline'], description: '線上米愿、還是線下環(huán)境'),booleanParam(name: 'ON_PINPOINT', defaultValue: true, description: '是否添加Pinpoint監(jiān)控'),booleanParam(name: 'ON_PROMETHEUS', defaultValue: true, description: '是否添加Prometheus監(jiān)控'),string(name: 'EMAIL', defaultValue: '104@qq.com', description: '打包結(jié)果通知')])
])
4.選擇源碼代碼庫:
需要添加認(rèn)證厦凤,將Jenkins的ssh秘鑰添加到GitLab的頁面中,且需要將此處gitlab中joint用戶添加到需要拉取代碼的項(xiàng)目中才有權(quán)限拉取代碼育苟。
Jenkinsfile位置放在項(xiàng)目的根目錄较鼓。
5. Jenkinsfile中指定maven目錄地址
MVNHOME = '/opt/maven354'
為防止手工填寫項(xiàng)目名和版本號等一系列信息,因此直接讀取pom文件中要編譯項(xiàng)目的這些信息給全局變量:
pom = readMavenPom file: 'pom.xml'
echo "group: ${pom.groupId}, artifactId: ${pom.artifactId}, version: ${pom.version} ,description: ${pom.description}"
artifactId = "${pom.artifactId}"
version = "${pom.version}"
description = "${pom.description}"
根據(jù)選擇的線上環(huán)境還是線下環(huán)境违柏,替換鏡像倉庫ip
if (params.ENV_TYPE == 'offline' || params.ENV_TYPE == null) {
sh "sed -i 's#39.95.40.97:7806#10.3.87.51:8080#g' pom.xml"
image = "10.3.87.51:8080/${artifactId}:${version}"
}
6.編譯
利用maven構(gòu)建博烂,利用上面的內(nèi)容先替換掉Dockerfile、Deployment中的變量漱竖,再根據(jù)選擇的條件是否啟用pinpoint和promethus禽篱,最后編譯。
def jarparam=''
def pinname = artifactId
if( pinname.length() > 23) {
? pinname = artifactId.substring(0,23)
}
//添加pinpointif(params.ON_PINPOINT) {
? jarparam ='"-javaagent:/app/pinpoint-agent/pinpoint-bootstrap-1.8.0.jar","-Dpinpoint.agentId={pinname}", "-Dpinpoint.applicationName={pinname}",'}//添加prometheusif(params.ON_PROMETHEUS) {
? jarparam = jarparam +'"-javaagent:/app/prometheus/jmx_prometheus_javaagent-0.11.0.jar=1234:/app/prometheus/jmx.yaml",'}
sh "sed -i 's#{jarparam}#${jarparam}#g' Dockerfile"sh "sed -i 's#{description}#${description}#g;s#{artifactId}#${artifactId}#g;s#{version}#${version}#g;s#{active}#${params.ACTIVE_TYPE}#g;s#{pinname}#${pinname}#g' Dockerfile"sh "sed -i 's#{artifactId}#${artifactId}#g;s#{version}#${version}#g;s#{port}#${params.PORT}#g;s#{image}#${image}#g' Deployment.yaml"sh "'${MVNHOME}/bin/mvn' -DskipTests clean package"
需要注意的是pinpoint的pinpoint.applicationName不能操作24個(gè)字符馍惹,否則啟用不成功躺率,因此超過的直接截?cái)唷?/p>
Department文件詳情看后文。
跳過測試編譯打包 '${MVNHOME}/bin/mvn' -DskipTests clean package 需要在Jenkins服務(wù)器安裝maven環(huán)境万矾,還有指定maven的jar包私有倉庫地址肥照。
7. Docker打包
前提是上一步指定pom文件中的鏡像倉庫和Dockerfile中的內(nèi)容是替換后的完整內(nèi)容。
sh "'${MVNHOME}/bin/mvn' docker:build"
8. 推送鏡像
sh "'${MVNHOME}/bin/mvn' docker:push"
如何發(fā)布服務(wù)到K8S集群
前面幾步已經(jīng)將項(xiàng)目打包并生成了鏡像并推送到了私有倉庫勤众,下面就是部署服務(wù)到K8S集群舆绎。
先看看Department.yaml文件:
---apiVersion: apps/v1
kind: Deployment
metadata:
? name: {artifactId}
? namespace: default
? labels:
? ? app: {artifactId}
? ? version: {version}
spec:
? selector:
? ? matchLabels:
? ? ? app: {artifactId}
? replicas: 1? template:
? ? metadata:
? ? ? labels:
? ? ? ? app: {artifactId}
? ? ? annotations:
? ? ? ? prometheus.io.jmx: "true"? ? ? ? prometheus.io.jmx.port: "1234"? ? spec:
? ? ? containers:
? ? ? - name: {artifactId}
? ? ? ? image: {image}
? ? ? ? # IfNotPresent\Always? ? ? ? imagePullPolicy: Always
? ? ? ? ports:
? ? ? ? - name: prometheusjmx
? ? ? ? ? containerPort: 1234? ? ? ? livenessProbe: #kubernetes認(rèn)為該pod是存活的,不存活則需要重啟? ? ? ? ? httpGet:
? ? ? ? ? ? path: /health
? ? ? ? ? ? port: {port}
? ? ? ? ? ? scheme: HTTP
? ? ? ? ? initialDelaySeconds: 60## 設(shè)置為系統(tǒng)完全啟動起來所需的最大時(shí)間+若干秒timeoutSeconds: 5? ? ? ? ? successThreshold: 1? ? ? ? ? failureThreshold: 5? ? ? ? readinessProbe: #kubernetes認(rèn)為該pod是啟動成功的? ? ? ? ? httpGet:
? ? ? ? ? ? path: /health
? ? ? ? ? ? port: {port}
? ? ? ? ? ? scheme: HTTP
? ? ? ? ? initialDelaySeconds: 40## 設(shè)置為系統(tǒng)完全啟動起來所需的最少時(shí)間timeoutSeconds: 5? ? ? ? ? successThreshold: 1? ? ? ? ? failureThreshold: 5? ? ? ? env:
? ? ? ? - name: eureka-server
? ? ? ? ? value: "eureka-server.default.svc.cluster.local"- name: eureka-server-replica
? ? ? ? ? value: "eureka-server-replica.default.svc.cluster.local"? ? ? ? resources:
? ? ? ? # 5%的CPU時(shí)間和700MiB的內(nèi)存? ? ? ? ? requests:#? ? ? ? ? ? cpu: 50m? ? ? ? ? ? memory: 700Mi
? ? ? ? ? # 最多允許它使用? ? ? ? ? limits:#? ? ? ? ? ? cpu: 100m? ? ? ? ? ? memory: 1000Mi
? ? ? ? # 指定在容器中掛載路徑? ? ? ? volumeMounts:
? ? ? ? - name: logs-volume
? ? ? ? ? mountPath: /logs
? ? ? ? - name: host-time
? ? ? ? ? mountPath: /etc/localtime
? ? ? ? ? readOnly: true
? ? ? ? - name: host-timezone
? ? ? ? ? mountPath: /etc/timezone
? ? ? ? ? readOnly: true
? ? ? ? - name: pinpoint-config
? ? ? ? ? mountPath: /app/pinpoint-agent/pinpoint.config
? ? ? volumes:
? ? ? - name: logs-volume
? ? ? ? hostPath:
? ? ? ? # 宿主機(jī)上的目錄path: /logs
? ? ? - name: host-time
? ? ? ? hostPath:
? ? ? ? ? path: /etc/localtime
? ? ? - name: host-timezone
? ? ? ? hostPath:
? ? ? ? ? path: /usr/share/zoneinfo/Asia/Shanghai
? ? ? - name: pinpoint-config
? ? ? ? configMap:
? ? ? ? ? name: pinpoint-config
? ? ? # 運(yùn)行在指定標(biāo)簽的節(jié)點(diǎn),前提是先給節(jié)點(diǎn)打標(biāo)? kubectl label nodes 192.168.0.113 edgenode=flow#? ? ? nodeSelector:#? ? ? ? edgenode: flow---apiVersion: v1
kind: Service
metadata:
? name: {artifactId}
? namespace: default
? labels:
? ? app: {artifactId}
? ? version: {version}
spec:
? selector:
? ? app: {artifactId}
? ports:
? - name: tcp-{port}-{port}
? ? protocol: TCP
? ? port: {port}
? ? targetPort: {port}
里面的變量會在前面幾步自動替換掉们颜。
添加了prometheus收集jvm的內(nèi)容:
prometheus.io.jmx: "true"
prometheus.io.jmx.port: "1234"
containerPort: 1234
將pinpoint的配置內(nèi)容pinpoint.config用configMap? 保存吕朵,方便更改內(nèi)容猎醇。
其它內(nèi)容不在此詳解,可自行g(shù)oogle努溃。
網(wǎng)上資料一般發(fā)布服務(wù)都是直接kubectl deploy硫嘶,這種情況只適用于jenkins的服務(wù)器已包含在K8S服務(wù)器集群中。第二種情況是在K8S集群服務(wù)器里面生成Jenkins的一個(gè)slave節(jié)點(diǎn)梧税,然后在pipeline里面設(shè)置node(“k8s”){ ……} 里面發(fā)布沦疾,具體方法自行g(shù)oogle。
??????? 這里為了避免麻煩第队,采用直接SSH到K8S服務(wù)器集群的方案發(fā)布服務(wù)哮塞。
配置sshagent
SSH Agent Plugin :sshagent方法支持,用于上傳構(gòu)建產(chǎn)物到目標(biāo)服務(wù)器凳谦,使用詳情見:
https://wiki.jenkins.io/display/JENKINS/SSH+Agent+Plugin
在Jenkins插件庫搜索后直接下載安裝(需要連外網(wǎng)環(huán)境)忆畅,生產(chǎn)環(huán)境已安裝,直接使用尸执。
使用:
sshagent(credentials: ['deploy_ssh_key_23']) {
sh "scp -P 2222 -r Deployment.yaml root@39.95.40.97:/docker/yaml/Deployment-${artifactId}.yaml"
sh "ssh -p 2222 root@39.95.40.97 'kubectl apply -f /docker/yaml/Deployment-${artifactId}.yaml && kubectl set env deploy/${artifactId} DEPLOY_DATE=${env.BUILD_ID}'"
}
先用ssh遠(yuǎn)程到K8S集群中的服務(wù)器家凯,將Deployment文件上傳,然后再遠(yuǎn)程執(zhí)行kubectl apply發(fā)布服務(wù)如失。
??????? 為了避免誤操作绊诲,在發(fā)布前做了發(fā)布確認(rèn)提示判斷。
timeout(time: 10, unit: 'MINUTES') {
input '確認(rèn)要部署嗎褪贵?'
}
????? 以上流程已完成整個(gè)流程驯镊,然后可以去K8S環(huán)境去看服務(wù)是否有正常運(yùn)行。
關(guān)于測試
上面的過程沒有加入代碼測試竭鞍、代碼質(zhì)量分析SonarQube板惑、發(fā)布后服務(wù)測試的階段(Selenium是一套完整的Web應(yīng)用程序測試系統(tǒng)http://www.51testing.com/zhuanti/selenium.html),后續(xù)可以接入偎快。
如何進(jìn)行多模塊如何構(gòu)建
很多項(xiàng)目采用的是多模塊構(gòu)成冯乘,因此每個(gè)項(xiàng)目配置和發(fā)布要求不一樣,需要單獨(dú)編譯到部署晒夹,所以每個(gè)模塊都需要獨(dú)立的Dockerfile和Deployment文件裆馒,Jenkinsfile通用一份,然后在發(fā)布時(shí)自動彈出模塊列表丐怯,選擇需要發(fā)布的模塊進(jìn)行編譯發(fā)布喷好。
//需要處理的項(xiàng)目多項(xiàng)目時(shí)先進(jìn)入子項(xiàng)目
projectwk ="."mainpom = readMavenPom file:'pom.xml'//存在多個(gè)模塊時(shí),選擇其中一個(gè)進(jìn)行編譯if(mainpom.modules.size() > 0 ) {
? echo "項(xiàng)目擁有模塊==${mainpom.modules}"? timeout(time: 10, unit:'MINUTES') {
? ? defselproj = input message:'請選擇需要處理的項(xiàng)目', parameters: [choice(choices: mainpom.modules, description:'請選擇需要處理的項(xiàng)目', name:'selproj')] //, submitterParameter:'project'? ? projectwk = selproj
? ? echo "選擇項(xiàng)目=${projectwk}"? }
}
?讀取主項(xiàng)目的pom中的modules判斷是否包含多個(gè)模塊读跷,供用戶選擇梗搅。
然后根據(jù)選擇的模塊進(jìn)行編譯,dir進(jìn)入選擇的模塊讀取信息并編譯。
dir("${projectwk}") {
? ? pom = readMavenPom file:'pom.xml'? ? echo "group: ${pom.groupId}, artifactId: ${pom.artifactId}, version: ${pom.version} ,description: ${pom.description}"? ? artifactId ="${pom.artifactId}"? ? version ="${pom.version}"? ? description ="${pom.description}"}
完整的Jenkinsfile
properties([
? ? ? ? parameters([string(name: 'PORT', defaultValue:'7082', description:'程序運(yùn)行端口'),choice(name:'ACTIVE_TYPE', choices: ['dev','prd','local'], description:'程序打包環(huán)境'),choice(name:'ENV_TYPE', choices: ['online','offline'], description:'線上无切、還是線下環(huán)境'),booleanParam(name:'ON_PINPOINT', defaultValue: true, description:'是否添加Pinpoint監(jiān)控'),booleanParam(name:'ON_PROMETHEUS', defaultValue: true, description:'是否添加Prometheus監(jiān)控'),string(name:'EMAIL', defaultValue:'1041126478@qq.com', description:'打包結(jié)果通知')])
])
node {
? ? stage('Prepare') {
? ? ? ? echo "1.Prepare Stage"? ? ? ? MVNHOME ='/opt/maven354'//echo"UUID=${UUID.randomUUID().toString()}"? ? ? ? checkout scm
? ? ? ? //需要處理的項(xiàng)目多項(xiàng)目時(shí)先進(jìn)入子項(xiàng)目
? ? ? ? projectwk ="."? ? ? ? mainpom = readMavenPom file:'pom.xml'? ? ? ? repostory ="${mainpom.properties['docker.repostory']}"http://存在多個(gè)模塊時(shí)荡短,選擇其中一個(gè)進(jìn)行編譯
? ? ? ? if(mainpom.modules.size() > 0 ) {
? ? ? ? ? echo "項(xiàng)目擁有模塊==${mainpom.modules}"? ? ? ? ? timeout(time: 10, unit:'MINUTES') {
? ? ? ? ? ? defselproj = input message:'請選擇需要處理的項(xiàng)目', parameters: [choice(choices: mainpom.modules, description:'請選擇需要處理的項(xiàng)目', name:'selproj')] //, submitterParameter:'project'? ? ? ? ? ? projectwk = selproj
? ? ? ? ? ? echo "選擇項(xiàng)目=${projectwk}"? ? ? ? ? }
? ? ? ? }
? ? ? ? dir("${projectwk}") {
? ? ? ? ? ? pom = readMavenPom file:'pom.xml'? ? ? ? ? ? echo "group: ${pom.groupId}, artifactId: ${pom.artifactId}, version: ${pom.version} ,description: ${pom.description}"? ? ? ? ? ? artifactId ="${pom.artifactId}"? ? ? ? ? ? version ="${pom.version}"? ? ? ? ? ? description ="${pom.description}"? ? ? ? }
? ? ? ? script {
? ? ? ? ? ? GIT_TAG = sh(returnStdout: true, script:'/usr/local/git/bin/git rev-parse --short HEAD').trim()
? ? ? ? ? ? echo "GIT_TAG== ${GIT_TAG}"? ? ? ? }
? ? ? ? image ="192.168.4.2:5000/${artifactId}:${version}"if(params.ENV_TYPE =='offline'|| params.ENV_TYPE == null) {
? ? ? ? sh "sed -i 's#39.95.40.97:5000#10.3.80.50:5000#g' pom.xml"? ? ? ? image ="10.3.80.50:5000/${artifactId}:${version}"? ? ? }
? ? }
? ? if(mainpom.modules.size() > 0 ) {
? ? ? stage('編譯總項(xiàng)目') {
? ? ? ? ? sh "'${MVNHOME}/bin/mvn' -DskipTests clean install"? ? ? }
? ? }
? ? dir("${projectwk}") {
? ? ? ? stage('編譯模塊') {
? ? ? ? ? ? echo "2.編譯模塊 ${artifactId}"defjarparam=''defpinname = artifactId
? ? ? ? ? ? if( pinname.length() > 23) {
? ? ? ? ? ? ? pinname = artifactId.substring(0,23)
? ? ? ? ? ? }
? ? ? ? ? ? //添加pinpoint
? ? ? ? ? ? if(params.ON_PINPOINT) {
? ? ? ? ? ? ? jarparam ='"-javaagent:/app/pinpoint-agent/pinpoint-bootstrap-1.8.0.jar","-Dpinpoint.agentId={pinname}", "-Dpinpoint.applicationName={pinname}",'? ? ? ? ? ? }
? ? ? ? ? ? //添加prometheus
? ? ? ? ? ? if(params.ON_PROMETHEUS) {
? ? ? ? ? ? ? jarparam = jarparam +'"-javaagent:/app/prometheus/jmx_prometheus_javaagent-0.11.0.jar=1234:/app/prometheus/jmx.yaml",'? ? ? ? ? ? }
? ? ? ? ? ? sh "sed -i 's#{jarparam}#${jarparam}#g' Dockerfile"? ? ? ? ? ? sh "sed -i 's#{description}#${description}#g;s#{artifactId}#${artifactId}#g;s#{version}#${version}#g;s#{active}#${params.ACTIVE_TYPE}#g;s#{pinname}#${pinname}#g' Dockerfile"? ? ? ? ? ? sh "sed -i 's#{artifactId}#${artifactId}#g;s#{version}#${version}#g;s#{port}#${params.PORT}#g;s#{image}#${image}#g' Deployment.yaml"? ? ? ? ? ? sh "'${MVNHOME}/bin/mvn' -DskipTests clean package"? ? ? ? ? ? stash includes: 'target/*.jar', name:'app'? ? ? ? }
? ? ? ? stage('Docker打包') {
? ? ? ? ? ? echo "3.Docker打包"? ? ? ? ? ? unstash 'app'? ? ? ? ? ? sh "'${MVNHOME}/bin/mvn' docker:build"? ? ? ? }
? ? ? ? stage('推送鏡像') {
? ? ? ? ? ? echo "4.Push Docker Image Stage"? ? ? ? ? ? sh "'${MVNHOME}/bin/mvn' docker:push"? ? ? ? }
? ? ? ? timeout(time: 10, unit:'MINUTES') {
? ? ? ? ? ? input '確認(rèn)要部署嗎?'? ? ? ? }
? ? ? ? stage('發(fā)布') {
? ? ? ? if(params.ENV_TYPE =='offline'|| params.ENV_TYPE == null) {
? ? ? ? ? ? ? sshagent(credentials: ['deploy_ssh_key_34']) {
? ? ? ? ? ? ? ? sh "scp -r Deployment.yaml root@10.2.85.30:/docker/yaml/Deployment-${artifactId}.yaml"? ? ? ? ? ? ? ? sh "ssh root@10.2.85.30 'kubectl apply -f /docker/yaml/Deployment-${artifactId}.yaml && kubectl set env deploy/${artifactId} DEPLOY_DATE=${env.BUILD_ID}'"? ? ? ? ? ? ? }
? ? ? ? ? } else {
? ? ? ? ? ? ? sshagent(credentials: ['deploy_ssh_key_238']) {
? ? ? ? ? ? ? ? sh "scp -P 22 -r Deployment.yaml root@39.95.40.97:/docker/yaml/Deployment-${artifactId}.yaml"? ? ? ? ? ? ? ? sh "ssh -p 22 root@39.95.40.97 'kubectl apply -f /docker/yaml/Deployment-${artifactId}.yaml && kubectl set env deploy/${artifactId} DEPLOY_DATE=${env.BUILD_ID}'"? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? ? ? ? echo "發(fā)布完成"? ? ? ? }
? ? }
? stage('通知負(fù)責(zé)人'){//? ? emailext body:"構(gòu)建項(xiàng)目:${description}\r\n構(gòu)建完成", subject:'構(gòu)建結(jié)果通知【成功】', to:"${EMAIL}"? ? ? ? echo "構(gòu)建項(xiàng)目:${description}\r\n構(gòu)建完成"? }
}
將Jenkinsfile文件放在項(xiàng)目根目錄哆键,然后將源碼都上傳到GitLab掘托。
打開BlueOcean,這是Jenkins新出的美化頁面籍嘹。
選擇自己的項(xiàng)目闪盔。
進(jìn)入后點(diǎn)擊運(yùn)行,其中會彈出框選擇發(fā)布參數(shù)(這里需要手工填寫發(fā)布的端口辱士,由于采用配置中心化泪掀,端口無法自動讀取)识补。
進(jìn)入查看流程狀態(tài)族淮,失敗會有相應(yīng)的提示:
顯示發(fā)布服務(wù)
在K8S內(nèi)查看部署的服務(wù)啟動情況辫红。
Jenkinsfile Pipeline
Jenkinsfile Pipeline語法內(nèi)容可參考官網(wǎng):https://jenkins.io/doc/book/pipeline/jenkinsfile/
還可以進(jìn)入項(xiàng)目后凭涂,有個(gè)流水線語法:
選擇想要的功能,生成:
Jenkins還可用作發(fā)布Vue前端項(xiàng)目贴妻,具體內(nèi)容可參考?Jenkins自動化構(gòu)建vue項(xiàng)目然后發(fā)布到遠(yuǎn)程服務(wù)器?文檔切油。
Jenkins要發(fā)布Net服務(wù)需要有一臺windows的Jenkins slave,還需要在此節(jié)點(diǎn)上安裝編譯器MSBuild框架名惩,Git框架澎胡、更改服務(wù)器上的IIS權(quán)限等功能,最后文件分發(fā)到其它windows服務(wù)器娩鹉,過程比較繁瑣攻谁,若無發(fā)布審核建議直接通過VS自帶發(fā)布功能發(fā)布程序。