From 9abc5885d820943f4e8682694d8b9b9644dc0889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Mory=20Mogyorosi?= Date: Tue, 21 Apr 2026 10:52:51 +0200 Subject: [PATCH] feat: Add multi-namespace watch support --- .../kubernetes/chart/reloader/README.md | 3 + .../chart/reloader/templates/deployment.yaml | 5 +- .../kubernetes/chart/reloader/values.yaml | 1 + internal/pkg/cmd/reloader.go | 67 +++++++++++++------ 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/deployments/kubernetes/chart/reloader/README.md b/deployments/kubernetes/chart/reloader/README.md index d72cec45f..14e6730b1 100644 --- a/deployments/kubernetes/chart/reloader/README.md +++ b/deployments/kubernetes/chart/reloader/README.md @@ -16,6 +16,8 @@ helm install {{RELEASE_NAME}} stakater/reloader -n {{NAMESPACE}} --set reloader. helm install stakater/reloader --set reloader.watchGlobally=false --namespace test --generate-name # Install Reloader in `test` namespace which will only watch `Deployments`, `Daemonsets` `Statefulsets` and `Rollouts` in `test` namespace. +helm install stakater/reloader --set reloader.watchNamespaces="team-a,team-b" --generate-name # Watch only the listed namespaces. + helm install stakater/reloader --set reloader.ignoreJobs=true --set reloader.ignoreCronJobs=true --generate-name # Install Reloader ignoring Jobs and CronJobs from reload monitoring ``` @@ -61,6 +63,7 @@ helm uninstall {{RELEASE_NAME}} -n {{NAMESPACE}} | `reloader.resourceLabelSelector` | List of comma separated label selectors, if multiple are provided they are combined with the AND operator | string | `""` | | `reloader.logFormat` | Set type of log format. Value could be either `json` or `""` | string | `""` | | `reloader.watchGlobally` | Allow Reloader to watch in all namespaces (`true`) or just in a single namespace (`false`) | boolean | `true` | +| `reloader.watchNamespaces` | Comma separated list of namespaces to watch. When set, it overrides `reloader.watchGlobally` | string | `""` | | `reloader.enableHA` | Enable leadership election allowing you to run multiple replicas | boolean | `false` | | `reloader.enablePProf` | Enables pprof for profiling | boolean | `false` | | `reloader.pprofAddr` | Address to start pprof server on | string | `:6060` | diff --git a/deployments/kubernetes/chart/reloader/templates/deployment.yaml b/deployments/kubernetes/chart/reloader/templates/deployment.yaml index e568f9fd0..a7efc911a 100644 --- a/deployments/kubernetes/chart/reloader/templates/deployment.yaml +++ b/deployments/kubernetes/chart/reloader/templates/deployment.yaml @@ -141,7 +141,10 @@ spec: fieldPath: {{ $value | quote}} {{- end }} {{- end }} - {{- if eq .Values.reloader.watchGlobally false }} + {{- if .Values.reloader.watchNamespaces }} + - name: KUBERNETES_NAMESPACE + value: {{ .Values.reloader.watchNamespaces | quote }} + {{- else if eq .Values.reloader.watchGlobally false }} - name: KUBERNETES_NAMESPACE valueFrom: fieldRef: diff --git a/deployments/kubernetes/chart/reloader/values.yaml b/deployments/kubernetes/chart/reloader/values.yaml index 03429366a..ba35faa94 100644 --- a/deployments/kubernetes/chart/reloader/values.yaml +++ b/deployments/kubernetes/chart/reloader/values.yaml @@ -45,6 +45,7 @@ reloader: logFormat: "" # json logLevel: info # Log level to use (trace, debug, info, warning, error, fatal and panic) watchGlobally: true + watchNamespaces: "" # Comma separated list of namespaces to watch (overrides watchGlobally when set) # Set to true to enable leadership election allowing you to run multiple replicas enableHA: false # Set to true to enable pprof for profiling diff --git a/internal/pkg/cmd/reloader.go b/internal/pkg/cmd/reloader.go index 771e2df2f..cc0417c36 100644 --- a/internal/pkg/cmd/reloader.go +++ b/internal/pkg/cmd/reloader.go @@ -102,6 +102,32 @@ func getHAEnvs() (string, string) { return podName, podNamespace } +func getWatchedNamespaces(namespaceEnv string) ([]string, bool) { + rawNamespaces := strings.Split(namespaceEnv, ",") + namespaces := make([]string, 0, len(rawNamespaces)) + seen := make(map[string]struct{}) + + for _, ns := range rawNamespaces { + trimmedNamespace := strings.TrimSpace(ns) + if trimmedNamespace == "" { + continue + } + + if _, exists := seen[trimmedNamespace]; exists { + continue + } + + namespaces = append(namespaces, trimmedNamespace) + seen[trimmedNamespace] = struct{}{} + } + + if len(namespaces) == 0 { + return []string{v1.NamespaceAll}, true + } + + return namespaces, false +} + func startReloader(cmd *cobra.Command, args []string) { common.GetCommandLineOptions() err := configureLogging(options.LogFormat, options.LogLevel) @@ -110,12 +136,11 @@ func startReloader(cmd *cobra.Command, args []string) { } logrus.Info("Starting Reloader") - isGlobal := false - currentNamespace := os.Getenv("KUBERNETES_NAMESPACE") - if len(currentNamespace) == 0 { - currentNamespace = v1.NamespaceAll - isGlobal = true + watchedNamespaces, isGlobal := getWatchedNamespaces(os.Getenv("KUBERNETES_NAMESPACE")) + if isGlobal { logrus.Warnf("KUBERNETES_NAMESPACE is unset, will detect changes in all namespaces.") + } else { + logrus.Infof("will detect changes in namespaces: %s", strings.Join(watchedNamespaces, ",")) } // create the clientset @@ -168,22 +193,24 @@ func startReloader(cmd *cobra.Command, args []string) { continue } - c, err := controller.NewController(clientset, k, currentNamespace, ignoredNamespacesList, namespaceLabelSelector, resourceLabelSelector, collectors) - if err != nil { - logrus.Fatalf("%s", err) - } - - controllers = append(controllers, c) - - // If HA is enabled we only run the controller when - if options.EnableHA { - continue + for _, watchedNamespace := range watchedNamespaces { + c, err := controller.NewController(clientset, k, watchedNamespace, ignoredNamespacesList, namespaceLabelSelector, resourceLabelSelector, collectors) + if err != nil { + logrus.Fatalf("%s", err) + } + + controllers = append(controllers, c) + + // If HA is enabled we only run the controller when + if options.EnableHA { + continue + } + // Now let's start the controller + stop := make(chan struct{}) + defer close(stop) + logrus.Infof("Starting Controller to watch resource type: %s in namespace: %s", k, watchedNamespace) + go c.Run(1, stop) } - // Now let's start the controller - stop := make(chan struct{}) - defer close(stop) - logrus.Infof("Starting Controller to watch resource type: %s", k) - go c.Run(1, stop) } // Run leadership election