Skip to content

ateapi: give the api-server a startup probe covering its store-connect budget - #1413

Open
Maya Wang (mayawang) wants to merge 1 commit into
agent-substrate:mainfrom
mayawang:fix/1394-ateapi-startup-probe
Open

ateapi: give the api-server a startup probe covering its store-connect budget#1413
Maya Wang (mayawang) wants to merge 1 commit into
agent-substrate:mainfrom
mayawang:fix/1394-ateapi-startup-probe

Conversation

@mayawang

Copy link
Copy Markdown
Collaborator

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-server configures itself a 60s store-connect budget and is killed by its own liveness probe at ~30s, halfway through it.

Why the kill happens

:9090 is bound near the end of boot — after connectStore and, since #1196, after the goose migrations — so nothing answers /healthz for the whole of that window. The liveness probe was the only probe governing it: initialDelaySeconds: 10 then probes at 10s, 20s and 30s, so the default failureThreshold: 3 is met at ~30s. The events show connect: 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 reports context canceled at attempt 15 of 30 rather than exhausting the budget, serverboot.Fatal exits, 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=0 and 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 /healthz answers, so boot gets ~115s before the kubelet intervenes, against a ~60s connect budget.

before after
kubelet kills at ~30s ~115s
connect attempts used 15 of 30 30 of 30
failure reported as context canceled connect 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 raising failureThreshold.

What this deliberately does not do

Testing

hack/verify-all.sh passes (metrics.sh skipped locally — needs weaver or docker; no metrics change here).

Note for anyone rebasing: make test currently fails on TestExternalVolumeRenders in cmd/ate-setup/internal/demos/counter on a clean cbae8250, unrelated to this change.

…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.
@mayawang

Copy link
Copy Markdown
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.

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.

ateapi: ate-api-server is SIGTERMed by its own liveness probe halfway through its store-connect retry budget, then exits instead of retrying

1 participant