github.com/cilium/cilium@v1.16.2/operator/watchers/k8s_service_sync.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package watchers 5 6 import ( 7 "context" 8 "sync" 9 10 "github.com/sirupsen/logrus" 11 12 cmtypes "github.com/cilium/cilium/pkg/clustermesh/types" 13 "github.com/cilium/cilium/pkg/k8s" 14 k8sClient "github.com/cilium/cilium/pkg/k8s/client" 15 "github.com/cilium/cilium/pkg/k8s/resource" 16 slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" 17 "github.com/cilium/cilium/pkg/kvstore" 18 "github.com/cilium/cilium/pkg/kvstore/store" 19 "github.com/cilium/cilium/pkg/lock" 20 "github.com/cilium/cilium/pkg/logging/logfields" 21 serviceStore "github.com/cilium/cilium/pkg/service/store" 22 ) 23 24 var ( 25 K8sSvcCache = k8s.NewServiceCache(nil, nil) 26 27 kvs store.SyncStore 28 ) 29 30 func k8sServiceHandler(ctx context.Context, cinfo cmtypes.ClusterInfo, shared bool) { 31 serviceHandler := func(event k8s.ServiceEvent) { 32 defer event.SWG.Done() 33 34 svc := k8s.NewClusterService(event.ID, event.Service, event.Endpoints) 35 svc.Cluster = cinfo.Name 36 svc.ClusterID = cinfo.ID 37 38 scopedLog := log.WithFields(logrus.Fields{ 39 logfields.K8sSvcName: event.ID.Name, 40 logfields.K8sNamespace: event.ID.Namespace, 41 "action": event.Action.String(), 42 "service": event.Service.String(), 43 "endpoints": event.Endpoints.String(), 44 "shared": event.Service.Shared, 45 }) 46 scopedLog.Debug("Kubernetes service definition changed") 47 48 if shared && !event.Service.Shared { 49 // The annotation may have been added, delete an eventual existing service 50 kvs.DeleteKey(ctx, &svc) 51 return 52 } 53 54 switch event.Action { 55 case k8s.UpdateService: 56 if err := kvs.UpsertKey(ctx, &svc); err != nil { 57 // An error is triggered only in case it concerns service marshaling, 58 // as kvstore operations are automatically re-tried in case of error. 59 scopedLog.WithError(err).Warning("Failed synchronizing service") 60 } 61 62 case k8s.DeleteService: 63 kvs.DeleteKey(ctx, &svc) 64 } 65 } 66 for { 67 select { 68 case event, ok := <-K8sSvcCache.Events: 69 if !ok { 70 return 71 } 72 73 serviceHandler(event) 74 75 case <-ctx.Done(): 76 return 77 } 78 } 79 } 80 81 type ServiceSyncParameters struct { 82 ClusterInfo cmtypes.ClusterInfo 83 Clientset k8sClient.Clientset 84 Services resource.Resource[*slim_corev1.Service] 85 Endpoints resource.Resource[*k8s.Endpoints] 86 Backend store.SyncStoreBackend 87 SharedOnly bool 88 StoreFactory store.Factory 89 SyncCallback func(context.Context) 90 } 91 92 // StartSynchronizingServices starts a controller for synchronizing services from k8s to kvstore 93 // 'shared' specifies whether only shared services are synchronized. If 'false' then all services 94 // will be synchronized. For clustermesh we only need to synchronize shared services, while for 95 // VM support we need to sync all the services. 96 func StartSynchronizingServices(ctx context.Context, wg *sync.WaitGroup, cfg ServiceSyncParameters) { 97 kvstoreReady := make(chan struct{}) 98 99 wg.Add(1) 100 go func() { 101 defer wg.Done() 102 if cfg.Backend == nil { 103 // Needs to be assigned in a separate goroutine, since it might block 104 // if the client is not yet initialized. 105 cfg.Backend = kvstore.Client() 106 } 107 108 store := cfg.StoreFactory.NewSyncStore(cfg.ClusterInfo.Name, 109 cfg.Backend, serviceStore.ServiceStorePrefix) 110 kvs = store 111 close(kvstoreReady) 112 store.Run(ctx) 113 }() 114 115 // Start synchronizing ServiceCache to kvstore 116 wg.Add(1) 117 go func() { 118 defer wg.Done() 119 120 // Wait for kvstore 121 <-kvstoreReady 122 123 log.Info("Starting to synchronize Kubernetes services to kvstore") 124 k8sServiceHandler(ctx, cfg.ClusterInfo, cfg.SharedOnly) 125 }() 126 127 // Start populating the service cache with Kubernetes services and endpoints 128 wg.Add(1) 129 go func() { 130 defer wg.Done() 131 132 swg := lock.NewStoppableWaitGroup() 133 serviceEvents := cfg.Services.Events(ctx) 134 endpointEvents := cfg.Endpoints.Events(ctx) 135 136 servicesSynced, endpointsSynced := false, false 137 138 // onSync is called when the initial listing and processing of 139 // services and endpoints has finished. 140 onSync := func() { 141 // Wait until all work has been finished up to the sync event. 142 swg.Stop() 143 swg.Wait() 144 145 log.Info("Initial list of services successfully received from Kubernetes") 146 kvs.Synced(ctx, cfg.SyncCallback) 147 } 148 149 for serviceEvents != nil || endpointEvents != nil { 150 select { 151 case ev, ok := <-serviceEvents: 152 if !ok { 153 serviceEvents = nil 154 continue 155 } 156 157 // Ignore kubernetes endpoints events 158 if ev.Key.Name == "kube-scheduler" || ev.Key.Name == "kube-controller-manager" { 159 ev.Done(nil) 160 continue 161 } 162 163 switch ev.Kind { 164 case resource.Sync: 165 servicesSynced = true 166 if servicesSynced && endpointsSynced { 167 onSync() 168 } 169 case resource.Upsert: 170 K8sSvcCache.UpdateService(ev.Object, swg) 171 case resource.Delete: 172 K8sSvcCache.DeleteService(ev.Object, swg) 173 } 174 ev.Done(nil) 175 176 case ev, ok := <-endpointEvents: 177 if !ok { 178 endpointEvents = nil 179 continue 180 } 181 182 switch ev.Kind { 183 case resource.Sync: 184 endpointsSynced = true 185 if servicesSynced && endpointsSynced { 186 onSync() 187 } 188 case resource.Upsert: 189 K8sSvcCache.UpdateEndpoints(ev.Object, swg) 190 case resource.Delete: 191 K8sSvcCache.DeleteEndpoints(ev.Object.EndpointSliceID, swg) 192 } 193 ev.Done(nil) 194 } 195 } 196 }() 197 }