istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/bootstrap/configcontroller.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package bootstrap
    16  
    17  import (
    18  	"fmt"
    19  	"net/url"
    20  
    21  	"google.golang.org/grpc"
    22  	"google.golang.org/grpc/credentials/insecure"
    23  
    24  	meshconfig "istio.io/api/mesh/v1alpha1"
    25  	"istio.io/istio/pilot/pkg/autoregistration"
    26  	configaggregate "istio.io/istio/pilot/pkg/config/aggregate"
    27  	"istio.io/istio/pilot/pkg/config/kube/crdclient"
    28  	"istio.io/istio/pilot/pkg/config/kube/gateway"
    29  	ingress "istio.io/istio/pilot/pkg/config/kube/ingress"
    30  	"istio.io/istio/pilot/pkg/config/memory"
    31  	configmonitor "istio.io/istio/pilot/pkg/config/monitor"
    32  	"istio.io/istio/pilot/pkg/features"
    33  	"istio.io/istio/pilot/pkg/leaderelection"
    34  	"istio.io/istio/pilot/pkg/model"
    35  	"istio.io/istio/pilot/pkg/status/distribution"
    36  	"istio.io/istio/pkg/adsc"
    37  	"istio.io/istio/pkg/config/analysis/incluster"
    38  	"istio.io/istio/pkg/config/schema/collections"
    39  	"istio.io/istio/pkg/config/schema/gvr"
    40  	"istio.io/istio/pkg/log"
    41  	"istio.io/istio/pkg/revisions"
    42  )
    43  
    44  // URL schemes supported by the config store
    45  type ConfigSourceAddressScheme string
    46  
    47  const (
    48  	// fs:///PATH will load local files. This replaces --configDir.
    49  	// example fs:///tmp/configroot
    50  	// PATH can be mounted from a config map or volume
    51  	File ConfigSourceAddressScheme = "fs"
    52  	// xds://ADDRESS - load XDS-over-MCP sources
    53  	// example xds://127.0.0.1:49133
    54  	XDS ConfigSourceAddressScheme = "xds"
    55  	// k8s:// - load in-cluster k8s controller
    56  	// example k8s://
    57  	Kubernetes ConfigSourceAddressScheme = "k8s"
    58  )
    59  
    60  // initConfigController creates the config controller in the pilotConfig.
    61  func (s *Server) initConfigController(args *PilotArgs) error {
    62  	s.initStatusController(args, features.EnableStatus && features.EnableDistributionTracking)
    63  	meshConfig := s.environment.Mesh()
    64  	if len(meshConfig.ConfigSources) > 0 {
    65  		// Using MCP for config.
    66  		if err := s.initConfigSources(args); err != nil {
    67  			return err
    68  		}
    69  	} else if args.RegistryOptions.FileDir != "" {
    70  		// Local files - should be added even if other options are specified
    71  		store := memory.Make(collections.Pilot)
    72  		configController := memory.NewController(store)
    73  
    74  		err := s.makeFileMonitor(args.RegistryOptions.FileDir, args.RegistryOptions.KubeOptions.DomainSuffix, configController)
    75  		if err != nil {
    76  			return err
    77  		}
    78  		s.ConfigStores = append(s.ConfigStores, configController)
    79  	} else {
    80  		err := s.initK8SConfigStore(args)
    81  		if err != nil {
    82  			return err
    83  		}
    84  	}
    85  
    86  	// If running in ingress mode (requires k8s), wrap the config controller.
    87  	if hasKubeRegistry(args.RegistryOptions.Registries) && meshConfig.IngressControllerMode != meshconfig.MeshConfig_OFF {
    88  		// Wrap the config controller with a cache.
    89  		// Supporting only Ingress/v1 means we lose support of Kubernetes 1.18
    90  		// Supporting only Ingress/v1beta1 means we lose support of Kubernetes 1.22
    91  		// Since supporting both in a monolith controller is painful due to lack of usable conversion logic between
    92  		// the two versions.
    93  		// As a compromise, we instead just fork the controller. Once 1.18 support is no longer needed, we can drop the old controller
    94  		s.ConfigStores = append(s.ConfigStores,
    95  			ingress.NewController(s.kubeClient, s.environment.Watcher, args.RegistryOptions.KubeOptions))
    96  
    97  		s.addTerminatingStartFunc("ingress status", func(stop <-chan struct{}) error {
    98  			leaderelection.
    99  				NewLeaderElection(args.Namespace, args.PodName, leaderelection.IngressController, args.Revision, s.kubeClient).
   100  				AddRunFunction(func(leaderStop <-chan struct{}) {
   101  					ingressSyncer := ingress.NewStatusSyncer(s.environment.Watcher, s.kubeClient)
   102  					// Start informers again. This fixes the case where informers for namespace do not start,
   103  					// as we create them only after acquiring the leader lock
   104  					// Note: stop here should be the overall pilot stop, NOT the leader election stop. We are
   105  					// basically lazy loading the informer, if we stop it when we lose the lock we will never
   106  					// recreate it again.
   107  					s.kubeClient.RunAndWait(stop)
   108  					log.Infof("Starting ingress controller")
   109  					ingressSyncer.Run(leaderStop)
   110  				}).
   111  				Run(stop)
   112  			return nil
   113  		})
   114  	}
   115  
   116  	// Wrap the config controller with a cache.
   117  	aggregateConfigController, err := configaggregate.MakeCache(s.ConfigStores)
   118  	if err != nil {
   119  		return err
   120  	}
   121  	s.configController = aggregateConfigController
   122  
   123  	// Create the config store.
   124  	s.environment.ConfigStore = aggregateConfigController
   125  
   126  	// Defer starting the controller until after the service is created.
   127  	s.addStartFunc("config controller", func(stop <-chan struct{}) error {
   128  		go s.configController.Run(stop)
   129  		return nil
   130  	})
   131  
   132  	return nil
   133  }
   134  
   135  func (s *Server) initK8SConfigStore(args *PilotArgs) error {
   136  	if s.kubeClient == nil {
   137  		return nil
   138  	}
   139  	configController := s.makeKubeConfigController(args)
   140  	s.ConfigStores = append(s.ConfigStores, configController)
   141  	if features.EnableGatewayAPI {
   142  		if s.statusManager == nil && features.EnableGatewayAPIStatus {
   143  			s.initStatusManager(args)
   144  		}
   145  		gwc := gateway.NewController(s.kubeClient, configController, s.kubeClient.CrdWatcher().WaitForCRD,
   146  			s.environment.CredentialsController, args.RegistryOptions.KubeOptions)
   147  		s.environment.GatewayAPIController = gwc
   148  		s.ConfigStores = append(s.ConfigStores, s.environment.GatewayAPIController)
   149  		s.addTerminatingStartFunc("gateway status", func(stop <-chan struct{}) error {
   150  			leaderelection.
   151  				NewLeaderElection(args.Namespace, args.PodName, leaderelection.GatewayStatusController, args.Revision, s.kubeClient).
   152  				AddRunFunction(func(leaderStop <-chan struct{}) {
   153  					log.Infof("Starting gateway status writer")
   154  					gwc.SetStatusWrite(true, s.statusManager)
   155  
   156  					// Trigger a push so we can recompute status
   157  					s.XDSServer.ConfigUpdate(&model.PushRequest{
   158  						Full:   true,
   159  						Reason: model.NewReasonStats(model.GlobalUpdate),
   160  					})
   161  					<-leaderStop
   162  					log.Infof("Stopping gateway status writer")
   163  					gwc.SetStatusWrite(false, nil)
   164  				}).
   165  				Run(stop)
   166  			return nil
   167  		})
   168  		if features.EnableGatewayAPIDeploymentController {
   169  			s.addTerminatingStartFunc("gateway deployment controller", func(stop <-chan struct{}) error {
   170  				leaderelection.
   171  					NewPerRevisionLeaderElection(args.Namespace, args.PodName, leaderelection.GatewayDeploymentController, args.Revision, s.kubeClient).
   172  					AddRunFunction(func(leaderStop <-chan struct{}) {
   173  						// We can only run this if the Gateway CRD is created
   174  						if s.kubeClient.CrdWatcher().WaitForCRD(gvr.KubernetesGateway, leaderStop) {
   175  							tagWatcher := revisions.NewTagWatcher(s.kubeClient, args.Revision)
   176  							controller := gateway.NewDeploymentController(s.kubeClient, s.clusterID, s.environment,
   177  								s.webhookInfo.getWebhookConfig, s.webhookInfo.addHandler, tagWatcher, args.Revision)
   178  							// Start informers again. This fixes the case where informers for namespace do not start,
   179  							// as we create them only after acquiring the leader lock
   180  							// Note: stop here should be the overall pilot stop, NOT the leader election stop. We are
   181  							// basically lazy loading the informer, if we stop it when we lose the lock we will never
   182  							// recreate it again.
   183  							s.kubeClient.RunAndWait(stop)
   184  							go tagWatcher.Run(leaderStop)
   185  							controller.Run(leaderStop)
   186  						}
   187  					}).
   188  					Run(stop)
   189  				return nil
   190  			})
   191  		}
   192  	}
   193  	if features.EnableAnalysis {
   194  		if err := s.initInprocessAnalysisController(args); err != nil {
   195  			return err
   196  		}
   197  	}
   198  	var err error
   199  	s.RWConfigStore, err = configaggregate.MakeWriteableCache(s.ConfigStores, configController)
   200  	if err != nil {
   201  		return err
   202  	}
   203  	s.XDSServer.WorkloadEntryController = autoregistration.NewController(configController, args.PodName, args.KeepaliveOptions.MaxServerConnectionAge)
   204  	return nil
   205  }
   206  
   207  // initConfigSources will process mesh config 'configSources' and initialize
   208  // associated configs.
   209  func (s *Server) initConfigSources(args *PilotArgs) (err error) {
   210  	for _, configSource := range s.environment.Mesh().ConfigSources {
   211  		srcAddress, err := url.Parse(configSource.Address)
   212  		if err != nil {
   213  			return fmt.Errorf("invalid config URL %s %v", configSource.Address, err)
   214  		}
   215  		scheme := ConfigSourceAddressScheme(srcAddress.Scheme)
   216  		switch scheme {
   217  		case File:
   218  			if srcAddress.Path == "" {
   219  				return fmt.Errorf("invalid fs config URL %s, contains no file path", configSource.Address)
   220  			}
   221  			store := memory.Make(collections.Pilot)
   222  			configController := memory.NewController(store)
   223  
   224  			err := s.makeFileMonitor(srcAddress.Path, args.RegistryOptions.KubeOptions.DomainSuffix, configController)
   225  			if err != nil {
   226  				return err
   227  			}
   228  			s.ConfigStores = append(s.ConfigStores, configController)
   229  			log.Infof("Started File configSource %s", configSource.Address)
   230  		case XDS:
   231  			xdsMCP, err := adsc.New(srcAddress.Host, &adsc.ADSConfig{
   232  				InitialDiscoveryRequests: adsc.ConfigInitialRequests(),
   233  				Config: adsc.Config{
   234  					Namespace: args.Namespace,
   235  					Workload:  args.PodName,
   236  					Revision:  args.Revision,
   237  					Meta: model.NodeMetadata{
   238  						Generator: "api",
   239  						// To reduce transported data if upstream server supports. Especially for custom servers.
   240  						IstioRevision: args.Revision,
   241  					}.ToStruct(),
   242  					GrpcOpts: []grpc.DialOption{
   243  						args.KeepaliveOptions.ConvertToClientOption(),
   244  						// Because we use the custom grpc options for adsc, here we should
   245  						// explicitly set transport credentials.
   246  						// TODO: maybe we should use the tls settings within ConfigSource
   247  						// to secure the connection between istiod and remote xds server.
   248  						grpc.WithTransportCredentials(insecure.NewCredentials()),
   249  					},
   250  				},
   251  			})
   252  			if err != nil {
   253  				return fmt.Errorf("failed to dial XDS %s %v", configSource.Address, err)
   254  			}
   255  			store := memory.Make(collections.Pilot)
   256  			// TODO: enable namespace filter for memory controller
   257  			configController := memory.NewController(store)
   258  			configController.RegisterHasSyncedHandler(xdsMCP.HasSynced)
   259  			xdsMCP.Store = configController
   260  			err = xdsMCP.Run()
   261  			if err != nil {
   262  				return fmt.Errorf("MCP: failed running %v", err)
   263  			}
   264  			s.ConfigStores = append(s.ConfigStores, configController)
   265  			log.Infof("Started XDS configSource %s", configSource.Address)
   266  		case Kubernetes:
   267  			if srcAddress.Path == "" || srcAddress.Path == "/" {
   268  				err2 := s.initK8SConfigStore(args)
   269  				if err2 != nil {
   270  					log.Warnf("Error loading k8s: %v", err2)
   271  					return err2
   272  				}
   273  				log.Infof("Started Kubernetes configSource %s", configSource.Address)
   274  			} else {
   275  				log.Warnf("Not implemented, ignore: %v", configSource.Address)
   276  				// TODO: handle k8s:// scheme for remote cluster. Use same mechanism as service registry,
   277  				// using the cluster name as key to match a secret.
   278  			}
   279  		default:
   280  			log.Warnf("Ignoring unsupported config source: %v", configSource.Address)
   281  		}
   282  	}
   283  	return nil
   284  }
   285  
   286  // initInprocessAnalysisController spins up an instance of Galley which serves no purpose other than
   287  // running Analyzers for status updates.  The Status Updater will eventually need to allow input from istiod
   288  // to support config distribution status as well.
   289  func (s *Server) initInprocessAnalysisController(args *PilotArgs) error {
   290  	if s.statusManager == nil {
   291  		s.initStatusManager(args)
   292  	}
   293  	s.addStartFunc("analysis controller", func(stop <-chan struct{}) error {
   294  		go leaderelection.
   295  			NewLeaderElection(args.Namespace, args.PodName, leaderelection.AnalyzeController, args.Revision, s.kubeClient).
   296  			AddRunFunction(func(leaderStop <-chan struct{}) {
   297  				cont, err := incluster.NewController(leaderStop, s.RWConfigStore,
   298  					s.kubeClient, args.Revision, args.Namespace, s.statusManager, args.RegistryOptions.KubeOptions.DomainSuffix)
   299  				if err != nil {
   300  					return
   301  				}
   302  				cont.Run(leaderStop)
   303  			}).Run(stop)
   304  		return nil
   305  	})
   306  	return nil
   307  }
   308  
   309  func (s *Server) initStatusController(args *PilotArgs, writeStatus bool) {
   310  	if s.statusManager == nil && writeStatus {
   311  		s.initStatusManager(args)
   312  	}
   313  	if features.EnableDistributionTracking {
   314  		s.statusReporter = &distribution.Reporter{
   315  			UpdateInterval: features.StatusUpdateInterval,
   316  			PodName:        args.PodName,
   317  		}
   318  		s.addStartFunc("status reporter init", func(stop <-chan struct{}) error {
   319  			s.statusReporter.Init(s.environment.GetLedger(), stop)
   320  			return nil
   321  		})
   322  		s.addTerminatingStartFunc("status reporter", func(stop <-chan struct{}) error {
   323  			if writeStatus {
   324  				s.statusReporter.Start(s.kubeClient.Kube(), args.Namespace, args.PodName, stop)
   325  			}
   326  			return nil
   327  		})
   328  		s.XDSServer.StatusReporter = s.statusReporter
   329  	}
   330  	if writeStatus {
   331  		s.addTerminatingStartFunc("status distribution", func(stop <-chan struct{}) error {
   332  			leaderelection.
   333  				NewLeaderElection(args.Namespace, args.PodName, leaderelection.StatusController, args.Revision, s.kubeClient).
   334  				AddRunFunction(func(leaderStop <-chan struct{}) {
   335  					// Controller should be created for calling the run function every time, so it can
   336  					// avoid concurrently calling of informer Run() for controller in controller.Start
   337  					controller := distribution.NewController(s.kubeClient.RESTConfig(), args.Namespace, s.RWConfigStore, s.statusManager)
   338  					s.statusReporter.SetController(controller)
   339  					controller.Start(leaderStop)
   340  				}).Run(stop)
   341  			return nil
   342  		})
   343  	}
   344  }
   345  
   346  func (s *Server) makeKubeConfigController(args *PilotArgs) *crdclient.Client {
   347  	opts := crdclient.Option{
   348  		Revision:     args.Revision,
   349  		DomainSuffix: args.RegistryOptions.KubeOptions.DomainSuffix,
   350  		Identifier:   "crd-controller",
   351  	}
   352  	return crdclient.New(s.kubeClient, opts)
   353  }
   354  
   355  func (s *Server) makeFileMonitor(fileDir string, domainSuffix string, configController model.ConfigStore) error {
   356  	fileSnapshot := configmonitor.NewFileSnapshot(fileDir, collections.Pilot, domainSuffix)
   357  	fileMonitor := configmonitor.NewMonitor("file-monitor", configController, fileSnapshot.ReadConfigFiles, fileDir)
   358  
   359  	// Defer starting the file monitor until after the service is created.
   360  	s.addStartFunc("file monitor", func(stop <-chan struct{}) error {
   361  		fileMonitor.Start(stop)
   362  		return nil
   363  	})
   364  
   365  	return nil
   366  }