• devops-5:从0开始构建一条完成的CI CD流水线


    从0开始构建一条完成的CI CD流水线

    前文中已经讲述了静态、动态增加agent节点,以动态的k8s cloud为例,下面就以Maven构建Java程序为例,开始构建出一条完整的CI CD流水线。
    实现功能目标:
     1.分别可以根据分支和tag从源码仓库clone代码
     2.拿到源码后开始编译
     3.构建image,并push到镜像仓库
     4.部署到对应k8s集群
     5.部署成功后,钉钉告警
    以上是此pipeline实现的功能,后续计划:
     1.通过webhooks实现源码仓库push代码后,自动出发pipeline运行
     2.增加SonarQube代码质量检测步骤
     3.配合argoCD实现自动CD
     
    后续文章会陆续更新,敬请期待。
    镜像准备
    以k8s cloud当做agent的话,肯定需要一个基础镜像,镜像中需要有git、java和maven这些必要的工具环境,当然,可以使用jenkins提供的tools功能来配置工具导入到环境中,例如:
        tools {
            maven 'apache-maven-3.8.6' 
        }
    这种方式是比较方便的,但是每到一个新宿主机都要去主动下载一遍,还需要在jenkins中配置下载地址和方式,迁移时也比较麻烦,所以这里就采用一劳永逸的方法,把这些环境都提前打包到agent的镜像中,方便以后使用。
    这里选用的基础镜像是jenkins官方的agent镜像:
    docker pull jenkins/agent:latest
    这个官方镜像内已经包含git、java环境,以及后边需要和jenkins master建立连接的agent.jar包,所以我们只需再将maven包打包进去即可。
     
    maven工具包的准备
    官方下载maven包:
    wget https://dlcdn.apache.org/maven/maven-3/3.8.6/binaries/apache-maven-3.8.6-bin.tar.gz
     
    maven的配置(可选)
    很多java程序会用maven来进行构建,maven中又存在很多依赖组件(常用的是jar包、war包、pom等,也可把Zip包等通过POM文件定义为依赖组件),这个时候就会有一个仓库的概念,这个仓库分为三种类型,即:
    1. central:中央仓库,是由Maven社区提供的资源仓库,它包含了大量的常用程序库组件(jar包)。默认Maven的中央仓库地址为:http://repo1.maven.org/maven2/
    2. local:本地仓库,是存放maven环境本地的一个文件夹,此文件夹在第一次运行Maven命令时就创建了。Maven在执行构建任务时,根据依赖关系从中心仓库、或远程仓库下载依赖组件到本地仓库,然后本地仓库的内容供项目引用。
    3. remote:远程仓库,例如项目需要指定外部其他公司、或开源组织的jar包,这些依赖组件通用性等原因,未纳入Maven中央仓库,这个时候就要手动指定一个私有的远程仓库来拉取依赖。
     
    修改Maven中央仓库地址
    一般Maven的中央仓库由于网络问题会访问不到,这个时候可以修改地址为国内的Maven仓库地址或者公司私有的仓库地址,例如阿里的:http://maven.aliyun.com/nexus/content/groups/public
    修改步骤是要修改apache-maven-3.8.6/conf/settings.xml文件中以下字段:
    0
    修改为:
    复制代码
        <mirror>
          <id>nexus-aliyunid>
          <mirrorOf>centralmirrorOf>
          <name>Nexus aliyunname>
          <url>http://maven.aliyun.com/nexus/content/groups/publicurl>
        mirror>
    复制代码
    若修改后未生效,可以检查代码pom.xml中是否指定了仓库地址,类似语句:
    复制代码
    <repositories>
        <repository>
            <id>springsource-reposid>
            <name>SpringSource Repositoryname>
            <url>http://repo.spring.io/release/url>
        repository>
    repositories>
    复制代码

     

    修改Maven本地仓库路径
    Maven本地仓库路径默认为 ${user.home}/.m2/repository
    0
    可以直接在此进行修改,也可以在构建时用参数指定:
    mvn clean install -Dmaven.repo.local=/home/maven/local_repo/
    也可以在构建时指定配置文件地址:
    mvn clean install -s /home/maven/settings.xml

     

    开始构建镜像
    准备好的物料包及Dockerfile:
    [root@node01 agent-jenkins]# ls
    apache-maven-3.8.6.tar.gz  Dockerfile  jenkins-agent kubectl.tar.gz
    这里要说下jenkins-agent这个脚本文件,这个脚本文件也是官方提供的,源码文件在这里:https://github.com/jenkinsci/docker-inbound-agent,这是专门用来agent连接jenkins master的,采用的jnlp的方式。
     
    查看Dockerfile内容
    复制代码
    [root@node01 agent-jenkins]# cat Dockerfile 
    FROM jenkins/agent:latest
    
    USER root
    
    ADD apache-maven-3.8.6.tar.gz /opt/
    ADD kubectl.tar.gz /usr/local/bin/
    ENV PATH $PATH:/opt/apache-maven-3.8.6/bin/
    COPY jenkins-agent /usr/local/bin/
    CMD ["/bin/sh","-c","/usr/local/bin/jenkins-agent"]
    复制代码
    用于CD环节的工具,这里添加了kubectl命令,可根据需要添加。
     
    构建镜像
    # docker build -t registry.example.com:5000/jenkins/agent:v1 .
    # docker push registry.example.com:5000/jenkins/agent:v1
    registry.example.com:5000 是我的私有仓库
     
    配置k8s cloud的pod Template
    前边镜像准备完毕,下边要准备一个pod yaml模板,来运行每次临时加入和运行job的agent,默认情况下,k8s cloud会有一个名称为jnlp的容器专门来和jenkins master连接,然后我们可以再启动一个容器专门来跑Pipeline的job,但这里有一点要注意,如果pod中有多个容器,我们需要在Pipeline中指定某个在哪个容器中运行,这个具体怎么指定后边再说,我们这里采用覆盖截jnlp容器的方式来实现全部的工作都由一个container来完成,最终pod Template如下:
    复制代码
    apiVersion: "v1"
    kind: "Pod"
    metadata:
      name: jenkins-agent
      namespace: "default"
    spec:
      containers:
      - env:
        - name: "MAVEN_HOME"
          value: "/opt/apache-maven-3.8.6/"
        image: "registry.example.com:5000/jenkins/agent:v1"
        imagePullPolicy: "IfNotPresent"
        name: "jnlp"
        resources:
          limits:
            memory: "2G"
            cpu: "1500m"
          requests:
            memory: "1G"
            cpu: "100m"
        volumeMounts:
        - mountPath: "/root/.m2"
          name: "m2"
          readOnly: false
        - mountPath: "/home/jenkins/agent"
          name: "workspace-volume"
          readOnly: false
        - mountPath: "/usr/bin/docker"
          name: "docker-client"
          readOnly: true
        - mountPath: "/var/run/docker.sock"
          name: "docker-engine"
          readOnly: true
      volumes:
      - hostPath:
          path: "/root/.m2"
          type: "DirectoryOrCreate"
        name: "m2"
      - hostPath:
          path: "/home/jenkins"
        name: "workspace-volume"
      - hostPath:
          path: "/usr/bin/docker"
          type: File
        name: "docker-client"
      - hostPath:
          path: "/var/run/docker.sock"
          type: Socket
        name: "docker-engine"
    复制代码
    这里有四个volume:
    • m2:这个是用作maven的本地仓库路径,使用hostpath挂载到了本地目录,当然也可以存储到某些共享存储中,目的就是让依赖包只下载一次。
    • workspace-volume:这个是将jenkins的工作目录也使用hostpath挂载。
    • docker-client:docker命令的挂载,用于build、push等命令
    • docker-engine:docker engine的挂载,用于build、push等
     
    gitlab项目克隆
    simple-java-maven-app项目地址:https://github.com/jenkins-docs/simple-java-maven-app,将此项目克隆到本地gitlab即可。
    网访问github慢的话,可以git我的码云:https://gitee.com/vfancloud/simple-java-maven-app.git
     
    Pipeline编写
    创建凭证
    1.代码仓库我们使用前边搭建的gitlab,需要提前将gitlab的用户凭证在Jenkins创建好,方便后边Jenkins下载代码使用:
    系统管理—>凭证管理—>创建Username with password类型凭证(id需要记住,Pipeline中会使用)
    0
     
    2.我们的服务是部署在k8s集群中,所以还需要目标k8s的kubeconfig凭证,用来管理操控目标k8s:
    系统管理—>凭证管理—>创建Secret file类型凭证
    0
    一般项目都会有多个环境,所以每个环境的kubeconfig凭证都要提前创建好。
     
    3.镜像仓库的账号密码也要提前准备好,Username with password类型即可。
    0
    安装插件
    一些常用的必须插件,要提前安装:
    1. Git
    2. Git Parameter
    3. DingTalk
    4. build user vars plugin
    Pipeline
    此Pipeline起一个示例效果,有些功能点可以省略或者选择使用,酌情增删即可:
    复制代码
    pipeline {
        agent {
          kubernetes {
            cloud 'kubernetes-internal'  //指定cloud name
            inheritFrom 'jenkins-agent'  //指定podTemplate,新版本已经不再用label指定
            namespace 'default'
          }
        }
        environment {
            GIT_CERT = credentials('vfan-gitlab')  //gitlab用户凭证
            HARBOR_HOST = 'registry.example.com:5000'
            SERVER_NAME = 'simple-java-maven-app'
        }
    /*    tools {
            maven 'apache-maven-3.8.6' 镜像有maven环境了,可以不指定
        } */   
        options {
            buildDiscarder(logRotator(numToKeepStr: '10'))  //保持历史构建的最大个数
            timeout(20)  //默认单位分钟,20分钟
            timestamps()  //Pipeline开始时间以及每个step执行开始时间
        }
        parameters {
            choice(
                name: 'GIT_REPO_URL',
                choices: 'http://10.85.122.128:880/vfan/simple-java-maven-app.git',
                description: 'Git Repo example environment'
            )
            choice(
                name: 'GIT_TYPE',
                choices: ['branch', 'tag'],
                description: 'Git Repo example brance'
            )
            choice(
                name: 'GIT_REPO_BRANCE',
                choices: ['master', 'dev', 'test'],
                description: 'Git Repo example brance'
            )
            gitParameter name: 'GIT_TAG',
                type: 'PT_TAG',
                branch: 'master',
                branchFilter: '.*',
                defaultValue: '',
                selectedValue: 'TOP',
                sortMode: 'DESCENDING_SMART',
                listSize: '1',
                    description: 'Select you git tag.'
            choice(
                name: 'ENVIRONMENT', 
                choices: ['INT', 'DEV', 'PROD'], 
                description: 'Select deployment environment'
            )
        }
        stages {
            stage('git clone branch') {
                when {
                     expression { params.GIT_TYPE == "branch" }
                }
                steps {
                    git(
                        branch: params.GIT_REPO_BRANCE, 
                        credentialsId: env.GIT_CERT, 
                        url: params.GIT_REPO_URL
                    )
                }
                post {
                    success {
                        sh '''
                        echo "use branch build"
                        git status
                        '''
                    }
                }
            }
            stage('git clone tag') {
                when {
                     expression { params.GIT_TYPE == "tag" }
                }
                steps {
                  checkout([$class: 'GitSCM', 
                  branches: [[name: "${GIT_TAG}"]], 
                  userRemoteConfigs: [[credentialsId: env.GIT_CERT, url: params.GIT_REPO_URL]]])
                }
                post {
                    success {
                        sh '''
                        echo "use tag build"
                        git status
                        '''
                    }
                }
            }
            stage('Maven Build') {
                steps {
                    sh 'mvn -B -DskipTests clean package'
                }
            }
            stage('Test') {
                steps {
                    sh 'mvn test'
                }
                post {
                    always {
                        junit 'target/surefire-reports/*.xml'
                    }
                }
            }
            stage('Deliver') {
                steps {
                    sh './jenkins/scripts/deliver.sh'
                }
            }
            stage('Docker build && push') {
                steps {
                    withCredentials([usernamePassword(credentialsId: 'harbor-auth', passwordVariable: 'HARBOR_PASSWD', usernameVariable: 'HARBOR_USER')]) {
                        sh '''
                            echo "Other operations..."
                            echo "Start building..."
                            date -d "+8 hour" +%Y%m%d_%H%M%S > /tmp/date
                            BUILD_TIME=`cat /tmp/date`
                            docker build --build-arg APP_NAME=simple-java-maven-app -t ${HARBOR_HOST}/${SERVER_NAME}:${GIT_REPO_BRANCE}_${BUILD_TIME} .
                            echo "Build complete."
                            docker login $HARBOR_HOST -u $HARBOR_USER -p $HARBOR_PASSWD
                            docker push ${HARBOR_HOST}/${SERVER_NAME}:${GIT_REPO_BRANCE}_${BUILD_TIME}
                            docker rmi ${HARBOR_HOST}/${SERVER_NAME}:${GIT_REPO_BRANCE}_${BUILD_TIME}
                        '''
                    }
                }
            }
            stage('Deploy to k8s'){
                input{
                    message "Should we continue deploy?"
                    ok "Yes, we should."
    
                }
                environment {
                    // 提前创建好secret file类型的凭据
                    KUBE_CONFIG_INT = credentials('mycluster_int')
                    // KUBE_CONFIG_DEV = credentials('mycluster_dev')
                    // KUBE_CONFIG_PROD = credentials('mycluster_prod')
                }
                steps{
                    sh'''
                        BUILD_TIME=`cat /tmp/date`
                        case $ENVIRONMENT in
                            "INT")
                                kubectl set image deployment ${SERVER_NAME} --kubeconfig=${KUBE_CONFIG_INT} app=${HARBOR_HOST}/${SERVER_NAME}:${GIT_REPO_BRANCE}_${BUILD_TIME}
                                kubectl rollout status deployment ${SERVER_NAME} --kubeconfig=${KUBE_CONFIG_INT}
                            ;;
                            "DEV")
                                kubectl set image deployment ${SERVER_NAME} --kubeconfig=${KUBE_CONFIG_DEV} app=${HARBOR_HOST}/${SERVER_NAME}:${GIT_REPO_BRANCE}_${BUILD_TIME}
                                kubectl rollout status deployment ${SERVER_NAME} --kubeconfig=${KUBE_CONFIG_DEV}
                            ;;
                        esac
                        echo "Deployment complete."
                    '''
                }
            }
        }
        post { 
            success{ 
                echo 'Deployment succeeded.'
                dingtalk (
                    robot: 'myapp-dingding-robot',
                    type: 'MARKDOWN',  // 发什么类型的消息,有TEXT、LINK、MARKDOWN、和ACTION_CARD,参考https://jenkinsci.github.io/dingtalk-plugin/guide/pipeline.html
                    at: [],
                    atAll: false,
                    title: 'Jenkins发版成功',
                    text: [
                        "## 构建结果:**${currentBuild.result}**",
                        '---',
                        "## 构建信息",
                        '---',
                        "- 项目名称:${SERVER_NAME}",
                        "- 构建环境:${ENVIRONMENT}",
                        "- 构建分支:${GIT_REPO_BRANCE}",
                        "- 构建标签:${GIT_TAG}",
                        "- 项目地址:${GIT_REPO_URL}",
                        "- 构建用户:${env.BUILD_USER}"
                        ],
                //    messageUrl: '',
                //    picUrl: '',
                //    singleTitle: '',
                //    btns: [],
                //    btnLayout: '', 
                //    hideAvatar: false
                )
            }
            failure{
                echo "Deployment failed."
                dingtalk (
                    robot: 'myapp-dingding-robot',
                    type: 'MARKDOWN',  // 发什么类型的消息,有TEXT、LINK、MARKDOWN、和ACTION_CARD,参考https://jenkinsci.github.io/dingtalk-plugin/guide/pipeline.html
                    at: [],
                    atAll: false,
                    title: 'Jenkins发版失败',
                    text: [
                        "## 构建结果:**${currentBuild.result}**",
                        '---',
                        "## 构建信息",
                        '---',
                        "- 项目名称:${SERVER_NAME}",
                        "- 构建环境:${ENVIRONMENT}",
                        "- 构建分支:${GIT_REPO_BRANCE}",
                        "- 构建标签:${GIT_TAG}",
                        "- 项目地址:${GIT_REPO_URL}",
                        "- 构建用户:${env.BUILD_USER}"
                        ],
                //    messageUrl: '',
                //    picUrl: '',
                //    singleTitle: '',
                //    btns: [],
                //    btnLayout: '', 
                //    hideAvatar: false
                )
            }
        }
    }
    复制代码

     

    测试运行Pipeline
    0
    运行完成,钉钉也已收到通知,后续更新更多内容。
  • 相关阅读:
    C.打牌的贝贝(卡特兰数)
    软件工程(Software Engineering)
    【shell】条件语句
    MySQL(3)
    企业业务中台应用架构和技术架构
    ssm+vue的毕业生跟踪调查反馈管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。
    ASUS华硕ZenBook灵耀X逍遥UXF3000E_UX363EA原装出厂预装Win11系统工厂模式安装包
    鸿蒙生态融合进行时!菊风启动适配HarmonyOS NEXT,赋能原生应用实时
    如何删除卸载苹果mac电脑应用软件没有残留垃圾
    Altair:Python数据可视化库的魅力之旅
  • 原文地址:https://www.cnblogs.com/v-fan/p/17337306.html