ateapi: give the api-server a startup probe covering its store-connect budget - #1413
Open
Maya Wang (mayawang) wants to merge 1 commit into
Open
ateapi: give the api-server a startup probe covering its store-connect budget#1413Maya Wang (mayawang) wants to merge 1 commit into
Maya Wang (mayawang) wants to merge 1 commit into
Conversation
…t budget The metrics server binds :9090 near the end of boot, after the store connect and the schema migrations, so nothing answers /healthz until both finish. The store connect budgets 60s of its own (30 attempts, 2s apart) and the migrations serialize across replicas on an advisory lock. The liveness probe was the only probe governing that window, and its budget is half as long: initialDelaySeconds 10 then probes at 10s, 20s and 30s, so the third failure kills the container at ~30s. The connect retry loop is rooted in the signal context, so the kill cancels it mid-budget -- the process reports "context canceled" at attempt 15 of 30 and exits into CrashLoopBackOff, where the backoff then holds it down well past the store returning. A running replica rides out the same outage untouched, so a booting one was strictly less resilient than a running one. A startup probe suspends liveness until :9090 answers, giving boot ~115s before the kubelet intervenes. A store slow to return is now waited out for the full configured budget, and exhausting it reports that honestly instead of as a cancellation; a genuinely dead process is still restarted, on a budget sized for boot rather than one that was not. This does not change the boot ordering, so a slow boot is still indistinguishable from a dead process while the port is closed, and an outage outlasting the process's own 60s budget still ends in a restart. Starting the health surface ahead of the store connect, and retrying for the life of the process, are the follow-ups.
Maya Wang (mayawang)
requested a review
from Julian Gutierrez Oschmann (juli4n)
September 2, 2026 20:43
Collaborator
Author
|
Jeremy Alvis (@iplay88keys) tagging you for context rather than as a formal reviewer — GitHub won't let me request you. This sits directly on top of #1196: the "can't bound the migrations" caveat is about the advisory lock you put on the boot path, and I'd like a sanity check that ~115s is the right budget now that goose runs ahead of the health server. |
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.
Partially addresses #1394 — the "spend the retry budget it configures for itself" half of that issue's expected behavior. Candidate fix 4 from the issue, done first because it is manifest-only.
The problem, in one line
ate-api-serverconfigures itself a 60s store-connect budget and is killed by its own liveness probe at ~30s, halfway through it.Why the kill happens
:9090is bound near the end of boot — afterconnectStoreand, since #1196, after the goose migrations — so nothing answers/healthzfor the whole of that window. The liveness probe was the only probe governing it:initialDelaySeconds: 10then probes at 10s, 20s and 30s, so the defaultfailureThreshold: 3is met at ~30s. The events showconnect: connection refused, not a probe returning failure — the port is closed, the process is fine.The kill then makes it worse than a plain restart.
connectStore(shutdownCtx)roots the retry loop in the signal context, so SIGTERM cancels it mid-budget: the loop reportscontext canceledat attempt 15 of 30 rather than exhausting the budget,serverboot.Fatalexits, and CrashLoopBackOff holds the pod down well past the point where the store came back.A running replica rides out the same outage with
restarts=0and serves again ~15s after the store returns. A booting replica was strictly less resilient than a running one, which is backwards.The change
One
startupProbe, 24 × 5s. Liveness is suspended until/healthzanswers, so boot gets ~115s before the kubelet intervenes, against a ~60s connect budget.context canceledconnect to PostgreSQL after 30 attempts: …A dead process is still restarted, just on a budget sized for boot rather than one that never was. And because a startup probe also suspends the readiness probe, the pod now stays out of Service endpoints while booting instead of being latched Ready by the zero-value
Readiness— a small move in the right direction for #1395 rather than against it, which is why this rather than simply raisingfailureThreshold.What this deliberately does not do
Fatals once its own 60s budget is gone. The motivating scenario there is an ungraceful node loss, where GKE's PD force-detach alone is ~6 minutes; this raises tolerance from 30s to 60s, and only "retry for the life of the process, report through readiness" survives six minutes. That fix needs the reversible readiness predicate ateapi: a control plane that cannot reach its store serves 200 on /healthz and /readyz, stays Ready, and fails every RPC #1395 describes.pg_advisory_xact_lock, so with two replicas restarting together one waits on the other — inside the window where nothing is listening. On an empty schema that is milliseconds; on a real database during an upgrade it is not bounded by anything, and no probe budget can bound it. Ordering can.connectStoreis candidate fix 1 and the natural follow-up.Testing
hack/verify-all.shpasses (metrics.shskipped locally — needs weaver or docker; no metrics change here).Note for anyone rebasing:
make testcurrently fails onTestExternalVolumeRendersincmd/ate-setup/internal/demos/counteron a cleancbae8250, unrelated to this change.