首頁>技術>

現在這家單位的 CICD 比較的混亂,然後突發奇想,想改造下,於是就用pipeline做了一個簡單的流水線,下面是關於它的一些介紹

寫一個簡單的流水線

大概就是這麼個流程簡單來說就是:拉程式碼---》編譯---》打映象---》推映象---》部署到 k8s 中,下面的 pipeline 就是在這條主線上進行,根據情況進行增加

pipeline {	agent { label 'pdc&&jdk8' }	environment {		git_addr = "程式碼倉庫地址"		git_auth = "拉程式碼時的認證ID"		pom_dir = "pom檔案的目錄位置(相對路徑)"		server_name = "服務名"		namespace_name = "服務所在的名稱空間"		img_domain = "映象地址"		img_addr = "${img_domain}/cloudt-safe/${server_name}"// 		cluster_name = "叢集名"	}	stages {		stage('Clear dir') {			steps {				deleteDir()			}		}		stage('Pull server code and ops code') {			parallel {				stage('Pull server code') {					steps {						script {							checkout(								[									$class: 'GitSCM',									branches: [[name: '${Branch}']],									userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_addr}"]]								]							)						}					}				}				stage('Pull ops code') {					steps {						script {							checkout(								[									$class: 'GitSCM',									branches: [[name: 'pipeline-0.0.1']], //拉取的構建指令碼的分支									doGenerateSubmoduleConfigurations: false,									extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'DEPLOYJAVA']], //DEPLOYJAVA: 把程式碼存放到此目錄中									userRemoteConfigs: [[credentialsId: 'chenf-o', url: '構建指令碼的倉庫地址']]								]							)						}					}				}			}		}		stage('Set Env') {			steps {				script {					date_time = sh(script: "date +%Y%m%d%H%M", returnStdout: true).trim()					git_cm_id = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()					whole_img_addr = "${img_addr}:${date_time}_${git_cm_id}"				}			}		}		stage('Complie Code') {			steps {				script {					withMaven(maven: 'maven_latest_linux') {					    sh "mvn -U package -am -amd -P${env_name} -pl ${pom_dir}"					}				}			}		}		stage('Build image') {			steps {				script {					dir("${env.WORKSPACE}/${pom_dir}") {						sh """							echo 'FROM 基礎映象地址' > Dockerfile  //由於我這裡進行了映象的最佳化,只指定一個基礎映象地址即可,後面會詳細的說明						"""						withCredentials([usernamePassword(credentialsId: 'faabc5e8-9587-4679-8c7e-54713ab5cd51', passwordVariable: 'img_pwd', usernameVariable: 'img_user')]) {							sh """								docker login -u ${img_user} -p ${img_pwd} ${img_domain}								docker build -t ${img_addr}:${date_time}_${git_cm_id} .								docker push ${whole_img_addr}							"""						}					}				}			}		}		stage('Deploy img to K8S') {			steps {				script {					dir('DEPLOYJAVA/deploy') {					    //執行構建指令碼						sh """							/usr/local/python3/bin/python3 deploy.py -n ${server_name} -s ${namespace_name} -i ${whole_img_addr} -c ${cluster_name}						"""					}				}			}			// 做了下判斷如果上面指令碼執行失敗,會把上面階段打的映象刪除掉			post {				failure {					sh "docker rmi -f ${whole_img_addr}"				}			}		}		stage('Clear somethings') {			steps {				script {				    // 刪除打的映象					sh "docker rmi -f ${whole_img_addr}"				}			}			post {				success {				    // 如果上面階段執行成功,將把當前目錄刪掉					deleteDir()				}			}		}	}}複製程式碼
最佳化構建映象

上面的 pipeline 中有一條命令是生成Dockerfile的,在這裡做了很多最佳化,雖然我的Dockerfile就寫了一個FROM,但是在這之後又會執行一系列的操作,下面我們對比下沒有做最佳化的Dockerfile 未最佳化

FROM 基礎映象地址RUN mkdir xxxxxCOPY *.jar /usr/app/app.jarENTRYPOINT java -jar app.jar複製程式碼

最佳化後的

FROM 基礎映象地址複製程式碼

最佳化後的Dockerfile就這一行就完了。。。。。 下面簡單介紹下這個ONBUILDONBUILD 可以這樣理解,就比如我們這裡使用的映象,是基於 java 語言做的一個映象,這個映象有兩部分,一個是包含 JDK 的基礎映象 A,另一個是包含 jar 包的映象 B,關係是先有 A 再有 B,也就是說 B 依賴於 A。假設一個完整的基於 Java 的 CICD 場景,我們需要拉程式碼,編譯,打映象,推映象,更新 pod 這一系列的步驟,而在打映象這個過程中,我們需要把編譯後的產物 jar 包 COPY 到基礎映象中,這就造成了,我們還得寫一個 Dockerfile,用來 COPY jar 包,就像下面這個樣子:

FROM jdk基礎映象COPY xxx.jar /usr/bin/app.jarENTRYPOINT java -jar app.jar複製程式碼

這樣看起來也還好,基本上三行就解決了,但是能用一行就解決為什麼要用三行呢?

FROM jdk基礎映象ONBUILD COPY target/*.jar /usr/bin/app.jarCMD ["/start.sh"]複製程式碼

打成一個映象,比如映象名是:java-service:jdk1.8,在打映象的時候,ONBUILD後面的在本地打映象的過程中不會執行,而是在下次引用時執行的

FROM java-service:jdk1.8複製程式碼

只需要這一行就可以了,並且這樣看起來更加簡潔,pipeline看起來也很規範,這樣的話,我們每一個 java 的服務都可以使用這一行 Dockerfile 了。

使用憑據

有時候使用 docker 進行 push 映象時需要進行認證,如果我們直接在 pipeline 裡寫的話不太安全,所以得進行脫敏,這樣的話我們就需要用到憑據了,新增憑據也是非常簡單,由於我們只是儲存我們的使用者名稱和密碼,所以用Username with password型別的憑據就可以了,如下所示

比如說:拉取 git 倉庫的程式碼需要用到,然後這裡就新增一個憑據,對應與 pipeline 裡的下面這段內容:

stage('Pull server code') {	steps {		script {			checkout(				[					$class: 'GitSCM',					branches: [[name: '${Branch}']],					userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_addr}"]]				]			)		}	}}複製程式碼

這裡的變數${git_auth}就是新增憑據時設定的ID,如果不設定ID會隨機生成一個ID

選擇withCredentials: Bind credentials to variables

然後和之前新增的憑據進行繫結,這裡選擇型別為:Username and password (separated)

設定使用者名稱和密碼的變數名,然後選擇剛才新增好的憑據

withCredentials([usernamePassword(credentialsId: 'faabc5e8-9587-4679-8c7e-54713ab5cd51', passwordVariable: 'img_pwd', usernameVariable: 'img_user')]) {	sh """		docker login -u ${img_user} -p ${img_pwd} ${img_domain}		docker build -t ${img_addr}:${date_time}_${git_cm_id} .		docker push ${whole_img_addr}	"""}複製程式碼

credentialsId: 這個 ID 就是隨機生成的 ID

執行指令碼進行更新映象

這裡是使用 python 寫了一個小指令碼,來呼叫 kubernetes 的介面做了一個patch的操作完成的。先來看下此指令碼的目錄結構

核心程式碼:deploy.py核心檔案:config.yaml 存放的是 kubeconfig 檔案,用於和 kubernetes 的認證

下面貼一下deploy.py的指令碼內容,可以參考下:

import osimport argparsefrom kubernetes import client, configclass deployServer:    def __init__(self, kubeconfig):        self.kubeconfig = kubeconfig        config.kube_config.load_kube_config(config_file=self.kubeconfig)        self._AppsV1Api = client.AppsV1Api()        self._CoreV1Api = client.CoreV1Api()        self._ExtensionsV1beta1Api = client.ExtensionsV1beta1Api()    def deploy_deploy(self, deploy_namespace, deploy_name, deploy_img=None, deploy_which=1):        try:            old_deploy = self._AppsV1Api.read_namespaced_deployment(                name=deploy_name,                namespace=deploy_namespace,            )            old_deploy_container = old_deploy.spec.template.spec.containers            pod_num = len(old_deploy_container)            if deploy_which == 1:                pod_name = old_deploy_container[0].name                old_img = old_deploy_container[0].image                print("獲取上一個版本的資訊\n")                print("當前Deployment有 {} 個pod, 為: {}\n".format(pod_num, pod_name))                print("上一個版本的映象地址為: {}\n".format(old_img))                print("此次構建的映象地址為: {}\n".format(deploy_img))                print("正在替換當前服務的映象地址....\n")                old_deploy_container[deploy_which - 1].image = deploy_img            else:                print("只支援替換一個映象地址")                exit(-1)            new_deploy = self._AppsV1Api.patch_namespaced_deployment(                name=deploy_name,                namespace=deploy_namespace,                body=old_deploy            )            print("映象地址已經替換完成\n")            return new_deploy        except Exception as e:            print(e)def run():    parser = argparse.ArgumentParser()    parser.add_argument('-n', '--name', help="構建的服務名")    parser.add_argument('-s', '--namespace', help="要構建的服務所處在的名稱空間")    parser.add_argument('-i', '--img', help="此次構建的映象地址")    parser.add_argument('-c', '--cluster',                        help="rancher中當前服務所處的叢集名稱")    args = parser.parse_args()    if not os.path.exists('../config/' + args.cluster):        print("當前叢集名未設定或名稱不正確: {}".format(args.cluster), 'red')        exit(-1)    else:        kubeconfig_file = '../config/' + args.cluster + '/' + 'config.yaml'        if os.path.exists(kubeconfig_file):            cli = deployServer(kubeconfig_file)            cli.deploy_deploy(                deploy_namespace=args.namespace,                deploy_name=args.name,                deploy_img=args.img            )        else:            print("當前叢集的kubeconfig不存在,請進行配置,位置為{}下的config.yaml.(注意: config.yaml名稱寫死,不需要改到)".format(args.cluster),                  'red')            exit(-1)if __name__ == '__main__':    run()複製程式碼

寫得比較簡單,沒有難懂的地方,關鍵的地方是:

new_deploy = self._AppsV1Api.patch_namespaced_deployment(                name=deploy_name,                namespace=deploy_namespace,                body=old_deploy            )複製程式碼

這一句是執行的 patch 操作,把替換好新的映象地址的內容進行 patch。然後就是執行就可以了。

其他

這裡有一個需要注意的地方是pipeline里加了一個異常捕獲,如下所示:

post {	success {	    // 如果上面階段執行成功,將把當前目錄刪掉		deleteDir()	}}複製程式碼

生命式的 pipeline 和指令碼式的 pipeline 的異常捕獲的寫法是有區別的,宣告式寫法是用的post來進行判斷,比較簡單,可以參考下官方文件

另外還有一個地方使用了並行執行,同時拉了服務的程式碼,和構建指令碼的程式碼,這樣可以提高執行整個流水線的速度,如下所示:

parallel {	stage('Pull server code') {		steps {			script {				checkout(					[						$class: 'GitSCM',						branches: [[name: '${Branch}']],						userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_addr}"]]					]				)			}		}	}	stage('Pull ops code') {		steps {			script {				checkout(					[						$class: 'GitSCM',						branches: [[name: 'pipeline-0.0.1']], //拉取的構建指令碼的分支						doGenerateSubmoduleConfigurations: false,						extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'DEPLOYJAVA']], //DEPLOYJAVA: 把程式碼存放到此目錄中						userRemoteConfigs: [[credentialsId: 'chenf-o', url: '構建指令碼的倉庫地址']]					]				)			}		}	}}複製程式碼

嗯,情況就是這麼個情況,一個簡簡單單的流水線就完成了,如果想快速使用流水線完成 CICD,可以參考下這篇文章。

12
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 萬字長文!阿里P8技術官手寫Mybatis筆記,我先收藏了