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  }