Nossa experiência de migração do Cassandra entre clusters Kubernetes sem perda de dados



Nos últimos seis meses, usamos o operador Rook para trabalhar com Cassandra em Kubernetes . No entanto, quando precisávamos executar uma operação muito trivial, ao que parece: alterar os parâmetros na configuração do Cassandra, verificou-se que o operador não fornecia flexibilidade suficiente. Para fazer alterações, era necessário clonar o repositório, fazer alterações nas fontes e reconstruir o operador (a configuração é incorporada ao próprio operador, portanto, o conhecimento do Go ainda é útil). Tudo isso leva muito tempo. Já fizemos uma

revisão dos operadores existentes e, desta vez, paramos no CassKop da Orange , que suporta os recursos necessários, em particular configurações personalizadas e monitoramento imediato .

Tarefa


Na história real, que será discutida mais adiante, foi decidido combinar a mudança de operador com a necessidade urgente de transferir toda a infraestrutura do cliente para o novo cluster. Após a migração das principais cargas de trabalho de aplicativos importantes, apenas Cassandra permaneceu, a perda de dados para a qual, é claro, era inaceitável.

Requisitos para sua migração:

  • O tempo ocioso máximo é de 2 a 3 minutos para efetivamente realizar essa transferência ao mesmo tempo em que o próprio aplicativo é transferido para um novo cluster;
  • Transfira todos os dados sem perda e dor de cabeça (ou seja, sem manipulações adicionais).

Como realizar essa operação? Por analogia com o RabbitMQ e o MongoDB , decidimos lançar uma nova instalação do Cassandra em um novo cluster Kubernetes, depois mesclar os dois Cassandra em diferentes clusters e transferir os dados, encerrando todo o processo, simplesmente desativando a instalação original.

No entanto, era complicado pelo fato de as redes dentro do Kubernetes se cruzarem, por isso não era tão fácil configurar a conexão. Era necessário registrar rotas para cada pod em cada nó, o que consome muito tempo e não é confiável. O fato é que a comunicação por pods IP funciona apenas com mestres e o Cassandra está sendo executado em nós dedicados. Portanto, você deve primeiro configurar a rota para o mestre e já no mestre - para outro cluster. Além disso, reiniciar o pod implica uma alteração no IP, e esse é outro problema ... Por que? Leia sobre isso mais adiante neste artigo.

Na parte prática subsequente do artigo, serão utilizadas três notações para os clusters de Cassandra:

  • Cassandra-new - a nova instalação que lançaremos no novo cluster Kubernetes;
  • Cassandra-current - uma instalação antiga com a qual os aplicativos estão trabalhando atualmente;
  • Cassandra-temporary é uma instalação temporária que executamos ao lado de Cassandra-current e a usamos apenas para o próprio processo de migração.

Como ser?


Como o Cassandra-current usa armazenamento local, uma simples migração de seus dados para um novo cluster - isso pode ser, por exemplo, no caso de discos vSphere ... - é impossível. Para resolver esse problema, criaremos um cluster temporário, usando-o como um tipo de buffer para migração.

A sequência geral de ações é reduzida para as seguintes etapas:

  1. Crie Cassandra-new com um novo operador em um novo cluster.
  2. Escale para 0 Cassandra-novo cluster .
  3. , PVC, .
  4. Cassandra-temporary Cassandra-current , Cassandra-new.
  5. Cassandra-temporary 0 ( ) Cassandra-temporary , Cassandra-temporary Cassandra-current. Cassandra - ( Cassandra ).
  6. Transfira dados entre os datacenters temporários e atuais do Cassandra .
  7. Escale os clusters Cassandra-current e Cassandra-temporary para 0 e execute Cassandra-new no novo cluster, sem esquecer de lançar os discos. Paralelamente, lançamos aplicativos para um novo cluster.

Como resultado de tais manipulações, o tempo de inatividade será mínimo.

Em detalhe


Não deve haver problemas com as três primeiras etapas - tudo é feito de maneira rápida e fácil.

Nesse ponto, o cluster atual do Cassandra terá algo parecido com isto:

Datacenter: x1
==============
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address     Load       Tokens       Owns    Host ID                               Rack
UN  10.244.6.5  790.7 GiB  256          ?       13cd0c7a-4f91-40d0-ac0e-e7c4a9ad584c  rack1
UN  10.244.7.5  770.9 GiB  256          ?       8527813a-e8df-4260-b89d-ceb317ef56ef  rack1
UN  10.244.5.5  825.07 GiB  256          ?       400172bf-6f7c-4709-81c6-980cb7c6db5c  rack1

Para verificar se tudo funciona como esperado, crie um espaço de chave no Cassandra-current . Isso é feito antes do lançamento do Cassandra-temporary :

create keyspace example with replication ={'class' : 'NetworkTopologyStrategy', 'x1':2};

Em seguida, crie uma tabela e preencha-a com dados:

use example;
CREATE TABLE example(id int PRIMARY KEY, name text, phone varint);
INSERT INTO example(id, name, phone) VALUES(1,'Masha', 983123123);
INSERT INTO example(id, name, phone) VALUES(2,'Sergey', 912121231);
INSERT INTO example(id, name, phone) VALUES(3,'Andrey', 914151617);

Execute o Cassandra-temporary , lembrando que antes disso, no novo cluster, já lançamos o Cassandra-new (etapa 1) e agora está desativado (etapa 2).

Notas:

  1. Quando iniciamos o Cassandra-temporary , devemos especificar o mesmo nome (com o Cassandra-current ) do cluster. Isso pode ser feito através de uma variável CASSANDRA_CLUSTER_NAME.
  2. Para que o Cassandra-temporary veja o cluster atual, é necessário definir as sementes. Isso é feito através de uma variável CASSANDRA_SEEDSou de uma configuração.

Atenção! Antes de começar a mover dados, você deve garantir que os tipos de consistência de leitura e gravação estejam definidos como LOCAL_ONEou LOCAL_QUORUM.

Após o início temporário do Cassandra , o cluster deve ficar assim (observe a aparência de um segundo data center com 3 nós):

Datacenter: x1
==============
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address     Load       Tokens       Owns    Host ID                               Rack
UN  10.244.6.5  790.7 GiB  256          ?       13cd0c7a-4f91-40d0-ac0e-e7c4a9ad584c  rack1
UN  10.244.7.5  770.9 GiB  256          ?       8527813a-e8df-4260-b89d-ceb317ef56ef  rack1
UN  10.244.5.5  825.07 GiB  256          ?       400172bf-6f7c-4709-81c6-980cb7c6db5c  rack1

Datacenter: x2
===============
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address       Load       Tokens       Owns (effective)  Host ID                               Rack
UN  10.244.16.96  267.07 KiB  256          64.4%             3619841e-64a0-417d-a497-541ec602a996  rack1
UN  10.244.18.67  248.29 KiB  256          65.8%             07a2f571-400c-4728-b6f7-c95c26fe5b11  rack1
UN  10.244.16.95  265.85 KiB  256          69.8%             2f4738a2-68d6-4f9e-bf8f-2e1cfc07f791  rack1

Agora você pode realizar a transferência. Para fazer isso, primeiro transfira o espaço para chaves de teste - verifique se está tudo bem:

ALTER KEYSPACE example WITH replication = {'class': 'NetworkTopologyStrategy', x1: 2, x2: 2};


Depois disso, em cada pod temporário do Cassandra , execute o comando:

nodetool rebuild -ks example x1

Vamos a qualquer pod do Cassandra-temporary e verifique se os dados foram transferidos. Você também pode adicionar mais 1 entrada ao Cassandra-current para verificar se novos dados começaram a se replicar:

SELECT * FROM example;

 id | name   | phone
----+--------+-----------
  1 |  Masha | 983123123
  2 | Sergey | 912121231
  3 | Andrey | 914151617

(3 rows)

Depois disso, você pode ALTERexecutar todos os espaços de teclas no Cassandra-current e executar nodetool rebuild.

Falta de espaço e memória


Nesse estágio, é útil lembrar que, quando a reconstrução está em execução, são criados arquivos temporários com tamanho equivalente ao tamanho do espaço de chave! Encontramos um problema em que o maior espaço para chaves era de 350 GB e havia menos espaço livre em disco.

Não foi possível expandir o disco, porque o armazenamento local é usado. O seguinte comando veio para o resgate (executado em cada pod de Cassandra-current ):

nodetool clearsnapshot

Portanto, o local foi liberado: no nosso caso, 500 GB de espaço livre em disco foram obtidos em vez dos 200 GB disponíveis anteriormente.

No entanto, apesar do espaço suficiente, a operação de reconstrução causava constantemente o reinício dos pods temporários do Cassandra com um erro:

failed; error='Cannot allocate memory' (errno=12)

Decidimos criar o DaemonSet, que é implementado apenas nos nós com Cassandra-temporary e executa:

sysctl -w vm.max_map_count=262144

Finalmente, todos os dados foram migrados!

Troca de cluster


Restou apenas trocar o Cassandra, realizado em 5 etapas:

  1. Dimensione Cassandra-temporary e Cassandra-current (não esqueça que o operador ainda funciona aqui!) Para 0.
  2. Troque de disco (tudo se resume a definir PV para Cassandra-new ).
  3. Começamos o Cassandra-new , rastreando se os discos necessários estão conectados.
  4. Fazemos ALTERtodas as tabelas para remover o cluster antigo:

    ALTER KEYSPACE example WITH replication = {'class': 'NetworkTopologyStrategy', 'x2': 2};
  5. Exclua todos os nós do cluster antigo. Para fazer isso, basta executar este comando em um de seus pods:

    nodetool removenode 3619841e-64a0-417d-a497-541ec602a996

O tempo total de inatividade do Cassandra foi de cerca de 3 minutos - é o tempo em que os contêineres pararam e começaram, pois os discos foram preparados com antecedência.

Toque final com Prometeu


No entanto, isso não terminou aí. Existe um exportador embutido com Cassandra-new (consulte a documentação do novo operador ) - é claro que nós o usamos. Cerca de 1 hora após o lançamento, começaram a surgir alertas sobre a inacessibilidade de Prometheus. Após verificar a carga, vimos que o consumo de memória nos nós com o Prometheus aumentou.

Um estudo mais aprofundado da questão mostrou que o número de métricas coletadas aumentou 2,5 vezes (!). A culpa foi de Cassandra, com a qual foram coletadas pouco mais de 500 mil métricas.

Realizamos uma auditoria das métricas e desativamos aquelas que não consideramos necessárias - por meio do ConfigMap (a propósito, o exportador está configurado). O resultado são 120 mil métricas e uma carga significativamente reduzida no Prometheus (apesar de importantes métricas permanecerem).

Conclusão


Então, conseguimos transferir o Cassandra para outro cluster, praticamente sem afetar o funcionamento da instalação de produção do Cassandra e sem interferir no trabalho dos aplicativos clientes. Ao longo do caminho, chegamos à conclusão de que usar a mesma rede de pods não é uma boa ideia (agora estamos mais atentos ao planejamento inicial da instalação do cluster).

Por fim: por que não usamos a ferramenta nodetool snapshotmencionada no artigo anterior? O fato é que esse comando cria uma captura instantânea do espaço de chave no estado em que estava antes da execução do comando. Além disso:

  • leva muito mais tempo para tirar uma foto e transferi-la;
  • tudo o que está escrito neste momento em Cassandra será perdido;
  • simples, no nosso caso, levaria cerca de uma hora - em vez de 3 minutos, o que acabou sendo combinado com sucesso com a implantação do aplicativo em um novo cluster.

PS


Leia também no nosso blog:


All Articles