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 }