Repository navigation
fix: Kubernetes launcher - Read a container log within a budget, and give up quietly - #417
Merged
Merged
Conversation
yuechao-qin
force-pushed
the
bound-log-upload-retries
branch
3 times, most recently
from
October 30, 2026 11:18
c1ff231 to
4fed669
Compare
Contributor
|
Let's try a slightly different approach and move the configuration and the retries themselves to the launcher (Kubernetes launcher). |
yuechao-qin
force-pushed
the
bound-log-upload-retries
branch
from
November 4, 2026 00:42
4fed669 to
b52e714
Compare
yuechao-qin
force-pushed
the
bound-log-upload-retries
branch
from
November 4, 2026 02:59
b52e714 to
24a1599
Compare
…give up quietly Reading a container log goes through the API server's proxy to the node's kubelet, and a read that fails has almost always lost the log for good. The caller retries `upload_log` five times, so each lost log cost its single loop thread five requests and five waits for a log that would never arrive. Both Kubernetes launchers now read within their own budget and return quietly when it runs out. The budget wraps the read only: a failed write to storage still raises and still earns the caller's retries.
yuechao-qin
force-pushed
the
bound-log-upload-retries
branch
from
November 4, 2026 04:23
24a1599 to
4e8d861
Compare
Ark-kun
reviewed
Nov 5, 2026
Comment on lines
+809
to
+811
| max_retries: int = _LOG_READ_MAX_RETRIES, | ||
| wait_seconds: float = _LOG_READ_WAIT_SECONDS, | ||
| max_total_seconds: float | None = _LOG_READ_MAX_TOTAL_SECONDS, |
Contributor
There was a problem hiding this comment.
Note the these are essentially unconfigurable (this is a private function that is always called without these optional parameters).
Ark-kun
reviewed
Nov 5, 2026
| # Disabled stream="All" due to error in some new GKE clusters | ||
| # HTTP response body: {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"PodLogOptions \"task-pod-xxxxx\" is invalid: stream: Forbidden: may not be specified","reason":"Invalid","details":{"name":"task-pod-xxxxx","kind":"PodLogOptions","causes":[{"reason":"FieldValueForbidden","message":"Forbidden: may not be specified","field":"stream"}]},"code":422} | ||
| # stream="All", | ||
| _request_timeout=launcher._request_timeout, |
Contributor
There was a problem hiding this comment.
Note that this log reading timeout is currently same as other request timeouts and is not affected by any of the new log timeout configurations.
Ark-kun
approved these changes
Nov 5, 2026
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.
What
Both Kubernetes launchers now read a container log within their own budget, and give up quietly
when it runs out instead of letting the failure propagate into the caller's retry.
10s is the launcher's existing
request_timeout, which already bounds a single read.Why
Reading a container log goes through the API server's proxy to the node's kubelet. A read that
fails has almost always lost the log for good: the node is gone or going, and the kubelet that
held the log went with it.
OrchestratorService_Sqlcallsupload_loginside its ownfive-attempt
_retry, on a single-threaded loop — so every lost log costs that loop fiverequests and five waits for a log that will never arrive.
Measured on one deployment over 72 hours: 323 failing log uploads produced 1723 attempt log
lines —
1723 / 323 = 5.33, the five attempts. Every failure in the window was the sameunreachable kubelet; the only other errors on that read path were 9 deleted Pods and one Pod
still initialising. Queries and raw figures are in
Shopify/oasis-backend#827, an internal
repository.
Key decisions
The budget wraps the log read only. The write to storage stays outside it and still raises,
so the caller's five retries continue to cover a failed upload — unlike a read from a dead node,
a failed write is worth retrying.
Giving up returns rather than raises. The caller catches
Exceptionaroundupload_log, soa launcher that raised after exhausting its own budget would simply have it applied five times.
Nothing is written on that path, so a log stored by an earlier attempt survives.
Nothing outside this file changes. The budget is the default arguments of one private
helper, so there is no class or base-class attribute for another launcher to inherit by
accident, and both call sites read
_read_log_within_budget(self.get_log).This bounds how long a failing read may hold the caller. What makes an unreachable kubelet stop
being an error at all is the companion change, #418;
the two are independent and can land in either order.
Verification
pytest --ignore=tests/instrumentation -q→ 445 passed,blackclean.The tests build both launched containers through their real constructors and raise a real
ApiExceptioncarrying the body an unreachable kubelet actually returns, so the exception thebudget catches is one the Kubernetes client raised. One case drives the caller's real
_retryand asserts 1 request and 0 sleeps, against 5 and 5 for the same failure raised. Removing the
give-up
returnturns 7 of them red.