DEVOPS架构师 -- 06基于sharedLibrary进行CICD流程的优化

基于sharedLibrary进行CI/CD流程的优化

由于公司内部项目众多,大量的项目使用同一套流程做CICD

  • 那么势必会存在大量的重复代码
  • 一旦某个公共的地方需要做调整,每个项目都需要修改

因此本章主要通过使用实现Jenkins的sharedLibrary的开发,以提取项目在CICD实践过程中的公共逻辑,提供一系列的流程的接口供公司内各项目调用。

开发完成后,对项目进行Jenkinsfile的改造,最后仅需通过简单的Jenkinsfile的配置,即可优雅的完成CICD流程的整个过程,此方式已在大型企业内部落地应用。

Library工作模式

由于流水线被组织中越来越多的项目所采用,常见的模式很可能会出现。 在多个项目之间共享流水线有助于减少冗余并保持代码 “DRY”。

流水线支持引用 “共享库” ,可以在外部源代码控制仓库中定义并加载到现有的流水线中。

@Library('my-shared-library') _

在实际运行过程中,会把library中定义的功能添加到构建目录中:

/var/jenkins_home/jobs/test-maven-build/branches/feature-CDN-2904.cm507o/builds/2/libs/my-shared-library/vars/devops.

使用library后,Jenkinsfile大致的样子如下:

@Library('my-shared-library') _

...
  stages {
    stage('build image') {
      steps {
         container('tools') {
           devops.buildImage("Dockerfile","172.21.51.67:5000/demo:latest")
         }
      }
    }
  }
  
  post {
    success {
      script {
          container('tools') {
              devops.notificationSuccess("dingTalk")
          }
      }
    }
  }
...
开发环境搭建

补录章节:及SpringBoot、SpringCloud都会使用

  • java
  • intelliJ idea
下载安装包

链接:https://pan.baidu.com/s/1B-bg2_IsB8dU7_62IEtnTg
提取码:wx6j

安装java

安装路径:D:\software\jdk

环境变量:

  • JAVA_HOME D:\software\jdk
  • CLASSPATH .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
  • PATH %JAVA_HOME%\bin
安装

解压路径:D:\software-3.0.2

环境变量:

  • _PATH D:\software-3.0.2
  • PATH D:\software-3.0.2\bin
安装idea

安装路径:D:\software\IntelliJ IDEA 2019.2.3

新建项目测试

Library代码结构介绍

共享库的目录结构如下:

(root)
+- src                     #  source files
|   +- org
|       +- foo
|           +- Bar.  # for org.foo.Bar class
+- vars
|   +- foo.          # for global 'foo' variable
|   +- foo.txt             # help for 'foo' variable

src 目录应该看起来像标准的 Java 源目录结构。当执行流水线时,该目录被添加到类路径下。

vars 目录定义可从流水线访问的全局变量的脚本。 每个 *. 文件的基名应该是一个 (~ Java) 标识符, 通常是 camelCased

基本语法介绍

新建项目

  • 变量

    使用数据类型的本地语法,或者使用def关键字

    // Defining a variable in lowercase  
    int x = 5;
    
    // Defining a variable in uppercase  
    int X = 6; 
    
    // Defining a variable with the underscore in it's name 
    def _Name = "Joe"; 
    
    println(x); 
    println(X); 
    println(_Name); 
    
    
  • 方法

    • 调用本地方法

      def sum(int a, int b){
          return a + b
      }
      
      println(sum(1,2))
      
    • 调用类中的方法

      # Hello.
      package demo
      
      def sayHi(String content) {
          return ("hi, " + content)
      }
      
      
      
      # Demo.
      import demo.Hello
      
      def demo() {
          return new Hello().sayHi("devops")
      }
      println(demo())
      
      
      
      # 级联调用
      # Hello.
      package demo
      
      def init(String content) {
          this.content = content
          return this
      }
      
      def sayHi() {
          println("hi, " + this.content)
          return this
      }
      
      def sayBye() {
          println("bye " + this.content)
      }
      
      
      # Demo.
      import demo.Hello
      
      def demo() {
          new Hello().init("devops").sayHi().sayBye()
      }
      
      demo()
      
      
  • 异常捕获

    def exceptionDemo(){
        try {
            def val = 10 / 0
            println(val)
        }catch(Exception e) {
            println(e.toString())
            throw e
        }
    }
    exceptionDemo()
    
  • 计时器与循环

    import .time.TimeCategory
    
    
    use( TimeCategory ) {
        def endTime = TimeCategory.plus(new Date(), TimeCategory.getSeconds(15))
        def counter = 0
        while(true) {
            println(counter++)
            sleep(1000)
            if (new Date() >= endTime) {
                println("done")
                break
            }
        }
    }
    
  • 解析yaml文件

    import org.yaml.snakeyaml.Yaml
    
    def readYaml(){
        def content = new File('myblog.yaml').text
        Yaml parser = new Yaml()
        def data = parser.load(content)
        def kind = data["kind"]
        def name = data["metadata"]["name"]
        println(kind)
        println(name)
    }
    readYaml()
    
library与Jenkins集成

先来看一下如何使用shared library实现最简单的helloworld输出功能,来理清楚使用shared library的流程。

Hello.
package com.luffy.devops

/**
* @author Yongxin
* @version v0.1
 */

/**
 * say hello
 * @param content
 */
def hello(String content) {
    this.content = content
    return this
}


def sayHi() {
    echo "Hi, ${this.content},how are you?"
    return this
}

def answer() {
    echo "${this.content}: fine, thank you, and you?"
    return this
}

def sayBye() {
    echo "i am fine too , ${this.content}, Bye!"
    return this
}

在gitlab创建项目,把library代码推送到镜像仓库。

配置Jenkins

[系统管理] -> [系统设置] -> [ Global Pipeline Libraries ]

  • Library Name:luffy-devops
  • Default Version:master
  • Source Code Management:Git
Jenkinsfile中引用

jenkins/pipelines/p11.yaml

@Library('luffy-devops') _

pipeline {
    agent { label 'jnlp-slave'}

    stages {
        stage('hello-devops') {
            steps {
                script {
                    devops.hello("树哥").sayHi().answer().sayBye()
                }
            }
        } 
    }
    post {
        success { 
            echo 'Congratulations!'
        }
        failure {
            echo 'Oh no!'
        }
        always { 
            echo 'I will always say Hello again!'
        }
    }
}

创建vars/devops.

import com.luffy.devops.Hello

def hello(String content) {
    return new Hello().hello(content)
}
library集成镜像构建及推送

需要实现的逻辑点:

  • docker build,docker push,docker login
  • 账户密码,jenkins凭据,(library中获取凭据内容),
  • docker login 172.21.51.67:5000
  • try catch
镜像构建逻辑实现

devops.

/**
 *
 * @param repo, 172.21.51.67:5000/demo/myblog/xxx/
 * @param tag, v1.0
 * @param dockerfile
 * @param credentialsId
 * @param context
 */
def docker(String repo, String tag, String credentialsId, String dockerfile="Dockerfile", String context=".") {
    return new Docker().docker(repo, tag, credentialsId, dockerfile, context)
}

Docker.

逻辑中需要注意的点:

  • 构建和推送镜像,需要登录仓库(需要认证)
  • 构建成功或者失败,需要将结果推给gitlab端
  • 为了将构建过程推送到钉钉消息中,需要将构建信息统一收集
package com.luffy.devops

/**
 *
 * @param repo
 * @param tag
 * @param credentialsId
 * @param dockerfile
 * @param context
 * @return
 */
def docker(String repo, String tag, String credentialsId, String dockerfile="Dockerfile", String context="."){
    this.repo = repo
    this.tag = tag
    this.dockerfile = dockerfile
    this.credentialsId = credentialsId
    this.context = context
    this.fullAddress = "${this.repo}:${this.tag}"
    this.isLoggedIn = false
    return this
}


/**
 * build image
 * @return
 */
def build() {
    this.login()
    retry(3) {
        try {
            sh "docker build ${this.context} -t ${this.fullAddress} -f ${this.dockerfile} "
        }catch (Exception exc) {
            throw exc
        }
        return this
    }
}


/**
 * push image
 * @return
 */
def push() {
    this.login()
    retry(3) {
        try {
            sh "docker push ${this.fullAddress}"
        }catch (Exception exc) {
            throw exc
        }
    }
    return this
}

/**
 * docker registry login
 * @return
 */
def login() {
    if(this.isLoggedIn || credentialsId == ""){
        return this
    }
    // docker login
    withCredentials([usernamePassword(credentialsId: this.credentialsId, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
        def regs = this.getRegistry()
        retry(3) {
            try {
                sh "docker login ${regs} -u $USERNAME -p $PASSWORD"
            } catch (Exception exc) {
                echo "docker login err, " + exc.toString()
            }
        }
    }
    this.isLoggedIn = true;
    return this;
}

/**
 * get registry server
 * @return
 */
def getRegistry(){
    def sp = this.repo.split("/")
    if (sp.size() > 1) {
        return sp[0]
    }
    return this.repo
}

Jenkinsfile

需要先在Jenkins端创建仓库登录凭据credential-registry

@Library('luffy-devops') _

pipeline {
    agent { label 'jnlp-slave'}
    options {
		timeout(time: 20, unit: 'MINUTES')
		gitLabConnection('gitlab')
	}
    environment {
        IMAGE_REPO = "172.21.51.67:5000/demo/myblog"
        IMAGE_CREDENTIAL = "credential-registry"
    }
    stages {
        stage('checkout') {
            steps {
                container('tools') {
                    checkout scm
                }
            }
        }
        stage('docker-image') {
            steps {
                container('tools') {
                    script{
                        devops.docker(
                            "${IMAGE_REPO}",
                            "${GIT_COMMIT}",
                            IMAGE_CREDENTIAL                          
                        ).build().push()
                    }
                }
            }
        }
    }
    post {
        success { 
            echo 'Congratulations!'
        }
        failure {
            echo 'Oh no!'
        }
    }
}
丰富构建通知逻辑

目前的构建镜像逻辑中缺少如下内容:

  • try逻辑中,若发生异常,是否该把异常抛出
    • 若直接抛出异常可能会导致多次重复的异常信息
    • 若不抛出,则如果未构建成功镜像,流水线感知不到错误
  • 通知gitlab端构建任务及状态
  • 构建通知格式

需要针对上述问题,做出优化

  1. 优化try逻辑

    def build() {
        this.login()
        def isSuccess = false
        def errMsg
        retry(3) {
            try {
                sh "docker build ${this.context} -t ${this.fullAddress} -f ${this.dockerfile}"
                isSuccess = true
            }catch (Exception err) {
                //ignore
                errMsg = err.toString()
            }
            // check if build success
            if(isSuccess){
                //todo
            }else {
                // throw exception,aborted pipeline
                error errMsg
            }
            return this
        }
    }
    
  2. 通知gitlab端构建任务及状态

    def build() {
        this.login()
        def isSuccess = false
        def errMsg = ""
        retry(3) {
            try {
                sh "docker build ${this.context} -t ${this.fullAddress} -f ${this.dockerfile} "
                isSuccess = true
            }catch (Exception err) {
                //ignore
                errMsg = err.toString()
            }
            // check if build success
            def stage = env.STAGE_NAME + '-build'
            if(isSuccess){
                updateGitlabCommitStatus(name: '${stage}', state: 'success')
            }else {
                updateGitlabCommitStatus(name: '${stage}', state: 'failed')
                // throw exception,aborted pipeline
                error errMsg
            }
    
            return this
        }
    }
    
  3. 钉钉消息通知格式

    由于每个stage都需要构建通知任务,因此抽成公共的逻辑,为各stage调用

    BuildMessage.

    package com.luffy.devops
    
    def updateBuildMessage(String source, String add) {
        if(!source){
            source = ""
        }
        env.BUILD_TASKS = source + add + "\n                    \n                    "
        return env.BUILD_TASKS
    }
    
    

    Docker. 中调用

    def getObject(String repo, String tag, String credentialsId, String dockerfile="Dockerfile", String context="."){
    	...
        this.msg = new BuildMessage()
        return this
    }
    
    
    ...
    
    def build() {
    ...
            // check if build success
            def stage = env.STAGE_NAME + '-build'
            if(isSuccess){
                updateGitlabCommitStatus(name: '${stage}', state: 'success')
                this.msg.updateBuildMessage(env.BUILD_TASKS, "${stage} OK...  √")
            }else {
                updateGitlabCommitStatus(name: '${stage}', state: 'failed')
                this.msg.updateBuildMessage(env.BUILD_TASKS, "${stage} Failed...  x")
                // throw exception,aborted pipeline
                error errMsg
            }
    
            return this
        }
    }
    

使用Jenkinsfile来验证上述修改是否正确:

@Library('luffy-devops') _

pipeline {
    agent { lab
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
第一章介绍docker的前世今生,了 解docker的实现原理,以Django项目为例,教大家如何编写最佳的Dockerfile实现构业务镜像的制作。通过本章的学习,大家会知道docker的概念及基本操作,并学会构建自己的业务镜像,并通过抓包的方式掌握Docker最常用的bridge网络模式的通信。第二章本章学习kubernetes的架构及工作流程,重点介绍如本章学习kubernetes的架构及工作流程,重点介绍如断的滚动更新,通过服务发现来实现集群内部的服务间访问,并通过ingress- -nginx实现外部使用域名访问集群内部的服务。同时介绍基于EFK如何搭建Kubernetes集群的日志收集系统。学完本章,我们的Django demo项目已经可以运行在k8s集群中,同时我们可以使用域名进行服务的访问。第三章本章基于k8s集群部署gitlab、sonarQube、 Jenkins等工具,并把上述工具集成到Jenkins中,以Django项目为例,通过多分支流水线及Jenkinsfle实现项目代码提交到不同的仓库分支,实现自动代码扫描、单元测试、docker容器构建、k8s服务的自动部署。第四章由于公司内部项目众多,大量的项目使用同一套流程CICD,那么势必会存在大量的重复代码,因此本章主要通过使用groovy实现Jenkins的sharedL ibrary的开发,以提取项目在CICD实践过程中的公共逻辑,提供一系列的流程的接口供公司内各项目调用,开发完成后,还是以Django的demo项目为例,进行Jenkinsfle的改造,最后仅需通过简单的Jenkinsfle的配置,即可优雅的完成CICD流程的整个过程,此方式已在大型企业内部落地应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FikL-09-19

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值