Cloud Native 16 min read

Transform Jenkins Pipelines into Tekton with Helm Charts for Multi‑Env Deployments

This article walks through converting a Jenkinsfile‑based pipeline to a Tekton Pipeline, adding Helm‑based deployment tasks, integrating SonarQube scanning, handling multi‑branch releases across different namespaces, and demonstrates the full PipelineRun execution with code snippets and images.

Ops Development Stories
Ops Development Stories
Ops Development Stories
Transform Jenkins Pipelines into Tekton with Helm Charts for Multi‑Env Deployments

In the previous "Tekton Series – Practice Part – My First Pipeline" article we built a simple pipeline; this guide shows how to customize a Tekton Pipeline based on an existing Jenkinsfile.

Jenkinsfile Overview

The original Jenkinsfile defines agents, environment variables, parameters, options, and stages for checkout, build & push, Helm repo addition, and deployments to dev, test, uat, pre, and prod environments, as well as a SonarQube scan.

<code>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)
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
''' 
                    }
                }
            }
        }
        // Additional stages for helm repo, deployments, and SonarQube omitted for brevity
    }
}
</code>

Key Differences from the First Pipeline

Multi‑branch pipeline releases

Deployment switched from kubectl to Helm charts

Added code scanning with SonarQube

Helm charts are stored in an Alibaba Cloud private repository; users can apply for access via Alibaba Cloud → Cloud DevOps → R&D → Private Repository.

Helm Chart Deployment Task

The following Task definition encapsulates Helm deployment parameters such as namespace, app name, chart name, and additional arguments.

<code>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}
</code>

SonarQube Scanner Task

This Task runs SonarQube analysis on the source code.

<code>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)"
</code>

Assembling the Full Pipeline

The pipeline stitches together cloning, unit testing, image building & pushing, Helm deployments to dev/test/pre/uat/prod based on branch names, and the SonarQube scan.

<code>apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: rd-pipeline
spec:
  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"
      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:
    - 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
      runAfter: [clone]
      taskRef:
        name: unit-test
      workspaces:
        - name: source
          workspace: rd-repo-pvc
    - name: build-push-image
      runAfter: [unit-test]
      taskRef:
        name: build-push-image
      params:
        - name: pathToDockerfile
          value: $(params.pathToDockerfile)
        - name: imageUrl
          value: $(params.imageUrl)
        - name: imageTag
          value: $(tasks.clone.results.commit)
      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]
    // Similar deploy-to-test, deploy-to-pre, deploy-to-uat, deploy-to-prod tasks omitted for brevity
    - 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
</code>

Running a PipelineRun

A sample PipelineRun supplies concrete parameter values and workspace definitions.

<code>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
</code>

The pipeline successfully builds the Docker image, pushes it, deploys the application to the selected namespace via Helm, and reports SonarQube analysis results.

Pipeline execution result
Pipeline execution result

SonarQube scan results are displayed as follows:

SonarQube results
SonarQube results

Conclusion

Migrating from Jenkins to Tekton mainly involves rewriting the pipeline syntax; the underlying steps—code checkout, build, image push, multi‑environment deployment, and code scanning—remain the same, making the transition straightforward.

CI/CDkubernetesDevOpsPipelineHelmTekton
Ops Development Stories
Written by

Ops Development Stories

Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.