@@ -4,10 +4,12 @@ import (
44 "context"
55 "flag"
66 "fmt"
7+ "log"
78 "os"
89 "os/exec"
910 "os/signal"
1011 "path/filepath"
12+ "sort"
1113 "strings"
1214 "sync"
1315 "syscall"
@@ -16,8 +18,10 @@ import (
1618 v1 "k8s.io/api/core/v1"
1719 apierrors "k8s.io/apimachinery/pkg/api/errors"
1820 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+ "k8s.io/apimachinery/pkg/labels"
1922 "k8s.io/client-go/informers"
2023 "k8s.io/client-go/kubernetes"
24+ corelisters "k8s.io/client-go/listers/core/v1"
2125 "k8s.io/client-go/rest"
2226 "k8s.io/client-go/tools/cache"
2327 "k8s.io/client-go/tools/clientcmd"
3034 watchKey string
3135 mainContainerName string
3236
33- clientset kubernetes.Interface
34- lock sync.Mutex
37+ clientset kubernetes.Interface
38+ namespaceLister corelisters.NamespaceLister
39+ lock sync.Mutex
3540
3641 // restartMainContainer is the action used to reload the ConfigMap.
3742 // It is a variable so tests can override it.
@@ -53,51 +58,44 @@ func main() {
5358
5459 config , err := getKubeConfig ()
5560 if err != nil {
56- fmt .Printf ("Error getting Kubernetes config: %v\n " , err )
57- os .Exit (1 )
61+ log .Fatalf ("Error getting Kubernetes config: %v" , err )
5862 }
5963
6064 clientset , err = kubernetes .NewForConfig (config )
6165 if err != nil {
62- fmt .Printf ("Error creating Kubernetes client: %v\n " , err )
63- os .Exit (1 )
66+ log .Fatalf ("Error creating Kubernetes client: %v" , err )
6467 }
6568
66- // Ensure the ConfigMap is created/updated at startup
67- updateConfigMap ()
69+ // Cancel the context on SIGTERM/SIGINT for graceful shutdown.
70+ ctx , stop := signal .NotifyContext (context .Background (), syscall .SIGTERM , syscall .SIGINT )
71+ defer stop ()
6872
69- // Set up signal handling for graceful shutdown
70- stopCh := make (chan struct {})
71- signalCh := make (chan os.Signal , 1 )
72-
73- // Listen for termination signals
74- signal .Notify (signalCh , syscall .SIGTERM , syscall .SIGINT )
75-
76- go func () {
77- <- signalCh
78- fmt .Println ("Received termination signal. Shutting down gracefully..." )
79- close (stopCh ) // Stop the namespace watcher
80- os .Exit (0 ) // Exit the program
81- }()
73+ // Start the namespace watcher and wait for its cache to sync.
74+ if err := watchNamespaces (ctx ); err != nil {
75+ log .Fatalf ("Error starting namespace watcher: %v" , err )
76+ }
8277
83- // Start namespace watcher
84- go watchNamespaces (stopCh )
78+ // Ensure the ConfigMap reflects the current state once the cache is synced.
79+ // This also covers the case where no matching namespace exists yet, for
80+ // which no informer Add event would fire.
81+ updateConfigMap (ctx )
8582
86- // Keep the program running
87- <- stopCh
83+ // Block until a termination signal cancels the context.
84+ <- ctx .Done ()
85+ log .Println ("Received termination signal. Shutting down gracefully..." )
8886}
8987
9088// getKubeConfig tries in-cluster config first, then falls back to local kubeconfig
9189func getKubeConfig () (* rest.Config , error ) {
9290 // Try in-cluster config
9391 config , err := rest .InClusterConfig ()
9492 if err == nil {
95- fmt .Println ("Using in-cluster configuration" )
93+ log .Println ("Using in-cluster configuration" )
9694 return config , nil
9795 }
9896
9997 // Fall back to local kubeconfig
100- fmt .Println ("In-cluster config failed, falling back to local kubeconfig" )
98+ log .Println ("In-cluster config failed, falling back to local kubeconfig" )
10199 kubeconfig := filepath .Join (os .Getenv ("HOME" ), ".kube" , "config" )
102100 if envKubeconfig := os .Getenv ("KUBECONFIG" ); envKubeconfig != "" {
103101 kubeconfig = envKubeconfig
@@ -108,48 +106,54 @@ func getKubeConfig() (*rest.Config, error) {
108106 return nil , fmt .Errorf ("failed to load kubeconfig: %w" , err )
109107 }
110108
111- fmt .Println ("Using local kubeconfig" )
109+ log .Println ("Using local kubeconfig" )
112110 return config , nil
113111}
114112
115- // watchNamespaces monitors namespace creation and deletion
116- func watchNamespaces (stopCh chan struct {}) {
113+ // watchNamespaces starts an informer that monitors namespace changes and keeps
114+ // the ConfigMap in sync. It returns once the informer cache has synced; the
115+ // informer keeps running in the background until ctx is cancelled.
116+ func watchNamespaces (ctx context.Context ) error {
117117 informerFactory := informers .NewSharedInformerFactoryWithOptions (clientset , time .Minute , informers .WithTweakListOptions (func (opts * metav1.ListOptions ) {
118118 opts .LabelSelector = labelSelector
119119 }))
120- namespaceInformer := informerFactory .Core ().V1 ().Namespaces ().Informer ()
120+ namespaceInformer := informerFactory .Core ().V1 ().Namespaces ()
121+ namespaceLister = namespaceInformer .Lister ()
121122
122- namespaceInformer .AddEventHandler (cache.ResourceEventHandlerFuncs {
123- AddFunc : func (obj interface {}) { updateConfigMap () },
124- UpdateFunc : func (oldObj , newObj interface {}) { updateConfigMap () },
125- DeleteFunc : func (obj interface {}) { updateConfigMap () },
123+ namespaceInformer .Informer (). AddEventHandler (cache.ResourceEventHandlerFuncs {
124+ AddFunc : func (obj interface {}) { updateConfigMap (ctx ) },
125+ UpdateFunc : func (oldObj , newObj interface {}) { updateConfigMap (ctx ) },
126+ DeleteFunc : func (obj interface {}) { updateConfigMap (ctx ) },
126127 })
127128
128- informerFactory .Start (stopCh )
129- informerFactory .WaitForCacheSync (stopCh )
129+ informerFactory .Start (ctx .Done ())
130+ if ! cache .WaitForCacheSync (ctx .Done (), namespaceInformer .Informer ().HasSynced ) {
131+ return fmt .Errorf ("failed to sync namespace informer cache" )
132+ }
133+ return nil
130134}
131135
132136// updateConfigMap updates the ConfigMap with the latest namespace list
133- func updateConfigMap () {
137+ func updateConfigMap (ctx context. Context ) {
134138 lock .Lock ()
135139 defer lock .Unlock ()
136140
137141 namespaces , err := getFilteredNamespaces ()
138142 if err != nil {
139- fmt .Printf ("Error getting namespaces: %v\n " , err )
143+ log .Printf ("Error getting namespaces: %v" , err )
140144 return
141145 }
142146
143147 newValue := strings .Join (namespaces , "," )
144148
145149 // Fetch the existing ConfigMap
146- cm , err := clientset .CoreV1 ().ConfigMaps (configMapNamespace ).Get (context . TODO () , configMapName , metav1.GetOptions {})
150+ cm , err := clientset .CoreV1 ().ConfigMaps (configMapNamespace ).Get (ctx , configMapName , metav1.GetOptions {})
147151 if err != nil {
148152 if apierrors .IsNotFound (err ) {
149153 // If ConfigMap doesn't exist, create it
150- createConfigMap (newValue )
154+ createConfigMap (ctx , newValue )
151155 } else {
152- fmt .Printf ("Error getting ConfigMap: %v\n " , err )
156+ log .Printf ("Error getting ConfigMap: %v" , err )
153157 }
154158 return
155159 }
@@ -164,34 +168,40 @@ func updateConfigMap() {
164168 cm .Data = make (map [string ]string )
165169 }
166170 cm .Data [watchKey ] = newValue
167- _ , err = clientset .CoreV1 ().ConfigMaps (configMapNamespace ).Update (context .TODO (), cm , metav1.UpdateOptions {})
168- if err != nil {
169- fmt .Printf ("Error updating ConfigMap: %v\n " , err )
171+ if _ , err := clientset .CoreV1 ().ConfigMaps (configMapNamespace ).Update (ctx , cm , metav1.UpdateOptions {}); err != nil {
172+ log .Printf ("Error updating ConfigMap: %v" , err )
170173 return
171174 }
172175
173176 // Trigger restart of the main container
174177 restartMainContainer ()
175178}
176179
177- // getFilteredNamespaces retrieves namespaces with the specified label
180+ // getFilteredNamespaces retrieves namespaces with the specified label from the
181+ // informer cache. The result is sorted so the joined value is stable and does
182+ // not trigger spurious ConfigMap updates (and container restarts) caused by
183+ // non-deterministic cache iteration order.
178184func getFilteredNamespaces () ([]string , error ) {
179- namespaces , err := clientset .CoreV1 ().Namespaces ().List (context .TODO (), metav1.ListOptions {
180- LabelSelector : labelSelector ,
181- })
185+ selector , err := labels .Parse (labelSelector )
186+ if err != nil {
187+ return nil , fmt .Errorf ("invalid label selector %q: %w" , labelSelector , err )
188+ }
189+
190+ namespaces , err := namespaceLister .List (selector )
182191 if err != nil {
183192 return nil , err
184193 }
185194
186- var nsList []string
187- for _ , ns := range namespaces . Items {
195+ nsList := make ( []string , 0 , len ( namespaces ))
196+ for _ , ns := range namespaces {
188197 nsList = append (nsList , ns .Name )
189198 }
199+ sort .Strings (nsList )
190200 return nsList , nil
191201}
192202
193203// createConfigMap creates a new ConfigMap
194- func createConfigMap (value string ) {
204+ func createConfigMap (ctx context. Context , value string ) {
195205 cm := & v1.ConfigMap {
196206 ObjectMeta : metav1.ObjectMeta {
197207 Name : configMapName ,
@@ -202,27 +212,25 @@ func createConfigMap(value string) {
202212 },
203213 }
204214
205- _ , err := clientset .CoreV1 ().ConfigMaps (configMapNamespace ).Create (context .TODO (), cm , metav1.CreateOptions {})
206- if err != nil {
207- fmt .Printf ("Error creating ConfigMap: %v\n " , err )
215+ if _ , err := clientset .CoreV1 ().ConfigMaps (configMapNamespace ).Create (ctx , cm , metav1.CreateOptions {}); err != nil {
216+ log .Printf ("Error creating ConfigMap: %v" , err )
208217 }
209218}
210219
211220// killMainContainer kills the main container to reload the ConfigMap
212221func killMainContainer () {
213- fmt .Println ("Restarting main container..." )
222+ log .Println ("Restarting main container..." )
214223
215224 // Find the main container's PID (assumes PID namespace is shared)
216225 pid , err := getMainContainerPID ()
217226 if err != nil {
218- fmt .Printf ("Error getting main container PID: %v\n " , err )
227+ log .Printf ("Error getting main container PID: %v" , err )
219228 return
220229 }
221230
222231 // Send SIGTERM to the main container
223- err = exec .Command ("kill" , "-SIGTERM" , pid ).Run ()
224- if err != nil {
225- fmt .Printf ("Error sending SIGTERM to main container: %v\n " , err )
232+ if err := exec .Command ("kill" , "-SIGTERM" , pid ).Run (); err != nil {
233+ log .Printf ("Error sending SIGTERM to main container: %v" , err )
226234 }
227235}
228236
0 commit comments