4

Tekton系列之实践篇-由Jenkins改成Tekton

 2 years ago
source link: https://www.51cto.com/article/704400.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Tekton系列之实践篇-由Jenkins改成Tekton-51CTO.COM
Tekton系列之实践篇-由Jenkins改成Tekton
作者:乔克 2022-03-21 09:40:48
从Jenkins迁移到Tekton,主要就是Pipeline的改写,但是从整体来看并不复杂,因为Jenkins中的过程都是定义好的,我们只需要按它的步骤改造成Tekton适配的语法即可。

66c805475825ab690472546948ca558e79efac.png

在《​​Tekton系列之实践篇-我的第一条Pipeline​​》中我们已经实现了第一条流水线,但是这条流水线还是比较简单,完成了基础的功能。这篇文章带你怎么根据Jenkins的Jenkinsfile来定制自己的Tekton Pipeline。

首先我们来看看Jenkinsfile中是什么样子,如下:

// 引入方法
def dingmes = new org.devops.sendDingTalk()
def BUILD_USER
def IS_IMAGE_PUSH

pipeline {
    agent {
        kubernetes {
            label "jenkins-slave-${UUID.randomUUID().toString()}"
            yaml """
apiVersion: v1
kind: Pod
spec:
  nodeSelector:
    kubernetes.io/hostname: node-2
  containers:
  - name: gradle
    image: registry.cn-hangzhou.aliyuncs.com/coolops/builder-gradle:v2
    command: ['cat']
    tty: true
    volumeMounts:
      - name: caches
        mountPath: /root/.gradle/caches/
      - name: indocker
        mountPath: /var/run/docker.sock
  - name: helm
    image: registry.cn-hangzhou.aliyuncs.com/coolops/helm3:3.2.4 
    command: ['cat']
    tty: true       
  - name: sonar
    image: registry.cn-hangzhou.aliyuncs.com/coolops/gradle:5.6.4-jdk11
    command: ['cat']
    tty: true
    volumeMounts:
      - name: sonarcache
        mountPath: /root/.gradle/caches/
  volumes:
    - name: caches
      hostPath:
        path: "/data/jenkins-job/${JOB_NAME}/gradle/"
    - name: indocker
      hostPath:
        path: "/var/run/docker.sock"
    - name: sonarcache
      hostPath:
        path: "/data/jenkins-job/${JOB_NAME}/sonar/"
"""
        }
    }
    environment {
        APP_NAME = "${params.APP_NAME}"
        DOCKER_CREDENTIAL_ID = 'dockerhub-token'
        GIT_CREDENTIAL_ID = 'git-token'
        SONAR_CREDENTIAL_ID = 'sonar-token'
        KUBECONFIG_CREDENTIAL_ID = 'kubeconfig-token'
        REGISTRY = 'registry.cn-hangzhou.aliyuncs.com'
        DOCKERHUB_NAMESPACE = 'coolops'
        CHART = 'coolops/rd'
        CHART_USERNAME=xxx
        CHART_PASSWORD=xxx
        IMG_REPO = "$REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME"
        IMG_TAG = "$GIT_COMMIT"
        COMMON_ARGS = "--set image.repository=$IMG_REPO \
                       --set image.tag=$IMG_TAG \
                       --set ingress.hosts[0].paths[0]=/ "
    }

    parameters {
        choice(description: '通过 Gradle --refresh-dependencies 参数进行 Jar 包强制刷新',  name: 'refresh', choices: ['false', 'true'])
    }

    options {
        timeout(time: 30, unit: 'MINUTES') 
    }

    stages {
        stage('Checkout SCM') {
            steps {
                checkout(scm)
            }
        }      
        stage('Build & Push') {
            steps {
                container('gradle') {
                    withCredentials([usernamePassword(credentialsId: "$DOCKER_CREDENTIAL_ID", passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME')]) {
                        sh 'echo "$DOCKER_PASSWORD" | docker login $REGISTRY -u "$DOCKER_USERNAME" --password-stdin'
                        sh '''
                            export EXIST_IMG=$(docker pull $IMG_REPO:$IMG_TAG &>/dev/null && echo true || echo false)
                            echo $EXIST_IMG
                            if [ $refresh == "true" -o $EXIST_IMG == "false" ]
                            then
                                echo "开始编译并推送镜像" ;
                                $refresh && gradle clean bootJar --configure-on-demand --build-cache --refresh-dependencies || gradle clean bootJar --configure-on-demand --build-cache            
                                docker build -f Dockerfile -t $IMG_REPO:$IMG_TAG . ;
                                docker push $IMG_REPO:$IMG_TAG ;
                            else
                                echo "镜像已存在,跳过编译";
                            fi
                            '''
                    }
                }
            }
        }       
        stage('helm3 add repo') {
                steps {
                    container('helm') {
                        withCredentials([kubeconfigContent(credentialsId : 'kubeconfig-token' ,variable : 'kubconfig' ,)]) {
                        sh '''
                            set +x
                            mkdir ~/.kube/
                            echo "$kubconfig" > ~/.kube/config
                            '''
                        sh 'helm repo add coolops https://repomanage.rdc.aliyun.com/helm_repositories/66465-coolops --username=${CHART_USERNAME} --password=${CHART_PASSWORD}'
                        }    
                    }
                }
            } 
        }
        stage('Deploy To Dev') {
            environment {
                NAMESPACE = 'coolops-dev'
                ENV = 'dev'
            }
            when {
                expression {
                    return "$BRANCH_NAME".contains('dev')
                }
            }
            steps {
                container('helm') {
                    script {
                      stepsHelm()
                    }               
                }
            }
        }      
        stage('Deploy To test') {
            environment {
                NAMESPACE = 'coolops-test'
                ENV = 'test'
            }
            when {
                expression {
                    return "$BRANCH_NAME".contains('test')
                }
            }
            steps {
                container('helm') {
                    script {
                      stepsHelm()
                    }               
                }
            }
        }
        stage('Deploy To Uat') {
            environment {
                NAMESPACE = 'coolops-uat'
                ENV = 'uat'
            }
            when {
                expression {
                    return "$BRANCH_NAME".contains('uat')
                }
            }
            steps {
                container('helm') {                                   
                    script {
                      stepsHelm()
                    }               
                }
            }
        }

        stage('Deploy To Pre') {
            environment {
                NAMESPACE = 'coolops-pre'
                ENV = 'pre'
            }
            when {
                expression {
                    return "$BRANCH_NAME".contains('pre')
                }
            }
            steps {
                container('helm') {                                   
                    script {
                      stepsHelm()
                    }               
                }
            }
        }        
        stage('Deploy To Prod') {
            environment {
                NAMESPACE = 'coolops-prod'
                ENV = 'prod'
            }
            when {
                expression {
                    return "$BRANCH_NAME".contains('prod')
                }
            }
            steps {
                container('helm') {                                   
                    script {
                      stepsHelm()
                    }               
                }
            }
        }
        // 扫描
        stage('Sonarqube Scanner') {   
              when {
                    expression {
                        return "$JOB_NAME".contains('skip')
                    }
                }    
              steps {
                timeout(time:20,unit:'MINUTES'){
                    catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
                    container('sonar') {
                        sh 'gradle sonarqube \
                            -x test\
                            -Dsonar.host.url=http://sonar.coolops.cn \
                            -Dsonar.login=c17650fa820d985daf1f29d8a3f685d789e47e45'
                        }
                    }
                }
            }
        }
    }
 }

整体的Jenkinsfile我做了一些删减,但是整个流程是没变的,咋一看是不是同样很简单?我将步骤整理如下:

  • 从代码仓库拉取代码。
  • 编译代码并推送到仓库。
  • 根据不同的分支推送到不同的环境。
  • 代码扫描。

整体的流程和上一篇文章没太大不同,区别在于:

  • 多分支流水线发布。
  • 由kubectl改成了helm chart。
  • 新增了代码扫描。

这里采用Helm Chart来部署应用,我使用的是阿里云的Chart仓库。不会使用的朋友可以通过阿里云-->云效DevOps-->研发-->私有仓库进行申请。

31047fa055fe386bf2c8313952e43025fd206e.png

我们现在先创建Task,然后再组装Pipeline。

使用Helm Chart发布应用Task

我们在之前的文章中使用的是kubectl来发布应用,由于在我实际的使用过程中,是使用的Helm来管理的,为了保持一致,这里先创建一个Helm发布应用的Task。

在创建之前,我们先来看看有哪些地方是需要参数的:

  • namespace:由于我是不同环境不同的namespace,所以在多分支发布的时候需要指定namespace。
  • app_name:应用名。
  • chart_name:helm chart 名。
  • args:helm chart 的其他参数。

所以我们定义的Task如下:

apiVersion: tekton.dev/v1alpha1
kind: Task
metadata:
  name: helm-to-k8s
spec:
  workspaces:
    - name: source
    - name: kubernetesconfig
      mountPath: /root/.kube
  params:
    - name: IMAGE
    - name: TAG
    - name: NAMESPACE
    - name: BRANCH_NAME
    - name: CHART_NAME
    - name: CHART_USERNAME
    - name: CHART_PASSWORD
    - name: APP_NAME
  steps:
    - name: run-helm
      image: registry.cn-hangzhou.aliyuncs.com/coolops/helm3:3.2.4
      workingDir: $(workspaces.source.path)
      script: |
        helm repo add coolops https://repomanage.rdc.aliyun.com/helm_repositories/66465-coolops --username=$(params.CHART_USERNAME) --password=$(params.CHART_PASSWORD)
        common_args="--set image.repository=$(params.IMAGE) --set image.tag=$(params.TAG) --set ingress.hosts[0].paths[0]=/"
        helm  -n $(params.NAMESPACE) upgrade $(params.APP_NAME) $(params.CHART_NAME) ${common_args} || \
        helm  -n $(params.NAMESPACE) install $(params.APP_NAME) $(params.CHART_NAME) ${common_args} 

代码扫描Task

由于在Jenkins中使用了代码扫描,所以这里加一个代码扫描的Task,如下:

apiVersion: tekton.dev/v1alpha1
kind: Task
metadata:
  name: sonar-scanner
spec:
  workspaces:
    - name: source
  params:
    - name: SONAR_USERNAME
    - name: SONAR_PASSWORD
    - name: SONAR_URL
    - name: APP_NAME
  steps:
    - name: sonar-scanner
      image: registry.cn-hangzhou.aliyuncs.com/coolops/sonar-scanner:2.2.0
      workingDir: $(workspaces.source.path)
      script: |
          scanTime=`date +%F-%H-%M-%S`
          sonar-scanner -Dsonar.host.url=$(params.SONAR_URL) \
                      -Dsonar.projectKey=$(params.APP_NAME)  \
                      -Dsonar.projectName=$(params.APP_NAME)  \
                      -Dsonar.projectVersion=${scanTime} \
                      -Dsonar.login=$(params.SONAR_USERNAME) \
                      -Dsonar.password=$(params.SONAR_PASSWORD) \
                      -Dsonar.projectDescription="$(workspaces.source.path)"

需要新增的Task就这两个,接下来就是组装Pipeline了,多分支发布也是在Pipeline中组装。

整合Pipeline

在整合Pipeline之前,还是先来梳理一下流程:

  • 编译构建、推送镜像。
  • 发布应用----多环境。
  • 代码扫描。
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: rd-pipeline
spec:
  workspaces: # 声明 workspaces
    - name: rd-repo-pvc
    - name: docker-config
    - name: kubernetes-config
  params:
    # 定义代码仓库
    - name: git_url
    - name: revision
      type: string
      default: "master"
    - name: gitInitImage
      type: string
      default: "registry.cn-hangzhou.aliyuncs.com/coolops/tekton-git-init:v0.29"
    # 定义镜像参数
    - name: pathToDockerfile
      description: The path to the build context, used by Kaniko - within the workspace
      default: .
    - name: imageUrl
      description: Url of image repository
    - name: imageTag
      description: Tag to apply to the built image
      default: latest
    - name: chart_name
      type: string
      default: coolops/coolops-rd
    - name: chart_username
      type: string
    - name: chart_password
      type: string
    - name: app_name
      type: string
    - name: namespace
      type: string
      default: default
    # 定义代码扫描
    - name: sonar_username
      type: string
      default: admin
    - name: sonar_password
      type: string
      default: admin
    - name: sonar_url
      type: string
  tasks: # 添加task到流水线中
    - name: clone
      taskRef:
        name: git-clone
      workspaces:
        - name: output
          workspace: rd-repo-pvc
      params:
        - name: url
          value: $(params.git_url)
        - name: revision
          value: $(params.revision)
        - name: gitInitImage
          value: $(params.gitInitImage)
    - name: unit-test
      workspaces: # 传递 workspaces
        - name: source
          workspace: rd-repo-pvc
      taskRef:
        name: unit-test
      runAfter:
        - clone
    - name: build-push-image
      params:
        - name: pathToDockerfile
          value: $(params.pathToDockerfile)
        - name: imageUrl
          value: $(params.imageUrl)
        - name: imageTag
          value: $(tasks.clone.results.commit)
      taskRef:
        name: build-push-image
      runAfter:
        - unit-test
      workspaces: # 传递 workspaces
        - name: source
          workspace: rd-repo-pvc
        - name: dockerconfig
          workspace: docker-config
    - name: deploy-to-dev
      when:
        - input: $(params.revision)
          operator: in
          values:
            - dev
      taskRef:
        name: helm-to-k8s
      params:
        - name: IMAGE
          value: $(params.imageUrl)
        - name: TAG
          value: $(tasks.clone.results.commit)
        - name: BRANCH_NAME
          value: $(params.revision)
        - name: CHART_NAME
          value: $(params.chart_name)
        - name: CHART_USERNAME
          value: $(params.chart_username)
        - name: CHART_PASSWORD
          value: $(params.chart_password)
        - name: APP_NAME
          value: $(params.app_name)
        - name: NAMESPACE
          value: coolops-dev
      workspaces:
        - name: source
          workspace: rd-repo-pvc
        - name: kubernetesconfig
          workspace: kubernetes-config
      runAfter:
        - build-push-image
    - name: deploy-to-test
      when:
        - input: $(params.revision)
          operator: in
          values:
            - test
      taskRef:
        name: helm-to-k8s
      params:
        - name: IMAGE
          value: $(params.imageUrl)
        - name: TAG
          value: $(tasks.clone.results.commit)
        - name: BRANCH_NAME
          value: $(params.revision)
        - name: CHART_NAME
          value: $(params.chart_name)
        - name: CHART_USERNAME
          value: $(params.chart_username)
        - name: CHART_PASSWORD
          value: $(params.chart_password)
        - name: APP_NAME
          value: $(params.app_name)
        - name: NAMESPACE
          value: coolops-test
      workspaces:
        - name: source
          workspace: rd-repo-pvc
        - name: kubernetesconfig
          workspace: kubernetes-config
      runAfter:
        - build-push-image
    - name: deploy-to-pre
      when:
        - input: $(params.revision)
          operator: in
          values:
            - pre
      taskRef:
        name: helm-to-k8s
      params:
        - name: IMAGE
          value: $(params.imageUrl)
        - name: TAG
          value: $(tasks.clone.results.commit)
        - name: BRANCH_NAME
          value: $(params.revision)
        - name: CHART_NAME
          value: $(params.chart_name)
        - name: CHART_USERNAME
          value: $(params.chart_username)
        - name: CHART_PASSWORD
          value: $(params.chart_password)
        - name: APP_NAME
          value: $(params.app_name)
        - name: NAMESPACE
          value: coolops-pre
      workspaces:
        - name: source
          workspace: rd-repo-pvc
        - name: kubernetesconfig
          workspace: kubernetes-config
      runAfter:
        - build-push-image
    - name: deploy-to-prod
      when:
        - input: $(params.revision)
          operator: in
          values:
            - prod
      taskRef:
        name: helm-to-k8s
      params:
        - name: IMAGE
          value: $(params.imageUrl)
        - name: TAG
          value: $(tasks.clone.results.commit)
        - name: BRANCH_NAME
          value: $(params.revision)
        - name: CHART_NAME
          value: $(params.chart_name)
        - name: CHART_USERNAME
          value: $(params.chart_username)
        - name: CHART_PASSWORD
          value: $(params.chart_password)
        - name: APP_NAME
          value: $(params.app_name)
        - name: NAMESPACE
          value: coolops-prod
      workspaces:
        - name: source
          workspace: rd-repo-pvc
        - name: kubernetesconfig
          workspace: kubernetes-config
      runAfter:
        - build-push-image
    - name: sonar-scanner
      when:
        - input: $(params.revision)
          operator: in
          values:
            - test
      taskRef:
        name: sonar-scanner
      runAfter:
        - clone
      params:
        - name: SONAR_USERNAME
          value: $(params.sonar_username)
        - name: SONAR_PASSWORD
          value: $(params.sonar_password)
        - name: SONAR_URL
          value: $(params.sonar_url)
        - name: APP_NAME
          value: $(params.app_name)
      workspaces:
        - name: source
          workspace: rd-repo-pvc

编排一个PipelineRun运行一下,如下:

apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: test-hello-world-pipeline-run
spec:
  pipelineRef:
    name: rd-pipeline
  params:
    - name: revision
      value: test
    - name: git_url
      value: https://gitee.com/coolops/devops-hello-world.git    
    - name: imageUrl
      value: registry.cn-hangzhou.aliyuncs.com/coolops/devops-hello-world
    - name: imageTag
      value: latest
    - name: pathToDockerfile
      value: Dockerfile
    - name: chart_username
      value: username
    - name: chart_password
      value: password
    - name: app_name
      value: hello-world
    - name: sonar_username
      value: username
    - name: sonar_password
      value: password
    - name: sonar_url
      value: http://sonarqube.coolops.cn
  workspaces:
    - name: rd-repo-pvc
      volumeClaimTemplate:
        spec:
          accessModes:
          - ReadWriteOnce
          storageClassName: openebs-hostpath
          resources:
            requests:
              storage: 1Gi
    - name: docker-config
      secret:
        secretName: docker-config
    - name: kubernetes-config
      secret:
        secretName: kubernetes-config
  serviceAccountName: tekton-build-sa

运行效果如下:

54880d767d806c71fcf0884b96050db597fb7b.png

上面只是把应用部署到同一个集群得不同namespace下,在实际情况下可能有多个集群,我们只需要指定不同的kubernetes-config即可,当然, 需保证Tekton所在的集群能与其他集群相通。

sonar上的扫描结果如下:

45e906849ab2704448b819147c389513d87e81.png

从Jenkins迁移到Tekton,主要就是Pipeline的改写,但是从整体来看并不复杂,因为Jenkins中的过程都是定义好的,我们只需要按它的步骤改造成Tekton适配的语法即可。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK