DevOps
Sugerencia
Este contenido es un extracto del libro electrónico “Architecting Cloud Native .NET Applications for Azure” (Diseño de la arquitectura de aplicaciones .NET nativas en la nube para Azure), disponible en Documentos de .NET o como un PDF descargable y gratuito que se puede leer sin conexión.
"Depende" es la respuesta favorita de cualquier consultor de software al que se le haga una pregunta. No se debe a que los consultores de software sean propensos a no tomar una postura de las cosas. Es porque en el mundo del software no hay una respuesta genuinamente verdadera a ninguna pregunta. No existen valores absolutos sobre lo que está bien y lo que está mal, sino un equilibrio entre opuestos.
Pensemos, por ejemplo, en las dos corrientes más importantes de desarrollo de aplicaciones web: aplicaciones de página única frente a aplicaciones del lado servidor. Por un lado, la experiencia del usuario tiende a ser mejor con las aplicaciones de página única, y la cantidad de tráfico al servidor web se puede reducir, lo que permite hospedarlas con métodos tan sencillos como el hospedaje estático. Pero, por otro, el desarrollo de aplicaciones de página única tiende a ser más lento y más difícil de comprobar. ¿Qué es lo más adecuado en mi caso? Bueno, depende de la situación.
Las aplicaciones nativas de nube no son inmunes a esa misma dicotomía. Tienen ventajas claras en términos de velocidad de desarrollo, estabilidad y escalabilidad, pero administrarlas puede ser bastante más difícil.
Hace años, no era raro que el proceso de traslado una aplicación de la fase de desarrollo a la de producción tardara un mes o incluso más. Las empresas lanzaban software a un ritmo de 6 meses o incluso cada año. No hay más que fijarse en Microsoft Windows para hacerse una idea de la cadencia de las versiones que eran aceptables antes de que llegara la estabilidad de Windows 10. Cinco años pasaron entre Windows XP y Vista, otros tres entre Vista y Windows 7.
Ahora todo el mundo tiene bastante claro que publicar software rápidamente supone para las empresas en vertiginoso crecimiento una ventaja de mercado enorme sobre sus competidores más indolentes. Ese es el motivo por el que ahora las principales actualizaciones de Windows 10 son aproximadamente cada seis meses.
Los procedimientos y patrones que permiten versiones más rápidas y confiables para ofrecer valor a la empresa se conocen en conjunto como DevOps. Constan de una amplia gama de ideas que abarcan todo el ciclo de vida de desarrollo de software, desde la especificación de una aplicación hasta la entrega y el funcionamiento de esa aplicación.
DevOps surgió antes de los microservicios, y es probable que la transición hacia servicios más pequeños y más adecuados a los propósitos no hubiera sido posible sin DevOps para facilitar la publicación y el funcionamiento no ya de una, sino de un sinfín aplicaciones en producción.
Figura 10-1 : DevOps y microservicios
Gracias a unos procedimientos adecuados de DevOps, se puede disfrutar de las ventajas de las aplicaciones nativas de nube sin verse inundado por un aluvión de trabajo para poner en marcha las aplicaciones.
En lo que respecta a DevOps, no existe una fórmula universal. Nadie puede comercializar una solución completa y global para lanzar y operar aplicaciones de alta calidad, ya que cada aplicación es muy diferente del resto. Sin embargo, hay herramientas que pueden hacer que DevOps sea una propuesta mucho menos intimidante. Una de estas herramientas es Azure DevOps.
Azure DevOps
La solera de Azure DevOps viene de lejos. Sus inicios se remontan a cuando Team Foundation Server pasó al modo en línea por primera vez, y se ha sometido a distintos cambios de nombre: Visual Studio Online y Visual Studio Team Services. Con los años, sin embargo, se ha convertido en algo mucho más grande que sus predecesores.
Azure DevOps se divide en cinco componentes principales:
Figura 10-2: Azure DevOps
Azure Repos: administración de código fuente que admite el respetable Control de versiones de Team Foundation (TFVC) y el favorito del sector, Git. Las solicitudes de incorporación de cambios proporcionan una manera de permitir la codificación social fomentando la posibilidad de debatir los cambios a medida que se van realizando.
Azure Boards: proporciona una herramienta de seguimiento de problemas y elementos de trabajo que se esfuerza por permitir a los usuarios elegir los flujos de trabajo que funcionan mejor para cada uno de ellos. Viene con una serie de plantillas preconfiguradas, incluidas las que admiten los estilos de desarrollo SCRUM y Kanban.
Azure Pipelines: sistema de administración de versiones y compilaciones que admite una estrecha integración con Azure. Las compilaciones se pueden ejecutar en varias plataformas, desde Windows a Linux y macOS. Los agentes de compilación se pueden aprovisionar en la nube o en el entorno local.
Azure Test Plans: nadie responsable del control de calidad se quedará atrás con la compatibilidad con pruebas exploratorias y la administración de pruebas que la característica Test Plans ofrece.
Azure Artifacts: fuente de artefactos que permite a las empresas crear sus propias versiones internas de NuGet, npm, etc. Su otra finalidad es actuar como memoria caché de paquetes ascendentes si se produce un error en un repositorio centralizado.
La unidad organizativa de nivel superior de Azure DevOps se conoce como proyecto. Dentro de cada proyecto, los distintos componentes (como Azure Artifacts) se pueden activar y desactivar. Cada uno de estos componentes proporciona diferentes ventajas para las aplicaciones nativas de nube. Los tres más útiles son los repositorios, los paneles y las canalizaciones. Si los usuarios quieren administrar su código fuente en otra pila de repositorio, como GitHub, pero al mismo tiempo seguir sacando partido de Azure Pipelines y de otros componentes, esto es perfectamente posible.
Afortunadamente, los equipos de desarrollo tienen muchas opciones al seleccionar un repositorio. Uno de ellos es GitHub.
Acciones de GitHub
Fundado en 2009, GitHub es un repositorio tremendamente popular basado en web donde hospedar proyectos, documentación y código. Muchas grandes empresas tecnológicas, como Apple, Amazon, Google, así como las empresas al uso, utilizan GitHub. GitHub emplea como base el sistema de control de versiones distribuido de código abierto denominado Git. Además, incorpora su propio conjunto de características, como seguimiento de defectos, solicitudes de características y de incorporación de cambios, administración de tareas y wikis para cada código base.
A medida que GitHub evoluciona, se van agregando también características de DevOps. Por ejemplo, GitHub tiene su propia canalización de integración continua y entrega continua (CI/CD), denominada GitHub Actions
. Acciones de GitHub es una herramienta de automatización de flujos de trabajo sustentada por la comunidad. Permite que los equipos de DevOps se integren con sus herramientas existentes, mezclen y combinen nuevos productos y se conecten a sus ciclos de vida de software, incluidos los asociados de CI/CD existentes.
GitHub tiene más de 40 millones de usuarios, lo que lo convierte en el host más grande del código fuente del mundo. En octubre de 2018, Microsoft compró GitHub. Microsoft ha prometido que GitHub seguirá siendo una plataforma abierta a la que cualquier desarrollador podrá conectarse y ampliarla. Sigue funcionando como una empresa independiente. GitHub ofrece planes de cuentas empresariales, de equipo, profesionales y gratuitas.
Control de código fuente
Organizar el código de una aplicación nativa de nube puede ser difícil. En lugar de una sola aplicación gigante, las aplicaciones nativas de nube tienden a estar formadas por una miríada de aplicaciones más pequeñas que se comunican entre sí. Como ocurre siempre en informática, la mejor forma de organizar el código sigue siendo un interrogante. Hay ejemplos de aplicaciones de éxito que usan diferentes tipos de diseños, pero dos variantes parecen tener la mayor popularidad.
Antes de dirigirse al control de código fuente real en sí, probablemente merezca la pena decidir cuántos proyectos son adecuados. Dentro de un mismo proyecto existe compatibilidad con varios repositorios y canalizaciones de compilación. Los paneles son un poco más complicados, pero en ellos las tareas también se pueden asignar fácilmente a varios equipos dentro de un mismo proyecto. Existe cabida para cientos, incluso miles de desarrolladores, en un mismo proyecto de Azure DevOps. Este sea probablemente el mejor enfoque, ya que proporciona un único lugar para que todos los desarrolladores trabajen y reduzcan la confusión de encontrar una aplicación cuando los desarrolladores no están seguros de en qué proyecto reside.
Dividir el código para microservicios dentro del proyecto de Azure DevOps puede ser algo más complicado.
Figura 10-3: Repositorio único frente a repositorios múltiples
Repositorio por microservicio
A primera vista, este enfoque parece el más lógico para dividir el código fuente para microservicios. Cada repositorio puede contener el código necesario para compilar el microservicio. Las ventajas de este enfoque son claramente perceptibles:
- Las instrucciones para compilar y mantener la aplicación se pueden agregar a un archivo LÉAME en la raíz de cada repositorio. Al desplazarse por los repositorios, es fácil encontrar estas instrucciones, lo que reduce el tiempo de puesta en marcha de los desarrolladores.
- Cada servicio se encuentra en un lugar lógico que se puede encontrar fácilmente sabiendo el nombre del servicio.
- Las compilaciones se pueden configurar fácilmente, de modo que solo se desencadenen cuando se realice un cambio en el repositorio propietario.
- El número de cambios que un repositorio admite se limita al reducido número de desarrolladores que trabajan en ese proyecto.
- La seguridad es fácil de configurar mediante la restricción de los repositorios en los que los desarrolladores tienen permisos de lectura y escritura.
- El equipo propietario puede cambiar la configuración del nivel de repositorio con una discusión apenas mínima con otros usuarios.
Una de las ideas clave detrás de los microservicios es que los servicios se deben estar separados entre sí. Al usar el diseño controlado por dominios para decidir los límites de los servicios, los servicios son los actúan como límites transaccionales. Las actualizaciones de base de datos no deben abarcar varios servicios. Esta colección de datos relacionados se conoce como contexto enlazado. Esta idea viene reflejada por el aislamiento de los datos de microservicio en una base de datos independiente y autónoma del resto de los servicios. Realmente tiene todo el sentido trasladar esta idea hasta el código fuente.
Sin embargo, este enfoque tiene sus problemas. Uno de los problemas de desarrollo más enrevesados de nuestro tiempo es la administración de dependencias. Pensemos en el número de archivos que componen un directorio node_modules
promedio. Una instalación nueva de algo como create-react-app
seguramente traiga con ella miles de paquetes. Resulta difícil responder a la pregunta de cómo administrar estas dependencias.
Si una dependencia se actualiza, los paquetes descendentes también deben actualizar esta dependencia. Por desgracia, eso conlleva un trabajo de desarrollo, de manera que, invariablemente, el directorio node_modules
acaba con varias versiones de un mismo paquete, cada una de ellas una dependencia de algún otro paquete con versiones con una cadencia ligeramente diferente. Al implementar una aplicación, ¿qué versión de una dependencia se debe usar? ¿La versión que está actualmente en producción? ¿La versión que está actualmente en versión beta, pero que probablemente esté en producción en el momento en que el consumidor la lleve a producción? Son problemas difíciles que no se resuelven única y exclusivamente con microservicios.
Hay bibliotecas que dependen de una amplia variedad de proyectos. Al dividir los microservicios con uno en cada repositorio, las dependencias internas se pueden resolver mejor si se usa el repositorio interno, Azure Artifacts. Las compilaciones de bibliotecas insertarán sus versiones más recientes en Azure Artifacts para su consumo interno. El proyecto descendente se debe seguir actualizando manualmente para establecer una dependencia con los paquetes recién actualizados.
Otro inconveniente aparece al mover código entre servicios. Aunque sería genial creer que la primera vez que una aplicación se divide en microservicios todo sale a pedir de boca, la realidad es que rara vez somos tan clarividentes como para no cometer errores al dividir servicios. Por lo tanto, la funcionalidad y el código que la hace posible tendrán que pasar de un servicio a otro: de un repositorio a otro. Al ir un repositorio a otro, el código pierde su historial. Hay muchos casos, especialmente ante una auditoría, donde disponer de un historial completo sobre un fragmento de código tiene un valor incalculable.
El inconveniente último, y más importante, es la coordinación de cambios. En una aplicación de microservicios de verdad, no debe haber dependencias de implementación entre servicios. Implementar los servicios A, B y C en cualquier orden debe ser factible, ya que tienen acoplamiento flexible. Sin embargo, en realidad habrá ocasiones en las que queremos hacer un cambio que afecte a varios repositorios al mismo tiempo. Algunos ejemplos de esto son actualizar una biblioteca para acabar con una vulnerabilidad de seguridad o cambiar un protocolo de comunicación que usan todos los servicios.
Para realizar un cambio entre repositorios, se necesita una confirmación en cada repositorio en orden. Cada cambio en cada repositorio deberá solicitarse y revisarse por separado. Esta actividad puede ser difícil de coordinar.
Una alternativa al uso de muchos repositorios es colocar todo el código fuente junto en un único repositorio gigante donde reside toda la información.
Repositorio único
Con este enfoque, a veces denominado repositorio mono, todo el código fuente de cada servicio se coloca en el mismo repositorio. Inicialmente, este enfoque parece una idea terrible que no hará más que dificultar el uso del código fuente. Sin embargo, hay algunas ventajas reseñables para trabajar de esta manera.
La primera ventaja es que es más fácil administrar las dependencias entre proyectos. En lugar de depender de alguna fuente de artefactos externa, los proyectos pueden importarse directamente entre sí. Esto significa que las actualizaciones son instantáneas y que probablemente las versiones en conflicto se detecten en tiempo de compilación, en la estación de trabajo del desarrollador. Dicho de otro modo: parte de las pruebas de integración se realizarían en las primeras fases de desarrollo.
Al mover código entre proyectos, ahora es más fácil conservar el historial, ya que los archivos se detectarán como movidos, y no como reescritos.
Otra ventaja es que se pueden realizar cambios de gran alcance que crucen los límites de servicio en una misma confirmación. Esta actividad reduce la sobrecarga de tener un sinfín de cambios que revisar uno a uno.
Hay muchas herramientas que pueden realizar análisis estáticos de código para detectar prácticas de programación no seguras o un uso cuestionable de las API. En un mundo con múltiples repositorios, cada repositorio deberá recorrerse mediante iteración para encontrar detectar problemas en ellos. Si se tiene un único repositorio, solo habrá que ejecutar el análisis en un único lugar.
El enfoque de repositorio único también conlleva bastantes inconvenientes. Uno de los más preocupantes es que tener un único repositorio plantea problemas de seguridad. Si el contenido de un repositorio se filtra en un modelo de repositorio por servicio, la cantidad de código perdido es mínima. Con un repositorio único, todo lo que posee la empresa podría perderse. Esto ha sucedido constantemente en el pasado y ha echado al traste muchos esfuerzos de desarrollo de juegos. Tener varios repositorios deja menos área expuesta, que es algo realmente conveniente en la mayoría de los procedimientos de seguridad.
Lo más probable es que, en poco tiempo, el tamaño del repositorio único sea imposible de administrar. Esto acarrea algunas implicaciones de rendimiento interesantes. Puede que sea necesario usar herramientas especializadas como el sistema de archivos virtuales de Git, que se diseñó inicialmente para mejorar la experiencia de los desarrolladores del equipo de Windows.
Con frecuencia, el argumento para usar un repositorio único se reduce a que Facebook o Google usan este método para organizar el código fuente. Si el enfoque es lo suficientemente bueno para estas compañías, seguramente sea adecuado para cualquier otra, cuando la verdad del asunto es que pocas compañías funcionan a la misma escala que Facebook o Google. Los problemas que se producen a esas escalas son diferentes de las cuestiones a las que se enfrentan la mayoría de los desarrolladores. Lo que es bueno para el bazo no tiene por qué serlo para el espinazo.
Al final, cualquiera de las soluciones se puede usar para hospedar el código fuente de los microservicios. Sin embargo, en la mayoría de los casos, la administración y la sobrecarga de ingeniería de poner en funcionamiento un repositorio único no merecen la pena por unas ventajas más bien raquíticas. Dividir el código en varios repositorios fomenta una mejor separación de las adversidades y es un incentivo para la autonomía entre los equipos de desarrollo.
Estructura de directorios estándar
Independientemente del debate de repositorio único frente a repositorios múltiples, cada servicio tendrá su propio directorio. Una de las mejores optimizaciones para permitir que los desarrolladores alternen rápidamente entre proyectos es mantener una estructura de directorios estándar.
Figura 10-4: Estructura de directorios estándar
Cada vez que se crea un proyecto, se debe usar una plantilla que implemente la estructura correcta. Esta plantilla también puede incluir elementos útiles como, por ejemplo, un archivo LÉAME maestro y un elemento azure-pipelines.yml
. En cualquier arquitectura de microservicios, el alto índice de varianza entre proyectos dificulta las operaciones masivas en los servicios.
Hay muchas herramientas que pueden proporcionar plantillas para un directorio completo que contiene varios directorios de código fuente. Yeoman es bastante conocido en el mundo de JavaScript, y hace poco se han publicado en GitHub plantillas de repositorio que proporcionan gran parte de esa misma funcionalidad.
Administración de tareas
La administración de tareas en cualquier proyecto puede ser difícil. Ya desde el inicio hay un sinfín de preguntas que deben responderse sobre qué tipo de flujos de trabajo se van a configurar para garantizar una productividad óptima de los desarrolladores.
Las aplicaciones nativas de nube tienden a ser más pequeñas que los productos de software tradicionales o, al menos, se dividen en servicios más pequeños. El seguimiento de tareas o problemas relacionados con estos servicios sigue siendo tan importante como en cualquier otro proyecto de software. Nadie quiere perderle la pista a ningún elemento de trabajo ni tener que explicar a un cliente que su problema no se ha registrado correctamente. Los paneles se configuran en el nivel de proyecto, pero dentro de cada proyecto, se pueden definir áreas. Esto permite desglosar los problemas en varios componentes. La ventaja de mantener todo el trabajo de toda la aplicación en un mismo lugar es que es fácil mover elementos de trabajo de un equipo a otro a medida que se van entendiendo mejor.
Azure DevOps viene con una serie de plantillas muy populares ya preconfiguradas. En la configuración más básica, todo lo que se necesita saber es qué queda de trabajo pendiente, en qué está trabajando cada uno y qué tareas se están culminando. Tener esta visibilidad en el proceso de creación de software es importante, ya que permitirá priorizar el trabajo e informar al cliente de las tareas completadas. Por supuesto, pocos proyectos de software se quedan en algo tan simple como to do
, doing
y done
; enseguida, se empiezan a agregar pasos como QA
o Detailed Specification
al proceso.
Una de las partes más importantes de las metodologías ágiles es la introspección automática a intervalos regulares. Estas revisiones están pensadas para proporcionar información sobre los problemas a los que se enfrenta el equipo y cómo se pueden mejorar. Con frecuencia, esto significa cambiar el flujo de los problemas y las características a lo largo del proceso de desarrollo. Por lo tanto, es perfectamente lícito ampliar los diseños de los paneles con más etapas.
Las etapas en los paneles no son la única herramienta organizativa. Dependiendo de la configuración del panel, hay una jerarquía de elementos de trabajo. El elemento más granular que puede tener un panel es una tarea. De forma predeterminada, una tarea contiene campos de título y descripción, una prioridad, una estimación de la cantidad de trabajo restante y la posibilidad de vincular a otros elementos de trabajo o elementos de desarrollo (ramas, confirmaciones, solicitudes de incorporación de cambios, compilaciones, etc.). Los elementos de trabajo se pueden clasificar en diferentes áreas de la aplicación y en distintas iteraciones (sprints) para que sea más fácil encontrarlos.
Figura 10-5: Tarea en Azure DevOps
El campo de descripción admite los estilos normales que cabría esperar (negrita, cursiva, subrayado y tachado) permite insertar imágenes. Esto hace que sea una herramienta eficaz para su uso al especificar el trabajo o cualquier error.
Las tareas se pueden agrupar en características, que definen una unidad de trabajo mayor. Las características, a su vez, se pueden agrupar en epopeyas. La clasificación de tareas en esta jerarquía permite saber con mucha más facilidad lo cerca que está de implementarse una característica importante.
Figura 10-6: Elemento de trabajo en Azure DevOps
En Azure Boards existen diferentes tipos de vistas de los problemas. Los elementos que aún no están programados aparecen como trabajo pendiente. Desde ahí, se pueden asignar a un sprint. Un sprint es un plazo de tiempo durante el cual se espera que se complete cierta cantidad de trabajo. Este trabajo puede incluir tareas, pero también la resolución de vales. Un sprint completo se puede administrar desde la sección Sprint del panel. Esta vista refleja cómo va avanzando el trabajo, e incluye un gráfico de evolución que proporciona una estimación en constante actualización de si el sprint se va a realizar correctamente.
Figura 10-7: Panel en Azure DevOps
Con todo esto, queda patente que los paneles de Azure DevOps son bastante poderosos. Para los desarrolladores, hay vistas sencillas que muestran en qué se está trabajando. Para los administradores de proyectos, hay vistas del próximo trabajo por realizar, así como una visión general del trabajo existente. Para los jefes, hay una infinidad de informes sobre los recursos y la capacidad. Por desgracia, las aplicaciones nativas de nube no tienen una varita mágica que acabe con la necesidad de realizar un seguimiento del trabajo. Pero si hay que llevar un seguimiento del trabajo, existen algunos lugares en los que la experiencia es mejor que en Azure DevOps.
Canalizaciones de CI/CD
Posiblemente no haya ningún cambio en el ciclo de vida de desarrollo de software que haya sido tan revolucionario como la aparición de la integración continua (CI) y la entrega continua (CD). Los errores se detectan desde bien pronto gracias a la compilación y realización de pruebas automatizadas en el código fuente de un proyecto en cuanto se introduce un cambio. Antes de la aparición de las compilaciones de integración continua, no era inusual extraer el código del repositorio y comprobar que no superaba las pruebas o que ni siquiera se podía compilar. Esto dio lugar a que se llevara un seguimiento del origen de la rotura de código.
Tradicionalmente, el envío de software al entorno de producción requería una amplia documentación y una lista de pasos. Cada uno de estos pasos debe completarse manualmente en un proceso muy propenso a errores.
Figura 10-8: Lista de comprobación
La hermana de la integración continua es la entrega continua, por la que los paquetes recién creados se implementan en un entorno. El proceso manual no puede escalar para que sea igual de rápido que el de desarrollo, por lo que la automatización pasa a tener una papel más importante. Las listas de comprobación se reemplazan por scripts que pueden ejecutar las mismas tareas de forma más rápida y precisa que cualquier persona.
El entorno de entrega de la entrega continua puede ser un entorno de prueba o, como están haciendo en muchas empresas tecnológicas importantes, el propio entorno de producción. Esto último requiere una inversión en pruebas de alta calidad que puedan dar confianza de que un cambio no interrumpirá la producción de cara a los usuarios. De la misma manera que la integración continua detectaba problemas en el código, una entrega continua temprana detecta con prontitud problemas en el proceso de implementación.
La importancia de automatizar el proceso de compilación y entrega se acentúa en las aplicaciones nativas de nube. Las implementaciones se producen con más frecuencia y en más entornos, por lo que hacerlas manualmente es imposible.
Compilaciones de Azure
Azure DevOps proporciona un conjunto de herramientas que hacen que la integración y la implementación continuas sean más fáciles de realizar que nunca. Estas herramientas se incluyen en Azure Pipelines. La primera de ellas son las compilaciones de Azure, que es una herramienta para ejecutar definiciones de compilación basadas en YAML a escala. Los usuarios pueden usar sus propios equipos de compilación (lo cual es ideal si la compilación requiere un entorno configurado minuciosamente), o bien usar un equipo de un grupo de máquinas virtuales que se actualiza constantemente y que se hospeda en Azure. Estos agentes de compilación hospedados vienen preinstalados con una amplia gama de herramientas de desarrollo no ya para el desarrollo de .NET, sino para todos los demás, desde Java a Python, pasando por el desarrollo de iPhone.
DevOps incluye una amplia gama de definiciones de compilación integradas que se pueden personalizar para cualquier compilación. Las definiciones de compilación se definen en un archivo denominado azure-pipelines.yml
, y se insertan en el repositorio para que las versiones se puedan controlar junto con el código fuente. Esto hace que resulte mucho más fácil realizar cambios en la canalización de compilación en una rama, ya que los cambios se pueden comprobar solo en esa rama. La figura 10-9 es un ejemplo de azure-pipelines.yml
para compilar una aplicación web de ASP.NET en el marco completo.
name: $(rev:r)
variables:
version: 9.2.0.$(Build.BuildNumber)
solution: Portals.sln
artifactName: drop
buildPlatform: any cpu
buildConfiguration: release
pool:
name: Hosted VisualStudio
demands:
- msbuild
- visualstudio
- vstest
steps:
- task: NuGetToolInstaller@0
displayName: 'Use NuGet 4.4.1'
inputs:
versionSpec: 4.4.1
- task: NuGetCommand@2
displayName: 'NuGet restore'
inputs:
restoreSolution: '$(solution)'
- task: VSBuild@1
displayName: 'Build solution'
inputs:
solution: '$(solution)'
msbuildArgs: '-p:DeployOnBuild=true -p:WebPublishMethod=Package -p:PackageAsSingleFile=true -p:SkipInvalidConfigurations=true -p:PackageLocation="$(build.artifactstagingdirectory)\\"'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
- task: VSTest@2
displayName: 'Test Assemblies'
inputs:
testAssemblyVer2: |
**\$(buildConfiguration)\**\*test*.dll
!**\obj\**
!**\*testadapter.dll
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
- task: CopyFiles@2
displayName: 'Copy UI Test Files to: $(build.artifactstagingdirectory)'
inputs:
SourceFolder: UITests
TargetFolder: '$(build.artifactstagingdirectory)/uitests'
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact'
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)'
ArtifactName: '$(artifactName)'
condition: succeededOrFailed()
Figura 10-9: Ejemplo de código azure-pipelines.yml
Esta definición de compilación usa una serie de tareas integradas que hacen que la creación de compilaciones sea tan simple como crear una construcción de Lego (más fácil que el gigantesco Halcón Milenario, claro). Por ejemplo, la tarea NuGet restaura paquetes NuGet, mientras que la tarea VSBuild llama a las herramientas de compilación de Visual Studio para materializar la compilación real. Hay un sinfín de tareas diferentes disponibles en Azure DevOps, y una infinidad más que la comunidad se encarga de mantener. Es probable que, independientemente de las tareas de compilación que quiera ejecutar, alguien ya haya creado una.
Las compilaciones se pueden desencadenar manualmente, mediante una comprobación, como consecuencia de una programación o una vez finalizada otra compilación. En la mayoría de los casos, conviene compilar en cada comprobación. Las compilaciones se pueden filtrar de forma que algunas compilaciones se ejecuten en diferentes partes del repositorio o en ramas diferentes. Esto posibilita escenarios como ejecutar compilaciones rápidas con pocas pruebas en las solicitudes de incorporación de cambios, o ejecutar un conjunto de regresión completo en el tronco cada noche.
El resultado final de una compilación es una colección de archivos que se conoce como artefacto de compilación. Estos artefactos se pueden pasar al paso siguiente del proceso de compilación o agregarlos a una fuente de Azure Artifacts, de forma que otras compilaciones puedan consumirlos.
Versiones de Azure DevOps
Las compilaciones se encargan de compilar el software en un paquete que puede enviarse, pero los artefactos se deben seguir insertando en un entorno de prueba para completar la entrega continua. Para ello, Azure DevOps usa una herramienta independiente de versiones. La herramienta de versiones usa la misma biblioteca de tareas que había disponible para la compilación, pero incorpora un concepto de "fases". Una fase es un entorno aislado en el que se instala el paquete. Por ejemplo, un producto podría hacer uso de un entorno de desarrollo, un entorno de control de calidad y un entorno de producción. El código se entrega continuamente en el entorno de desarrollo, donde se pueden ejecutar pruebas automatizadas en él. Una vez que las pruebas se superan, la versión se traslada al entorno de control de calidad para realizar pruebas manuales. Por último, el código se inserta en producción, donde es visible para todos.
Figura 10-10: Canalización de versión
Cada fase de la compilación se puede desencadenar automáticamente previa finalización de la fase anterior. Sin embargo, en muchos casos esto no es lo más deseable. Mover código a producción podría requerir la aprobación de alguien. La herramienta de versiones admite esto, ya que permite la presencia de aprobadores en cada paso de la canalización de versión. Se pueden configurar reglas para que una persona o un grupo de personas en concreto deba aprobar una versión para que pueda pasar en producción. Estas "puertas" permiten comprobaciones manuales de calidad y, asimismo, que se cumpla cualquier requisito normativo relacionado con el control de lo que pasa a producción.
Canalizaciones de compilación para todos
Configurar muchas canalizaciones de compilación no conlleva coste extra alguno, por lo que es conveniente tener al menos una canalización de compilación por microservicio. Lo ideal es que los microservicios se puedan implementar de manera independiente en cualquier entorno, de ahí que resulte perfecto que cada microservicio se pueda liberar a través de su propia canalización, sin la necesidad de un montón de código no relacionado. Cada canalización puede tener su propio conjunto de aprobaciones, lo que permite variaciones en el proceso de compilación de cada servicio.
Control de versiones
Un inconveniente de usar la funcionalidad de versiones es que no se puede definir en un archivo azure-pipelines.yml
protegido. Y hay muchas razones por las que conviene hacer esto, desde tener definiciones de la versión en cada rama hasta incluir un archivo maestro de la versión en la plantilla de proyecto. Por suerte, estamos trabajando para trasladar algunas de las fases de compatibilidad al componente de compilación. Esto se conoce como compilación de varias fases, y la primera versión ya está disponible.