En Volterra, el trabajo del equipo SRE es operar una plataforma edge global basada en SaaS . Tenemos que resolver varios desafíos en la gestión de una gran cantidad de clústeres de aplicação en varios estados (es decir, en línea, fuera de línea, administrador inactivo, etc.) y lo hacemos aprovechando el ecosistema y las herramientas de Kubernetes con un modelo declarativo basado en extracción usando GitOps.
En este blog describiremos:
Uso de GitOps para administrar y monitorear eficazmente una gran flota de infraestructura (hosts físicos o en la nube) y clústeres K8
Profundizaremos en las lecciones aprendidas a escala (3000 sitios de borde), que también se trataron en mi reciente charla en Cloud Native Rejekts en San Diego.
El diagrama de arquitectura (Figura 1) anterior muestra la conectividad lógica entre nuestros RE y CE, donde cada CE establece conexiones redundantes (IPSec o SSL VPN) con el RE más cercano.
Cuando comenzamos a diseñar nuestra plataforma hace aproximadamente 2 años, nuestro equipo de producto nos pidió que resolviéramos los siguientes desafíos:
Considerando nuestros requisitos y desafíos de operar un sistema altamente distribuido, decidimos establecer varios principios que necesitábamos que nuestro equipo de SRE siguiera para reducir los problemas posteriores:
Como parte de la administración del ciclo de vida del sitio perimetral, tuvimos que resolver cómo aprovisionar el sistema operativo host, realizar configuraciones básicas (por ejemplo, administración de usuarios, autoridad de certificación, páginas enormes, etc.), activar K8, implementar cargas de trabajo y administrar los cambios de configuración en curso.
Una de las opciones que consideramos pero finalmente rechazamos fue utilizar KubeSpray+Ansible (para administrar el sistema operativo e implementar K8) y Helm/Spinnaker (para implementar cargas de trabajo). La razón por la que rechazamos esto fue que habría requerido que administráramos 2 o 3 herramientas de código abierto y luego realizar modificaciones significativas para cumplir con nuestros requisitos, que continuaron creciendo a medida que agregamos más funciones como escalamiento automático de clústeres de borde, soporte para módulos TPM seguros, actualizaciones diferenciales, etc.
Como nuestro objetivo era mantenerlo simple y minimizar la cantidad de componentes que se ejecutan directamente en el sistema operativo (fuera de Kubernetes), decidimos escribir un demonio Golang liviano llamado Volterra Platform Manager (VPM). Este es el único contenedor Docker systemd en el sistema operativo y actúa como una navaja suiza que realiza muchas funciones:
VPM es responsable de administrar el ciclo de vida del sistema operativo host, incluida la instalación, las actualizaciones, los parches, la configuración, etc. Hay muchos aspectos que deben configurarse (por ejemplo, asignación de páginas enormes, /etc/hosts, etc.)
Administración para proporcionar el ciclo de vida para el manifiesto de Kubernetes. En lugar de utilizar Helm, decidimos utilizar la biblioteca cliente-go K8s, que integramos en VPM y utilizamos varias características de esta biblioteca:
Optimista = crear recursos y no esperar el estado. Es muy similar al comando apply de Kubernetes, donde no se sabe si los pods reales se inician correctamente.
Pesimista = esperar el estado del recurso de Kubernetes. Por ejemplo, la implementación espera hasta que todos los pods estén listos. Esto es similar al nuevo comando kubectl wait .
Además de las configuraciones relacionadas con los manifiestos de K8, también necesitamos configurar varios servicios de Volterra a través de sus API. Un ejemplo son las configuraciones VPN IPsec/SSL: VPM recibe la configuración de nuestro plano de control global y las programa en nodos individuales.
Esta función nos permite restablecer una caja de forma remota a su estado original y realizar nuevamente todo el proceso de instalación y registro. Es una característica muy importante para recuperar un sitio que necesita acceso físico o a la consola.
Si bien la gestión del ciclo de vida de K8 puede parecer un gran tema de discusión para muchas personas, para nuestro equipo probablemente representa solo el 40-50 % del volumen de trabajo total.
El aprovisionamiento sin intervención del sitio perimetral en cualquier ubicación (nube, local o perimetral nómada) es una funcionalidad fundamental, ya que no podemos esperar tener acceso a sitios individuales ni queremos contratar a tantos expertos en Kubernetes para instalar y administrar sitios individuales. Simplemente no se puede escalar a miles.
El siguiente diagrama (Figura 2) muestra cómo VPM está involucrado en el proceso de registro de un nuevo sitio:
Como puede ver, todo el proceso está completamente automatizado y el usuario no necesita saber nada sobre la configuración detallada ni ejecutar ningún paso manual. Se necesitan aproximadamente 5 minutos para poner todo el dispositivo en estado en línea y listo para atender aplicaciones y solicitudes de los clientes.
La actualización es una de las cosas más complicadas que tuvimos que resolver. Definamos qué se está actualizando en los sitios de borde:
Hay dos métodos conocidos que podrían usarse para enviar actualizaciones a sitios perimetrales:
Nuestro objetivo con la actualización era maximizar la simplicidad y la confiabilidad, similar a las actualizaciones de teléfonos celulares estándar. Además, hay otras consideraciones que la estrategia de actualización debía satisfacer: el contexto de actualización podía ser únicamente con el operador del sitio, o el dispositivo podía estar fuera de línea o no disponible durante algún tiempo debido a problemas de conectividad, etc. Estos requisitos podrían satisfacerse más fácilmente con el método pull y por eso decidimos adoptarlo para satisfacer nuestras necesidades.
Además, elegimos GitOps porque nos permitió proporcionar un modelo operativo estándar para gestionar clústeres de Kubernetes, flujos de trabajo y cambios de auditoría a nuestro equipo de SRE.
Para resolver los problemas de escalabilidad de miles de sitios, ideamos la arquitectura para SRE que se muestra en la Figura 3:
Primero, quiero enfatizar que no usamos Git solo para almacenar estados o manifiestos. La razón es que nuestra plataforma no solo tiene que gestionar los manifiestos de K8, sino también las configuraciones de API en curso, versiones de K8, etc. En nuestro caso, los manifiestos de K8 representan aproximadamente el 60% de toda la configuración declarativa. Por este motivo tuvimos que idear nuestra propia abstracción DSL, que se almacena en git. Además, dado que Git no proporciona una API ni ninguna capacidad de fusión de parámetros, tuvimos que desarrollar daemons Golang adicionales para SRE: API de configuración, ejecutor y controlador VP.
Repasemos el flujo de trabajo de lanzamiento de una nueva versión de software en el borde del cliente utilizando nuestra plataforma SaaS:
Puedes ver una demostración del flujo de trabajo completo aquí:
En secciones anteriores, describimos cómo se utilizan nuestras herramientas para implementar y administrar el ciclo de vida de los sitios perimetrales. Para validar nuestro diseño, decidimos construir un entorno a gran escala con tres mil sitios de borde del cliente (como se muestra en la Figura 4)
Utilizamos Terraform para aprovisionar 3000 máquinas virtuales en AWS, Azure, Google y nuestra propia nube local para simular la escala. Todas esas máquinas virtuales eran CE independientes (sitios de borde del cliente) que establecían túneles redundantes hacia nuestros sitios de borde regionales (también conocidos como PoP).
La siguiente captura de pantalla es de nuestro panel SRE y muestra números de borde en ubicaciones representadas por el tamaño del círculo. Al momento de tomar la captura de pantalla teníamos alrededor de 2711 sitios de borde saludables y 356 no saludables.
Como parte del escalamiento, encontramos algunos problemas en la configuración y el aspecto operativo que nos obligaron a realizar modificaciones en nuestros daemons de software. Además, nos topamos con muchos problemas con un proveedor de nube que llevaron a la apertura de múltiples tickets de soporte; por ejemplo, latencia de respuesta de API, imposibilidad de obtener más de 500 máquinas virtuales en una sola región, etc.
La observabilidad en un sistema distribuido planteó un conjunto mucho mayor de desafíos a medida que escalamos el sistema.
Inicialmente, para las métricas comenzamos con la federación de Prometheus: Prometheus central en control global que federa Promethei en bordes regionales (RE), que extrae sus métricas de servicio y federa las métricas de sus CE conectados. Prometheus de nivel superior evaluó las alertas y sirvió como fuente de métricas para análisis posteriores. Llegamos rápidamente a los límites de este enfoque (alrededor de 1000 CE) y tratamos de minimizar el impacto del creciente número de CE. Comenzamos a generar series precalculadas para histogramas y otras métricas de alta cardinalidad. Esto nos ahorró uno o dos días y luego tuvimos que emplear listas blancas para las métricas. Al final, pudimos reducir el número de métricas de series de tiempo de alrededor de 60.000 a 2000 para cada sitio de CE.
Finalmente, después de seguir escalando más allá de los 3000 sitios CE y de funcionar durante muchos días en producción, quedó claro que esto no era escalable y tuvimos que repensar nuestra infraestructura de monitoreo. Decidimos eliminar el Prometheus de nivel superior (bajo control global) y dividir el Prometheus en cada RE en dos instancias separadas. Uno es responsable de recopilar métricas de servicios locales y el segundo de federar métricas de CE. Ambos generan alertas y envían métricas al almacenamiento central en Cortex. Cortex se utiliza como fuente de análisis y visualización y no es parte del flujo de alerta de monitoreo central. Probamos varias soluciones de métricas centralizadas, a saber, Thanos y M3db, y descubrimos que Cortex era la que mejor se adaptaba a nuestras necesidades.
La siguiente captura de pantalla (Figura 7) muestra el consumo de memoria al raspar prometheus-cef en el momento de 3000 puntos finales. Lo interesante son los 29,7 GB de RAM consumidos, que en realidad no es tanta dada la escala del sistema. Se puede optimizar aún más dividiendo el raspado en varios de ellos o moviendo la escritura remota a Cortex directamente al borde mismo.
La siguiente captura de pantalla (Figura 8) muestra la cantidad de memoria y recursos de CPU que necesitábamos para los ingestadores Cortex (máximo 19 GB de RAM) y distribuidores a esta escala. La mayor ventaja de Cortex es el escalamiento horizontal, que nos permite agregar más réplicas en comparación con Prometheus, donde el escalamiento debe realizarse verticalmente.
Para registrar la infraestructura en CE y RE, utilizamos servicios de Fluentbit por nodo que recopilan los eventos de registro del servicio y del sistema y los reenvían a Fluentd en el RE conectado. Fluentd reenvía los datos al ElasticSearch presente en el RE. Elastalert evalúa los datos de ElasticSearch y se establecen reglas para crear alertas de Alertmanager. Estamos utilizando nuestra integración personalizada de Elastalert con Alertmananger para producir alertas con las mismas etiquetas que produce Prometheus.
Los puntos clave de nuestro recorrido de monitoreo:
- Inicialmente teníamos alrededor de 50.000 series de tiempo por CE con un promedio de 15 etiquetas
- Lo optimizamos a 2000 por CE en promedio
Listas while simples para nombres de métricas y listas negras para nombres de etiquetas
- Prometheus centralizado eliminó todos los Prometheus de RE y CE
- En el año 1000 d. C. se volvió insostenible gestionar la cantidad de métricas.
- Actualmente tenemos Prometheus en cada RE (federando a Promethei de los CE conectados) con RW a Cortex
- Arquitectura de registro descentralizada
- Fluentbit como recopilador en cada nodo reenvía registros a Fluentd (agregador) en RE
- ElasticSearch se implementa en cada RE mediante la búsqueda de clúster remoto para consultar registros desde una única instancia de Kibana
Espero que este blog le brinde una idea de todo lo que debe tenerse en cuenta para administrar miles de sitios y clústeres de borde implementados en todo el mundo. Aunque hemos podido cumplir y validar la mayoría de nuestros requisitos de diseño iniciales, aún nos quedan muchas mejoras por delante…