A deep dive into how controller-runtime's cache works internally for Kubernetes controller authors. The cache is built on client-go primitives — Reflector, DeltaFIFO, and Indexer — and is the foundational operating model, not an optimization. Key insights: r.Get() and r.List() read from an in-memory store populated via list+watch, never hitting the API server directly; writes go straight to the API server; and the cache is fully warmed before the first Reconcile runs. The post covers common mistakes (expecting read-after-write consistency, mutating shared objects in predicates/handlers, misunderstanding resync vs relist), how to use IndexField for O(1) lookups instead of O(n) scans, selective caching with namespace/label/field selectors and Transform to reduce memory, PartialObjectMetadata for metadata-only watches, and when to use APIReader for guaranteed freshness.

32m read timeFrom kubernetes.io
Post cover image
Table of contents
TL;DRA bit of context: what a reconciliation loop isWhy the cache exists in controller-runtime at allGlossaryAnatomy: what lives under the cache packageWhat happens at startup and on the very first r.GetClient ≠ Cache: read from memory, write to the API serverCommon mistakes that everyone makescache + index = almost SQLSelective cache: do not pull the whole cluster into your controllerMetadata-only: when spec and data are not neededAPIReader: when the cache is not enoughGood practicesWrapping up
595 Impressions