Diseño de la canalización
En esta unidad, acompañará al equipo web de Tailspin a lo largo del proceso de definir la canalización de versión para el sitio web de Space Game.
Al planear una canalización de versión, lo habitual es empezar identificando las fases (o divisiones principales) de la canalización. Cada fase suele asignarse a un entorno. Por ejemplo, en el módulo anterior, la canalización básica de Andy y Mara tenía una fase de implementación asignada a una instancia de Azure App Service.
En este módulo, promoverá los cambios de una fase a la siguiente. En cada fase, implementará el sitio web de Space Game en el entorno asociado a esa fase.
Después de definir las fases que necesita, observará cómo se promocionan los cambios de una fase a la siguiente. Cada fase puede definir los criterios de éxito que se deben cumplir antes de que la compilación pase a la fase siguiente. Azure Pipelines proporciona varias maneras de ayudarle a controlar cómo y cuándo se mueven los cambios a lo largo de la canalización. Estos métodos se usan en su conjunto para la administración de versiones.
En esta sección:
- Descubrirá qué diferencias existen entre las fases de canalización comunes, como compilación, desarrollo, pruebas y ensayo.
- Aprenderá a usar los desencadenadores manuales, programados y de implementación continua para controlar cuándo se mueve un artefacto a la fase siguiente de la canalización.
- Observará cómo una aprobación de versión pausa la canalización hasta que un aprobador acepta o rechaza la versión.
La reunión
El equipo web de Tailspin se reúne al completo. En Creación de una canalización de versión con Azure Pipelines, el equipo planeó las tareas para el sprint actual. Cada tarea está relacionada con la creación de la canalización de versión para el sitio web de Space Game.
Como recordará, el equipo estableció estas cinco tareas para el sprint:
- Creación de una canalización de varias fases
- Conexión de la aplicación web a una base de datos.
- Automatización de las pruebas de calidad.
- Automatización de las pruebas de rendimiento.
- Mejora de la cadencia de las versiones
El equipo se reúne para hablar sobre la primera tarea: Creación de una canalización de varias fases. Después de que el equipo define la canalización, este puede pasar de su prueba básica de concepto a una canalización de versión que incluya más fases, comprobaciones de calidad y aprobaciones.
Amita y Tim están viendo cómo demuestran Andy y Mara la canalización de versión por segunda vez. Como pueden comprobar, el artefacto está compilado e instalado en App Service.
¿Qué fases de la canalización se necesitan?
Si quiere implementar una canalización de versión, es importante que primero identifique qué fases necesita. Las fases que elija dependerán de los requisitos. Acompañemos al equipo mientras decide las fases.
Tim: Vale, ya entiendo en qué consiste una canalización automatizada. Me gusta lo fácil que es de implementar en Azure. Pero ¿qué hacemos con esta demostración? Necesitamos algo que podamos usar de verdad en nuestras versiones.
Amita: ¡Genial! Necesitamos agregar otras fases. Por ejemplo, en la actualidad, no tenemos espacio para una fase de pruebas.
Tim: Además, necesitamos una fase en la que podamos mostrar nuevas características a los responsables de administración. No puedo enviar nada a producción sin la aprobación de la administración.
Andy: Totalmente. Ahora que estamos al tanto de lo que hace una canalización de versión, ¿cómo podemos conseguir que esta canalización haga lo que necesitamos?
Mara: Vamos a esbozar nuestros requisitos para planificar los siguientes pasos. Empecemos por lo que tenemos.
Mara se acerca a la pizarra y hace un esbozo de la canalización existente.
Mara: La fase de compilación compila el código fuente y genera un paquete. En nuestro caso, ese paquete es un archivo .zip. La fase de implementación instala el archivo .zip, que es el sitio web de Space Game, en una instancia de App Service. ¿Qué falta en nuestra canalización de versión?
Incorporación de la fase de desarrollo
Andy: Puede que parezca que no estoy siendo imparcial, pero creo que necesitamos una fase de desarrollo. Esta fase debería ser la primera parada del artefacto una vez que se ha compilado. Los desarrolladores no siempre pueden ejecutar todo el servicio desde su entorno de desarrollo local. Por ejemplo, un sistema de comercio electrónico podría requerir un sitio web, la base de datos de productos y un sistema de pago. Necesitamos una fase que incluya todo lo que le hace falta a la aplicación.
En nuestro caso, la característica de tabla de clasificación del sitio web de Space Game lee puntuaciones altas procedentes de un origen externo. En este momento, lee las puntuaciones ficticias de un archivo. La configuración de una fase de desarrollo nos proporcionaría un entorno en el que podremos integrar la aplicación web con una base de datos real. Esa base de datos también contendrá puntuaciones ficticias, pero nos acercará un paso más a la aplicación final.
Mara: Me gusta la idea. Todavía no realizaremos la integración con una base de datos real. Pero en una fase de desarrollo, podemos llevar a cabo la implementación en un entorno en el que podremos agregar una base de datos.
Mara actualiza el dibujo en la pizarra. Reemplaza "Implementación" por "Desarrollo" para mostrar la fase de desarrollo.
Andy: Lo que mencionas es muy interesante. Compilamos la aplicación cada vez que insertamos un cambio en GitHub. ¿Significa esto que cada compilación se promueve a la fase de desarrollo una vez que se ha completado?
Mara: La compilación continua nos proporciona comentarios importantes sobre el estado de la compilación y las pruebas. Pero queremos promover a la fase de desarrollo solo cuando combinamos código en alguna rama central, ya sea la principal u otra rama de versión. Actualizaré el dibujo para incluir este requisito.
Mara: Creo que será fácil conseguir esta promoción. Podemos definir una condición que promueva a la fase de desarrollo solo cuando se producen cambios en una rama de versión.
¿Qué son las condiciones?
En Azure Pipelines, use una condición para ejecutar una tarea o un trabajo en función del estado de la canalización. Ha trabajado con condiciones en módulos anteriores.
Como recordará, estas son algunas de las condiciones que se pueden especificar:
- Solo cuando todas las tareas dependientes anteriores se han realizado correctamente.
- Incluso si se ha producido un error en una dependencia anterior, a menos que se haya cancelado la ejecución.
- Incluso si se ha producido un error en una dependencia anterior, incluso si se ha cancelado la ejecución.
- Solo cuando se ha producido un error en una dependencia anterior.
- Una condición personalizada.
Este es un ejemplo básico:
steps:
- script: echo Hello!
condition: always()
La condición always()
hace que esta tarea imprima "Hola" de forma incondicional, incluso aunque se hayan producido errores en las tareas anteriores.
Esta condición se usa si no se especifica una condición:
condition: succeeded()
La función integrada succeeded()
comprueba si la tarea anterior se ha realizado correctamente. Si se ha producido un error en la tarea anterior, se omiten esta tarea y las siguientes con la misma condición.
En este caso, quiere compilar una condición que especifique que:
- La tarea anterior se ha realizado correctamente.
- El nombre de la rama de Git actual es release.
Para crear esta condición, se usa la función integrada and()
. Esta función comprueba si cada una de sus condiciones son ciertas. Si alguna condición no es cierta, se produce un error en la condición global.
Para obtener el nombre de la rama actual, se usa la variable Build.SourceBranchName
integrada. Se puede acceder a las variables de una condición de varias maneras. Aquí se usará la sintaxis variables[]
.
Para probar el valor de una variable, puede usar la función integrada eq()
. Esta función comprueba si sus argumentos son iguales.
Teniendo esto en cuenta, aplica esta condición para ejecutar la fase de desarrollo solo cuando el nombre de la rama actual es "release":
condition: |
and
(
succeeded(),
eq(variables['Build.SourceBranchName'], 'release')
)
La primera condición de la función and()
comprueba si la tarea anterior se ha realizado correctamente. La segunda condición comprueba si el nombre de la rama actual es igual a release.
En YAML, se usa la sintaxis de canalización (|
) para definir una cadena que abarca varias líneas. Podría definir la condición en una sola línea, pero se escribe de esta manera para que sea más legible.
Nota:
En este módulo, usamos la rama release como ejemplo. Puede combinar condiciones para definir el comportamiento que necesita. Por ejemplo, podría crear una condición que ejecute la fase solo cuando la compilación se desencadena mediante una solicitud de incorporación de cambios en la rama principal.
En la unidad siguiente, al configurar la fase de desarrollo, se trabaja con un ejemplo más completo.
Para obtener una descripción más completa de las condiciones en Azure Pipelines, consulte la documentación sobre las expresiones.
Mara: Mediante el uso de condiciones, puede controlar qué cambios se promueven a qué fases. Podemos generar un artefacto de compilación para cualquier cambio con el fin de validar la compilación y confirmar que es correcta. Cuando estamos listos, podemos fusionar esos cambios en una rama de versión y promover esa compilación a la fase de desarrollo.
Incorporación de la fase de pruebas
Mara: Por el momento, tenemos las fases de compilación y desarrollo. ¿Qué viene a continuación?
Amita: ¿Podemos agregar a continuación la fase de pruebas? Parece el lugar adecuado para probar los cambios más recientes.
Mara agrega la fase de pruebas en el dibujo de la pizarra.
Amita: Una cuestión que me preocupa es con qué frecuencia necesito probar la aplicación. Cada vez que Mara o Andy realizan un cambio, recibo una notificación por correo electrónico. Se producen cambios a lo largo de todo el día y nunca sé cuándo debo intervenir. Creo que me gustaría que hubiera una compilación una vez al día, quizás a la hora a la que llego a la oficina. ¿Esto es factible?
Andy: Claro que sí. ¿Por qué no implementamos las pruebas en horario no laborable? Por ejemplo, podemos enviarte una compilación todos los días a las 03:00.
Mara: Me parece estupendo. Siempre podemos desencadenar el proceso manualmente si es necesario. Por ejemplo, podemos desencadenarlo si necesitamos comprobar de inmediato una corrección de errores importante.
Mara actualiza el dibujo para indicar que la compilación pasa de la fase de desarrollo a la fase de pruebas a las 03:00 a. m.
Qué son los desencadenadores
Amita: Perfecto, ya sabemos cómo una fase pasa a otra. Pero ¿cómo controlamos cuándo se ejecuta una fase?
Mara: En Azure Pipelines, podemos usar desencadenadores. Un desencadenador define cuándo se ejecuta una fase. Azure Pipelines proporciona varios tipos de desencadenadores. Tenemos estas opciones:
- Desencadenador de integración continua (CI)
- Desencadenador de solicitud de incorporación de cambios (PR)
- Desencadenador programado
- Desencadenador de finalización de la compilación
Los desencadenadores de CI y de PR permiten controlar qué ramas participan en el proceso general. Por ejemplo, le interesa compilar el proyecto cuando se realiza un cambio en una rama. Un desencadenador programado inicia una implementación en un momento determinado. Un desencadenador de finalización de la compilación ejecuta una compilación cuando otra compilación (por ejemplo, una para un componente dependiente) se completa correctamente. Parece que lo que queremos es un desencadenador programado.
¿Qué son los desencadenadores programados?
Un desencadenador programado usa la sintaxis de cron para hacer que una compilación se ejecute según una programación definida.
En los sistemas UNIX y Linux, cron es una forma popular de programar trabajos de modo que se ejecuten en un intervalo de tiempo establecido o en un momento determinado. En Azure Pipelines, los desencadenadores programados usan la sintaxis de cron para definir cuándo se ejecuta una fase.
Una expresión cron incluye campos que coinciden con determinados parámetros de tiempo. A continuación se indican los campos:
mm HH DD MM DW
\ \ \ \ \__ Days of week
\ \ \ \____ Months
\ \ \______ Days
\ \________ Hours
\__________ Minutes
Por ejemplo, esta expresión cron describe "3 a. m. todos los días": 0 3 * * *
Una expresión cron puede incluir caracteres especiales para especificar una lista de valores o un intervalo de valores. En este ejemplo, el asterisco (*) coincide con todos los valores de los campos Days (días), Months (meses) y Days of week (días de la semana).
Dicho de otra manera, esta expresión cron se lee así:
- En el minuto 0,
- En la tercera hora,
- En cualquier día del mes,
- En cualquier mes,
- En cualquier día de la semana,
- Ejecutar el trabajo
Para especificar 3 a. m. solo de lunes a viernes, se usaría esta expresión: 0 3 * * 1-5
.
Nota:
La zona horaria de las programaciones cron es la hora universal coordinada (UTC), por lo que en este ejemplo, 3 a. m. hace referencia a las 3 a. m. en UTC. En la práctica, es posible que quiera ajustar la hora de la programación cron en lo que respecta al valor UTC, de modo que la canalización se ejecute cuando le interesa a usted y su equipo.
Para configurar un desencadenador programado en Azure Pipelines, necesita una sección schedules
en el archivo YAML. Este es un ejemplo:
schedules:
- cron: '0 3 * * *'
displayName: 'Deploy every day at 3 A.M.'
branches:
include:
- release
always: false
En esta sección schedules
:
cron
especifica la expresión cron.branches
especifica que solo se va a implementar desde la ramarelease
.always
especifica si la implementación se ejecutará de forma incondicional (true
) o solo cuando la ramarelease
haya cambiado desde la última ejecución (false
). Aquí, especificaráfalse
porque solo necesita realizar la implementación cuando la ramarelease
ha cambiado desde la última ejecución.
Cuando Azure Pipelines ejecuta un desencadenador programado, se ejecuta toda la canalización. La canalización también se ejecuta bajo otras condiciones, como cuando se inserta un cambio en GitHub. Para ejecutar una fase solo en respuesta a un desencadenador programado, puede usar una condición que compruebe si el motivo de la compilación es una ejecución programada.
Este es un ejemplo:
- stage: 'Test'
displayName: 'Deploy to the Test environment'
condition: and(succeeded(), eq(variables['Build.Reason'], 'Schedule'))
Esta fase (Test
) solo se ejecuta cuando la fase anterior se lleva a cabo correctamente y la variable de canalización Build.Reason
integrada es igual a Schedule
.
Más adelante en este módulo verá un ejemplo más completo.
Amita: Me gusta la idea. Ni siquiera tengo que seleccionar la versión de forma manual e instalarla. Está listo para mí.
Andy: Además, si necesitamos automatizar más procesos más adelante, podemos hacerlo. No hay nada grabado sobre piedra. La canalización evoluciona a medida que mejoramos y aprendemos.
Incorporación de la fase de ensayo
Tim: Me toca a mí. Necesito una fase para ejecutar más pruebas de esfuerzo. También necesitamos una fase en la que podamos realizar una demostración a los responsables de administración para conseguir su aprobación. Por ahora, podemos combinar estas dos necesidades en una fase que podamos llamar Ensayo.
Andy: Bien dicho, Tim. Es importante tener un entorno de ensayo, o preproducción. Este entorno de ensayo suele ser la última parada antes de que una característica o una corrección de errores lleguen a los usuarios.
Mara agrega Ensayo al dibujo en la pizarra.
Amita: Usamos un desencadenador programado para promocionar los cambios de la fase de desarrollo a la de pruebas. Pero ¿cómo promovemos los cambios de la fase de pruebas a la de ensayo? ¿Esta promoción debe producirse también según una programación?
Mara: Creo que la mejor manera de controlarlo consiste en usar una aprobación de versión. Una aprobación de versión permite promover manualmente un cambio de una fase a la siguiente.
Amita: Parece que es justo lo que necesito. Una aprobación de versión me daría tiempo suficiente para probar los cambios más recientes antes de presentar la compilación a los responsables de administración. Puedo promover la compilación cuando esté lista.
Mara actualiza el dibujo para mostrar que la compilación pasa de la fase de pruebas a la de ensayo solo cuando Amita lo aprueba.
Tim: También podríamos usar aprobaciones de versión para promover de la fase de ensayo a la de producción después de que los responsables de administración nos den su visto bueno. Nunca sé calcular cuánto tiempo se tarda. Después de que nos den su visto bueno, puedo aprobar la versión y promoverla a producción de forma manual. Pero ¿cómo funcionan las aprobaciones de versiones?
Qué son las aprobaciones de versión
Una aprobación de versión es una manera de pausar la canalización hasta que un aprobador acepta o rechaza la versión. Para definir el flujo de trabajo de la versión, puede combinar aprobaciones, condiciones y desencadenadores.
Como recordará, en Creación de una canalización de versión con Azure Pipelines, definió un entorno en la configuración de la canalización para representar el entorno de implementación. A continuación se muestra un ejemplo de la canalización existente:
- stage: 'Deploy'
displayName: 'Deploy the web application'
dependsOn: Build
jobs:
- deployment: Deploy
pool:
vmImage: 'ubuntu-20.04'
environment: dev
variables:
- group: Release
El entorno puede incluir criterios específicos para la versión. Los criterios pueden especificar qué canalizaciones pueden implementarse en ese entorno y qué aprobaciones humanas se necesitan para promover la versión de una fase a la siguiente.
Más adelante en este módulo, definirá el entorno de ensayo y se asignará a sí mismo como aprobador para promover la aplicación web de Space Game de la fase de prueba a la de ensayo.
Automatización a medida
Azure Pipelines le ofrece flexibilidad para automatizar algunas fases mientras sigue controlando manualmente las que no están listas para la automatización.
Tim: Me gusta que podamos definir qué criterios promueven los cambios de una fase a la siguiente. Pero hemos definido algunos criterios manuales en la canalización. Pensaba que en DevOps se automatizaba todo.
Mara: Lo que comentas es muy interesante. En realidad, DevOps automatiza las tareas repetitivas y propensas a errores. A veces, es necesaria la intervención humana. Por ejemplo, obtenemos la aprobación de los responsables de administración antes de lanzar nuevas características. A medida que adquirimos experiencia con nuestras implementaciones automatizadas, podemos automatizar más pasos manuales para acelerar el proceso. Por ejemplo, podemos automatizar más comprobaciones de calidad en la fase de pruebas para que Amita no tenga que aprobar cada compilación.
Tim: Me parece estupendo. Vamos a seguir este plan por ahora y ya veremos cómo podemos acelerar el sistema más adelante.
Amita: Hablando de planes, ¿podemos resumir los pasos siguientes?
El plan
Vamos a revisar el plan del equipo de Tailspin a medida que avanza hacia los pasos siguientes.
Mara: Esta es la canalización de versión que queremos compilar.
Mara señala la pizarra.
Mara: En resumen, los pasos son los siguientes:
- Generar un artefacto de compilación cada vez que se inserta un cambio en GitHub. Este paso se produce en la fase de compilación.
- Promover el artefacto de compilación a la fase de desarrollo. Este paso se produce automáticamente cuando la fase de compilación se realiza correctamente y el cambio se encuentra en la rama de versión.
- Promover el artefacto de compilación a la fase de prueba cada mañana a las 3 a. m. Usamos un desencadenador programado para promocionar el artefacto de compilación automáticamente.
- Promover el artefacto de compilación a la fase de ensayo después de que Amita pruebe la compilación y la apruebe. Usamos una aprobación de versión para promover el artefacto de compilación.
Una vez que los responsables de administración aprueban la compilación, podemos implementar el artefacto de compilación en un entorno de producción.
Amita: ¿Creéis que será difícil? Parece mucho trabajo.
Mara: No creo que eso sea algo demasiado malo. Cada fase es independiente de las demás. Además, las fases son discretas. Cada una tiene su propio conjunto de tareas. Por ejemplo, lo que ocurre en la fase de pruebas se queda en la fase de pruebas.
Cada fase de implementación de la canalización también tiene su propio entorno. Por ejemplo, al implementar la aplicación en la fase de desarrollo o de pruebas, el entorno es una instancia de App Service.
Por último, solo se prueba una versión de cada vez. Nunca cambiamos las versiones en medio de la canalización. Usamos la misma versión en la fase de desarrollo y en la de ensayo, y cada versión tiene su propio número de versión. Si la versión se interrumpe en una de las fases, la corregimos y la volvemos a compilar con un nuevo número de versión. Después, esa nueva versión pasa a través de la canalización desde el principio.
Consideraciones sobre la calidad
Acaba de ver cómo diseña el equipo una canalización que lleva la aplicación desde la fase de compilación hasta la de ensayo. La finalidad de esta canalización no es solo facilitarles las cosas, sino garantizar la calidad del software que entregan a los clientes.
¿Cómo se mide la calidad del proceso de versión? La calidad del proceso de versión no se puede medir directamente. Lo que sí se puede medir es lo bien que funciona el proceso. Si cambia el proceso constantemente, esto podría indicar que hay algún problema. El hecho de que las versiones produzcan errores de forma sistemática en un punto determinado de la canalización también podría ser un síntoma de que hay un problema con el proceso de versión.
¿Se produce siempre un error en las versiones en un día o una hora determinados? ¿Se produce siempre un error después de realizar la implementación en un entorno determinado? Busque estos y otros patrones para descubrir si algunos aspectos del proceso de versión son dependientes o están relacionados.
Una buena forma de llevar un seguimiento de la calidad del proceso de versión consiste en crear visualizaciones de la calidad de las versiones. Por ejemplo, agregue un widget de panel que muestre el estado de cada versión.
Si quiere medir la calidad de la versión en sí misma, puede realizar todo tipo de comprobaciones dentro de la canalización. Por ejemplo, puede ejecutar diferentes tipos de pruebas, como pruebas de carga y pruebas de interfaz de usuario mientras ejecuta la canalización.
El uso de una puerta de calidad es también una excelente manera de comprobar la calidad de la versión. Existen muchas puertas de calidad diferentes. Por ejemplo, las puertas de elementos de trabajo pueden comprobar la calidad del proceso de requisitos. También puede agregar más comprobaciones de seguridad y de cumplimiento. Por ejemplo, ¿cumple con el principio de los cuatro ojos o tiene una rastreabilidad adecuada?
A medida que avance por esta ruta de aprendizaje, verá cómo se ponen en práctica muchas de estas técnicas.
Por último, al diseñar un proceso de versión de calidad, debe considerar qué tipo de documentación o notas de la versión tiene que proporcionar al usuario. Puede ser difícil mantener actualizada la documentación. Tal vez le interese usar una herramienta, como el generador de notas de la versión de Azure DevOps. El generador es una aplicación de funciones que contiene una función desencadenada por HTTP. Al usar Azure Blob Storage, crea un archivo Markdown cada vez que se crea una versión en Azure DevOps.