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»:
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:
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«:
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: