Tekton Pipelines — Architecture#

Architectural style#

Kubernetes Operator (Multi-Controller, Event-Driven, Level-Triggered)

Tekton is a Kubernetes operator built on the Knative reconciler framework. The core execution model is level-triggered reconciliation: controllers watch CRD objects (TaskRun, PipelineRun, ResolutionRequest) via informers, enqueue work items when resources change, and drive each resource toward its desired state in a ReconcileKind loop. There is no central dispatcher or message bus — state is stored entirely in Kubernetes API objects, making the system crash-safe and resumable.

The multi-controller design decomposes the system into single-responsibility controllers: the main controller binary hosts three reconcilers (TaskRun, PipelineRun, ResolutionRequest), a separate webhook binary handles admission, and the resolvers binary hosts five resolver controllers. All binaries share one Go module and vendor tree.

Component diagram (textual)#

┌──────────────────────────────────────────────────────────────────┐
│  cmd/controller                                                   │
│  ┌─────────────────┐  ┌──────────────────┐  ┌────────────────┐  │
│  │ TaskRun          │  │ PipelineRun       │  │ Resolution     │  │
│  │ Reconciler       │  │ Reconciler        │  │ Request        │  │
│  │ pkg/reconciler/  │  │ pkg/reconciler/   │  │ Reconciler     │  │
│  │ taskrun/         │  │ pipelinerun/      │  │                │  │
│  └────────┬─────────┘  └────────┬──────────┘  └───────────────┘  │
│           │                     │                                  │
│     creates Pod           creates TaskRun                         │
│           │               manages DAG                             │
└───────────┼─────────────────────┼──────────────────────────────────┘
            │                     │
            ▼                     ▼
   ┌─────────────────┐  ┌──────────────────────────────┐
   │  Kubernetes Pod  │  │  ResolutionRequest (CRD)      │
   │  ┌────────────┐  │  └──────────────┬───────────────┘
   │  │ init:      │  │                  │
   │  │ entrypoint │  │   cmd/resolvers  ▼
   │  │ (injected) │  │  ┌──────────────────────────────┐
   │  ├────────────┤  │  │ Git / Hub / Bundle / Cluster │
   │  │ step-0     │  │  │ HTTP Resolver Controllers     │
   │  │ (wrapped   │  │  │ pkg/remoteresolution/resolver │
   │  │  by entry  │  │  └──────────────────────────────┘
   │  │  point)    │  │
   │  ├────────────┤  │   cmd/webhook
   │  │ step-1     │  │  ┌──────────────────────────────┐
   │  │  ...       │  │  │ Defaulting + Validation       │
   │  ├────────────┤  │  │ CRD Version Conversion        │
   │  │ sidecar:   │  │  │ (v1alpha1/v1beta1/v1)         │
   │  │ nop / log  │  │  └──────────────────────────────┘
   │  │ results    │  │
   │  └────────────┘  │
   └─────────────────┘

Core components#

TaskRun Reconciler#

  • Package: pkg/reconciler/taskrun
  • Responsibility: Drives a TaskRun object from pending → running → complete. Creates Kubernetes Pods, monitors pod status, extracts step results, stops sidecars on completion, integrates SPIRE signing, emits CloudEvents and metrics.
  • Key types: Reconciler struct (taskrun.go:80), implements taskrunreconciler.Interface
  • Dependencies: pkg/pod (Pod builder), pkg/remoteresolution (Task resolution), pkg/spire (signing), pkg/workspace, pkg/taskrunmetrics, pkg/trustedresources

PipelineRun Reconciler#

  • Package: pkg/reconciler/pipelinerun
  • Responsibility: Drives a PipelineRun through DAG-scheduled task execution. Resolves the Pipeline definition, builds the DAG, identifies schedulable tasks, creates TaskRuns for ready tasks, aggregates results, propagates parameters and workspaces, handles cancellation and timeout.
  • Key types: Reconciler struct (pipelinerun.go), uses dag.Graph for scheduling
  • Dependencies: pkg/reconciler/pipeline/dag, pkg/remoteresolution, pkg/substitution, pkg/workspace, pkg/pipelinerunmetrics

DAG Engine#

  • Package: pkg/reconciler/pipeline/dag
  • Responsibility: Builds a directed acyclic graph from pipeline task dependencies and runAfter declarations. GetCandidateTasks returns the set of tasks whose predecessors have all succeeded, driving scheduling.
  • Key types: Graph, Node, Task interface
  • Dependencies: Pure logic, no Kubernetes dependencies

Pod Builder#

  • Package: pkg/pod
  • Responsibility: Translates a TaskSpec + TaskRun into a Kubernetes Pod spec. Injects the entrypoint binary as an init container, rewrites each step’s command to be wrapped by the entrypoint, mounts workspace volumes, applies credential init, configures resource requests (via pkg/internal/computeresources), creates results sidecars.
  • Key types: Builder struct with Build(ctx, TaskRun, TaskSpec, ...Transformer) (*corev1.Pod, error) at pod.go:150
  • Dependencies: pkg/entrypoint, pkg/workspace, pkg/credentials, pkg/internal/computeresources

Resolution Framework#

  • Package: pkg/remoteresolution (current), pkg/resolution (legacy)
  • Responsibility: Decouples Task/Pipeline definition fetching from execution. Reconcilers submit a ResolutionRequest CRD object; the resolution controller routes it to the appropriate resolver (git, hub, bundle, cluster, http). The Requester interface abstracts this interaction.
  • Key types: Requester interface (resource/request.go:45), Resolver interface (framework/interface.go:29), CRDRequester (concrete implementation)
  • Dependencies: ResolutionRequest CRD, Kubernetes API

Entrypoint Binary#

  • Package: pkg/entrypoint + cmd/entrypoint
  • Responsibility: Injected into every step container to enforce sequential execution within a Pod (normally containers run in parallel). Gates start by polling for a post-file written by the previous step. Writes its own post-file on completion. Extracts step results and handles SPIRE result signing.
  • Key types: Entrypointer struct
  • Dependencies: File system (shared emptyDir volumes), pkg/termination, pkg/spire

Admission Webhook#

  • Package: cmd/webhook + pkg/apis/pipeline/v1{,beta1,alpha1} (webhook logic)
  • Responsibility: Kubernetes admission webhook for defaulting (filling in defaults), validation (rejecting invalid objects), and CRD version conversion between v1alpha1, v1beta1, and v1. Built on Knative’s webhook infrastructure.
  • Key types: All CRD types implement knative.dev/pkg/webhook interfaces (Defaultable, Validatable)
  • Dependencies: knative.dev/pkg/webhook, all CRD type packages

Configuration Store#

  • Package: pkg/apis/config
  • Responsibility: Parses config-defaults and feature-flags ConfigMaps into Go structs. Propagates live config via context.Context using a Store backed by Knative’s configmap.Watcher. Reconcilers call config.FromContext(ctx) to read current config.
  • Key types: Store, Defaults, FeatureFlags
  • Dependencies: knative.dev/pkg/configmap

Data flow#

Typical PipelineRun execution trace:

1. User applies PipelineRun YAML → Kubernetes API server
2. PipelineRun Reconciler enqueued by informer event
3. ReconcileKind: fetch VerificationPolicies, build getPipelineFunc
4. Submit ResolutionRequest CRD for Pipeline definition (if remote)
   → Resolution controller routes to git/bundle/cluster resolver
   → Resolver fetches YAML, writes ResolvedResource to ResolutionRequest status
5. Reconciler reads resolved Pipeline; builds DAG (dag.Build)
6. dag.GetCandidateTasks → identifies tasks with no pending predecessors
7. For each schedulable task:
   a. Submit ResolutionRequest for Task definition (if remote)
   b. Apply parameter substitution (pkg/substitution)
   c. Bind workspaces (pkg/workspace)
   d. Create TaskRun object → Kubernetes API server
8. TaskRun Reconciler enqueued for each new TaskRun
9. ReconcileKind: resolve Task definition (via ResolutionRequest)
   → Verify trusted resource (pkg/trustedresources, Sigstore)
10. pod.Builder.Build(ctx, taskRun, taskSpec):
    a. Rewrite step commands → entrypoint wrappers
    b. Inject entrypoint init container (copies binary into Pod)
    c. Mount workspace volumes
    d. Add creds-init volume/container
    e. Create results sidecar (sidecarlogresults)
    → Returns corev1.Pod spec
11. Create Pod → Kubernetes API server
12. Inside Pod:
    a. entrypoint init container copies binary to /tekton/bin/entrypoint
    b. step-0 container starts: entrypoint polls for "start file" (already present for first step)
    c. step-0 executes user command, writes results to /tekton/results/
    d. entrypoint writes post-file for step-0
    e. step-1 entrypoint sees post-file → unblocked → runs
    f. ... sequential execution enforced by file semaphores
13. TaskRun reconciler watches Pod status changes:
    a. Reads termination messages → extract step results
    b. SPIRE: sign results if configured
    c. Updates TaskRun status with step statuses and results
14. PipelineRun reconciler sees TaskRun status change (via informer):
    a. Updates DAG: marks task as done
    b. Runs dag.GetCandidateTasks again → new schedulable tasks unlocked
    c. Repeat from step 7 for newly schedulable tasks
15. When all tasks done: aggregate pipeline results, update PipelineRun status
16. Metrics recorded (Prometheus); CloudEvents emitted; OTel spans closed

Initialization / Bootstrap#

cmd/controller/main.go uses Knative’s sharedmain.MainWithConfig:

  1. Flag parsing: injection.ParseAndGetRESTConfigOrDie() — parses Kubernetes in-cluster config and custom flags (image refs, thread counts, namespace scope).
  2. Context setup: signals.NewContext() for graceful shutdown; filteredinformerfactory.WithSelectors to scope informers by label.
  3. Controller factories: Three factory functions are passed to sharedmain:
    • taskrun.NewController(opts, clock)
    • pipelinerun.NewController(opts, clock)
    • resolutionrequest.NewController(clock)
  4. Knative sharedmain: Initializes informer factories, sets up leader election (HA), starts the work queue, and calls each factory with (ctx, configmap.Watcher).
  5. Per-controller init:
    • Extracts clients/informers from ctx (Knative injection pattern)
    • Creates config.Store and calls WatchConfigs(cmw) to watch live ConfigMaps
    • Registers event handlers: informers enqueue items when resources change
    • Returns controller.Impl — the work queue and reconciler wrapper

Dependency injection pattern: Manual construction via Knative’s context-based injection. Clients and informers are registered into ctx before controllers are created; controllers extract them with typed Get(ctx) functions (e.g., kubeclient.Get(ctx), taskruninformer.Get(ctx)). No wire or dig — it is a form of service locator via context.Context.

Configuration#

  • Mechanism: Kubernetes ConfigMaps watched live. Two main ConfigMaps: config-defaults (default values for TaskRuns/PipelineRuns) and feature-flags (experimental feature toggles).
  • Access pattern: config.FromContext(ctx) inside reconcilers returns the current Config struct. The config.Store (Knative configmap.Store) updates the config on every ConfigMap change without restart.
  • Binary configuration: Command-line flags for image references (--entrypoint-image, --nop-image, etc.) and controller tuning (--threads-per-controller, --namespace). Environment variable THREADS_PER_CONTROLLER also accepted.
  • No Viper: Config is loaded via Knative configmap machinery, not Viper.

Key design decisions#

  1. Knative reconciler framework as the foundation: By building on knative.dev/pkg, Tekton inherits production-grade controller infrastructure (work queues, leader election, informer caching, configmap watching, metrics, structured logging). This was a deliberate early choice that accelerated development but creates a tight coupling to Knative’s opinionated patterns (context-based injection, configmap stores).

  2. File-based step sequencing within Pods: Kubernetes Pods run all containers concurrently. Tekton needs sequential steps. Rather than using Kubernetes Jobs (which create separate Pods per step, incurring scheduling overhead), the entrypoint binary enforces ordering within a single Pod using shared emptyDir volumes as semaphores. Each step waits for the previous step’s post-file before executing. This is architecturally unusual but avoids O(N) pod scheduling for N-step tasks.

  3. CRD-based resolution as an async protocol: Remote resource resolution is modeled as a CRD (ResolutionRequest) rather than an in-process fetch. A reconciler creates the CRD, the resolution controller picks it up and writes the result back into the CRD status, and the original reconciler reads it on the next reconcile cycle. This asynchronous, level-triggered approach makes resolution crash-safe and observable, at the cost of extra API server round-trips.

  4. DAG-first pipeline scheduling: PipelineRun execution is driven purely by the DAG — tasks run as soon as their predecessors complete, maximizing parallelism. The reconciler re-evaluates the DAG on every TaskRun status change. There is no central scheduler or queue: the Kubernetes informer triggers the reconciler, which queries the DAG for what’s schedulable. This aligns naturally with the level-triggered reconciliation model.

  5. Supply chain security baked in (not bolted on): SPIRE/SPIFFE workload identity and Sigstore verification are first-class components, not plugins. The pkg/spire and pkg/trustedresources packages are imported directly by the reconcilers. This means trusted resource verification and result signing are enforced in the hot path of every TaskRun reconcile cycle when enabled via feature flags — a strong security posture but one that increases reconciler complexity and adds external service dependencies (SPIRE agent, KMS).