Skip to content

Make leader election a top level pilot component, use it for singleton components#20809

Merged
istio-testing merged 13 commits intoistio:masterfrom
howardjohn:pilot/leader-election
Feb 7, 2020
Merged

Make leader election a top level pilot component, use it for singleton components#20809
istio-testing merged 13 commits intoistio:masterfrom
howardjohn:pilot/leader-election

Conversation

@howardjohn
Copy link
Copy Markdown
Member

This PR does a few things, which I attempted to decompose into different commits

  1. Add a new leaderelection component. This is largely a copy of the ingress leader election (which in turn is directly copied from some other implementation)
  2. Switch ingress status to use the new component
  3. Make validation and namespace controller use the new leader election

@howardjohn howardjohn requested a review from a team as a code owner February 3, 2020 21:09
@googlebot googlebot added the cla: yes Set by the Google CLA bot to indicate the author of a PR has signed the Google CLA. label Feb 3, 2020
@istio-testing istio-testing added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Feb 3, 2020
Copy link
Copy Markdown
Contributor

@nmittler nmittler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was there previous leader election code in Istio? I was expecting to see moves/deletes

@howardjohn
Copy link
Copy Markdown
Member Author

Yes, you can see it moved in ingress/status.go. The code is just calling a k8s library, so its not complex/long code or anything.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any chance of losing the lock without shutting down? Is this what line 100 below is referring to?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is - at the very least if someone took away our rbac permission for config map, api server went down, etc we would. In this case, the context will be canceled, so the functions will stop (the library does this, not us).

I was confused looking at this original since I thought we just did nothing when we lose the lock, but its already handled. Probably warrants a comment.

The L100 comment is just for if WE explicitly cancel the context, we also drop the lock immediately. This allows another instance to take over immediately when we exit

Copy link
Copy Markdown
Contributor

@nmittler nmittler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good overall ... took a first pass.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this will only be called before it's Run? If so, maybe we should just have a separate builder. Then we could do something like:

le := leaderelection.NewBuilder().
  Namespace(ns).
  Name(name).
  Client(client).
  WithRun(runFn1).
  WithRun(runFn2).
  Build()
...
le.Run(...)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@howardjohn there are a few options here:

  1. lock access to the runFns array wherever it's used
  2. Use a separate builder so that the API makes it clear that runFns can not be mutated after start.
  3. Use a separate Config object that you pass to New... and this is the thing that you build up before you create the elector.

Personally, I think my preference is 3, it's probably the simplest since you could just have a simple struct with no additional methods, like a builder would require. It also makes the New method clean.

Also, this method should probably be renamed to something like RunWhenLeader.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to use a stop channel as opposed to context.Context? Seems like it would simplify the cancel code below.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of pilot is doing the stop channel passing. So at some point, we will need to convert stop channel into context? So I think its reasonable to do here rather than outside this function?

I feel like there should be a simpler way though..

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this a constant at the top?

Also, does this name need to be different for each component (e.g. webhook vs ingress)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we will have a single leader election for all components. Technically we could split it up, but I see no reason to have 4+ locks when we can have one?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also I moved to a constant

@howardjohn
Copy link
Copy Markdown
Member Author

/retest

Copy link
Copy Markdown
Member

@hzxuzhonghu hzxuzhonghu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly i am concerned when leader lost, will it renew?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by default hostname equals podname.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@howardjohn looks like you're just using pod name for Identity in the election config? What are the requirements for this identity? Does it have to be a pod name?

@howardjohn
Copy link
Copy Markdown
Member Author

I think it will renew but I will check the library

@howardjohn
Copy link
Copy Markdown
Member Author

Neither Istio's current implementation nor nginx-ingress do anything special to handle leader being lost: https://github.com/nginxinc/kubernetes-ingress/blob/5d4c5ddc70c2a40e78c803d0000908bfb2e6b63f/internal/k8s/controller.go#L310. So based on this I assume we have nothing to worry about here?

@howardjohn
Copy link
Copy Markdown
Member Author

also looked at the code, it basically does

for {
  renew()
}

so if we lose it we can get it back again

@hzxuzhonghu
Copy link
Copy Markdown
Member

I remember in kube-controller-manager, the OnStoppedLeading is set to panic and exit.

// Run starts the leader election loop
func (le *LeaderElector) Run(ctx context.Context) {
	defer func() {
		runtime.HandleCrash()
		le.config.Callbacks.OnStoppedLeading()
	}()
	if !le.acquire(ctx) {
		return // ctx signalled done
	}
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	go le.config.Callbacks.OnStartedLeading(ctx)
	le.renew(ctx)
}

@howardjohn
Copy link
Copy Markdown
Member Author

But for us we do nothing for OnStoppedLeading, so I don't think there is an issue?

@hzxuzhonghu
Copy link
Copy Markdown
Member

We do nothing just print a log. But this goroutine will exit. There is no way to re-acquire the lock.

@howardjohn
Copy link
Copy Markdown
Member Author

@hzxuzhonghu thanks for pushing back on this, i misunderstood the code. I simulated loosing the lock (by removing RBAC permission) and found

So I think you are right, we need some looping to acquire leadership again

@howardjohn
Copy link
Copy Markdown
Member Author

/hold

I added a test that fails due to the k8s crashing bug

@howardjohn howardjohn force-pushed the pilot/leader-election branch from 4163c65 to df198c2 Compare February 5, 2020 23:16
@howardjohn howardjohn requested a review from a team as a code owner February 5, 2020 23:16
@istio-testing istio-testing added the needs-rebase Indicates a PR needs to be rebased before being merged label Feb 5, 2020
@howardjohn howardjohn force-pushed the pilot/leader-election branch from df198c2 to c3ceabd Compare February 6, 2020 02:10
@istio-testing istio-testing removed the needs-rebase Indicates a PR needs to be rebased before being merged label Feb 6, 2020
@howardjohn howardjohn force-pushed the pilot/leader-election branch 2 times, most recently from 4801164 to be71e58 Compare February 6, 2020 23:28
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defer

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@howardjohn looks like you're just using pod name for Identity in the election config? What are the requirements for this identity? Does it have to be a pod name?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@howardjohn there are a few options here:

  1. lock access to the runFns array wherever it's used
  2. Use a separate builder so that the API makes it clear that runFns can not be mutated after start.
  3. Use a separate Config object that you pass to New... and this is the thing that you build up before you create the elector.

Personally, I think my preference is 3, it's probably the simplest since you could just have a simple struct with no additional methods, like a builder would require. It also makes the New method clean.

Also, this method should probably be renamed to something like RunWhenLeader.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the name creates a stutter with the package name leaderelection.LeaderElection. Also, I think Elector is a little better than Election. Perhaps the package could be leader and the type could be Elector, so that callsites would see leader.Elector? WDYT?

@istio-testing
Copy link
Copy Markdown
Collaborator

In response to a cherrypick label: new pull request created: #20939

sdake pushed a commit to sdake/istio that referenced this pull request Feb 21, 2020
…n components (istio#20809)

* Add initial leader election

* Cleanup

* Add unit tests

* user leader election for ns controller, validation controller

* Log about ingress

* Add graceful shutdown

* Fix tests, add injector controller

* Fix lint

* Add renewal when lease is droppped, failing test

* wip

* Get everything working

* Fix build

* lint
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: yes Set by the Google CLA bot to indicate the author of a PR has signed the Google CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants