Skip to content

Commit e981692

Browse files
committed
✨ Make leader election resourcelock configurable
1 parent ab55aa7 commit e981692

File tree

3 files changed

+59
-11
lines changed

3 files changed

+59
-11
lines changed

pkg/leaderelection/leader_election.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,32 @@ type Options struct {
3737
// starting the manager.
3838
LeaderElection bool
3939

40+
// LeaderElectionResourceLock determines which resource lock to use for leader election,
41+
// defaults to "configmapsleases".
42+
LeaderElectionResourceLock string
43+
4044
// LeaderElectionNamespace determines the namespace in which the leader
41-
// election configmap will be created.
45+
// election resource will be created.
4246
LeaderElectionNamespace string
4347

44-
// LeaderElectionID determines the name of the configmap that leader election
48+
// LeaderElectionID determines the name of the resource that leader election
4549
// will use for holding the leader lock.
4650
LeaderElectionID string
4751
}
4852

49-
// NewResourceLock creates a new config map resource lock for use in a leader
50-
// election loop
53+
// NewResourceLock creates a new resource lock for use in a leader election loop.
5154
func NewResourceLock(config *rest.Config, recorderProvider recorder.Provider, options Options) (resourcelock.Interface, error) {
5255
if !options.LeaderElection {
5356
return nil, nil
5457
}
5558

59+
// Default resource lock to "configmapsleases". We must keep this default until we are sure all controller-runtime
60+
// users have upgraded from the original default ConfigMap lock to a controller-runtime version that has this new
61+
// default. Many users of controller-runtime skip versions, so we should be extremely conservative here.
62+
if options.LeaderElectionResourceLock == "" {
63+
options.LeaderElectionResourceLock = resourcelock.ConfigMapsLeasesResourceLock
64+
}
65+
5666
// LeaderElectionID must be provided to prevent clashes
5767
if options.LeaderElectionID == "" {
5868
return nil, errors.New("LeaderElectionID must be configured")
@@ -80,8 +90,7 @@ func NewResourceLock(config *rest.Config, recorderProvider recorder.Provider, op
8090
return nil, err
8191
}
8292

83-
// TODO(JoelSpeed): switch to leaderelection object in 1.12
84-
return resourcelock.New(resourcelock.ConfigMapsLeasesResourceLock,
93+
return resourcelock.New(options.LeaderElectionResourceLock,
8594
options.LeaderElectionNamespace,
8695
options.LeaderElectionID,
8796
client.CoreV1(),

pkg/manager/manager.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,26 @@ type Options struct {
142142
// starting the manager.
143143
LeaderElection bool
144144

145+
// LeaderElectionResourceLock determines which resource lock to use for leader election,
146+
// defaults to "configmapsleases". Change this value only if you know what you are doing.
147+
// Otherwise, users of your controller might end up with multiple running instances that
148+
// each acquired leadership through different resource locks during upgrades and thus
149+
// act on the same resources concurrently.
150+
// If you want to migrate to the "leases" resource lock, you might do so by migrating to the
151+
// respective multilock first ("configmapsleases" or "endpointsleases"), which will acquire a
152+
// leader lock on both resources. After all your users have migrated to the multilock, you can
153+
// go ahead and migrate to "leases". Please also keep in mind, that users might skip versions
154+
// of your controller.
155+
//
156+
// Note: before controller-runtime version v0.7, the resource lock was set to "configmaps".
157+
// Please keep this in mind, when planning a proper migration path for your controller.
158+
LeaderElectionResourceLock string
159+
145160
// LeaderElectionNamespace determines the namespace in which the leader
146-
// election configmap will be created.
161+
// election resource will be created.
147162
LeaderElectionNamespace string
148163

149-
// LeaderElectionID determines the name of the configmap that leader election
164+
// LeaderElectionID determines the name of the resource that leader election
150165
// will use for holding the leader lock.
151166
LeaderElectionID string
152167

@@ -329,9 +344,10 @@ func New(config *rest.Config, options Options) (Manager, error) {
329344
leaderConfig = options.LeaderElectionConfig
330345
}
331346
resourceLock, err := options.newResourceLock(leaderConfig, recorderProvider, leaderelection.Options{
332-
LeaderElection: options.LeaderElection,
333-
LeaderElectionID: options.LeaderElectionID,
334-
LeaderElectionNamespace: options.LeaderElectionNamespace,
347+
LeaderElection: options.LeaderElection,
348+
LeaderElectionResourceLock: options.LeaderElectionResourceLock,
349+
LeaderElectionID: options.LeaderElectionID,
350+
LeaderElectionNamespace: options.LeaderElectionNamespace,
335351
})
336352
if err != nil {
337353
return nil, err

pkg/manager/manager_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,15 @@ var _ = Describe("manger.Manager", func() {
296296
<-m2done
297297
})
298298

299+
It("should return an error if it can't create a ResourceLock", func() {
300+
m, err := New(cfg, Options{
301+
newResourceLock: func(_ *rest.Config, _ recorder.Provider, _ leaderelection.Options) (resourcelock.Interface, error) {
302+
return nil, fmt.Errorf("expected error")
303+
},
304+
})
305+
Expect(m).To(BeNil())
306+
Expect(err).To(MatchError(ContainSubstring("expected error")))
307+
})
299308
It("should return an error if namespace not set and not running in cluster", func() {
300309
m, err := New(cfg, Options{LeaderElection: true, LeaderElectionID: "controller-runtime"})
301310
Expect(m).To(BeNil())
@@ -320,6 +329,20 @@ var _ = Describe("manger.Manager", func() {
320329
Expect(secondaryIsLeaseLock).To(BeTrue())
321330

322331
})
332+
It("should use the specified ResourceLock", func() {
333+
m, err := New(cfg, Options{
334+
LeaderElection: true,
335+
LeaderElectionResourceLock: resourcelock.LeasesResourceLock,
336+
LeaderElectionID: "controller-runtime",
337+
LeaderElectionNamespace: "my-ns",
338+
})
339+
Expect(m).ToNot(BeNil())
340+
Expect(err).ToNot(HaveOccurred())
341+
cm, ok := m.(*controllerManager)
342+
Expect(ok).To(BeTrue())
343+
_, isLeaseLock := cm.resourceLock.(*resourcelock.LeaseLock)
344+
Expect(isLeaseLock).To(BeTrue())
345+
})
323346
})
324347

325348
It("should create a listener for the metrics if a valid address is provided", func() {

0 commit comments

Comments
 (0)