diff --git a/cmd/appgw-ingress/main.go b/cmd/appgw-ingress/main.go index 6acb91651..47cd6d774 100644 --- a/cmd/appgw-ingress/main.go +++ b/cmd/appgw-ingress/main.go @@ -6,8 +6,12 @@ package main import ( + "context" "flag" "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/leaderelection" + "k8s.io/client-go/tools/leaderelection/resourcelock" "os" "os/signal" "strconv" @@ -224,20 +228,64 @@ func main() { metricStore, env.HTTPServicePort) httpServer.Start() - - if err := appGwIngressController.Start(env); err != nil { - errorLine := fmt.Sprint("Could not start AGIC: ", err) - if agicPod != nil { - recorder.Event(agicPod, v1.EventTypeWarning, events.ReasonARMAuthFailure, errorLine) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go runWithLeaderElection(ctx, kubeClient, env, func(ctx context.Context) { + if err := appGwIngressController.Start(env); err != nil { + errorLine := fmt.Sprint("Could not start AGIC: ", err) + if agicPod != nil { + recorder.Event(agicPod, v1.EventTypeWarning, events.ReasonARMAuthFailure, errorLine) + } + klog.Fatal(errorLine) } - klog.Fatal(errorLine) - } + }, appGwIngressController.Stop) sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) <-sigChan - appGwIngressController.Stop() httpServer.Stop() klog.Info("Goodbye!") } + +func runWithLeaderElection(ctx context.Context, kubeClient *kubernetes.Clientset, env environment.EnvVariables, start func(ctx context.Context), stop func()) { + + id, err := os.Hostname() + if err != nil { + klog.Fatalf("Error getting hostname: %v", err) + } + + lock := &resourcelock.LeaseLock{ + LeaseMeta: metav1.ObjectMeta{ + Name: env.IngressClassControllerName + "-lease", + Namespace: env.AGICPodNamespace, + }, + Client: kubeClient.CoordinationV1(), + LockConfig: resourcelock.ResourceLockConfig{ + Identity: id, + }, + } + + leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{ + Lock: lock, + ReleaseOnCancel: true, + LeaseDuration: 15 * time.Second, + RenewDeadline: 10 * time.Second, + RetryPeriod: 2 * time.Second, + Callbacks: leaderelection.LeaderCallbacks{ + OnStartedLeading: func(ctx context.Context) { + klog.Infof("Became leader: %s", id) + start(ctx) + }, + OnStoppedLeading: func() { + klog.Infof("Leader lost: %s", id) + stop() + }, + OnNewLeader: func(identity string) { + if identity != id { + klog.Infof("New leader elected: %s", identity) + } + }, + }, + }) +} diff --git a/helm/ingress-azure/templates/deployment.yaml b/helm/ingress-azure/templates/deployment.yaml index 13a836d91..d65c59b57 100644 --- a/helm/ingress-azure/templates/deployment.yaml +++ b/helm/ingress-azure/templates/deployment.yaml @@ -8,7 +8,7 @@ metadata: heritage: {{ .Release.Service }} release: {{ .Release.Name }} spec: - replicas: 1 # TODO: Make configurable when leader election is supported. + replicas: {{ .Values.kubernetes.replicas }} selector: matchLabels: app: {{ template "application-gateway-kubernetes-ingress.name" . }} diff --git a/helm/ingress-azure/templates/poddisruptionbudget.yaml b/helm/ingress-azure/templates/poddisruptionbudget.yaml new file mode 100644 index 000000000..612c94d1b --- /dev/null +++ b/helm/ingress-azure/templates/poddisruptionbudget.yaml @@ -0,0 +1,9 @@ +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ template "application-gateway-kubernetes-ingress.fullname" . }} +spec: + maxUnavailable: 1 + selector: + matchLabels: + release: {{ .Release.Name }} \ No newline at end of file diff --git a/helm/ingress-azure/values.yaml b/helm/ingress-azure/values.yaml index d9cbf7f9a..6b7ac0e18 100644 --- a/helm/ingress-azure/values.yaml +++ b/helm/ingress-azure/values.yaml @@ -12,6 +12,8 @@ image: kubernetes: + # Replicas for AGIC pod + replicas: 1 # Namespace(s) AGIC watches; Leaving this blank watches all namespaces; # Accepts one or many comma-separated values