github.com/cilium/cilium@v1.16.2/pkg/k8s/resource/example/main.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package main 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "log/slog" 11 "math/rand/v2" 12 13 "github.com/cilium/hive/cell" 14 "github.com/cilium/workerpool" 15 "github.com/spf13/pflag" 16 corev1 "k8s.io/api/core/v1" 17 18 "github.com/cilium/cilium/pkg/hive" 19 "github.com/cilium/cilium/pkg/k8s/client" 20 "github.com/cilium/cilium/pkg/k8s/resource" 21 "github.com/cilium/cilium/pkg/k8s/utils" 22 "github.com/cilium/cilium/pkg/labels" 23 "github.com/cilium/cilium/pkg/logging" 24 "github.com/cilium/cilium/pkg/logging/logfields" 25 "github.com/cilium/cilium/pkg/time" 26 ) 27 28 // PrintServices for the pkg/k8s/resource which observers pods and services and once a second prints the list of 29 // services with the pods associated with each service. 30 // 31 // Run with: 32 // 33 // go run . --k8s-kubeconfig-path ~/.kube/config 34 // 35 // To test, try running: 36 // 37 // kubectl run -it --rm --image=nginx --port=80 --expose nginx 38 39 var ( 40 log = logging.DefaultLogger.WithField(logfields.LogSubsys, "example") 41 ) 42 43 func main() { 44 hive := hive.New( 45 client.Cell, 46 resourcesCell, 47 printServicesCell, 48 49 cell.Invoke(func(*PrintServices) {}), 50 ) 51 hive.RegisterFlags(pflag.CommandLine) 52 pflag.Parse() 53 hive.Run(slog.Default()) 54 } 55 56 var resourcesCell = cell.Module( 57 "resources", 58 "Kubernetes Pod and Service resources", 59 60 cell.Provide( 61 func(lc cell.Lifecycle, c client.Clientset) resource.Resource[*corev1.Pod] { 62 if !c.IsEnabled() { 63 return nil 64 } 65 lw := utils.ListerWatcherFromTyped[*corev1.PodList](c.CoreV1().Pods("")) 66 return resource.New[*corev1.Pod](lc, lw, resource.WithMetric("Pod")) 67 }, 68 func(lc cell.Lifecycle, c client.Clientset) resource.Resource[*corev1.Service] { 69 if !c.IsEnabled() { 70 return nil 71 } 72 lw := utils.ListerWatcherFromTyped[*corev1.ServiceList](c.CoreV1().Services("")) 73 return resource.New[*corev1.Service](lc, lw, resource.WithMetric("Service")) 74 }, 75 ), 76 ) 77 78 var printServicesCell = cell.Module( 79 "print-services", 80 "Prints Kubernetes Services", 81 82 cell.Provide(newPrintServices), 83 ) 84 85 type PrintServices struct { 86 wp *workerpool.WorkerPool 87 88 pods resource.Resource[*corev1.Pod] 89 services resource.Resource[*corev1.Service] 90 } 91 92 type printServicesParams struct { 93 cell.In 94 95 Lifecycle cell.Lifecycle 96 Pods resource.Resource[*corev1.Pod] 97 Services resource.Resource[*corev1.Service] 98 } 99 100 func newPrintServices(p printServicesParams) (*PrintServices, error) { 101 if p.Pods == nil || p.Services == nil { 102 return nil, fmt.Errorf("Resources not available. Missing --k8s-kubeconfig-path?") 103 } 104 ps := &PrintServices{ 105 pods: p.Pods, 106 services: p.Services, 107 } 108 p.Lifecycle.Append(ps) 109 return ps, nil 110 } 111 112 func (ps *PrintServices) Start(startCtx cell.HookContext) error { 113 ps.wp = workerpool.New(1) 114 ps.wp.Submit("processLoop", ps.processLoop) 115 116 // Using the start context, do a blocking dump of all 117 // services. Using the start context here makes sure that 118 // this operation is aborted if it blocks too long. 119 ps.printServices(startCtx) 120 121 return nil 122 } 123 124 func (ps *PrintServices) Stop(cell.HookContext) error { 125 ps.wp.Close() 126 return nil 127 } 128 129 // printServices prints services at start to show how Store() can be used. 130 func (ps *PrintServices) printServices(ctx context.Context) { 131 132 // Retrieve a handle to the store. Blocks until the store has synced. 133 // Can fail if the context is cancelled (e.g. PrintServices is being stopped). 134 store, err := ps.services.Store(ctx) 135 if err != nil { 136 log.Errorf("Failed to retrieve store: %s, aborting", err) 137 return 138 } 139 140 log.Info("Services:") 141 for _, svc := range store.List() { 142 labels := labels.Map2Labels(svc.Spec.Selector, labels.LabelSourceK8s) 143 log.Infof(" - %s/%s\ttype=%s\tselector=%s", svc.Namespace, svc.Name, svc.Spec.Type, labels) 144 } 145 146 } 147 148 // processLoop observes changes to pods and services and periodically prints the 149 // services and the pods that each service selects. 150 func (ps *PrintServices) processLoop(ctx context.Context) error { 151 ticker := time.NewTicker(time.Second) 152 defer ticker.Stop() 153 154 // Subscribe to pods and services. 155 pods := ps.pods.Events(ctx) 156 services := ps.services.Events(ctx) 157 158 // State: 159 podLabels := make(map[resource.Key]labels.Labels) 160 serviceSelectors := make(map[resource.Key]labels.Labels) 161 162 // Process the pod and service events and periodically print the services. 163 // Loop until the pods and services have completed. We need to process 164 // both streams to the end to make sure we're not blocking the resource even 165 // if we're stopping (e.g. context cancelled). 166 for pods != nil || services != nil { 167 select { 168 case <-ticker.C: 169 for key, selectors := range serviceSelectors { 170 log.Infof("%s (%s)", key, selectors) 171 for podName, lbls := range podLabels { 172 match := true 173 for _, sel := range selectors { 174 match = match && lbls.Has(sel) 175 } 176 if match { 177 log.Infof(" - %s", podName) 178 } 179 } 180 } 181 log.Println("----------------------------------------------------------") 182 183 case ev, ok := <-pods: 184 if !ok { 185 pods = nil 186 continue 187 } 188 189 switch ev.Kind { 190 case resource.Sync: 191 // Pods have now been synced and the set of Upsert events 192 // received thus far forms a coherent snapshot of the pods 193 // at a specific point in time. This is usually used in the context 194 // of garbage collection at startup: we now know what is the set of pods that 195 // existed at the api-server brief moment ago and can remove persisted 196 // data of pods that are not part of this set. 197 case resource.Upsert: 198 log.Infof("Pod %s updated", ev.Key) 199 podLabels[ev.Key] = labels.Map2Labels(ev.Object.Labels, labels.LabelSourceK8s) 200 case resource.Delete: 201 log.Infof("Pod %s deleted", ev.Key) 202 delete(podLabels, ev.Key) 203 } 204 205 // Always mark the event as processed. This tells the resource that more 206 // events can be now emitted for this key and if error is nil it clears 207 // any rate limiting state related to failed attempts. 208 ev.Done(nil) 209 210 case ev, ok := <-services: 211 if !ok { 212 services = nil 213 continue 214 } 215 216 // Simulate a fault 10% of the time. This will cause this event to be retried 217 // later. 218 if rand.IntN(10) == 1 { 219 log.Info("Injecting a fault!") 220 ev.Done(errors.New("injected fault")) 221 continue 222 } 223 224 switch ev.Kind { 225 case resource.Sync: 226 log.Info("Services synced") 227 case resource.Upsert: 228 log.Infof("Service %s updated", ev.Key) 229 if len(ev.Object.Spec.Selector) > 0 { 230 serviceSelectors[ev.Key] = labels.Map2Labels(ev.Object.Spec.Selector, labels.LabelSourceK8s) 231 } 232 case resource.Delete: 233 log.Infof("Service %s deleted", ev.Key) 234 delete(serviceSelectors, ev.Key) 235 } 236 ev.Done(nil) 237 } 238 } 239 240 return nil 241 }