Herramientas de desarrollo: Azure Pipelines (CD)

La imagen muestra el logo de Azure Pipelines

Todo lo que empieza tiene un final, y hoy es el final de la serie sobre “Integración Continua y Despliegue Continuo” (CI/CD). Hoy vamos a hablar sobre cómo hacer una integración y despliegue completos sobe Azure Web Apps de un proyecto ASP NET Core con Azure Pipelines.

Para poder hacerlo, lo primero que necesitamos es aprovisionar los recursos en Azure, y hay varias maneras de hacerlo. Hablo de ellas en Variable Not Found:

En este caso, vamos a utilizar el sistema de aprovisionamiento desde AzureRM para crear el plan, la webapp y una segunda ranura que será donde hagamos el deploy de nuestro proyecto para cambiarlo después a producción. Utilizando este sistema vamos a conseguir que nuestro proyecto esté siempre online incluso mientras se hace la publicación, ya que vamos a publicar sobre una segunda ranura y solo después de que nuestro proyecto este publicado y online, haremos el cambio entre ranuras (internamente hace un cambio de DNS, por lo que el cambio es instantáneo). Una vez dicho esto, ¡vamos a meternos en faena!

Creando la integración continua

Dentro de esta serie, publique como hacer la integración de nuestro proyectos en Azure Pieplines, así que nos vamos a saltar esa parte. El fichero .yml podría ser algo como esto:

# Configuramos el pool con la imagen
pool:
  vmImage: 'ubuntu-16.04'

# Variables de trabajo
variables:
  buildConfiguration : 'Release'
  project : '**/*.csproj'
 
# Pasos
steps:
# Restore Nuget
- task: [email protected]
  displayName: Restore
  inputs:
    command: restore
    projects: '$(project)'
# Compilamos el proyecto
- task: [email protected]
  displayName: Build
  inputs:
    projects: '$(project)'
    arguments: '--configuration $(buildConfiguration)'

# Aquí van las pruebas unitarias
# ------

# Publicamos la solución indicándole que queremos zipearla
- task: [email protected]
  displayName: Publish
  inputs:
    command: publish
    publishWebProjects: True
    arguments: '--configuration $(buildConfiguration) --output $(build.artifactstagingdirectory)'
    zipAfterPublish: True

# Subimos los artefactos
- task: [email protected]
  displayName: 'Publish Artifact'
  inputs:
    PathtoPublish: '$(build.artifactstagingdirectory)'

Creando el despliegue continuo en Azure Pipelines

Con esto, ya tenemos nuestra integración funcionando y subiendo los artefactos, ahora solo falta el despliegue. La manera más sencilla quizás sea desde la propia ventana de resultados de la integración, pulsando sobre el botón Release:

La imagen muestra el botón "Release" dentro de la ventana de resultados de la integración de Azure Pipelines

Eso nos va a lanzar a una nueva página donde vamos a crear el pipeline. Por defecto nos va a dar muchas opciones, pero vamos a hacerlo desde 0, así que elegimos “empty job”:

La imagen indica "empty job" en el asistente de Azure Pipelines

Esto va a crear un job vacío, en el cual vamos a añadir todos los pasos que necesitamos. En nuestro caso son 3:

  1. Aprovisionar los recursos.
  2. Desplegar nuestro proyecto a una ranura de staging
  3. Cambiar las ranuras de staging y producción.

Para eso, vamos a pulsar sobre el Stage 1, para añadir tareas al job:

La imagen muestra el enlace que hay que pulsar para abrir la configuración

Esto nos lleva a las tareas que tiene el job, y ahí vamos a pulsar en “+” para añadir las nuestras:

La imagen señala el botón "+" para añadir tareas al job

Aquí vamos a tener que añadir 3 tareas, una por cada una de las 3 acciones que queremos hacer. La primera, será una tarea de aprovisionamiento, así que vamos a la pestaña “Deploy” y seleccionamos “Azure Resurce Group Deployment”:

La imagen muestra el recurso Azure Resurce Group Deployment

El siguiente que vamos a introducir, es “Azure App Service Deploy”:

La imagen muestra el recurso Azure App Service Deploy

Y por último, vamos a añadir “Azure App Service Manage”:

La imagen muestra el recurso Azure App Service Manage

Una vez añadidos los 3 recursos, nos quedará algo como esto:

La imagen muestra el resultado de añadir las 3 tareas al job de Azure Pipelines

Ahora vamos a configurarlos, para ello, vamos al primero y al pulsar sobre él, nos aparecen sus opciones:

La imagen enseña el asistente de aprovisionamiento de Azure Pipelines

Simplemente, tenemos que seleccionar nuestra suscripción y grupo de recursos donde queremos que se añadan, así como la zona para el despliegue. Después le indicamos que el template va a estar en una url y le pasamos las direcciones “raw” de los ficheros. Por ejemplo, en GitHub existe un botón para ir al fichero raw y luego basta con copiar la url:

La imagen señala el botón raw de github

Como se puede intuir, estos ficheros los genera el proyecto de “Grupo de Recursos de Azure” que hemos creado en Visual Studio, y los parámetros que hayamos puesto al validar nuestro script de aprovisionamiento:

La imagen señala los valores indicados en Visual Studio para el aprovisionamiento

Con esto, ya tenemos listo el aprovisionamiento de recursos. Ahora, vamos a configurar el despliegue de nuestro proyecto. Azure Pipelines nos da un asistente como este:

La imagen señala los campos del asistente de publicación

Aquí tenemos que tener especial cuidado, ya que vamos a “ciegas”. Me explico, como los recursos aún no están creados en Azure, vamos a tener que introducir los datos a mano porque los desplegables van a estar vacíos. Para eso, vamos a escribir los datos que hemos puesto al validar nuestro script de despliegue. También hay que tener cuidado con cuál es el nombre real que aplica nuestro despliegue, por ejemplo, la plantilla que hemos seleccionado para el aprovisionamiento añade “Portal” al nombre que hallamos elegido:

"webAppPortalName": "[concat(parameters('baseResourceName'), 'Portal')]"

Lo mismo pasa con la ranura (Slot) al que vamos a desplegar, realmente no existe mientras estamos configurando el despliegue, así que la escribimos a mano.

Este “problema” se puede solucionar si hacemos el aprovisionamiento de recursos antes de configurar esta parte. Si los recursos ya están creados, aparecerán todos correctamente en los desplegables.

Una vez que lo hemos terminado de configurar, lo último que nos falta es cambiar la ranura de “Staging” por la de “Produccion”, de manera que se haga un despliegue completo, y nuestro proyecto este accesible en producción. Para eso, configuramos el último apartado:

La imagen señala los campos del asistente de swap de ranuras

Igual que en el caso anterior, vamos a “ciegas” porque los recursos aun no existen. Sabiendo esto, escribimos manualmente el nombre del servicio que estamos operando, el grupo de recursos al que pertenece, y la ranura que queremos poner en producción. Puede que no queramos poner en producción la ranura y solo cambiarla por otra, esto lo podríamos conseguir si desmarcamos la casilla “Swap whit Production”, lo cual nos hace aparecer un segundo campo para rellenar con la ranura por la que queremos cambiar.

Una vez que hemos configurado las 3 tareas, pulsamos sobre el botón “Save” para guardar nuestro job:

La imagen muestra el botón de guardar el job

Ya tenemos nuestro despliegue listo. Ahora cada vez que hagamos un cambio en el repositorio, automáticamente se publicará. Antes de hacer cambios, podemos comprobar que nuestro pipeline funciona pulsando sobre el botón “Create a release”:

El la imagen señala el botón para lanzar un ciclo de release

Esto lanzará un ciclo de despliegue como podemos ver en la pestaña “Releases”:

La imagen muestra la nueva release en marcha

Una vez termine, si vamos a https://{nombre de nuestro servicio}.azurewebsites.net debería estar disponible nuestro proyecto. Si añadimos algún cambio y lo subimos a github, de que acabe la integración y el despliegue deberíamos poder ver en la url el cambio que hemos hecho. Por ejemplo:

La imagen muestra el resultado de hacer un cambio en el repositorio

Como es habitual en este tipo de entradas, el código esta disponible en GitHub para poder consultarlo.

Aviso a navegantes

No es oro todo lo que reluce, poner webs en producción de manera automática puede ser peligroso ya que puede haber errores de interfaz gráfica (o cualquier otro tipo) que no se hayan testeado mediante pruebas automáticas. Una mejor practica puede ser hacer despliegue sobre una ranura de “develop” donde poder comprobar la funcionalidad completa de la web, y una vez que estamos seguros de que todo esta bien, hacer el swap desde la ranura que hemos testeado de manera que los posibles errores estén comprobados ya.

Si por el contrario, lo único que estamos publicando es una API RESTful que hemos desarrollado con buenas prácticas y que no tiene estado (no hay datos que son necesarios mantener en memoria entre peticiones), no debería ser un problema hacer despliegue continuo automáticamente, ya que las peticiones de aquello que consuma nuestro servicio se lanzaran contra la versión establecida, y solo cuando cambiemos los clientes a la nueva versión se aplicará el cambio.

Conclusión sobre CI/CD

Aunque quizás el despliegue continuo a producción es algo idílico y difícil de alcanzar por la cantidad de posibles problemas y errores que podemos introducir, este solo es el paso final de una serie de buenas prácticas y metodologías de trabajo. Quedarse en cualquiera de los puntos anteriores al despliegue en producción sigue siendo algo muy válido, ya que estamos haciendo que no exista una dependencia tan fuerte entre código y programador, lo cual siempre facilita el trabajo y la mantenibilidad de los proyectos. Esto añadido a una buena política de pruebas unitarias,
evita los problemas asociados a realizar proyectos en equipos de trabajo, donde cada persona no tiene por qué conocer el alcance del trabajo del resto. Con la variedad de proveedores que ofrecen estos servicios, no buscar uno que se adapte a nuestras necesidades e implementarlo puede ser una decisión que a la larga salga cara.

Cómo medir tiempos en C# y .Net con precisión

Cómo medir tiempos en .Net (con precisión)

Hace unos días mientras comíamos los compañeros de trabajo, salió el tema sobre lo preciso o impreciso que puede ser medir tiempos desde el sistema operativo. Mis compañeros (programadores C/C++ en su mayoría), tenían dudas sobre lo preciso que puede ser medir tiempos desde un entorno administrado. Nunca he necesitado tales precisiones, así que era totalmente novato en ese tema, pero hoy que he tenido un momento, he investigado un poco sobre cómo medir tiempos en C# y .Net con precisión:

La clase Stopwatch

En C#, el ecosistema .Net nos provee de una herramienta ya lista para utilizar con una precisión de microsegundo (si el hardware lo permite) dentro del namespace “System.Diagnostics“. Está herramienta es la clase Stopwatch . Esta disponible en todos los entornos a partir de .Net Framework 2.0 (incluyendo .Net Core y .Net Standard), así que si no la conocías, es una buena idea añadirla al cinturón.

Para usarla, simplemente es necesario instanciarla y llamar a sus métodos Start y Stop, después nos devuelve un TimeSpan con el tiempo transcurrido:

Stopwatch timeMeasure = new Stopwatch();
timeMeasure.Start();
int operacion = 10 / 4;
timeMeasure.Stop();
Console.WriteLine($"Tiempo: {timeMeasure.Elapsed.TotalMilliseconds} ms");

Pero no se queda ahí, la clase también tiene dos propiedades estáticas (ReadOnly) que nos pueden dar mucha información sobre la medida:

  • IsHighResolution (bool): Nos va a indicar si el hardware permite mediciones de alta precisión.
  • Frequency (long): Nos va a dar el valor de ticks (mediciones) por segundo que soporta nuestro hardware.

Con estos dos datos extra, podemos saber si nuestra medida es de alta precisión, e incluso la resolución de la medida:

using System.Diagnostics;
//------
Stopwatch timeMeasure = new Stopwatch();
timeMeasure.Start();
int operacion = 10 / 4;
timeMeasure.Stop();
Console.WriteLine($"Tiempo: {timeMeasure.Elapsed.TotalMilliseconds} ms");
Console.WriteLine($"Precision: {(1.0 / Stopwatch.Frequency).ToString("E")} segundos");
if (Stopwatch.IsHighResolution)
    Console.WriteLine("Alta precisión");
else
    Console.WriteLine("Baja precisión");

Después de probarlo, ya puedo decirles a mis compañeros que se pueden medir tiempos en C# y .Net con precisión y sin nada que envidiar a otros lenguajes de más bajo nivel como C/C++.

Herramientas de desarrollo: AppVeyor

La imagen muestra el logo de Nuget y AppVeyor

Hoy vengo con otra entrada de esas que me hacen ilusión, y es que volvemos a colaborar con José de VariableNotFound, esta vez para hablar de cómo crear paquetes Nuget (con SourceLink, por supuesto), sobre el repositorio Nuget.org, y sobre como publicar nuestros paquetes automáticamente con un sistema de CI/CD como AppVeyor.

En la primera parte de la entrada, que se ha publicado en VariableNotFound hablábamos de cómo crear un paquete Nuget con SourceLink y publicarlo en Nuget.org (si no sabes lo que es, deberías echarle un vistazo a la entrada hablando del tema). Como comentábamos al final, hacerlo a mano se hace tedioso, por eso es una buena idea utilizar una herramienta de Despliegue Continuo (CD por sus siglas en ingles).

En su momento publique en el blog de compañero de trabajo unas entradas sobre AppVeyor en las que explicaba como añadir AppVeyor a nuestro repositorio. Hoy, vamos a continuar aquella entrada añadiendo el CD hacia Nuget.org (aunque repasemos la parte que es común).

En primer lugar, nos tendremos que crear una cuenta en AppVeyor, algo que es muy fácil si tenemos cuenta de GitHub. Simplemente vamos a “Sign in” y seleccionamos iniciar con GitHub (u otros servicios).

Añadiendo AppVeyor a nuestro repositorio

Login AppVeyor

Con nuestra cuenta creada, añadimos el proyecto con el botón “NEW PROJECT”

Señala el botón de crear proyecto en AppVeyor

Y después, seleccionamos el proyecto que queremos, y pulsamos sobre el botón “Add”

Con esto, nuestro proyecto se compilará automáticamente cada vez que hagamos un “push” al repositorio y ejecutará las pruebas unitarias, aunque obviamente fallará porque aún no hemos configurado que tiene que hacer.

Para configurarlo, vamos a la pestaña “settings”

En primer lugar, en el menú “General” vamos a tener que bajar hasta casi el final, y seleccionar “On” en la opción “.NET Core .csproj patching “, con esto, vamos a conseguir cambiar la versión del paquete a la que queramos. Recordemos, qué si intentamos resubir un paquete con la misma versión que uno que ya existe, fallará. Como lo que nos interesa es que la versión se actualice y no se repita, vamos a utilizar la variable de AppVeyor “version“, la cual depende de los campos “Next build number” y “Build version format“, siendo el primero el número de compilación (es autoincremental) y el segundo la versión “Major” que queremos que tenga nuestro paquete, una vez hecho, pulsamos sobre el botón “Save” para guardar los cambios.

La imagen muestra como activar el parcheo de versión del paquete Nuget en AppVeyor

Pasamos al menú “Environment”, en este solo vamos a necesitar seleccionar la imagen de Visual Studio 2017 y pulsar en el botón “Save”.

La imagen muestra como seleccionar Visual Studio 2017 en AppVeyor

Ahora vamos con el menú “Build”, aquí le vamos a indicar como tiene que hacer la compilación, además de ejecutar scripts para preparar los prerrequisitos que tengamos, por ejemplo, hacer un “nuget restore”. Esta es quizás la parte más compleja del trabajo.

Lo primero, seleccionamos la configuración “Release” escribiéndolo en el apartado “Configuraton” al inicio, y después hay que bajar casi hasta el final para seleccionar que queremos hacer un paquete (“Package NuGet projects”), que queremos incluir los símbolos para SourceLink (“Include NuGet symbol packages”), y por último y muy importante (si no, no compilará), añadir un comando previo a la compilación que ejecutará un “nuget restore”.

La imagen muestra la configuración del menú "Build" de AppVeyor

Con esto, tras pulsar en “Save”, si hacemos cambios sobre el repositorio, podemos ver que ejecuta toda la integración continua.

La imagen muestra la salida de la consola de AppVeyor

Configurando el despliegue en Nuget.org desde AppVeyor

Por último, vamos al menú “Deployment”, y añadimos un nuevo despliegue pulsando sobre “Add deployment” y seleccionando Nuget en la lista desplegable. Ahora, simplemente introducimos nuestra API Key, le decimos que no queremos publicar un paquete de símbolos, y por ultimo y MUY IMPORTANTE, como no queremos que se haga deploy de cada push, añadimos la condición de que para hacer un deploy, tiene que ser un push con un “tag”, esto lo conseguimos poniendo como condición que la variable de AppVeyor “APPVEYOR_REPO_TAG” sea true.

La imagen muestra la configuración de el despliegue en AppVeyor

Si con esto pulsamos sobre salvar, y mandamos un nuevo tag al repositorio, veremos cómo al igual que si lo enviábamos manualmente, aparece disponible en Nuget.org. Como siempre, dejo el código fuente en GitHub.

En próximas entradas, hablaremos sobre como utilizar Azure Pipelines como CD además de CI y las ventajas que tiene cerrar el ciclo CI/CD.