Kubernetes 环境下 Jenkins Pipeline 同时拉取多个 Git 仓库的最佳实践

449 阅读3分钟

背景

在现代软件开发过程中,持续集成和持续部署(CI/CD)已成为提升开发效率和交付质量的关键环节。Jenkins作为广泛应用的CI/CD工具,经常用于自动化构建、测试和部署流程。随着项目的复杂性增加,开发者面临同时管理和构建多个Git仓库的需求,而如何在Jenkinsfile中有效地实现这一点成为了挑战。本文将探讨如何在Kubernetes环境下,通过Jenkinsfile并行检出多个Git仓库,提升构建效率。

概要

本文将详细介绍如何在Jenkinsfile中拉取多个Git仓库,首先从基本的单仓库检出开始,逐步深入探讨如何将仓库检出到指定文件夹,并最终通过并行操作实现多个仓库的同时检出。文章中提供了完整的代码示例,并针对如何在Kubernetes环境下运行这些Jenkins Pipeline进行了实际操作指导。

通常,在 Jenkinsfile 中使用 Git 仓库是这样的:

stage('Checkout git repo') {
    steps {
        checkout([
            $class: 'GitSCM',
            branches: [[name: 'develop']],
            userRemoteConfigs: [[
                credentialsId:  'ssh-private-key-id',
                url: 'project-git-repo-url'
            ]]
        ])
    }
}

以上写法的结果是远程 Git 仓库中所有内容都被检出到当前工作目录下,因此我们才能像下面这样(不用切换目录而)直接运行一些命令:

stage('generate version number') {
    steps {
        VERSION_NUBMER = sh(script: "git describe --always", returnStdout: true).trim()
    }
}

将 Git 仓库检出到指定文件夹

如果要同时拉取多个 Git 仓库,面临的首要问题是如何将 Git 仓库检出到指定文件夹。对此,可以通过使用 $class: 'RelativeTargetDirectory' 解决,具体代码如下:

stage('Checkout git repo') {
    steps {
        checkout([
            $class: 'GitSCM',
            branches: [[name: 'develop']],
            userRemoteConfigs: [[
                credentialsId:  'ssh-private-key-id',
                url: 'project-git-repo-url'
            ]],
            extensions: [[
                $class: 'RelativeTargetDirectory',
                relativeTargetDir: 'repo-folder'
            ]]
        ])
    }
}

这样一来,远程 Git 仓库中所有内容将被检出到 ./repo-folder 文件夹下;后续我们若想在 Git 项目目录下操作,就需要先 cd ./repo-folder 了,例如:

stage('generate version number') {
    steps {
        VERSION_NUBMER = sh(script: "cd ./repo-folder && git describe --always", returnStdout: true).trim()
    }
}

检出多 Git 仓库

在解决了将 Git 仓库检出到指定文件夹这一问题后,检出多 Git 仓库这一需求就稍显容易,下面以检出repoA、repoB、repoC三个仓库为例:

stage('Checkout multiple git repos') {
    steps {
        checkout([
            $class: 'GitSCM',
            branches: [[name: 'develop']],
            userRemoteConfigs: [[
                credentialsId:  'ssh-private-key-id',
                url: 'project-git-repoA-url'
            ]],
            extensions: [[
                $class: 'RelativeTargetDirectory',
                relativeTargetDir: 'repoA-folder'
            ]]
        ])
 
        checkout([
            $class: 'GitSCM',
            branches: [[name: 'develop']],
            userRemoteConfigs: [[
                credentialsId:  'ssh-private-key-id',
                url: 'project-git-repoB-url'
            ]],
            extensions: [[
                $class: 'RelativeTargetDirectory',
                relativeTargetDir: 'repoB-folder'
            ]]
        ])
 
        checkout([
            $class: 'GitSCM',
            branches: [[name: 'develop']],
            userRemoteConfigs: [[
                credentialsId:  'ssh-private-key-id',
                url: 'project-git-repoC-url'
            ]],
            extensions: [[
                $class: 'RelativeTargetDirectory',
                relativeTargetDir: 'repoC-folder'
            ]]
        ])
    }
}

上面👆代码的结果就是工作目录下有了 repoA-folderrepoB-folderrepoC-folder 三个文件夹。

同时

截止现在,还没有达到我们的最终目的。本文标题叫做“Jenkinsfile 同时检出多个 Git 仓库”,但现在我们仅仅做到了“多个”还没有做到“同时”,为此我们需要使用Jenkins Pipeline语法中的 parallel 关键字。

stage('Checkout multiple git repos at the same time') {
    parallel {
        stage('repoA') {
            steps {
                checkout([
                    $class: 'GitSCM',
                    branches: [[name: 'develop']],
                    userRemoteConfigs: [[
                        credentialsId:  'ssh-private-key-id',
                        url: 'project-git-repoA-url'
                    ]],
                    extensions: [[
                        $class: 'RelativeTargetDirectory',
                        relativeTargetDir: 'repoA-folder'
                    ]]
                ])
            }
        }
 
        stage('repoB') {
            steps {
                checkout([
                    $class: 'GitSCM',
                    branches: [[name: 'develop']],
                    userRemoteConfigs: [[
                        credentialsId:  'ssh-private-key-id',
                        url: 'project-git-repoB-url'
                    ]],
                    extensions: [[
                        $class: 'RelativeTargetDirectory',
                        relativeTargetDir: 'repoB-folder'
                    ]]
                ])
            }
        }
 
        stage('repoC') {
            steps {
                checkout([
                    $class: 'GitSCM',
                    branches: [[name: 'develop']],
                    userRemoteConfigs: [[
                        credentialsId:  'ssh-private-key-id',
                        url: 'project-git-repoC-url'
                    ]],
                    extensions: [[
                        $class: 'RelativeTargetDirectory',
                        relativeTargetDir: 'repoC-folder'
                    ]]
                ])
            }
        }
    }
}
 

k8s 上跑jenkins pipeline 示例

pipeline {
  agent {
    node {
      label 'maven'
    }
​
  }
  stages {
    stage('Initialization') {
      agent none
      steps {
        script {
          echo "SONAR_PROJECT value: ${env.SONAR_PROJECT}"
          // 如果SONAR_PROJECT为空,则尝试从SOURCE_URL中提取
          if (env.SONAR_PROJECT == null || env.SONAR_PROJECT.trim() == '') {
            env.SONAR_PROJECT_NAME = sh(script :"basename -s .git ${env.SOURCE_URL}",returnStdout: true).trim()
            echo "reponame:${env.SONAR_PROJECT_NAME}"
          }
          // 如果harbor 项目名称为空,则尝试从SOURCE_URL中提取group
          echo "HARBOR_PROJECT value: ${env.HARBOR_PROJECT}"
          if (env.HARBOR_PROJECT == null || env.HARBOR_PROJECT.trim() == '') {
            def group = env.SOURCE_URL =~ /^https?://[^/]+/([^/]+)/
            env.TEMP_HARBOR_PROJECT = group ? group[0][1].toLowerCase() + '-aswatson' : ''
            echo "TEMP_HARBOR_PROJECT: ${env.TEMP_HARBOR_PROJECT}"
          }
          // 如果镜像名为空,则尝试从SOURCE_URL中提取repoName
          echo "HARBOR_PROJECT_IMAGE_NAME value: ${env.HARBOR_PROJECT_IMAGE_NAME}"
          if (env.HARBOR_PROJECT_IMAGE_NAME == null || env.HARBOR_PROJECT_IMAGE_NAME.trim() == '') {
            env.TEMP_HARBOR_PROJECT_IMAGE_NAME = sh(script :"basename -s .git ${env.SOURCE_URL}",returnStdout: true).trim()
          }
        }
​
      }
    }
    stage('Checkout git repo cae-authorization') {
        agent none
        steps {
            container('maven') {
                checkout([
                    $class: 'GitSCM',
                    branches: [[name: 'dev_keycloak_jdk17']],
                    userRemoteConfigs: [[
                        credentialsId:  "${SOURCE_CERT}",
                        url: 'https://www.example.com/rbac/cae-authorization.git'
                    ]],
                    extensions: [[
                        $class: 'RelativeTargetDirectory',
                        relativeTargetDir: 'repo-cae-authorization'
                    ]]
                ])
        }
        }
    }
        stage('Checkout git repo keycloak') {
        agent none
        steps {
            container('maven') {
                checkout([
                    $class: 'GitSCM',
                    branches: [[name: 'develop/1.1.0']],
                    userRemoteConfigs: [[
                        credentialsId:  'gitlab-admin',
                        url: 'https://www.example.com/rbac/keycloak.git'
                    ]],
                    extensions: [[
                        $class: 'RelativeTargetDirectory',
                        relativeTargetDir: 'repo-keycloak'
                    ]]
                ])
                sh "ls -al ./"
        }
        }
    }
​
​
​
​
     stage('Docker Build & Push') {
       agent none
       steps {
         container('maven') {
           withCredentials([usernamePassword(credentialsId:'admin',passwordVariable:'password',usernameVariable:'username')]) {
             sh '''
               set -ex
               cd ./repo-cae-authorization
               sed -i 's/10.32.224.129/bootstrap.ms-ctl.aswatson.net/g' ${DOCKERFILE}
               GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
               GIT_COMMIT=$(git rev-parse --short HEAD)
               export IMG=${REGISTRY_URL}/${TEMP_HARBOR_PROJECT}/${TEMP_HARBOR_PROJECT_IMAGE_NAME}:${GIT_BRANCH}-${GIT_COMMIT}
               docker build -f ${DOCKERFILE} ${DOCKER_CONTEXT} -t ${IMG} --tls-verify=false
               docker login harbor.example.com -u $username -p $password --tls-verify=false
               docker push $IMG --tls-verify=false
               echo ${IMG} > img
               '''
           }
​
         }
​
       }
     }
​
   }
  environment {
    REGISTRY_URL = 'harbor.example.com'
    REGISTRY_CERT = 'admin'
    HARBOR_PROJECT = ''
    HARBOR_PROJECT_IMAGE_NAME = ''
    SOURCE_URL = 'https://www.example.com/rbac/cae-authorization.git'
    SOURCE_REVISION = 'dev_keycloak_jdk17'
    THEME_SOURCE_URL = 'https://www.example.com/rbac/keycloak.git'
    THEME_SOURCE_REVISION = 'develop/1.1.0'
    SOURCE_CERT = 'gitlab-admin'
    DOCKER_CONTEXT = '.'
    DOCKERFILE = 'docker-keycloak/Dockerfile'
    SONAR_PROJECT = ''
    SONAR_SOURCE = './src/main'
    SONAR_QUALITYGATE = 'true'
    MAVEN_OPTS = '-XX:CICompilerCount=2 -Xms2048m -Xmx2048m -Xss4m'
  }
  parameters {
    string(name: 'GIT_BRANCHES', defaultValue: 'dev_keycloak_jdk17', description: '分支')
    booleanParam(name: 'WAIT_FOR_QUALITY_GATE', defaultValue: true, description: '是否等待SonarQube分析完成并返回质量门状态')
  }
}

结束语

通过以上方法,我们成功实现了在Kubernetes中通过Jenkinsfile同时拉取多个Git仓库的需求。这不仅优化了CI/CD流程,还提升了项目的构建效率。当然,随着项目的复杂度增加,可能还会面临更多挑战。未来,我将继续分享更多与Kubernetes、Jenkinsfile和DevOps相关的技巧和经验,帮助你在持续集成和持续交付的实践中更加游刃有余。敬请期待!