使用Jenkins更新Openshift上的應用

前言

本篇介紹了一種利用Jenkins Pipeline指令碼更新Openshift容器雲平臺上已部署應用的方式,主要涉及如下知識:

如何利用Openshift在Jenkins的外掛更新部署並獲取狀態

如何編寫Jenkins Pipeline指令碼

如何為Pipeline指令碼建立動態引數、動態步驟

如何讓Pipeline步驟並行執行

如何從容器映象庫獲取映象Tag列表

以下為執行效果示意,如感興趣可繼續閱讀。

使用Jenkins更新Openshift上的應用

執行效果

說明

本例中使用的Jenkins運行於Openshift平臺上,可能是Openshift外掛可以正常使用的條件。

本例中指令碼對映象Tag個格式有具體要求,詳見下文。

如何更新

瞭解K8S和Openshift的同學應該知道,我們把StatefulSet、Deployment、DeploymentConfig裡容器映象替換掉,就會觸發部署。

在以下Groovy方法中,呼叫了Openshift在Jenkins的外掛的selector方法,找到部署物件,然後修改目標容器的映象,從而觸發更新。

def patchImage(appConfig) { def p = openshift。selector(“${appConfig。deploymentType}/${appConfig。deploymentName}”)。object() def originalImage = p。spec。template。spec。containers[0]。image def targetImage = “image-registry。openshift-image-registry。svc:5000/${openshift。project()}/${appConfig。imageStreamName}:${appConfig。imageTag}” p。spec。template。spec。containers[0]。image = targetImage openshift。apply(p) if (targetImage != originalImage) { echo “${appConfig。deploymentType}/${appConfig。deploymentName} 映象已由 ${originalImage} 更新為: ${openshift。project()}/${appConfig。imageStreamName}:${appConfig。imageTag},將自動觸發Rollout” } else { echo “${appConfig。deploymentType}/${appConfig。deploymentName} 映象未改變: ${originalImage},將強制rollout。” openshift。selector(“${appConfig。deploymentType}/${appConfig。deploymentName}”)。rollout()。latest() }}

如何判斷更新狀態

Deployment在Rollout的過程中,我們可以透過openshift外掛獲取拿到最新的部署版本,並判斷其是否更新完成。

由於DeploymentConfig(簡寫為dc)和StatefulSet(簡寫為sts)有些不同,所以,我們分開進行判斷。

對於dc,主要透過比較desiredReplicas(期望副本數)和readyReplicas(就緒副本數)來判斷是否完成了更新。

if (appConfig。deploymentType == “dc”) { def latestDeploymentVersion = openshift。selector(appConfig。deploymentType, appConfig。deploymentName)。object()。status。latestVersion def rc = openshift。selector(‘rc’, “${appConfig。deploymentName}-${latestDeploymentVersion}”) timeout (time: 30, unit: ‘MINUTES’) { rc。untilEach(1){ def rcMap = it。object() def desiredReplicas = rcMap。metadata。annotations[‘kubectl。kubernetes。io/desired-replicas’] if (desiredReplicas == null) { desiredReplicas = rcMap。status。replicas } def readyReplicas = rcMap。status。readyReplicas == null ? 0 : rcMap。status。readyReplicas echo “desired replicas: ${desiredReplicas}, readyReplicas: ${readyReplicas}” if (waitEachCopy) { return desiredReplicas != null && (desiredReplicas。equals(readyReplicas)) } else { return desiredReplicas != null && readyReplicas >=1 } } } }

對於sts,方式是判斷每個容器均Ready。

if (appConfig。deploymentType == “sts”) { def latestStsVersion = openshift。selector(appConfig。deploymentType, appConfig。deploymentName)。object()。status。updateRevision def stsPods = openshift。selector(‘pods’, [ ‘controller-revision-hash’: latestStsVersion ]) timeout (time: 30, unit: ‘MINUTES’) { stsPods。untilEach(1){ if (waitEachCopy) { return it。object()。status。containerStatuses。every { echo “pod container ready: ${it。ready}” it。ready } } else { return it。object()。status。containerStatuses。any { echo “pod container ready: ${it。ready}” it。ready } } } } }

如何將映象匯入內部映象庫

在更新部署物件的映象之前,我們可以先把容器映象由外部映象庫匯入到Openshift內部映象庫,可以多留存一份映象,同時利於工作節點快速獲取映象(未驗證)。

這裡用到了openshit外掛的raw方法,可以執行原生oc命令,所以匯入映象實際使用的命令為oc image mirror。不知什麼原因,這裡經常會報Unknown Blob錯誤,多試幾次就好了。

在同步映象之前,需要登入源和目標映象倉庫,使用的也是oc命令registry login。

Closure createImportImageStep(appConfig) { return { echo “當前叢集:${openshift。cluster()} 當前專案: ${openshift。project()}” echo “登入內部映象庫。。。” openshift。raw(“registry login ——registry=${internalRegistryAddress} ——insecure=true ——skip-check=true”) echo “登入外部映象庫。。。” openshift。raw(“registry login ——registry ${externalRegistryAddress} ——auth-basic=YourUsername:YourPassword ——insecure=true”) echo “匯入映象到內部映象庫。。。” if (!appConfig。imageTag) { echo “${appConfig。app} 因無目標映象跳過映象匯入” return } retry(5) { importDockerImage(appConfig) } }}def importDockerImage(appConfig) { echo “開始匯入映象:istag/${appConfig。imageStreamName}:${appConfig。imageTag}” def p = openshift。selector(“istag/${appConfig。imageStreamName}:${appConfig。imageTag}”) if (p。exists() && importImageForce) { p。delete() echo “imagestreamtag/${appConfig。imageStreamName}:${appConfig。imageTag} deleted。” } if (!p。exists() || importImageForce) { openshift。raw(“image mirror ${externalRegistryAddress}/${appConfig。imagePath}/${appConfig。imageName}:${appConfig。imageTag} ${internalRegistryAddress}/${openshift。project()}/${appConfig。imageStreamName}:${appConfig。imageTag} ——insecure=true”) } echo “完成匯入映象:istag/${appConfig。imageStreamName}:${appConfig。imageTag}”}

如何列舉映象Tag

為了方便運維同事透過介面選擇要更新的應用和Tag,需要Pipeline指令碼從外部Docker映象庫讀取各應用映象的Tag。

這裡指令碼的核心點是呼叫了映象庫的HTTP API。

//訪問映象庫api獲取映象Tagdef getDockerTags(appConfig) { def url = “http://${externalRegistryAddress}/v2/${appConfig。imagePath}/${appConfig。imageName}/tags/list” def list = getDockerImageTags(url) list}// 分為3步,獲得json文字,轉換為json物件,從物件獲得所有Tagdef getDockerImageTags(url) { def myjson = getUrl(url) def json = jsonParse(myjson); def tags = json。tags tags}//透過curl訪問映象庫API,將結果儲存為result。json檔案,然後返回檔案內容def getUrl(url) { sh(returnStdout: true, script: “curl -s ——max-time 15 ——retry 3 ——retry-delay 5 ${url} 2>&1 | tee result。json”) def data = readFile(‘result。json’)。trim() data}// 將映象Tag倒序排列後,取前5個def getLatestTag(appConfig) { def tags = getDockerTags(appConfig) if (tags == null || tags。size() == 0) { return null } def sortedTags = sortReverse(tags) def topTags = sortedTags。size() > 5 ? new ArrayList(sortedTags。subList(0,4)) : sortedTags topTags}// 對映象Tag按倒序排列,要求映象Tag遵循一定格式,如2。1。0-20211105@NonCPSdef sortReverse(list) { list。sort{a,b -> def ay = a。split(“[。-]”) def by = b。split(“[。-]”) for (int i = 0; i < ay。length; i++) { def ai = ay[i]。isInteger() ? ay[i]。toInteger() : ay[i]; def bi = by[i]。isInteger() ? by[i]。toInteger() : by[i]; if (bi。compareTo(ai) == 0) { continue } return bi。compareTo(ai) } }}// 將json文字轉換為json物件@NonCPSdef jsonParse(json) { new groovy。json。JsonSlurperClassic()。parseText(json)}

如何生成選型引數

為Jenkins Pipeline建立選項引數,方法是建立一個ChoiceParameterDefinition的陣列就可以了。

//生成映象Tag選擇器def getTagChoiceParamter() { def tags = [] tagSet。each { entry -> tags。add(entry。key) } def sortedTags = sortReverse(tags) def parameters = [] def para = new ChoiceParameterDefinition(“latestTag”, sortedTags, sortedTags[0], “最新版本”) parameters。add para parameters}//這個是映象選擇的stage 片段 stage(‘選擇版本’) { def tagSelectorInputTimeout = false try { timeout(time: 180, unit: ‘SECONDS’) { tagSelectorInput = input( id: ‘tagInput’, ok: ‘繼續’, message: ‘請選擇要更新到的映象Tag:’, parameters: tagParameters ) } } catch(err) { def user = err。getCauses()[0]。getUser() if(‘SYSTEM’ == user。toString()) { tagSelectorInputTimeout = true } else { tagSelectorInput = null echo “映象Tag選擇被使用者中止: [${user}]” } } }

驗證身份

能夠訪問Pipeline,說明已經當前使用者已經登入Jenkins了。為了多一道安全控制,需要使用者在Openshift介面或透過命令oc whoami -t獲取一個Token,在正式執行Pipeline部署步驟之前驗證一下身份。

這裡主要使用了Openshift外掛的withCluster、withCredentials和raw三個方法,透過執行oc status -v命令驗證其身份。

stage(‘登入Openshift’) { timeout(time: 180, unit: ‘SECONDS’) { myToken = input( id: ‘myTokenInput’, ok: ‘繼續’, message: ‘請輸入訪問Openshift的Token’, parameters: [password(defaultValue: ‘’, description: ‘OCP訪問令牌,可以在登入openshift後,透過執行 oc whoami -t 獲得’, name: ‘Token for Openshift’)] ) } openshift。withCluster() { openshift。withCredentials(myToken) { def status = openshift。raw(‘status’, ‘-v’) echo “登入成功。 叢集是:${openshift。cluster()} 預設專案: ${openshift。project()} 流水線版本:${pipelineVersion}” } } }

應用元資料

應用的列表和應用之間的依賴關係,以及各應用部署物件的型別和名稱、映象庫路徑等等,透過在指令碼定義欄位實現。

@Field def final myConfigJson = ‘’‘[ { “app”: “activity”, //應用 “imagePath”: “xxx-group/release/yyy”, //應用映象在映象庫的路徑 “imageName”: “activity”, //應用映象的名稱 “imageStreamName”: “activity”, //應用映象在內部映象庫的名稱 “deploymentType”: “dc”, //應用的部署型別,dc即DeploymentConfig “deploymentName”: “activity”, //應用的Deployment或StatefulSet的名稱 “selected”: false, //是否預設選中,如果經常更新該應用,可預設選中 “dependencies”: [ ], //依賴的其它應用,可以控制更新順序 “status”: “new”, //更新狀態控制,這裡都是new,其它由指令碼控制 “imageTag”: “”, //目標映象Tag,在執行中賦值 “group”: [“積分活動”] //應用組,方便一次性選擇多個應用,當前指令碼以無此功能 }, { “app”: “point”, “imagePath”: “xxx-group/release/yyy”, “imageName”: “point”, “imageStreamName”: “point”, “deploymentType”: “dc”, “deploymentName”: “point”, “selected”: false, “dependencies”: [ ], “status”: “new”, “imageTag”: “”, “group”: [“積分活動”] }]’‘’

完整指令碼

完整的Pipeline指令碼如下,為方便理解,加了一些註釋。為方便選擇應用,選擇應用的方式幾經修改,部分方法可能已經不再使用了。使用該指令碼在Jenkins建立Pipeline型別的Job即可,不要選擇“Use Groovy Sandbox”。可能因安全原因,指令碼變更後,每次都需要Jenkins管理員授權。

import groovy。transform。Field@Field def final pipelineVersion = “20210813” //此Pipeline的版本,用於發生問題時調查依據@Field def final envType = “” // 環境型別 “。test” 為測試環境,“”為生產環境,方便在測試環境和生產環境同時使用@Field def final externalRegistryAddress = “docker-app。nexus${envType}。ccc” //外部映象庫域名@Field def final internalRegistryAddress = “default-route-openshift-image-registry。apps。ocp${envType}。ccc” //內部映象庫域名@Field def ocp_project = ‘yyy-prod’ //針對哪個Openshift Project//應用元資料@Field def final myConfigJson = ‘’‘[ { “app”: “activity”, //應用 “imagePath”: “xxx-group/release/yyy”, //應用映象在映象庫的路徑 “imageName”: “activity”, //應用映象的名稱 “imageStreamName”: “activity”, //應用映象在內部映象庫的名稱 “deploymentType”: “dc”, //應用的部署型別,dc即DeploymentConfig “deploymentName”: “activity”, //應用的Deployment或StatefulSet的名稱 “selected”: false, //是否預設選中,如果經常更新該應用,可預設選中 “dependencies”: [ ], //依賴的其它應用,可以控制更新順序 “status”: “new”, //更新狀態控制,這裡都是new,其它由指令碼控制 “imageTag”: “”, //目標映象Tag,在執行中賦值 “group”: [“積分活動”] //應用組,方便一次性選擇多個應用,當前指令碼以無此功能 }, { “app”: “point”, “imagePath”: “xxx-group/release/yyy”, “imageName”: “point”, “imageStreamName”: “point”, “deploymentType”: “dc”, “deploymentName”: “point”, “selected”: false, “dependencies”: [ ], “status”: “new”, “imageTag”: “”, “group”: [“積分活動”] }]’‘’@Field def myConfig;//Openshift Token,二次驗證身份用@Field def myToken@Field def tagSet = [:] //映象Tag的Map,key為映象Tag,value為應用列表@Field def rolloutGroupSelector //應用組選擇器,此版本已廢棄@Field def rolloutAppSelector //應用選擇器@Field def tagParameters //映象Tag引數//執行選項@Field def waitEachCopy = true //等待每個副本更新完成@Field def waitDepen = true //等待依賴應用更新完成@Field def importImageForce = false //當映象已存在於內部映象庫時,強制匯入@Field def stepsForImportImages = [:] //匯入映象的Pipeline步驟,用於並行執行@Field def stepsForUpdateApp = [:] //更新應用的Pipeline步驟,用於並行執行node { stage(‘登入Openshift’) { timeout(time: 180, unit: ‘SECONDS’) { myToken = input( id: ‘myTokenInput’, ok: ‘繼續’, message: ‘請輸入訪問Openshift的Token’, parameters: [password(defaultValue: ‘’, description: ‘OCP訪問令牌,可以在登入openshift後,透過執行 oc whoami -t 獲得’, name: ‘Token for Openshift’)] ) } openshift。withCluster() { openshift。withCredentials(myToken) { def status = openshift。raw(‘status’, ‘-v’) echo “登入成功。 叢集是:${openshift。cluster()} 預設專案: ${openshift。project()} 流水線版本:${pipelineVersion}” } } myConfig = jsonParse(myConfigJson) getTagSet(myConfig) tagParameters = getTagChoiceParamter() } stage(‘選擇版本’) { def tagSelectorInputTimeout = false try { timeout(time: 180, unit: ‘SECONDS’) { tagSelectorInput = input( id: ‘tagInput’, ok: ‘繼續’, message: ‘請選擇要更新到的映象Tag:’, parameters: tagParameters ) } } catch(err) { def user = err。getCauses()[0]。getUser() if(‘SYSTEM’ == user。toString()) { tagSelectorInputTimeout = true } else { tagSelectorInput = null echo “映象Tag選擇被使用者中止: [${user}]” } } if (tagSelectorInputTimeout) { currentBuild。result = ‘FAILURE’ error “選擇映象Tag超時” } else if (tagSelectorInput == null) { currentBuild。result = ‘FAILURE’ error “選擇映象錯誤,可能被使用者中止” } else { echo “成功選擇映象標籤” } } stage(‘選擇應用’) { def selectedTag = tagSelectorInput def rolloutAppSelectorTimeout = false try { def paras = [] def appSelectedFollowTag = tagSet。get(selectedTag) for (i=0; i < appSelectedFollowTag。size(); i++) { paras。add booleanParam(defaultValue: true, description: ‘’, name: appSelectedFollowTag[i]。app) } timeout(time: 180, unit: ‘SECONDS’) { rolloutAppSelector = input(id: ‘rolloutAppSelectorInput’, ok: ‘繼續’, message: ‘請選擇要更新的應用’, parameters: paras) } } catch(err) { def user = err。getCauses()[0]。getUser() if(‘SYSTEM’ == user。toString()) { rolloutAppSelectorTimeout = true } else { rolloutAppSelector = null echo “Aborted by: [${user}]” } } if (rolloutAppSelectorTimeout) { currentBuild。result = ‘FAILURE’ error “選擇待更新應用超時。” } else if (rolloutAppSelector == null) { currentBuild。result = ‘FAILURE’ error “選擇待更新應用可能被使用者中止了。” } else { echo “選擇待更新應用成功。” rolloutAppSelector。findAll{key, value -> value == true}。each { entry -> echo “應用 ${entry} 已選擇。” } def apps = getSelectedApp() for (int i = 0; i < apps。size(); i++) { def appConfig = apps[i] appConfig。imageTag = selectedTag } } } stage (‘設定選項’) { def optionsSelectorInputTimeout = false try { timeout(time: 180, unit: ‘SECONDS’) { runOptions = input(id: ‘runOptionsInput’, ok: ‘繼續’, message: ‘請選擇更新選項’, parameters: [ booleanParam(defaultValue: true, description: ‘等待應用的每個副本都更新完成?’, name: “waitEachCopy”), booleanParam(defaultValue: true, description: ‘等待被依賴應用更新完成?’, name: “waitDepen”), booleanParam(defaultValue: false, description: ‘當映象已存在時,強制重新匯入?’, name: “importImageForce”) ]) } } catch(err) { def user = err。getCauses()[0]。getUser() if(‘SYSTEM’ == user。toString()) { optionsSelectorInputTimeout = true } else { runOptions = null echo “選擇更新選項被使用者中止: [${user}]” } } if (optionsSelectorInputTimeout) { currentBuild。result = ‘FAILURE’ error “選擇更新選項超時” } else if (runOptions == null) { currentBuild。result = ‘FAILURE’ error “選擇更新選項錯誤,可能被使用者中止” } else { waitEachCopy = runOptions。waitEachCopy。value waitDepen = runOptions。waitDepen。value importImageForce = runOptions。importImageForce。value echo “成功選擇更新選項” } } stage (‘確認’) { def apps = “”; sortAppSelectorByDependency()。each { appConfig -> wait = waitFinished(appConfig) ? “-等待” : “” apps += “${appConfig。deploymentType}/${appConfig。deploymentName}:${appConfig。imageTag}${wait}\n” } def waitType = “” if (waitDepen) { waitType += “\n在更新前,等待被依賴的應用都更新完成。” } else { waitType += “\n直接更新,不等待被依賴的應用更新完成。” } if (waitEachCopy) { waitType += “\n等待所有副本都更新完成” } else { waitType += “\n只要有一個副本更新完成即可” } def message = “”“將更新${ocp_project}的如下應用:${apps}等待方式:${waitType}目標映象、更新順序及等待方式如上。強制重新整理映象: ${importImageForce}請確認以上內容,並選擇繼續或中止?”“” input (message: “將更新${ocp_project}的如下應用”, ok: ‘確認並繼續’, parameters: [ string(name: ‘confirmAppsToUpdate’, description: message, defaultValue: “——-不用輸入——-”)] ) } stage (‘匯入映象’) { openshift。withCluster() { openshift。withCredentials(myToken) { openshift。withProject(ocp_project) { rolloutAppSelector。findAll{key, value -> value == true}。each { key, value -> def appConfig = myConfig。find{item -> item。app == key} stepsForImportImages[“匯入映象:${appConfig。app}”] = createImportImageStep(appConfig) } stepsForImportImages[“failFast”] = true parallel stepsForImportImages } } } } stage (‘更新應用’) { openshift。withCluster() { openshift。withCredentials(myToken) { openshift。withProject(ocp_project) { sortAppSelectorByDependency()。each { appConfig -> stepsForUpdateApp[“更新應用:${appConfig。app}”] = createUpdateAppStep(appConfig) } parallel stepsForUpdateApp } } } }}def getAppGroups() { def groups = [:] groups。put(“全部”, new HashSet()) for (i=0; i < myConfig。size(); i++) { def curApp = myConfig[i] groups[“全部”]。add(curApp。app) def group = curApp。group if (group) { group。each { if (!groups。containsKey(it)) { groups。put(it, new HashSet()) } groups[it]。add(curApp。app) } } } groups}def getSelectedAppFollowGroup() { def selectedApps = new HashSet() rolloutGroupSelector。findAll{key, value -> value == true }。each { key, value -> configs = myConfig。findAll{config -> “全部”。equals(key) || (config。group && config。group。contains(key))} if (configs) { configs。each { config -> selectedApps。add(config。app) } } } selectedApps}Closure createImportImageStep(appConfig) { return { echo “當前叢集:${openshift。cluster()} 當前專案: ${openshift。project()}” echo “登入內部映象庫。。。” openshift。raw(“registry login ——registry=${internalRegistryAddress} ——insecure=true ——skip-check=true”) echo “登入外部映象庫。。。” openshift。raw(“registry login ——registry ${externalRegistryAddress} ——auth-basic=YourUsername:YourPassword ——insecure=true”) echo “匯入映象到內部映象庫。。。” if (!appConfig。imageTag) { echo “${appConfig。app} 因無目標映象跳過映象匯入” return } retry(5) { importDockerImage(appConfig) } }}Closure createUpdateAppStep(appConfig) { return { if (!appConfig。imageTag) { echo “${appConfig。app} 因無目標映象跳過更新” appConfig。status = “skiped” return } if (waitFinished(appConfig)) { def selectedApps = getSelectedApp() timeout (time: 60, unit: ‘MINUTES’) { waitUntil(initialRecurrencePeriod: 30000, quiet: false) { appConfig。dependencies。every {depAppName -> def depApp = selectedApps。find{item -> item。app。equals(depAppName)} def depFinished = depApp == null || depApp。status。equals(“updated”) || depApp。status。equals(“skiped”) if (depApp) { echo “${appConfig。app}依賴的${depApp。app}當前狀態是:${depApp。status}” } depFinished } } } } patchImage(appConfig) if (appConfig。deploymentType == “dc”) { def latestDeploymentVersion = openshift。selector(appConfig。deploymentType, appConfig。deploymentName)。object()。status。latestVersion def rc = openshift。selector(‘rc’, “${appConfig。deploymentName}-${latestDeploymentVersion}”) timeout (time: 30, unit: ‘MINUTES’) { rc。untilEach(1){ def rcMap = it。object() def desiredReplicas = rcMap。metadata。annotations[‘kubectl。kubernetes。io/desired-replicas’] if (desiredReplicas == null) { desiredReplicas = rcMap。status。replicas } def readyReplicas = rcMap。status。readyReplicas == null ? 0 : rcMap。status。readyReplicas echo “desired replicas: ${desiredReplicas}, readyReplicas: ${readyReplicas}” if (waitEachCopy) { return desiredReplicas != null && (desiredReplicas。equals(readyReplicas)) } else { return desiredReplicas != null && readyReplicas >=1 } } } } else if (appConfig。deploymentType == “sts”) { def latestStsVersion = openshift。selector(appConfig。deploymentType, appConfig。deploymentName)。object()。status。updateRevision def stsPods = openshift。selector(‘pods’, [ ‘controller-revision-hash’: latestStsVersion ]) timeout (time: 30, unit: ‘MINUTES’) { stsPods。untilEach(1){ if (waitEachCopy) { return it。object()。status。containerStatuses。every { echo “pod container ready: ${it。ready}” it。ready } } else { return it。object()。status。containerStatuses。any { echo “pod container ready: ${it。ready}” it。ready } } } } } appConfig。status = “updated” echo “${appConfig。deploymentType}/${appConfig。deploymentName} rollout success。” }}def waitFinished(app) { def hasDependency = app。dependencies != null && app。dependencies。size() > 0 waitDepen && hasDependency}def sortAppSelectorByDependency() { def apps = getSelectedApp() sortByDependency(apps)}def getSelectedApp() { def selectedApps = [] rolloutAppSelector。findAll{key, value -> value == true }。each { key, value -> config = myConfig。find{item -> item。app == key} if (config != null) { selectedApps。add(config) } } selectedApps}@NonCPSdef sortByDependency(list) { list。sort {a,b -> b。dependencies。contains(a。app) ? -1 : a。dependencies。contains(b。app) ? 1 : 0 }}def patchImage(appConfig) { def p = openshift。selector(“${appConfig。deploymentType}/${appConfig。deploymentName}”)。object() def originalImage = p。spec。template。spec。containers[0]。image def targetImage = “image-registry。openshift-image-registry。svc:5000/${openshift。project()}/${appConfig。imageStreamName}:${appConfig。imageTag}” p。spec。template。spec。containers[0]。image = targetImage openshift。apply(p) if (targetImage != originalImage) { echo “${appConfig。deploymentType}/${appConfig。deploymentName} 映象已由 ${originalImage} 更新為: ${openshift。project()}/${appConfig。imageStreamName}:${appConfig。imageTag},將自動觸發Rollout” } else { echo “${appConfig。deploymentType}/${appConfig。deploymentName} 映象未改變: ${originalImage},將強制rollout。” openshift。selector(“${appConfig。deploymentType}/${appConfig。deploymentName}”)。rollout()。latest() }}def importDockerImage(appConfig) { echo “開始匯入映象:istag/${appConfig。imageStreamName}:${appConfig。imageTag}” def p = openshift。selector(“istag/${appConfig。imageStreamName}:${appConfig。imageTag}”) if (p。exists() && importImageForce) { p。delete() echo “imagestreamtag/${appConfig。imageStreamName}:${appConfig。imageTag} deleted。” } if (!p。exists() || importImageForce) { openshift。raw(“image mirror ${externalRegistryAddress}/${appConfig。imagePath}/${appConfig。imageName}:${appConfig。imageTag} ${internalRegistryAddress}/${openshift。project()}/${appConfig。imageStreamName}:${appConfig。imageTag} ——insecure=true”) } echo “完成匯入映象:istag/${appConfig。imageStreamName}:${appConfig。imageTag}”}def mergeTagList(source, target) { def result = [] if (source != null) { result。addAll(source) } if (target != null) { if (result。size() == 0) { result。addAll(target) } else { def tmp = [] for (i = 0; i < target。size(); i++) { if (result。contains(target[i])) { tmp。add(target[i]) } } result = tmp } } result}def getTagParameters() { def parameters = [] def apps = getSelectedApp() for (i=0; i < apps。size(); i++) { def para = getAppTagChoiceParamter(apps[i]) if (para != null) { parameters。add para } } parameters}def getAppTagChoiceParamter(appConfig) { def topTags = getLatestTag(appConfig) if (topTags == null || topTags。size() == 0) { return null } def choiceParamter = new ChoiceParameterDefinition(appConfig。app, topTags, topTags[0], “${appConfig。app}”); choiceParamter}def getTagChoiceParamter() { def tags = [] tagSet。each { entry -> tags。add(entry。key) } def sortedTags = sortReverse(tags) def parameters = [] def para = new ChoiceParameterDefinition(“latestTag”, sortedTags, sortedTags[0], “最新版本”) parameters。add para parameters}def getTagSet(configs) { for (i=0; i < configs。size(); i++) { def curApp = configs[i] def latestTags = getLatestTag(curApp) if (latestTags == null || latestTags。size() ==0) { continue } for (int j=0; j < latestTags。size(); j++) { def tag = latestTags[j] if (!tagSet。containsKey(tag)) { tagSet。put(tag, new HashSet()) } tagSet[tag]。add(curApp) } }}def getLatestTag(appConfig) { def tags = getDockerTags(appConfig) if (tags == null || tags。size() == 0) { return null } def sortedTags = sortReverse(tags) def topTags = sortedTags。size() > 5 ? new ArrayList(sortedTags。subList(0,4)) : sortedTags topTags}def getDockerTags(appConfig) { def url = “http://${externalRegistryAddress}/v2/${appConfig。imagePath}/${appConfig。imageName}/tags/list” def list = getDockerImageTags(url) list}@NonCPSdef sortReverse(list) { list。sort{a,b -> def ay = a。split(“[。-]”) def by = b。split(“[。-]”) for (int i = 0; i < ay。length; i++) { def ai = ay[i]。isInteger() ? ay[i]。toInteger() : ay[i]; def bi = by[i]。isInteger() ? by[i]。toInteger() : by[i]; if (bi。compareTo(ai) == 0) { continue } return bi。compareTo(ai) } }}def getDockerImageTags(url) { def myjson = getUrl(url) def json = jsonParse(myjson); def tags = json。tags tags}@NonCPSdef jsonParse(json) { new groovy。json。JsonSlurperClassic()。parseText(json)}def getUrl(url) { sh(returnStdout: true, script: “curl -s ——max-time 15 ——retry 3 ——retry-delay 5 ${url} 2>&1 | tee result。json”) def data = readFile(‘result。json’)。trim() data}

結語

本例這種方式實際為遷就部分運維同事,因部分運維同事習慣於使用WebLogic Admin Portal在介面中透過勾選操作來更新應用,故透過Jenkins Pipeline來儘量模擬這一過程。個人認為這不是一個正常的路子,還是應該透過指令碼來操作,並以自動化的方式執行。