Skip to content

Commit 8e99942

Browse files
authored
Merge pull request #128 from M00nF1sh/election
add leaderElection
2 parents f565f3f + 6cdc5a2 commit 8e99942

File tree

5 files changed

+161
-36
lines changed

5 files changed

+161
-36
lines changed

cmd/app-mesh-controller/root.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@ import (
2323
)
2424

2525
var (
26-
cfgFile string
27-
master string
28-
kubeconfig string
29-
region string
30-
threadiness int
26+
cfgFile string
27+
master string
28+
kubeconfig string
29+
region string
30+
threadiness int
31+
leaderElection bool
32+
leaderElectionID string
33+
leaderElectionNamespace string
3134
)
3235

3336
func init() {
@@ -37,10 +40,16 @@ func init() {
3740
rootCmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "Path to your kubeconfig")
3841
rootCmd.Flags().StringVar(&region, "aws-region", "", "AWS Region")
3942
rootCmd.Flags().IntVar(&threadiness, "threadiness", controller.DefaultThreadiness, "Worker concurrency.")
43+
rootCmd.Flags().BoolVar(&leaderElection, "election", controller.DefaultElection, `Whether to do leader election for controller`)
44+
rootCmd.Flags().StringVar(&leaderElectionID, "election-id", controller.DefaultElectionID, "Namespace of leader-election configmap for ingress controller")
45+
rootCmd.Flags().StringVar(&leaderElectionNamespace, "election-namespace", controller.DefaultElectionNamespace, "Namespace of leader-election configmap for ingress controller. If unspecified, the namespace of this controller pod will be used")
4046

4147
viper.BindPFlag("master", rootCmd.Flags().Lookup("master"))
4248
viper.BindPFlag("kubeconfig", rootCmd.Flags().Lookup("kubeconfig"))
4349
viper.BindPFlag("aws-region", rootCmd.Flags().Lookup("aws-region"))
50+
viper.BindPFlag("election", rootCmd.Flags().Lookup("election"))
51+
viper.BindPFlag("election-id", rootCmd.Flags().Lookup("election-id"))
52+
viper.BindPFlag("election-namespace", rootCmd.Flags().Lookup("election-namespace"))
4453
}
4554

4655
func main() {
@@ -77,7 +86,7 @@ var rootCmd = &cobra.Command{
7786
klog.V(1).Infof("FLAG: --%s=%q", flag.Name, flag.Value)
7887
})
7988

80-
var stopCh chan struct{}
89+
stopCh := make(chan struct{})
8190

8291
cfg, err := getConfig()
8392
if err != nil {
@@ -114,6 +123,9 @@ var rootCmd = &cobra.Command{
114123
meshInformerFactory.Appmesh().V1beta1().VirtualNodes(),
115124
meshInformerFactory.Appmesh().V1beta1().VirtualServices(),
116125
stats,
126+
leaderElection,
127+
leaderElectionID,
128+
leaderElectionNamespace,
117129
)
118130

119131
if err != nil {

deploy/all.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,13 @@ rules:
599599
- apiGroups: [""]
600600
resources: ["pods"]
601601
verbs: ["*"]
602+
- apiGroups: [""]
603+
resources: ["configmaps"]
604+
verbs: ["create"]
605+
- apiGroups: [""]
606+
resources: ["configmaps"]
607+
resourceNames: ["app-mesh-controller-leader"]
608+
verbs: ["*"]
602609
- apiGroups: ["appmesh.k8s.aws"]
603610
resources: ["meshes", "virtualnodes", "virtualservices", "meshes/status", "virtualnodes/status", "virtualservices/status"]
604611
verbs: ["*"]

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSN
5555
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
5656
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
5757
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
58+
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
5859
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5960
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
6061
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=

pkg/controller/controller.go

Lines changed: 110 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import (
55
"fmt"
66
"time"
77

8+
"k8s.io/apimachinery/pkg/util/uuid"
9+
"k8s.io/client-go/tools/leaderelection"
10+
"k8s.io/client-go/tools/leaderelection/resourcelock"
11+
812
appmeshv1beta1 "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/apis/appmesh/v1beta1"
913
"github.com/aws/aws-app-mesh-controller-for-k8s/pkg/aws"
1014
meshclientset "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/client/clientset/versioned"
@@ -29,7 +33,11 @@ import (
2933

3034
const (
3135
//setting default threadiness to number of go-routines
32-
DefaultThreadiness = 5
36+
DefaultThreadiness = 5
37+
DefaultElection = true
38+
DefaultElectionID = "app-mesh-controller-leader"
39+
DefaultElectionNamespace = ""
40+
3341
controllerAgentName = "app-mesh-controller"
3442
meshDeletionFinalizerName = "meshDeletion.finalizers.appmesh.k8s.aws"
3543
virtualNodeDeletionFinalizerName = "virtualNodeDeletion.finalizers.appmesh.k8s.aws"
@@ -73,6 +81,18 @@ type Controller struct {
7381

7482
// stats records mesh Prometheus metrics
7583
stats *metrics.Recorder
84+
85+
// LeaderElection determines whether or not to use leader election when
86+
// starting the manager.
87+
leaderElection bool
88+
89+
// LeaderElectionID determines the name of the configmap that leader election
90+
// will use for holding the leader lock.
91+
leaderElectionID string
92+
93+
// LeaderElectionNamespace determines the namespace in which the leader
94+
// election configmap will be created.
95+
leaderElectionNamespace string
7696
}
7797

7898
func NewController(
@@ -83,7 +103,10 @@ func NewController(
83103
meshInformer meshinformers.MeshInformer,
84104
virtualNodeInformer meshinformers.VirtualNodeInformer,
85105
virtualServiceInformer meshinformers.VirtualServiceInformer,
86-
stats *metrics.Recorder) (*Controller, error) {
106+
stats *metrics.Recorder,
107+
leaderElection bool,
108+
leaderElectionID string,
109+
leaderElectionNamespace string) (*Controller, error) {
87110

88111
utilruntime.Must(meshscheme.AddToScheme(scheme.Scheme))
89112
klog.V(4).Info("Creating event broadcaster")
@@ -93,24 +116,27 @@ func NewController(
93116
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName})
94117

95118
controller := &Controller{
96-
name: controllerAgentName,
97-
cloud: cloud,
98-
kubeclientset: kubeclientset,
99-
meshclientset: meshclientset,
100-
podsLister: podInformer.Lister(),
101-
podsSynced: podInformer.Informer().HasSynced,
102-
meshLister: meshInformer.Lister(),
103-
meshSynced: meshInformer.Informer().HasSynced,
104-
virtualNodeLister: virtualNodeInformer.Lister(),
105-
virtualNodeSynced: virtualNodeInformer.Informer().HasSynced,
106-
virtualServiceLister: virtualServiceInformer.Lister(),
107-
virtualServiceSynced: virtualServiceInformer.Informer().HasSynced,
108-
mq: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
109-
nq: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
110-
sq: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
111-
pq: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
112-
recorder: recorder,
113-
stats: stats,
119+
name: controllerAgentName,
120+
cloud: cloud,
121+
kubeclientset: kubeclientset,
122+
meshclientset: meshclientset,
123+
podsLister: podInformer.Lister(),
124+
podsSynced: podInformer.Informer().HasSynced,
125+
meshLister: meshInformer.Lister(),
126+
meshSynced: meshInformer.Informer().HasSynced,
127+
virtualNodeLister: virtualNodeInformer.Lister(),
128+
virtualNodeSynced: virtualNodeInformer.Informer().HasSynced,
129+
virtualServiceLister: virtualServiceInformer.Lister(),
130+
virtualServiceSynced: virtualServiceInformer.Informer().HasSynced,
131+
mq: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
132+
nq: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
133+
sq: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
134+
pq: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
135+
recorder: recorder,
136+
stats: stats,
137+
leaderElection: leaderElection,
138+
leaderElectionID: leaderElectionID,
139+
leaderElectionNamespace: leaderElectionNamespace,
114140
}
115141

116142
podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
@@ -198,21 +224,76 @@ func (c *Controller) Run(threadiness int, stopCh chan struct{}) error {
198224
return fmt.Errorf("failed to wait for caches to sync")
199225
}
200226

227+
ctx, cancel := context.WithCancel(context.Background())
228+
go func() {
229+
select {
230+
case <-stopCh:
231+
cancel()
232+
case <-ctx.Done():
233+
}
234+
}()
235+
if c.leaderElection {
236+
return c.runWorkersWithLeaderElection(ctx, threadiness)
237+
}
238+
c.runWorkers(ctx, threadiness)
239+
return nil
240+
}
241+
242+
func (c *Controller) runWorkersWithLeaderElection(ctx context.Context, threadiness int) error {
243+
leaderElectionNamespace := c.leaderElectionNamespace
244+
if leaderElectionNamespace == "" {
245+
var err error
246+
if leaderElectionNamespace, err = getInClusterNamespace(); err != nil {
247+
return err
248+
}
249+
}
250+
251+
leaderElectionIdentity := string(uuid.NewUUID())
252+
leaderElectionLock, err := resourcelock.New(resourcelock.ConfigMapsResourceLock,
253+
leaderElectionNamespace,
254+
c.leaderElectionID,
255+
c.kubeclientset.CoreV1(),
256+
c.kubeclientset.CoordinationV1(),
257+
resourcelock.ResourceLockConfig{
258+
Identity: leaderElectionIdentity,
259+
})
260+
if err != nil {
261+
return err
262+
}
263+
elector, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{
264+
Lock: leaderElectionLock,
265+
LeaseDuration: 60 * time.Second,
266+
RenewDeadline: 15 * time.Second,
267+
RetryPeriod: 5 * time.Second,
268+
Callbacks: leaderelection.LeaderCallbacks{
269+
OnStartedLeading: func(ctx context.Context) {
270+
c.runWorkers(ctx, threadiness)
271+
},
272+
OnStoppedLeading: func() {
273+
klog.Info("losing leader")
274+
},
275+
},
276+
})
277+
if err != nil {
278+
return err
279+
}
280+
elector.Run(ctx)
281+
return nil
282+
}
283+
284+
func (c *Controller) runWorkers(ctx context.Context, threadiness int) {
201285
klog.Info("Starting workers")
202286
// Launch workers to process Mesh resources
203287
for i := 0; i < threadiness; i++ {
204-
go wait.Until(c.meshWorker, time.Second, stopCh)
205-
go wait.Until(c.vNodeWorker, time.Second, stopCh)
206-
go wait.Until(c.vServiceWorker, time.Second, stopCh)
207-
go wait.Until(c.podWorker, time.Second, stopCh)
208-
go wait.Until(c.cloudmapReconciler, 1*time.Minute, stopCh)
288+
go wait.Until(c.meshWorker, time.Second, ctx.Done())
289+
go wait.Until(c.vNodeWorker, time.Second, ctx.Done())
290+
go wait.Until(c.vServiceWorker, time.Second, ctx.Done())
291+
go wait.Until(c.podWorker, time.Second, ctx.Done())
292+
go wait.Until(c.cloudmapReconciler, 1*time.Minute, ctx.Done())
209293
}
210-
211294
klog.Info("Started workers")
212-
<-stopCh
295+
<-ctx.Done()
213296
klog.Info("Shutting down workers")
214-
215-
return nil
216297
}
217298

218299
// podAdded adds the pods endpoint to matching CloudMap Services.

pkg/controller/controller_util.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ package controller
22

33
import (
44
"fmt"
5-
"k8s.io/apimachinery/pkg/api/meta"
5+
"io/ioutil"
6+
"os"
67
"strings"
8+
9+
"k8s.io/apimachinery/pkg/api/meta"
710
)
811

12+
const inClusterNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
13+
914
func containsFinalizer(obj interface{}, finalizer string) (bool, error) {
1015
metaobj, err := meta.Accessor(obj)
1116
if err != nil {
@@ -62,3 +67,22 @@ func namespacedResourceName(resourceName string, defaultResourceNamespace string
6267
}
6368
return resourceName + "-" + defaultResourceNamespace
6469
}
70+
71+
// getInClusterNamespace returns the namespace of the controller pod.
72+
func getInClusterNamespace() (string, error) {
73+
// Check whether the namespace file exists.
74+
// If not, we are not running in a cluster and can't get the namespace.
75+
_, err := os.Stat(inClusterNamespacePath)
76+
if os.IsNotExist(err) {
77+
return "", fmt.Errorf("not running in-cluster, please specify --election-namespace")
78+
} else if err != nil {
79+
return "", fmt.Errorf("error checking namespace file: %v", err)
80+
}
81+
82+
// Load the namespace file and return its content
83+
namespace, err := ioutil.ReadFile(inClusterNamespacePath)
84+
if err != nil {
85+
return "", fmt.Errorf("error reading namespace file: %v", err)
86+
}
87+
return string(namespace), nil
88+
}

0 commit comments

Comments
 (0)