Llega el fin de las máquinas Tier-1 gestionadas por Microsoft, y esto nos deja sin la capacidad de poder sincronizar la DB o ejecutar tests, a no ser que despleguemos una nueva máquina de build en nuestra suscripción o la de nuestro cliente. Por supuesto esto puede traer preocupación por los costes de esta máquina, y para eso tenemos Azure DevTest Labs.

He escrito este post gracias a la sesión de Joris de Gruyter en la pasada DynamicsCon: Azure Devops Automation for Finance and Operations Like You’ve Never Seen! Y también ha habido bastante investigación y (un monton de) prueba y error por mi parte hasta que todo ha funcionado.

Azure DevTest Labs
Configurando la VM de Build en Azure DevTest Labs

Si quieres saber más sobre builds, releases y el ALM de desarrollo de Dynamics 365 puedes leer mi guía sobre MSDyn365 y Azure DevOps ALM.

Pero primero…

Lo que enseño en este post no es una guía paso a paso perfecta. Hay una alta probabilidad que si haces exactamente lo que yo he hecho no obtengas el mismo resultado. Pero es una buena guía para empezar y que hagas un poco de investigación y aprendas.

Azure DevTest Labs

Azure DevTest Labs es un servicio/herramienta de Azure que nos permite desplegar máquinas virtuales e integrarlas con pipelines de Azure DevOps, y otras cosas, pero lo que voy a explicar es sólo la parte de la máquina virtual y pipeline.

¿Qué es lo que voy a contar aquí? Cómo preparar una imágen VHD de Dynamics 365 Finance and Operations para ser usada como base para crear una máquina de build desde un pipeline de Azure DevOps, compilar nuestro código, sincronizar la base de datos, ejecutar tests, e incluso desplegar los reports, generar el deployable package y borrar la máquina.

Obtener y prepara el VHD

Esto es de largo la parte más tediosa de todo el proceso porque necesitamos descargar 11 ZIPs desde la Shared Asset Library de LCS, y todos sabemos lo rápido que bajan las cosas desde LCS.

¿Cómo se descargan las cosas desde LCS?

Así que para ir más rápidos podemos crear un blob en una cuenta de almacenamiento de Azure y, una vez más, usar las d365fo.tools de Mötz Jensen y su comando Invoke-D365AzCopyTransfer. Sólo hay que ir a LCS, hacer click en el botón «Generate SAS link» para cada archivo, usarlo como el parámetro de origen en el comando y la URL SAS de tu blob en el de destino. Una vez tenemos todos los archivos en el blob los podemos descargar en nuestra máquina local a una velocidad más decente.

Cuando tenemos el VHD descomprimido tenemos que cambiar su tipo de Dynamic a Fixed usando este comando de PowerShell:

Convert-VHD –Path VHDLOCATION.vhd –DestinationPath NEWVHD.vhd –VHDType Fixed

La razón para hacer esto es que no podemos crear una VM de Azure a partir de un VHD de tamaño dinámico. Y me costó unos cuantos intentos descubrir esto 🙂

Crear una cuenta de DevTest Labs

Para hacer esto necesitáis una cuenta de Azure. Si no tenéis una podéis conseguir una gratuita con 180 euros de crédito (200 dólares) para usar en 30 días, y muchos otros servicios gratuitos durante 12 meses.

Buscamos DevTest Labs en la barra superior y creamos un nuevo DevTest Lab. Una vez creado abrimos los detalles y deberíamos ver algo como esto:

Azure DevTest Labs
Azure DevTest Labs

Hacemos clic en el menú «Configuration and policies» en la parte inferior de la lista y bajamos hasta ver la sección «Virtual machine bases»:

DevTest Labs custom VHD image
Imagen personalizada de DevTest Labs

Y ahora viene la segunda parte más divertida del proceso: tenemos que subir los 130GB del VHD a un blob! Así que hacemos clic en el botón «Add» en la parte superior y en el nuevo diálogo que se abrirá seleccionamos «Upload a VHD using PowerShell». Esto nos generará un script de PowerShell para subir el VHD al blob de DevTest Labs. Por ejemplo:

<#
Generated script to upload a local VHD to Azure.
WARNING: The destination will be publicly available for 24 hours, after which it will expire.
          Ensure you complete your upload by then.
Run the following command in a Azure PowerShell console after entering
the LocalFilePath to your VHD.
#>
Add-AzureRmVhd -Destination "https://YOURBLOB.blob.core.windows.net/uploads/tempImage.vhd?sv=2019-07-07&st=2020-12-27T09%3A08%3A26Z&se=2020-12-28T09%3A23%3A26Z&sr=b&sp=rcw&sig=YTeXpxpVEJdSM7KZle71w8NVw9oznNizSnYj8Q3hngI%3D" -LocalFilePath "<Enter VHD location here>"
DevTest Labs custom image upload
Subida del VHD a DevTest Labs

Como alternativa también podemos usar el Azure Storage Explorer como podemos ver en la imagen de la izquierda.

Habría que subirlo al blob uploads.

Cualquiera de los dos métodos es válido para subir el VHD y no sé si alguno de los dos es más rápido que el otro..

Una vez el VHD está subido abrimos la opción «Custom images» de nuevo y deberíamos ver el VHD en el desplegable:

DevTest Labs custom image
Imagen personalizada en DevTest Labs

Le damos un nombre y hacemos clic en OK.

Lo que tenemos ahora es una base para una máquina de desarrollo de Dynamics 365 Finance and Operations que tenemos que preparar para poder usar como máquina de build.

Creando la VM

Tenemos lo esencial, un VHD listo para usar de base para crear la VM en Azure. Nuestro siguiente paso tiene que ser encontrar una forma de hacer el despliegue de esta máquina predecible y automatizable. Conseguiremos esto gracias a los templates ARM de Azure.

Volvemos a la página inicial de DevTest Labs y hacemos clic en el botón «Add», en la opción «Choose base» seleccionamos la base que acabamos de crear, y en la siguiente pantalla el enlace «Add or Remove Artifacts»:

Add artifacts to the VM
Añadimos artefactos a la VM

Buscamos WinRM, seleccionamos «Configure WinRM», y en la siguiente pantalla ponemos «Shared IP address» como nombre de host y hacemos clic en «Add».

Nota: si al ejecutar la maquina los artefactos no se pueden ejecutar comprobad si el Azure VM Agent está instalado en el VHD base. Gracias a Joris por señalar este problema!

Configurar servicio del agente de Azure DevOps

Opción A: usar un artefacto

Actualización: gracias a Florian Hopfner por recordarme esto porque lo había olvidado… Si elegís la Opción A para instalar el servicio del agente tenemos que hacer unas cosas antes!

Lo primero es ejecutar unos scripts de PowerShell que crearán un entradas en el registro y variables de entorno. Vamos a C:\DynamicsSDK y ejecutamos esto:

Import-Module $(Join-Path -Path "C:\DynamicsSDK" -ChildPath "DynamicsSDKCommon.psm1") -Function "Write-Message", "Set-AX7SdkRegistryValues", "Set-AX7SdkEnvironmentVariables"
Set-AX7SdkEnvironmentVariables -DynamicsSDK "C:\DynamicsSDK"
Set-AX7SdkRegistryValues -DynamicsSDK "c:\DynamicsSDK" -TeamFoundationServerUrl "https://dev.azure.com/YOUR_ORG" -AosWebsiteName $AosWebsiteName "AosService"

El primer comando cargará las funciones y hará que las podamos ejecutar por línea de comando, los otros dos crean las entradas del registro y las variables de entorno.

Ahora tenemos que añadir un artefacto para el servicio del agente de Azure DevOps. Esto configurará el servicio del agente en la VM cada vez que se despliegue la máquina. Buscamos «Azure Pipelines Agent» y hacemos clic, veremos esto:

DevTest Labs Azure DevOps Agent
DevTest Labs Azure DevOps Agent

Tenemos que rellenar algo de información:

En «Azure DevOps Organization Name» tenemos que poner el nombre de la organización. Por ejemplo si vuestra URL de AZDO es https://dev.azure.com/blackbeltcorp tenemos que poner blackbeltcorp.

En «AZDO Personal Access Token» tenemos que poner el token generado desde Azure DevOps.

En «Agent Name» le damos un nombre al agente, como por ejemplo DevTestAgent. Y en «Agent Pool» un nombre para el pool, como DevTestPool o uno que ya exista como Default.

En «Account Name» pondremos el mismo usuario que tendremos luego en nuestra pipeline. Recordad esto. Y en «Account Password» su password. Usar secrets con un KeyVaul es mejor, pero no lo voy a explicar aquí.

Y, para acabar, ponemos «Replace Agent» a true.

Opción B: Configurar el agente de Azure DevOps Agent en la VM

Para hacer esto tendremos que crear una VM a partir de la base que tenemos y después ir a C:\DynamicsSDK y ejecutar el script SetupBuildAgent con los parámetros necesarios:

SetupBuildAgent.ps1 -VSO_ProjectCollection "https://dev.azure.com/YOUR_ORG" -ServiceAccountName "myUser" -ServiceAccountPassword "mYPassword" -AgentName "DevTestAgent" -AgentPoolName "DevTestPool" -VSOAccessToken "YOUR_VSTS_TOKEN"

ATENCIÓN: Si elegimos usar la opción B tenemos que crear una nueva imagen base a partir de esta VM en la que hemos ejecutado el script. Luego tendremos que repetir el paso de WinRM para generar el nuevo template ARM como veremos a continuación.

Template ARM

Después vamos a la pestaña «Advanced Settings» y hacemos clic en «View ARM template«:

Get the ARM template
Obtenemos el template ARM

Esto nos mostrará el template ARM que usaremos para crear la VM desde la pipeline. Es algo así:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "newVMName": {
      "type": "string",
      "defaultValue": "aariste001"
    },
    "labName": {
      "type": "string",
      "defaultValue": "aristeinfo"
    },
    "size": {
      "type": "string",
      "defaultValue": "Standard_B4ms"
    },
    "userName": {
      "type": "string",
      "defaultValue": "myUser"
    },
    "password": {
      "type": "securestring",
      "defaultValue": "[[[VmPassword]]"
    },
    "Configure_WinRM_hostName": {
      "type": "string",
      "defaultValue": "Public IP address"
    },
    "Azure_Pipelines_Agent_vstsAccount": {
      "type": "string",
      "defaultValue": "ariste"
    },
    "Azure_Pipelines_Agent_vstsPassword": {
      "type": "securestring"
    },
    "Azure_Pipelines_Agent_agentName": {
      "type": "string",
      "defaultValue": "DevTestAgent"
    },
    "Azure_Pipelines_Agent_agentNameSuffix": {
      "type": "string",
      "defaultValue": ""
    },
    "Azure_Pipelines_Agent_poolName": {
      "type": "string",
      "defaultValue": "DevTestPool"
    },
    "Azure_Pipelines_Agent_RunAsAutoLogon": {
      "type": "bool",
      "defaultValue": false
    },
    "Azure_Pipelines_Agent_windowsLogonAccount": {
      "type": "string",
      "defaultValue": "aariste"
    },
    "Azure_Pipelines_Agent_windowsLogonPassword": {
      "type": "securestring"
    },
    "Azure_Pipelines_Agent_driveLetter": {
      "type": "string",
      "defaultValue": "C"
    },
    "Azure_Pipelines_Agent_workDirectory": {
      "type": "string",
      "defaultValue": "DevTestAgent"
    },
    "Azure_Pipelines_Agent_replaceAgent": {
      "type": "bool",
      "defaultValue": true
    }
  },
  "variables": {
    "labSubnetName": "[concat(variables('labVirtualNetworkName'), 'Subnet')]",
    "labVirtualNetworkId": "[resourceId('Microsoft.DevTestLab/labs/virtualnetworks', parameters('labName'), variables('labVirtualNetworkName'))]",
    "labVirtualNetworkName": "[concat('Dtl', parameters('labName'))]",
    "vmId": "[resourceId ('Microsoft.DevTestLab/labs/virtualmachines', parameters('labName'), parameters('newVMName'))]",
    "vmName": "[concat(parameters('labName'), '/', parameters('newVMName'))]"
  },
  "resources": [
    {
      "apiVersion": "2018-10-15-preview",
      "type": "Microsoft.DevTestLab/labs/virtualmachines",
      "name": "[variables('vmName')]",
      "location": "[resourceGroup().location]",
      "properties": {
        "labVirtualNetworkId": "[variables('labVirtualNetworkId')]",
        "notes": "Dynamics365FnO10013AgentLessV2",
        "customImageId": "/subscriptions/6715778f-c852-453d-b6bb-907ac34f280f/resourcegroups/devtestlabs365/providers/microsoft.devtestlab/labs/devtestd365/customimages/dynamics365fno10013agentlessv2",
        "size": "[parameters('size')]",
        "userName": "[parameters('userName')]",
        "password": "[parameters('password')]",
        "isAuthenticationWithSshKey": false,
        "artifacts": [
          {
            "artifactId": "[resourceId('Microsoft.DevTestLab/labs/artifactSources/artifacts', parameters('labName'), 'public repo', 'windows-winrm')]",
            "parameters": [
              {
                "name": "hostName",
                "value": "[parameters('Configure_WinRM_hostName')]"
              }
            ]
          },
          {
            "artifactId": "[resourceId('Microsoft.DevTestLab/labs/artifactSources/artifacts', parameters('labName'), 'public repo', 'windows-vsts-build-agent')]",
            "parameters": [
              {
                "name": "vstsAccount",
                "value": "[parameters('Azure_Pipelines_Agent_vstsAccount')]"
              },
              {
                "name": "vstsPassword",
                "value": "[parameters('Azure_Pipelines_Agent_vstsPassword')]"
              },
              {
                "name": "agentName",
                "value": "[parameters('Azure_Pipelines_Agent_agentName')]"
              },
              {
                "name": "agentNameSuffix",
                "value": "[parameters('Azure_Pipelines_Agent_agentNameSuffix')]"
              },
              {
                "name": "poolName",
                "value": "[parameters('Azure_Pipelines_Agent_poolName')]"
              },
              {
                "name": "RunAsAutoLogon",
                "value": "[parameters('Azure_Pipelines_Agent_RunAsAutoLogon')]"
              },
              {
                "name": "windowsLogonAccount",
                "value": "[parameters('Azure_Pipelines_Agent_windowsLogonAccount')]"
              },
              {
                "name": "windowsLogonPassword",
                "value": "[parameters('Azure_Pipelines_Agent_windowsLogonPassword')]"
              },
              {
                "name": "driveLetter",
                "value": "[parameters('Azure_Pipelines_Agent_driveLetter')]"
              },
              {
                "name": "workDirectory",
                "value": "[parameters('Azure_Pipelines_Agent_workDirectory')]"
              },
              {
                "name": "replaceAgent",
                "value": "[parameters('Azure_Pipelines_Agent_replaceAgent')]"
              }
            ]
          }
        ],
        "labSubnetName": "[variables('labSubnetName')]",
        "disallowPublicIpAddress": true,
        "storageType": "Premium",
        "allowClaim": false,
        "networkInterface": {
          "sharedPublicIpAddressConfiguration": {
            "inboundNatRules": [
              {
                "transportProtocol": "tcp",
                "backendPort": 3389
              }
            ]
          }
        }
      }
    }
  ],
  "outputs": {
    "labVMId": {
      "type": "string",
      "value": "[variables('vmId')]"
    }
  }
}

NOTA: si te decantas por la opción B no tendrás el nodo del artefacto del agente de VSTS.

Este archivo JSON se usará como base para crear las máquinas desde la pipeline de Azure DevOps. Esto es conocido como Infrastructure as Code (IaC, Infraestructura como código) y es una forma de definir nuestra infraestructura en un fichero como si fuera código. Es otra parte de la práctica de DevOps que debería resolver el clásico «pues funciona en mi máquina».

Si le echamos un vistazo a los nodos de parámetros del JSON veremos que tienen la siguiente información:

  • newVMName y labName serán los nombres de la VM y el lab de DevTest Labs que usaremos. El nombre de la máquina en realidad no importa porque lo definiremos más tarde en la pipeline.
  • size es el tamaño de la VM, una D3 V2 en el ejemplo de arriba, pero lo podemos cambiar (y lo haremos) luego.
  • userName y passWord son las credenciales para acceder a la VM y que deben coincidir con las que hemos puesto para el agente de DevOps.
  • Configure_WinRM_hostName es el artefacto que hemos añadido en el template y que permite ejecutar parte de las pipelines en la máquina.

Para hacerlo más rápido y como es una demo voy a usar texto plano para las contraseñas en el template ARM, cambiando el nodo de password a algo así:

"password": {
      "type": "string",
      "defaultValue": "yourPassword"
    },

Haré lo mismo con todas los nodos de secureString, pero lo mejor, repito, es usar un Azure KeyVault que viene con la cuenta de Azure DevTest Labs.

Por supuesto nunca vamos a sabuir un template a Azure DevOps con la contraseña en texto plano. Hay multitud de recursos online que explican como usar parámetros, Azure KeyVault, etc. como por ejemplo este: 6 Ways Passing Secrets to ARM Templates.

Muy bien, ahora vamos a guardar el template y lo subiremos al repo de Azure DevOps. He creado una carpeta en la raíz de mi repositorio llamado ARM en el que he guardado todos mis templates ARM:

ARM templates on Azure DevOps
Templates ARM en Azure DevOps

Preparando la VM

La imagen VHD que hemos descargado se puede usar como máquina de desarrollo sin ningún paso adicional, se ejecuta Visual Studio, lo conectamos a nuestro proyecto de AZDO y listo. Pero si la queremos usar como máquina de build tenemos que hacer unas cuantas cosas primero.

Recordad que el usuario y contraseña por defecto de estas máquinas son Administrator y Pass@word1.

Deshabilitar servicios

Primero de todo pararemos y deshabilitaremos algunos servicios como el Batch, Management Reporter, SSAS, SSIS, etc. Cualquier cosa que no se necesite para ejecutar una build.

Crear un nuevo usuario de SQL

Abriremos SSMS (como administrador) y crearemos un nuevo usuario de SQL como una copia del axdbadmin. Después abriremos el archivo web.config y actualizaremos el usuario y contraseña de la DB con el que acabamos de crear.

Preparar SSRS (opcional)

Si váis a desplegar reports como parte de vuestra build tenéis que ir a SSMS y ejecutar la siguiente query contra la DB de reporting:

exec DeleteEncryptedContent

Scripts de PowerShell

La build por defecto que corre en la máquina de build usa varios scripts de PowerShell para ejecutar tareas. Voy a añadir un script adicional llamado PrepareForAgent.

Los scripts se encuentran en la carpeta C:\DynamicsSDK de la VM.

PrepareForBuild

Este script viene con la VM y debemos modificarlo para evitar una cosa: el backup de PackagesLocalDirectory que se hace en la primera ejecución de cada build. Tenemos que evitar que se haga o perderemos una hora en cada ejecución hasta que se termine.

No lo necesitamos porque nuestra máquina de build será nueva cada vez que ejecutemos la pipeline!

Así que abrimos el script, vamos a la línea 696 y buscamos esta parte del código:

# Create packages backup (if it does not exist).
$NewBackupCreated = Backup-AX7Packages -BackupPath $PackagesBackupPath -DeploymentPackagesPath $DeploymentPackagesPath -LogLocation $LogLocation
# Restore packages backup (unless a new backup was just created).
if (!$NewBackupCreated)
{
    Restore-AX7Packages -BackupPath $PackagesBackupPath -DeploymentPackagesPath $DeploymentPackagesPath -LogLocation $LogLocation -RestoreAllFiles:$RestorePackagesAllFiles
}
    
if (!$DatabaseBackupToRestore)
{
    $DatabaseBackupPath = Get-BackupPath -Purpose "Databases"
    Backup-AX7Database -BackupPath $DatabaseBackupPath
}
else
{
    # Restore a database backup (if specified).
    Restore-AX7Database -DatabaseBackupToRestore $DatabaseBackupToRestore
}

Tenemos que cambiarlo para que sea así:

if ($DatabaseBackupToRestore)
{
    Restore-AX7Database -DatabaseBackupToRestore $DatabaseBackupToRestore
}

Simplemente dejaremos la parte de restaurar la DB y nos saltamos el backup, o perderemos 45 minutos por ejecución por algo que no necesitamos porque la VM se borra en cada ejecución.

Opcional (pero recomendado): instalar las d365fo.tools

Simplemente ejecutaremos esto:

Install-Module -Name d365fo.tools

Podemos usar las tools para hacer sincronizaciones parciales, por modelos o desplegar sólo nuestros reports en vez de todos.

Crear una nueva imagen

Una vez hayamos hecho todos estos pasos de preparación vamos a deslogearnos de la máquina y la vamos a detener. Pero no la borréis! Vamos a ir a «Create custom image», le daremos un nuevo nombre, seleccionamos «I have not generalized this virtual machine» y haremos clic en el botón de «OK».

Esto generará una nueva imagen con los cambios que hemos hecho al VHD original y que podemos usar como base.

Pipelines de Azure DevOps

Ya estamos listos para configurar nuestra pipeline en Azure DevOps. Esta pipeline va a consistir de tres pasos: creación de la VM, build, y borrado de la VM:

Primero de todo comprobaremos que nuestra pipeline se ejecuta en Azure (Azure Pipelines):

DevTest Labs Azure Pipelines
DevTest Labs Azure Pipelines

Los pasos de crear y borrar se ejecutarán en una pipeline de Azure. El de build se ejecutará en el pool DevTestLabs que hemos creado, o el nombre que le hayáis dado al configurar el artefacto en DevTest Labs o el script en la VM.

Crear VM en Azure DevTest Labs

Creamos una nueva pipeline y elegimos «Use the classic editor». Nos aseguramos de que hemos seleccionado TFVC como origen y hacemos clic en «Continue» y «Empty job». Añadimos una nueva tarea a la pipeline y buscamos «Azure DevTest Labs Create VM». Sólo tenemos que rellenar la información necesaria con los parámetros de nuestra suscripción, lab, etc.

Create VM Azure DevTest Labs
Crear VM en Azure DevTest Labs

Recordad que este paso tiene que ejecutarse en una pipeline de Azure.

Build

Este es muy fácil. Exportamos una build y la importamos.

Este paso tiene que ejecutarse en vuestro pool:

Runs on self-hosted pool
Se ejecuta en el pool autohospedado

Opcional: usar SelectiveSync (no recomendado, ver siguiente apartado)

Podéis sustituir la tarea Database Sync por un script de PowerShell que solo sincroniza las tablas de vuestros modelos:

SelectiveSync.ps1
SelectiveSync.ps1

¡Muchas gracias a Joris por el consejo!

Opcional: usa las d365fo.tools para sincronizar tus paquetes/modelos

Esta opcion es mejor que usar SelectiveSync. Puedes sincronizar sólo tus paquetes o modelos y ganar algo de tiempo. Este comando usa sync.exe como Visual Studio y debería ser mejor que usar SelectiveSync.

Añadimos una nueva tarea de PowerShell, seleccionamos Inline Script y este es el comando:

Invoke-D365DbSyncModule -Module "Module1", "Module2" -ShowOriginalProgress -Verbose

Opcional: usa las d365fo.tools para publicar los reports de SSRS

Si quieres añadir el paso de desplegar los reports en tu pipeline puedes ahorrar un poco de tiempo más usando las d365fo.tools y desplegando sólo los reports de tus modelos como hemos hecho con la sincronización de la DB.

Ejecutaremos esto en una nueva task de PowerShell:

Publish-D365SsrsReport -Module YOUR_MODULE -ReportName *

Borrar VM de Azure DevTest Labs

Es muy parecido al primer paso de crear, completamos los campos de suscripción, labs y VM y listo:

Delete VM
Borrar VM

Y este paso, igual que el de crear, se ejecutan en el pool de Azure.

Dependencias y condiciones

Cuando tengamos los 3 pasos configurados tenemos que añadir unas dependencias y condiciones a algunos de ellos. Por ejemplo, asegurarnos que el paso de borrado de la VM solo se ejecuta cuando falle el paso de build, pero que no lo haga cuando falle el de creación.

Build

El paso de build depende del de crear VM, y sólo debería ejecutarse si el paso previo ha terminado bien:

Build step dependencies and conditions
Dependencias y condiciones del paso de build

Borrar VM

El paso de borrado depende de los anteriores y sólo debe ejecutarse si el de creación termian bien. Si falla el primero no hay que borrar nada:

Dependencies and conditions on delete VM step
Dependencias y condiciones del paso de borrado

Esta es la condición personalizada que usaremos:

and(always(), eq(dependencies.Job_1.status, 'Succeeded'))

Si necesitáis saber cómo se llama el primer paso de vuestra pipeline, exportadla a YAML y lo encontraréis ahí:

Export pipeline to YAML
Exportar pipeline a YAML
Job name on YAML
Nombre del Job en el YAML

Si este paso fallase al ejecutar la build, no borréis la VM aun, primero cambiad el nombre de la VM en el paso de borrado, guardad la pipeline, y luego usad el desplegable para seleccionar la que queréis borrar.

Ejecutar la build

Y, creo, que ya estamos listos para ejecutar nuestra pipeline con Azure DevTest Labs para Dynamics 365 Finance and Operations… hacemos clic en «Run pipeline» y a esperar…

Tadaaaa!!

Tiempos

La pipeline de la imagen superior es una real con código real de un cliente pero no puedo compararlo con las hospedadas en Azure porque en estas no hay sincronización, ni tests y necesitan instalar primero los nugets.

Pero una comparación que si hice fue esta:

Azure DevTest Labs B2ms vs B4ms
Azure DevTest Labs B2ms vs B4ms

Tarda sobre 1 hora en crearla VM, compilar, hacer una sincronización completa de la DB, desplegar informes, ejecutar tests, generar el Deployable Package y, finalmente, borrar la VM:

Si no desplegamos reports ganaremos 15 minutos más y se quedará en unos 45 minutos.

Si usamos el partial sync en vez de hacer una sincronización completa ganaremos entre 5 y 7 minutos.

Esto nos dejaría con una build de 35-40 minutos.

Comparativa 1

Sin DB

La imagen muestra un paquete simple que se compila, sin tablas, así que el selective sync termina muy rápido. Los tiempos de build mejoran con el tamaño de la VM.

Comparativa 2

Mismo código pero Full DB Sync

Esta compila la misma base de código pero hace una sincronización de toda la DB. El tiempo de sincronización mejora en la B4ms respecto una B2ms, pero es casi el mismo en una B8ms. Los tiempos de compilación son mejores cuanto más potente es la VM.

Comparativa 3

Código real + sincronización completa

Y la imagen de arriba muestra algo más realista. Hay mucho más código que compilar y hacemos una sincronización completo de la DB.

De forma similar a la anterior comparativa, hay una mejora notable en el salto de una B2ms a una B4ms, pero no tanto de una B4ms a una B8ms.

Show me the money!

Creo que esta es la comparación interesante. Cuanto costaba una VM Tier-1? Unos 400€? Cuanto es comparado con la alternativa con Azure DevTest Labs?

Sólo hay un sote fijo cuando usemos Azure DevTest Labs: el almacenamiento blob en el que se sube el VHD. El tamaño de la imagen es de unos 130GB y esto debería tener un coste de unos 5 euros/mes más o menos. Recordad que hay que limpiar las imágenes personalizadas cuando la vuestra esté prepada, las nuevas se guardan como snapshots y también consumen espacio de almacenamiento.

Después tenemos el coste variable que viene con el despliegue de cada VM pero es un coste ridículo. Imaginad que usamos una VM de tipo B4ms, con un SSD Premium de 256GB, pagaríamos 0.18€/hora por la VM más la parte proporcional de 35.26€/mes del disco SSD, que serían como… 5 céntimos/hora?

Pero es que esta build puede ejecutarse en una B2ms que cuesta la mitad, por 9 céntimos la hora.

Si ejecutamos esta build una vez al día, 30 veces, el coste de la B4ms sería de… 7 euros? Añadimos el almacenamiento de blob y estaríamos pagando 12€ al mes por ejecutar nuestras builds con sincronización de DB y tests.

Es mucho más barato que desplegar un entorno hospedado en la nube, iniciándolo y parándolo cada vez que lo ejecutamos con el nuevo Cmdlet de las d365fo.tools? Sí lo es. Porque si desplegamos una máquina desde LCS tenemos que pagar por el SSD durante todo el mes!

Algunas observaciones finales

  1. He conseguido esto mayormente a través de prueba y error. Estoy seguro de que hay muchas mejoras y mejores prácticas aplicables a todo el proceso, especialmente usar un Azure Key Vault para guardar los secretos en para los artefactos y la pipeline.
  2. Esto es otro ejemplo de que los desarrolladores de X++ tenemos que salir de X++ y Dynamics 365 FnO. Ya no somos sólo programadores de X++, tenemos mucha suerte de trabajar con un producto que usa Azure.
  3. Estoy seguro que hay escenarios en los que usar DevTest Labs para crear una máquina de build es útil. Quizás no para un partner de implantación, pero igual sí para un partner que hace verticales (ISV). Es una opción más.
  4. La única parte mala para mi de esto es que tenemos que aplicar los upgrades de versión manualmente porque el VHD sólo se publica dos veces al año.
  5. Como dije al principio del post, esto puede que me haya funcionado a mi con estos pasos, pero que si tú lo pruebas tengas que cambiar algunas cosas. Pero es una buena forma de empezar.

¡Suscríbete!

Recibe un correo cuando se publique un nuevo post
Author

Microsoft Dynamics 365 Finance & Operations technical architect and developer. Business Applications MVP since 2020.

Write A Comment

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

ariste.info