Criando seu primeiro cluster de Cassandra
- Postado por Adriano Bonacin
- Categorias cassandra
- Data 23/06/2023
- Comentários 0 comentário
Introdução
Agora que passamos pela instalação e primeiros passos com o Cassandra, chega a hora de incrementar nosso cluster. Na verdade, o que queremos agora é transformar nosso node standalone em um cluster com múltiplos nodes. Portanto, vamos descobrir como criar seu primeiro cluster de Cassandra.
Este assunto ficará um pouco longo, então vou dividi-lo em duas partes. Esta que fala das configurações necessárias e o próximo em que adicionaremos nodes no nosso cluster.
Pré-requisitos
Vamos precisar do Cassandra instalado em todos os nodes, no meu caso são três. Você pode seguir o passo a passo deste post.
Além da instalação padrão, isso vai envolver alteração de alguns parâmetros e vou tentar fazer isso de forma gradual e discutindo cada um deles. A menos que eu mencione algum arquivo específico, todas as alterações serão no /etc/cassandra/conf/cassandra.yaml.
listen_address ou listen_interface
Vimos no artigo dos primeiros passos do Cassandra que ele ouve no IP 127.0.0.1, ou seja, aceita apenas conexões locais. E essa é a primeira coisa que precisamos alterar.
Os parâmetros que controlam esse comportamento são o listen_address e o listen_interface, mas eles nunca devem ser usados juntos, usamos um ou outro.
Por default temos o listen_address apontando para o localhost (127.0.0.1) e o listen_interface comentado:
listen_address: localhost
# listen_interface: eth0
Particularmente eu gosto mais do listen_interface, porque uma vez que você tenha todos seus nodes iguais, como o mesmo sistema operacional, com as interfaces com o mesmo nome, você pode usar o mesmo valor para todo mundo. Caso prefira usar o listen_address, para cada node você precisa informar o seu IP. Então faremos essa mudança:
# listen_address: localhost
listen_interface: eth0
Mas como saber qual o nome da interface ou do IP? Você pode usar o “ip a” ou ifconfig:
[rocky@node1 ~]$ ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 inet 127.0.0.1/8 scope host lo ... 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state ... inet 10.0.7.8/20 brd 10.0.15.255 scope global dynamic noprefixroute eth0
Com essas mudanças os nodes passam a se falar uns com os outros na interface eth0 (que aceita conexões remotas) ao invés da loopback.
rpc_address ou rpc_interface
As discussões aqui são semelhantes às que fizemos acima. Estes parâmetros definem a interface em que recebemos conexões de nossos clients. Também são excludentes, usamos um ou outro e pode ser o mesmo que o anterior. Os valores defaults são:
rpc_address: localhost
# rpc_interface: eth1
E vamos trocar para:
# rpc_address: localhost
rpc_interface: eth0
Desta forma a conexão node to node e client to node acontecem na interface eth0.
Portas
Com relação às portas, normalmente não precisamos alterá-las. Os parâmetros que as controlam são:
storage_port: 7000
ssl_storage_port: 7001
native_transport_port: 9042
Apenas olhe com carinho na imagem acima, nas portas envolvidas na comunicação. A porta 9042 deve estar aberta no firewall (security groups e afins) para receber conexões da sua aplicação e dos outros nodes, enquanto a porta 7000 precisa estar liberada para comunicação somente entre os nodes.
Seeds
Agora vem uma parte muito importante. Quem já trabalhou com Oracle Dataguard sabe o que é configurar as entradas do tnsnames.ora para fazer os nodes se comunicarem, quem já trabalhou com replicação no MySQL já fez o change master informando o source DB e quem já brincou com ReplicaSet no MongoDB já fez um add secondary.
Por sorte, na minha opinião, o Cassandra é o mais simples de todos. O ponto é, como dizemos para os nodes quem faz parte do nosso cluster?
Nas primeiras versões do Cassandra a gente (ou pelo menos quem trabalhava com ele na época) criava um arquivo com a lista de todos os nodes, o chamado PropertyFileSnitch. Então todos os nodes sabiam quem fazia parte do cluster baseado em um arquivo chamado cassandra-topology.properties.
Cassandra, um banco de dados criado para escalar horizontalmente de forma rápida, precisava alterar um arquivo em todos os nodes sempre que um node fosse adicionado ou removido. Estratégia ruim, não?
Isso evoluiu e hoje usamos uma estratégia ligeiramente diferente. Atualmente o node tem um arquivo cassandra-rackdc.properties dizendo qual seu Datacenter (DC) e qual seu Rack. Não precisamos mais dessa lista de nodes. Agora essa informação é propagada através de um protocolo usado pelo Cassandra chamado Gossip.
E agora o papel do SEED vai fazer sentido. Quando queremos adicionar um node no nosso cluster, precisamos informar (via parâmetro chamado “seeds”) qual(is) node(s) devemos conectar para “entrar” no cluster. Perceba que não há um procedimento para adicionar o node, ele simplesmente entra no cluster ao startar o cassandra, uma vez que suas configurações estejam corretas (como o cluster_name) e que consiga conectar com sucesso em determinados nodes, chamados SEEDs. Caso nosso node seja o primeiro do cluster a subir, ele precisa ser um SEED. Os próximos nodes devem conseguir se comunicar com ele.
Então, basicamente os SEEDs são nodes especiais que “controlam” a entrada e saída de nodes do cluster. Normalmente não temos overhead por conta disso e geralmente usamos dois ou três nodes por datacenter.
No meu teste estou usando 3 nodes, com ips:
node1: 10.0.7.8
node2: 10.0.11.239
node3: 10.0.3.116
Vou usar apenas os dois primeiros, então meu parâmetro seed ficará:
seed_provider:
- class_name: org.apache.cassandra.locator.SimpleSeedProvider
parameters:
- seeds: "10.0.7.8,10.0.11.239"
Um detalhe muito importante é que esse arquivo de parâmetro é um arquivo yaml, então os espaços fazem muita diferença, é preciso ter cuidado com eles.
Snitch
Aqui chega a parte que o Cassandra entende a topologia atual e faz o melhor esforço para distribuir as requisições de forma eficiente. Temos algumas formas nativas: SimpleSnitch (default), GossipingPropertyFileSnitch (recomendada para produção), PropertyFileSnitch (antiga, que comentamos acima) e outras relacionadas com provedores de cloud, como Ec2Snitch e Ec2MultiRegionSnitch.
Enfim, não vamos discutir detalhes de cada uma, focaremos na GossipingPropertyFileSnitch, que devemos usar em todos nossos clusters, você pode usar GossipingPropertyFileSnitch na AWS, mas não Ec2Snitch on premises ou na Azure. Se houver uma mínima possibilidade de você migrar seu workload para outro provedor ou mesmo para on premises, use o GossipingPropertyFileSnitch.
Como ele funciona? Já comentamos um pouco acima, mas basicamente ele confia em um arquivo chamado cassandra-rackdc.properties. O que precisamos ter neste arquivo? Somente o datacenter e o rack em que esse node se encontra. Entenda aqui o rack como a “geladeira” onde fica seu servidor em um datacenter on premises e o dc um nome para identificar o DC, como dc-producao / dc-standby.
No nosso primeiro cluster vamos usar:
node1:
[rocky@node1 ~]$ cat /etc/cassandra/conf/cassandra-rackdc.properties
dc=dc1
rack=rack1
node2:
[rocky@node2 ~]$ cat /etc/cassandra/conf/cassandra-rackdc.properties
dc=dc1
rack=rack2
node3:
[rocky@node3 ~]$ cat /etc/cassandra/conf/cassandra-rackdc.properties
dc=dc2
rack=rack1
E no nosso cassandra.yaml precisamos alterar o endpoint_snitch.
endpoint_snitch: GossipingPropertyFileSnitch
Cluster Name
Já finalizando os parâmetros que precisamos alterar, aqui escolhemos o nome do nosso cluster. Normalmente relacionamos o nome com o produto ou serviço, como fazemos para outros DBs. Importante é que esse é um parâmetro que não pode ser alterado após a criação do cluster.
cluster_name: 'Yadax'
Cluster
Isso é o suficiente para nosso novo cluster. É necessário que todos os nodes tenham essas mesmas configurações. Esse truque vai trazer os valores atuais do nosso arquivo de configuração. Verifique em todos os nodes.
[rocky@node1 ~]$ cat /etc/cassandra/conf/cassandra.yaml | egrep -v '^$|^#' | egrep '^cluster_name|^listen_interface|^rpc_interface|^storage_port|^ssl_storage_port|^native_transport_port|^rpc_address|^lister_address|seeds|^endpoint_snitch'
cluster_name: 'Yadax'
# seeds is actually a comma-delimited list of addresses.
- seeds: "10.0.7.8,10.0.11.239"
storage_port: 7000
ssl_storage_port: 7001
listen_interface: eth0
native_transport_port: 9042
rpc_interface: eth0
endpoint_snitch: GossipingPropertyFileSnitch
[rocky@node2 ~]$ cat /etc/cassandra/conf/cassandra.yaml | egrep -v '^$|^#' | egrep '^cluster_name|^listen_interface|^rpc_interface|^storage_port|^ssl_storage_port|^native_transport_port|^rpc_address|^lister_address|seeds|^endpoint_snitch'
cluster_name: 'Yadax'
# seeds is actually a comma-delimited list of addresses.
- seeds: "10.0.7.8,10.0.11.239"
storage_port: 7000
ssl_storage_port: 7001
listen_interface: eth0
native_transport_port: 9042
rpc_interface: eth0
endpoint_snitch: GossipingPropertyFileSnitch
[rocky@node3 ~]$ cat /etc/cassandra/conf/cassandra.yaml | egrep -v '^$|^#' | egrep '^cluster_name|^listen_interface|^rpc_interface|^storage_port|^ssl_storage_port|^native_transport_port|^rpc_address|^lister_address|seeds|^endpoint_snitch'
cluster_name: 'Yadax'
# seeds is actually a comma-delimited list of addresses.
- seeds: "10.0.7.8,10.0.11.239"
storage_port: 7000
ssl_storage_port: 7001
listen_interface: eth0
native_transport_port: 9042
rpc_interface: eth0
endpoint_snitch: GossipingPropertyFileSnitch
Caso tenha algum dado de um cluster antigo, agora é hora de parar o cassandra e limpar o /var/lib/cassandra/*.
[rocky@node1 ~]$ ll /var/lib/cassandra/*
/var/lib/cassandra/commitlog:
total 0
/var/lib/cassandra/data:
total 0
/var/lib/cassandra/hints:
total 0
/var/lib/cassandra/saved_caches:
total 0
[rocky@node1 ~]$ sudo service cassandra start
Starting cassandra (via systemctl): [ OK ]
[rocky@node1 ~]$
Conectando no cassandra
Agora acontece o primeiro grande susto. Quando tentamos conectar no Cassandra usando o cqlsh, tomamos um erro. Mas antes estava funcionando. Foi exatamente por isso que escrevi esse post.
O que mudou agora em relação ao primeiro cluster que criamos aqui é que agora estamos ouvindo conexões de nossos clientes na interface eth0, e não na loopback. E por default, o cqlsh tenta conexões na interface 127.0.0.1. Para corrigir isso, basta incluir nosso IP como parâmetro para o cqlsh.
[rocky@node1 ~]$ ss -nltp State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 512 10.0.7.8:7000 0.0.0.0:* LISTEN 0 50 127.0.0.1:7199 0.0.0.0:* LISTEN 0 50 127.0.0.1:38255 0.0.0.0:* LISTEN 0 128 0.0.0.0:111 0.0.0.0:* LISTEN 0 2048 10.0.7.8:9042 0.0.0.0:* LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 128 [::]:111 [::]:* LISTEN 0 128 [::]:22 [::]:* [rocky@node1 ~]$ [rocky@node1 ~]$ cqlsh Connection error: ('Unable to connect to any servers', {'127.0.0.1:9042': ConnectionRefusedError(111, "Tried connecting to [('127.0.0.1', 9042)]. Last error: Connection refused")}) [rocky@node1 ~]$ [rocky@node1 ~]$ cqlsh 10.0.7.8 Connected to Yadax at 10.0.7.8:9042 [cqlsh 6.1.0 | Cassandra 4.1.2 | CQL spec 3.4.6 | Native protocol v5] Use HELP for help. cqlsh>
E o que mostra o nodetool status? Vemos o nome do nosso DC, o rack que usamos e o IP privado do nosso node.
[rocky@node1 ~]$ nodetool status
Datacenter: dc1
===============
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
-- Address Load Tokens Owns (effective) Host ID Rack
UN 10.0.7.8 104.31 KiB 16 100.0% cab5eaa2-7816-4750-88be-ab94ad9c89b7 rack1
Próximos passos
Este artigo ficou longo, vou parar por aqui. Já temos nosso cluster de um node só rodando e no próximo artigo vou falar de como adicionamos nodes neste cluster. Mas é importante que para o próximo node você tenha tudo exatamente igual: java, python, cassandra e arquivos de configuração. Nos vemos lá.