feat: readyz: make the overall wait timeout configurable per template - #487
feat: readyz: make the overall wait timeout configurable per template#487Maya Wang (mayawang) wants to merge 5 commits into
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
62b205b to
a72632b
Compare
|
Maya Wang (@mayawang) - I have recently added substrate/internal/readyz/readyz.go Line 42 in 0f6144e Have you considered to extend the readyz with a timeout via actorTemplate and push it up to the atelet? |
Thanks Dmitry Berkovich (@dberkov) — agreed, readyz is the better mechanism. One note on how it fits with this PR: Strong +1 on making the timeout configurable — and it's live for us, not hypothetical. The Hermes actor we're onboarding declares readyz ( The failure mode is harsher than the timer's, too. A WaitAll error propagates out of RunWorkload/RestoreWorkload ( Shape I'd propose: optional I'd keep it as a separate PR rather than folding it in — this one is internal-only, and adding a v1alpha1 field pulls in codegen and API review. The two don't overlap, so they can land in either order. Happy to take it since I have the workload to validate against, unless you'd rather own it as your API — either way I'd want your input on the field bounds. |
6a8d224 to
cff5609
Compare
|
Dmitry Berkovich (@dberkov) — this has been reshaped since your review, so rather than have you re-read Two of the four knobs are gone, not rebased. Request parking landed on main and covers that ground properly: the resume timeout is now The two survivors moved from env vars to flags, matching the convention parking established. One correction to my comment abve: The runsc opt-out became an automatic capability probe, and that turned up a real bug worth flagging since it's the one change with no e2e coverage. The probe originally shelled Rebased onto current main, so this is now post-atunnel. Relevant to the route timeout: it attaches to the
|
cff5609 to
ee2163b
Compare
The 30s readiness deadline was a package constant, so a workload that legitimately takes longer to bind its HTTP server could not be accommodated without raising the ceiling for every actor in the cluster. How long a workload takes to become ready is a property of that workload, so this plumbs a per-template timeout through the existing readyz chain: ContainerReadyz.timeoutSeconds -> ateletpb.Readyz -> ateompb.Readyz -> readyz.Wait. Unset means the ateom's default, which is the renamed DefaultOverallTimeout, still 30s. Zero is not a meaningful deadline here -- unlike a warmup delay, a zero timeout could never be met -- so a non-positive value on the wire is read as "unset" and falls back to the default. The CRD field is a pointer with Minimum=1 so the API rejects it outright rather than silently substituting. No readyz.WaitAll call site changes: the timeout rides on the probe. The e2e probe fixture now declares a readyz probe with a non-default timeoutSeconds. That gives the readyz path its first e2e coverage and exercises the value crossing ateapi -> atelet -> ateom on real binaries, on both the run and restore paths.
ee2163b to
45f90d8
Compare
Dmitry Berkovich (dberkov)
left a comment
There was a problem hiding this comment.
2 minor comments, otherwise LGTM
Per review: declare the 30s default with +kubebuilder:default=30 so it is visible on the stored object and in `kubectl explain`, rather than being a constant an author has to know the ateom applies. With the API server defaulting the field, the nil check in toAteletReadyz is no longer load-bearing and becomes a plain deref. It stays nil-safe: the conversion is also reachable with an in-process template that never went through admission, and zero on the wire already means the same 30s. envtest now asserts the read-back value is 30, so the default is checked against a real API server rather than only the marker.
Per review, following the API convention for CRDs: when the zero value is not valid, a pointer buys nothing. Minimum=1 rejects an explicit 0 before any controller sees the object, and +kubebuilder:default=30 fills in the rest, so the field always holds valid data and the conversion is a plain assignment rather than a deref behind a nil check. Also folds the readyz defaulting assertions into TestActorTemplateValidation alongside the other readyz cases, via an optional verify hook for cases that check what the API server stored rather than whether it accepted the create. The standalone path-default test goes away with it. The explicit-zero case goes too: omitempty makes 0 indistinguishable from unset through a typed client, so it now defaults to 30 rather than being rejected. The -1 case still covers the Minimum=1 bound that rejects a manifest spelling out 0.
With TimeoutSeconds a plain int32, toAteletReadyz assigns it unconditionally, so a container whose probe omits the timeout no longer exercises a distinct path through the conversion.
Thanks — all addressed, PTAL. One consequence worth flagging: with omitempty on a non-pointer, a Go client that sets 0 doesn't serialize the field, so it's read as unset and defaults to 30 rather than being rejected. A manifest that spells out 0 still hits Minimum=1. I dropped the envtest case for explicit zero since it can't be expressed through the typed client; the -1 case still covers the bound. |
Summary
readyz.Waitpolls until the container returns 200 or a hardcoded 30selapses. A workload that legitimately takes longer to bind its HTTP server
cannot be accommodated without raising the ceiling for every actor in the
cluster, and losing that race fails the actor start.
How long a workload takes to become ready is a property of that workload, so
this makes the deadline a per-template setting rather than a package constant.
Adds optional
timeoutSecondstoContainerReadyz. Unset keeps today's 30s,so no existing template changes behavior.
Changes
The value rides on the existing probe, so it follows the chain the probe already
takes and no call site needs to know about it:
ContainerReadyz.timeoutSeconds→toAteletReadyz→ateletpb.Readyz→toAteomReadyz→ateompb.Readyz→readyz.Waitpkg/api/v1alpha1/actortemplate_types.go—TimeoutSeconds *int32,+optional,Minimum=1,Maximum=3600.internal/proto/ateletpb/atelet.proto,internal/proto/ateompb/ateom.proto—int32 timeout_seconds = 2on bothReadyzmessages.cmd/ateapi/internal/controlapi/workload_spec.go,cmd/atelet/main.go— passit through the two conversions.
internal/readyz/readyz.go—OverallTimeoutbecomesDefaultOverallTimeout(still 30s) andWaitresolves its deadline through anew
overallTimeout(probe)helper..pb.go,zz_generated.deepcopy.go, and theactortemplatesCRD.None of the four
readyz.WaitAllcall sites change.On the zero value. Unlike a warmup delay — where zero is a real request
meaning "checkpoint immediately" — a zero readiness deadline could never be met,
so it is never something a template author means. A non-positive value on the
wire is therefore read as "unset" and falls back to the default, and the CRD
field is a pointer with
Minimum=1so the API rejects0outright rather thansilently substituting 30s behind the author's back.
On bounding, which was the open question left on the previous revision:
bounded at
3600. A template asking to wait longer than an hour for readinessis expressing a broken workload, not a slow one, and the bound keeps a typo from
pinning a worker for a day.
Verification
go build ./...,go vet ./...,gofmt,go test ./...— all pass.internal/readyz/readyz_test.go—overallTimeoutresolves unset andnegative to the default and honors an explicit value;
Waitagainst a portnothing binds gives up at the probe's 1s deadline rather than the 30s default.
workload_spec_test.go,cmd/atelet/main_test.go— the timeout crosses bothconversions, and a probe without one stays zero on the wire.
actortemplate_validation_test.go— the bounds are enforced by a real APIserver. This suite runs under envtest against the generated CRD directory, so
it exercises the regenerated
actortemplatesCRD rather than the Go markers:300is accepted, unset is accepted, and0,-1and3601are allrejected by apiserver schema validation.
On a real cluster, via CI.
internal/e2e/fixtures/probenow declares areadyzprobe withtimeoutSeconds: 60, pointed at the/healthzthe probebinary already serves on
:80. The kind e2e that runs on every PR thereforeexercises the value crossing ateapi → atelet → ateom on real binaries, across
the auth matrix, on both the run and restore paths. This is also the readyz
path's first e2e coverage — no fixture declared a probe before.
Wire compatibility degrades safely in both skew directions:
timeout_secondsisa new field 2 on a
Readyzmessage that has only ever had field 1, so an oldateom ignores it and an old ateapi leaves it zero, which reads as the 30s
default.
No GKE run. What that would add over the above is a workload whose readiness
genuinely exceeds 30s, and that is the readiness-sidecar work rather than this
PR.
Fixes #<issue_number_goes_here>