feat: Implement a retry mechanism for kubernetes job spawning - #975
Open
mlanth wants to merge 1 commit into
Open
feat: Implement a retry mechanism for kubernetes job spawning#975mlanth wants to merge 1 commit into
mlanth wants to merge 1 commit into
Conversation
…ate spawn failures caused by overwhelmed clusters.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds a retry mechanism to the kubernetes executor spawn operation to mitigate hung runs caused by API rejection on busy clusters. Issue surfaced through testing large volume of concurrent runs causing etcd instability. While there are cluster measures to be taken to help boost etcd performance, it is out of scope and this PR focuses on implementing recoverability.
Problem
With the kubernetes executor, two bugs cause a run to hang in
planningorapplyingwith no pod, no log output and no error surfaced in the UI until its phase timeout expires. On a busy deployment this is invisible as the run sits there with no indication of why and just shows an active phase with no log output.Both bugs are triggered by rejection from the Kubernetes API server. The issue surfaced as
etcdserver: request timed outwhile running 100s of concurrent runs. The issue isn't that the API server occasionally rejects a write which is normal and recoverable, it's that OTF turns the recoverable rejection into a stranded run.Bug 1: job is marked running before its pod exists
In the
Runner.Startprocess jobs loop:StartJobcommits the state transition before the pod is created. WhenSpawnOperationthen fails,processJobsreturns an error and thebackoff.RetryNotifywrapper retries it but the job is nowJobRunning, so the next line of the loop skips it forever:The allocator can't help either: it only reallocates jobs in
JobAllocated, so nothing in the system ever touches that job again. It staysrunningwith no corresponding Kubernetes Job, and its run hangs untilPlanningTimeout/ApplyingTimeout.The OTFD logs are the clearest way to see it. A healthy spawn produces five lines:
A failed one produces three, then nothing until the phase timeout fires:
Bug 2: setting the job token secret's owner reference is fatal
SpawnOperationmakes three sequential API calls:secrets.Createjobs.Createsecrets.Update(owner reference)By the time (3) runs, the kubernetes Job has already been created and its pod is going to run to completion. Returning an error there reports a job as having failed to spawn when it has in fact spawned and via Bug 1 that strands the run, for a job that is working as expected. The only real consequence of a missing owner reference is that the secret isn't garbage collected along with its job.
Fix
1. Retry the spawn
SpawnOperationnow retries the whole spawn rather than each call individually. The newspawnstruct records which steps have completed, so a retry resumes from the first incomplete one and issues exactly one API call, the one that failed rather than repeating work:AlreadyExistsis treated as success, which is what makes the resume correct.etcdserver: request timed outis ambiguous, the write may have committed and only the response was lost so without this, retrying would convert a successful write into a hard failure.2. An owner-reference failure no longer fails the spawn
Steps reached after the Job exists return an
optionalStepError. They're retried like anything else, but if the budget is exhaustedSpawnOperationreturnsnil, because the pod is running:As previously mentioned, the only real consequence of a missing owner reference is that the secret isn't garbage collected along with its job.
3. Report the job errored when the spawn ultimately fails
When retries are exhausted,
Runner.failJobreports the job asJobErrored, which errors its run phase. The run fails visibly with the API error attached, and is immediately re-runnable, instead of hanging for the phase timeout.This needed no new service method, API endpoint, or state transition.
Service.FinishJobonly accepts the job itself as caller, andStartJobhas already returned the job's token, sofailJobbuilds a client via the existingoperationClientCreator, the same mechanismDoOperationuses.