Desarrollo de Microsoft con DevOps

Microsoft se esfuerza por utilizar One Engineering System para compilar e implementar todos los productos de Microsoft con un proceso sólido de DevOps centrado en un flujo de ramificación y versión de Git. En este artículo se destaca la implementación práctica, cómo el sistema se escala desde los servicios pequeños hasta las necesidades de desarrollo de plataformas masivas y las lecciones aprendidas del uso del sistema en varios equipos de Microsoft.

La adopción de un proceso de desarrollo estandarizado es una tarea ambiciosa. Los requisitos de diferentes organizaciones de Microsoft varían considerablemente y los requisitos de diferentes equipos dentro de las organizaciones se escalan en tamaño y complejidad. Para satisfacer estas distintas necesidades, Microsoft utiliza una estrategia de ramificación troncal para ayudar a desarrollar los productos rápidamente, implementarlos con regularidad y entregar cambios de forma segura en producción.

Microsoft también usa principios de ingeniería de plataformas como parte de One Engineering System.

Flujo de versiones de Microsoft

Cada organización debe establecerse en un proceso estándar de lanzamiento de versiones de código para garantizar la coherencia entre los equipos. El flujo de versiones de Microsoft incorpora procesos de DevOps desde el desarrollo hasta el lanzamiento. Los pasos básicos del flujo de versiones constan de ramificación, inserción, solicitud de incorporación de cambios y combinación.

Rama

Para corregir un error o implementar una característica, un desarrollador crea una nueva rama fuera de la rama de integración principal. El modelo de ramificación ligera de Git crea estas ramas puntuales de corta duración para cada contribución de código. Los desarrolladores confirman de forma temprana y evitan ramas de características de ejecución larga mediante marcas de características.

Push

Cuando el desarrollador está listo para integrar y enviar los cambios al resto del equipo, inserta su rama local en una rama del servidor y abre una solicitud de incorporación de cambios. Los repositorios que tienen varios cientos de desarrolladores trabajando en muchas ramas usan una convención de nomenclatura para las ramas del servidor a fin de mitigar la confusión y la proliferación de ramas. Normalmente, los desarrolladores crean ramas denominadas users/<username>/feature, donde <username> es su nombre de cuenta.

Solicitud de incorporación de cambios

Las solicitudes de incorporación de cambios controlan las combinaciones de ramas puntuales con la rama principal y aseguran que se cumplan las directivas de rama. El proceso de solicitud de incorporación de cambios compila los cambios propuestos y ejecuta una prueba rápida superada. Los conjuntos de pruebas de primer y segundo nivel ejecutan alrededor de 60 000 pruebas en menos de cinco minutos. Esta no es la matriz de pruebas de Microsoft completa, pero es suficiente para dar confianza rápidamente en las solicitudes de incorporación de cambios.

A continuación, otros miembros del equipo revisan el código y aprueban los cambios. La revisión de código continúa desde donde lo dejaron las pruebas automatizadas y es especialmente útil para detectar problemas arquitectónicos. Las revisiones manuales de código aseguran que otros ingenieros del equipo tengan visibilidad sobre los cambios y que la calidad del código siga siendo alta.

Merge

Una vez que la solicitud de incorporación de cambios satisface todas las directivas de compilación y los revisores han dado su aprobación, la rama puntual se combina con la rama de integración principal y se completa la solicitud de incorporación de cambios.

Después de la combinación, se ejecutan otras pruebas de aceptación que tardan más tiempo en completarse. Estas pruebas tradicionales posteriores a la comprobación realizan una validación más exhaustiva. Este proceso de pruebas proporciona un buen equilibrio entre tener pruebas rápidas durante la revisión de la solicitud de incorporación de cambios y tener una cobertura de prueba completa antes del lanzamiento de la versión.

Diferencias con GitHub Flow

El flujo de GitHub es un popular flujo de versiones de desarrollo troncal que permite a las organizaciones implementar un enfoque escalable para Git. Sin embargo, algunas organizaciones encuentran que, a medida que sus necesidades crecen, deben diferir de partes del flujo de GitHub.

Por ejemplo, una parte que se suele pasar por alto del flujo de GitHub es que las solicitudes de incorporación de cambios deben implementarse en producción para realizar pruebas antes de poder combinarlas con la rama principal. Este proceso significa que todas las solicitudes de incorporación de cambios esperan en la cola de implementación para combinarse.

Algunos equipos tienen varios cientos de desarrolladores trabajando constantemente en un único repositorio, los cuales pueden completar más de 200 solicitudes de incorporación de cambios en la rama principal al día. Si cada solicitud de incorporación de cambios requiere una implementación en varios centros de datos de Azure en todo el mundo para realizar pruebas, los desarrolladores dedican tiempo a esperar a que las ramas se combinen, en lugar de escribir software.

En cambio, los equipos de Microsoft siguen desarrollando en la rama principal y procesan por lotes las implementaciones en lanzamientos con hora, normalmente alineadas con una cadencia de sprint de tres semanas.

Detalles de la implementación

Estos son algunos detalles clave de implementación del flujo de versiones de Microsoft:

Estrategia del repositorio de Git

Los distintos equipos tienen estrategias diferentes para administrar sus repositorios de Git. Algunos equipos mantienen la mayoría de su código en un repositorio de Git. El código se divide en componentes, cada uno de ellos en su propia carpeta de nivel raíz. Los componentes grandes, especialmente los componentes más antiguos, pueden tener varios subcomponentes que tienen subcarpetas independientes dentro del componente primario.

Screenshot showing a Git repository structure.

Repositorios adjuntos

Algunos equipos también administran repositorios adjuntos. Por ejemplo, los agentes y las tareas de compilación y versión, la extensión de VS Code y los proyectos de código abierto se desarrollan en GitHub. Los cambios de configuración se activan en un repositorio independiente. Otros paquetes de los que depende el equipo proceden de otros lugares y se consumen a través de NuGet.

Repositorio mono o múltiple

Aunque algunos equipos eligen tener un único repositorio monolítico, un repositorio mono, otros productos de Microsoft usan un enfoque de repositorio múltiple. Skype, por ejemplo, tiene cientos de repositorios pequeños que se unen en varias combinaciones para crear muchos clientes, servicios y herramientas diferentes. Especialmente para los equipos que adoptan microservicios, los repositorios múltiples pueden ser el enfoque adecuado. Normalmente, los productos más antiguos que comenzaron como monolitos encuentran un enfoque de repositorio mono para ser la transición más sencilla a Git, y su organización de código lo refleja.

Ramas de versión

El flujo de versiones de Microsoft permite que la rama principal admita la compilación en todo momento. Los desarrolladores trabajan en ramas puntuales de corta duración que se combinan con main. Cuando un equipo está listo para enviarse, ya sea al final de un sprint o para una actualización importante, inicia una nueva rama de versión a partir de la rama principal. Las ramas de versión nunca se combinan con la rama principal, por lo que pueden requerir la selección exclusiva de cambios importantes.

En el diagrama siguiente se muestran ramas de corta duración en azul y ramas de versión en negro. Hay una rama con una confirmación que necesita la selección exclusiva que aparece en rojo.

Diagram showing Git release branch structure.

Directivas y permisos de rama

Las directivas de rama de Git ayudan a aplicar la estructura de las ramas de versión y mantienen la rama principal limpia. Por ejemplo, las directivas de rama pueden impedir inserciones directas en la rama principal.

Para mantener ordenada la jerarquía de ramas, los equipos usan permisos para bloquear la creación de ramas en el nivel raíz de la jerarquía. En el ejemplo siguiente, todos los usuarios pueden crear ramas en carpetas como usuarios/, características/ y equipos/. Solo los administradores de versiones tienen permiso para crear ramas en versiones/, y algunas herramientas de automatización tienen permiso para la carpeta integraciones/.

Screenshot that shows branches.

Flujo de trabajo del repositorio de Git

Dentro del repositorio y la estructura de rama, los desarrolladores realizan su trabajo diario. Los entornos de trabajo varían en gran medida según el equipo y el individuo. Algunos desarrolladores prefieren la línea de comandos, otros como Visual Studio y otros funcionan en distintas plataformas. Las estructuras y directivas en vigor en los repositorios de Microsoft aseguran una base sólida y coherente.

Un flujo de trabajo típico implica las siguientes tareas comunes:

Creación de una nueva característica

La creación de una nueva característica es el núcleo del trabajo de un desarrollador de software. Entre las partes del proceso que no son de Git se incluyen el análisis de los datos de telemetría, la creación de un diseño y una especificación, y la escritura del código real. A continuación, el desarrollador comienza a trabajar con el repositorio mediante la sincronización con la confirmación más reciente en main. La rama principal siempre admite la compilación, por lo que se garantiza que sea un buen punto de partida. El desarrollador extrae una nueva rama de características, realiza cambios de código, confirmaciones e inserciones en el servidor e inicia una nueva solicitud de incorporación de cambios.

Uso de directivas y comprobaciones de rama

Tras la creación de una solicitud de incorporación de cambios, los sistemas automatizados comprueban que el nuevo código se compila, no interrumpe nada y no infringe ninguna directiva de seguridad o cumplimiento. Este proceso no impide que se produzca otro trabajo en paralelo.

Las directivas y comprobaciones de rama pueden requerir una compilación correcta, incluidas las pruebas superadas, la aprobación de los propietarios de cualquier código alterado y varias comprobaciones externas para verificar las directivas corporativas antes de que se pueda completar una solicitud de incorporación de cambios.

Screenshot showing the checks on a pull request.

Integrar con Microsoft Teams

Muchos equipos configuran la integración con Microsoft Teams, que anuncia la nueva solicitud de incorporación de cambios a los compañeros de equipo de los desarrolladores. Los propietarios de cualquier código alterado se agregan automáticamente como revisores. Los equipos de Microsoft suelen usar revisores opcionales para el código que muchas personas alteran, como la generación de clientes REST y los controles compartidos, para tener ojos expertos sobre esos cambios.

Screenshot showing Teams integration.

Screenshot showing Teams notification of a pull request.

Implementación con marcas de características

Una vez satisfechos los revisores y los propietarios del código y la automatización se ha realizado correctamente, el desarrollador puede completar la solicitud de incorporación de cambios. Si hay un conflicto de combinación, el desarrollador obtiene instrucciones sobre cómo sincronizar con el conflicto, corregirlo y volver a insertar los cambios. La automatización se ejecuta de nuevo en el código corregido, pero las personas no tienen que aprobarlo de nuevo.

La rama se combina con main y el nuevo código se implementa en el siguiente sprint o versión principal. Esto no significa que la nueva característica se muestre inmediatamente. Microsoft desacopla la implementación y exposición de nuevas características mediante marcas de características.

Incluso si la característica necesita un poco más de trabajo antes de que esté lista para mostrarse, es seguro ir a main si el producto compila e implementa. Una vez en main, el código pasa a formar parte de una compilación oficial, donde se vuelve a probar, confirmar que cumple la directiva y firmar digitalmente.

Desplazamiento a la izquierda para detectar problemas pronto

Este flujo de trabajo de Git proporciona varias ventajas. En primer lugar, el funcionamiento de una sola rama principal prácticamente elimina la deuda de combinación. En segundo lugar, el flujo de solicitud de incorporación de cambios proporciona un punto común para aplicar pruebas, revisión de código y detección de errores de forma temprana en la canalización. Esta estrategia de desplazamiento a la izquierda ayuda a acortar el ciclo de comentarios a los desarrolladores porque puede detectar errores en minutos, en vez de horas o días. Esta estrategia también proporciona confianza para la refactorización, ya que todos los cambios se prueban constantemente.

Actualmente, un producto con más de 200 solicitudes de incorporación de cambios podría producir más de 300 compilaciones de integración continuas al día, lo que equivale a más de 500 ejecuciones de pruebas cada 24 horas. Este nivel de prueba sería imposible sin el flujo de trabajo de ramificación y versión troncal.

Lanzamiento en hitos de sprint

Al final de cada sprint, el equipo crea una rama de versión a partir de la rama principal. Por ejemplo, al final del sprint 129, el equipo crea una nueva rama de versión releases/M129. A continuación, el equipo coloca la rama sprint 129 en producción.

Después de la rama de la rama de versión, la rama principal permanece abierta para que los desarrolladores combinen los cambios. Estos cambios se implementarán tres semanas más tarde en la siguiente implementación de sprint.

Illustration of the release branch at sprint 129.

Revisiones de la versión

A veces, los cambios deben ir a producción rápidamente. Microsoft no suele agregar nuevas características en medio de un sprint, pero a veces quiere traer una corrección de errores rápidamente para desbloquear a los usuarios. Los problemas pueden ser menores, como errores tipográficos, o lo suficientemente grandes como para provocar un problema de disponibilidad o un incidente de sitio activo.

La rectificación de estos problemas comienza con el flujo de trabajo normal. Un desarrollador crea una rama a partir de main, obtiene el código revisado y completa la solicitud de incorporación de cambios para combinarla. El proceso siempre comienza realizando el cambio en main primero. Esto permite crear la corrección rápidamente y validarla localmente sin tener que cambiar a la rama de versión.

Al seguir este proceso también se garantiza que el cambio se aplica en main, lo cual es fundamental. Corregir un error en la rama de versión sin devolver el cambio a main significaría que el error se repetiría durante la siguiente implementación, cuando la versión del sprint 130 se ramifica a partir de main. Es fácil olvidarse de actualizar main durante la confusión y el estrés que pueden surgir durante una interrupción. La incorporación de cambios en main en primer lugar significa tener siempre los cambios en la rama principal y en la rama de versión.

La funcionalidad de Git habilita este flujo de trabajo. Para incorporar los cambios inmediatamente en producción, una vez que un desarrollador combina una solicitud de incorporación de cambios con main, puede usar la página de solicitud de incorporación de cambios para seleccionar los cambios en la rama de versión. Este proceso crea una nueva solicitud de incorporación de cambios que tiene como destino la rama de versión y devuelve el contenido que se acaba de combinar con main.

Illustration of cherry-picking a hotfix commit into branch 129.

El uso de la funcionalidad de selección exclusiva abre rápidamente una solicitud de incorporación de cambios, lo que proporciona la rastreabilidad y confiabilidad de las directivas de rama. La selección exclusiva puede producirse en el servidor, sin tener que descargar la rama de versión en un equipo local. Realizar cambios, corregir conflictos de combinación o realizar cambios menores debido a las diferencias entre las dos ramas son cosas que pueden ocurrir en el servidor. Los equipos pueden editar los cambios directamente desde el editor de texto basado en explorador o a través de la Extensión de conflicto de combinación de solicitud de incorporación de cambios para obtener una experiencia más avanzada.

Una vez que una solicitud de incorporación de cambios tiene como destino la rama de versión, el código del equipo la revisa de nuevo, evalúa las directivas de rama, prueba la solicitud de incorporación de cambios y la combina. Después de la combinación, la corrección se implementa en el primer anillo de servidores en minutos. Desde allí, el equipo implementa progresivamente la corrección en más cuentas mediante anillos de implementación. A medida que los cambios se implementan en más usuarios, el equipo supervisa el éxito y comprueba que el cambio corrige el error sin introducir deficiencias ni ralentizaciones. La corrección finalmente se implementa en todos los centros de datos de Azure.

Pasar al siguiente sprint

Durante las tres semanas siguientes, el equipo termina de agregar características al sprint 130 y está listo para implementar esos cambios. Crean la nueva rama de versión, releases/M130 a partir de main, y la implementan.

En este momento, de hecho hay dos ramas en producción. Con una implementación basada en anillos para llevar los cambios a producción de forma segura, el anillo anticipado obtiene los cambios del sprint 130 y los servidores del anillo lento permanecen en el sprint 129 mientras los nuevos cambios se validan en producción.

La revisión de un cambio en medio de una implementación podría requerir la revisión de dos versiones diferentes, la versión del sprint 129 y la versión del sprint 130. El equipo conecta al puerto e implementa la revisión en ambas ramas de versión. La rama 130 se vuelve a implementar con la revisión en los anillos que ya se han actualizado. La rama 129 se vuelve a implementar con la revisión en los anillos externos que aún no se han actualizado a la versión del sprint siguiente.

Una vez implementados todos los anillos, se abandona la rama del sprint 129 anterior, ya que los cambios introducidos en la rama del sprint 129 como revisión también se han realizado en main. Por lo tanto, esos cambios también estarán en la rama releases/M130.

Illustration of a release branch at sprint 130.

Resumen

El modelo de flujo de versión es la piedra angular de cómo Microsoft desarrolla con DevOps para ofrecer servicios en línea. Este modelo usa una estrategia de ramificación troncal sencilla. Sin embargo, en lugar de mantener a los desarrolladores bloqueados en una cola de implementación, esperando para combinar sus cambios, el flujo de versión de Microsoft les permite seguir trabajando.

Este modelo de versión también permite implementar nuevas características en centros de datos de Azure a una cadencia regular, a pesar del tamaño de los código base de Microsoft y del número de desarrolladores que trabajan en ellos. El modelo también permite incorporar revisiones en producción de forma rápida y eficaz.