Operating Tekton
terminal · tkn
kubectl context: dev · ns defaultTekton Dashboard
PipelineRuns · defaultTry: start a run, then follow it with tkn pr logs -f --last and watch the same lines appear in the Dashboard. Tick "a unit test fails", start another, and run tkn pr describe --last: build-image shows up under Skipped Tasks. Start a third and cancel it mid-run.
Retention: runs accumulate in etcd until something deletes them
ops-pruningops-keep-sincetekton-pruneretcd: size of PipelineRuns + TaskRuns + Pods over 30 days
apiVersion: operator.tekton.dev/v1alpha1kind: TektonConfigmetadata:name: configspec:pruner:disabled: true
Try: with no pruning, slide runs/day to 1000 and watch etcd cross its quota. Then pick keep N = 100 on a daily schedule and notice the sawtooth: objects pile up for 24 h between CronJob runs. Switch to hourly or to the event-driven TTL pruner to flatten it.
cluster (etcd)
0 PipelineRunsblue = running · amber = finished, not yet archived · grey = archived, waiting out the grace period
results watcher
Results API → PostgreSQL
0 recordslogs storage (S3 / GCS / PVC)
query history
$ kubectl get pipelineruns No resources found in default namespace.
- -api_addr=tekton-results-api-service:8080- -completed_run_grace_period=10s- -logs_api=true
Try: press Play and watch runs leave the cluster once archived, while the Results API answers for all of them. Then tick "Results API down": finished runs stay in etcd (amber) because the watcher never deletes what it hasn't stored. Bring it back and the backlog drains.
apiVersion: operator.tekton.dev/v1alpha1kind: TektonConfigmetadata:name: configspec:profile: basictargetNamespace: tekton-pipelinespipeline:enable-api-fields: betapruner:keep: 100schedule: "0 8 * * *"
what the operator owns
reconciling…operator log
Try: switch lite → all and watch the operator create one component CR at a time, each fanning out to its Deployments. Delete the pipelines controller by hand: the next reconcile puts it back. Press Upgrade and every Deployment rolls to the new release, component by component.
Pipelines-as-Code: the repo carries its own CI
pipelines-as-codepac-tekton-dirpac-on-eventpac-gitops-commandspac-checksapiVersion: tekton.dev/v1kind: PipelineRunmetadata:generateName: api-pull-request-annotations:pipelinesascode.tekton.dev/on-event: "[pull_request]"pipelinesascode.tekton.dev/on-target-branch: "[main]"pipelinesascode.tekton.dev/max-keep-runs: "5"spec:params:- name: revisionvalue: "{{ revision }}"pipelineRef:name: build
GitHub · checks
pipelines-as-code controller
Try: open PR #42 and watch the flaky failure land as a red check; comment /retest and only the failed one reruns. Open dana's PR from a fork: nothing runs until a maintainer says /ok-to-test (dana can't approve herself). PR #44 targets release-1.3, so no file matches.
running_pipelineruns (gauge)
pipelinerun_duration_seconds (per bucket)
Prometheus stores cumulative le buckets; quantiles are interpolated from them, not measured exactly.
what you'd see
# HELP tekton_pipelines_controller_pipelinerun_duration_seconds The pipelinerun execution time in seconds
# TYPE tekton_pipelines_controller_pipelinerun_duration_seconds histogram
tekton_pipelines_controller_pipelinerun_duration_seconds_bucket{namespace="default",pipeline="build",status="success",le="30"} 0
tekton_pipelines_controller_pipelinerun_duration_seconds_bucket{namespace="default",pipeline="build",status="success",le="60"} 0
tekton_pipelines_controller_pipelinerun_duration_seconds_bucket{namespace="default",pipeline="build",status="success",le="120"} 0
tekton_pipelines_controller_pipelinerun_duration_seconds_bucket{namespace="default",pipeline="build",status="success",le="300"} 0
tekton_pipelines_controller_pipelinerun_duration_seconds_bucket{namespace="default",pipeline="build",status="success",le="600"} 0
tekton_pipelines_controller_pipelinerun_duration_seconds_bucket{namespace="default",pipeline="build",status="success",le="+Inf"} 0
tekton_pipelines_controller_pipelinerun_duration_seconds_bucket{namespace="default",pipeline="build",status="failed",le="30"} 0
tekton_pipelines_controller_pipelinerun_duration_seconds_bucket{namespace="default",pipeline="build",status="failed",le="60"} 0
tekton_pipelines_controller_pipelinerun_duration_seconds_bucket{namespace="default",pipeline="build",status="failed",le="120"} 0
tekton_pipelines_controller_pipelinerun_duration_seconds_bucket{namespace="default",pipeline="build",status="failed",le="300"} 0
tekton_pipelines_controller_pipelinerun_duration_seconds_bucket{namespace="default",pipeline="build",status="failed",le="600"} 0
tekton_pipelines_controller_pipelinerun_duration_seconds_bucket{namespace="default",pipeline="build",status="failed",le="+Inf"} 0
tekton_pipelines_controller_pipelinerun_duration_seconds_sum{namespace="default",pipeline="build"} 0
tekton_pipelines_controller_pipelinerun_duration_seconds_count{namespace="default",pipeline="build"} 0
# TYPE tekton_pipelines_controller_running_pipelineruns gauge
tekton_pipelines_controller_running_pipelineruns 0Try: Play for a while, then tick "image registry is slow". The running gauge climbs first (Little's law: same arrivals, longer stays), then completions shift into the 300 s+ buckets and p95 turns red. That is the alert you want, before users notice.
Scaling the controller: leader election buckets and API rate limits
ops-leader-electionops-controller-qpsleases (one per bucket)
reconcile keys → bucket = hash(key) mod buckets
performance:disable-ha: falsereplicas: 3buckets: 1threads-per-controller: 2# config-leader-election# lease-duration: 60s# retry-period: 10s
Try: with 1 bucket and 3 replicas only one pod works; kill it and every key stalls for about a minute until a standby's lease attempt succeeds. Set buckets to 6: work spreads across all replicas, and killing one stalls only its share of keys.
PipelineRuns fully reconciled over time
performance:threads-per-controller: 2kube-api-qps: 5kube-api-burst: 10
Try: at the defaults, 300 runs take minutes because the client-side rate limiter (qps 5) caps API calls; adding threads does nothing. Raise kube-api-qps to 50 and the bottleneck moves to threads; raise those too. Burst only helps the first few seconds. (Rough model: each run needs ~6 reconciles of ~2 API calls each.)
- takeawaytkn, the Dashboard and kubectl all read the same objects. Runs live in etcd, so they need a retention policy (prune) and, if you care about history, an archive (Results) before deletion.
- takeawayLet controllers do the operating: the Operator reconciles installs and upgrades from one TektonConfig, and Pipelines-as-Code turns Git events and PR comments into PipelineRuns and check runs.
- takeawayWatch duration histograms and running gauges. When load grows, the API rate limiter is usually the first bottleneck; buckets let several replicas share the work and shrink the impact of a crash.
Key concepts · 20
book chapter 12- tkn CLI
The Tekton command-line client: start, list, describe, follow logs of, cancel and delete Tekton objects.
It is a convenience layer over the Kubernetes API. Everything it does is reading or writing the same CRDs kubectl sees, plus following Pod container logs in DAG order.
tkn pipeline starttkn pr logs -f --lasttkn tr describetkn pr canceltkn pr delete --keep 5↑ see it in the visualization - tkn bundle push
Packages Tasks and Pipelines into an OCI image (a Tekton Bundle) and pushes it to a registry.
Bundles version and distribute pipeline definitions like container images; the bundles resolver fetches them by digest at run time.
tkn bundle pushtkn bundle listresolver: bundles↑ see it in the visualization - Tekton Dashboard
A web UI that lists PipelineRuns and TaskRuns and shows their tasks, steps and logs.
It reads the same live objects as tkn. When an object is pruned it disappears from the Dashboard too, unless logs and records are served from an external store like Results.
tekton-dashboard--read-only--external-logs↑ see it in the visualization - Pruning
Deleting finished PipelineRuns and TaskRuns (and with them their Pods) so they don't accumulate in etcd.
Every run leaves several status-heavy objects behind. Without cleanup, etcd grows until it hits its quota and the API server starts refusing writes, which breaks far more than CI.
TektonConfig spec.prunertkn pr delete --keepetcd --quota-backend-bytes↑ see it in the visualization - keep / keep-since
The operator pruner's two retention rules: keep the newest N runs, or keep runs younger than M minutes. They are mutually exclusive.
The operator implements them as a CronJob, so objects still pile up between schedule firings. Keep N bounds the count; keep-since bounds age but lets busy days grow large.
spec.pruner.keepspec.pruner.keep-sincespec.pruner.schedulespec.pruner.resources↑ see it in the visualization - tekton-pruner
A newer, event-driven pruner controller that deletes runs based on TTL after completion and per-status history limits.
Reacting to completions instead of a schedule removes the CronJob sawtooth and allows global, namespace or per-pipeline policies.
ttlSecondsAfterFinishedsuccessfulHistoryLimitfailedHistoryLimittekton-pruner-default-spec↑ see it in the visualization - Tekton Results
A service that stores finished PipelineRuns and TaskRuns as Results and Records in a database, with an API to query them.
It separates history from live cluster state: once a run is archived it can be deleted from etcd and still be queried, audited and shown in UIs.
results.tekton.dev/v1alpha2parents/{ns}/results/{uid}/recordsPostgreSQLtkn-results↑ see it in the visualization - Results watcher
The Results controller that watches runs in the cluster and sends each one to the Results API over gRPC, plus its logs.
It only deletes an object after it was stored and a grace period passed, so an outage of the API makes objects linger instead of losing history.
tekton-results-watcher-completed_run_grace_periodresults.tekton.dev/record annotation↑ see it in the visualization - Logs storage
Where Results keeps step logs after the Pods are gone: object storage like S3 or GCS, or a PVC.
Pod logs vanish with the Pod. Archiving them is what keeps a failed build debuggable a week later.
LOGS_APILOGS_TYPE=S3|GCS|File↑ see it in the visualization - Tekton Operator
A Kubernetes operator that installs, configures and upgrades Tekton components from a single TektonConfig resource.
It reconciles continuously: delete a controller by hand and it comes back; bump the operator and every component rolls forward together.
TektonConfigTektonPipelineTektonTriggerTektonChainTektonDashboardTektonInstallerSet↑ see it in the visualization - TektonConfig profiles
spec.profile picks which components the operator installs: lite (Pipelines), basic (adds Triggers and Chains) or all (adds the Dashboard).
One field decides the footprint. Changing it adds or removes component CRs, and the operator handles the rest.
profile: liteprofile: basicprofile: alltargetNamespace↑ see it in the visualization - Pipelines-as-Code
A Tekton project that runs PipelineRuns kept in the repository's .tekton/ directory in response to Git provider events.
Pipeline definitions are versioned and reviewed with the code they build, and a PR can change its own CI.
Repository CRGitHub Apptkn pac{{ revision }}↑ see it in the visualization - .tekton directory
The folder Pipelines-as-Code scans on each event for PipelineRun YAML files to match.
Each file is one candidate PipelineRun; annotations on it decide whether it runs for this event.
.tekton/pull-request.yaml.tekton/push.yaml↑ see it in the visualization - on-event / on-target-branch
Annotations that match a PipelineRun to an event type and target branch (globs allowed); on-cel-expression allows richer matching.
Matching happens before anything is created, so unmatched events cost nothing and you can keep separate runs for PRs, merges and tags.
pipelinesascode.tekton.dev/on-eventpipelinesascode.tekton.dev/on-target-branchon-cel-expressionmax-keep-runs↑ see it in the visualization - GitOps commands
PR comments that drive CI: /retest, /test <name>, /cancel, and /ok-to-test to approve a run for an untrusted author.
Running a fork's PR means running a stranger's code with your credentials, so it waits for a maintainer; comments from users outside the ACL are ignored.
/retest/ok-to-test/test <pipelinerun>/cancelOWNERS↑ see it in the visualization - Status as GitHub Checks
Pipelines-as-Code reports each PipelineRun back to the commit as a check run (or commit status), with a summary of its tasks.
Developers see CI where they already are, and branch protection rules can require the checks to pass before merge.
Check Runs APIcommit status↑ see it in the visualization - Controller metrics
Prometheus metrics exposed by the controller, such as pipelinerun_duration_seconds (histogram) and running_pipelineruns (gauge), all prefixed tekton_pipelines_controller_.
Duration percentiles and running counts are the first signal of a slow registry, an overloaded cluster or a stuck controller. config-observability controls levels and histogram vs last-value.
…_pipelinerun_duration_seconds…_running_pipelinerunsconfig-observabilityhistogram_quantile↑ see it in the visualization - Events and controller logs
Kubernetes Events the controller emits on runs (Started, Running, Succeeded, Failed) and its structured JSON logs keyed by namespace/name.
Events explain one run's story; logs explain what the controller decided and why. Both are short-lived, so ship them somewhere if you need them later.
kubectl get eventsknative.dev/keyconfig-logging↑ see it in the visualization - HA leader election (buckets)
Running several controller replicas that split the key space into buckets, each guarded by a Lease that one replica holds at a time.
With one bucket only one replica works and the rest wait. More buckets spread the load, and a crash stalls only the dead replica's share until its leases expire.
config-leader-electionbucketslease-durationcoordination.k8s.io/Lease↑ see it in the visualization - Controller QPS, burst and threads
Performance knobs: kube-api-qps and kube-api-burst limit the controller's client-side API rate; threads-per-controller sets workqueue workers.
Defaults are conservative. Under a spike of runs the rate limiter, not CPU, is usually the bottleneck, so raise QPS and burst before adding threads.
-kube-api-qps-kube-api-burst-threads-per-controllerspec.pipeline.performance↑ see it in the visualization