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

Invoke DELETE on pod prepare-downscale path if any POSTs failed #146

Merged

Conversation

seizethedave
Copy link
Contributor

@seizethedave seizethedave commented May 9, 2024

This addresses a bug in rollout-operator where:

  1. Kubernetes receives a request to downscale a statefulset by X hosts.
  2. The prepare-downscale admission webhook attempts to prepare X pods for shutdown by sending an HTTP POST to their handler identified by the grafana.com/prepare-downscale-http-path and -port annotations.
  3. At least one of these requests fails. The admission webhook returns an error to Kubernetes, so the downscale is not approved.
  4. 💥 But some hosts may have been prepared for downscale. 💥

This PR adds cleanup logic to issue DELETE requests on all involved pods if any of the POSTs failed. Notes:

  • DELETE calls are attempted once.
  • DELETE failures are logged but otherwise ignored.
  • For simplicity, we'll invoke DELETE on all of the pods involved in the scaledown operation, not just ones that received a POST.

This doesn't fix the similar issue where replica count changing from 10->9->10 leaves that one pod prepared for shutdown. (But that's in the works.)

@seizethedave seizethedave requested a review from a team as a code owner May 9, 2024 22:28
resp, err := client.Do(req)
if err != nil {
level.Error(logger).Log("msg", fmt.Sprintf("error sending HTTP %s request", method), "err", err)
return err
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this line was absent from this routine before. I'm 90% sure that was a bug.

Copy link
Contributor

@pr00se pr00se May 14, 2024

Choose a reason for hiding this comment

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

You mean line 477 above, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 Ah yeah, it was the one after http.NewRequest, not client.Do.

req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://"+ep.url, nil)
if err != nil {
level.Error(logger).Log("msg", "error creating HTTP POST request", "err", err)
}

@pracucci pracucci requested a review from pstibrany May 11, 2024 06:20
@pracucci
Copy link
Collaborator

Let's wait for @pstibrany review here. He's the expert in this area.

Copy link
Contributor

@pr00se pr00se left a comment

Choose a reason for hiding this comment

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

A couple nits, but otherwise LGTM. Thank you!

I'd also like @pstibrany to take a look to make sure it doesn't conflict with anything he's been working on.


logger.SetSpanAndLogTag("url", ep.url)
logger.SetSpanAndLogTag("index", ep.index)
const maxGoroutines = 32
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this just to keep things in check, or is there a specific reason for 32?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nothing magic about 32, just to smooth out potentially spiky network IO.

pkg/admission/prep_downscale.go Outdated Show resolved Hide resolved
pkg/admission/prep_downscale.go Outdated Show resolved Hide resolved
resp, err := client.Do(req)
if err != nil {
level.Error(logger).Log("msg", fmt.Sprintf("error sending HTTP %s request", method), "err", err)
return err
Copy link
Contributor

@pr00se pr00se May 14, 2024

Choose a reason for hiding this comment

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

You mean line 477 above, right?

assert.NoError(t, err)
}
if c.lastPostsFail > 0 {
assert.Greater(t, postCalls.Load(), int32(0))
Copy link
Contributor

Choose a reason for hiding this comment

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

If I understand correctly, we can't test against a specific value here because once the errGroup context is cancelled we'll stop sending POSTs entirely, and we don't know how many will have been in flight (and return errors) before that happens, right?

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 went back in and tightened this up a little more, as we can expect at least endpoints-failures POSTs. But yep you are right about why we can't do an equality check.

pkg/admission/prep_downscale_test.go Outdated Show resolved Hide resolved
Copy link
Member

@pstibrany pstibrany 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, thank you.

I checked how Mimir ingester behaves on DELETE method when using ingest storage, and it simply doesn't support that option (returns error), and will stay "prepared for downscale", which means it will unregister from rings and flush storage. When using ingest storage, prepare-for-shutdown should only be called after period in which ingester was not receiving any data for some time already. If pod comes back, it will register back and activate the partition. Overall, I think this is fine.

pkg/admission/prep_downscale.go Outdated Show resolved Hide resolved
g, ectx := errgroup.WithContext(ctx)
g.SetLimit(maxGoroutines)
for _, ep := range eps {
ep := ep
Copy link
Member

Choose a reason for hiding this comment

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

nit: we don't need this in go 1.22 anymore (we use go 1.22 in rollout-operator). same comment in next for-loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This makes me a little paranoid as I know others are using this tool. We specify 1.22 in our go.mod but IIUC you can still build and run with an older compiler. And if you don't run the tests it'll probably be race central. :)
Unless you know something that can lessen my paranoia, I think I'll leave it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We specify 1.22 in our go.mod but IIUC you can still build and run with an older compiler.

It used to be like that, but now it's mandatory. From https://go.dev/doc/modules/gomod-ref#go

The go directive sets the minimum version of Go required to use this module. Before Go 1.21, the directive was advisory only; now it is a mandatory requirement: Go toolchains refuse to use modules declaring newer Go versions.

@seizethedave seizethedave merged commit 33c4fcf into grafana:main May 15, 2024
6 checks passed
@seizethedave seizethedave deleted the davidgrant/scaledown-fail-delete branch May 15, 2024 19:47
seizethedave added a commit that referenced this pull request May 22, 2024
seizethedave added a commit that referenced this pull request May 22, 2024
Add a changelog entry for #146, and prepare changelog for v0.16.0.


Co-authored-by: Patryk Prus <[email protected]>

---------

Co-authored-by: Patryk Prus <[email protected]>
seizethedave added a commit that referenced this pull request Jun 21, 2024
…s to store (#151)

Fix a snag found in #146 where if the "downscaled" annotation/configmap fails to persist, the scale operation is denied, but the pods are not informed via DELETE that they should no longer shutdown.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants