Open-Source Wikis

/

Istio

/

Systems

/

krt (Kubernetes declarative controller runtime)

istio/istio

krt (Kubernetes declarative controller runtime)

Active contributors: howardjohn, stevenctl, hzxuzhonghu, keithmattix

Purpose

krt is Istio's home-grown declarative controller framework. It provides a way to write controllers as func(input) -> output transformations and lets the framework manage state, dependencies, and event delivery. The framework was added to the codebase in December 2023 (Initial krt library, ("Building Better Controllers") (#47891)) specifically to support Ambient mode's cross-cutting state machines, and it has since become the substrate for new controller code throughout the project.

The package is pkg/kube/krt/. It has its own README with the original design doc and a KubeCon talk linked from the top.

Why it exists

Hand-written Kubernetes controllers all share the same shape: an informer per type, a workqueue, a reconciler that pulls from the queue and reads the current state from informers. That pattern works for "watch X, do Y", but it falls apart when you need to compose state from many sources. The Ambient registry needs to combine Pods, Services, ServiceEntries, Gateway API resources, namespaces, AuthorizationPolicies, and a half-dozen others into a single denormalized output. Doing that with workqueues meant manual dependency tracking, manual fan-out, and manual deduplication.

krt replaces that pattern with declarative transforms:

ConfigMapCount := krt.NewSingleton[int](func(ctx krt.HandlerContext) *int {
    cms := krt.Fetch(ctx, ConfigMaps)
    return ptr.Of(len(cms))
})

The framework tracks that ConfigMapCount depends on ConfigMaps. When ConfigMaps changes, ConfigMapCount recomputes automatically; consumers get an event with the new value.

Directory layout

pkg/kube/krt/
├── README.md            # The user-facing intro
├── core.go              # Collection interface, Event, HandlerContext
├── collection.go        # The default Collection implementation (~900 lines)
├── informer.go          # Build a Collection from a Kubernetes informer
├── static.go            # Build a Collection from a static slice
├── singleton.go         # Single-value collections
├── join.go              # Join two collections by key
├── mergejoin.go         # Merge collections with optional types
├── nestedjoinmerge.go   # Nested joins (rare; powerful)
├── map.go               # Map a Collection through another function
├── index.go             # Secondary indexes
├── filter.go            # Fetch filters (by name, labels, generic predicate)
├── fetch.go             # Fetch operation
├── recomputetrigger.go  # Manual triggers (escape hatch)
├── status.go            # Status reconciliation helpers
├── helpers.go / processor.go / sync.go / options.go
├── debug.go             # Runtime introspection
├── assert_*.go          # Optional invariant assertions
└── krttest/             # Tests helpers (TestController etc.)

Key abstractions

Symbol File Role
Collection[T] core.go The central interface: an observable set of T
Singleton[T] singleton.go A Collection of one
HandlerContext core.go Passed to transforms; tracks dependencies
Fetch[T] fetch.go Read inputs and register a dependency
NewCollection[I, O] collection.go One-to-one: each I becomes one O (or none)
NewManyCollection[I, O] collection.go One-to-many
NewSingleton[O] singleton.go Pure: depends only on Fetch'd collections
WrapClient[T] informer.go Wrap a Kubernetes informer
JoinCollection, Map, MergeJoin, NestedJoinMerge various Composition combinators
Index index.go Secondary index over a Collection

How it works

graph LR
    inf[Kube informer] --> wrap[WrapClient]
    wrap --> coll[Collection]
    coll --> fetch[Fetch in handler]
    fetch --> tx[Transform fn]
    tx --> derived[Derived Collection]
    derived --> ev[Event handlers]
    classDef in fill:#cef,stroke:#08f
    classDef out fill:#fec,stroke:#f80
    class inf,coll in
    class derived,ev out

Object requirements

Every object T in a Collection[T] needs:

  • A unique Key[T] (a typed string). Default implementations exist for Kubernetes types, Istio config.Config, and any type with a ResourceName() string.
  • An Equals(T) bool method. Default implementations exist for Kubernetes objects (compares ResourceVersion), protobuf messages (compares marshaled output), and any other type via reflect.DeepEqual.
  • Optionally a Name, Namespace, Labels, and LabelSelector for filter support.

Three transform shapes

// One-to-one: pod → workload (or nil)
Workloads := krt.NewCollection[Pod, Workload](Pods, func(ctx krt.HandlerContext, p Pod) *Workload {
    ns := krt.FetchOne(ctx, Namespaces, krt.FilterKey(p.Namespace))
    if ns == nil || !ns.AmbientEnabled() {
        return nil
    }
    return &Workload{Name: p.Name, Identity: p.SAIdentity()}
})

// One-to-many: service → list of endpoints
Endpoints := krt.NewManyCollection[Service, Endpoint](Services, func(ctx krt.HandlerContext, s Service) []Endpoint {
    pods := krt.Fetch(ctx, Pods, krt.FilterLabel(s.Selector))
    return mapPodsToEndpoints(pods)
})

// Pure: depends only on what Fetch reads
Total := krt.NewSingleton[int](func(ctx krt.HandlerContext) *int {
    n := len(krt.Fetch(ctx, Workloads))
    return &n
})

Dependency tracking

Each Fetch call records a dependency on the source collection. When a fetch is filtered (e.g. FilterLabel), the framework remembers the filter so it can decide whether a given input change should re-run the transform. This is what makes krt cheaper than naïve "recompute everything on every change" — most events touch only a handful of dependent transforms.

Status

status.go provides the pattern Istio uses to reconcile status fields back to a CR: declare a StatusCollection keyed by the CR, compute the desired status as a transform, and the framework writes it back via the kube client. This replaced ad-hoc status writers in several controllers.

Static and singleton

For controllers that need an in-process source of truth (e.g. mesh config), NewStatic produces a Collection from a slice and exposes setters. Tests use this heavily.

Where it's used

The biggest user is the Ambient registry. Other adopters include:

  • The Kubernetes Gateway API translator (pilot/pkg/config/kube/gateway/).
  • Multicluster controller (pkg/kube/multicluster/).
  • Status reconcilers (pilot/pkg/status/).
  • The ipallocate controller (pilot/pkg/controllers/ipallocate/).
  • pilot/pkg/xds/krtxds.go — feeds krt-derived data into the xDS push pipeline.

Sidecar-mode networking still uses hand-written informers and queues. There is no plan to rewrite that code; the rule of thumb is "use krt for new state machines".

Limitations

  • Generic constraints are not expressed in the Go type system. The Key/Equals requirements are enforced at runtime, not compile time.
  • The framework is single-process. There is no distributed equivalent.
  • Stage-by-stage debugging requires the debug.go introspection endpoints; printf debugging is harder than with a queue-based controller.
  • For "fire and forget" controllers (read X, do Y, no derived state), informers + workqueues are still simpler.

Performance

krt's bench suite is in pkg/kube/krt/bench_test.go. The framework adds overhead vs hand-written informers — typically tens of microseconds per event in steady state — but the trade-off is worth it for any controller with 3+ inputs. The Ambient registry, with about a dozen sources, would be substantially more code without it.

Entry points for modification

  • New combinator → look at join.go / mergejoin.go for examples; add tests in the same package.
  • New filter — filter.go. Filters compose via FilterAnd / FilterOr.
  • Performance tuning — the hot path is Collection.run() in collection.go. Profile under bench_test.go.

Key source files

File Purpose
pkg/kube/krt/README.md The intro
pkg/kube/krt/core.go Public interface
pkg/kube/krt/collection.go Default Collection impl
pkg/kube/krt/informer.go Kube-informer adapter
pkg/kube/krt/singleton.go Single-value collections
pkg/kube/krt/join.go Join combinator
pkg/kube/krt/index.go Secondary indexes
pkg/kube/krt/fetch.go Fetch operation
pkg/kube/krt/conformance_test.go The cross-implementation conformance suite
pkg/kube/krt/bench_test.go Microbenchmarks

See also

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

krt (Kubernetes declarative controller runtime) – Istio wiki | Factory