当初这家单位的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就这一行就完了。。。。。 上面简略介绍下这个ONBUILD
ONBUILD能够这样了解,就比方咱们这里应用的镜像,是基于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

而后docker push时会须要进行认证,也须要增加凭据,增加形式和下面是一样的,不过咱们能够用pipeline的语法来生成一个,形式如下:
点击Pipeline Syntax

抉择withCredentials: Bind credentials to variables

而后和之前增加的凭据进行绑定,这里抉择类型为:Username and password (separated)

设置用户名和明码的变量名,而后抉择方才增加好的凭据

点击生成即可,就是下面pipeline里的上面这段:

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,能够参考下这篇文章。

欢送各位朋友关注我的公众号,来一起学习提高哦