Repository navigation
Issue #10437: derive velero server's graceful shutdown timeout from terminationGracePeriodSeconds - #10438
Issue #10437: derive velero server's graceful shutdown timeout from terminationGracePeriodSeconds#10438adonispd wants to merge 8 commits into
Conversation
…ut from terminationGracePeriodSeconds velero server built its controller-runtime manager without setting GracefulShutdownTimeout, so it always inherited controller-runtime's hardcoded 30s default regardless of how high an operator raised the pod's terminationGracePeriodSeconds. On node disruption (Karpenter drift/consolidation, cluster-autoscaler scale-down, node maintenance) the manager force-exited after only 30s, cutting off in-flight backup/restore reconciles. This derives the manager's shutdown timeout automatically from the server's own pod's terminationGracePeriodSeconds (minus a safety buffer), falling back to the historical 30s whenever derivation isn't possible, while still allowing an explicit override: - Add --graceful-shutdown-timeout to override the derived value explicitly; an explicit 0 or negative value is rejected at flag-parse time. - Add --graceful-shutdown-safety-buffer (default 10s) to control the buffer subtracted from terminationGracePeriodSeconds during derivation. - Add the POD_NAME Downward API env var to the velero install-generated Deployment so the self pod lookup works for non-Helm installs too. - Document the new flags and add a changelog fragment. Signed-off-by: Adonis Dutra <adonis.dutra@outsystems.com> Signed-off-by: AdonisPD <adonis.dutra@outsystems.com>
👷 Deploy request for velero pending review.Visit the deploys page to approve it
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
| return defaultGracefulShutdownTimeout | ||
| } | ||
|
|
||
| derived := terminationGracePeriod - cfg.GracefulShutdownSafetyBuffer |
There was a problem hiding this comment.
If derived is the default 30, wouldn't this forcibly shrink it to 20 effectively?
There was a problem hiding this comment.
Good catch — fixed in 6d637fe: now short-circuits to the 30s default whenever terminationGracePeriodSeconds <= 30s, so default installs no longer shrink.
| return defaultGracefulShutdownTimeout | ||
| } | ||
|
|
||
| return derived |
There was a problem hiding this comment.
A logline to note the derived value may be helpful:
logger.WithFields(logrus.Fields{
"gracefulShutdownTimeout": derived,
"terminationGracePeriod": terminationGracePeriod,
"safetyBuffer": cfg.GracefulShutdownSafetyBuffer,
}).Info("Resolved graceful shutdown timeout")
There was a problem hiding this comment.
Added, thanks — logs gracefulShutdownTimeout, terminationGracePeriod, and safetyBuffer at Info level in 6d637fe.
| CredentialsDirectory: credentials.DefaultStoreDirectory(), | ||
| ItemBlockWorkerCount: DefaultItemBlockWorkerCount, | ||
| ConcurrentBackups: DefaultConcurrentBackups, | ||
| GracefulShutdownSafetyBuffer: defaultGracefulShutdownSafetyBuffer, |
There was a problem hiding this comment.
validation to ensure this field is non-negative could be needed
otherwise we end up with a higher value that actual (3600 - (-30)) = 3630 which is higher that k8s value
There was a problem hiding this comment.
Fixed — --graceful-shutdown-safety-buffer now rejects negative values via a custom pflag.Value, same pattern as --graceful-shutdown-timeout.
…imeout - Don't shrink the derived timeout below defaultGracefulShutdownTimeout when terminationGracePeriodSeconds is at or below Kubernetes' own 30s default, preserving today's behavior for installs that never set it (NFR2). - Reject a negative --graceful-shutdown-safety-buffer, which would otherwise inflate the derived timeout past terminationGracePeriodSeconds. - Log the resolved graceful shutdown timeout, termination grace period, and safety buffer at Info level. - Rename the changelog fragment to match the actual PR number (10438). - Add test coverage for all of the above. Signed-off-by: AdonisPD <adonis.dutra@outsystems.com>
kaovilai
left a comment
There was a problem hiding this comment.
Avoid using the entire terminationGracePeriodSeconds as the application’s shutdown timeout. Reserve a safety margin and account for any preStop hook: the termination grace-period countdown begins before the hook runs, and SIGTERM is sent only after the hook completes. Both therefore consume the same budget, after which Kubernetes may forcibly kill the container.
The current safety-buffer approach is appropriate, but the documentation should clarify that operators with a preStop hook must configure the buffer to cover the hook’s maximum duration plus a small final shutdown margin.
| if terminationGracePeriod <= defaultGracefulShutdownTimeout { | ||
| // Subtracting the safety buffer here would shrink the timeout below today's | ||
| // hardcoded default for installs that never set terminationGracePeriodSeconds | ||
| // (Kubernetes itself defaults it to 30s), which would be a behavior change (NFR2). | ||
| return defaultGracefulShutdownTimeout |
There was a problem hiding this comment.
The derived timeout does not always preserve a safety margin.
When terminationGracePeriodSeconds <= 30s, this branch returns a full 30-second manager timeout:
For a 30-second Pod grace period, that consumes the entire termination budget; below 30 seconds, it exceeds it. Kubernetes may therefore send SIGKILL before controller-runtime finishes waiting.
Also, a preStop hook runs before SIGTERM and consumes the same termination grace period. The current derivation does not inspect the hook, so the default 10-second buffer may be insufficient when one is configured.
Could we ensure the resolved timeout is always strictly below the Pod’s grace period? At minimum, the documentation should state that --graceful-shutdown-safety-buffer must cover the maximum preStop duration plus a final safety margin. The explicit timeout override should carry the same warning because it can equal or exceed the Pod’s total budget.
There was a problem hiding this comment.
Hey @kaovilai, really appreciate the careful read here — this caught a real gap. I pushed a fix in the latest commit that I'd love your eyes on:
- Removed the
terminationGracePeriodSeconds <= 30s → return 30sshort-circuit entirely.resolveGracefulShutdownTimeoutnow uses one uniform formula for every case,derived = terminationGracePeriodSeconds - safetyBuffer, with no exceptions — including pods at Kubernetes' own 30s default, which are no longer exempt. So the resolved timeout should now always be strictly below the pod's grace period, not just "never above" it. - Lowered
--graceful-shutdown-safety-buffer's default from 10s to 2s, so that change stays small for existing installs (30s → 28s instead of 30s → 20s). - The
derived <= 0fallback (buffer doesn't fit withinterminationGracePeriodSecondsat all) now returnsterminationGracePeriodSecondsitself with zero margin, instead of a flat 30s — so it won't discard a grace period an operator deliberately raised above 30s just because the buffer happened not to fit.
I also went and checked the Kubernetes container lifecycle hooks docs, and they back up your point nicely: the grace period countdown starts before preStop runs, and its execution time counts against terminationGracePeriodSeconds rather than being additional to it — Kubernetes' own worked example (60s grace period, 55s hook, 10s app shutdown → killed before finishing) is pretty much the failure mode you described. One thing worth flagging, in case it's useful context: --graceful-shutdown-safety-buffer itself isn't a Kubernetes-documented mechanism as such — the k8s docs describe the operator's responsibility to size terminationGracePeriodSeconds generously, but don't have a notion of the app subtracting its own buffer. Ours is a Velero-side defensive margin for the "last mile" after controller-runtime's manager gives up waiting on reconciles (process exit, log flush, scheduling jitter), rather than something Kubernetes itself prescribes — I added a note about that distinction in customize-installation.md so it's not misread as an official guideline.
One question I'd genuinely like your take on: should we also try to read the preStop hook itself (e.g. Sleep.Seconds off the pod spec) and fold its duration into the derivation automatically? I'm currently leaning toward not doing that, mainly because:
- Kubernetes doesn't seem to model this as something an app introspects — the convention is that SIGTERM arrival is the "start shutting down" signal, and sizing the budget around a
preStophook reads more like the operator's job via the pod spec than the app's at runtime. Sleepis the only hook type with a bounded duration in the API;Exec/HTTPGetdon't have one, so auto-detection would only ever cover a fraction of real configurations.- Before this PR,
velero serverhad zero awareness ofterminationGracePeriodSecondsorpreStopat all, so documenting the buffer's role (which I've now done) already feels like a solid improvement over the status quo.
But I hold that opinion loosely — if you think auto-detecting Sleep is worth the added complexity, happy to explore it further.
Thanks again for pushing on this, it made the fix noticeably more correct.
… shutdown timeout - Remove the terminationGracePeriodSeconds <= 30s short-circuit entirely. It returned a flat 30s even for pods with a shorter grace period, overshooting the pod's own budget. resolveGracefulShutdownTimeout now uses one uniform formula for every case, including pods at Kubernetes' own 30s default, so the resolved timeout is always strictly below the pod's grace period. - Lower --graceful-shutdown-safety-buffer's default from 10s to 2s, so the behavior change for default installs stays small (30s -> 28s instead of 30s -> 20s). - Change the derived<=0 fallback (safety buffer doesn't fit within terminationGracePeriodSeconds at all) to return terminationGracePeriodSeconds itself with zero margin, instead of a flat 30s, so it no longer discards a grace period an operator deliberately raised above 30s. - Document that the safety buffer is a velero-side defensive margin, not a Kubernetes-documented mechanism, and note the preStop hook interaction kaovilai raised. - Update test coverage for the new derivation behavior. Signed-off-by: AdonisPD <adonis.dutra@outsystems.com>
|
Let's target this one to post v1.19 as we are controlling the scope of it to ensure meeting the FC milestone. |
…ceful-shutdown-timeout # Conflicts: # pkg/cmd/server/config/config.go
…t names The Velero.io word list check in the Pull Request Codespell Check workflow rejects a lowercase " kubernetes ", which two test case names in TestResolveGracefulShutdownTimeout were tripping. Signed-off-by: AdonisPD <adonis.dutra@outsystems.com>
Summary
velero serverbuilds itscontroller-runtimemanager without settingGracefulShutdownTimeout, so it always inheritscontroller-runtime's hardcoded 30s default, regardless of how high an operator raises the pod'sterminationGracePeriodSeconds. On node disruption (Karpenter drift/consolidation, cluster-autoscaler scale-down, cloud-provider node maintenance) the pod is deleted directly rather than replaced through a rollout, so the manager force-exits after only 30s, cutting off any in-flight backup/restore reconcile and leaving itFailedorPartiallyFailed.This PR makes
velero serverderive its shutdown timeout automatically from the pod's ownterminationGracePeriodSeconds(minus a safety buffer), falling back to today's 30s whenever derivation isn't possible, while still allowing an explicit override:--graceful-shutdown-timeoutto override the derived value explicitly; an explicit0or negative value is rejected at flag-parse time.--graceful-shutdown-safety-buffer(default 10s) to control the buffer subtracted fromterminationGracePeriodSecondsduring derivation.POD_NAMEDownward API env var to thevelero install-generated Deployment, so the self pod lookup used for derivation works for non-Helm installs too.site/content/docs/main/customize-installation.md, and adds a changelog fragment.Closes #10437.
Not addressed by this PR (tracked separately):
Failedon startup.POD_NAME, so Helm-based installs will hit the 30s fallback until that chart change merges. Tracked as vmware-tanzu/helm-charts#749, fixed by vmware-tanzu/helm-charts#748, sequenced to merge after this PR.Test plan
go test ./pkg/cmd/server/... ./pkg/install/...— all pass, no regressions.TestGracefulShutdownTimeoutFlag/TestGracefulShutdownSafetyBufferFlag(pkg/cmd/server/config/config_test.go) — flag defaults, valid explicit values, and rejection of0/negative values.TestResolveGracefulShutdownTimeout(pkg/cmd/server/server_test.go) — covers explicit-flag precedence, successful derivation from the pod spec,POD_NAMEunset fallback,terminationGracePeriodSecondsunset fallback, and underflowed-derivation fallback.TestDeployment(pkg/install/deployment_test.go) — updated for the newPOD_NAMEenv var.