feat: inject shutdown-on-sigterm failover by default - #268
Conversation
Render `shutdown-on-sigterm failover` in the base cluster config so a primary hands its slots off to a replica during graceful shutdown (node drain, eviction, preemption). This covers out-of-band descheduling that the operator's own rolling failover never observes. Requires Valkey 9.0+. Signed-off-by: melancholictheory <selimvhorst@gmail.com>
|
| Filename | Overview |
|---|---|
| internal/controller/config.go | Adds "shutdown-on-sigterm failover" to getBaseConfig; correctly scoped to ValkeyCluster only (standalone ValkeyNode uses buildManagedConfig which is unaffected), and last-value-wins placement makes it non-overridable by user config. |
| internal/controller/config_test.go | Adds a ContainSubstring assertion for "shutdown-on-sigterm failover" in the rendered config; matches the exact "key value\n" format produced by writeConfigLine. |
| docs/valkeycluster.md | Adds a Graceful shutdown section; default values cited (30s terminationGracePeriodSeconds, 5s cluster-manual-failover-timeout) are accurate per Valkey documentation and give correct margin guidance. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant K8s as Kubernetes
participant Primary as Valkey Primary Pod
participant Replica as Valkey Replica Pod
K8s->>Primary: SIGTERM (node drain / eviction / preemption)
Note over Primary: shutdown-on-sigterm failover triggers
Primary->>Replica: CLUSTER FAILOVER (manual)
Note over Replica: Pauses replication stream,<br/>catches up to primary offset
Replica-->>Primary: Failover votes secured
Note over Replica: Promoted to Primary<br/>(within cluster-manual-failover-timeout, default 5s)
Primary->>Primary: Demoted to Replica
Note over Primary: Normal shutdown sequence<br/>(within terminationGracePeriodSeconds, default 30s)
Primary->>K8s: Pod exits cleanly
Note over K8s: SIGKILL never sent<br/>(~25s margin with defaults)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant K8s as Kubernetes
participant Primary as Valkey Primary Pod
participant Replica as Valkey Replica Pod
K8s->>Primary: SIGTERM (node drain / eviction / preemption)
Note over Primary: shutdown-on-sigterm failover triggers
Primary->>Replica: CLUSTER FAILOVER (manual)
Note over Replica: Pauses replication stream,<br/>catches up to primary offset
Replica-->>Primary: Failover votes secured
Note over Replica: Promoted to Primary<br/>(within cluster-manual-failover-timeout, default 5s)
Primary->>Primary: Demoted to Replica
Note over Primary: Normal shutdown sequence<br/>(within terminationGracePeriodSeconds, default 30s)
Primary->>K8s: Pod exits cleanly
Note over K8s: SIGKILL never sent<br/>(~25s margin with defaults)
Reviews (2): Last reviewed commit: "docs: clarify shutdown-on-sigterm grace-..." | Re-trigger Greptile
Spell out the actual defaults: the Kubernetes terminationGracePeriodSeconds of 30s comfortably exceeds the Valkey cluster-manual-failover-timeout default of 5s, so the failover completes out of the box. A grace-period bump is only needed when the failover timeout is raised. Signed-off-by: melancholictheory <selimvhorst@gmail.com>
|
on the grace-period note: the numbers are the other way round. the default cluster-manual-failover-timeout is 5s, not 30s (config.c registers it with a 5000ms default), so with the Kubernetes default terminationGracePeriodSeconds of 30s there is roughly 25s of margin and the failover completes out of the box. updated the docs to spell out both defaults and to say you only need to bump the grace period if you raise the timeout. on whether shutdown-on-sigterm should be overridable: fair distinction, it is more of an operational preference than the correctness settings it sits next to in getBaseConfig. the operator doesn't have an "overridable defaults" layer today, it is either enforced base config or user config. #248 framed it as injected by default, so i kept it enforced, but happy to move it to an overridable layer if you'd rather users could set nosave / default. @jdheyburn that one is a design call for you. |
|
Let's keep it enforced for now, if there is a need to override the defaults then we'll consider that when we get there. |
jdheyburn
left a comment
There was a problem hiding this comment.
LGTM, thank you for updating the docs too!
|
For the e2e test, I think a follow up issue is fine for now. |
Closes valkey-io#248 ### Summary Inject `shutdown-on-sigterm failover` into the managed Valkey config by default for every ValkeyCluster. On SIGTERM (node drain, eviction, preemption, or `kubectl delete pod`), a primary fails its slots over to a replica during graceful shutdown, which covers the unplanned termination that the operator's own proactive failover never sees. ### Implementation - Added the directive to `getBaseConfig`, alongside the other operator-managed defaults. - It is a Valkey 9.0+ directive, which matches the operator's documented baseline, so it renders unconditionally with no version gate. - No double-failover risk: it only fires if the node is still primary at SIGTERM, and it is a no-op on replicas. ### Acceptance criteria - [x] `shutdown-on-sigterm failover` injected into valkey.conf by default - [x] Works alongside the operator's proactive failover without double-firing (no-op on replicas) - [ ] E2E test (drain the primary's node, verify a replica is promoted before the pod exits) I left the E2E test out for now since it needs node-drain orchestration in the e2e harness. Happy to add it here or as a follow-up, whichever you prefer. The unit test asserts the directive is rendered. The `terminationGracePeriodSeconds` constraint is tracked separately in valkey-io#260; the docs note the relationship. ### Testing - Unit test asserts `shutdown-on-sigterm failover` is in the rendered config. - `make test` and `make lint` pass locally. ### References - Discussion valkey-io#231 (shutdown-on-sigterm section) - valkey-io#120 (out-of-band termination discussion) - valkey/valkey#1091 ### Checklist - [x] This Pull Request is related to one issue. - [x] Commit message explains what changed and why - [x] Tests are added or updated. - [x] Documentation files are updated. - [ ] I have run pre-commit locally (ran `make test` and `make lint` instead) --------- Signed-off-by: melancholictheory <selimvhorst@gmail.com>
Cover the graceful-termination handover deferred from valkey-io#268: delete a shard primary with the default grace period and assert a replica is promoted before the grace period ends, the shard keeps serving writes, the replaced pod rejoins as a replica, and no keys are lost. Roles are read live from INFO replication rather than ValkeyNode status, which can report stale roles right after cluster formation (see valkey-io#261). VALKEYCLI_AUTH is unset when running valkey-cli inside the server container, since valkey-cli would otherwise auto-send AUTH as the default user and fail. Closes valkey-io#270 Signed-off-by: Sagar Utekar <sagarutekar2366@gmail.com>
#271) Closes #260 ### Summary Add `spec.terminationGracePeriodSeconds` to `ValkeyCluster` (threaded through to `ValkeyNode` and onto the pod) so the graceful `CLUSTER FAILOVER` triggered on SIGTERM has time to hand the shard off to a replica before SIGKILL. ### Design Following the direction in #260: - New `TerminationGracePeriodSeconds *int64` on both `ValkeyClusterSpec` (user-facing) and `ValkeyNodeSpec` (the cluster controller threads it through to the pod). Not grouped under a pod-template surface yet, per your note that we can redesign that while still in alpha. - When unset, the operator derives a safe value: `max(30s, cluster-manual-failover-timeout / 1000 + 10s)`. With the defaults (5s timeout) that stays at the Kubernetes default of 30s. Raising the timeout pulls the grace period up with it. - An explicit value is honoured as-is, so a user who wants a long grace period (for example to allow a final RDB snapshot) gets exactly what they asked for. If the value is below the recommended minimum, the operator emits a `GracePeriodTooShort` warning event on the `ValkeyCluster` instead of silently overriding it. ### On the "block the apply" idea You floated blocking the manifest apply when the value is too short. i went with respect-the-value-and-warn instead, because the recommended minimum depends on `cluster-manual-failover-timeout`, which lives in `spec.config` as a string map entry. a CEL admission rule can't cleanly parse and compare that, so a hard block would need a validating webhook. happy to add that as a follow-up if you'd rather it be a hard stop; the warning event is the lighter "inform the user" mechanism for now. ### Acceptance criteria - [x] Warns (event + log) when `terminationGracePeriodSeconds` is below the recommended minimum - [~] Reconcile-time check (CEL block deferred, see above) - [x] Documented in the CRD field comments and `docs/valkeycluster.md` ### Testing - Unit tests for the timeout / recommended / effective grace-period helpers. - `make test` and `make lint` pass locally. Docs note: i added a `Termination grace period` section, which sits next to the `Graceful shutdown` section from #268 once that merges. ### Checklist - [x] This Pull Request is related to one issue. - [x] Commit message explains what changed and why - [x] Tests are added or updated. - [x] Documentation files are updated. - [ ] I have run pre-commit locally (ran `make test` and `make lint` instead) --------- Signed-off-by: melancholictheory <selimvhorst@gmail.com>
This PR closes #270 ### Summary Adds the E2E test deferred from #268: verify that when a primary pod is gracefully terminated, the `shutdown-on-sigterm failover` directive hands the shard off to a replica before the pod exits, so the shard keeps a writer through the disruption and no data is lost. ### Features / Behaviour Changes Test-only change; no operator behaviour is modified. The new spec runs under the `failover` Ginkgo label. ### Implementation The test follows the outline in #270: 1. Creates a `ValkeyCluster` with `shards: 3, replicas: 1` and waits for `Ready`. 2. Identifies shard 0's primary and replica, and records the primary pod's UID so the StatefulSet-recreated pod (same name) can be distinguished from the old one. 3. Writes 50 keys across the keyspace, then deletes the primary pod with the default grace period (SIGTERM path — the lighter proxy for a drain mentioned in the issue notes). 4. Asserts the replica reports `role:master` within the 30s grace window (`Eventually` timeout = `terminationGracePeriodSeconds`), i.e. the handover beat SIGKILL. 5. Asserts the shard keeps accepting writes, the replaced pod comes back (new UID) and rejoins as `role:slave`, `cluster_state:ok`, and all 50 keys read back intact. Two things reviewers may want to pay attention to, both learned from runs of this test against Kind: - **Roles are read live from `INFO replication`, not from `ValkeyNode.status.role`.** Right after cluster formation the status can report two primaries for a shard (every node boots as a master before `CLUSTER REPLICATE`, and the status refresh lags) — this is the staleness described in #261, and the first draft of this test flaked on exactly that. The replica is also only accepted once `master_link_status:up`, so the failover is not attempted against a still-syncing replica. - **`VALKEYCLI_AUTH` is unset before running `valkey-cli` inside the server container** (`execValkeyPodShell` helper). The operator injects that variable for the probe scripts, and `valkey-cli` auto-sends `AUTH` as the *default* user whenever it is set — which fails (`ERR AUTH ... without any password configured for the default user`) and pollutes command output. Commands run as the default nopass user, consistent with the rest of the e2e suite. The `_operator` user cannot be used instead because its ACL has no `SET`/`GET`. ### Limitations - Uses `kubectl delete pod` (graceful, default grace period) rather than a node drain; the issue notes name this as the acceptable lighter proxy for CI. A drain-based variant can be layered on later. - "Promoted before the grace period ends" is asserted by bounding the promotion check at 30s after the delete, matching the default `terminationGracePeriodSeconds`; the observed promotion latency in practice is ~4s. ### Testing Run against a 3-node Kind cluster via: ``` KIND_CLUSTER=<cluster> go test -tags=e2e ./test/e2e/ -v -ginkgo.v -ginkgo.label-filter failover ``` Result: `1 Passed | 0 Failed` in 159s. Timeline from the passing run: SIGTERM at `22:21:06.4`, replica reported `role:master` by `22:21:10.1` (~4s, well inside the 30s grace period), replaced pod rejoined as replica ~4s later, `cluster_state:ok`, `readable=50` keys plus the write made during the disruption. `go vet -tags=e2e` and `golangci-lint run --build-tags e2e` are clean for the new file. ### Checklist Before submitting the PR make sure the following are checked: - [x] This Pull Request is related to one issue. - [x] Commit message explains what changed and why - [x] Tests are added or updated. - [x] Documentation files are updated. - [x] I have run pre-commit locally (`pre-commit run --all-files` or hooks on commit) --------- Signed-off-by: Sagar Utekar <sagarutekar2366@gmail.com> Co-authored-by: sandeep kunusoth <31273507+sandeepkunusoth@users.noreply.github.com>
Closes #248
Summary
Inject
shutdown-on-sigterm failoverinto the managed Valkey config by default for every ValkeyCluster. On SIGTERM (node drain, eviction, preemption, orkubectl delete pod), a primary fails its slots over to a replica during graceful shutdown, which covers the unplanned termination that the operator's own proactive failover never sees.Implementation
getBaseConfig, alongside the other operator-managed defaults.Acceptance criteria
shutdown-on-sigterm failoverinjected into valkey.conf by defaultI left the E2E test out for now since it needs node-drain orchestration in the e2e harness. Happy to add it here or as a follow-up, whichever you prefer. The unit test asserts the directive is rendered.
The
terminationGracePeriodSecondsconstraint is tracked separately in #260; the docs note the relationship.Testing
shutdown-on-sigterm failoveris in the rendered config.make testandmake lintpass locally.References
Checklist
make testandmake lintinstead)