Conversation
Replication mode alone cannot recover from a master failure: the master is always pod-0 and clients keep writing to it until an operator intervenes. Run a valkey-sentinel container next to the Valkey server in every pod, so that the Sentinels monitor each other and promote a replica automatically when the master stops responding. Enabled with replica.sentinel.enabled. * Stop pinning the valkey service selector to pod-0 when Sentinel is enabled, the master can be any pod after a failover. * Preserve the replication target Sentinel wrote via CONFIG REWRITE across restarts, instead of resetting every pod to "pod-0 is master". * Rediscover the current master on Sentinel startup by asking the running peers first and the local Valkey node second, so that a restarted Sentinel never tries to demote the real master. * Ask Sentinel to fail over in a preStop hook when the terminating pod is the master, so a rolling update does not leave clients on a dying master. * Secure the Sentinel port and the Sentinel gossip with the default user credentials whenever auth is enabled. * Drop a stray tab in the statefulset volumeClaimTemplates that made the rendered manifests unparseable for strict YAML parsers. Co-authored-by: KHTee <teekahhui@hotmail.com> Co-authored-by: Jose Dominguez <jdominguez.ops@gmail.com> Co-authored-by: yoannrt <yoann.rousseau@gmail.com> Co-authored-by: Dieter Maes <dieter.maes@dmaes.be> Co-authored-by: Tim Karger <49390121+tkarger@users.noreply.github.com> Co-authored-by: lazariv <lazariv.taras@gmail.com> Signed-off-by: pat-s <patrick.schratz@devxy.io>
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe Helm chart adds Valkey Sentinel high-availability mode. It introduces Sentinel configuration, StatefulSets, services, topology discovery, authentication, TLS support, graceful failover, validation, examples, documentation, and Helm tests. ChangesValkey Sentinel high availability
Sequence Diagram(s)sequenceDiagram
participant SentinelStatefulSet
participant SentinelStartup
participant ValkeyInit
participant ValkeyNode
participant SentinelService
SentinelStatefulSet->>SentinelStartup: start Sentinel
SentinelStartup->>SentinelService: discover current master
ValkeyInit->>ValkeyNode: restore or assign replication target
SentinelStartup->>ValkeyNode: monitor Valkey configuration
SentinelStatefulSet->>SentinelStartup: run pre-stop hook
SentinelStartup->>SentinelService: request master failover
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
valkey/README.md (1)
43-45: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winRemove the empty
**IMPORTANT**label.The content that followed this label moved out of the Replication Mode section. The label now renders as a bold word with no body.
📝 Proposed change
-**IMPORTANT** - **Services:**🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@valkey/README.md` around lines 43 - 45, Remove the standalone **IMPORTANT** label immediately before the Services section in the README, leaving the Services content and surrounding documentation unchanged.
🧹 Nitpick comments (2)
valkey/examples/ha-sentinel.yaml (1)
29-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the
onflag fromreplicator.permissions.The chart emits the ACL rule as
user <name> on >password <permissions>, as shown invalkey/values.yamlline 238. Thedefaultuser above and the examples invalues.yamlomiton. Keep the same form here so users do not copy a duplicated flag.♻️ Proposed change
permissions: >- - on ~* &* +multi +exec +ping +info +role +subscribe +publish +slaveof + ~* &* +multi +exec +ping +info +role +subscribe +publish +slaveof +replicaof +config|rewrite +client|setname +client|kill +script|kill +psync +replconf🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@valkey/examples/ha-sentinel.yaml` around lines 29 - 36, Remove the leading “on” token from the replicator.permissions ACL rule while preserving all existing key patterns and command permissions, matching the format used by the chart’s ACL rendering and other examples.valkey/templates/service.yaml (1)
34-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThe service keeps write semantics that it no longer provides in Sentinel mode.
Removing the pod-0 pin is correct, because the master moves after a failover. The service still carries
app.kubernetes.io/component: primaryat line 7, andNOTES.txtstill presents it under "WRITE Operations". Clients that follow the label or the notes will send writes to replicas and receive-READONLY.Consider setting a different component label in Sentinel mode, so that consumers of the label do not treat this service as master-only.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@valkey/templates/service.yaml` around lines 34 - 38, Update the service metadata around the component label and the Sentinel configuration condition so Sentinel mode no longer advertises the service as primary/master-only. Preserve the primary component label for non-Sentinel deployments, and align the service label with the behavior documented for Sentinel consumers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@valkey/README.md`:
- Around line 104-107: Update the fenced code block containing the ACL
permission list in the README to specify the text language, changing the opening
fence to use text while preserving the block contents.
In `@valkey/templates/_helpers.tpl`:
- Around line 219-224: Update the Sentinel monitor-user resolution in the
auth-enabled block around $monitorUser and valkey.validateSentinelConfig so
auth.aclConfig-only configurations do not dereference a missing auth.aclUsers
map. Add the same nil-safe guard before reading auth.aclUsers, or ensure the
validator emits the existing explicit missing-user failure before this lookup,
while preserving the current fallback and fail message for defined ACL users.
In `@valkey/templates/init_config.yaml`:
- Around line 181-196: The sentinel bootstrap decision in init.sh must not let
pod 0 become MASTER solely because local PVC state is missing after failover.
Before the POD_INDEX master branch, reuse sentinel-start.sh’s peer-discovery
mechanism to query a reachable Sentinel and, when it reports a promoted master,
set REPLICATION_TARGET to replicaof that address; only fall back to pod 0 as
MASTER when discovery finds no existing master.
- Around line 169-179: Update the listening-port configuration to use
.Values.service.port instead of a hardcoded 6379, preserving the existing
TLS/non-TLS directive ordering. Apply this in valkey/templates/init_config.yaml
lines 169-179 and update the corresponding hardcoded port references in
valkey/templates/sentinel-configmap.yaml lines 75-81 and 279-285 so Sentinel
uses the same configured port.
In `@valkey/templates/NOTES.txt`:
- Around line 26-32: Update the Sentinel connection examples in NOTES.txt to
branch on .Values.replica.sentinel.service.enabled: use the existing service
hostname and sentinel.service.port when enabled, and use the headless service
hostname with .Values.replica.sentinel.port when disabled. Apply the same
conditional endpoints to both the valkey-cli command and the Python Sentinel
example.
In `@valkey/templates/sentinel-configmap.yaml`:
- Around line 86-101: The master_from_peers function must validate sentinel_cli
output before accepting it: require exactly two stdout lines containing a valid
master host and port, and reject empty, malformed, or error-reply output when
the peer is not configured for MASTER_SET. Only log, echo, and return success
for an unambiguous validated address; otherwise continue checking the remaining
peers.
In `@valkey/templates/sentinel-service.yaml`:
- Around line 21-23: Update the loadBalancerSourceRanges rendering in the
sentinel service template to serialize the list with Helm’s toYaml helper,
preserving valid YAML sequence syntax and the existing conditional block.
In `@valkey/templates/statefulset.yaml`:
- Around line 19-25: Add an upgrade note under the Sentinel section in
valkey/README.md documenting that enabling Sentinel may change immutable
StatefulSet fields, including podManagementPolicy and persistence
volumeClaimTemplates, so users must delete the StatefulSet with --cascade=orphan
before helm upgrade to allow existing pods and PVCs to be re-adopted.
---
Outside diff comments:
In `@valkey/README.md`:
- Around line 43-45: Remove the standalone **IMPORTANT** label immediately
before the Services section in the README, leaving the Services content and
surrounding documentation unchanged.
---
Nitpick comments:
In `@valkey/examples/ha-sentinel.yaml`:
- Around line 29-36: Remove the leading “on” token from the
replicator.permissions ACL rule while preserving all existing key patterns and
command permissions, matching the format used by the chart’s ACL rendering and
other examples.
In `@valkey/templates/service.yaml`:
- Around line 34-38: Update the service metadata around the component label and
the Sentinel configuration condition so Sentinel mode no longer advertises the
service as primary/master-only. Preserve the primary component label for
non-Sentinel deployments, and align the service label with the behavior
documented for Sentinel consumers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c6fd83c9-180e-40e7-9759-3b120b5dc905
📒 Files selected for processing (16)
valkey/.helmignorevalkey/Chart.yamlvalkey/README.mdvalkey/examples/ha-sentinel.yamlvalkey/templates/NOTES.txtvalkey/templates/_helpers.tplvalkey/templates/deploy_valkey.yamlvalkey/templates/init_config.yamlvalkey/templates/sentinel-configmap.yamlvalkey/templates/sentinel-service.yamlvalkey/templates/service-headless.yamlvalkey/templates/service.yamlvalkey/templates/statefulset.yamlvalkey/tests/sentinel_test.yamlvalkey/values.schema.jsonvalkey/values.yaml
The bootstrap marker was written on every pod, so a pod that lost its valkey.conf while keeping the marker, for example when the init container was interrupted between removing the old config and writing the new one, read the marker as proof that it used to be the master and came back as a second writable master. Drop the marker and derive the role from the previous config instead, which already carries the answer: a config with a replicaof directive means replica, a complete config without one means master, and no config at all means this is a first boot that falls back to the pod index. Assemble the config in a temporary file and move it into place at the end, so a config on disk is always complete and the distinction holds. Also only disable protected mode on Sentinel when auth is disabled, where peers could not connect otherwise, and say so in values.yaml. Signed-off-by: pat-s <patrick.schratz@devxy.io>
|
Thanks bots. Pushed a fix for two of the three findings: Bootstrap marker split-brain (Greptile, P1) — real, fixed. The marker is gone. The role now comes from the previous config, which already carries the answer: a Verified on a kind cluster by wiping Unauthenticated Sentinel port (Greptile, P1) — Nil Also took the MD040 suggestion. |
…argv Review follow-ups on the Sentinel support. * Ask the running Sentinels for the current master when a pod starts without a usable config. A pod that lost its volume previously fell back to the pod index, so pod-0 came back as a second writable master after a failover. * Render the Valkey listen port from service.port instead of a hardcoded 6379, which made a custom service.port disagree with the container port. * Pass credentials to valkey-cli through REDISCLI_AUTH so they no longer show up in the process list of the pod. * Reject an incomplete answer from a peer Sentinel, an error reply would otherwise be written into the monitor directive and stop Sentinel from starting. Bound the peer queries with a timeout and widen the startup probe to cover discovery. * Set terminationGracePeriodSeconds and refuse a preStop failover timeout that would be cut short by SIGKILL. * Allow replica.podManagementPolicy to be pinned, so an existing release can enable Sentinel without recreating the immutable StatefulSet, and document the recreate for anyone who takes the default. * Render loadBalancerSourceRanges as a YAML list, and point the notes at the headless service when the Sentinel service is disabled. Signed-off-by: pat-s <patrick.schratz@devxy.io>
|
Second round of review fixes pushed. Verified on a kind cluster with ACL auth. Fixed
Not changing
167 unit tests, |
|
thanks @pat-s for the pr can you provide me an example values.yaml for testing |
Signed-off-by: pat-s <patrick.schratz@gmail.com>
|
@Bloodraven21 sure — here is a complete example using an existing Secret (recommended): First create the three credentials. kubectl create secret generic valkey-users \
--from-literal=default="replace-with-a-strong-app-password" \
--from-literal=replicator="replace-with-a-strong-replication-password" \
--from-literal=sentinel="replace-with-a-strong-sentinel-password"Save this as replica:
enabled: true
replicas: 2
persistence:
size: 1Gi
replicationUser: replicator
sentinel:
enabled: true
quorum: 2
passwordKey: sentinel
auth:
enabled: true
usersExistingSecret: valkey-users
aclUsers:
default:
permissions: "~* &* +@all"
replicator:
permissions: >-
~* &* +multi +exec +ping +info +role +subscribe +publish +slaveof
+replicaof +config|rewrite +client|setname +client|kill +script|kill
+psync +replconfInstall from the PR checkout with: helm install valkey ./valkey -f values-sentinel.yamlThe chart also contains an inline-password example at |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@valkey/values.yaml`:
- Around line 356-361: Update the comment beside replica.sentinel.password and
passwordKey to state that the existing secret’s sentinel key takes priority,
while the inline password is used as a fallback when that key is absent. Remove
the wording that claims the inline value is ignored, while preserving the
explanation that Sentinel credentials are independent from Valkey ACLs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ef0b6a6e-2254-4105-875c-464b85033776
📒 Files selected for processing (13)
valkey/README.mdvalkey/examples/ha-sentinel.yamlvalkey/templates/NOTES.txtvalkey/templates/_helpers.tplvalkey/templates/init_config.yamlvalkey/templates/secret.yamlvalkey/templates/sentinel-configmap.yamlvalkey/templates/sentinel-service.yamlvalkey/templates/statefulset.yamlvalkey/tests/secret_test.yamlvalkey/tests/sentinel_test.yamlvalkey/values.schema.jsonvalkey/values.yaml
🚧 Files skipped from review as they are similar to previous changes (6)
- valkey/templates/NOTES.txt
- valkey/templates/statefulset.yaml
- valkey/templates/sentinel-service.yaml
- valkey/templates/sentinel-configmap.yaml
- valkey/values.schema.json
- valkey/README.md
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@valkey/templates/sentinel-configmap.yaml`:
- Line 204: Update the SENTINEL_PASSHASH assignment in the Sentinel
configuration template to use printf with a %s format for SENTINEL_PASSWORD
before piping it to sha256sum, replacing echo -n while preserving the existing
hash extraction.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: cd8aa689-6463-4a4e-9d10-97e969d63384
📒 Files selected for processing (10)
valkey/README.mdvalkey/examples/ha-sentinel.yamlvalkey/templates/_helpers.tplvalkey/templates/init_config.yamlvalkey/templates/secret.yamlvalkey/templates/sentinel-configmap.yamlvalkey/tests/secret_test.yamlvalkey/tests/sentinel_test.yamlvalkey/values.schema.jsonvalkey/values.yaml
🚧 Files skipped from review as they are similar to previous changes (9)
- valkey/values.schema.json
- valkey/tests/secret_test.yaml
- valkey/templates/secret.yaml
- valkey/values.yaml
- valkey/examples/ha-sentinel.yaml
- valkey/tests/sentinel_test.yaml
- valkey/templates/init_config.yaml
- valkey/templates/_helpers.tpl
- valkey/README.md
Signed-off-by: pat-s <patrick.schratz@gmail.com>
…e usable A password containing a backslash escape was corrupted on its way into the config, because /bin/sh in the Valkey image is dash and its echo expands \t and friends. With auth.aclUsers.default.password set to 'app\tpass' the server refused to start: *** FATAL CONFIG FILE ERROR *** >>> 'masterauth repl pass' wrong number of arguments Use printf everywhere a password is read, hashed or written, so the bytes the user configured are the bytes that end up in valkey.conf, the ACL file and sentinel.conf. This covers the ACL hashes flagged in review as well as the replication and Sentinel credentials, which had the same defect. Also create the generated auth Secret when replica.sentinel.password is the only inline password. It was gated on auth.aclUsers alone, so combining auth.usersExistingSecret with an inline Sentinel password produced a Secret and a volume that were never rendered and a Sentinel that could not start. Correct the values.yaml comment accordingly: the inline password is a fallback when the existing Secret lacks the key, it is not ignored. Signed-off-by: pat-s <patrick.schratz@devxy.io>
|
Addressed the valid findings in
I left unauthenticated Sentinel behavior unchanged when Verified on kind with backslash-containing application, replication, and Sentinel passwords. |
|
Want your agent to iterate on Greptile's feedback? Try greploops. |
Signed-off-by: pat-s <patrick.schratz@gmail.com>
|
Hi! Thanks for creating the new branch. I tested this branch in an environment with TLS and ACLs enabled. When trying to connect through HAProxy using a TLS-enabled client ( Looking at the current HAProxy configuration, the frontend is bound as plain TCP ( For environments where Valkey expects TLS directly from the client, having HAProxy act as a transparent L4 TCP passthrough works seamlessly:
I updated the config locally with this approach and confirmed that TLS connections from clients through HAProxy work as expected. Here is the backend config snippet that resolved the issue: global
ssl-server-verify none
backend valkey_master
mode tcp
balance first
option tcp-check
tcp-check connect port 6379 ssl
tcp-check send AUTH\ redis_haproxy_watcher\ "${VALKEY_CHECK_PASSWORD}"\r\n
tcp-check expect string +OK
tcp-check send info\ replication\r\n
tcp-check expect rstring role:master
tcp-check send QUIT\r\n
tcp-check expect string +OK
timeout check 5s
default-server check inter 5s fall 1 rise 1 init-addr last,libc,none init-state down resolvers kubernetes on-marked-down shutdown-sessions
backend valkey_replicas
mode tcp
balance roundrobin
option tcp-check
tcp-check connect port 6379 ssl
tcp-check send AUTH\ redis_haproxy_watcher\ "${VALKEY_CHECK_PASSWORD}"\r\n
tcp-check expect string +OK
tcp-check send PING\r\n
tcp-check expect string +PONG
tcp-check send QUIT\r\n
tcp-check expect string +OK
timeout check 5s
default-server check inter 5s fall 2 rise 1 init-addr last,libc,none init-state down resolvers kubernetes |
|
@jose-10000 thanks, reproduced and fixed. Your diagnosis was exactly right. The fix is in #235, which is where the HAProxy templates live. Reproduced first on kind with TLS and ACLs, using a client cert and CA from the same secret the chart mounts: So the frontend only ever served plaintext clients and silently hung TLS ones, which is a bad default when someone has deliberately enabled TLS. I took the passthrough approach you suggested and made it the default, with the old behaviour kept as an option, since bridging plaintext clients into a TLS cluster is a legitimate reason to run HAProxy in the first place.
One difference from your snippet: instead of Verified on kind after the change, TLS end to end through HAProxy: and One thing worth flagging for your setup: in passthrough the client validates the certificate presented by the Valkey pod while connecting to the HAProxy service name, so the server certificate needs a SAN for that name too (e.g. You may also see a brief |
A reading only counted once two checks agreed on it, which delayed a demotion by a second for no benefit. Only one direction is ambiguous: a check landing inside a config rewrite sees no replicaof line, exactly as a promotion does, and recording that wrongly brings the pod back writable. A replicaof line cannot be invented by a partial read, so record it at once and confirm only this pod naming itself. Stop parsing a last line that has no closing newline. That line is the tail of a file still being rewritten, and a truncated "replicaof host 63" was being recorded as a real port. Signed-off-by: pat-s <patrick.schratz@gmail.com>
With TLS enabled the init container asks Sentinel which node is the master using valkey-cli --tls --cacert /tls/ca.crt, but nothing mounted the TLS secret there. The query could not negotiate a connection, so a fresh pod found no topology from Sentinel and none on its volume, and refused to start. Mount the secret read only, and only alongside Sentinel: without it the init container opens no connection and has no reason to hold the key. Signed-off-by: pat-s <patrick.schratz@gmail.com>
A pod that starts from its recorded topology because no Sentinel answered can follow a node that has since been demoted. It then replicates through that node, and once the node it follows goes away it serves stale reads indefinitely. Sentinel does not repair this. It learns which nodes are replicas by asking the primary, and a node replicating from a replica is not in that answer, so its own fix-slave-config never applies to it. Have each Sentinel look for such a node and point it back at the primary. It only touches nodes Sentinel does not list, which are exactly the ones Sentinel is not reconfiguring itself, stands down while the primary is not plainly up, and leaves a node that answers as a primary alone. Signed-off-by: pat-s <patrick.schratz@gmail.com>
A zero byte value in auth.usersExistingSecret reads back as an empty password, and the scripts hashed it into the ACL. The SHA of an empty password is the same in every installation, so the account was left open to anyone who could reach the port: as default with full data access, and as sentinel with every Sentinel command including failover. Treat empty as missing wherever a credential is read from a mounted file, in the init script, in the Sentinel entrypoint and in the preStop hook. Signed-off-by: pat-s <patrick.schratz@gmail.com>
A pod that was down across a failover still has its own name in its record, so on a cold start with no Sentinel it came back writable next to the node that had been promoted. Two masters cannot be reconciled afterwards, and this needed no timing window at all: only a node that missed the failover and a Sentinel that is not answering yet. Ask the other nodes first and follow the one that answers as the master. It also settles a record that names a node which has since been demoted, so the pod attaches to the primary instead of replicating through it. The record still decides when nothing answers, so a whole cluster cold start behaves as before and cannot deadlock. Signed-off-by: pat-s <patrick.schratz@gmail.com>
The startup scan for a running primary ran INFO as replica.replicationUser, whose documented minimum is +psync +replconf +ping. That is refused, and valkey-cli prints the refusal with a zero exit, so the node read as "not the master" and the pod fell back to its record. A pod that had been down across a failover then came back writable next to the promoted node, which is what the scan exists to prevent. Ask as replica.sentinel.monitorUser, which Sentinel already requires to be allowed INFO, so this needs no permission the deployment does not have. Recognise a refusal and log it rather than reading it as a role. Signed-off-by: pat-s <patrick.schratz@gmail.com>
Without Sentinel the Service selects pod-0 and is the write endpoint. With Sentinel the master can be any pod, so the selector covers all of them and a write sent there can land on a replica and come back -READONLY. The README and the install notes both say so and send writes to the address Sentinel names, but the Service still carried app.kubernetes.io/component: primary, which says the opposite to anything selecting on it. Label it nodes in that mode. A Service cannot select on a role that moves, so the routing itself is unchanged. Signed-off-by: pat-s <patrick.schratz@gmail.com>
A fresh pod has no record, no running node, and no Sentinel that can name a master yet, and it exited on the spot. The install still completed, because the Sentinels bootstrap a master after their own startupTimeoutSeconds and a later retry picked that up, but only after the init container had crash looped four times and 93s had passed, with the pod sitting in Init:Error the whole time. Wait for the answer instead, bounded by replica.sentinel.initialTopologyWaitSeconds. On the same cluster that took 64s and no restarts, which is the Sentinels' own discovery time and nothing more. A deployment that never gets an answer still exits. Signed-off-by: pat-s <patrick.schratz@gmail.com>
The two timeouts are ordered but were independently settable, so raising replica.sentinel.startupTimeoutSeconds above the default topology wait put the init container back to exiting shortly before the Sentinels bootstrap a master, and the install went back to depending on a later retry. Refuse to render unless the wait is at least 30s above the bootstrap timeout, which is the same shape as the existing check between the preStop failover and the termination grace period. Signed-off-by: pat-s <patrick.schratz@gmail.com>
The tracker discarded write errors. A node demoted to follow a new primary would keep a record naming itself, and a later cold start with no Sentinel and no reachable peer comes back on exactly that record, writable. Say so once per outage, on stderr where the container log picks it up, and retry on the next tick as before. Write through a temporary name and rename, so a cold start reading the record while it is replaced sees the previous one rather than an empty file. Signed-off-by: pat-s <patrick.schratz@gmail.com>
The commands shown after an install carried --tls alone. With TLS enabled that cannot verify the server, and with tls.requireClientCertificate it cannot authenticate either, so following them verbatim fails. Print the CA, and the client certificate and key when they are required. The command that runs inside the Sentinel container uses the paths mounted there; the rest run from a shell, so they name the files the operator holds rather than paths that exist only in the pods. Signed-off-by: pat-s <patrick.schratz@gmail.com>
Three places write the record, and each redirected straight onto it. A pod killed part way through leaves an empty record, or on an unclean node a half written one, and the preStop hook writes it while the pod is already being torn down. Write under a temporary name and rename in all three, and stop the preStop hook from discarding the error. Refuse a record that is not a complete line when reading it back. That is what an interrupted write leaves, and the port half of it can still look numeric: a record cut after the first digit was accepted as port 6, and the pod came up replicating from a port nothing listens on. Signed-off-by: pat-s <patrick.schratz@gmail.com>
With TLS enabled the Sentinel endpoint listens on tls-port only, and the printed Sentinel() call carried credentials but no TLS, so it could not reach the Sentinels at all and discovery ended in MasterNotFoundError. Print the CA, and the client certificate and key when they are required, on both connections: the one inside sentinel_kwargs that reaches the Sentinels, and the one beside it that reaches the master they name. Signed-off-by: pat-s <patrick.schratz@gmail.com>
|
@Bloodraven21 @jose-10000 is there any status update on this pr? |
|
Thanks for picking this up and splitting it — I gave the branch a proper workout on a cluster and it holds together really well. Numbers below, plus a patch for the one open blocker. Setup
Each disruption ran under a monotonic-counter writer that resolves the master through Sentinel and reconnects on every write, at ~400-500 writes/s. So the outage column is what a client actually saw, and the counter is checked for regressions to catch a lost acknowledged write. Results
A repeat of the delete-master run measured 35997/36321 with a 2.0s outage, so expect roughly 0-2s of client-visible write outage depending on where in the The cold start is my favourite bit, because the log shows the mechanism. Master on pod-2 before the shutdown, so pod-0 has an index saying "you are the master" and no reachable Sentinel yet: And after wiping pod-0's PVC (confirmed replaced, not reused) it takes the other branch and asks Sentinel instead. Both paths do exactly what they say. Also worth noting for @jdheyburn's point: two data pods with three Sentinels is a working configuration here, and a failover still completes in it. That is the concrete payoff of the separate StatefulSet. One thing worth knowing operationally rather than fixing: every rolling restart left one replica chained behind another for a while — The
|
|
@discostur — confirmed on our cluster, Setup: $ valkey-cli -p 26379 -a "$PW" SENTINEL get-master-addr-by-name gitlab-kv
gitlab-kv-valkey-1.gitlab-kv-valkey-headless.gitlab-kv.svc.cluster.local
6379
$ valkey-cli -p 26379 -a "$PW" ACL WHOAMI
default
$ valkey-cli --user sentinel -p 26379 PING # stale username now fails closed
AUTH failed: WRONGPASS invalid username-password pair or user is disabled.Inter-Sentinel auth was my one worry, since dropping $ valkey-cli -p 26379 -a "$PW" SENTINEL master gitlab-kv
flags
master
num-slaves
2
num-other-sentinels
20 restarts. Enabling the switch rolled both StatefulSets and Sentinel tracked the primary through it For context on why no username can be sent: GitLab's chart exposes |
|
Please stop posting these verbose and non-helpful walls of text. I use AI myself and I am not against it but these kind of postings are really not helpful and close to spam. |
|
This is a great feature and I'm looking forward to being able to use it. Thanks for all the hard work @pat-s. Any idea when this can get merged in? |
Supersedes #137, rebased onto current
mainwith the review feedback applied.This PR adds Sentinel only; the HAProxy front-end follows separately.
Replication mode cannot recover from a primary failure on its own: pod 0 remains the configured primary until someone intervenes.
This change adds optional Valkey Sentinel support through
replica.sentinel.enabled, allowing a replica to be promoted automatically.Architecture
Sentinel runs in its own StatefulSet rather than as a Valkey sidecar.
The default topology is three Sentinel pods, independently of the number of Valkey pods.
This keeps Sentinel quorum and data-replica count separate, so Valkey can run with one primary and one replica while retaining three Sentinels.
Failover and recovery
valkeyService no longer pins its selector to pod 0 when Sentinel is enabled, because the primary can move, and is labelledapp.kubernetes.io/component: nodesrather thanprimaryto match.Each pod therefore keeps a credential-free record of the current primary on its data volume, refreshed while it runs, and that is what a cold start falls back on when no Sentinel and no node can be reached.
preStophook requests failover before terminating the current primary.Authentication
Sentinel has a dedicated ACL identity and password, independently of whether authentication is enabled for the Valkey data endpoint.
When Valkey authentication is enabled, Sentinel uses
replica.sentinel.monitorUserto monitor nodes and promote replicas, and a starting pod asks that same user which node is the primary, since the documented minimum for a replication user cannot runINFO.Credentials are mounted from Secrets and passed to
valkey-clithroughREDISCLI_AUTHrather than command-line arguments.Generated Valkey and Sentinel configuration quotes and escapes credentials containing whitespace, double quotes, or backslashes.
An empty value in a Secret is refused rather than hashed into an ACL, because the hash of an empty password is the same in every installation.
Testing
just validate: 285 unit tests across all three charts.helm lint ./valkey.valkey/examples/ha-sentinel.yaml.One drive-by fix removes a stray tab from
statefulset.yamlthat caused strict YAML parsers to reject the rendered manifest.Credit to @khtee, @jose-10000, @yoannrt, @tkarger, @lazariv, and @dmaes, whose work in #137 this builds on.