Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add label-with-exclude-from-external-lbs CLI argument to enable graceful removal/addition from external load balancers #419

Closed
wants to merge 1 commit into from

Conversation

amorey
Copy link
Contributor

@amorey amorey commented Aug 4, 2021

Previously, kured issued the system reboot command without first removing nodes from any connected external load balancers (ELBs).

This behavior caused downtime on restart because ELBs send traffic to kube-proxy pods running on nodes until the ELB health checks fail or the node is de-registered explicitly.

This patch solves the problem by adding a command line argument (label-with-exclude-from-external-lbs) that, when enabled, adds a "node.kubernetes.io/exclude-from-external-load-balancers" label to nodes undergoing kured reboot. This label tells the Kubernetes control plane to de-register the affected node from any connected ELBs. The node label is removed after restart which causes the control plane to re-register the node with the ELBs.

Close #358

@kingdonb
Copy link
Contributor

kingdonb commented Aug 6, 2021

I released the e2e checks that were waiting for approval. Thanks for re-submitting this!

@evrardjp
Copy link
Collaborator

I will review asap.

Copy link
Collaborator

@evrardjp evrardjp left a comment

Choose a reason for hiding this comment

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

Little detail: I think this should be opt-in.

cmd/kured/main.go Outdated Show resolved Hide resolved
cmd/kured/main.go Outdated Show resolved Hide resolved
@amorey
Copy link
Contributor Author

amorey commented Aug 25, 2021

Little detail: I think this should be opt-in.

What would you suggest calling the command line argument?

@evrardjp
Copy link
Collaborator

Little detail: I think this should be opt-in.

What would you suggest calling the command line argument?

I have no strong opinion :)
Maybe "--label-with-exclude-from-external-lb" ?

@evrardjp
Copy link
Collaborator

evrardjp commented Aug 25, 2021

I think it would be wise to clarify this (future) CLI flag's purpose in our documentation + include it (commented out) in our manifest.

@amorey amorey force-pushed the exclude-from-elbs branch 2 times, most recently from d57c317 to fff8cdf Compare August 25, 2021 20:37
@amorey amorey changed the title Add support for graceful removal/addition from external load balancers Add label-with-exclude-from-external-lbs CLI argument to enable graceful removal/addition from external load balancers Aug 25, 2021
@amorey
Copy link
Contributor Author

amorey commented Aug 25, 2021

I rebased with upstream/main, added a label-with-exclude-from-external-lbs CLI option and updated the documentation, manifests and helm charts. I didn't bump the helm chart version number because I bumped it in #430 and didn't want to cause a conflict. I also updated the title and description of this PR to reflect the current commit message.

@evrardjp
Copy link
Collaborator

I should have been clearer and mention no change in the helm chart, as the helm chart is trailing the release. Sorry for that. The manifest and docs are to be included in the release however.

@amorey
Copy link
Contributor Author

amorey commented Aug 27, 2021

I should have been clearer and mention no change in the helm chart, as the helm chart is trailing the release. Sorry for that. The manifest and docs are to be included in the release however.

Ok, fixed.

@@ -492,6 +552,9 @@ func rebootAsRequired(nodeID string, rebootCommand []string, sentinelCommand []s
log.Fatal(err)
}

// Remove ExcludeFromELBs label immediately to allow ELB registration
disableExcludeFromELBs(client, nodeID)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a reason you only guard the enable, not the disable? I would expect no change to the labels by default if the option is disabled. (hence, adding a guard here too).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was thinking about the case of a user who disables the option during a reboot after the label has been added. In order to avoid leaving the rebooted node off of the external load balancers when the node is rebooted, disable() has to run on startup. On the other hand, there's no harm in running disable() when there's no label present since it won't do anything.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It does an api call, so it doesn't do nothing ;)

However, it made me realise that an error in disableExcludeFromELBs (e.g. one that the API can throw) is not used in any way... Why do we return the error if it's to do nothing with it? How should we behave if the API call fails?

Copy link
Contributor Author

@amorey amorey Sep 28, 2021

Choose a reason for hiding this comment

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

Currently the returned error is being used in the unit tests.

In terms of what to do if the API call fails, I think it makes sense to make API failures in enableExcludeFromELBs() fatal since success is a must for users who have enabled --label-with-exclude-from-external-lbs.

On the other hand, I don't think API errors in disableExcludeFromELBs() necessarily need to be fatal but it looks like similar operations such as annotation deletion failures are treated fatally elsewhere in the code.

Should we make the API failures fatal in enableExcludeFromELBs() and disableExcludeFromELBs()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What would you like to do here? Do you want to guard the disable and make API errors fatal?

@evrardjp
Copy link
Collaborator

Looks good overall, just a small final nit.

@amorey
Copy link
Contributor Author

amorey commented Sep 3, 2021

Are there any changes that need to be made to the PR?

Copy link
Collaborator

@evrardjp evrardjp left a comment

Choose a reason for hiding this comment

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

I think I would like to discuss the guard and the last bit of the code.

@@ -492,6 +552,9 @@ func rebootAsRequired(nodeID string, rebootCommand []string, sentinelCommand []s
log.Fatal(err)
}

// Remove ExcludeFromELBs label immediately to allow ELB registration
disableExcludeFromELBs(client, nodeID)
Copy link
Collaborator

Choose a reason for hiding this comment

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

It does an api call, so it doesn't do nothing ;)

However, it made me realise that an error in disableExcludeFromELBs (e.g. one that the API can throw) is not used in any way... Why do we return the error if it's to do nothing with it? How should we behave if the API call fails?

@evrardjp
Copy link
Collaborator

I don't want to be a blocker for progress. If we feel it's a good feature, then let's move ahead... please let's just solve git conflicts and human ones ;)

@amorey
Copy link
Contributor Author

amorey commented Nov 24, 2021

I don't want to be a blocker for progress. If we feel it's a good feature, then let's move ahead... please let's just solve git conflicts and human ones ;)

Sounds good to me! I rebased with main, fixed the conflicts and am waiting for feedback on the changes you requested above.

…ceful removal/addition from external load balancers

Previously, kured issued the system reboot command without first
removing nodes from any connected external load balancers (ELBs).

This behavior caused downtime on restart because ELBs send traffic to
kube-proxy pods running on nodes until the ELB health checks fail or the
node is de-registered explicitly.

This patch solves the problem by adding a command line argument
(`label-with-exclude-from-external-lbs`) that, when enabled, adds a
"node.kubernetes.io/exclude-from-external-load-balancers" label to nodes
undergoing kured reboot. This label tells the Kubernetes control plane
to de-register the affected node from any connected ELBs. The node label
is removed after restart which causes the control plane to re-register
the node with the ELBs.

Close kubereboot#358
@amorey
Copy link
Contributor Author

amorey commented Dec 2, 2021

#419 (review)

I fenced in disableExcludeFromELBs() and modified the code to fail if the API returns an error. Is there anything left to do before this can be merged?

@@ -493,6 +553,12 @@ func rebootAsRequired(nodeID string, rebootCommand []string, sentinelCommand []s
log.Fatal(err)
}

if labelWithELBX {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't we want to move this remove node label logic down into the the part where we know we're holding the lock? Similar to where we do if annotateNodes && !rebootRequired and then delete node annotations?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The disableExcludeFromELBs() method can be run safely with or without the lock. Are there any particular cases you're thinking of where you would want the method to run only if the lock is present?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm just thinking that if we do this when we're not holding the lock, that means we're doing it in every iteration. And that means that we're calling the apiserver for the node object every time. On a cluster with hundreds of thousands of nodes, with a small kured interval (e.g., 1 min), that means we're going to potentially be sending many apiserver requests every second.

If I understand correctly, the only reason we want to disable this label is to remove it after a successful reboot. In every scenario that fits that description, we should have the lock (because we only ever release the lock inside that block).

Does that make sense?

cc @dholbach @ckotzbauer for thoughts as well

Copy link
Member

Choose a reason for hiding this comment

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

I agree with @jackfrancis, that we should avoid requesting the apiserver to excessive. The logic should be safe when only called with a lock being present.

@github-actions
Copy link

github-actions bot commented Feb 7, 2022

This PR was automatically considered stale due to lack of activity. Please refresh it and/or join our slack channels to highlight it, before it automatically closes (in 7 days).

@ckotzbauer ckotzbauer added keep This won't be closed by the stale bot. and removed no-pr-activity labels Feb 7, 2022
@github-actions
Copy link

github-actions bot commented Apr 9, 2022

This PR was automatically considered stale due to lack of activity. Please refresh it and/or join our slack channels to highlight it, before it automatically closes (in 7 days).

@amorey
Copy link
Contributor Author

amorey commented Apr 9, 2022

/remove no-pr-activity

@github-actions
Copy link

github-actions bot commented Jun 9, 2022

This PR was automatically considered stale due to lack of activity. Please refresh it and/or join our slack channels to highlight it, before it automatically closes (in 7 days).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
keep This won't be closed by the stale bot. no-pr-activity
Projects
None yet
Development

Successfully merging this pull request may close these issues.

EC2 instance graceful restart
5 participants