sigs.k8s.io/controller-runtime@v0.18.2/pkg/manager/internal.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package manager 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "net" 24 "net/http" 25 "net/http/pprof" 26 "sync" 27 "sync/atomic" 28 "time" 29 30 "github.com/go-logr/logr" 31 "k8s.io/apimachinery/pkg/api/meta" 32 "k8s.io/apimachinery/pkg/runtime" 33 kerrors "k8s.io/apimachinery/pkg/util/errors" 34 "k8s.io/client-go/rest" 35 "k8s.io/client-go/tools/leaderelection" 36 "k8s.io/client-go/tools/leaderelection/resourcelock" 37 "k8s.io/client-go/tools/record" 38 39 "sigs.k8s.io/controller-runtime/pkg/cache" 40 "sigs.k8s.io/controller-runtime/pkg/client" 41 "sigs.k8s.io/controller-runtime/pkg/cluster" 42 "sigs.k8s.io/controller-runtime/pkg/config" 43 "sigs.k8s.io/controller-runtime/pkg/healthz" 44 "sigs.k8s.io/controller-runtime/pkg/internal/httpserver" 45 intrec "sigs.k8s.io/controller-runtime/pkg/internal/recorder" 46 metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 47 "sigs.k8s.io/controller-runtime/pkg/webhook" 48 ) 49 50 const ( 51 // Values taken from: https://github.com/kubernetes/component-base/blob/master/config/v1alpha1/defaults.go 52 defaultLeaseDuration = 15 * time.Second 53 defaultRenewDeadline = 10 * time.Second 54 defaultRetryPeriod = 2 * time.Second 55 defaultGracefulShutdownPeriod = 30 * time.Second 56 57 defaultReadinessEndpoint = "/readyz" 58 defaultLivenessEndpoint = "/healthz" 59 ) 60 61 var _ Runnable = &controllerManager{} 62 63 type controllerManager struct { 64 sync.Mutex 65 started bool 66 67 stopProcedureEngaged *int64 68 errChan chan error 69 runnables *runnables 70 71 // cluster holds a variety of methods to interact with a cluster. Required. 72 cluster cluster.Cluster 73 74 // recorderProvider is used to generate event recorders that will be injected into Controllers 75 // (and EventHandlers, Sources and Predicates). 76 recorderProvider *intrec.Provider 77 78 // resourceLock forms the basis for leader election 79 resourceLock resourcelock.Interface 80 81 // leaderElectionReleaseOnCancel defines if the manager should step back from the leader lease 82 // on shutdown 83 leaderElectionReleaseOnCancel bool 84 85 // metricsServer is used to serve prometheus metrics 86 metricsServer metricsserver.Server 87 88 // healthProbeListener is used to serve liveness probe 89 healthProbeListener net.Listener 90 91 // Readiness probe endpoint name 92 readinessEndpointName string 93 94 // Liveness probe endpoint name 95 livenessEndpointName string 96 97 // Readyz probe handler 98 readyzHandler *healthz.Handler 99 100 // Healthz probe handler 101 healthzHandler *healthz.Handler 102 103 // pprofListener is used to serve pprof 104 pprofListener net.Listener 105 106 // controllerConfig are the global controller options. 107 controllerConfig config.Controller 108 109 // Logger is the logger that should be used by this manager. 110 // If none is set, it defaults to log.Log global logger. 111 logger logr.Logger 112 113 // leaderElectionStopped is an internal channel used to signal the stopping procedure that the 114 // LeaderElection.Run(...) function has returned and the shutdown can proceed. 115 leaderElectionStopped chan struct{} 116 117 // leaderElectionCancel is used to cancel the leader election. It is distinct from internalStopper, 118 // because for safety reasons we need to os.Exit() when we lose the leader election, meaning that 119 // it must be deferred until after gracefulShutdown is done. 120 leaderElectionCancel context.CancelFunc 121 122 // elected is closed when this manager becomes the leader of a group of 123 // managers, either because it won a leader election or because no leader 124 // election was configured. 125 elected chan struct{} 126 127 webhookServer webhook.Server 128 // webhookServerOnce will be called in GetWebhookServer() to optionally initialize 129 // webhookServer if unset, and Add() it to controllerManager. 130 webhookServerOnce sync.Once 131 132 // leaderElectionID is the name of the resource that leader election 133 // will use for holding the leader lock. 134 leaderElectionID string 135 // leaseDuration is the duration that non-leader candidates will 136 // wait to force acquire leadership. 137 leaseDuration time.Duration 138 // renewDeadline is the duration that the acting controlplane will retry 139 // refreshing leadership before giving up. 140 renewDeadline time.Duration 141 // retryPeriod is the duration the LeaderElector clients should wait 142 // between tries of actions. 143 retryPeriod time.Duration 144 145 // gracefulShutdownTimeout is the duration given to runnable to stop 146 // before the manager actually returns on stop. 147 gracefulShutdownTimeout time.Duration 148 149 // onStoppedLeading is callled when the leader election lease is lost. 150 // It can be overridden for tests. 151 onStoppedLeading func() 152 153 // shutdownCtx is the context that can be used during shutdown. It will be cancelled 154 // after the gracefulShutdownTimeout ended. It must not be accessed before internalStop 155 // is closed because it will be nil. 156 shutdownCtx context.Context 157 158 internalCtx context.Context 159 internalCancel context.CancelFunc 160 161 // internalProceduresStop channel is used internally to the manager when coordinating 162 // the proper shutdown of servers. This channel is also used for dependency injection. 163 internalProceduresStop chan struct{} 164 } 165 166 type hasCache interface { 167 Runnable 168 GetCache() cache.Cache 169 } 170 171 // Add sets dependencies on i, and adds it to the list of Runnables to start. 172 func (cm *controllerManager) Add(r Runnable) error { 173 cm.Lock() 174 defer cm.Unlock() 175 return cm.add(r) 176 } 177 178 func (cm *controllerManager) add(r Runnable) error { 179 return cm.runnables.Add(r) 180 } 181 182 // AddMetricsServerExtraHandler adds extra handler served on path to the http server that serves metrics. 183 func (cm *controllerManager) AddMetricsServerExtraHandler(path string, handler http.Handler) error { 184 cm.Lock() 185 defer cm.Unlock() 186 if cm.started { 187 return fmt.Errorf("unable to add new metrics handler because metrics endpoint has already been created") 188 } 189 if cm.metricsServer == nil { 190 cm.GetLogger().Info("warn: metrics server is currently disabled, registering extra handler %q will be ignored", path) 191 return nil 192 } 193 if err := cm.metricsServer.AddExtraHandler(path, handler); err != nil { 194 return err 195 } 196 cm.logger.V(2).Info("Registering metrics http server extra handler", "path", path) 197 return nil 198 } 199 200 // AddHealthzCheck allows you to add Healthz checker. 201 func (cm *controllerManager) AddHealthzCheck(name string, check healthz.Checker) error { 202 cm.Lock() 203 defer cm.Unlock() 204 205 if cm.started { 206 return fmt.Errorf("unable to add new checker because healthz endpoint has already been created") 207 } 208 209 if cm.healthzHandler == nil { 210 cm.healthzHandler = &healthz.Handler{Checks: map[string]healthz.Checker{}} 211 } 212 213 cm.healthzHandler.Checks[name] = check 214 return nil 215 } 216 217 // AddReadyzCheck allows you to add Readyz checker. 218 func (cm *controllerManager) AddReadyzCheck(name string, check healthz.Checker) error { 219 cm.Lock() 220 defer cm.Unlock() 221 222 if cm.started { 223 return fmt.Errorf("unable to add new checker because healthz endpoint has already been created") 224 } 225 226 if cm.readyzHandler == nil { 227 cm.readyzHandler = &healthz.Handler{Checks: map[string]healthz.Checker{}} 228 } 229 230 cm.readyzHandler.Checks[name] = check 231 return nil 232 } 233 234 func (cm *controllerManager) GetHTTPClient() *http.Client { 235 return cm.cluster.GetHTTPClient() 236 } 237 238 func (cm *controllerManager) GetConfig() *rest.Config { 239 return cm.cluster.GetConfig() 240 } 241 242 func (cm *controllerManager) GetClient() client.Client { 243 return cm.cluster.GetClient() 244 } 245 246 func (cm *controllerManager) GetScheme() *runtime.Scheme { 247 return cm.cluster.GetScheme() 248 } 249 250 func (cm *controllerManager) GetFieldIndexer() client.FieldIndexer { 251 return cm.cluster.GetFieldIndexer() 252 } 253 254 func (cm *controllerManager) GetCache() cache.Cache { 255 return cm.cluster.GetCache() 256 } 257 258 func (cm *controllerManager) GetEventRecorderFor(name string) record.EventRecorder { 259 return cm.cluster.GetEventRecorderFor(name) 260 } 261 262 func (cm *controllerManager) GetRESTMapper() meta.RESTMapper { 263 return cm.cluster.GetRESTMapper() 264 } 265 266 func (cm *controllerManager) GetAPIReader() client.Reader { 267 return cm.cluster.GetAPIReader() 268 } 269 270 func (cm *controllerManager) GetWebhookServer() webhook.Server { 271 cm.webhookServerOnce.Do(func() { 272 if cm.webhookServer == nil { 273 panic("webhook should not be nil") 274 } 275 if err := cm.Add(cm.webhookServer); err != nil { 276 panic(fmt.Sprintf("unable to add webhook server to the controller manager: %s", err)) 277 } 278 }) 279 return cm.webhookServer 280 } 281 282 func (cm *controllerManager) GetLogger() logr.Logger { 283 return cm.logger 284 } 285 286 func (cm *controllerManager) GetControllerOptions() config.Controller { 287 return cm.controllerConfig 288 } 289 290 func (cm *controllerManager) addHealthProbeServer() error { 291 mux := http.NewServeMux() 292 srv := httpserver.New(mux) 293 294 if cm.readyzHandler != nil { 295 mux.Handle(cm.readinessEndpointName, http.StripPrefix(cm.readinessEndpointName, cm.readyzHandler)) 296 // Append '/' suffix to handle subpaths 297 mux.Handle(cm.readinessEndpointName+"/", http.StripPrefix(cm.readinessEndpointName, cm.readyzHandler)) 298 } 299 if cm.healthzHandler != nil { 300 mux.Handle(cm.livenessEndpointName, http.StripPrefix(cm.livenessEndpointName, cm.healthzHandler)) 301 // Append '/' suffix to handle subpaths 302 mux.Handle(cm.livenessEndpointName+"/", http.StripPrefix(cm.livenessEndpointName, cm.healthzHandler)) 303 } 304 305 return cm.add(&Server{ 306 Name: "health probe", 307 Server: srv, 308 Listener: cm.healthProbeListener, 309 }) 310 } 311 312 func (cm *controllerManager) addPprofServer() error { 313 mux := http.NewServeMux() 314 srv := httpserver.New(mux) 315 316 mux.HandleFunc("/debug/pprof/", pprof.Index) 317 mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 318 mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 319 mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 320 mux.HandleFunc("/debug/pprof/trace", pprof.Trace) 321 322 return cm.add(&Server{ 323 Name: "pprof", 324 Server: srv, 325 Listener: cm.pprofListener, 326 }) 327 } 328 329 // Start starts the manager and waits indefinitely. 330 // There is only two ways to have start return: 331 // An error has occurred during in one of the internal operations, 332 // such as leader election, cache start, webhooks, and so on. 333 // Or, the context is cancelled. 334 func (cm *controllerManager) Start(ctx context.Context) (err error) { 335 cm.Lock() 336 if cm.started { 337 cm.Unlock() 338 return errors.New("manager already started") 339 } 340 cm.started = true 341 342 var ready bool 343 defer func() { 344 // Only unlock the manager if we haven't reached 345 // the internal readiness condition. 346 if !ready { 347 cm.Unlock() 348 } 349 }() 350 351 // Initialize the internal context. 352 cm.internalCtx, cm.internalCancel = context.WithCancel(ctx) 353 354 // This chan indicates that stop is complete, in other words all runnables have returned or timeout on stop request 355 stopComplete := make(chan struct{}) 356 defer close(stopComplete) 357 // This must be deferred after closing stopComplete, otherwise we deadlock. 358 defer func() { 359 // https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/gettyimages-459889618-1533579787.jpg 360 stopErr := cm.engageStopProcedure(stopComplete) 361 if stopErr != nil { 362 if err != nil { 363 // Utilerrors.Aggregate allows to use errors.Is for all contained errors 364 // whereas fmt.Errorf allows wrapping at most one error which means the 365 // other one can not be found anymore. 366 err = kerrors.NewAggregate([]error{err, stopErr}) 367 } else { 368 err = stopErr 369 } 370 } 371 }() 372 373 // Add the cluster runnable. 374 if err := cm.add(cm.cluster); err != nil { 375 return fmt.Errorf("failed to add cluster to runnables: %w", err) 376 } 377 378 // Metrics should be served whether the controller is leader or not. 379 // (If we don't serve metrics for non-leaders, prometheus will still scrape 380 // the pod but will get a connection refused). 381 if cm.metricsServer != nil { 382 // Note: We are adding the metrics server directly to HTTPServers here as matching on the 383 // metricsserver.Server interface in cm.runnables.Add would be very brittle. 384 if err := cm.runnables.HTTPServers.Add(cm.metricsServer, nil); err != nil { 385 return fmt.Errorf("failed to add metrics server: %w", err) 386 } 387 } 388 389 // Serve health probes. 390 if cm.healthProbeListener != nil { 391 if err := cm.addHealthProbeServer(); err != nil { 392 return fmt.Errorf("failed to add health probe server: %w", err) 393 } 394 } 395 396 // Add pprof server 397 if cm.pprofListener != nil { 398 if err := cm.addPprofServer(); err != nil { 399 return fmt.Errorf("failed to add pprof server: %w", err) 400 } 401 } 402 403 // First start any HTTP servers, which includes health probes, metrics and profiling if enabled. 404 // 405 // WARNING: HTTPServers includes the health probes, which MUST start before any cache is populated, otherwise 406 // it would block conversion webhooks to be ready for serving which make the cache never get ready. 407 logCtx := logr.NewContext(cm.internalCtx, cm.logger) 408 if err := cm.runnables.HTTPServers.Start(logCtx); err != nil { 409 return fmt.Errorf("failed to start HTTP servers: %w", err) 410 } 411 412 // Start any webhook servers, which includes conversion, validation, and defaulting 413 // webhooks that are registered. 414 // 415 // WARNING: Webhooks MUST start before any cache is populated, otherwise there is a race condition 416 // between conversion webhooks and the cache sync (usually initial list) which causes the webhooks 417 // to never start because no cache can be populated. 418 if err := cm.runnables.Webhooks.Start(cm.internalCtx); err != nil { 419 return fmt.Errorf("failed to start webhooks: %w", err) 420 } 421 422 // Start and wait for caches. 423 if err := cm.runnables.Caches.Start(cm.internalCtx); err != nil { 424 return fmt.Errorf("failed to start caches: %w", err) 425 } 426 427 // Start the non-leaderelection Runnables after the cache has synced. 428 if err := cm.runnables.Others.Start(cm.internalCtx); err != nil { 429 return fmt.Errorf("failed to start other runnables: %w", err) 430 } 431 432 // Start the leader election and all required runnables. 433 { 434 ctx, cancel := context.WithCancel(context.Background()) 435 cm.leaderElectionCancel = cancel 436 go func() { 437 if cm.resourceLock != nil { 438 if err := cm.startLeaderElection(ctx); err != nil { 439 cm.errChan <- err 440 } 441 } else { 442 // Treat not having leader election enabled the same as being elected. 443 if err := cm.startLeaderElectionRunnables(); err != nil { 444 cm.errChan <- err 445 } 446 close(cm.elected) 447 } 448 }() 449 } 450 451 ready = true 452 cm.Unlock() 453 select { 454 case <-ctx.Done(): 455 // We are done 456 return nil 457 case err := <-cm.errChan: 458 // Error starting or running a runnable 459 return err 460 } 461 } 462 463 // engageStopProcedure signals all runnables to stop, reads potential errors 464 // from the errChan and waits for them to end. It must not be called more than once. 465 func (cm *controllerManager) engageStopProcedure(stopComplete <-chan struct{}) error { 466 if !atomic.CompareAndSwapInt64(cm.stopProcedureEngaged, 0, 1) { 467 return errors.New("stop procedure already engaged") 468 } 469 470 // Populate the shutdown context, this operation MUST be done before 471 // closing the internalProceduresStop channel. 472 // 473 // The shutdown context immediately expires if the gracefulShutdownTimeout is not set. 474 var shutdownCancel context.CancelFunc 475 if cm.gracefulShutdownTimeout < 0 { 476 // We want to wait forever for the runnables to stop. 477 cm.shutdownCtx, shutdownCancel = context.WithCancel(context.Background()) 478 } else { 479 cm.shutdownCtx, shutdownCancel = context.WithTimeout(context.Background(), cm.gracefulShutdownTimeout) 480 } 481 defer shutdownCancel() 482 483 // Start draining the errors before acquiring the lock to make sure we don't deadlock 484 // if something that has the lock is blocked on trying to write into the unbuffered 485 // channel after something else already wrote into it. 486 var closeOnce sync.Once 487 go func() { 488 for { 489 // Closing in the for loop is required to avoid race conditions between 490 // the closure of all internal procedures and making sure to have a reader off the error channel. 491 closeOnce.Do(func() { 492 // Cancel the internal stop channel and wait for the procedures to stop and complete. 493 close(cm.internalProceduresStop) 494 cm.internalCancel() 495 }) 496 select { 497 case err, ok := <-cm.errChan: 498 if ok { 499 cm.logger.Error(err, "error received after stop sequence was engaged") 500 } 501 case <-stopComplete: 502 return 503 } 504 } 505 }() 506 507 // We want to close this after the other runnables stop, because we don't 508 // want things like leader election to try and emit events on a closed 509 // channel 510 defer cm.recorderProvider.Stop(cm.shutdownCtx) 511 defer func() { 512 // Cancel leader election only after we waited. It will os.Exit() the app for safety. 513 if cm.resourceLock != nil { 514 // After asking the context to be cancelled, make sure 515 // we wait for the leader stopped channel to be closed, otherwise 516 // we might encounter race conditions between this code 517 // and the event recorder, which is used within leader election code. 518 cm.leaderElectionCancel() 519 <-cm.leaderElectionStopped 520 } 521 }() 522 523 go func() { 524 // First stop the non-leader election runnables. 525 cm.logger.Info("Stopping and waiting for non leader election runnables") 526 cm.runnables.Others.StopAndWait(cm.shutdownCtx) 527 528 // Stop all the leader election runnables, which includes reconcilers. 529 cm.logger.Info("Stopping and waiting for leader election runnables") 530 // Prevent leader election when shutting down a non-elected manager 531 cm.runnables.LeaderElection.startOnce.Do(func() {}) 532 cm.runnables.LeaderElection.StopAndWait(cm.shutdownCtx) 533 534 // Stop the caches before the leader election runnables, this is an important 535 // step to make sure that we don't race with the reconcilers by receiving more events 536 // from the API servers and enqueueing them. 537 cm.logger.Info("Stopping and waiting for caches") 538 cm.runnables.Caches.StopAndWait(cm.shutdownCtx) 539 540 // Webhooks and internal HTTP servers should come last, as they might be still serving some requests. 541 cm.logger.Info("Stopping and waiting for webhooks") 542 cm.runnables.Webhooks.StopAndWait(cm.shutdownCtx) 543 544 cm.logger.Info("Stopping and waiting for HTTP servers") 545 cm.runnables.HTTPServers.StopAndWait(cm.shutdownCtx) 546 547 // Proceed to close the manager and overall shutdown context. 548 cm.logger.Info("Wait completed, proceeding to shutdown the manager") 549 shutdownCancel() 550 }() 551 552 <-cm.shutdownCtx.Done() 553 if err := cm.shutdownCtx.Err(); err != nil && !errors.Is(err, context.Canceled) { 554 if errors.Is(err, context.DeadlineExceeded) { 555 if cm.gracefulShutdownTimeout > 0 { 556 return fmt.Errorf("failed waiting for all runnables to end within grace period of %s: %w", cm.gracefulShutdownTimeout, err) 557 } 558 return nil 559 } 560 // For any other error, return the error. 561 return err 562 } 563 564 return nil 565 } 566 567 func (cm *controllerManager) startLeaderElectionRunnables() error { 568 return cm.runnables.LeaderElection.Start(cm.internalCtx) 569 } 570 571 func (cm *controllerManager) startLeaderElection(ctx context.Context) (err error) { 572 l, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{ 573 Lock: cm.resourceLock, 574 LeaseDuration: cm.leaseDuration, 575 RenewDeadline: cm.renewDeadline, 576 RetryPeriod: cm.retryPeriod, 577 Callbacks: leaderelection.LeaderCallbacks{ 578 OnStartedLeading: func(_ context.Context) { 579 if err := cm.startLeaderElectionRunnables(); err != nil { 580 cm.errChan <- err 581 return 582 } 583 close(cm.elected) 584 }, 585 OnStoppedLeading: func() { 586 if cm.onStoppedLeading != nil { 587 cm.onStoppedLeading() 588 } 589 // Make sure graceful shutdown is skipped if we lost the leader lock without 590 // intending to. 591 cm.gracefulShutdownTimeout = time.Duration(0) 592 // Most implementations of leader election log.Fatal() here. 593 // Since Start is wrapped in log.Fatal when called, we can just return 594 // an error here which will cause the program to exit. 595 cm.errChan <- errors.New("leader election lost") 596 }, 597 }, 598 ReleaseOnCancel: cm.leaderElectionReleaseOnCancel, 599 Name: cm.leaderElectionID, 600 }) 601 if err != nil { 602 return err 603 } 604 605 // Start the leader elector process 606 go func() { 607 l.Run(ctx) 608 <-ctx.Done() 609 close(cm.leaderElectionStopped) 610 }() 611 return nil 612 } 613 614 func (cm *controllerManager) Elected() <-chan struct{} { 615 return cm.elected 616 }