Provisionando Redis Sentinel para uma aplicação voltada a transações com cartão.
Laerte Antonio Giraldelli
Posted on July 28, 2022
Motivação
Nesse artigo quero compartilhar como construí uma infraestrutura de Redis Sentinel altamente disponivel em uma cloud privada baseada em VMWare com cisco ACI na camada de rede, em uma arquitetura de microserviços voltada para autorização de transações com cartão.
Os dados de host, IP e outras informações apresentados no decorrer desse post são fictícios ou foram mascarados por motivos de segurança
Como é um fluxo de uma transação com cartão
Antes de aprofundar na solução do Redis Sentinel, um resumo rápido sobre o contexto de um fluxo de autorização de uma transação de cartão feita via TEF (as famosas maquininhas) e qual o impacto do Redis.
Basicamente uma compra é feita via cartão em uma maquininha no estabelecimento comercial, a partir daí a transação passa pelo adquirente (fornecedor das TEFs), pela bandeira do cartão, pela processadora da transação e por fim no emissor do cartão que vai autorizar ou não a compra e aí o retorno até a maquininha, onde o recebemos no visor a resposta.
Onde entra o Redis nessa história
O serviço do Redis Sentinel entra no ponto da resposta do emissor para a processadora, autorizando ou não a compra, onde essa resposta tem que ser em até 2 segundos e com média diária de 1 milhão de transações dia.
O Redis Sentinel entra no ecosistema do microserviço responsável por tratar a transação e responder de volta para a processadora.
O Redis mantém um tipo de cache em memória, onde salva algumas informações importantes, por exemplo, IDs do cartão da processadora, associado ao usuário do lado do emissor, também tem os dados da conta bancária na aplicação de core bancário (agência e conta), dados de pacote de serviço, etc. , reduzindo drasticamente a busca desses dados entre vários microserviços e banco de dados.
Os dados em memória são persistidos e sincronizados em disco entre as máquinas onde existe o serviço do Redis.
O desafio para criar a solução do Redis Sentinel
Recebemos a demanda para criar uma solução com Redis com disponibilidade e resiliência, subindo a infraestrutura no nossa recém-criada cloud privada baseada em VMWare vRealize 7.x na camada de virtualização de máquinas e na camada de rede uma SDN Cisco ACI Anywhere.
Abaixo segue o diagrama de como ficou a arquitetura final.
Entrada GSLB e Load Balance no F5
Como podemos ver no diagrama acima, em cada datacenter foi criado um Load Balancer no F5 (ver print da conf mais abaixo) para balancear as requisições entre as máquinas virtuais na porta 5679 (redis service).
O campo send string contém a string do Redis que retorna a informação se o server é um master ou slave, no F5 configuramos o receive string como 'role:master', assim o LB estará "Up" se encontrar o master.
A gestão e eleição do master entre as 4 VM's fica a cargo do serviço do sentinel.
Na frente desses 2 LB's, foi criado uma entrada GSLB (Global Server Load Balance)(ver print da conf mais abaixo), também uma feature no F5 para receber as requisições do microserviço vindas do orquestrador de container Mesos e balancear entre os 2 datacenters.
Do lado do cliente Redis na aplicação a configuração da conexão vai passar somente o hostname definido no GSLB e a porta do Redis, onde o GSLB vai encaminhar direto para o server onde estará o master do redis. Diferente de outras soluções com Redis Sentinel onde na configuração do cliente redis é passado o IP/hostname de todos os nodes sentinel que são validados durante a conexão inicial para encontrar qual deles é o master.
Provisionamento da Máquinas Virtuais
O provisionamento das máquinas virtuais nesse ambiente VMWare VRA7 foi feito com terraform
O código terraform utilizado pode ser consultado no meu github: redis-sentinel-vmware-iac
Para a execução do terraform, clonado repositório do github e executado terraform, no exemplo abaixo provisionado as máquinas no Datacenter A (ex: dca).
$ git clone git@github.com:laerteg/redis-sentinel-vmware-iac.git
$ cd terraform/prod/redis-dca
$ terraform init
$ terraform plan -var-file="../../dc/dca-prod.tfvars" -var-file="../../service/redis/variables.tfvars"
$ terraform apply -var-file="../../dc/dca-prod.tfvars" -var-file="../../service/redis/variables.tfvars"
Gestão de Configuração com Ansible
Após criadas as máquinas virtuais via terraform, a parte de configuração e instalação do Redis Sentinel foi feita utilizando Ansible.
Para a execução do ansible, clonado repositório do github e executado terraform, no exemplo abaixo provisionado as máquinas no Datacenter A (ex: dca).
Antes de executar o playbook do ansible, alguns ajustes foram necessários:
Ajustar o inventário do ansible (hosts) com os IPs das instâncias criadas;
Editar o arquivo main.yml e ajustar as variáveis de acordo com o projeto:
mymaster_ip: colocar o IP da instância que será o Master inicial.
quorum_num: colocar o numero do quorum que será utilizado para eleição do novo master em caso de problemas. (Ex: Redis com 4 instâncias quorum = 2).
newrelic_key: inserir o código do new_relic de acordo com o ambiente (Prod ou QA).
env: ambiente do Redis. (Ex: prod ou QA).
persist_data: Se o redis terá persistencia de dados ou não. Opções: 'yes' ou 'no'.
Inicialmente foi definido quorum_num = 3 porém quando o Datacenter B ficou sem conectividade, o sentinel não conseguiu fazer a re-eleição do master pois haviam somente 2 nodes do Datacenter A, com quorum de 2, o sentinel conseguiu eleger um master e um slave..
Abaixo exemplo de execução do playbook do ansible:
$ git clone git@github.com:laerteg/redis-sentinel-vmware-iac.git
$
$ cd ansible/install_redis
$ ansible-playbook -i hosts main.yml
Validações e Troubleshooting
Para verificações e testes com as portas TCP 5679 e 25679 entre as máquinas:
nmap
ncat, nc
Para verificações do sistema redis, utilizado redis-cli.
$ redis-cli -h 10.181.26.109 info replication
# Replication
role:master
connected_slaves:2
slave0:ip=10.181.26.110,port=6379,state=online,offset=165366625665,lag=1
slave1:ip=10.181.26.120,port=6379,state=online,offset=165366625804,lag=0
master_replid:119a4cc3b2c5cc8e374423ab9034acd540befbad
master_replid2:cb7cc24457975dde598db2b78b54afe76b9f630d
master_repl_offset:165366625818
Na saída acima podemos verificar que o node do Redis em questão é um master e tem 2 slaves conectados via sentinel.
$ redis-cli -h 10.181.26.110 info replication
# Replication
role:slave
master_host:10.181.26.109
master_port:6379
master_link_status:up
Na saída acima podemos verificar que o node do Redis em questão é um slave, qual é o master, porta e conexão com master Up.
$ redis-cli -h 10.181.26.109 -p 26379 sentinel masters
1) "name"
2)"mymaster"
3) "ip"
4) "10.181.26.109"
5) "port"
6) "6379"
...
...
17) "last-ok-ping-reply"
18) "73"
19) "last-ping-reply"
20) "73"
21) "down-after-milliseconds"
22) "10000"
23) "info-refresh"
24) "216"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "4313841777"
29) "config-epoch"
30) "3"
31) "num-slaves"
32) "2"
33) "num-other-sentinels"
34) "2"
35) "quorum"
36) "2"
37) "failover-timeout"
38) "30000"
39) "parallel-syncs"
40) "1"
Nesta saída acima podemos ver várias informações sobre o master sentinel, slaves conectados, etc.. No caso de problemas de conectividade entre os nodes apareceríam informações conforme abaixo:
10) "s_down,master,disconnected"
33) "num-slaves"
34) "0"
35) "num-other-sentinels"
36) "0"
Passo a passo - Instalação do Redis Sentinel
Ajustes no Kernel do S.O.
- Desabilitar Transparent Huge Pages (THP)
echo 'never' > /sys/kernel/mm/transparent_hugepage/enabled (após reboot volta opção 'always')
Para desabilitar o THP após reboot do servidor, criar arquivo /etc/init.d/after.local com o seguinte conteudo:
# Disable THP for REDIS
if test -f /sys/kernel/mm/transparent_hugepage/enabled; then
echo 'never' > /sys/kernel/mm/transparent_hugepage/enabled
fi
if test -f /sys/kernel/mm/transparent_hugepage/defrag; then
echo 'never' > /sys/kernel/mm/transparent_hugepage/defrag
fi
- Ulimit, Max Connections, overcommit memory, etc..
# sysctl -w vm.min_free_kbytes=1448
# sysctl -w vm.overcommit_memory=1
# sysctl -w net.core.somaxconn=65365
# sysctl -w fs.file-max=100000
# ulimit -H -n 32768
# ulimit -S -n 24576
- /etc/security/limits.conf
soft nofile 24576
hard nofile 32768
redis soft nofile 24576
redis hard nofile 32768
- Listar e Atualizar limits.conf
sysctl -a (show variables)
sysctl -p (reload conf)
- Verificar limits de um processo
ps -ef | grep redis | awk '{print $2}'
60224
sudo grep 'open files' /proc/60224/limits
Max open files 24576 32768 files
Instalação do pacote Redis no SUSE Linux
# zypper addrepo https://download.opensuse.org/repositories/server:database/SLE_15_SP1/server:database.repo
# zypper search-packages -d redis
Package Module or Repository SUSEConnect Activation Command
-------------------------------------------- ------------------------------------------------------- ---------------------------------------------------------
redis-4.0.11-bp151.2.1.x86_64 susemanager:suse-packagehub-15-sp1-standard-pool-x86_64
redis-5.0.8-129.3.x86_64 server_database
# zypper refresh
# zypper install redis
# zypper packages --installed-only
i+ | server_database | redis | 5.0.8-129.3 | x86_64
v | SUSE-PackageHub-15-SP1-Standard-Pool for x86_64 | redis | 4.0.11-bp151.2.1 | x86_64
#/usr/sbin> ls -la | grep redis
lrwxrwxrwx 1 root root 7 abr 1 01:18 rcredis -> service
lrwxrwxrwx 1 root root 12 abr 1 01:18 redis-check-aof -> redis-server
lrwxrwxrwx 1 root root 12 abr 1 01:18 redis-check-rdb -> redis-server
lrwxrwxrwx 1 root root 12 abr 1 01:18 redis-sentinel -> redis-server
-rwxr-xr-x 1 root root 1781592 abr 1 01:18 redis-server
#/usr/bin> ls -la | grep redis
-rwxr-xr-x 1 root root 646712 abr 1 01:18 redis-benchmark
lrwxrwxrwx 1 root root 20 abr 1 01:18 redis-check-aof -> ../sbin/redis-server
lrwxrwxrwx 1 root root 20 abr 1 01:18 redis-check-rdb -> ../sbin/redis-server
-rwxr-xr-x 1 root root 812944 abr 1 01:18 redis-cli
Criação de Usuário e Grupo redis
# groupadd -r redis
# useradd --system -g redis -d /home/redis
Criação Diretório de Configuração
# mkdir -p /etc/redis
# chown redis:redis /etc/redis
Criação Diretório de Dump (*.RDB e *.AOF)
# mkdir -p /var/lib/redis
# chown redis:redis /var/lib/redis
Criação Diretório de Logs
# touch /var/log/redis.log
# touch /var/log/sentinel.log
# chown redis:redis /var/log/redis.log
# chown redis:redis /var/log/sentinel.log
NOTA: o serviço do Redis/Sentinel é sensível as pastas onde escreve como /etc/redis, /var/lib, /var/log, etc.. em caso de não ter permissão, o serviço não ativa.
Master & Slave | |
---|---|
redis.conf | bind 127.0.0.1 |
protected-mode no | |
port 6379 | |
daemonize yes | |
supervised systemd | |
pidfile "/opt/redis/run/redis_6379.pid" | |
logfile "/opt/redis/log/redis.log" | |
save 900 1 (cada 900s se 1 chave mudar) | |
save 300 10 (cada 300s se 10 chaves mudarem) | |
save 60 10000 (cada 60s se 10000 chaves mudarem) | |
dbfilename "dump.rdb" | |
appendonly yes | |
appendfilename "NOME_PROJETO.aof" | |
------------- | ---------------------------------------------------- |
sentinel.conf | # Deixar o bind ser feito em todas interfaces |
#bind 127.0.0.1 | |
protected-mode no | |
# The port that sentinel instance will run on | |
port 26379 | |
supervised systemd | |
pidfile /opt/redis/run/redis-sentinel.pid | |
logfile "/opt/redis/log/sentinel.log" | |
# IP Redis Master inicial, porta e quorum | |
sentinel monitor mymaster <IP_MASTER> 6379 <QUORUM> | |
sentinel down-after-milliseconds mymaster 5000 |
supervised systemd: se comentado serviço systemctl não sobe
Persistencia dos dados: Se for ligar, as 3 linhas abaixo devem ser descomentadas
- save 900 1 (cada 15min se 1 chave mudar)
- save 300 10 (cada 5min se 10 chaves mudarem)
- save 60 10000 (cada 1min se 10000 chaves mudarem)
dbfilename "dump.rdb": dados persistidos são escritos no dump
appendonly yes: habilita escrita do arquivo AOF
appendfilename "NOME_PROJETO.aof": nome do arquivo AOF (default: "appendonly.aof").
QUORUM: Com minimo de 2 nodes e quorum igual a 2, o sentinel consegue eleger um master.
Configuração dos Serviços Systemctl
/etc/systemd/system/redis.service
[Unit]
Description=Redis In-Memory Data Store
After=network.target
[Service]
#Type=notify
Type=forking
LimitNOFILE=64000
User=redis
Group=redis
ExecStart=/usr/sbin/redis-server /opt/redis/conf/redis.conf
ExecStop=/usr/bin/redis-cli -h 127.0.0.1 shutdown
Restart=always
[Install]
WantedBy=multi-user.target
/etc/systemd/system/sentinel.service
[Unit]
Description=Sentinel for Redis
After=network.target
[Service]
Type=notify
LimitNOFILE=64000
User=redis
Group=redis
PIDFile=/opt/redis/run/sentinel.pid
ExecStart=/usr/sbin/redis-sentinel /opt/redis/conf/sentinel.conf
ExecStop=/bin/kill -s TERM $MAINPID
Restart=always
[Install]
WantedBy=multi-user.target
Como usuário ROOT habilitar e iniciar serviço redis em todos os nodes:
# systemctl enable redis.service
# systemctl start redis.service
Saída de log do serviço redis:
47705:C 07 Apr 2020 12:26:07.485 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
47705:C 07 Apr 2020 12:26:07.485 # Redis version=5.0.8, bits=64, commit=00000000, modified=0, pid=47705, just started
47705:C 07 Apr 2020 12:26:07.485 # Configuration loaded
47705:C 07 Apr 2020 12:26:07.485 * supervised by systemd, will signal readiness
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 5.0.8 00000000/0) 64 bit
_.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 47705
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
47705:S 07 Apr 2020 12:26:07.486 # Server initialized
47705:S 07 Apr 2020 12:26:07.486 * Ready to accept connections
47705:S 07 Apr 2020 12:26:07.486 * Connecting to MASTER 10.181.26.110:6379
47705:S 07 Apr 2020 12:26:07.486 * MASTER <-> REPLICA sync started
47705:S 07 Apr 2020 12:26:07.486 * Non blocking connect for SYNC fired the event.
47705:S 07 Apr 2020 12:26:07.487 * Master replied to PING, replication can continue...
47705:S 07 Apr 2020 12:26:07.487 * Partial resynchronization not possible (no cached master)
47705:S 07 Apr 2020 12:26:07.488 * Full resync from master: e1a164521f59ed78711cc7e3a186dda89562021d:0
47705:S 07 Apr 2020 12:26:07.575 * MASTER <-> REPLICA sync: receiving 175 bytes from master
Como usuário ROOT habilitar e iniciar serviço sentinel em todos os nodes:
#systemctl enable sentinel.service
#systemctl start sentinel.service
Saída de log do serviço sentinel:
112167:X 14 Apr 2020 11:44:24.554 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
112167:X 14 Apr 2020 11:44:24.554 # Redis version=5.0.8, bits=64, commit=00000000, modified=0, pid=112167, just started
112167:X 14 Apr 2020 11:44:24.554 # Configuration loaded
112167:X 14 Apr 2020 11:44:24.554 * supervised by systemd, will signal readiness
112167:X 14 Apr 2020 11:44:24.555 * Running mode=sentinel, port=26379.
112167:X 14 Apr 2020 11:44:24.555 # Sentinel ID is dac84d11847121c48bc18e6ddb37b7cdd800e276
112167:X 14 Apr 2020 11:44:24.555 # +monitor master mymaster 10.181.26.110 6379 quorum 3
112167:X 14 Apr 2020 11:44:24.557 * +slave slave 10.181.26.107:6379 10.181.26.107 6379 @ mymaster 10.181.26.110 6379
112167:X 14 Apr 2020 11:44:24.558 * +slave slave 10.181.26.108:6379 10.181.26.108 6379 @ mymaster 10.181.26.110 6379
112167:X 14 Apr 2020 11:44:24.559 * +slave slave 10.181.26.109:6379 10.181.26.109 6379 @ mymaster 10.181.26.110 6379
112167:X 14 Apr 2020 11:44:25.163 * +sentinel sentinel 5eff2261037d126bea40bbd5cd64523b1cfe1604 10.181.26.108 26379 @ mymaster 10.181.26.110 6379
112167:X 14 Apr 2020 11:44:26.427 * +sentinel sentinel 1ded330e6ac1da4f4430d72a5cb257546ebe0d9e 10.181.26.109 26379 @ mymaster 10.181.26.110 6379
112167:X 14 Apr 2020 11:44:35.955 * +sentinel sentinel aaeeb987bb282d63fa32788c660a748b377b11bb 10.181.26.109 26379 @ mymaster 10.181.26.110 6379
Para verificar possiveis problemas durante inicialização dos serviços utilizar JOURNALCTL:
$ journalctl -u redis
$ journalctl -xe
Ou verificar os arquivos de log do serviço.
Em caso de alteração do arquivo de serviço, carregar novamente e fazer o restart dos serviços:
# systemctl daemon-reload
# systemctl restart redis.service
Benchmark do REDIS
Na própria instalação do Redis existe uma ferramenta para fazer um benchmark contra a instalação do Redis e avaliar por exemplo se ele vai dar conta de certo número de requisições com chaves de tamanho X.
redis-benchmark -q -n <requests> -c <conexões> -P <pipeline x instruções paralelas>
q = quiet
n = num. requests (default = 100000)
c = conexões client paralelas
P = Pipeline x comandos paralelos (default 1 = no pipeline)
d = tamanho da chave
t = subconjunto de comandos (GET, SET)
Exemplo: 1000 requisições de comando set/get com chaves no valor de 50Mb.
$ redis-benchmark -h 10.181.26.110 -t set,get -d 50 -n 1000 -q
SET: 71428.57 requests per second
GET: 71428.57 requests per second
Latência das Operações
O redis-cli tem opção pra ver latencia das operações sendo executados pelo redis, no exemplo o primeiro temos uma média de 0,04 microsegundos de latencia de uma operação.
$ redis-cli -h 10.181.26.110 --latency
min: 0, max: 1, avg: 0.04 (1313 samples)^C
Esse segundo exemplo é a latencia intrinseca, ou seja latencia daquilo que está fora do scopo do redis, por exemplo das camadas de virtualização, SO, etc.
$ redis-cli -h 10.181.26.110 --intrinsic-latency 30
Max latency so far: 1 microseconds.
Max latency so far: 6 microseconds.
Max latency so far: 7 microseconds.
Max latency so far: 9 microseconds.
Max latency so far: 24 microseconds.
Max latency so far: 29 microseconds.
Max latency so far: 165 microseconds.
Max latency so far: 636 microseconds.
Max latency so far: 713 microseconds.
748875096 total runs (avg latency: 0.0401 microseconds / 40.06 nanoseconds per run).
Worst run took 17798x longer than the average latency.
A média da latencia-intrinseca / media da latencia = % de tempo total da requisição é gasto pelo sistema em processos que não são controlados pelo Redis.
Por exemplo: media latencia = 0,18 microseg / media latencia-intrinseca = 0,06 microseg.
Significa que 30% do tempo total de uma requisição, é gasto com processos fora do redis.
Monitoração da Infraestrutura Redis
Nesse projeto optamos por configurar nos nodes do Redis o envio das métricas de infraestrutura para o New Relic.
Instalação do módulo do NewRelic e especifico para o Redis:
Adicionar repositório SUSE:
$ sudo rpm --import https://download.newrelic.com/infrastructure_agent/gpg/newrelic-infra.gpg
$ sudo curl -o /etc/zypp/repos.d/newrelic-infra.repo https://download.newrelic.com/infrastructure_agent/linux/zypp/sles/12.4/x86_64/newrelic-infra.repo
Instalar pacote padrão do NewRelic e específico Redis:
$ sudo zypper -n install newrelic-infra
$ sudo zypper -n install nri-redis
NOTA: o serviço systemd do NR é ativado durante a instalação do pacote.
/etc/systemd/system/newrelic-infra.service
Configuração do New Relic:
Editar o arquivo /etc/newrelic-infra.yml e inserir a chave do NR correta. No nosso caso temos uma chave para QA e outra para Prod.
license_key: 1234567890abcdef
log_file: /var/log/newrelic-infra.log
log_to_stdout: false
Remover o arquivo Sample para Docker:
rm /etc/newrelic-infra/integrations.d/docker-config.yml-sample
Copiar o arquivo SAMPLE de configuraçao do Redis de exemplo para o arquivo a ser usado pelo NR:
# cd /etc/newrelic-infra/integrations.d/
# cp redis-config.yml.sample redis-config.yml
Editar o arquivo alterando os campos hostname e environment de acordo com cada node e ambiente:
integration_name: com.newrelic.redis
instances:
- name: redis-metrics
command: metrics
arguments:
hostname: 10.181.26.110
port: 6379
keys: '{"0":["<KEY_1>"],"1":["<KEY_2>"]}'
remote_monitoring: true
labels:
environment: production
- name: redis-inventory
command: inventory
arguments:
hostname: 10.181.26.110
port: 6379
remote_monitoring: true
labels:
environment: production
Após os ajustes reiniciar o serviço do New Relic:
$ sudo systemctl restart newrelic-infra
O código Ansible para a configuração do New Relic está no
repositório.
Espero que esse conteúdo possa dar algum insight para alguém que esteja pensando em uma solução com Redis nos mesmos moldes em que tive que desenhar lá trás, na ocasião não encontrei muita informação. Nos dias de hoje temos outras opções mas na ocasião foi a que fez sentido e está dando conta do recado até hoje.
Boa leitura.
Posted on July 28, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
July 28, 2022