Files
tech-log-backend/infra/redis-sdk/sentinel/compose.yml
T

127 lines
5.0 KiB
YAML

# Sentinel lane. Three sentinels because a two-sentinel quorum cannot survive losing one, and a
# failover test that cannot lose a sentinel is not testing failover.
#
# Host networking, not a bridge with published ports. Sentinel does not proxy: it answers
# `SENTINEL get-master-addr-by-name` with the address it monitors, and the client then connects
# there itself. On a bridge that address is the container's internal IP, which the client on the
# host cannot reach, so the lane would resolve a primary it can never talk to — and after a
# promotion it would resolve a different unreachable one. Sharing the host network namespace makes
# the address Sentinel hands out the same address the client can dial, which is the only thing that
# makes the promotion observable from outside.
#
# Ports are fixed rather than parameterised because Sentinel stores them in its own config: the
# monitored address has to match what the client is told, and a lane whose two halves can disagree
# is a lane that fails for reasons that are not the SDK's.
#
# primary 7010 · replica 7011 · sentinels 27010 27011 27012
#
# The ACL file is loaded on both data nodes. The accounts are the deployment's last enforcement
# boundary, so "they exist in standalone" is not evidence that they exist in the topology that will
# actually be run in production.
#
# Both data nodes take their entire configuration from one definition, and that is load-bearing
# rather than tidiness. These two nodes swap roles on every failover, so a setting written only into
# the one that happens to start as primary silently stops applying the moment the lane does the
# thing it exists to do. The lane learned this the hard way: min-replicas-to-write was set on the
# primary only, the first promotion passed, and the second promotion — now writing to the node that
# never had the setting — discarded 2,099 acknowledged writes.
x-data-node: &data-node
image: "redis:${REDIS_VERSION:-7.4}"
network_mode: host
volumes:
- ../acl:/etc/redis/acl:ro
entrypoint:
- /bin/sh
- -c
# REPLICA_OF is deliberately unquoted: it is either empty or a two-word --replicaof argument.
#
# min-replicas-to-write is what stops a superseded primary from acknowledging writes it cannot
# keep. Without it a promotion silently destroys them — measured here at eleven seconds and two
# thousand confirmed-then-discarded writes — because Sentinel does not demote the old primary
# until well after it has promoted the new one. Requiring an in-sync replica turns that window
# into an explicit NOREPLICAS refusal the caller can see and act on. Any deployment where an
# acknowledgement is supposed to mean something has to set these.
- |
exec redis-server \
--port $$NODE_PORT \
$$REPLICA_OF \
--appendonly no \
--save '' \
--min-replicas-to-write 1 \
--min-replicas-max-lag 1 \
--masteruser ca-skeleton-replication \
--masterauth fixture-replication \
--aclfile /etc/redis/acl/all-accounts.acl
healthcheck:
test: ["CMD-SHELL", "[ \"$$(redis-cli -p $$NODE_PORT --user ca-skeleton-application --pass fixture-application --no-auth-warning ping)\" = PONG ]"]
interval: 2s
timeout: 2s
retries: 15
services:
primary:
<<: *data-node
environment:
NODE_PORT: "7010"
REPLICA_OF: ""
replica:
<<: *data-node
environment:
NODE_PORT: "7011"
REPLICA_OF: "--replicaof 127.0.0.1 7010"
depends_on:
primary:
condition: service_healthy
sentinel-1: &sentinel
image: "redis:${REDIS_VERSION:-7.4}"
network_mode: host
# The config is written at start-up rather than mounted because Sentinel rewrites its own file
# when it promotes. A read-only mount would make the first failover fail on a write error, and
# a shared writable mount would have three sentinels rewriting one file.
entrypoint:
- /bin/sh
- -c
- |
cat > /tmp/sentinel.conf <<CONF
port $$SENTINEL_PORT
sentinel monitor skeleton 127.0.0.1 7010 2
sentinel auth-user skeleton ca-skeleton-sentinel
sentinel auth-pass skeleton fixture-sentinel
sentinel down-after-milliseconds skeleton 2000
sentinel failover-timeout skeleton 10000
sentinel parallel-syncs skeleton 1
CONF
exec redis-sentinel /tmp/sentinel.conf
environment:
SENTINEL_PORT: "27010"
healthcheck:
test: ["CMD-SHELL", "[ \"$$(redis-cli -p 27010 ping)\" = PONG ]"]
interval: 2s
timeout: 2s
retries: 15
depends_on:
primary:
condition: service_healthy
sentinel-2:
<<: *sentinel
environment:
SENTINEL_PORT: "27011"
healthcheck:
test: ["CMD-SHELL", "[ \"$$(redis-cli -p 27011 ping)\" = PONG ]"]
interval: 2s
timeout: 2s
retries: 15
sentinel-3:
<<: *sentinel
environment:
SENTINEL_PORT: "27012"
healthcheck:
test: ["CMD-SHELL", "[ \"$$(redis-cli -p 27012 ping)\" = PONG ]"]
interval: 2s
timeout: 2s
retries: 15