sigs.k8s.io/controller-runtime@v0.18.2/pkg/leaderelection/leader_election.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 leaderelection
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"os"
    23  
    24  	"k8s.io/apimachinery/pkg/util/uuid"
    25  	coordinationv1client "k8s.io/client-go/kubernetes/typed/coordination/v1"
    26  	corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
    27  	"k8s.io/client-go/rest"
    28  	"k8s.io/client-go/tools/leaderelection/resourcelock"
    29  
    30  	"sigs.k8s.io/controller-runtime/pkg/recorder"
    31  )
    32  
    33  const inClusterNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
    34  
    35  // Options provides the required configuration to create a new resource lock.
    36  type Options struct {
    37  	// LeaderElection determines whether or not to use leader election when
    38  	// starting the manager.
    39  	LeaderElection bool
    40  
    41  	// LeaderElectionResourceLock determines which resource lock to use for leader election,
    42  	// defaults to "leases".
    43  	LeaderElectionResourceLock string
    44  
    45  	// LeaderElectionNamespace determines the namespace in which the leader
    46  	// election resource will be created.
    47  	LeaderElectionNamespace string
    48  
    49  	// LeaderElectionID determines the name of the resource that leader election
    50  	// will use for holding the leader lock.
    51  	LeaderElectionID string
    52  }
    53  
    54  // NewResourceLock creates a new resource lock for use in a leader election loop.
    55  func NewResourceLock(config *rest.Config, recorderProvider recorder.Provider, options Options) (resourcelock.Interface, error) {
    56  	if !options.LeaderElection {
    57  		return nil, nil
    58  	}
    59  
    60  	// Default resource lock to "leases". The previous default (from v0.7.0 to v0.11.x) was configmapsleases, which was
    61  	// used to migrate from configmaps to leases. Since the default was "configmapsleases" for over a year, spanning
    62  	// five minor releases, any actively maintained operators are very likely to have a released version that uses
    63  	// "configmapsleases". Therefore defaulting to "leases" should be safe.
    64  	if options.LeaderElectionResourceLock == "" {
    65  		options.LeaderElectionResourceLock = resourcelock.LeasesResourceLock
    66  	}
    67  
    68  	// LeaderElectionID must be provided to prevent clashes
    69  	if options.LeaderElectionID == "" {
    70  		return nil, errors.New("LeaderElectionID must be configured")
    71  	}
    72  
    73  	// Default the namespace (if running in cluster)
    74  	if options.LeaderElectionNamespace == "" {
    75  		var err error
    76  		options.LeaderElectionNamespace, err = getInClusterNamespace()
    77  		if err != nil {
    78  			return nil, fmt.Errorf("unable to find leader election namespace: %w", err)
    79  		}
    80  	}
    81  
    82  	// Leader id, needs to be unique
    83  	id, err := os.Hostname()
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	id = id + "_" + string(uuid.NewUUID())
    88  
    89  	// Construct clients for leader election
    90  	rest.AddUserAgent(config, "leader-election")
    91  	corev1Client, err := corev1client.NewForConfig(config)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	coordinationClient, err := coordinationv1client.NewForConfig(config)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	return resourcelock.New(options.LeaderElectionResourceLock,
   102  		options.LeaderElectionNamespace,
   103  		options.LeaderElectionID,
   104  		corev1Client,
   105  		coordinationClient,
   106  		resourcelock.ResourceLockConfig{
   107  			Identity:      id,
   108  			EventRecorder: recorderProvider.GetEventRecorderFor(id),
   109  		})
   110  }
   111  
   112  func getInClusterNamespace() (string, error) {
   113  	// Check whether the namespace file exists.
   114  	// If not, we are not running in cluster so can't guess the namespace.
   115  	if _, err := os.Stat(inClusterNamespacePath); os.IsNotExist(err) {
   116  		return "", fmt.Errorf("not running in-cluster, please specify LeaderElectionNamespace")
   117  	} else if err != nil {
   118  		return "", fmt.Errorf("error checking namespace file: %w", err)
   119  	}
   120  
   121  	// Load the namespace file and return its content
   122  	namespace, err := os.ReadFile(inClusterNamespacePath)
   123  	if err != nil {
   124  		return "", fmt.Errorf("error reading namespace file: %w", err)
   125  	}
   126  	return string(namespace), nil
   127  }