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  }