diff --git a/changelog/fragments/ansible-leader-election.yaml b/changelog/fragments/ansible-leader-election.yaml new file mode 100644 index 0000000000..ce8b5e82c3 --- /dev/null +++ b/changelog/fragments/ansible-leader-election.yaml @@ -0,0 +1,9 @@ +# entries is a list of entries to include in +# release notes and/or the migration guide +entries: + - description: > + In the Ansible operator, use controller-runtime's lease-based leader + election instead of SDK's leader-for-life mechnism. + + kind: "change" + breaking: false diff --git a/cmd/ansible-operator/main.go b/cmd/ansible-operator/main.go index f259823050..5d97582df4 100644 --- a/cmd/ansible-operator/main.go +++ b/cmd/ansible-operator/main.go @@ -15,7 +15,6 @@ package main import ( - "context" "fmt" "os" "runtime" @@ -40,7 +39,6 @@ import ( "github.com/operator-framework/operator-sdk/pkg/ansible/proxy/controllermap" "github.com/operator-framework/operator-sdk/pkg/ansible/runner" "github.com/operator-framework/operator-sdk/pkg/ansible/watches" - "github.com/operator-framework/operator-sdk/pkg/leader" "github.com/operator-framework/operator-sdk/pkg/log/zap" sdkVersion "github.com/operator-framework/operator-sdk/version" ) @@ -71,11 +69,26 @@ func main() { os.Exit(1) } + // Deprecated: OPERATOR_NAME environment variable is an artifact of the + // legacy operator-sdk project scaffolding. Flag `--leader-election-id` + // should be used instead. + if operatorName, found := os.LookupEnv("OPERATOR_NAME"); found { + log.Info("Environment variable OPERATOR_NAME has been deprecated, use --leader-election-id instead.") + if pflag.CommandLine.Lookup("leader-election-id").Changed { + log.Info("Ignoring OPERATOR_NAME environment variable since --leader-election-id is set") + } else { + f.LeaderElectionID = operatorName + } + } + // Set default manager options // TODO: probably should expose the host & port as an environment variables options := manager.Options{ - HealthProbeBindAddress: fmt.Sprintf("%s:%d", metricsHost, healthProbePort), - MetricsBindAddress: f.MetricsAddress, + HealthProbeBindAddress: fmt.Sprintf("%s:%d", metricsHost, healthProbePort), + MetricsBindAddress: f.MetricsAddress, + LeaderElection: f.EnableLeaderElection, + LeaderElectionID: f.LeaderElectionID, + LeaderElectionNamespace: f.LeaderElectionNamespace, NewClient: func(cache cache.Cache, config *rest.Config, options client.Options) (client.Client, error) { c, err := client.New(config, options) if err != nil { @@ -152,19 +165,6 @@ func main() { }, w.Blacklist) } - operatorName, err := k8sutil.GetOperatorName() - if err != nil { - log.Error(err, "Failed to get the operator name") - os.Exit(1) - } - - // Become the leader before proceeding - err = leader.Become(context.TODO(), operatorName+"-lock") - if err != nil { - log.Error(err, "Failed to become leader.") - os.Exit(1) - } - err = mgr.AddHealthzCheck("ping", healthz.Ping) if err != nil { log.Error(err, "Failed to add Healthz check.") diff --git a/internal/scaffold/ansible/deploy_operator.go b/internal/scaffold/ansible/deploy_operator.go index 3cde60554f..b3f4bdffe0 100644 --- a/internal/scaffold/ansible/deploy_operator.go +++ b/internal/scaffold/ansible/deploy_operator.go @@ -58,6 +58,9 @@ spec: - name: [[.ProjectName]] # Replace this with the built image name image: "REPLACE_IMAGE" + args: + - "--enable-leader-election" + - "--leader-election-id=[[.ProjectName]]" imagePullPolicy: "Always" volumeMounts: - mountPath: /tmp/ansible-operator/runner diff --git a/pkg/ansible/flags/flag.go b/pkg/ansible/flags/flag.go index a645567e0f..30795bc1bd 100644 --- a/pkg/ansible/flags/flag.go +++ b/pkg/ansible/flags/flag.go @@ -28,11 +28,14 @@ type Flags struct { ReconcilePeriod time.Duration WatchesFile string InjectOwnerRef bool + EnableLeaderElection bool MaxConcurrentReconciles int AnsibleVerbosity int AnsibleRolesPath string AnsibleCollectionsPath string MetricsAddress string + LeaderElectionID string + LeaderElectionNamespace string } const AnsibleRolesPathEnvVar = "ANSIBLE_ROLES_PATH" @@ -81,5 +84,22 @@ func (f *Flags) AddTo(flagSet *pflag.FlagSet) { ":8080", "The address the metric endpoint binds to", ) - + flagSet.BoolVar(&f.EnableLeaderElection, + "enable-leader-election", + false, + "Enable leader election for controller manager. Enabling this will"+ + " ensure there is only one active controller manager.", + ) + flagSet.StringVar(&f.LeaderElectionID, + "leader-election-id", + "", + "Name of the configmap that is used for holding the leader lock.", + ) + flagSet.StringVar(&f.LeaderElectionNamespace, + "leader-election-namespace", + "", + "Namespace in which to create the leader election configmap for"+ + " holding the leader lock (required if running locally with leader"+ + " election enabled).", + ) }