Cache Architecture Analysis¶
The cache analyzer is one of the most impactful features. It cross-references controller-runtime cache configuration against controller watches and deployment memory limits to detect OOM risks.
Why this matters¶
Kubernetes operators use controller-runtime's informer cache to watch resources. By default, the cache watches resources cluster-wide. In large clusters with thousands of resources, this can consume gigabytes of memory, causing OOM kills.
The analyzer has caught real production bugs:
What it detects¶
| Finding | Severity | Description |
|---|---|---|
| Cluster-wide informers | Warning | Types watched cluster-wide that should be namespace-scoped or filtered |
| Missing cache filters | Warning | Watched types without ByObject cache filters |
| Implicit informers | Info | client.Get calls for types not in the watch list (creates hidden informers) |
| Missing DefaultTransform | Warning | No cache.DefaultTransform configured (managedFields waste memory) |
| Missing GOMEMLIMIT | Warning | No GOMEMLIMIT in deployment (Go GC can't pressure-tune) |
| GOMEMLIMIT > 90% | Warning | GOMEMLIMIT exceeds 90% of container memory limit |
How it works¶
The cache analyzer runs after deployments and controller watches are extracted. It performs a 4-way cross-reference:
flowchart TB
subgraph Inputs
WATCHES["Controller Watches\n(For/Owns/Watches)"]
CACHE["Cache Config\n(cache.Options)"]
DEPLOY["Deployment\n(resource limits)"]
CODE["Go Source\n(client.Get calls)"]
end
WATCHES --> ANALYZE["Cache Analyzer"]
CACHE --> ANALYZE
DEPLOY --> ANALYZE
CODE --> ANALYZE
ANALYZE --> FINDINGS["Findings"]
classDef input fill:#3498db,stroke:#2980b9,color:#fff
classDef analyze fill:#e74c3c,stroke:#c0392b,color:#fff
class WATCHES,CACHE,DEPLOY,CODE input
class ANALYZE analyze
Step 1: Extract cache configuration¶
From Go source code, the analyzer finds ctrl.NewManager and cache.Options:
// Detected patterns
ctrl.NewManager(cfg, ctrl.Options{
Cache: cache.Options{
ByObject: map[client.Object]cache.ByObject{
&corev1.ConfigMap{}: {Field: ...},
},
DefaultTransform: cache.TransformStripManagedFields(),
},
})
Step 2: Extract controller watches¶
From controller SetupWithManager functions:
Step 3: Cross-reference¶
For each watched type:
- Is there a
ByObjectcache filter? If not, it's a cluster-wide informer. - Is the type namespace-scoped? If yes and watched cluster-wide, flag as OOM risk.
- Are there
client.Getcalls for unwatched types? These create implicit informers.
Step 4: Check deployment limits¶
- Is
GOMEMLIMITset in the deployment? - If set, is it less than 90% of the container memory limit?
- What's the total memory limit vs. estimated cache memory?
Example output¶
{
"cache_config": {
"scope": "cluster",
"filtered_types": ["ConfigMap"],
"disabled_types": [],
"implicit_informers": [
{
"type": "Namespace",
"source": "controllers/dsc_controller.go:145",
"reason": "client.Get call for unwatched type"
}
],
"gomemlimit": "",
"container_memory_limit": "1Gi",
"default_transform": false,
"findings": [
{
"severity": "warning",
"message": "Missing GOMEMLIMIT in deployment - Go GC cannot pressure-tune memory usage",
"recommendation": "Set GOMEMLIMIT to 80-90% of container memory limit"
},
{
"severity": "warning",
"message": "Missing DefaultTransform - managedFields consuming extra memory in cache",
"recommendation": "Add cache.DefaultTransform to strip managedFields from cached objects"
},
{
"severity": "info",
"message": "Implicit informer for Namespace via client.Get at controllers/dsc_controller.go:145",
"recommendation": "Add Namespace to controller watches or use uncached client for this call"
}
]
}
}
Limitations¶
- The analyzer uses tree-sitter for Go parsing, not full type resolution. Complex cache configurations using variables or factory functions may not be detected.
- Implicit informer detection is based on
client.Getcall patterns and may produce false positives for calls using uncached clients. - Memory estimation is approximate. Actual memory consumption depends on cluster size, resource count, and field sizes.