@@ -11,6 +11,8 @@ import (
11
11
"sync"
12
12
"time"
13
13
14
+ admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
15
+
14
16
"github.com/solo-io/istio-usage-collector/internal/logging"
15
17
"github.com/solo-io/istio-usage-collector/internal/utils"
16
18
"github.com/solo-io/istio-usage-collector/pkg/models"
@@ -197,7 +199,12 @@ func processNodes(ctx context.Context, clientset kubernetes.Interface, metricsCl
197
199
errorCh := make (chan error , totalNodes )
198
200
199
201
// Use a semaphore to control concurrency
200
- concurrentLimit := runtime .NumCPU () * 2
202
+ var concurrentLimit int
203
+ if cfg .MaxProcessors > 0 {
204
+ concurrentLimit = cfg .MaxProcessors
205
+ } else {
206
+ concurrentLimit = runtime .NumCPU () * 2
207
+ }
201
208
semaphore := make (chan struct {}, concurrentLimit )
202
209
203
210
logging .Debug ("Processing nodes with up to %d concurrent requests" , concurrentLimit )
@@ -326,10 +333,30 @@ func processNamespaces(ctx context.Context, clientset kubernetes.Interface, metr
326
333
errorCh := make (chan error , totalNamespaces )
327
334
328
335
// Use a semaphore to control concurrency based on system resources
329
- concurrentLimit := runtime .NumCPU () * 4
336
+ var concurrentLimit int
337
+ if cfg .MaxProcessors > 0 {
338
+ concurrentLimit = cfg .MaxProcessors
339
+ } else {
340
+ concurrentLimit = runtime .NumCPU () * 4
341
+ }
330
342
semaphore := make (chan struct {}, concurrentLimit )
331
343
332
344
logging .Debug ("Processing namespaces with up to %d concurrent requests" , concurrentLimit )
345
+
346
+ // Get all mutating webhook configurations - istio uses mwhs to define its automatic sidecar injection policy
347
+ webhooks , err := clientset .AdmissionregistrationV1 ().MutatingWebhookConfigurations ().List (ctx , metav1.ListOptions {})
348
+ if err != nil {
349
+ logging .Warn ("Failed to list mutating webhook configurations: %v" , err )
350
+ }
351
+ if webhooks == nil || len (webhooks .Items ) == 0 {
352
+ logging .Warn ("No mutating webhook configurations found in cluster %s" , cfg .KubeContext )
353
+ }
354
+ // filter out non-istio webhooks
355
+ istioWebhooks := utils .FilterIstioWebhooks (webhooks .Items )
356
+ if len (istioWebhooks ) == 0 {
357
+ logging .Warn ("No Istio-related mutating webhook configurations found in cluster %s" , cfg .KubeContext )
358
+ }
359
+
333
360
for _ , ns := range namespaces .Items {
334
361
// Check parent context for cancellation before spawning more goroutines
335
362
if ctx .Err () != nil {
@@ -367,8 +394,7 @@ func processNamespaces(ctx context.Context, clientset kubernetes.Interface, metr
367
394
return
368
395
}
369
396
370
- nsInfo , err := processNamespace (workerCtx , clientset , metricsClient , namespace .Name , hasMetrics )
371
-
397
+ nsInfo , err := processNamespace (workerCtx , clientset , metricsClient , namespace .Name , hasMetrics , istioWebhooks )
372
398
if progress != nil {
373
399
progress .Increment ()
374
400
}
@@ -410,8 +436,9 @@ func processNamespaces(ctx context.Context, clientset kubernetes.Interface, metr
410
436
return nil
411
437
}
412
438
413
- // processNamespace processes an individual namespace
414
- func processNamespace (ctx context.Context , clientset kubernetes.Interface , metricsClient metricsv.Interface , namespace string , hasMetrics bool ) (* models.NamespaceInfo , error ) {
439
+ // processNamespace processes an individual namespace and its pods
440
+ // TODO: We currently don't check for Sidecar CRs -- https://istio.io/latest/docs/reference/config/networking/sidecar/
441
+ func processNamespace (ctx context.Context , clientset kubernetes.Interface , metricsClient metricsv.Interface , namespace string , hasMetrics bool , istioWebhooks []admissionregistrationv1.MutatingWebhookConfiguration ) (* models.NamespaceInfo , error ) {
415
442
if ctx .Err () != nil {
416
443
return nil , ctx .Err ()
417
444
}
@@ -422,19 +449,6 @@ func processNamespace(ctx context.Context, clientset kubernetes.Interface, metri
422
449
return nil , fmt .Errorf ("failed to get namespace details: %w" , err )
423
450
}
424
451
425
- isIstioInjected := false
426
- if value , ok := ns .Labels ["istio-injection" ]; ok && value == "enabled" {
427
- isIstioInjected = true
428
- } else if _ , ok := ns .Labels ["istio.io/rev" ]; ok {
429
- isIstioInjected = true
430
- }
431
-
432
- if isIstioInjected {
433
- logging .Debug ("Namespace %s has Istio injection enabled" , namespace )
434
- } else {
435
- logging .Debug ("Namespace %s has no Istio injection" , namespace )
436
- }
437
-
438
452
// Get pods in the namespace
439
453
pods , err := clientset .CoreV1 ().Pods (namespace ).List (ctx , metav1.ListOptions {})
440
454
if err != nil {
@@ -478,11 +492,26 @@ func processNamespace(ctx context.Context, clientset kubernetes.Interface, metri
478
492
istioCpuActual := 0.0
479
493
istioMemActual := 0.0
480
494
495
+ // Whether the namespace has at least one pod with istio injection
496
+ isIstioInjected := false
497
+
481
498
// Process all pods
482
499
for _ , pod := range pods .Items {
500
+ // Check if istio injection is enabled on the pod-level
501
+ isPodIstioInjected := utils .CheckInject (istioWebhooks , pod .Labels , ns .Labels )
502
+
503
+ // If any pod within the namespace has istio injection occurring, we should count the namespace as having istio injected
504
+ isIstioInjected = isIstioInjected || isPodIstioInjected
505
+
483
506
// Check each container
484
507
for _ , container := range pod .Spec .Containers {
485
- isIstioProxy := container .Name == "istio-proxy"
508
+ // we only count istio-proxy container as an istio sidecar if the pod has istio injection enabled
509
+ isIstioProxyContainer := container .Name == "istio-proxy"
510
+ isIstioProxy := isIstioProxyContainer && isPodIstioInjected
511
+ if isIstioProxyContainer && ! isPodIstioInjected {
512
+ // add a debug log if the pod has an istio-proxy container but istio injection is disabled, meaning we won't treat it as an istio sidecar
513
+ logging .Debug ("%s.%s does not have istio injection enabled, treating its 'istio-proxy' container as a regular container" , namespace , pod .Name )
514
+ }
486
515
487
516
// Count container types
488
517
if isIstioProxy {
@@ -539,9 +568,10 @@ func processNamespace(ctx context.Context, clientset kubernetes.Interface, metri
539
568
}
540
569
}
541
570
542
- // Create namespace info
571
+ // Create namespace info (before appending actual resource usage and istio resources)
543
572
nsInfo := & models.NamespaceInfo {
544
- Pods : len (pods .Items ),
573
+ Pods : len (pods .Items ),
574
+ // the namespace had istio injected if it was either enabled on the namespace-level OR within any of its pods
545
575
IsIstioInjected : isIstioInjected ,
546
576
Resources : models.ResourceInfo {
547
577
Regular : models.ContainerResources {
@@ -561,8 +591,8 @@ func processNamespace(ctx context.Context, clientset kubernetes.Interface, metri
561
591
}
562
592
}
563
593
564
- // Add Istio resources if the namespace has Istio injection
565
- if isIstioInjected {
594
+ // Only add the Istio resources field if the namespace contained at least one pod with istio injection
595
+ if nsInfo . IsIstioInjected {
566
596
nsInfo .Resources .Istio = & models.ContainerResources {
567
597
Containers : istioContainers ,
568
598
Request : models.Resources {
@@ -578,7 +608,6 @@ func processNamespace(ctx context.Context, clientset kubernetes.Interface, metri
578
608
}
579
609
}
580
610
}
581
-
582
611
return nsInfo , nil
583
612
}
584
613
0 commit comments