Kubernetes-native CI/CD
CRDs are the API: apply YAML, get objects
crdcustom-resourcenative-task-taskrunnative-pipeline-pipelinerunowner-referencegarbage-collectionetcd · objects and their ownerReferences
idle# a kind cluster with Tekton Pipelines installed
$ kubectl get tekton,podsNo resources found in default namespace.
Try: create a PipelineRun before applying anything (it fails with CouldntGetPipeline). Then apply both files, run twice, and delete a run with each cascade mode. Deleting the Task does nothing to the runs: a reference by name is not ownership.
cluster (actual state)
The scheduler and kubelet move the Pod forward on their own. Each change fires an informer event at the controller, if it is listening.
controller
● running# controller watching TaskRuns and Pods
apiVersion: tekton.dev/v1kind: TaskRunmetadata: {name: build-run}spec:taskRef: {name: build} # steps: clone, build, pushstatus:{} # not reconciled yet
Try: play, and crash the controller while the Pod is running. Fire a couple of cluster events while it is down, then restart. In level-triggered mode one resync reads the current Pod and catches up. Switch to edge-triggered and do the same: the missed "step finished" events never arrive, so the count never reaches 3 and the TaskRun stays Running forever.
the request path through the API server
apiVersion: tekton.dev/v1kind: TaskRunmetadata:name: build-runspec:params:- name: revisionvalue: maintaskSpec:params:- name: revisionsteps:- name: cloneimage: alpine/gitscript: git checkout $(params.revison)- name: buildimage: golang:1.23script: go build ./...
$ kubectl apply -f taskrun.yaml# press send
Try: send it as is (the param typo is caught before anything runs). Fix it and send again to see the defaulting webhook add three fields. Then tick stdoutConfig and flip enable-api-fields to alpha.
Agents vs Pods
pod-per-taskruntraditional CI server · fixed agents
2 CPU per agentAgents are long-lived machines with tools preinstalled. They cost the same whether busy or idle, and a burst queues behind them.
Tekton · every TaskRun is a Pod
4 CPU per nodeNo build agents: each run is a Pod packed onto nodes by CPU request. With the autoscaler, nodes appear when Pods are pending and go away when empty (max 10).
queue length over the day · 10:30
Try: at 24 builds/hour, size the CI pool so its p95 wait is under 10 minutes and compare CPU-hours with the autoscaled cluster. Then turn the autoscaler off: Tekton is not magic, a fixed cluster queues Pods the same way. What changes is that capacity is shared and elastic, not tied to a pet build server.
the Tekton project, as one flow
Pipelines
The core. It adds the CRDs and runs the controller that reconciles PipelineRuns into TaskRuns and TaskRuns into Pods, plus the admission webhook that defaults and validates them.
- ← Triggers: creates PipelineRun
- ← Pipelines-as-Code: creates PipelineRun
- ← Hub / Catalog: resolved Task
- → Kubernetes: creates Pods
- → Chains: TaskRun done
- → Results: run records
- ← Dashboard: reads / creates
- ← tkn: kubectl-style API
Try: follow a git push, then click the Operator. Every Tekton component is just more CRDs and controllers on the same API server; none of them is a central build server.
the same YAML, applied to three clusters
click a gate to change itapiVersion: tekton.dev/v1kind: Pipeline # fragments, one per feature# when expressions · stablewhen:- input: $(params.branch)operator: invalues: [main]# StepAction ref · stablesteps:- name: cloneref: {name: git-clone}# matrix fan-out · betamatrix:params:- name: goosvalue: [linux, darwin]
apiVersion: v1kind: ConfigMapmetadata:name: feature-flagsnamespace: tekton-pipelinesdata:enable-api-fields: "stable"await-sidecar-readiness: "true"coschedule: "workspaces"results-from: "termination-message"
Try: keep only stable features and every cluster accepts the file. Add matrix or CEL and watch prod reject it. Portability holds as long as you stay within the level every target cluster enables.
- takeawayTekton is CRDs plus controllers. Definitions and runs are ordinary API objects, so RBAC, namespaces, kubectl, GitOps and garbage collection apply to CI/CD unchanged.
- takeawayThe controller is level-triggered: it reconciles from the current state, not from a history of events, which is why a restart or a burst of missed updates heals itself.
- takeawayThere are no build agents. Each TaskRun is a Pod scheduled by Kubernetes, so capacity is the cluster's and portability is the YAML's, limited only by which feature gates each cluster enables.
Key concepts · 19
book chapter 1- CustomResourceDefinition (CRD)
A Kubernetes object that adds a new resource type, such as Task or PipelineRun, to the API server.
Because Tekton's types are CRDs, kubectl, RBAC, namespaces, labels, watches and GitOps tools all work on pipelines exactly as they do on Deployments. There is no separate Tekton API or database.
kubectl get crd | grep tekton.devkubectl explain task.spec.steps↑ see it in the visualization - Custom resource
An instance of a CRD: one Task, one PipelineRun, stored in etcd like any other object.
Definitions (Task, Pipeline) and executions (TaskRun, PipelineRun) are separate objects. You keep the first in Git and create the second each time something should run.
task.tekton.dev/git-clonekubectl get pipelineruns↑ see it in the visualization - Task and TaskRun
A Task is a reusable list of steps; a TaskRun is one execution of it, with concrete params and workspaces.
Splitting what from when means a Task can be shared, versioned and reviewed, while each TaskRun records exactly what ran and how it ended.
spec.taskRef.nametkn task start↑ see it in the visualization - Pipeline and PipelineRun
A Pipeline arranges Tasks into a graph; a PipelineRun executes it by creating one TaskRun per pipeline task.
The PipelineRun owns its TaskRuns, which own their Pods, so one object represents the whole run and deleting it cleans up everything beneath.
spec.pipelineRef.nametkn pipeline start↑ see it in the visualization - ownerReference
metadata.ownerReferences on a child object pointing at the object that created it: Pod → TaskRun → PipelineRun.
Kubernetes' garbage collector follows these links. A taskRef by name is not ownership: deleting a Task leaves its past runs alone.
metadata.ownerReferencesblockOwnerDeletion↑ see it in the visualization - Cascading deletion
When an owner is deleted the garbage collector deletes its dependents, in background (default), foreground, or not at all (orphan).
Pruning old PipelineRuns is enough to reclaim their TaskRuns and Pods. Run history therefore lives in etcd only until you prune it, which is why Tekton Results exists.
kubectl delete pr --cascade=foregroundtkn pipelinerun delete --keep 5↑ see it in the visualization - Controller
A long-running process that watches resources through informers and works to make the actual state match the declared one.
Tekton's tekton-pipelines-controller is the only Tekton process on the run path. It does not execute builds; it creates Pods and lets the kubelet run them.
deployment/tekton-pipelines-controllerknative.dev/pkg↑ see it in the visualization - Reconcile loop
The function reconcile(key) called for each queued object: read the spec and the current world, act, update status.
Changes are coalesced into a workqueue of keys, so ten Pod updates in a burst can mean a single reconcile. The reconciler never assumes it saw every event.
workqueueinformer resync↑ see it in the visualization - Level-triggered
Acting on the current state (the level) rather than on the sequence of changes (the edges).
If the controller crashes or misses events, a fresh LIST and one reconcile per object are enough to catch up. Edge-triggered logic that counts events stays wrong forever after a miss.
↑ see it in the visualization - Status conditions
status.conditions on every run; Tekton's key condition is type Succeeded with status Unknown, True or False and a reason.
Unknown means still going (reason Pending, Running…); True and False are final (Succeeded, Failed, TaskRunTimeout, Cancelled…). Every tool, from tkn to Dashboard, reads this one field.
status.conditions[type=Succeeded]kubectl wait --for=condition=Succeeded↑ see it in the visualization - Admission webhook
An HTTPS hook the API server calls before storing an object: mutating (defaulting) first, then validating.
Tekton's webhook rejects bad YAML (missing image, unknown variable, gated field) when you apply it, not minutes later when a Pod fails. It also fills defaults such as timeout and serviceAccountName.
validation.webhook.pipeline.tekton.devwebhook.pipeline.tekton.devconfig-defaults↑ see it in the visualization - config-defaults
A ConfigMap in tekton-pipelines holding cluster-wide defaults applied by the defaulting webhook.
default-timeout-minutes (60) and default-service-account (default) are why a TaskRun you never gave a timeout still has one.
default-timeout-minutesdefault-service-accountdefault-pod-template↑ see it in the visualization - feature-flags ConfigMap
The ConfigMap that switches Tekton behaviours and API fields on or off for the whole installation.
It controls stability gates and runtime choices such as await-sidecar-readiness, results-from and coschedule. Two clusters with different flags can accept different YAML.
kubectl edit cm feature-flags -n tekton-pipelines↑ see it in the visualization - enable-api-fields
The flag that sets which API fields the webhook accepts: stable, beta (the shipped default) or alpha.
Alpha fields may change or disappear; beta fields follow a gentler policy; stable fields are covered by the v1 compatibility promise. Gating them per cluster keeps production on a known surface.
enable-api-fields: betaenable-param-enumenable-cel-in-whenexpression↑ see it in the visualization - tekton.dev/v1
The stable API version for Task, TaskRun, Pipeline and PipelineRun; v1beta1 is still served and converted by the webhook.
v1 dropped PipelineResources and renamed some fields (a step's resources became computeResources). Writing v1 YAML is what makes it portable across conformant clusters.
apiVersion: tekton.dev/v1conversion webhook↑ see it in the visualization - tekton-pipelines namespace
Where the Pipelines controller, webhook, resolvers and their ConfigMaps run.
Tekton is installed with one kubectl apply of release.yaml (or by the Operator); nothing runs outside the cluster.
kubectl get pods -n tekton-pipelinestekton-pipelines-resolvers↑ see it in the visualization - A Pod per TaskRun
Every TaskRun executes as one ordinary Pod that the Kubernetes scheduler places on a node.
There are no long-lived build agents to size, patch or keep warm: capacity is the cluster's, it bin-packs by resource request, and it scales with the node pool.
↑ see it in the visualization - Tekton components
Pipelines (core), Triggers, Chains, Results, Dashboard, CLI (tkn), Operator, the catalog/Hub, and Pipelines-as-Code.
Each one is more CRDs and controllers on the same API server. You can adopt Pipelines alone and add the others as you need them.
tektoncd/pipelinetektoncd/triggerstektoncd/chainstektoncd/operator↑ see it in the visualization - Continuous Delivery Foundation
The Linux Foundation project that hosts Tekton, alongside Jenkins, Spinnaker and others.
Vendor-neutral governance is why Tekton serves as the engine under several products (OpenShift Pipelines and Jenkins X among them) and why its API is meant as a common standard.
cd.foundation↑ see it in the visualization