En este blog, exploraremos cómo MyScale logra cero tiempo de inactividad durante las actualizaciones de Kubernetes (k8s) para sus clústeres replicados. Alojado en la nube de AWS y utilizando Amazon EKS, MyScale ofrece un servicio de base de datos como servicio (DBaaS) resiliente y escalable. Exploraremos los aspectos globales y regionales de la arquitectura de MyScale, abordaremos los desafíos del escalado automático y las actualizaciones de Kubernetes, y delinearemos nuestras estrategias para migraciones de clústeres de usuarios sin problemas, todo ello asegurando un servicio ininterrumpido.
# Explorando la arquitectura de MyScale
MyScale (opens new window) es un servicio de base de datos como servicio (DBaaS) construido en la plataforma de nube de AWS. Todos sus servicios se implementan en el servicio de Kubernetes administrado de AWS, Amazon EKS (opens new window), lo que proporciona a MyScale la capacidad de aprovechar al máximo las potentes características de Kubernetes, como el descubrimiento de servicios, el equilibrio de carga, el escalado automático y el aislamiento de seguridad.
Como se muestra en el siguiente diagrama, hemos diseñado una arquitectura altamente elástica y escalable con un plano global y servicios web, gestión de usuarios, pagos de facturación, etc. También tenemos múltiples servicios regionales ubicados en diferentes ubicaciones geográficas, donde cada servicio regional proporciona las mismas funciones, como la creación, gestión, actualización y eliminación de clústeres de usuarios, así como el monitoreo del uso y las interfaces de aplicaciones.
Además, cada servicio regional consta de un único plano de control y múltiples planos de datos.
El plano de control de cada región es su cerebro, responsable de la gestión y programación de tareas o solicitudes. También sirve como enlace entre el plano de datos y el plano global, aceptando las solicitudes de gestión emitidas por el plano global y transmitiéndolas para que sean ejecutadas por el plano de datos correspondiente. Además, este plano de control es responsable de recopilar datos y monitorear el estado y las métricas de uso detalladas de cada plano de datos, asegurando que los clústeres de usuarios se ejecuten correctamente en el plano de datos.
Cada plano corresponde a un clúster K8s independiente y, en circunstancias normales, crear y mantener estos clústeres implica una gran cantidad de recursos en la nube, especialmente para crear y configurar clústeres de Kubernetes. Utilizamos Crossplane (opens new window) para administrar la infraestructura, las aplicaciones y los clústeres de usuarios de manera eficiente de manera unificada con los Patrones de Kubernetes.
Debido a las diferentes especificaciones de los clústeres de usuarios, hemos creado diferentes NodeGroups (opens new window) para especificaciones y cargas de trabajo de clústeres de usuarios individuales para maximizar la utilización de recursos en el plano de datos. Con la ayuda de Cluster Autoscaler (opens new window), hemos logrado el escalado automático de los clústeres de Kubernetes.
apiVersion: eks.aws.crossplane.io/v1alpha1
kind: NodeGroup
metadata:
name: <eks-nodegroup-name>
spec:
forProvider:
region: us-east-1
clusterNameRef:
name: <eks-cluster-name>
subnets:
- subnet-for-us-east-1a
- subnet-for-us-east-1b
- subnet-for-us-east-1c
labels:
myscale.com/workload: db
myscale.com/instance-type: <myscale-instance-type>
taints:
- key: myscale.com/workload
value: db
effect: NO_EXECUTE
instanceTypes:
- <instance-type>
- <instance-type>
scalingConfig:
maxSize: 100
minSize: 3
# Abordando los desafíos del escalado automático en Kubernetes
El uso de NodeGroups funcionó muy bien al principio, pero a medida que las especificaciones de los clústeres de usuarios se expandieron, nos encontramos con dos problemas:
- El NodeGroup no se distribuye de manera uniforme en las zonas disponibles como se esperaba, sino que concentra gradualmente los clústeres de usuarios en una de las zonas disponibles al crear, eliminar, iniciar y detener estos clústeres.
- El elemento de tamaño mínimo (
minSize
) de cada NodeGroup siempre debe ser distinto de cero. Si, en algunos casos, se establece en cero, como cuando un pod utiliza un PVC existente, la expansión automática no se activará y el pod permanecerá pendiente permanentemente.
Para encontrar una solución a estos problemas, encontramos referencias a este fenómeno al estudiar los registros del Cluster Autoscaler, así como los siguientes documentos:
Registros:
Found multiple availability zones for ASG "eks-nodegroup-xxxxxxx-90c4246a-215b-da2a-0ce8-c871017528ab"; using us-east-1a for failure-domain.beta.kubernetes.io/zone label
Documentos:
- https://aws.github.io/aws-eks-best-practices/cluster-autoscaling/#scaling-from-0 (opens new window)
- https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#auto-discovery-setup (opens new window)
Siguiendo las mejores prácticas en los documentos de referencia, modificamos el NodeGroup multi-zona original para representar múltiples NodeGroups de una sola zona de disponibilidad. Además, se implementó la escala desde cero mediante la configuración de etiquetas de ASG.
La configuración se describe en el siguiente script YAML:
apiVersion: eks.aws.crossplane.io/v1alpha1
kind: NodeGroup
metadata:
name: <eks-nodegroup-name-for-az1>
spec:
forProvider:
region: us-east-1
clusterNameRef:
name: <eks-cluster-name>
subnets:
- subnet-for-us-east-1a
labels:
myscale.com/workload: db
myscale.com/instance-type: <myscale-instance-type>
tags:
k8s.io/cluster-autoscaler/node-template/label/topology.kubernetes.io/region: us-east-1
k8s.io/cluster-autoscaler/node-template/label/topology.kubernetes.io/zone: us-east-1a
k8s.io/cluster-autoscaler/node-template/label/topology.ebs.csi.aws.com/zone: us-east-1a
k8s.io/cluster-autoscaler/node-template/taint/myscale.com/workload: db:NO_EXECUTE
k8s.io/cluster-autoscaler/node-template/label/myscale.com/workload: db
k8s.io/cluster-autoscaler/node-template/label/myscale.com/instance-type: <myscale-instance-type>
taints:
- key: myscale.com/workload
value: db
effect: NO_EXECUTE
instanceTypes:
- <instance-type>
- <instance-type>
scalingConfig:
maxSize: 100
minSize: 0
# Navegando las actualizaciones de Kubernetes
Pronto nos asignaron la tarea de actualizar nuestros clústeres de K8s debido al aviso de descontinuación de AWS para EKS 1.24. En consecuencia, el desafío era actualizar a la última versión antes de la fecha límite de soporte.
Según el calendario de lanzamiento (opens new window), AWS proporcionará soporte para estas nuevas versiones dentro de los catorce meses posteriores a la fecha de lanzamiento.
Probamos el proceso de actualización de AWS basado en la documentación que proporcionaron:
- https://docs.aws.amazon.com/eks/latest/userguide/update-cluster.html (opens new window)
- https://docs.aws.amazon.com/eks/latest/userguide/update-managed-node-group.html (opens new window)
- https://docs.aws.amazon.com/eks/latest/userguide/managed-node-update-behavior.html (opens new window)
Al probar, nos encontramos con los siguientes problemas:
- No es posible actualizar múltiples versiones simultáneamente; solo se puede actualizar una versión a la vez y se pueden actualizar un máximo de tres versiones en un año.
- El tiempo de actualización del NodeGroup es relativamente largo. Un solo NodeGroup tarda decenas de minutos con migración de pods o migración forzada.
Las actualizaciones frecuentes de versiones, junto con los largos tiempos de actualización y la migración forzada de pods, podrían causar la interrupción anormal de las conexiones de red existentes, lo que afectaría significativamente a los clústeres de usuarios del plano de datos y obligaría a los usuarios a prestar atención al estado del sistema (opens new window) para lidiar con posibles problemas.
Para encontrar soluciones a estos desafíos, estudiamos la documentación relevante de AWS EKS y encontramos la siguiente información útil:
- El plano de control de EKS y el NodeGroup pueden diferir en una versión. A partir de EKS 1.28, se permite una diferencia de dos versiones.
- Después de que se actualiza el plano de control de EKS, se puede crear un NodeGroup con la versión del plano de control para coexistir con el antiguo NodeGroup que no se ha actualizado.
Basándonos en estos puntos, realizamos los siguientes cambios:
- Agregamos una etiqueta
instance-version
a la configuración existente del NodeGroup:
labels:
myscale.com/instance-version: v1.24
- Luego, actualizamos el plano de control de EKS:
apiVersion: eks.aws.crossplane.io/v1beta1
kind: Cluster
metadata:
name: <eks-cluster-name>
spec:
forProvider:
version: "1.25"
Después de completar la actualización del plano de control de EKS, no actualizaremos el NodeGroup antiguo/existente. En su lugar, crearemos un nuevo NodeGroup basado en la versión actualizada del plano de control de EKS y agregaremos una etiqueta para marcar la versión.
labels:
myscale.com/instance-version: v1.25
Posteriormente, agregamos la siguiente configuración al controlador del clúster para admitir la programación de clústeres de usuarios durante el período de transición de la versión de actualización:
node_group:
default: v1.25
allowed:
- "v1.25"
- "v1.24"
Ahora, para los clústeres de usuarios recién creados o cuando los usuarios activamente desencadenan actualizaciones de versión, reinicios, etc., se programan en el NodeGroup marcado como v1.25 de forma predeterminada, mientras que otros clústeres permanecen sin cambios. Hemos resuelto el problema de migración de pods durante la actualización de EKS, haciéndolo transparente para los usuarios.
Tenga en cuenta que EKS 1.28 admite una desviación de versión de dos versiones; para 1.26 y superiores, podemos actualizar el plano de control de EKS en dos versiones consecutivas a partir de EKS 1.26, reduciendo así a la mitad el número de actualizaciones.
# Estrategias para las actualizaciones de clústeres de usuarios
Sin embargo, algunos clústeres de usuarios todavía se están ejecutando en el antiguo NodeGroup v1.24.
¿Cuándo se migrarán estos clústeres?
Antes de discutir este problema, primero clasifiquemos los clústeres de usuarios de la siguiente manera:
- Clústeres de usuarios con múltiples réplicas
- Clústeres de usuarios con una sola réplica
Las réplicas de un clúster con múltiples réplicas se encuentran en varios nodos en diferentes zonas de disponibilidad; las nuevas solicitudes de usuarios pueden programarse en otros pods mediante el equilibrador de carga para que los pods a migrar ya no manejen nuevas solicitudes de usuarios. Una vez que se procesen las solicitudes existentes, se puede migrar el pod. La migración del clúster de usuarios se puede completar repitiendo este proceso hasta que todos los pods en un clúster de usuarios con múltiples réplicas se hayan migrado.
Sin embargo, el tipo de clúster de usuarios con una sola réplica solo contiene un solo pod; la lógica del clúster de usuarios con múltiples réplicas no se puede aplicar.
Entonces, ¿cómo manejamos esta migración? ¿Podemos referirnos y reutilizar el método de clústeres de usuarios con múltiples réplicas?
Cuando el mecanismo de réplica de MyScale expande el clúster y aumenta las réplicas, las réplicas recién creadas sincronizan automáticamente los datos de las réplicas existentes, y varias réplicas proporcionan servicios simultáneamente después de que se complete la sincronización de datos.
Por lo tanto, lo único que necesitamos hacer es configurar el clúster de usuarios para que se migre.
apiVersion: db.myscale.com/v1alpha1
kind: Cluster
metadata:
name: <cluster-name>
spec:
replicas: 1
shards: 1
......
Expandir antes de la migración de una sola réplica a una doble réplica:
apiVersion: db.myscale.com/v1alpha1
kind: Cluster
metadata:
name: <cluster-name>
spec:
replicas: 2
shards: 1
......
Luego, reutilizamos la lógica de migración de los clústeres de usuarios con múltiples réplicas para realizar la migración. Después de que se complete la migración, reducimos la escala y eliminamos los pods adicionales.
Finalmente, todos los clústeres de usuarios se han migrado. Los pods en los nodos del antiguo NodeGroup se han migrado. El Cluster Autoscaler elimina automáticamente los nodos vacíos en el antiguo NodeGroup. Solo necesitamos verificar y eliminar el NodeGroup con un tamaño de nodo cero. Este proceso no tiene ningún impacto en los usuarios, y EKS también se ha actualizado por completo a la nueva versión.
# En conclusión
Nuestro recorrido a través de estos desafíos arquitectónicos y actualizaciones ha aumentado la robustez y eficiencia de MyScale y ha asegurado cero tiempo de inactividad durante las actualizaciones críticas de Kubernetes. Por último, este recorrido refleja nuestro compromiso de proporcionar a nuestros usuarios una experiencia de DBaaS sin problemas y de alto rendimiento.