Skip to content

Commit 66c86ac

Browse files
committed
feat: Make LeaderElection configurable. (#260)
This also fixes #254. When no leader election is used, the operator is always considered "leader". Furthermore, if leader election is configured, the RBAC generator includes V1Lease and V1Deployment rules.
1 parent 4fe94a2 commit 66c86ac

File tree

6 files changed

+60
-6
lines changed

6 files changed

+60
-6
lines changed

src/KubeOps/Operator/Builder/OperatorBuilder.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,15 @@ internal IOperatorBuilder AddOperatorBase(OperatorSettings settings)
215215
.ForwardToPrometheus();
216216

217217
// Support for leader election via V1Leases.
218-
Services.AddHostedService<LeaderElector>();
219-
Services.AddSingleton<ILeaderElection, LeaderElection>();
218+
if (settings.EnableLeaderElection)
219+
{
220+
Services.AddHostedService<LeaderElector>();
221+
Services.AddSingleton<ILeaderElection, LeaderElection>();
222+
}
223+
else
224+
{
225+
Services.AddSingleton<ILeaderElection, DisabledLeaderElection>();
226+
}
220227

221228
// Register event handler
222229
Services.AddTransient<IEventManager, EventManager>();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.Reactive.Linq;
3+
using System.Reactive.Subjects;
4+
5+
namespace KubeOps.Operator.Leadership
6+
{
7+
internal class DisabledLeaderElection : ILeaderElection, IDisposable
8+
{
9+
private readonly BehaviorSubject<LeaderState> _leadershipChange = new(LeaderState.Leader);
10+
11+
public IObservable<LeaderState> LeadershipChange => _leadershipChange.DistinctUntilChanged();
12+
13+
public void Dispose()
14+
{
15+
_leadershipChange.Dispose();
16+
}
17+
18+
void ILeaderElection.LeadershipChanged(LeaderState state)
19+
{
20+
}
21+
}
22+
}

src/KubeOps/Operator/Leadership/LeaderElector.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
namespace KubeOps.Operator.Leadership
1818
{
19-
[EntityRbac(typeof(V1Lease), Verbs = RbacVerb.All)]
20-
[EntityRbac(typeof(V1Deployment), Verbs = RbacVerb.Get | RbacVerb.List)]
2119
internal class LeaderElector : IHostedService
2220
{
2321
private readonly ILogger<LeaderElector> _logger;

src/KubeOps/Operator/OperatorSettings.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,21 @@ public sealed class OperatorSettings
5555
/// </summary>
5656
public string ReadinessEndpoint { get; set; } = "/ready";
5757

58+
/// <summary>
59+
/// <para>
60+
/// Defines if the leader elector should run. You may disable this,
61+
/// if you don't intend to run your operator multiple times.
62+
/// </para>
63+
/// <para>
64+
/// If this is disabled, and an operator runs in multiple instance
65+
/// (in the same namespace) it can lead to a "split brain" problem.
66+
/// </para>
67+
/// <para>
68+
/// This could be disabled when developing locally.
69+
/// </para>
70+
/// </summary>
71+
public bool EnableLeaderElection { get; set; } = true;
72+
5873
/// <summary>
5974
/// The interval in seconds in which this particular instance of the operator
6075
/// will check for leader election.

src/KubeOps/Operator/Rbac/RbacBuilder.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ internal class RbacBuilder : IRbacBuilder
1212
private readonly List<Type> _componentTypes;
1313

1414
private readonly bool _hasWebhooks;
15+
private readonly bool _hasLeaderElection;
1516

16-
public RbacBuilder(IComponentRegistrar componentRegistrar)
17+
public RbacBuilder(IComponentRegistrar componentRegistrar, OperatorSettings settings)
1718
{
1819
var controllerTypes = componentRegistrar.ControllerRegistrations
1920
.Select(t => t.ControllerType)
@@ -37,6 +38,7 @@ public RbacBuilder(IComponentRegistrar componentRegistrar)
3738
.ToList();
3839

3940
_hasWebhooks = validatorTypes.Any() || mutatorTypes.Any();
41+
_hasLeaderElection = settings.EnableLeaderElection;
4042
}
4143

4244
public V1ClusterRole BuildManagerRbac()
@@ -61,6 +63,16 @@ public V1ClusterRole BuildManagerRbac()
6163
rules = rules.Concat(servicePolicies).ToList();
6264
}
6365

66+
if (_hasLeaderElection)
67+
{
68+
rules = rules
69+
.Concat(new EntityRbacAttribute(typeof(V1Lease)) { Verbs = RbacVerb.All }.CreateRbacPolicies())
70+
.Concat(
71+
new EntityRbacAttribute(typeof(V1Deployment)) { Verbs = RbacVerb.Get | RbacVerb.List }
72+
.CreateRbacPolicies())
73+
.ToList();
74+
}
75+
6476
return new V1ClusterRole(
6577
null,
6678
$"{V1ClusterRole.KubeGroup}/{V1ClusterRole.KubeApiVersion}",

tests/KubeOps.TestOperator/Startup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public class Startup
99
{
1010
public void ConfigureServices(IServiceCollection services)
1111
{
12-
services.AddKubernetesOperator().AddWebhookLocaltunnel();
12+
services.AddKubernetesOperator(s => s.EnableLeaderElection = false).AddWebhookLocaltunnel();
1313
services.AddTransient<IManager, TestManager.TestManager>();
1414
}
1515

0 commit comments

Comments
 (0)