Nuestra experiencia de migración Cassandra entre clústeres de Kubernetes sin pérdida de datos



Durante los últimos seis meses, hemos utilizado el operador Rook para trabajar con Cassandra en Kubernetes . Sin embargo, cuando necesitábamos realizar una operación muy trivial, parecería: cambiar los parámetros en la configuración de Cassandra, resultó que el operador no proporcionaba suficiente flexibilidad. Para realizar cambios, era necesario clonar el repositorio, realizar cambios en las fuentes y reconstruir el operador (la configuración está integrada en el operador, por lo que el conocimiento de Go sigue siendo útil). Todo esto lleva mucho tiempo. Ya hicimos una

revisión de los operadores existentes , y esta vez nos detuvimos en CassKop de Orange , que admite las características necesarias, en particular, configuraciones personalizadas y monitoreo listo para usar .

Tarea


En la historia real, que se discutirá más adelante, se decidió combinar el cambio de operador con la necesidad urgente de transferir toda la infraestructura del cliente al nuevo clúster. Después de la migración de las principales cargas de trabajo de aplicaciones importantes, solo quedó Cassandra, cuya pérdida de datos, por supuesto, era inaceptable.

Requisitos para su migración:

  • El tiempo de inactividad máximo es de 2-3 minutos para llevar a cabo esta transferencia al mismo tiempo que la aplicación se transfiere a un nuevo clúster;
  • Transfiera todos los datos sin pérdida y dolor de cabeza (es decir, sin ninguna manipulación adicional).

¿Cómo llevar a cabo tal operación? Por analogía con RabbitMQ y MongoDB , decidimos lanzar una nueva instalación de Cassandra en un nuevo clúster de Kubernetes, luego fusionar las dos Cassandra en diferentes grupos y transferir los datos, finalizando todo el proceso simplemente deshabilitando la instalación original.

Sin embargo, fue complicado por el hecho de que las redes dentro de Kubernetes se cruzan, por lo que no fue tan fácil configurar la conexión. Se requería registrar rutas para cada pod en cada nodo, lo que lleva mucho tiempo y no es confiable en absoluto. El hecho es que la comunicación a través de pods IP solo funciona con maestros, y Cassandra se ejecuta en nodos dedicados. Por lo tanto, primero debe configurar la ruta al maestro y ya en el maestro, a otro clúster. Además de esto, reiniciar el pod implica un cambio en IP, y este es otro problema ... ¿Por qué? Lea sobre esto más adelante en el artículo.

En la parte práctica posterior del artículo, se utilizarán tres notaciones para los grupos de Cassandra:

  • Cassandra-new : la nueva instalación que lanzaremos en el nuevo clúster de Kubernetes;
  • Cassandra-current : una instalación antigua con la que las aplicaciones están trabajando actualmente;
  • Cassandra-temporary es una instalación temporal que ejecutamos junto a Cassandra-current y la usamos solo para el proceso de migración en sí.

¿Cómo ser?


Dado que Cassandra-current utiliza almacenamiento local, una simple migración de sus datos a un nuevo clúster, esto podría ser, por ejemplo, en el caso de discos vSphere ... es imposible. Para resolver este problema, crearemos un clúster temporal, utilizándolo como una especie de búfer para la migración.

La secuencia general de acciones se reduce a los siguientes pasos:

  1. Eleve Cassandra-new con un nuevo operador en un nuevo clúster.
  2. Escala a 0 Cassandra-nuevo clúster .
  3. , PVC, .
  4. Cassandra-temporary Cassandra-current , Cassandra-new.
  5. Cassandra-temporary 0 ( ) Cassandra-temporary , Cassandra-temporary Cassandra-current. Cassandra - ( Cassandra ).
  6. Transfiera datos entre los centros de datos Cassandra-temporal y Cassandra-actual .
  7. Escale los clústeres Cassandra-current y Cassandra-temporary a 0 y ejecute Cassandra-new en el nuevo clúster, sin olvidar tirar los discos. Paralelamente, trasladamos las aplicaciones a un nuevo clúster.

Como resultado de tales manipulaciones, el tiempo de inactividad será mínimo.

En detalle


No debería haber ningún problema con los primeros 3 pasos: todo se hace rápida y fácilmente.

En este punto, el clúster Cassandra-current se verá más o menos así:

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 que todo funcione como se esperaba, cree un espacio de teclas en Cassandra-current . Esto se hace antes del lanzamiento de Cassandra-temporary :

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

A continuación, cree una tabla y llénela con datos:

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);

Ejecute Cassandra-temporary , recordando que antes de eso, en el nuevo clúster, ya lanzamos Cassandra-new (paso # 1) y ahora está apagado (paso # 2).

Notas:

  1. Cuando comenzamos Cassandra-temporary , debemos especificar el mismo nombre (con Cassandra-current ) del clúster. Esto se puede hacer a través de una variable CASSANDRA_CLUSTER_NAME.
  2. Para que Cassandra-temporal vea el grupo actual, debe establecer las semillas. Esto se hace a través de una variable CASSANDRA_SEEDSo de una configuración.

¡Atención! Antes de comenzar a mover datos, debe asegurarse de que los tipos de coherencia de lectura y escritura estén establecidos en LOCAL_ONEo LOCAL_QUORUM.

Después de que Cassandra-temporal comience , el clúster debería verse así (observe la apariencia de un segundo centro de datos con 3 nodos):

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

Ahora puedes realizar la transferencia. Para hacer esto, primero transfiera el espacio clave de prueba; asegúrese de que todo esté bien:

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


Después de eso, en cada pod Cassandra-temporal , ejecute el comando:

nodetool rebuild -ks example x1

Vayamos a cualquier pod de Cassandra-temporal y verifiquemos que los datos hayan sido transferidos. También puede agregar 1 entrada más a Cassandra-current para verificar que los nuevos datos hayan comenzado a replicarse:

SELECT * FROM example;

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

(3 rows)

Después de eso, puede hacer ALTERtodos los espacios de teclas en Cassandra-current y ejecutar nodetool rebuild.

Falta de espacio y memoria.


En esta etapa, es útil recordar que cuando se está ejecutando la reconstrucción, se crean archivos temporales que tienen un tamaño equivalente al tamaño del espacio de teclas. Nos encontramos con un problema de que el espacio de teclas más grande era de 350 GB y había menos espacio libre en disco.

No fue posible expandir el disco porque se usa localstorage. El siguiente comando vino al rescate (ejecutado en cada pod de Cassandra-current ):

nodetool clearsnapshot

Así que se liberó el lugar: en nuestro caso, se obtuvieron 500 GB de espacio libre en disco en lugar de los 200 GB disponibles anteriormente.

Sin embargo, a pesar del hecho de que había suficiente espacio, la operación de reconstrucción constantemente causaba el reinicio de las cápsulas temporales de Cassandra con un error:

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

Lo decidimos creando DaemonSet, que se implementa solo en nodos con Cassandra-temporary y realiza:

sysctl -w vm.max_map_count=262144

¡Finalmente, todos los datos han sido migrados!

Cambio de clúster


Solo quedaba cambiar la Cassandra, que se llevó a cabo en 5 etapas:

  1. Escala Cassandra-temporal y Cassandra-current (¡no olvides que el operador todavía trabaja aquí!) A 0.
  2. Cambie los discos (se reduce a configurar PV para Cassandra-new ).
  3. Comenzamos Cassandra-new , rastreando que los discos necesarios están conectados.
  4. Hacemos ALTERtodas las tablas para eliminar el clúster antiguo:

    ALTER KEYSPACE example WITH replication = {'class': 'NetworkTopologyStrategy', 'x2': 2};
  5. Eliminar todos los nodos del clúster antiguo. Para hacer esto, simplemente ejecute este comando en uno de sus pods:

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

El tiempo de inactividad total de Cassandra fue de aproximadamente 3 minutos: este es el momento en que los contenedores se detuvieron y comenzaron, ya que los discos se prepararon de antemano.

Toque final con Prometeo


Sin embargo, esto no terminó allí. Hay un exportador incorporado con Cassandra-new (consulte la documentación del nuevo operador ) ; por supuesto, lo usamos. Aproximadamente 1 hora después del lanzamiento, comenzaron a llegar alertas sobre la inaccesibilidad de Prometheus. Después de verificar la carga, vimos que el consumo de memoria en los nodos con Prometheus ha aumentado.

Un estudio adicional del problema mostró que el número de métricas recopiladas aumentó en 2,5 veces (!). La culpa fue Cassandra, con la que se recopilaron poco más de 500 mil métricas.

Realizamos una auditoría de las métricas y deshabilitamos las que no consideramos necesarias, a través de ConfigMap (en él, por cierto, el exportador está configurado). El resultado es 120 mil métricas y una carga significativamente reducida en Prometheus (a pesar del hecho de que quedan métricas importantes).

Conclusión


Así que logramos transferir Cassandra a otro clúster, prácticamente sin afectar el funcionamiento de la instalación de producción de Cassandra y sin interferir con el trabajo de las aplicaciones del cliente. En el camino, llegamos a la conclusión de que usar la misma red de pod no es una buena idea (ahora estamos más atentos a la planificación inicial para instalar el clúster).

Finalmente: ¿por qué no utilizamos la herramienta nodetool snapshotmencionada en el artículo anterior? El hecho es que este comando crea una instantánea de espacio de teclas en el estado en que estaba antes de que se ejecutara el comando. Además:

  • toma mucho más tiempo tomar una foto y transferirla;
  • todo lo que está escrito en este momento en Cassandra se perderá;
  • simple en nuestro caso sería aproximadamente una hora, en lugar de 3 minutos, que resultó combinarse con éxito con la implementación de la aplicación en un nuevo clúster.

PD


Lea también en nuestro blog:


All Articles