Nacimiento de la plataforma



El mundo ha cambiado. Lo siento en el agua, lo veo en el suelo, lo siento en el aire. Todo lo que una vez existió se ha ido, y ya no hay quienes lo recuerden.
De la película "El Señor de los Anillos: La Comunidad del Anillo"

Hay 100500 artículos e informes en Internet sobre el tema "cómo vimos un monolito", y no deseo escribir otro. Traté de ir un poco más allá y contar cómo los cambios tecnológicos condujeron a la aparición de un producto completamente nuevo (spoiler: escribimos el cuadro y escribimos la plataforma). El artículo es en gran parte una revisión, sin detalles técnicos. Los detalles vendrán más tarde.

Será sobre el sitio del panel de control y el servidor Vepp. Este es un producto del sistema ISP donde lidero el desarrollo. Lea sobre las capacidades del nuevo panel en otro artículo , aquí, solo sobre tecnología. Pero primero, como siempre, un poco de historia.

Parte 1. Es necesario cambiar algo.


Nuestra empresa ha estado escribiendo software para la automatización de servicios de alojamiento durante más de 15 años. Durante este tiempo, varias generaciones de nuestros productos han cambiado. Cuando cambiamos del segundo al cuarto, cambiamos Perl a C ++ y comenzamos la venta gratuita de nuestro software ... Cuando cambiamos del cuarto al quinto, en pos de la velocidad, cambiamos de una aplicación de un solo subproceso a un subproceso múltiple (de un monolito que contiene todos nuestros productos a marco).

Pero, no solo estábamos cambiando, nuestros clientes y competidores estaban cambiando. Si hace 10-15 años el propietario del sitio estaba técnicamente bien versado (otros estaban débilmente interesados ​​en Internet), ahora puede ser una persona que no está conectada con TI de ninguna manera. Hay muchas soluciones competitivas. En tales condiciones, simplemente trabajar un producto no es suficiente, es necesario que sea fácil y agradable de usar.

Rediseño heroico


Este cambio se ha convertido en todos los sentidos en el más notable. Todo este tiempo, la interfaz de nuestros productos se ha mantenido prácticamente sin cambios y unificada. Ya escribimos sobre esto por separado, una vista desde UX. En la quinta generación de productos, la API definió la apariencia de formularios y listas. Lo que, por un lado, hizo posible implementar muchas cosas sin involucrar a los desarrolladores frontend, por otro lado, dio lugar a llamadas complejas muy complejas, que a veces afectaron la mayor parte del sistema, y ​​limitaron en gran medida la capacidad de cambiar la interfaz. Después de todo, cualquier cambio es inevitablemente un cambio en la API. Y eso es todo, ¡hola a las integraciones!

Por ejemplo: crear un usuario en ISPmanager también puede conducir a la creación de un usuario FTP, dominio de correo, buzón, registro DNS, sitio. Y todo esto se hace atómicamente y, por lo tanto, bloquea los cambios en los componentes enumerados hasta que se completa la operación.

En la nueva API, cambiamos a operaciones atómicas pequeñas y simples, y los desarrolladores Frontend se quedaron con acciones complejas y complejas. Esto le permite implementar una interfaz compleja y cambiarla sin afectar la API principal y, por lo tanto, sin interrumpir la integración.

Para los desarrolladores frontend, hemos implementado un mecanismo de ejecución de consultas por lotes con la capacidad de realizar acciones inversas en caso de error. Como muestra la práctica, en la mayoría de los casos las consultas complejas son solicitudes para crear algo, y la creación es bastante fácil de cancelar al realizar la eliminación.

Reduce el tiempo de respuesta y las notificaciones instantáneas


Rechazamos solicitudes de modificación prolongadas. Las versiones anteriores de nuestros productos podrían colgarse durante mucho tiempo, tratando de cumplir con una solicitud del usuario. Decidimos que para cada acción es más difícil cambiar trivialmente los datos en la base de datos, crearemos una tarea en el sistema y responderemos al cliente lo más rápido posible, lo que le permitirá continuar trabajando y no mirar el proceso de carga sin fin.

Pero, ¿y si necesita el resultado, cómo se llama aquí y ahora? Creo que muchas personas están familiarizadas con la situación cuando vuelves a cargar la página una y otra vez con la esperanza de que la operación esté a punto de finalizar.

En la nueva generación de productos, utilizamos websocket para la entrega instantánea de eventos.

La primera implementación usó longpoll, pero los desarrolladores frontend se sintieron incómodos con este enfoque.

Comunicación interna HTTP


HTTP como transporte ganado. Ahora, en cualquier idioma, se implementa un servidor HTTP en segundos (si googleas, luego en minutos). Incluso a nivel local, es más fácil formar una solicitud HTTP que bloquear su protocolo.

En la generación anterior, las extensiones (complementos) eran aplicaciones que, si era necesario, se lanzaban como CGI. Pero para escribir una extensión de larga duración, tuve que esforzarme mucho: escribir un complemento en C ++ y reconstruirlo con cada actualización del producto.

Por lo tanto, en la sexta generación, cambiamos a la interacción interna a través de HTTP, y las extensiones, de hecho, se convirtieron en pequeños servidores WEB.

Nueva API (no del todo REST)


En generaciones anteriores de productos, pasamos todos los parámetros a través de GET o POST. Si no hay problemas especiales con GET: su tamaño es pequeño, en el caso de POST no había forma de verificar el acceso o redirigir la solicitud hasta que se haya leído completamente.

Imagine lo triste que es: aceptar unos cientos de megabytes o gigabytes, y luego descubrir que fueron vertidos por un usuario no autorizado o que ahora deben transferirse a ese servidor.

Ahora el nombre de la función se pasa en el URI, y la autorización se encuentra exclusivamente en los encabezados. Y esto le permite realizar parte de las comprobaciones antes de leer el cuerpo de la solicitud.

Además, las funciones API se han vuelto más simples. Sí, ahora no garantizamos la atomicidad de crear un usuario, correo, sitio web y similares. Pero damos la oportunidad de combinar estas operaciones según sea necesario.

Hemos implementado la capacidad de ejecución de consultas por lotes. De hecho, este es un servicio separado que acepta una lista de solicitudes y las ejecuta secuencialmente. También puede revertir las operaciones ya completadas en caso de error.

¡Viva SSH!


Otra decisión que tomamos en base a nuestra experiencia previa es trabajar con el servidor solo a través de SSH (incluso si es un servidor local). Inicialmente, trabajamos con el servidor local en VMmanager e ISPmanager, y solo entonces pudimos agregar otros remotos adicionales. Esto llevó a la necesidad de soportar dos implementaciones.

Y cuando nos negamos a trabajar con el servidor local, las últimas razones para usar las bibliotecas nativas del sistema operativo del usuario desaparecieron. Con ellos nos han atormentado desde la fundación de la empresa. No, las bibliotecas nativas tienen sus ventajas, pero hay más desventajas.

Una ventaja absoluta es el menor consumo de disco y memoria, y para trabajar dentro de VDS esto puede ser bastante significativo. De los inconvenientes: usar una biblioteca cuya versión no controlas puede generar resultados inesperados, lo que aumenta enormemente la carga tanto en el desarrollo como en las pruebas. Otro inconveniente es la incapacidad de usar las últimas versiones de bibliotecas y C ++ moderno (por ejemplo, en CentOS 6, incluso C ++ 11 no es totalmente compatible).

Viejos hábitos


Cambiando los enfoques y cambiando a nuevas tecnologías, continuamos actuando de la manera antigua. Esto condujo a dificultades.

El tema del bombo con microservicios no nos pasó por alto. También decidimos dividir la aplicación en componentes de servicio separados. Esto permitió estrechar el control sobre la interacción y realizar pruebas de partes individuales de la aplicación. Y para controlar la interacción aún más estrictamente, los colocamos en contenedores, que, sin embargo, siempre se pueden juntar en una pila.

En una aplicación monolítica, puede acceder fácilmente a casi cualquier información. Pero incluso si divide el producto en varias aplicaciones y las deja cerca, ellas, como seres vivos, pueden formar enlaces. Por ejemplo, en forma de archivos compartidos o solicitudes directas entre sí.

Cambiar a microservicios no fue fácil. El viejo patrón de "escribir una biblioteca y conectarla donde sea necesario" nos ha perseguido durante bastante tiempo. Por ejemplo, tenemos un servicio responsable de realizar operaciones "largas". Inicialmente, se implementó como una biblioteca conectada a la aplicación que la necesitaba.

Otro hábito se puede describir de la siguiente manera: por qué escribir otro servicio, si puede enseñar este existente. Lo primero que cortamos de nuestro monolito es el mecanismo de autorización. Pero entonces la tentación pareció empujar todos los componentes comunes a este servicio, como en COREmanager (el marco básico para productos de quinta generación).

Parte 2. Combinando lo incompatible


Al principio, las solicitudes de lectura y escritura se realizaban mediante un solo proceso. Por lo general, las solicitudes de escritura son solicitudes de bloqueo, pero son muy rápidas: escribí en la base de datos, creé una tarea y respondí. Con una solicitud de lectura, la historia es diferente. Crear una tarea en él es difícil. Puede generar una respuesta bastante voluminosa, y ¿qué hacer con esta respuesta si el cliente no regresa después? ¿Cuánto cuesta almacenarlo? Pero al mismo tiempo, el procesamiento de las solicitudes de lectura está perfectamente paralelo. Estas diferencias llevaron a problemas al reiniciar dichos procesos. ¡Sus ciclos de vida son simplemente incompatibles!

Dividimos la aplicación en dos partes: lectura y escritura. Es cierto que pronto se hizo evidente que esto no es muy conveniente desde el punto de vista del desarrollo. Leyendo la lista que haces en un lugar, editando en otro. Y aquí lo principal: no olvides arreglar el segundo, si cambias el primero. Sí, y cambiar entre archivos es al menos molesto. Por lo tanto, llegamos a una aplicación que se ejecuta en dos modos: lectura y escritura.

La generación anterior de nuestros productos hizo un amplio uso de hilos. Pero, como ha demostrado la práctica, no nos salvaron mucho. Debido a los muchos bloqueos, la carga en la CPU rara vez supera el 100%. La aparición de una gran cantidad de servicios separados bastante rápidos ha permitido abandonar el subprocesamiento múltiple en favor del trabajo asincrónico y de subproceso único.

Durante el proceso de desarrollo, intentamos usar secuencias junto con la asincronía (boost :: Asio lo permite). Pero es muy probable que este enfoque traiga a su proyecto todas las deficiencias de ambos enfoques que le brinde ventajas visibles: tendrá que combinar la necesidad de control al acceder a objetos compartidos y la dificultad de escribir código asincrónico.

Parte 3. Como escribimos el recuadro, y escribimos la plataforma


Todos los servicios se organizan en contenedores y funcionan con el servidor del cliente de forma remota. ¿Y por qué poner la aplicación en el servidor del cliente? Esa es la pregunta que le hice a la gerencia cuando llegó el momento de empaquetar el producto resultante para su instalación en el servidor.

¿Qué es una plataforma? Primero, implementamos SaaS, un servicio que se ejecuta en nuestros servidores y le permite configurar su servidor. Si utilizó algún panel de control del servidor y lo compró usted mismo, esta es la solución para usted. Pero no es adecuado para los proveedores: no están listos para proporcionar acceso a los servidores de sus clientes a una empresa externa, y los entiendo muy bien. Esto plantea preguntas de seguridad y tolerancia a fallas. Por lo tanto, decidimos darles todo nuestro SaaS para que pudieran implementarlo en casa. Es como Amazon, que puede ejecutar en su propio centro de datos y conectarse a su sistema de facturación. Llamamos a esta solución una plataforma.

El primer despliegue no fue muy sencillo. Para cada usuario activo, levantamos un contenedor separado. Los contenedores Docker se elevan rápidamente, pero el descubrimiento de servicios no funciona instantáneamente: no está destinado a elevar / detener dinámicamente los contenedores en un segundo. Y desde el momento de la subida hasta el momento en que se puede usar el servicio, ¡a veces pasan los minutos!

Ya escribí que el usuario de hosting ha cambiado mucho en la última década. Ahora imagine: le compra un alojamiento y obtiene acceso al shell. WTF?!?! Para usarlo, primero tendrá que encontrar algún cliente SSH (en Windows esto puede convertirse en un problema; de forma predeterminada, no hay cliente, generalmente no digo nada sobre los clientes móviles).

Después de que aún pudiera entrar a la consola, necesita instalar el panel. Y esta operación tampoco es rápida. ¿Y si algo sale mal? Por ejemplo, RosKomNadzor puede bloquear los servidores desde los que se descargan los paquetes para su sistema operativo. Con tales errores, el usuario quedará cara a cara.



Puede argumentar que, en la mayoría de los casos, el usuario recibe el panel ya instalado por el host, y lo anterior no se aplica a él. Posiblemente Pero el panel que se ejecuta en el servidor consume los recursos que pagó (ocupa espacio en disco, consume su procesador y memoria). Su rendimiento depende directamente del rendimiento del servidor (el servidor se bloqueó, el panel se bloqueó).

Todavía puede haber quienes no usan ningún panel y piensan: esto no me concierne. Pero tal vez en su servidor, si tiene uno, algún tipo de panel sigue en pie y consume recursos, pero ¿simplemente no lo usa?

"Entonces, incluso es maravilloso, nos ayudas a vender recursos", dicen los hosteleros rusos. Otros agregan: "¿Por qué gastamos nuestros recursos desplegando una plataforma que consume más que un panel independiente?"

Hay varias respuestas a esta pregunta.
  1. Se mejora la calidad del servicio: puede controlar la versión del panel; actualícela rápidamente cuando aparezca una nueva funcionalidad o se detecten errores; Puede anunciar acciones directamente en el panel.
  2. En una escala de infraestructura, se ahorra disco, memoria y un procesador, ya que algunos procesos se inician solo para servir a usuarios activos, y algunos sirven a muchos clientes al mismo tiempo.
  3. El soporte no necesita analizar si el comportamiento es una característica de una versión particular del panel o un error. Se ahorra tiempo.

Muchos más de nuestros clientes compraron licencias en paquetes y luego las hicieron malabares y las revenden a sus clientes. Ya no hay necesidad de hacer esto, en principio, un trabajo sin sentido. Después de todo, ahora este es un producto.

Además, tuvimos la oportunidad de utilizar soluciones pesadas, que ofrecen una funcionalidad previamente inaccesible. Por ejemplo, un servicio para obtener capturas de pantalla de un sitio requiere el lanzamiento de un cromo sin cabeza. No creo que el usuario esté muy contento si, debido a tal operación, se queda sin memoria y se disparó, por ejemplo, MySQL.

En conclusión, quiero señalar que aprendemos de la experiencia de otros y estamos desarrollando activamente la nuestra. Registro, ventana acoplable, descubrimiento de servicios, todo tipo de retrasos, reintentos y colas ... Ahora no recuerda todo lo que tenía que dominar o reinventar. Todo esto no facilita el desarrollo, sino que abre nuevas oportunidades y hace que nuestro trabajo sea más emocionante.

La historia aún no ha terminado, pero lo que sucedió se puede ver en el sitio web de Vepp .

Source: https://habr.com/ru/post/467973/


All Articles