How to Manage Events with a Dynamic Informer in Kubernetes
Kubernetes operators and controllers rely heavily on informers to watch and react to changes in the cluster. While static informers work well for known resource types, dynamic informers are essential when dealing with Custom Resource Definitions (CRDs) or multiple unknown resources at runtime.
Rafay Siddiquie
July 7, 2025

In this guide, you’ll learn:
✔ What a dynamic informer is and how it differs from a static informer.
✔ How to set up a dynamic informer to watch Kubernetes events.
✔ Best practices for managing multiple resources efficiently.
✔ Real-world Golang code examples for dynamic informers.
✔ Common pitfalls and how to avoid them.
By the end, you’ll be able to implement a production-ready dynamic informer that scales with your Kubernetes workloads.
1. What is a Dynamic Informer in Kubernetes?
A dynamic informer is a Kubernetes client-go component that watches unstructured resources (like CRDs) without requiring compile-time schema definitions. Unlike static informers (which require predefined types), dynamic informers use unstructured.Unstructured
to handle arbitrary Kubernetes objects.
Key Use Cases for Dynamic Informers
✅ Watching Custom Resource Definitions (CRDs) dynamically.
✅ Monitoring multiple resource types with a single informer.
✅ Building generic controllers that adapt to new CRDs at runtime.
Dynamic Informer vs. Static Informer
Feature | Static Informer | Dynamic Informer |
---|---|---|
Resource Type | Predefined (e.g., v1.Pod ) | Any (unstructured.Unstructured ) |
Flexibility | Limited to known types | Works with CRDs and unknown resources |
Performance | Optimized for known schemas | Slightly slower due to runtime type checks |
Use Case | Built-in resources (Pods, Deployments) | Custom resources, multi-resource watchers |
2. Setting Up a Dynamic Informer in Golang
To create a dynamic informer, we’ll use:
dynamic.Interface
(fromk8s.io/client-go/dynamic
)informer.GenericInformer
(for unstructured data)cache.SharedIndexInformer
(to cache and index resources)
Step 1: Initialize the Dynamic Client
package main
import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/clientcmd"
)
func main() {
// Load kubeconfig
config, err := clientcmd.BuildConfigFromFlags("", "/path/to/kubeconfig")
if err != nil {
panic(err)
}
// Create dynamic client
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
panic(err)
}
}
Step 2: Define the Resource to Watch (GVR)
Use schema.GroupVersionResource
to specify which resource to watch.
import (
"k8s.io/apimachinery/pkg/runtime/schema"
)
// Example: Watch for changes in a CRD (e.g., "myresources.example.com")
gvr := schema.GroupVersionResource{
Group: "example.com",
Version: "v1",
Resource: "myresources",
}
Step 3: Create the Dynamic Informer
import (
"k8s.io/client-go/informers"
)
// Create a factory for dynamic informers
factory := informers.NewSharedInformerFactoryWithOptions(
dynamicClient,
0, // Resync period (0 = no resync)
informers.WithNamespace("default"), // Optional: Watch a specific namespace
)
// Get a dynamic informer for the GVR
informer := factory.ForResource(gvr)
Step 4: Add Event Handlers
informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
// Handle newly created resource
log.Println("Resource added:", obj)
},
UpdateFunc: func(oldObj, newObj interface{}) {
// Handle updates
log.Println("Resource updated:", newObj)
},
DeleteFunc: func(obj interface{}) {
// Handle deletions
log.Println("Resource deleted:", obj)
},
})
Step 5: Start the Informer
stopCh := make(chan struct{})
defer close(stopCh)
factory.Start(stopCh)
factory.WaitForCacheSync(stopCh)
// Keep the informer running
select {}
3. Advanced Dynamic Informer Techniques
A. Watching Multiple Resources with One Informer
Use dynamicinformer.NewDynamicSharedInformerFactory
to watch multiple CRDs:
import (
"k8s.io/client-go/dynamic/dynamicinformer"
)
factory := dynamicinformer.NewDynamicSharedInformerFactory(dynamicClient, 0)
// Watch multiple GVRs
informer1 := factory.ForResource(gvr1).Informer()
informer2 := factory.ForResource(gvr2).Informer()
// Add event handlers to each
informer1.AddEventHandler(...)
informer2.AddEventHandler(...)
B. Mocking a Dynamic Informer for Testing
Use fake.NewSimpleDynamicClient
for unit tests:
import (
"k8s.io/client-go/dynamic/fake"
)
// Create a fake dynamic client
fakeClient := fake.NewSimpleDynamicClient(runtime.NewScheme())
// Use in tests
factory := dynamicinformer.NewDynamicSharedInformerFactory(fakeClient, 0)
C. Handling Timeouts in Dynamic Informers
To prevent hanging, use a context with timeout:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Start informer with context
go informer.Run(ctx.Done())
4. Best Practices for Dynamic Informers
🔹 Optimize Performance: Use WithNamespace()
to limit scope.
🔹 Error Handling: Always check cache.WaitForCacheSync()
.
🔹 Logging: Use structured logging (zap
, logrus
) for debugging.
🔹 Rate Limiting: Avoid API throttling with RateLimitingQueue
.
5. Common Pitfalls & Solutions
Issue | Solution |
---|---|
Informer not detecting changes | Ensure AddEventHandler is registered before starting. |
Memory leaks | Always close(stopCh) to clean up informers. |
API throttling | Use client-go 's built-in rate limiter. |
Unstructured parsing errors | Validate with unstructured.Unstructured.GetXXX() . |
Conclusion
Dynamic informers are essential for Kubernetes operators that need to watch CRDs or multiple resource types. By following this guide, you can:
✔ Set up a dynamic informer in Golang.
✔ Watch multiple resources efficiently.
✔ Handle edge cases like timeouts and testing.
Now you’re ready to build scalable, event-driven Kubernetes controllers with dynamic informers! 🚀