32.4 Tekton: Kubernetes-Native CI/CD Pipelines
Alright, let’s talk Tekton. If you’ve been slapping Jenkins or GitLab runners onto your Kubernetes cluster and hoping for the best, you’re going to love this. Tekton is different. It’s not just another CI/CD tool that runs on Kubernetes; it’s a framework built from Kubernetes resources. This isn’t a square peg in a round hole. It’s a round peg made of the same wood as the hole. The entire pipeline—every task, every step—is defined and runs as a Kubernetes Pod. This means you get to use kubectl to manage your CI/CD infrastructure. No more bespoke APIs or weird configuration languages. It’s all just YAML, my friend. Beautiful, terrifying, powerful YAML.
Now, the mental model. Tekton introduces a few key Custom Resource Definitions (CRDs) that you need to wrap your head around. Don’t worry, they make sense once you see them in action.
The Core Building Blocks
First, a Task. This is your smallest unit of work, a single function. It contains one or more Steps. Each Step is a container image that does one specific thing, like run a test or build a binary. A TaskRun is the actual execution of a Task. It’s the instance, the Pod that gets created.
Then, you have a Pipeline. This is a composition of Tasks, defining the order they run in (sequentially or in parallel) and what data passes between them. A PipelineRun is, you guessed it, the instance of a Pipeline being executed.
This separation of definition (Task, Pipeline) from execution (TaskRun, PipelineRun) is brilliant. It means you can define your CI/CD logic once and run it a million times with different parameters, all tracked as first-class Kubernetes objects.
Let’s build a simple Task to run some unit tests. You’d apply this with kubectl apply -f:
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: unit-test
spec:
params:
- name: app-repo-url
type: string
description: The URL of the git repo to clone
steps:
- name: clone-and-test
image: golang:1.21
script: |
# Clone the repository
git clone $(params.app-repo-url) /workspace/source
cd /workspace/source
# Run the tests. The WORKSPACE is a shared volume between steps.
go test ./...
See? It’s just a Pod spec at heart. But now, to run it, you create a TaskRun:
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: unit-test-run-1
spec:
taskRef:
name: unit-test
params:
- name: app-repo-url
value: "https://github.com/myorg/myapp.git"
Watch it go with kubectl get taskruns and kubectl get pods. It’s magic. But the real power comes from chaining these together.
Wiring Things Together with Pipelines and Workspaces
The example above has a glaring problem. It clones the repo itself. What if the next Task needs that same code? You don’t want to clone it again. This is where Workspaces come in. Think of them as shared volumes that you can pass between Tasks in a Pipeline. You declare a workspace in your Task and the Pipeline handles mounting it (often a PersistentVolumeClaim).
Let’s refactor. First, a better Task that expects its source code to be provided:
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: run-unit-tests
spec:
workspaces:
- name: source
description: The git repository to run tests against.
steps:
- name: test
image: golang:1.21
workingDir: $(workspaces.source.path)
script: |
go test ./...
Now, let’s create a Pipeline that uses this. It’ll need a Task to clone the repo first, which is so common that Tekton provides a ready-to-use one from their catalog.
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: test-pipeline
spec:
workspaces:
- name: shared-workspace
tasks:
- name: fetch-source
taskRef:
name: git-clone
kind: ClusterTask # This is a pre-installed cluster-scoped Task
params:
- name: url
value: "https://github.com/myorg/myapp"
workspaces:
- name: output
workspace: shared-workspace
- name: test-application
runAfter: [fetch-source] # Explicitly define order
taskRef:
name: run-unit-tests
workspaces:
- name: source
workspace: shared-workspace # Same workspace!
The git-clone ClusterTask writes the code to its output workspace, which is our pipeline’s shared-workspace. The test-application task then uses that same workspace. No redundant clones, and everything is neatly isolated.
The Rough Edges and Pitfalls
It’s not all sunshine and rainbows. Tekton is powerful, but that power comes with complexity.
- YAML Fatigue: You will write. So. Much. YAML. A full pipeline can be a behemoth. This is where a tool like the Tekton CLI (
tkn) becomes your best friend for interacting with runs without drowning inkubectloutput. - Debugging: When a
PipelineRunfails, you’re debugging a tree of Pods. You have to find the specific failed Pod, then the specific failed container within it, and check its logs.tkn pipelinerun logs -fhelps, but it’s still more complex than a traditional CI/CD server’s linear log output. - The Catalog is a Mixed Bag: The community
ClusterTaskcatalog is fantastic for common tasks (git-clone, buildpacks, etc.), but the versions can be inconsistent. Always check the docs for the specific ClusterTask you’re using—don’t assume the parameters. - Workspace Binding: This is a common stubbing point. When you create a
PipelineRun, you must explicitly bind the pipeline’s workspaces. You can’t just run it. For a shared volume, you need to provide a PVC. This is a classic “it’s flexible but now you have to make a decision” moment.
# This PipelineRun MUST bind the workspace
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: test-pipeline-run-
spec:
pipelineRef:
name: test-pipeline
workspaces:
- name: shared-workspace
volumeClaimTemplate: # Tekton will create a PVC on the fly for this run
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
The beauty is that once you get this, you have a CI/CD system that scales with your cluster, speaks native Kubernetes, and gives you an immense amount of control. You’re not fighting a foreign tool; you’re extending your platform. And that, if you ask me, is exactly how it should be.