127 lines
5.0 KiB
YAML
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
|