Redis in HA utilizzando Sentinel
Redis (REmote DIctionary Server) è un database in-memory non relazionale, open-source, ad alte prestazioni, che può essere utilizzato come archivio di strutture dati di tipo chiave-valore. Può essere ovviamente usato come singola istanza, quindi installato su un singolo server, oppure configurato in Alta Affidabilità. Esistono due modi per avere un’architettura Redis di tipo HA, attivare un Cluster Redis avendo a disposizione almeno 6 nodi (3 Master + 3 Slave), oppure utilizzare il tool Sentinel avendo a disposizione 3 nodi su cui attivare Sentinel oltre i nodi Master e Slave di Redis stesso.
Il meccanismo di replica funziona in modo che i dati scritti sul master vengono replicati in modo asincrono sui server slave. In caso di problemi di connettività tra il master e lo slave la sincronizzazione si interrompe, ma non appena il canale di comunicazione torna disponibile lo slave si riconnette al master e la replica riprende. Occorre sottolineare che sul nodo master sono sempre possibili operazioni di lettura/scrittura, mentre su un nodo slave sono possibili solo operazioni di lettura, almeno fino a che lo slave non venga eletto come master. In questo modo, i client possono leggere i dati anche se il server master non è disponibile, a condizione che almeno uno dei server slave sia disponibile e contenga i dati richiesti.
Sentinel ha il compito di monitorare i server Redis del cluster (master e slave) e di coordinare il failover automatico del master a uno dei suoi slave disponibili in caso di un guasto del master. Quando un master non è più in grado di rispondere, Sentinel sceglie uno dei suoi slave disponibili e lo promuove a master, in modo che il cluster continui a funzionare senza interruzioni.
In questo articolo vedremo come realizzare una semplice architettura HA di Redis con 3 nodi, o meglio, 2 nodi che ospitano realmente l’engine Redis e Sentinel, più un nodo su cui è attivo il solo Sentinel e che quindi non necessita di particolari risorse o capacità di calcolo. Lo schema è quello illustrato di seguito.
Installazione di Redis
Nel mio caso ho usato come server macchine con CentOS 7 come sistema operativo, ma a livello di passi logici da eseguire poco cambia se si usano altro sistemi operativi.
Dato che Redis non è disponibile come rpm nei repository di CentoOS abilitiamo il repository Remi e procediamo con l’installazione su tutti e 3 i nodi. Sentinel viene installato come tool aggiuntivo di Redis, pertanto andiamo a fare l’installazione base e poi su uno dei nodi faremo partire il solo Sentinel, mentre sugli altri due avvieremo sia Sentinel che Redis.
yum install epel-release yum-utils
yum install http://rpms.remirepo.net/enterprise/remi-release-7.rpm
yum-config-manager --enable remi
yum install redis
Configurazione in replica
Procediamo con l’attivazione di 2 server Redis configurati in replica, quindi avremo un nodo master ed uno slave in replica.
Il file di configurazione sul quale apportare le modifiche è /etc/redis/redis.conf.
Per il nodo master (IP 172.31.6.218):
bind 127.0.0.1 172.31.6.218
protected-mode no
supervised systemd
masterauth P@ssw0rd
masteruser mymasteruser
user default off
user mymasteruser +@all on >P@ssw0rd
user superuser +@all ~* on >P@ssw0rd
user sentineluser on >P@ssw0rd allchannels +multi +slaveof +ping +exec +subscribe +config|rewrite +role +publish +info +client|setname +client|kill +script|kill
Per il nodo slave (IP 172.31.94.134):
bind 127.0.0.1 172.31.94.134
protected-mode no
supervised systemd
replicaof 172.31.6.218 6379
masterauth P@ssw0rd
masteruser mymasteruser
user default off
user mymasteruser +@all on >P@ssw0rd
user superuser +@all ~* on >P@ssw0rd
user sentineluser on >P@ssw0rd allchannels +multi +slaveof +ping +exec +subscribe +config|rewrite +role +publish +info +client|setname +client|kill +script|kill
Sulla documentazione ufficiale di redis potete trovare il dettaglio della configurazione, e tutti i parametri sono ben descritti anche all’interno del file di configurazione stesso. Qui vediamo brevemente il significato dei parametri usati.
- bind: l’indirizzo su cui redis si mette in ascolto, è necessario aggiungere l’IP della macchina oltre localhost per permettere la connessione da parte dei nodi slave e dei nodi sentinel.
- protected-mode: settato a no per far accettare a redis connessioni remote.
- supervised: la modalità di gestione dell’applicazione; nel nostro caso systemd.
- replicaof: permette di dire al nodo slave qual è il server redis da cui effettuare la replica.
- masterauth: la password del masteruser utilizzato per le operazioni di replica.
- masteruser: username del masteruser utilizzato per le operazioni di replica.
- user: permette di configurare utenti diversi per effettuare operazioni diverse su redis, secondo la logica di gestione ACL; nel mio caso ho configurato l’utente mymasteruser per gestire le operazioni di replica, l’utente superuser che può eseguire tutti i comandi su redis server, l’utente sentineluser per gestire le operazioni necessarie a Sentinel.
A questo punto avviamo redis su entrambi i server.
systemctl start redis
systemctl enable redis
Una volta avviato, utilizzando il tool redis-cli è possibile verificare lo stato dei server. Dato che abbiamo configurato le ACL per gli utenti, è necessario autenticarsi per poter eseguire comandi, quindi dopo aver avviato redis-cli dobbiamo eseguire il comando AUTH
Sul nodo master abbiamo questo:
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=172.31.94.134,port=6379,state=wait_bgsave,offset=0,lag=0
master_failover_state:no-failover
master_replid:7483bc9c74537c06dad78ec10bae247ac26bea52
master_replid2:a9dbab0000bbf9b7306e617a3bb07b7070721a03
master_repl_offset:2098991
second_repl_offset:2086135
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1166345
repl_backlog_histlen:932647
Mentre sul nodo slave abbiamo:
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:172.31.6.218
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_read_repl_offset:2122994
slave_repl_offset:2122994
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:7483bc9c74537c06dad78ec10bae247ac26bea52
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:2122994
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2098992
repl_backlog_histlen:24003
In particolare role indica il ruolo del server, slave0:ip=172.31.94.134,port=6379,state=wait_bgsave,offset=0,lag=0 sul master ci da indicazione dello slave connesso, master_host:172.31.6.218 sullo slave ci da indicazione del master al quale lo slave è connesso.
Configurazione di Sentinel
Bene, a questo punto abbiamo 2 nodi Redis configurati in replica, di cui uno è il master e l’altro è lo slave. In caso di problemi sul master, potremmo eleggere manualmente lo slave a master e continuare ad effettuare tutte le operazioni di lettura/scrittura sui nostri dati.
Il ruolo di Sentinel è quello di monitorare costantemente lo stato dei nodi che compongono l’architettura e migrare in automatico il ruolo dei server in caso di problemi.
Procediamo quindi a configurare Sentinel sui nostri 3 nodi. Il file di configurazione di Sentinel è /etc/redis/sentinel.conf.
Nodo redis 1 (IP 172.31.6.218)
bind 172.31.6.218
port 26379
sentinel announce-ip 172.31.6.218
sentinel monitor mymaster 172.31.6.218 6379 2
sentinel auth-pass mymaster P@ssw0rd
sentinel auth-user mymaster sentineluser
sentinel down-after-milliseconds mymaster 3000
sentinel failover-timeout mymaster 120000
Nodo redis 2 (IP 172.31.94.134)
bind 172.31.94.134
port 26379
sentinel announce-ip 172.31.94.134
sentinel monitor mymaster 172.31.6.218 6379 2
sentinel auth-pass mymaster P@ssw0rd
sentinel auth-user mymaster sentineluser
sentinel down-after-milliseconds mymaster 3000
sentinel failover-timeout mymaster 120000
Nodo sentinel (IP 172.31.13.69)
bind 172.31.13.69
port 26379
sentinel announce-ip 172.31.13.69
sentinel monitor mymaster 172.31.6.218 6379 2
sentinel auth-pass mymaster P@ssw0rd
sentinel auth-user mymaster sentineluser
sentinel down-after-milliseconds mymaster 3000
sentinel failover-timeout mymaster 120000
Avviamo Sentinel su tutti i server:
systemctl start redis-sentinel
systemctl enable redis-sentinel
Ora utilizziamo di nuovo redis-cli, ma questa volta per connetterci a Sentinel e non a Redis server.
[root@ip-172-31-13-69 redis]# redis-cli -h 172.31.13.69 -p 26379
172.31.13.69:26379> sentinel masters
1) 1) "name"
2) "mymaster"
3) "ip"
4) "172.31.6.218"
5) "port"
6) "6379"
7) "runid"
8) "b9b82fc75dd0bab18bb26ac8d38c6867a04626ca"
9) "flags"
10) "master"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "321"
19) "last-ping-reply"
20) "321"
21) "down-after-milliseconds"
22) "3000"
23) "info-refresh"
24) "7559"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "3295851"
29) "config-epoch"
30) "5"
31) "num-slaves"
32) "1"
33) "num-other-sentinels"
34) "2"
35) "quorum"
36) "2"
37) "failover-timeout"
38) "120000"
39) "parallel-syncs"
40) "1"
Tra i vari parametri ottenuti in output, sono significativi l’IP del master che in questo caso è 172.31.6.218, num-slaves che nel nostro caso è 1 (abbiamo 1 solo nodo slave) e num-other-sentinels che ci indica il numero degli ulteriori nodi Sentinel. Se quest’ultimo valore fosse 0 starebbe a significare che il sistema non comunica correttamente con gli altri nodi Sentinel.
Failover test
Per provare se il meccanismo di failover sta funzionando spegniamo Redis sul primo nodo, quello che al momento è master systemctl stop redis
.
Passati i 3 secondi che abbiamo impostato sul parametro down-after-milliseconds, nel log /var/log/redis/sentinel.log avremo questo:
16268:X 22 Mar 2023 08:25:02.864 # +sdown master mymaster 172.31.6.218 6379
16268:X 22 Mar 2023 08:25:02.908 * Sentinel new configuration saved on disk
16268:X 22 Mar 2023 08:25:02.908 # +new-epoch 6
16268:X 22 Mar 2023 08:25:02.912 * Sentinel new configuration saved on disk
16268:X 22 Mar 2023 08:25:02.912 # +vote-for-leader adecd88892552108121902d37e2d279ab4a1dc1c 6
16268:X 22 Mar 2023 08:25:02.936 # +odown master mymaster 172.31.6.218 6379 #quorum 3/2
16268:X 22 Mar 2023 08:25:02.936 # Next failover delay: I will not start a failover before Thu Mar 22 08:29:03 2023
16268:X 22 Mar 2023 08:25:04.061 # +config-update-from sentinel adecd88892552108121902d37e2d279ab4a1dc1c 172.31.94.134 26379 @ mymaster 172.31.6.218 6379
16268:X 22 Mar 2023 08:25:04.061 # +switch-master mymaster 172.31.6.218 6379 172.31.94.134 6379
16268:X 22 Mar 2023 08:25:04.061 * +slave slave 172.31.6.218:6379 172.31.6.218 6379 @ mymaster 172.31.94.134 6379
16268:X 22 Mar 2023 08:25:04.064 * Sentinel new configuration saved on disk
16268:X 22 Mar 2023 08:25:07.107 # +sdown slave 172.31.6.218:6379 172.31.6.218 6379 @ mymaster 172.31.94.134 6379
Se controlliamo nuovamente su Sentinel lo stato del master vediamo che ora il nodo master è il 172.31.94.134, quello che prima era slave.
[root@ip-172-31-13-69 redis]# redis-cli -h 172.31.13.69 -p 26379
172.31.13.69:26379> sentinel masters
1) 1) "name"
2) "mymaster"
3) "ip"
4) "172.31.94.134"
5) "port"
6) "6379"
7) "runid"
8) "6a6c7352bd37ad247e40c6b498ceb688a8e2ae7e"
9) "flags"
10) "master"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "390"
19) "last-ping-reply"
20) "390"
21) "down-after-milliseconds"
22) "3000"
23) "info-refresh"
24) "7036"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "359407"
29) "config-epoch"
30) "6"
31) "num-slaves"
32) "1"
33) "num-other-sentinels"
34) "2"
35) "quorum"
36) "2"
37) "failover-timeout"
38) "120000"
39) "parallel-syncs"
40) "1"
Da notare che la gestione Master-Slave è ora completamente a carico di Sentinel, e anche riavviando Redis sul nodo dove lo abbiamo spento, il master non migrerà indietro su di esso, ma resterà sul secondo nodo fino a quando non subentreranno problemi (redis spento o non raggiungibile) o spostato manualmente.