Repository: kubernetes-sigs/controller-runtime

Issue:
Leverage label functionality for leader election
This is an issue to mark the fact that when https://github.com/kubernetes/kubernetes/pull/131632 is merged, I will be opening a PR to add functionality to leverage the new label functions from client-go

Discussion:

Solution patch:
diff --git a/pkg/leaderelection/leader_election.go b/pkg/leaderelection/leader_election.go
index 5cc25391..6c013e79 100644
--- a/pkg/leaderelection/leader_election.go
+++ b/pkg/leaderelection/leader_election.go
@@ -56,6 +56,10 @@ type Options struct {
 	// Without that, a single slow response from the API server can result
 	// in losing leadership.
 	RenewDeadline time.Duration
+
+	// LeaderLabels are an optional set of labels that will be set on the lease object
+	// when this replica becomes leader
+	LeaderLabels map[string]string
 }
 
 // NewResourceLock creates a new resource lock for use in a leader election loop.
@@ -63,7 +67,6 @@ func NewResourceLock(config *rest.Config, recorderProvider recorder.Provider, op
 	if !options.LeaderElection {
 		return nil, nil
 	}
-
 	// Default resource lock to "leases". The previous default (from v0.7.0 to v0.11.x) was configmapsleases, which was
 	// used to migrate from configmaps to leases. Since the default was "configmapsleases" for over a year, spanning
 	// five minor releases, any actively maintained operators are very likely to have a released version that uses
@@ -93,22 +96,21 @@ func NewResourceLock(config *rest.Config, recorderProvider recorder.Provider, op
 	}
 	id = id + "_" + string(uuid.NewUUID())
 
-	// Construct clients for leader election
-	rest.AddUserAgent(config, "leader-election")
+	// Construct config for leader election
+	config = rest.AddUserAgent(config, "leader-election")
 
+	// Timeout set for a client used to contact to Kubernetes should be lower than
+	// RenewDeadline to keep a single hung request from forcing a leader loss.
+	// Setting it to max(time.Second, RenewDeadline/2) as a reasonable heuristic.
 	if options.RenewDeadline != 0 {
-		return resourcelock.NewFromKubeconfig(options.LeaderElectionResourceLock,
-			options.LeaderElectionNamespace,
-			options.LeaderElectionID,
-			resourcelock.ResourceLockConfig{
-				Identity:      id,
-				EventRecorder: recorderProvider.GetEventRecorderFor(id),
-			},
-			config,
-			options.RenewDeadline,
-		)
+		timeout := options.RenewDeadline / 2
+		if timeout < time.Second {
+			timeout = time.Second
+		}
+		config.Timeout = timeout
 	}
 
+	// Construct clients for leader election
 	corev1Client, err := corev1client.NewForConfig(config)
 	if err != nil {
 		return nil, err
@@ -118,7 +120,8 @@ func NewResourceLock(config *rest.Config, recorderProvider recorder.Provider, op
 	if err != nil {
 		return nil, err
 	}
-	return resourcelock.New(options.LeaderElectionResourceLock,
+
+	return resourcelock.NewWithLabels(options.LeaderElectionResourceLock,
 		options.LeaderElectionNamespace,
 		options.LeaderElectionID,
 		corev1Client,
@@ -127,6 +130,7 @@ func NewResourceLock(config *rest.Config, recorderProvider recorder.Provider, op
 			Identity:      id,
 			EventRecorder: recorderProvider.GetEventRecorderFor(id),
 		},
+		options.LeaderLabels,
 	)
 }
 
diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go
index c3ae317b..a10e0d51 100644
--- a/pkg/manager/manager.go
+++ b/pkg/manager/manager.go
@@ -201,10 +201,15 @@ type Options struct {
 	// LeaseDuration time first.
 	LeaderElectionReleaseOnCancel bool
 
+	// LeaderElectionLabels allows a controller to supplement all leader election api calls with a set of custom labels based on
+	// the replica attempting to acquire leader status.
+	LeaderElectionLabels map[string]string
+
 	// LeaderElectionResourceLockInterface allows to provide a custom resourcelock.Interface that was created outside
 	// of the controller-runtime. If this value is set the options LeaderElectionID, LeaderElectionNamespace,
-	// LeaderElectionResourceLock, LeaseDuration, RenewDeadline and RetryPeriod will be ignored. This can be useful if you
-	// want to use a locking mechanism that is currently not supported, like a MultiLock across two Kubernetes clusters.
+	//  LeaderElectionResourceLock, LeaseDuration, RenewDeadline, RetryPeriod and LeaderElectionLeases will be ignored.
+	// This can be useful if you want to use a locking mechanism that is currently not supported, like a MultiLock across
+	// two Kubernetes clusters.
 	LeaderElectionResourceLockInterface resourcelock.Interface
 
 	// LeaseDuration is the duration that non-leader candidates will
@@ -390,6 +395,7 @@ func New(config *rest.Config, options Options) (Manager, error) {
 			LeaderElectionID:           options.LeaderElectionID,
 			LeaderElectionNamespace:    options.LeaderElectionNamespace,
 			RenewDeadline:              *options.RenewDeadline,
+			LeaderLabels:               options.LeaderElectionLabels,
 		})
 		if err != nil {
 			return nil, err



Test patch:
diff --git a/pkg/manager/manager_test.go b/pkg/manager/manager_test.go
index 247a33f9..db161952 100644
--- a/pkg/manager/manager_test.go
+++ b/pkg/manager/manager_test.go
@@ -498,6 +498,25 @@ var _ = Describe("manger.Manager", func() {
 				Expect(err).ToNot(HaveOccurred())
 				Expect(record.HolderIdentity).To(BeEmpty())
 			})
+			It("should set the leaselocks's label field when LeaderElectionLabels is set", func() {
+				labels := map[string]string{"my-key": "my-val"}
+				m, err := New(cfg, Options{
+					LeaderElection:             true,
+					LeaderElectionResourceLock: resourcelock.LeasesResourceLock,
+					LeaderElectionID:           "controller-runtime",
+					LeaderElectionNamespace:    "default",
+					LeaderElectionLabels:       labels,
+				})
+				Expect(err).ToNot(HaveOccurred())
+				Expect(m).ToNot(BeNil())
+				cm, ok := m.(*controllerManager)
+				Expect(ok).To(BeTrue())
+				ll, isLeaseLock := cm.resourceLock.(*resourcelock.LeaseLock)
+				Expect(isLeaseLock).To(BeTrue())
+				val, exists := ll.Labels["my-key"]
+				Expect(exists).To(BeTrue())
+				Expect(val).To(Equal("my-val"))
+			})
 			When("using a custom LeaderElectionResourceLockInterface", func() {
 				It("should use the custom LeaderElectionResourceLockInterface", func() {
 					rl, err := fakeleaderelection.NewResourceLock(nil, nil, leaderelection.Options{})