Skip to content

Commit c6bb7f8

Browse files
authored
Add Deployment informer to replace direct API calls (#68)
* add deployemnt informer in place of direct API call Signed-off-by: Brian DeHamer <bdehamer@github.com> * clarify ClusterRole permission reqs in README Signed-off-by: Brian DeHamer <bdehamer@github.com> --------- Signed-off-by: Brian DeHamer <bdehamer@github.com>
1 parent 3aadf0b commit c6bb7f8

File tree

5 files changed

+25
-15
lines changed

5 files changed

+25
-15
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ which includes:
110110

111111
- **Namespace**: `deployment-tracker`
112112
- **ServiceAccount**: Identity for the controller pod
113-
- **ClusterRole**: Minimal permissions (`get`, `list`, `watch` on pods; `get` on other supported objects)
113+
- **ClusterRole**: Minimal permissions (`get`, `list`, `watch` on pods and deployments; `get` on other supported objects)
114114
- **ClusterRoleBinding**: Binds the ServiceAccount to the ClusterRole
115115
- **Deployment**: Runs the controller with security hardening
116116

@@ -140,6 +140,8 @@ The controller requires the following minimum permissions:
140140
| API Group | Resource | Verbs |
141141
|-----------|----------|-------|
142142
| `""` (core) | `pods` | `get`, `list`, `watch` |
143+
| `apps` | `deployments` | `get`, `list`, `watch` |
144+
| `apps` | `replicasets` | `get` |
143145

144146
If you only need to monitor a single namespace, you can modify the manifest to use a `Role` and `RoleBinding` instead of `ClusterRole` and `ClusterRoleBinding` for more restricted permissions.
145147

deploy/charts/deployment-tracker/templates/clusterrole.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ rules:
1919
- deployments
2020
verbs:
2121
- get
22+
- list
23+
- watch
2224
- apiGroups:
2325
- apps
2426
resources:

deploy/manifest.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ rules:
1919
verbs: ["get", "list", "watch"]
2020
- apiGroups: ["apps"]
2121
resources: ["deployments"]
22-
verbs: ["get"]
22+
verbs: ["get", "list", "watch"]
2323
- apiGroups: ["apps"]
2424
resources: ["replicasets"]
2525
verbs: ["get"]

internal/controller/controller.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"k8s.io/apimachinery/pkg/util/wait"
2323
"k8s.io/client-go/informers"
2424
"k8s.io/client-go/kubernetes"
25+
appslisters "k8s.io/client-go/listers/apps/v1"
2526
"k8s.io/client-go/tools/cache"
2627
"k8s.io/client-go/util/workqueue"
2728
)
@@ -64,6 +65,8 @@ type Controller struct {
6465
clientset kubernetes.Interface
6566
metadataAggregator podMetadataAggregator
6667
podInformer cache.SharedIndexInformer
68+
deploymentInformer cache.SharedIndexInformer
69+
deploymentLister appslisters.DeploymentLister
6770
workqueue workqueue.TypedRateLimitingInterface[PodEvent]
6871
apiClient deploymentRecordPoster
6972
cfg *Config
@@ -82,6 +85,8 @@ func New(clientset kubernetes.Interface, metadataAggregator podMetadataAggregato
8285
factory := createInformerFactory(clientset, namespace, excludeNamespaces)
8386

8487
podInformer := factory.Core().V1().Pods().Informer()
88+
deploymentInformer := factory.Apps().V1().Deployments().Informer()
89+
deploymentLister := factory.Apps().V1().Deployments().Lister()
8590

8691
// Create work queue with rate limiting
8792
queue := workqueue.NewTypedRateLimitingQueue(
@@ -117,6 +122,8 @@ func New(clientset kubernetes.Interface, metadataAggregator podMetadataAggregato
117122
clientset: clientset,
118123
metadataAggregator: metadataAggregator,
119124
podInformer: podInformer,
125+
deploymentInformer: deploymentInformer,
126+
deploymentLister: deploymentLister,
120127
workqueue: queue,
121128
apiClient: apiClient,
122129
cfg: cfg,
@@ -237,14 +244,15 @@ func (c *Controller) Run(ctx context.Context, workers int) error {
237244
defer runtime.HandleCrash()
238245
defer c.workqueue.ShutDown()
239246

240-
slog.Info("Starting pod informer")
247+
slog.Info("Starting informers")
241248

242-
// Start the informer
249+
// Start the informers
243250
go c.podInformer.Run(ctx.Done())
251+
go c.deploymentInformer.Run(ctx.Done())
244252

245-
// Wait for the cache to be synced
246-
slog.Info("Waiting for informer cache to sync")
247-
if !cache.WaitForCacheSync(ctx.Done(), c.podInformer.HasSynced) {
253+
// Wait for the caches to be synced
254+
slog.Info("Waiting for informer caches to sync")
255+
if !cache.WaitForCacheSync(ctx.Done(), c.podInformer.HasSynced, c.deploymentInformer.HasSynced) {
248256
return errors.New("timed out waiting for caches to sync")
249257
}
250258

@@ -327,7 +335,7 @@ func (c *Controller) processEvent(ctx context.Context, event PodEvent) error {
327335
// the referenced image digest to the newly observed (via
328336
// the create event).
329337
deploymentName := getDeploymentName(pod)
330-
if deploymentName != "" && c.deploymentExists(ctx, pod.Namespace, deploymentName) {
338+
if deploymentName != "" && c.deploymentExists(pod.Namespace, deploymentName) {
331339
slog.Debug("Deployment still exists, skipping pod delete (scale down)",
332340
"namespace", pod.Namespace,
333341
"deployment", deploymentName,
@@ -390,16 +398,14 @@ func (c *Controller) processEvent(ctx context.Context, event PodEvent) error {
390398
return lastErr
391399
}
392400

393-
// deploymentExists checks if a deployment exists in the cluster.
394-
func (c *Controller) deploymentExists(ctx context.Context, namespace, name string) bool {
395-
_, err := c.clientset.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
401+
// deploymentExists checks if a deployment exists in the local informer cache.
402+
func (c *Controller) deploymentExists(namespace, name string) bool {
403+
_, err := c.deploymentLister.Deployments(namespace).Get(name)
396404
if err != nil {
397405
if k8serrors.IsNotFound(err) {
398406
return false
399407
}
400-
// On error, assume it exists to be safe
401-
// (avoid false decommissions)
402-
slog.Warn("Failed to check if deployment exists, assuming it does",
408+
slog.Warn("Failed to check if deployment exists in cache, assuming it does",
403409
"namespace", namespace,
404410
"deployment", name,
405411
"error", err,

internal/controller/controller_integration_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func setup(t *testing.T, onlyNamespace string, excludeNamespaces string) (*kuber
119119
go func() {
120120
_ = ctrl.Run(ctx, 1)
121121
}()
122-
if !cache.WaitForCacheSync(ctx.Done(), ctrl.podInformer.HasSynced) {
122+
if !cache.WaitForCacheSync(ctx.Done(), ctrl.podInformer.HasSynced, ctrl.deploymentInformer.HasSynced) {
123123
t.Fatal("timed out waiting for informer cache to sync")
124124
}
125125

0 commit comments

Comments
 (0)