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 umarevisã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:- Crie Cassandra-new com um novo operador em um novo cluster.
- Escale para 0 Cassandra-novo cluster .
- , PVC, .
- Cassandra-temporary Cassandra-current , Cassandra-new.
- Cassandra-temporary 0 ( ) Cassandra-temporary , Cassandra-temporary Cassandra-current. Cassandra - ( Cassandra ).
- Transfira dados entre os datacenters temporários e atuais do Cassandra .
- 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:- 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
. - Para que o Cassandra-temporary veja o cluster atual, é necessário definir as sementes. Isso é feito através de uma variável
CASSANDRA_SEEDS
ou 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_ONE
ou 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 ALTER
executar 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:- Dimensione Cassandra-temporary e Cassandra-current (não esqueça que o operador ainda funciona aqui!) Para 0.
- Troque de disco (tudo se resume a definir PV para Cassandra-new ).
- Começamos o Cassandra-new , rastreando se os discos necessários estão conectados.
- Fazemos
ALTER
todas as tabelas para remover o cluster antigo:
ALTER KEYSPACE example WITH replication = {'class': 'NetworkTopologyStrategy', 'x2': 2};
- 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 snapshot
mencionada 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: