Nextcloud e Redis Cluster – Persistência de autenticação em clientes aplicacionais e clientes humanos com OTP.

Olá a todos,

Hoje o post é mais técnico – já tinha saudades – e vamos abordar a configuração que teremos de fazer para configurar um redis em cluster (non-sentinel) para dar serviço a duas instancias de nextcloud.

Qual a necessidade?  Bem, como certamente já devem saber, o nextcloud beneficia grandemente de sistemas de cache de objectos em memória como o memcached e/ou o redis.
Devido ao desenho multi-tier da minha arquitetura, sempre usei duas instancias em docker de memcached. Contudo, ambas as instancias não falavam uma com a outra – memcached por defeito não é clustered / replicated e existem problemas de segurança com a ultima versão clustered que existe e que aparentemente foi abandonada (recordam-se deste post?).

Embora o nextcloud consiga gerir isto em termos aplicacionais, em caso de falha ou restart de ambas as instancias de memcached (nem precisavam de ser ao mesmo tempo, bastava ambas ficarem limpas), o nextcloud iria perder todas as keys de autenticação obrigado a gerar novas passwords aplicacionais únicas quando os utilizadores com OTP se voltassem a ligar a esta instancia.
Pode não parecer nada de absurdo, até se pensarem que todos nós temos 3 ou 4 dispositivos digitais moveis e fixos.
Imaginem ter que trocar a password das vossas contas de caldav, carddav, etc em todas sempre que tivessem que re-inicar as plataformas de memcached – por exemplo para patching.

Fiz experiencias, e notei que as keys em redis, como são replicadas em cluster, não sofriam deste problema. Basta não ter que parar tudo ao mesmo tempo, pois os serviços após reiniciarem, irão buscar novamente aos irmãos de cluster as keys perdidas em reboot.
A somar a isto, um cluster de redis é algo que dá sempre jeito e o mesmo cluster pode ser usado para várias aplicações que dele necessitem, desde que tenham o cuidado de separar as DB’s de keys em si.

Em termos de mão na massa, a coisa é relativamente simples:

3 servers/lxc container em rockylinux 8.X 64bits x86_64 com 4GB de RAM.  Atenção: Rockylinux e não Centos. IBM .|.

Nos hosts dos lxc containers colocar caso não estejam já carregados:

# redis cluster stuff
#
echo never > /sys/kernel/mm/transparent_hugepage/enabled 
sysctl -w net.core.somaxconn=65535
sysctl -w vm.overcommit_memory=1

Em seguida, dentro dos containers, instalar normalmente o Redis, e configurar desta forma em todos os nós:

sudo mkdir -p /etc/redis/cluster
sudo mkdir /etc/redis/cluster/7000
sudo mkdir /var/lib/redis/7000
sudo mkdir /etc/redis/cluster/7001
sudo mkdir /var/lib/redis/7001

Em seguida, criar dois ficheiros de configuração com o vosso editor de texto favorito:

/etc/redis/cluster/7000/redis_7000.conf

port 7000
dir /var/lib/redis/7000/
appendonly no
protected-mode no
cluster-enabled yes
cluster-node-timeout 5000
cluster-config-file /etc/redis/cluster/7000/nodes_7000.conf
pidfile /var/run/redis/redis_7000.pid
logfile /var/log/redis/redis_7000.log
loglevel notice
requirepass [PASSWORDKEY]
masterauth [PASSWORDKEY]

/etc/redis/cluster/7001/redis_7001.conf

port 7001
dir /var/lib/redis/7001/
appendonly no
protected-mode no
cluster-enabled yes
cluster-node-timeout 5000
cluster-config-file /etc/redis/cluster/7001/nodes_7001.conf
pidfile /var/run/redis/redis_7001.pid
logfile /var/log/redis/redis_7001.log
loglevel notice
requirepass [PASSWORDKEY]
masterauth [PASSWORDKEY

Em seguida, criar os systemd files para gerir as instancias:

/etc/systemd/system/redis_7000.service

[Unit]
Description=Redis key-value database on 7000
After=network.target
[Service]
ExecStart=/usr/bin/redis-server /etc/redis/cluster/7000/redis_7000.conf --supervised systemd
ExecStop=/bin/redis-cli -h 127.0.0.1 -p 7000 shutdown
Type=notify
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target

/etc/systemd/system/redis_7000.service

 [Unit]
Description=Redis key-value database on 7001
After=network.target
[Service]
ExecStart=/usr/bin/redis-server /etc/redis/cluster/7001/redis_7001.conf --supervised systemd
ExecStop=/bin/redis-cli -h 127.0.0.1 -p 7001 shutdown
Type=notify
User=redis
Group=redis
RuntimeDirectory=/etc/redis/cluster/7001
RuntimeDirectoryMode=0755
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target

 

Em seguida, alterar permissões de diretorias, e fazer enable dos serviços de redis, dois por máquina:

sudo systemctl enable /etc/systemd/system/redis_7000.service
sudo systemctl enable /etc/systemd/system/redis_7001.service

sudo chown redis:redis -R /var/lib/redis
sudo chmod 770 -R /var/lib/redis
sudo chown redis:redis -R /etc/redis

Em seguida, replicar estas configurações e passos pelos três containers e fazer reboot aos mesmos.

Após o reboot e assumindo que todas as instancias de redis vieram acima sem problemas – podem as verificar pelos logs das mesmas conforme descritos nas configuracoes acima – chegou a altura de criar o nosso cluster através de um comando muito simples:

redis-cli --cluster create 172.16.0.44:7000 172.16.0.45:7000 172.16.0.46:7000 172.16.0.44:7001 172.16.0.45:7001 172.16.0.46:7001 --cluster-replicas 1 -a [PASSWORDKEY]

No meu caso em especifico, os três lxc-containers são o 172.16.0.44–>172.16.0.46 e as portas as 7000 e 7001, sendo a 7000 a master, e a 7001 a replica.
Respondam as questões, aguardem, e ele deverá vos devolver o prompt de sistema.

Para validarem se a vossa configuração está correta, será questão de se ligarem ao vosso redis cluster. Para o meu exemplo temos:

redis-cli -c -h 172.16.0.44 -p 7000 -a [PASSWORDKEY]

127.0.0.1:7000> CLUSTER INFO
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:9912
cluster_stats_messages_pong_sent:9899
cluster_stats_messages_sent:19811
cluster_stats_messages_ping_received:9894
cluster_stats_messages_pong_received:9912
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:19811
127.0.0.1:7000> CLUSTER NODES
d9b6be8d6071cbfa1272a7ac0c54c448aa7d0e80 172.16.0.44:7000@17000 myself,master - 0 1694696438000 1 connected 0-5460
3159e9abe5bba803583dd5718189349350520a9d 172.16.0.45:7000@17000 master - 0 1694696440000 2 connected 5461-10922
9b95046fbe27c3264e64c9eb56f7ccfb2310a18a 172.16.0.46:7000@17000 master - 0 1694696439344 3 connected 10923-16383
4838802c8a3878f8183f06c1f41abdbed501a1be 172.16.0.45:7001@17001 slave 9b95046fbe27c3264e64c9eb56f7ccfb2310a18a 0 1694696440550 5 connected
c366cdc3661922e5fb1f1bd3de40c739a694993b 172.16.0.44:7001@17001 slave 3159e9abe5bba803583dd5718189349350520a9d 0 1694696440350 4 connected
ceb787c0de685b7714250953bcc2b3c51e6052f0 172.16.0.46:7001@17001 slave d9b6be8d6071cbfa1272a7ac0c54c448aa7d0e80 0 1694696440550 6 connected
127.0.0.1:7000>

Agora que já temos o nosso cluster em funcionamento será apenas uma questão de adicionarem a configuração ao vosso nextcloud, ficheiro config.php, depois de adicionarem o vosso modulo php8-redis. No meu caso tive de o instalar com o pecl – pecl install redis.

A configuração, no meu caso ficou algo assim:

'memcache.local' => '\\OC\\Memcache\\Redis',
'memcache.locking' => '\\OC\\Memcache\\Redis',
'memcache.distributed' => '\\OC\\Memcache\\Redis',
'redis.cluster' => [
'seeds' => [ 
'172.16.0.44:7000',
'172.16.0.45:7000',
'172.16.0.46:7000',
],
'timeout' => 2.0,
'read_timeout' => 2.0,
'failover_mode' => \RedisCluster::FAILOVER_ERROR,
'password' => '[PASSWORDKEY]',
'dbindex' => XXX, // colocar um indice diferente para cada instancia que esteja a partilhar o redis.
],

E pronto, assumindo que nada correu mal, passaram a ter um nextcloud com suporte de OTP, com passwords aplicacionais perssistentes através dos vossos equipamentos, mesmo quando tiverem de fazer um reboot aos vossos servidores (desde que seja feito um de cada vez).

Se tiverem alguma duvida ou reparo, já sabem onde me encontrar.
Um abraço e até para a semana.

Nuno

 

PS: a configuração do cluster de redis, foi derivada de uma que a outsystems tem muito completa e muito bem feita que pode ser consultada aqui.