How to use Tekton to set up a CI pipeline with OpenShift Pipelines
The Tekton project is a new Kubernetes- and container-native way to manage continuous integration/continuous delivery (CI/CD) systems. It's also the basis for OpenShift Pipelines. This article demonstrates a reference implementation for OpenShift Pipelines using a sample Quarkus project.
This article has two objectives:
- To show you how to set up a CI pipeline with OpenShift Pipelines using Tekton.
- To serve as a quickstart example for building your own CI pipeline.
That's a lot to cover, but you might be surprised at just how manageable it is.
The pipeline looks like this:
The pipeline's goals are to:
- Clone a source code repository at the dev branch.
- Run test cases.
- Build an artifact for the image.
- Use a Trivy task to scan build artifacts in parallel for vulnerabilities.
- Build a tag image using the source code commit ID.
- Use a Trivy task to scan the image locally in parallel for vulnerabilities.
- Push the image to the image repository ccop-dev (artifactory.xxx.corp.xxx.ca:5073).
- Use a Trivy task to scan the image in its remote image repository for vulnerabilities.
- Update the Kubernetes repository under the path k8s/overlays/dev on the dev branch to point to the latest image in the Artifactory registry.
Prerequisites
To run this reference pipeline (and possibly your pipeline), you or your cluster administrator must install Red Hat OpenShift Pipelines. It may already be included in Installed Operators.
You must also create a new OpenShift namespace called cop-pipeline. You can do this in the UI or from a terminal:
$ oc create namespace cop-pipeline
Next, create or access both the reference source code repository and the reference Kubernetes repository.
Write some YAML
Most tasks in Kubernetes can be defined in YAML, so the first step in building this pipeline is to write some setup YAML.
[ Try our interactive scenarios to learn Red Hat OpenShift at your own pace. ]
From your profile page in the source code repository, set up an Access Token with the necessary permissions (api, read_api, read_repository, write_repository), as this screenshot shows:
Note that the same Access Token is used for the Kubernetes repository in the reference implementation. If you have different user profiles, you must set up multiple Access Tokens.
Set up a secret for the Access Tokens in the cop-pipeline OpenShift namespace and annotate the secrets. The tekton.dev/git-0: 'https://gitlab.xxx.corp.xxx.ca' line shows the annotation in the YAML file below:
apiVersion: v1
metadata:
name: gitlab-token
namespace: cop-pipeline
annotations:
tekton.dev/git-0: 'https://gitlab.xxx.corp.xxx.ca'
data:
password: xxxxxxxxxxxxxxxxxx=
username: xxxxxx==
type: kubernetes.io/basic-auth
Add the secrets to the service account pipeline:
kind: ServiceAccount
apiVersion: v1
metadata:
name: pipeline
namespace: cop-pipeline
secrets:
- name: pipeline-token-xxxxx
- name: pipeline-dockercfg-xxxx
- name: gitlab-token
- name: artifactory-token
imagePullSecrets:
- name: pipeline-dockercfg-xxxxx
Run a CI pipeline in a dev environment
You can manually run the pipeline, or you can create a webhook on GitLab or GitHub to trigger it:
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: ci-pipeline
namespace: cop-pipeline
spec:
params:
- description: git url to clone
name: git-source-url
type: string
- default: master
description: 'git revision to checkout (branch, tag, sha, ref…)'
name: git-source-revision
type: string
- description: image tag - truncated commit Id
name: short-commit-id
type: string
- default: 'http://sonarqube-cicd-tools.apps.ocp-dev-ade.xxxdev.dev.xxx.ca'
description: SonarQube url for static code analysis
name: SONAR_URL
type: string
- default: cb98fc7b68ea37feb56d151e10b87d6dc596f4b2
description: SonarQube authentication token for static code analysis
name: SONAR_AUTH_TOKEN
type: string
- default: ./
description: image path
name: LOCAL_SCAN_PATH
type: string
- default: ./
description: image path
name: LOCAL_IMAGE_SCAN_PATH
type: string
- default: 'artifactory.xxx.corp.xxx.ca:5073/ccop-dev/quarkus-ref-image-dev'
description: image path
name: REMOTE_IMAGE_URL
type: string
- default: 'artifactory.xxx.corp.xxx.ca:5073/ccop-dev/quarkus-ref-image-dev'
description: image path for security scanning
name: SCAN_IMAGE_URL
type: string
- default: UNKNOWN
description: vulnerability severity level
name: SEVERITY_LEVELS
type: string
- default: 'https://gitlab.xxx.corp.xxx.ca/xx/xxxxx/tekton-pipeline.git'
description: Kustomize git repo for CD
name: KUSTOMIZE_GIT_URL
type: string
- default: k8s/overlays/dev
description: Kustomize git repo context directory for CD
name: KUSTOMIZE_GIT_CONTEXT_DIR
type: string
- default: dev
description: Kustomize git repo branch
name: KUSTOMIZE_GIT_BRANCH
type: string
tasks:
- name: git-clone
params:
- name: url
value: $(params.git-source-url)
- name: revision
value: $(params.git-source-revision)
- name: sslVerify
value: 'false'
- name: noProxy
value: 'true'
taskRef:
kind: ClusterTask
name: git-clone
workspaces:
- name: output
workspace: app-source
- name: run-test-cases
params:
- name: GOALS
value:
- clean
- test
runAfter:
- git-clone
taskRef:
kind: Task
name: maven
workspaces:
- name: source
workspace: app-source
- name: maven-settings
workspace: maven-settings
- name: static-code-analysis
params:
- name: GOALS
value:
- 'sonar:sonar'
- '-Dsonar.projectKey=ci-pipeline-ref-arc'
- '-Dsonar.host.url=$(params.SONAR_URL)'
- '-Dsonar.login=$(params.SONAR_AUTH_TOKEN)'
- '-Dsonar.exclusions=**/*.java'
- '-s $(workspaces.maven-settings.path)/settings.xml'
runAfter:
- run-test-cases
taskRef:
kind: Task
name: maven
workspaces:
- name: source
workspace: app-source
- name: maven-settings
workspace: maven-settings
- name: build-artifact
params:
- name: GOALS
value:
- '-DskipTests'
- package
- '-Dquarkus.native.container-build=true'
runAfter:
- static-code-analysis
taskRef:
kind: ClusterTask
name: maven
workspaces:
- name: source
workspace: app-source
- name: maven-settings
workspace: maven-settings
- name: scan-build-artifact
params:
- name: SCAN_TYPE
value: filesystem
- name: SEVERITY_LEVELS
value: $(params.SEVERITY_LEVELS)
- name: SCAN_PATH_OR_IMAGE_URL
value: $(params.LOCAL_SCAN_PATH)
runAfter:
- build-artifact
taskRef:
kind: Task
name: trivy-scan
workspaces:
- name: local-image-repo
workspace: app-source
- name: build-image
params:
- name: IMAGE
value: $(params.REMOTE_IMAGE_URL)
- name: TLSVERIFY
value: 'false'
runAfter:
- build-artifact
taskRef:
kind: Task
name: buildah-build
workspaces:
- name: source
workspace: app-source
- name: varlibcontainers
workspace: shared-image-repo
- name: scan-local-image
params:
- name: SCAN_TYPE
value: filesystem
- name: SEVERITY_LEVELS
value: $(params.SEVERITY_LEVELS)
- name: SCAN_PATH_OR_IMAGE_URL
value: $(params.LOCAL_IMAGE_SCAN_PATH)
runAfter:
- build-image
taskRef:
kind: Task
name: trivy-scan
workspaces:
- name: local-image-repo
workspace: shared-image-repo
- name: push-image
params:
- name: IMAGE
value: $(params.REMOTE_IMAGE_URL)
- name: IMAGE_TAG
value: $(params.short-commit-id)
- name: TLSVERIFY
value: 'false'
runAfter:
- build-image
taskRef:
kind: Task
name: buildah-push
workspaces:
- name: source
workspace: app-source
- name: varlibcontainers
workspace: shared-image-repo
- name: scan-remote-image
params:
- name: SCAN_TYPE
value: image
- name: SEVERITY_LEVELS
value: $(params.SEVERITY_LEVELS)
- name: SCAN_PATH_OR_IMAGE_URL
value: '$(params.SCAN_IMAGE_URL):$(params.short-commit-id)'
- name: IGNORE_UNFIXED
value: 'true'
runAfter:
- push-image
taskRef:
kind: Task
name: trivy-scan
workspaces:
- name: local-image-repo
workspace: shared-image-repo
- name: update-kustomize-repo
params:
- name: gitRepositoryUrl
value: $(params.KUSTOMIZE_GIT_URL)
- name: gitRepositoryRevision
value: $(params.KUSTOMIZE_GIT_BRANCH)
- name: gitPath
value: $(params.KUSTOMIZE_GIT_CONTEXT_DIR)
- name: imageTag
value: $(params.short-commit-id)
- name: verbose
value: 'true'
runAfter:
- scan-remote-image
taskRef:
kind: Task
name: update-kustomize-repo
workspaces:
- name: repository
workspace: kustomize-repo
workspaces:
- name: app-source
- name: maven-settings
- name: shared-image-repo
- name: kustomize-repo
A successful pipeline run on the dev environment returns something like this:
A failed pipeline, on the other hand, returns this:
Create webhooks to trigger the pipeline run
Next, set up the following triggers on OpenShift.
First, create a trigger template using this YAML code:
apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerTemplate
metadata:
name: ci-pipeline-template
namespace: cop-pipeline
spec:
params:
- name: git-repo-url
- name: git-revision
resourcetemplates:
- apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: ci-pipeline-
spec:
params:
- name: git-source-url
value: $(tt.params.git-repo-url)
- name: git-source-revision
value: $(tt.params.git-revision)
- name: SONAR_URL
value: 'http://sonarqube:9000'
- name: SONAR_AUTH_TOKEN
value: xxxxxxxxxxxxxxxxxxxxx
- name: LOCAL_SCAN_PATH
value: ./
- name: LOCAL_IMAGE_SCAN_PATH
value: ./
- name: REMOTE_IMAGE_URL
value: >-
image-registry.openshift-image-registry.svc:5000/cop-pipeline/xxx-app-image
- name: SEVERITY_LEVELS
value: CRITICAL
- name: KUSTOMIZE_GIT_URL
value: 'https://gitlab.xxx.corp.xxx.ca/xx/xxxxx/tekton-pipeline.git'
- name: KUSTOMIZE_GIT_CONTEXT_DIR
value: k8s/overlays/dev
pipelineRef:
name: ci-pipeline
workspaces:
- name: app-source
persistentVolumeClaim:
claimName: workspace-pvc2
- name: maven-settings
persistentVolumeClaim:
claimName: maven-settings-pvc
- name: shared-image-repo
persistentVolumeClaim:
claimName: workspace-pvc2
- emptyDir: {}
name: kustomize-repo
Next, create a trigger binding using this YAML:
apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerBinding
metadata:
name: ci-pipeline-binding
namespace: cop-pipeline
spec:
params:
- name: git-repo-url
value: $(body.repository.git_http_url)
- name: git-revision
value: $(body.after)
Then create an event listener:
apiVersion: triggers.tekton.dev/v1alpha1
kind: EventListener
metadata:
name: ci-pipeline
namespace: cop-pipeline
spec:
namespaceSelector: {}
podTemplate: {}
resources: {}
serviceAccountName: pipeline
triggers:
- bindings:
- kind: TriggerBinding
ref: ci-pipeline-binding
name: ci-pipeline
template:
ref: ci-pipeline-template
In the GitLab source code repository, go to Settings/webhooks and set up a webhook for the repo to initiate a pipeline run after a successful commit.
Promote code to staging
Here's the YAML to promote your code to the staging environment:
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: uat-cd-pipeline
namespace: cop-pipeline
spec:
params:
- description: Git commitId of the change to deploy
name: IMAGE_TAG
type: string
- default: >-
image-registry.openshift-image-registry.svc:xxxx/cop-pipeline/dapo-app-image
description: Source image url without tag
name: SRC_IMAGE_URL
type: string
- default: 'image-registry.openshift-image-registry.svc:xxxx/ccop-dev/dapo-app-image-uat'
description: Destination image url without tag
name: DEST_IMAGE_URL
type: string
- default: UNKNOWN
description: Image Scan severity levels
name: SEVERITY_LEVELS
type: string
- default: 'https://gitlab.xxx.xxxx.xxx.ca/tekton-pipeline.git'
description: Kustomize git repo for CD
name: KUSTOMIZE_GIT_URL
type: string
- default: k8s/overlays/uat
description: Kustomize git repo context directory for CD
name: KUSTOMIZE_GIT_CONTEXT_DIR
type: string
- default: uat
description: Kustomize git repo branch
name: KUSTOMIZE_GIT_BRANCH
type: string
tasks:
- name: skopeo-copy
params:
- name: srcImageURL
value: 'docker://$(params.SRC_IMAGE_URL):$(params.IMAGE_TAG)'
- name: destImageURL
value: 'docker://$(params.DEST_IMAGE_URL):$(params.IMAGE_TAG)'
- name: srcTLSverify
value: 'false'
- name: destTLSverify
value: 'false'
taskRef:
kind: ClusterTask
name: skopeo-copy
workspaces:
- name: images-url
workspace: images-url
- name: trivy-scan
params:
- name: NO_PROXY
value: 'localhost,127.0.0.1'
- name: HTTP_PROXY
value: ''
- name: INSECURE_REGISTRY
value: 'false'
- name: SEVERITY_LEVELS
value: $(params.SEVERITY_LEVELS)
- name: SCAN_TYPE
value: image
- name: TRIVY_IMAGE
value: >-
image-registry.openshift-image-registry.svc:5000/cop-pipeline/trivy-image:v0.18.3
- name: SCAN_PATH_OR_IMAGE_URL
value: '$(params.DEST_IMAGE_URL):$(params.IMAGE_TAG)'
- name: IGNORE_UNFIXED
value: 'false'
runAfter:
- skopeo-copy
taskRef:
kind: Task
name: trivy-scan
workspaces:
- name: local-image-repo
workspace: image-repo
- name: update-kustomize-repo
params:
- name: gitRepositoryUrl
value: $(params.KUSTOMIZE_GIT_URL)
- name: gitRepositoryRevision
value: $(params.KUSTOMIZE_GIT_BRANCH)
- name: gitPath
value: $(params.KUSTOMIZE_GIT_CONTEXT_DIR)
- name: fileName
value: deployment-patches.yaml
- name: imageTag
value: $(params.IMAGE_TAG)
- name: verbose
value: 'true'
runAfter:
- trivy-scan
taskRef:
kind: Task
name: update-kustomize-repo
workspaces:
- name: repository
workspace: kustomize-repo
workspaces:
- name: images-url
- name: image-repo
- name: kustomize-repo
The pipeline looks like this:
The pipeline performs a Skopeo copy from the dev image repository to the staging environment's image repository. It then scans the image in the remote image repository for vulnerabilities using a Trivy task.
[ Free eBook: Getting GitOps: A practical platform with OpenShift, Argo CD, and Tekton. ]
The pipeline also updates the repository under the path k8s/overlays/uat in the uat branch to point to the latest image in the Artifactory registry.
Promote to production
Refer to the source YAML below to promote your code to production:
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: prod-cd-pipeline
namespace: cop-pipeline
spec:
params:
- description: Git commitId of the change to deploy
name: IMAGE_TAG
type: string
- default: >-
image-registry.openshift-image-registry.svc:xxxx/cop-pipeline/dapo-app-image
description: Source image url without tag
name: SRC_IMAGE_URL
type: string
- default: 'image-registry.openshift-image-registry.svc:xxxx/ccop-dev/dapo-app-image-prod'
description: Destination image url without tag
name: DEST_IMAGE_URL
type: string
- default: UNKNOWN
description: Image Scan severity levels
name: SEVERITY_LEVELS
type: string
- default: 'https://gitlab.xxx.xxxx.xxx.ca/tekton-pipeline.git'
description: Kustomize git repo for CD
name: KUSTOMIZE_GIT_URL
type: string
- default: k8s/overlays/prod
description: Kustomize git repo context directory for CD
name: KUSTOMIZE_GIT_CONTEXT_DIR
type: string
- default: prod
description: Kustomize git repo branch
name: KUSTOMIZE_GIT_BRANCH
type: string
tasks:
- name: skopeo-copy
params:
- name: srcImageURL
value: 'docker://$(params.SRC_IMAGE_URL):$(params.IMAGE_TAG)'
- name: destImageURL
value: 'docker://$(params.DEST_IMAGE_URL):$(params.IMAGE_TAG)'
- name: srcTLSverify
value: 'false'
- name: destTLSverify
value: 'false'
taskRef:
kind: ClusterTask
name: skopeo-copy
workspaces:
- name: images-url
workspace: images-url
- name: trivy-scan
params:
- name: NO_PROXY
value: 'localhost,127.0.0.1'
- name: HTTP_PROXY
value: ''
- name: INSECURE_REGISTRY
value: 'false'
- name: SEVERITY_LEVELS
value: $(params.SEVERITY_LEVELS)
- name: SCAN_TYPE
value: image
- name: TRIVY_IMAGE
value: >-
image-registry.openshift-image-registry.svc:5000/cop-pipeline/trivy-image:v0.18.3
- name: SCAN_PATH_OR_IMAGE_URL
value: '$(params.DEST_IMAGE_URL):$(params.IMAGE_TAG)'
- name: IGNORE_UNFIXED
value: 'false'
runAfter:
- skopeo-copy
taskRef:
kind: Task
name: trivy-scan
workspaces:
- name: local-image-repo
workspace: image-repo
- name: update-kustomize-repo
params:
- name: gitRepositoryUrl
value: $(params.KUSTOMIZE_GIT_URL)
- name: gitRepositoryRevision
value: $(params.KUSTOMIZE_GIT_BRANCH)
- name: gitPath
value: $(params.KUSTOMIZE_GIT_CONTEXT_DIR)
- name: fileName
value: deployment-patches.yaml
- name: imageTag
value: $(params.IMAGE_TAG)
- name: verbose
value: 'true'
runAfter:
- trivy-scan
taskRef:
kind: Task
name: update-kustomize-repo
workspaces:
- name: repository
workspace: kustomize-repo
workspaces:
- name: images-url
- name: image-repo
- name: kustomize-repo
The pipeline looks like this:
Skopeo copies this pipeline from the dev image repository to the production image repository and then scans the image in the remote image repository for vulnerabilities using a Trivy task.
It also updates the repository under the k8s/overlays/prod path in the master branch to point to the latest image in the Artifactory image repository.
A complete pipeline
Now you know the components involved in creating a complete CI pipeline, from dev to production, and you have YAML examples of the steps required to make it happen. Refer to the reference repo as you develop a pipeline for your own code. The repositories and environments will differ, but the principles are the same.
In my follow-up article, I'll demonstrate how to create a CD pipeline with ArgoCD.
Dapo Oloyede
Dapo is a Senior Application Development Consultant at Red Hat and a Red Hat Certified Architect in Enterprise Applications (RHCA). He has over 15 years of experience in enterprise application development; and also has a strong passion for digital transformation in organizations. In his spare tim More about me
Olu Oteniya
Olu is a Staff Solution Architect at VMWare. He was a Senior Architect at Red Hat, who worked with various customers to formulate their digital transformation strategy to respond to the constantly changing business environments. More about me
Navigate the shifting technology landscape. Read An architect's guide to multicloud infrastructure.
OUR BEST CONTENT, DELIVERED TO YOUR INBOX