Cómo crear un desarrollo interno completo utilizando DevOps: experiencia VTB

Las prácticas de DevOps funcionan. Nosotros mismos nos convencimos de ello cuando redujimos el tiempo de instalación de la versión 10 veces. En el sistema FIS Profile, que utilizamos en VTB, la instalación ahora demora 90 minutos en lugar de 10. El tiempo de compilación del lanzamiento se ha reducido de dos semanas a dos días. El número de defectos de implementación persistentes se ha reducido casi al mínimo. Para alejarnos del “trabajo manual” y eliminar la dependencia del proveedor, tuvimos que trabajar con muletas y encontrar soluciones inesperadas. Debajo del corte hay una historia detallada sobre cómo construimos un desarrollo interno completo.

Cómo crear un desarrollo interno completo utilizando DevOps: experiencia VTB
 

Prólogo: DevOps es una filosofía

Durante el año pasado, trabajamos mucho para organizar el desarrollo interno y la implementación de prácticas de DevOps en VTB:

  • Construimos procesos de desarrollo internos para 12 sistemas;
  • Lanzamos 15 oleoductos, cuatro de los cuales entraron en producción;
  • 1445 escenarios de prueba automatizados;
  • Implementamos con éxito una serie de lanzamientos preparados por equipos internos.

Una de las prácticas internas de desarrollo e implementación de DevSecOps más difíciles de organizar resultó ser el sistema FIS Profile, un procesador de productos minoristas en un DBMS no relacional. Sin embargo, pudimos desarrollar el desarrollo, lanzar la canalización, instalar paquetes individuales que no son de lanzamiento en el producto y aprendimos cómo ensamblar lanzamientos. La tarea no fue fácil, pero sí interesante y sin restricciones obvias en la implementación: aquí está el sistema: es necesario crear un desarrollo interno. La única condición es utilizar el CD ante un entorno productivo.

Al principio, el algoritmo de implementación parecía simple y claro:

  • Desarrollamos experiencia en desarrollo inicial y logramos un nivel aceptable de calidad por parte del equipo de código sin defectos graves;
  • Nos integramos en los procesos existentes tanto como sea posible;
  • Para transferir código entre etapas obvias, cortamos una tubería y empujamos uno de sus extremos hacia la continuación.

Durante este tiempo, el equipo de desarrollo del tamaño requerido debe desarrollar habilidades y aumentar la proporción de su contribución a los lanzamientos a un nivel aceptable. Y listo, podemos dar por cumplida la tarea.

Parecería que este es un camino completamente eficiente desde el punto de vista energético hacia el resultado requerido: aquí está DevOps, aquí están las métricas de rendimiento del equipo, aquí está la experiencia acumulada... Pero en la práctica, recibimos otra confirmación de que DevOps todavía es una cuestión de filosofía. , y no "adjunto al proceso de gitlab, ansible, nexus y más abajo en la lista".

Tras analizar una vez más el plan de acción, nos dimos cuenta de que estábamos construyendo dentro de nosotros una especie de proveedor subcontratado. Por lo tanto, al algoritmo descrito anteriormente se le sumó la reingeniería de procesos, así como el desarrollo de experiencia a lo largo de toda la ruta de desarrollo para lograr un rol protagónico en este proceso. No es la opción más fácil, pero es el camino hacia un desarrollo ideológicamente correcto.
 

¿Dónde comienza el desarrollo interno? 

No era el sistema más amigable para trabajar. Arquitectónicamente, era un gran DBMS no relacional, constaba de muchos objetos ejecutables separados (scripts, procedimientos, lotes, etc.), que se llamaban según era necesario y funcionaba según el principio de una caja negra: recibe una solicitud y emite una respuesta. Otras dificultades que vale la pena señalar incluyen:

  • Lenguaje exótico (MUMPS);
  • Interfaz de consola;
  • Falta de integración con marcos y herramientas de automatización populares;
  • Volumen de datos en decenas de terabytes;
  • Carga de más de 2 millones de operaciones por hora;
  • Importancia: crítico para el negocio.

Al mismo tiempo, no había ningún repositorio de código fuente de nuestra parte. En absoluto. Había documentación, pero todos los conocimientos y competencias clave estaban del lado de una organización externa.
Comenzamos a dominar el desarrollo del sistema casi desde cero, teniendo en cuenta sus características y su baja distribución. Iniciado en octubre de 2018:

  • Estudió la documentación y los conceptos básicos de la generación de código;
  • Estudiamos el curso corto sobre desarrollo recibido del proveedor;
  • Habilidades de desarrollo inicial dominadas;
  • Compilamos un manual de capacitación para los nuevos miembros del equipo;
  • Acordamos incluir al equipo en modo “combate”;
  • Se resolvió el problema con el control de calidad del código;
  • Organizamos un stand para el desarrollo.

Pasamos tres meses desarrollando experiencia y sumergiéndonos en el sistema, y ​​desde principios de 2019, el desarrollo interno comenzó su avance hacia un futuro brillante, a veces con dificultad, pero con confianza y determinación.

Migración de repositorio y pruebas automáticas

La primera tarea de DevOps es el repositorio. Rápidamente acordamos brindar acceso, pero fue necesario migrar del SVN actual con una rama troncal a nuestro Git objetivo con la transición a un modelo de varias ramas y el desarrollo de Git Flow. También contamos con 2 equipos con infraestructura propia, más parte del equipo del proveedor en el exterior. Tuve que vivir con dos Gits y asegurar la sincronización. En tal situación, era el menor de dos males.

La migración del repositorio se pospuso repetidamente y no se completó hasta abril, con la ayuda de colegas de primera línea. Con Git Flow, decidimos mantener las cosas simples para empezar y nos decidimos por el esquema clásico con revisión, desarrollo y lanzamiento. Decidieron abandonar el maestro (también conocido como pinchazo). A continuación explicaremos por qué esta opción resultó ser óptima para nosotros. Como trabajador se utilizó un repositorio externo del proveedor, común para dos equipos. Se sincronizó con el repositorio interno según un cronograma. Ahora con Git y Gitlab se pudo automatizar procesos.

El problema de las pruebas automáticas se resolvió con sorprendente facilidad: se nos proporcionó un marco ya preparado. Teniendo en cuenta las peculiaridades del sistema, llamar a una operación separada era una parte comprensible del proceso de negocio y al mismo tiempo sirvió como prueba unitaria. Todo lo que quedaba era preparar los datos de la prueba y establecer el orden deseado para llamar a los scripts y evaluar los resultados. A medida que se fue completando la lista de escenarios formada sobre la base de las estadísticas de operación, la criticidad de los procesos y la metodología de regresión existente, comenzaron a aparecer las pruebas automáticas. Ahora podríamos empezar a construir el oleoducto.

Cómo era: el modelo antes de la automatización

El modelo existente del proceso de implementación es una historia aparte. Cada modificación se transfirió manualmente como un paquete de instalación incremental independiente. Luego vino el registro manual en Jira y la instalación manual en entornos. Para los paquetes individuales todo parecía claro, pero con la preparación del lanzamiento las cosas se complicaron más.

El montaje se realizó a nivel de entregas individuales, que eran objetos independientes. Cualquier cambio es una nueva entrega. Entre otras cosas, se agregaron entre 60 y 70 versiones técnicas a los paquetes 10-15 de la composición de la versión principal: versiones obtenidas al agregar o excluir algo de la versión y que reflejan cambios en las ventas fuera de las versiones.

Los objetos dentro de las entregas se superponían entre sí, especialmente en el código ejecutable, que era menos de la mitad único. Había muchas dependencias tanto del código ya instalado como del que acababa de instalarse. 

Para obtener la versión requerida del código, era necesario seguir estrictamente el orden de instalación, durante el cual los objetos se reescribían físicamente muchas veces, entre 10 y 12 veces.

Después de instalar un lote de paquetes, tuve que seguir manualmente las instrucciones para inicializar la configuración. La versión fue ensamblada e instalada por el proveedor. La composición del lanzamiento se aclaró casi antes del momento de la implementación, lo que implicó la creación de paquetes de "desacoplamiento". Como resultado, una parte importante de los suministros pasó de una versión a otra con su propia cola de "desacoplamientos".

Ahora está claro que con este enfoque (ensamblar el rompecabezas de lanzamiento a nivel de paquete) una única rama maestra no tenía significado práctico. La instalación en producción requirió de una hora y media a dos horas de trabajo manual. Es bueno que al menos en el nivel del instalador se haya especificado el orden de procesamiento de los objetos: los campos y estructuras se ingresaron antes que los datos y procedimientos para ellos. Sin embargo, esto sólo funcionó dentro de un paquete separado.

El resultado lógico de este enfoque fueron los defectos de instalación obligatorios en forma de versiones torcidas de los objetos, códigos innecesarios, instrucciones faltantes e influencias mutuas no contabilizadas de los objetos, que fueron eliminados febrilmente después del lanzamiento. 

Primeras actualizaciones: compromiso de montaje y entrega.

La automatización comenzó transmitiendo código a través de una tubería a lo largo de esta ruta:

  • Recoger la entrega terminada del almacén;
  • Instálelo en un entorno dedicado;
  • Ejecute pruebas automáticas;
  • Evaluar el resultado de la instalación;
  • Llame a la siguiente canalización en el lado del comando de prueba.

La siguiente canalización debe registrar la tarea en Jira y esperar a que los comandos se distribuyan a los bucles de prueba seleccionados, que dependen del momento de implementación de la tarea. Desencadenante: una carta sobre la preparación para la entrega en una dirección determinada. Esto, por supuesto, era una muleta obvia, pero tenía que empezar por algún lado. En mayo de 2019 se inició la transferencia de código con comprobaciones de nuestros entornos. El proceso ha comenzado, solo queda ponerlo en buenas condiciones:

  • Cada modificación se realiza en una rama separada, que corresponde al paquete de instalación y se fusiona con la rama maestra de destino;
  • El desencadenante del lanzamiento de la canalización es la aparición de una nueva confirmación en la rama maestra a través de una solicitud de fusión, que los mantenedores del equipo interno cierran;
  • Los repositorios se sincronizan una vez cada cinco minutos;
  • Se inicia el montaje del paquete de instalación utilizando el ensamblador recibido del proveedor.

Después de esto, ya existían pasos para verificar y transferir el código, lanzar la tubería y ensamblar de nuestro lado.

Esta opción se lanzó en julio. Las dificultades de la transición provocaron cierta insatisfacción entre el proveedor y la primera línea, pero durante el mes siguiente logramos eliminar todas las asperezas y establecer un proceso entre los equipos. Ahora tenemos montaje por compromiso y entrega.
En agosto, logramos completar la primera instalación de un paquete separado en producción utilizando nuestro canal y, desde septiembre, sin excepción, todas las instalaciones de paquetes individuales que no se publican se realizaron a través de nuestra herramienta de CD. Además, logramos lograr una parte de las tareas internas en el 40% de la composición del lanzamiento con un equipo más pequeño que el del proveedor; esto es un éxito definitivo. Quedaba la tarea más seria: ensamblar e instalar el lanzamiento.

La solución final: paquetes de instalación acumulativos 

Entendíamos perfectamente que escribir las instrucciones del proveedor era una automatización regular; teníamos que repensar el proceso en sí. La solución era obvia: recolectar un suministro acumulativo de la rama de lanzamiento con todos los objetos de las versiones requeridas.

Comenzamos con una prueba de concepto: ensamblamos manualmente el paquete de lanzamiento de acuerdo con el contenido de la implementación anterior y lo instalamos en nuestros entornos. Todo salió bien, el concepto resultó viable. A continuación, resolvimos el problema de programar la configuración de inicialización e incluirla en la confirmación. Preparamos un nuevo paquete y lo probamos en entornos de prueba como parte de la actualización de Contour. La instalación fue exitosa, aunque con una amplia gama de comentarios por parte del equipo de implementación. Pero lo principal es que recibimos el visto bueno para comenzar la producción en el lanzamiento de noviembre con nuestro ensamblaje.

Faltando poco más de un mes, los suministros cuidadosamente seleccionados indicaban claramente que el tiempo se estaba acabando. Decidieron realizar la compilación desde la rama de lanzamiento, pero ¿por qué debería separarse? No tenemos un tipo Prod y las ramas existentes no son buenas: hay mucho código innecesario. Necesitamos urgentemente eliminar los prod-likes, y esto son más de tres mil compromisos. Montarlo a mano no es una opción en absoluto. Dibujamos un script que recorre el registro de instalación del producto y recopila confirmaciones para la rama. A la tercera vez funcionó correctamente, y tras “terminar con una lima” la rama estaba lista. 

Escribimos nuestro propio constructor para el paquete de instalación y lo terminamos en una semana. Luego tuvimos que modificar el instalador de la funcionalidad principal del sistema, ya que es de código abierto. Después de una serie de comprobaciones y modificaciones, el resultado se consideró exitoso. Mientras tanto, tomó forma la composición del lanzamiento, para cuya correcta instalación fue necesario alinear el circuito de prueba con el de producción, y para ello se redactó un guión aparte.

Naturalmente, hubo muchos comentarios sobre la primera instalación, pero en general el código funcionó. Y después de la tercera instalación, todo empezó a verse bien. El control de la composición y el control de la versión de los objetos se monitoreaban por separado en modo manual, lo que en esta etapa estaba bastante justificado.

Un desafío adicional fue el gran número de no liberaciones que debían tenerse en cuenta. Pero con la rama Prod-like y Rebase, la tarea se volvió transparente.

Primera vez, rápido y sin errores.

Abordamos el lanzamiento con actitud optimista y más de una docena de instalaciones exitosas en diferentes circuitos. Pero, literalmente, un día antes de la fecha límite, resultó que el proveedor no había completado el trabajo de preparación de la versión para la instalación de la manera aceptada. Si por alguna razón nuestra compilación no funciona, el lanzamiento se verá interrumpido. Además, gracias a nuestros esfuerzos, lo cual es especialmente desagradable. No teníamos forma de retirarnos. Por lo tanto, pensamos en opciones alternativas, preparamos planes de acción y comenzamos la instalación.

Sorprendentemente, todo el lanzamiento, compuesto por más de 800 objetos, se inició correctamente a la primera y en sólo 10 minutos. Pasamos una hora revisando los registros en busca de errores, pero no encontramos ninguno.

Todo el día siguiente hubo silencio en el chat de lanzamiento: no hubo problemas de implementación, versiones corruptas o código “inapropiado”. Incluso fue algo incómodo. Posteriormente surgieron algunos comentarios, pero en comparación con otros sistemas y experiencias anteriores, su número y prioridad eran notablemente menores.

Un efecto adicional del efecto acumulativo fue un aumento en la calidad del montaje y las pruebas. Debido a las múltiples instalaciones de la versión completa, se identificaron oportunamente defectos de compilación y errores de implementación. Las pruebas en configuraciones de versión completa permitieron identificar adicionalmente defectos en la influencia mutua de objetos que no aparecieron durante las instalaciones incrementales. Definitivamente fue un éxito, especialmente considerando nuestra contribución del 57% al lanzamiento.

Resultados y conclusiones

En menos de un año logramos:

  • Construya un desarrollo interno completo utilizando un sistema exótico;
  • Eliminar la dependencia crítica de los proveedores;
  • Lanzar CI/CD para un legado muy hostil;
  • Elevar los procesos de implementación a un nuevo nivel técnico;
  • Reducir significativamente el tiempo de implementación;
  • Reducir significativamente el número de errores de implementación;
  • Declararse con confianza como un experto líder en desarrollo.

Por supuesto, gran parte de lo que se describe parece una absoluta tontería, pero estas son las características del sistema y las limitaciones del proceso que existen en él. Por el momento, los cambios afectan a los productos y servicios de IS Profile (cuentas maestras, tarjetas de plástico, cuentas de ahorro, depósitos en garantía, préstamos en efectivo), pero potencialmente el enfoque se puede aplicar a cualquier IS al que se le haya asignado la tarea de implementar prácticas DevOps. El modelo acumulativo se puede replicar de forma segura para implementaciones posteriores (incluidas las que no son de lanzamiento) de muchas entregas.

Fuente: habr.com

Añadir un comentario