github.com/cilium/cilium@v1.16.2/operator/cmd/root.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package cmd
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"log/slog"
    11  	"os"
    12  	"path/filepath"
    13  	"sync"
    14  	"sync/atomic"
    15  	"time"
    16  
    17  	"github.com/cilium/hive/cell"
    18  	"github.com/sirupsen/logrus"
    19  	"github.com/spf13/cobra"
    20  	"github.com/spf13/viper"
    21  	"google.golang.org/grpc"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  	"k8s.io/apimachinery/pkg/util/rand"
    24  	"k8s.io/client-go/tools/leaderelection"
    25  	"k8s.io/client-go/tools/leaderelection/resourcelock"
    26  
    27  	operatorApi "github.com/cilium/cilium/api/v1/operator/server"
    28  	ciliumdbg "github.com/cilium/cilium/cilium-dbg/cmd"
    29  	"github.com/cilium/cilium/operator/api"
    30  	"github.com/cilium/cilium/operator/auth"
    31  	"github.com/cilium/cilium/operator/endpointgc"
    32  	"github.com/cilium/cilium/operator/identitygc"
    33  	operatorK8s "github.com/cilium/cilium/operator/k8s"
    34  	operatorMetrics "github.com/cilium/cilium/operator/metrics"
    35  	operatorOption "github.com/cilium/cilium/operator/option"
    36  	"github.com/cilium/cilium/operator/pkg/bgpv2"
    37  	"github.com/cilium/cilium/operator/pkg/ciliumendpointslice"
    38  	"github.com/cilium/cilium/operator/pkg/ciliumenvoyconfig"
    39  	controllerruntime "github.com/cilium/cilium/operator/pkg/controller-runtime"
    40  	gatewayapi "github.com/cilium/cilium/operator/pkg/gateway-api"
    41  	"github.com/cilium/cilium/operator/pkg/ingress"
    42  	"github.com/cilium/cilium/operator/pkg/lbipam"
    43  	"github.com/cilium/cilium/operator/pkg/networkpolicy"
    44  	"github.com/cilium/cilium/operator/pkg/nodeipam"
    45  	"github.com/cilium/cilium/operator/pkg/secretsync"
    46  	operatorWatchers "github.com/cilium/cilium/operator/watchers"
    47  	"github.com/cilium/cilium/pkg/clustermesh/endpointslicesync"
    48  	"github.com/cilium/cilium/pkg/clustermesh/mcsapi"
    49  	operatorClusterMesh "github.com/cilium/cilium/pkg/clustermesh/operator"
    50  	cmtypes "github.com/cilium/cilium/pkg/clustermesh/types"
    51  	"github.com/cilium/cilium/pkg/controller"
    52  	"github.com/cilium/cilium/pkg/defaults"
    53  	"github.com/cilium/cilium/pkg/dial"
    54  	"github.com/cilium/cilium/pkg/gops"
    55  	"github.com/cilium/cilium/pkg/hive"
    56  	"github.com/cilium/cilium/pkg/ipam/allocator"
    57  	ipamOption "github.com/cilium/cilium/pkg/ipam/option"
    58  	"github.com/cilium/cilium/pkg/k8s/apis"
    59  	k8sClient "github.com/cilium/cilium/pkg/k8s/client"
    60  	k8sversion "github.com/cilium/cilium/pkg/k8s/version"
    61  	"github.com/cilium/cilium/pkg/kvstore"
    62  	"github.com/cilium/cilium/pkg/kvstore/store"
    63  	"github.com/cilium/cilium/pkg/logging"
    64  	"github.com/cilium/cilium/pkg/logging/logfields"
    65  	"github.com/cilium/cilium/pkg/metrics"
    66  	"github.com/cilium/cilium/pkg/option"
    67  	"github.com/cilium/cilium/pkg/pprof"
    68  	"github.com/cilium/cilium/pkg/version"
    69  )
    70  
    71  var (
    72  	Operator = cell.Module(
    73  		"operator",
    74  		"Cilium Operator",
    75  
    76  		Infrastructure,
    77  		ControlPlane,
    78  	)
    79  
    80  	Infrastructure = cell.Module(
    81  		"operator-infra",
    82  		"Operator Infrastructure",
    83  
    84  		// Register the pprof HTTP handlers, to get runtime profiling data.
    85  		pprof.Cell,
    86  		cell.ProvidePrivate(func(cfg operatorPprofConfig) pprof.Config {
    87  			return pprof.Config{
    88  				Pprof:        cfg.OperatorPprof,
    89  				PprofAddress: cfg.OperatorPprofAddress,
    90  				PprofPort:    cfg.OperatorPprofPort,
    91  			}
    92  		}),
    93  		cell.Config(operatorPprofConfig{
    94  			OperatorPprofAddress: operatorOption.PprofAddressOperator,
    95  			OperatorPprofPort:    operatorOption.PprofPortOperator,
    96  		}),
    97  
    98  		// Runs the gops agent, a tool to diagnose Go processes.
    99  		gops.Cell(defaults.GopsPortOperator),
   100  
   101  		// Provides Clientset, API for accessing Kubernetes objects.
   102  		k8sClient.Cell,
   103  
   104  		// Provides a ClientBuilderFunc that can be used by other cells to create a client.
   105  		k8sClient.ClientBuilderCell,
   106  
   107  		// Provides the modular metrics registry, metric HTTP server and legacy metrics cell.
   108  		operatorMetrics.Cell,
   109  		cell.Provide(func(
   110  			operatorCfg *operatorOption.OperatorConfig,
   111  		) operatorMetrics.SharedConfig {
   112  			return operatorMetrics.SharedConfig{
   113  				// Cloud provider specific allocators needs to read operatorCfg.EnableMetrics
   114  				// to add their metrics when it's set to true. Therefore, we leave the flag as global
   115  				// instead of declaring it as part of the metrics cell.
   116  				// This should be changed once the IPAM allocator is modularized.
   117  				EnableMetrics:    operatorCfg.EnableMetrics,
   118  				EnableGatewayAPI: operatorCfg.EnableGatewayAPI,
   119  			}
   120  		}),
   121  	)
   122  
   123  	// ControlPlane implements the control functions.
   124  	ControlPlane = cell.Module(
   125  		"operator-controlplane",
   126  		"Operator Control Plane",
   127  
   128  		cell.Config(cmtypes.DefaultClusterInfo),
   129  		cell.Invoke(cmtypes.ClusterInfo.InitClusterIDMax),
   130  		cell.Invoke(cmtypes.ClusterInfo.Validate),
   131  
   132  		cell.Invoke(
   133  			registerOperatorHooks,
   134  		),
   135  
   136  		cell.Provide(func() *option.DaemonConfig {
   137  			return option.Config
   138  		}),
   139  
   140  		cell.Provide(func() *operatorOption.OperatorConfig {
   141  			return operatorOption.Config
   142  		}),
   143  
   144  		cell.Provide(func(
   145  			daemonCfg *option.DaemonConfig,
   146  			operatorCfg *operatorOption.OperatorConfig,
   147  		) identitygc.SharedConfig {
   148  			return identitygc.SharedConfig{
   149  				IdentityAllocationMode: daemonCfg.IdentityAllocationMode,
   150  			}
   151  		}),
   152  
   153  		cell.Provide(func(
   154  			daemonCfg *option.DaemonConfig,
   155  		) ciliumendpointslice.SharedConfig {
   156  			return ciliumendpointslice.SharedConfig{
   157  				EnableCiliumEndpointSlice: daemonCfg.EnableCiliumEndpointSlice,
   158  			}
   159  		}),
   160  
   161  		cell.Provide(func(
   162  			operatorCfg *operatorOption.OperatorConfig,
   163  			daemonCfg *option.DaemonConfig,
   164  		) endpointgc.SharedConfig {
   165  			return endpointgc.SharedConfig{
   166  				Interval:                 operatorCfg.EndpointGCInterval,
   167  				DisableCiliumEndpointCRD: daemonCfg.DisableCiliumEndpointCRD,
   168  			}
   169  		}),
   170  
   171  		api.HealthHandlerCell(
   172  			kvstoreEnabled,
   173  			isLeader.Load,
   174  		),
   175  		api.MetricsHandlerCell,
   176  		controller.Cell,
   177  		operatorApi.SpecCell,
   178  		api.ServerCell,
   179  
   180  		// These cells are started only after the operator is elected leader.
   181  		WithLeaderLifecycle(
   182  			// The CRDs registration should be the first operation to be invoked after the operator is elected leader.
   183  			apis.RegisterCRDsCell,
   184  			operatorK8s.ResourcesCell,
   185  
   186  			bgpv2.Cell,
   187  			lbipam.Cell,
   188  			nodeipam.Cell,
   189  			auth.Cell,
   190  			store.Cell,
   191  			operatorClusterMesh.Cell,
   192  			endpointslicesync.Cell,
   193  			mcsapi.Cell,
   194  			legacyCell,
   195  
   196  			// When running in kvstore mode, the start hook of the identity GC
   197  			// cell blocks until the kvstore client has been initialized, which
   198  			// is performed by the legacyCell start hook. Hence, the identity GC
   199  			// cell is registered afterwards, to ensure the ordering of the
   200  			// setup operations. This is a hacky workaround until the kvstore is
   201  			// refactored into a proper cell.
   202  			identitygc.Cell,
   203  
   204  			// CiliumEndpointSlice controller depends on the CiliumEndpoint and
   205  			// CiliumEndpointSlice resources. It reconciles the state of CESs in the
   206  			// cluster based on the CEPs and CESs events.
   207  			// It is disabled if CiliumEndpointSlice is disabled in the cluster -
   208  			// when --enable-cilium-endpoint-slice is false.
   209  			ciliumendpointslice.Cell,
   210  
   211  			// Cilium Endpoint Garbage Collector. It removes all leaked Cilium
   212  			// Endpoints. Either once or periodically it validates all the present
   213  			// Cilium Endpoints and delete the ones that should be deleted.
   214  			endpointgc.Cell,
   215  
   216  			// Integrates the controller-runtime library and provides its components via Hive.
   217  			controllerruntime.Cell,
   218  
   219  			// Cilium Gateway API controller that manages the Gateway API related CRDs.
   220  			gatewayapi.Cell,
   221  
   222  			// Cilium Ingress controller that manages the Kubernetes Ingress related CRDs.
   223  			ingress.Cell,
   224  
   225  			// Cilium Secret synchronizes K8s TLS Secrets referenced by
   226  			// Ciliums "Ingress resources" from the application namespaces into a dedicated
   227  			// secrets namespace that is accessible by the Cilium Agents.
   228  			// Resources might be K8s `Ingress` or Gateway API `Gateway`.
   229  			secretsync.Cell,
   230  
   231  			// Cilium L7 LoadBalancing with Envoy.
   232  			ciliumenvoyconfig.Cell,
   233  
   234  			// Informational policy validation.
   235  			networkpolicy.Cell,
   236  
   237  			// Provide the logic to map DNS names matching Kubernetes services to the
   238  			// corresponding ClusterIP, without depending on CoreDNS. Leveraged by etcd
   239  			// and clustermesh.
   240  			dial.ServiceResolverCell,
   241  		),
   242  	)
   243  
   244  	binaryName = filepath.Base(os.Args[0])
   245  
   246  	log = logging.DefaultLogger.WithField(logfields.LogSubsys, binaryName)
   247  
   248  	FlagsHooks []ProviderFlagsHooks
   249  
   250  	leaderElectionResourceLockName = "cilium-operator-resource-lock"
   251  
   252  	// Use a Go context so we can tell the leaderelection code when we
   253  	// want to step down
   254  	leaderElectionCtx       context.Context
   255  	leaderElectionCtxCancel context.CancelFunc
   256  
   257  	// isLeader is an atomic boolean value that is true when the Operator is
   258  	// elected leader. Otherwise, it is false.
   259  	isLeader atomic.Bool
   260  )
   261  
   262  func NewOperatorCmd(h *hive.Hive) *cobra.Command {
   263  	cmd := &cobra.Command{
   264  		Use:   binaryName,
   265  		Short: "Run " + binaryName,
   266  		Run: func(cobraCmd *cobra.Command, args []string) {
   267  			cmdRefDir := h.Viper().GetString(option.CMDRef)
   268  			if cmdRefDir != "" {
   269  				genMarkdown(cobraCmd, cmdRefDir)
   270  				os.Exit(0)
   271  			}
   272  
   273  			initEnv(h.Viper())
   274  
   275  			if err := h.Run(logging.DefaultSlogLogger); err != nil {
   276  				log.Fatal(err)
   277  			}
   278  		},
   279  	}
   280  
   281  	h.RegisterFlags(cmd.Flags())
   282  
   283  	// Enable fallback to direct API probing to check for support of Leases in
   284  	// case Discovery API fails.
   285  	h.Viper().Set(option.K8sEnableAPIDiscovery, true)
   286  
   287  	// Overwrite the metrics namespace with the one specific for the Operator
   288  	metrics.Namespace = metrics.CiliumOperatorNamespace
   289  
   290  	cmd.AddCommand(
   291  		MetricsCmd,
   292  		StatusCmd,
   293  		ciliumdbg.TroubleshootCmd,
   294  		h.Command(),
   295  	)
   296  
   297  	InitGlobalFlags(cmd, h.Viper())
   298  	for _, hook := range FlagsHooks {
   299  		hook.RegisterProviderFlag(cmd, h.Viper())
   300  	}
   301  
   302  	cobra.OnInitialize(option.InitConfig(cmd, "Cilium-Operator", "cilium-operators", h.Viper()))
   303  
   304  	return cmd
   305  }
   306  
   307  func Execute(cmd *cobra.Command) {
   308  	if err := cmd.Execute(); err != nil {
   309  		fmt.Println(err)
   310  		os.Exit(1)
   311  	}
   312  }
   313  
   314  func registerOperatorHooks(log *slog.Logger, lc cell.Lifecycle, llc *LeaderLifecycle, clientset k8sClient.Clientset, shutdowner hive.Shutdowner) {
   315  	var wg sync.WaitGroup
   316  	lc.Append(cell.Hook{
   317  		OnStart: func(cell.HookContext) error {
   318  			wg.Add(1)
   319  			go func() {
   320  				runOperator(log, llc, clientset, shutdowner)
   321  				wg.Done()
   322  			}()
   323  			return nil
   324  		},
   325  		OnStop: func(ctx cell.HookContext) error {
   326  			if err := llc.Stop(log, ctx); err != nil {
   327  				return err
   328  			}
   329  			doCleanup()
   330  			wg.Wait()
   331  			return nil
   332  		},
   333  	})
   334  }
   335  
   336  func initEnv(vp *viper.Viper) {
   337  	// Prepopulate option.Config with options from CLI.
   338  	option.Config.Populate(vp)
   339  	operatorOption.Config.Populate(vp)
   340  
   341  	// add hooks after setting up metrics in the option.Config
   342  	logging.DefaultLogger.Hooks.Add(metrics.NewLoggingHook())
   343  
   344  	// Logging should always be bootstrapped first. Do not add any code above this!
   345  	if err := logging.SetupLogging(option.Config.LogDriver, logging.LogOptions(option.Config.LogOpt), binaryName, option.Config.Debug); err != nil {
   346  		log.Fatal(err)
   347  	}
   348  
   349  	option.LogRegisteredOptions(vp, log)
   350  	log.Infof("Cilium Operator %s", version.Version)
   351  }
   352  
   353  func doCleanup() {
   354  	isLeader.Store(false)
   355  
   356  	// Cancelling this context here makes sure that if the operator hold the
   357  	// leader lease, it will be released.
   358  	leaderElectionCtxCancel()
   359  }
   360  
   361  // runOperator implements the logic of leader election for cilium-operator using
   362  // built-in leader election capability in kubernetes.
   363  // See: https://github.com/kubernetes/client-go/blob/master/examples/leader-election/main.go
   364  func runOperator(slog *slog.Logger, lc *LeaderLifecycle, clientset k8sClient.Clientset, shutdowner hive.Shutdowner) {
   365  	isLeader.Store(false)
   366  
   367  	leaderElectionCtx, leaderElectionCtxCancel = context.WithCancel(context.Background())
   368  
   369  	if clientset.IsEnabled() {
   370  		capabilities := k8sversion.Capabilities()
   371  		if !capabilities.MinimalVersionMet {
   372  			log.Fatalf("Minimal kubernetes version not met: %s < %s",
   373  				k8sversion.Version(), k8sversion.MinimalVersionConstraint)
   374  		}
   375  	}
   376  
   377  	// We only support Operator in HA mode for Kubernetes Versions having support for
   378  	// LeasesResourceLock.
   379  	// See docs on capabilities.LeasesResourceLock for more context.
   380  	if !k8sversion.Capabilities().LeasesResourceLock {
   381  		log.Info("Support for coordination.k8s.io/v1 not present, fallback to non HA mode")
   382  
   383  		if err := lc.Start(slog, leaderElectionCtx); err != nil {
   384  			log.WithError(err).Fatal("Failed to start leading")
   385  		}
   386  		return
   387  	}
   388  
   389  	// Get hostname for identity name of the lease lock holder.
   390  	// We identify the leader of the operator cluster using hostname.
   391  	operatorID, err := os.Hostname()
   392  	if err != nil {
   393  		log.WithError(err).Fatal("Failed to get hostname when generating lease lock identity")
   394  	}
   395  	operatorID = fmt.Sprintf("%s-%s", operatorID, rand.String(10))
   396  
   397  	ns := option.Config.K8sNamespace
   398  	// If due to any reason the CILIUM_K8S_NAMESPACE is not set we assume the operator
   399  	// to be in default namespace.
   400  	if ns == "" {
   401  		ns = metav1.NamespaceDefault
   402  	}
   403  
   404  	leResourceLock, err := resourcelock.NewFromKubeconfig(
   405  		resourcelock.LeasesResourceLock,
   406  		ns,
   407  		leaderElectionResourceLockName,
   408  		resourcelock.ResourceLockConfig{
   409  			// Identity name of the lock holder
   410  			Identity: operatorID,
   411  		},
   412  		clientset.RestConfig(),
   413  		operatorOption.Config.LeaderElectionRenewDeadline)
   414  	if err != nil {
   415  		log.WithError(err).Fatal("Failed to create resource lock for leader election")
   416  	}
   417  
   418  	// Start the leader election for running cilium-operators
   419  	log.Info("Waiting for leader election")
   420  	leaderelection.RunOrDie(leaderElectionCtx, leaderelection.LeaderElectionConfig{
   421  		Name: leaderElectionResourceLockName,
   422  
   423  		Lock:            leResourceLock,
   424  		ReleaseOnCancel: true,
   425  
   426  		LeaseDuration: operatorOption.Config.LeaderElectionLeaseDuration,
   427  		RenewDeadline: operatorOption.Config.LeaderElectionRenewDeadline,
   428  		RetryPeriod:   operatorOption.Config.LeaderElectionRetryPeriod,
   429  
   430  		Callbacks: leaderelection.LeaderCallbacks{
   431  			OnStartedLeading: func(ctx context.Context) {
   432  				if err := lc.Start(slog, ctx); err != nil {
   433  					log.WithError(err).Error("Failed to start when elected leader, shutting down")
   434  					shutdowner.Shutdown(hive.ShutdownWithError(err))
   435  				}
   436  			},
   437  			OnStoppedLeading: func() {
   438  				log.WithField("operator-id", operatorID).Info("Leader election lost")
   439  				// Cleanup everything here, and exit.
   440  				shutdowner.Shutdown(hive.ShutdownWithError(errors.New("Leader election lost")))
   441  			},
   442  			OnNewLeader: func(identity string) {
   443  				if identity == operatorID {
   444  					log.Info("Leading the operator HA deployment")
   445  				} else {
   446  					log.WithFields(logrus.Fields{
   447  						"newLeader":  identity,
   448  						"operatorID": operatorID,
   449  					}).Info("Leader re-election complete")
   450  				}
   451  			},
   452  		},
   453  	})
   454  }
   455  
   456  func kvstoreEnabled() bool {
   457  	if option.Config.KVStore == "" {
   458  		return false
   459  	}
   460  
   461  	return option.Config.IdentityAllocationMode == option.IdentityAllocationModeKVstore ||
   462  		operatorOption.Config.SyncK8sServices ||
   463  		operatorOption.Config.SyncK8sNodes
   464  }
   465  
   466  var legacyCell = cell.Invoke(registerLegacyOnLeader)
   467  
   468  func registerLegacyOnLeader(lc cell.Lifecycle, clientset k8sClient.Clientset, resources operatorK8s.Resources, factory store.Factory, svcResolver *dial.ServiceResolver) {
   469  	ctx, cancel := context.WithCancel(context.Background())
   470  	legacy := &legacyOnLeader{
   471  		ctx:          ctx,
   472  		cancel:       cancel,
   473  		clientset:    clientset,
   474  		resources:    resources,
   475  		storeFactory: factory,
   476  		svcResolver:  svcResolver,
   477  	}
   478  	lc.Append(cell.Hook{
   479  		OnStart: legacy.onStart,
   480  		OnStop:  legacy.onStop,
   481  	})
   482  }
   483  
   484  type legacyOnLeader struct {
   485  	ctx          context.Context
   486  	cancel       context.CancelFunc
   487  	clientset    k8sClient.Clientset
   488  	wg           sync.WaitGroup
   489  	resources    operatorK8s.Resources
   490  	storeFactory store.Factory
   491  	svcResolver  *dial.ServiceResolver
   492  }
   493  
   494  func (legacy *legacyOnLeader) onStop(_ cell.HookContext) error {
   495  	legacy.cancel()
   496  
   497  	// Wait for background goroutines to finish.
   498  	legacy.wg.Wait()
   499  
   500  	return nil
   501  }
   502  
   503  // OnOperatorStartLeading is the function called once the operator starts leading
   504  // in HA mode.
   505  func (legacy *legacyOnLeader) onStart(_ cell.HookContext) error {
   506  	isLeader.Store(true)
   507  
   508  	// Restart kube-dns as soon as possible since it helps etcd-operator to be
   509  	// properly setup. If kube-dns is not managed by Cilium it can prevent
   510  	// etcd from reaching out kube-dns in EKS.
   511  	// If this logic is modified, make sure the operator's clusterrole logic for
   512  	// pods/delete is also up-to-date.
   513  	if !legacy.clientset.IsEnabled() {
   514  		log.Infof("KubeDNS unmanaged pods controller disabled due to kubernetes support not enabled")
   515  	} else if option.Config.DisableCiliumEndpointCRD {
   516  		log.Infof("KubeDNS unmanaged pods controller disabled as %q option is set to 'disabled' in Cilium ConfigMap", option.DisableCiliumEndpointCRDName)
   517  	} else if operatorOption.Config.UnmanagedPodWatcherInterval != 0 {
   518  		legacy.wg.Add(1)
   519  		go func() {
   520  			defer legacy.wg.Done()
   521  			enableUnmanagedController(legacy.ctx, &legacy.wg, legacy.clientset)
   522  		}()
   523  	}
   524  
   525  	var (
   526  		nodeManager allocator.NodeEventHandler
   527  		err         error
   528  		withKVStore bool
   529  	)
   530  
   531  	log.WithField(logfields.Mode, option.Config.IPAM).Info("Initializing IPAM")
   532  
   533  	switch ipamMode := option.Config.IPAM; ipamMode {
   534  	case ipamOption.IPAMAzure,
   535  		ipamOption.IPAMENI,
   536  		ipamOption.IPAMClusterPool,
   537  		ipamOption.IPAMMultiPool,
   538  		ipamOption.IPAMAlibabaCloud:
   539  		alloc, providerBuiltin := allocatorProviders[ipamMode]
   540  		if !providerBuiltin {
   541  			log.Fatalf("%s allocator is not supported by this version of %s", ipamMode, binaryName)
   542  		}
   543  
   544  		if err := alloc.Init(legacy.ctx); err != nil {
   545  			log.WithError(err).Fatalf("Unable to init %s allocator", ipamMode)
   546  		}
   547  
   548  		if pooledAlloc, ok := alloc.(operatorWatchers.PooledAllocatorProvider); ok {
   549  			// The following operation will block until all pools are restored, thus it
   550  			// is safe to continue starting node allocation right after return.
   551  			operatorWatchers.StartIPPoolAllocator(legacy.ctx, legacy.clientset, pooledAlloc, legacy.resources.CiliumPodIPPools)
   552  		}
   553  
   554  		nm, err := alloc.Start(legacy.ctx, &ciliumNodeUpdateImplementation{legacy.clientset})
   555  		if err != nil {
   556  			log.WithError(err).Fatalf("Unable to start %s allocator", ipamMode)
   557  		}
   558  
   559  		nodeManager = nm
   560  	}
   561  
   562  	if operatorOption.Config.BGPAnnounceLBIP {
   563  		log.Info("Starting LB IP allocator")
   564  		operatorWatchers.StartBGPBetaLBIPAllocator(legacy.ctx, legacy.clientset, legacy.resources.Services)
   565  	}
   566  
   567  	if kvstoreEnabled() {
   568  		var goopts *kvstore.ExtraOptions
   569  		scopedLog := log.WithFields(logrus.Fields{
   570  			"kvstore": option.Config.KVStore,
   571  			"address": option.Config.KVStoreOpt[fmt.Sprintf("%s.address", option.Config.KVStore)],
   572  		})
   573  
   574  		if legacy.clientset.IsEnabled() && operatorOption.Config.SyncK8sServices {
   575  			clusterInfo := cmtypes.ClusterInfo{
   576  				ID:   option.Config.ClusterID,
   577  				Name: option.Config.ClusterName,
   578  			}
   579  			operatorWatchers.StartSynchronizingServices(legacy.ctx, &legacy.wg, operatorWatchers.ServiceSyncParameters{
   580  				ClusterInfo:  clusterInfo,
   581  				Clientset:    legacy.clientset,
   582  				Services:     legacy.resources.Services,
   583  				Endpoints:    legacy.resources.Endpoints,
   584  				SharedOnly:   true,
   585  				StoreFactory: legacy.storeFactory,
   586  				SyncCallback: func(_ context.Context) {},
   587  			})
   588  		}
   589  
   590  		if legacy.clientset.IsEnabled() {
   591  			// If K8s is enabled we can do the service translation automagically by
   592  			// looking at services from k8s and retrieve the service IP from that.
   593  			// This makes cilium to not depend on kube dns to interact with etcd
   594  			log := log.WithField(logfields.LogSubsys, "etcd")
   595  			goopts = &kvstore.ExtraOptions{
   596  				DialOption: []grpc.DialOption{
   597  					grpc.WithContextDialer(dial.NewContextDialer(log, legacy.svcResolver)),
   598  				},
   599  			}
   600  		}
   601  
   602  		scopedLog.Info("Connecting to kvstore")
   603  		if err := kvstore.Setup(legacy.ctx, option.Config.KVStore, option.Config.KVStoreOpt, goopts); err != nil {
   604  			scopedLog.WithError(err).Fatal("Unable to setup kvstore")
   605  		}
   606  
   607  		if legacy.clientset.IsEnabled() && operatorOption.Config.SyncK8sNodes {
   608  			withKVStore = true
   609  		}
   610  
   611  		startKvstoreWatchdog()
   612  	}
   613  
   614  	if legacy.clientset.IsEnabled() &&
   615  		(operatorOption.Config.RemoveCiliumNodeTaints || operatorOption.Config.SetCiliumIsUpCondition) {
   616  		log.WithFields(logrus.Fields{
   617  			logfields.K8sNamespace:       operatorOption.Config.CiliumK8sNamespace,
   618  			"label-selector":             operatorOption.Config.CiliumPodLabels,
   619  			"remove-cilium-node-taints":  operatorOption.Config.RemoveCiliumNodeTaints,
   620  			"set-cilium-node-taints":     operatorOption.Config.SetCiliumNodeTaints,
   621  			"set-cilium-is-up-condition": operatorOption.Config.SetCiliumIsUpCondition,
   622  		}).Info("Managing Cilium Node Taints or Setting Cilium Is Up Condition for Kubernetes Nodes")
   623  
   624  		operatorWatchers.HandleNodeTolerationAndTaints(&legacy.wg, legacy.clientset, legacy.ctx.Done())
   625  	}
   626  
   627  	ciliumNodeSynchronizer := newCiliumNodeSynchronizer(legacy.clientset, nodeManager, withKVStore)
   628  
   629  	if legacy.clientset.IsEnabled() {
   630  		if err := ciliumNodeSynchronizer.Start(legacy.ctx, &legacy.wg); err != nil {
   631  			log.WithError(err).Fatal("Unable to setup cilium node synchronizer")
   632  		}
   633  
   634  		if operatorOption.Config.NodesGCInterval != 0 {
   635  			operatorWatchers.RunCiliumNodeGC(legacy.ctx, &legacy.wg, legacy.clientset, ciliumNodeSynchronizer.ciliumNodeStore, operatorOption.Config.NodesGCInterval)
   636  		}
   637  	}
   638  
   639  	if option.Config.IPAM == ipamOption.IPAMClusterPool || option.Config.IPAM == ipamOption.IPAMMultiPool {
   640  		// We will use CiliumNodes as the source of truth for the podCIDRs.
   641  		// Once the CiliumNodes are synchronized with the operator we will
   642  		// be able to watch for K8s Node events which they will be used
   643  		// to create the remaining CiliumNodes.
   644  		<-ciliumNodeSynchronizer.ciliumNodeManagerQueueSynced
   645  
   646  		// We don't want CiliumNodes that don't have podCIDRs to be
   647  		// allocated with a podCIDR already being used by another node.
   648  		// For this reason we will call Resync after all CiliumNodes are
   649  		// synced with the operator to signal the node manager, since it
   650  		// knows all podCIDRs that are currently set in the cluster, that
   651  		// it can allocate podCIDRs for the nodes that don't have a podCIDR
   652  		// set.
   653  		nodeManager.Resync(legacy.ctx, time.Time{})
   654  	}
   655  
   656  	if option.Config.IdentityAllocationMode == option.IdentityAllocationModeCRD {
   657  		if !legacy.clientset.IsEnabled() {
   658  			log.Fatal("CRD Identity allocation mode requires k8s to be configured.")
   659  		}
   660  		if operatorOption.Config.EndpointGCInterval == 0 {
   661  			log.Fatal("Cilium Identity garbage collector requires the CiliumEndpoint garbage collector to be enabled")
   662  		}
   663  	}
   664  
   665  	if legacy.clientset.IsEnabled() {
   666  		err = enableCNPWatcher(legacy.ctx, &legacy.wg, legacy.clientset)
   667  		if err != nil {
   668  			log.WithError(err).WithField(logfields.LogSubsys, "CNPWatcher").Fatal(
   669  				"Cannot connect to Kubernetes apiserver ")
   670  		}
   671  
   672  		err = enableCCNPWatcher(legacy.ctx, &legacy.wg, legacy.clientset)
   673  		if err != nil {
   674  			log.WithError(err).WithField(logfields.LogSubsys, "CCNPWatcher").Fatal(
   675  				"Cannot connect to Kubernetes apiserver ")
   676  		}
   677  	}
   678  
   679  	log.Info("Initialization complete")
   680  	return nil
   681  }