Crear agentes de Azure DevOps de manera dinámica con una pipeline multi-stage

Hace unos días surgió la necesidad de poder crear agentes de Azure DevOps de manera dinámica justo antes de que comenzaran las tareas propias de build. Estuve dando vueltas a diferentes maneras de gestionar este escenario y una de las que se contempló fue a través de una pipeline multi-stage. En este artículo te muestro un ejemplo de cómo se haría.

Crear un pool de agentes

Lo primero que necesitas tener siempre es un pool de agentes, al que tu pipeline se va a referir, aunque este no tenga agentes todavía asociados. Es súper sencillo, sólo tienes que ir a Organization Settings > Pipelines > Agents Pools y allí hacer clic en el botón Add pool para crear uno nuevo. Elige un nombre y si quieres que esté disponible para todas las pipelines y disponible en todos los proyectos.

Crear un agent pool en Azure DevOps

Es importante hacer este paso antes de crear el pipeline, para que la misma esté autorizada. En caso contrario, deberás autorizar de manera explícita a la misma p

The pool does not exist or has not been authorized for use.

El siguiente paso será crear un agente que pertenezca a este pool.

Crear un pipeline multi-stage

En el mes de Mayo se anunció la posibilidad de crear pipelines multi-stage, lo cual te permite gestionar diferentes fases dentro de una misma pipeline. Para este caso es perfecto ya que por cada stage puedo elegir un pool diferente. Esta modalidad sólo está disponible en formato YAML, por lo que no es posible usar la interfaz gráfica para definirla. Para mi objetivo he creado la siguiente con las fases mínimas para este escenario:

# Multi-stage pipeline which creates the agent dynamically for its tasks and then delete it.
trigger:
- master

variables:

  # Azure Resource Manager connection created during pipeline creation
  azureSubscription: 'd2d1892c-dcc1-42b2-b514-429d85272091'

  # Agent VM image name that help you to create your agent in ACI
  vmImageName: 'ubuntu-latest'

  #Agent's resource group
  resourceGroupName: 'Agent-$(Build.BuildId)'
  #Location for the resource group
  location: 'northeurope'

  #Container image name see more here: https://hub.docker.com/_/microsoft-azure-pipelines-vsts-agent
  containerImageName: 'mcr.microsoft.com/azure-pipelines/vsts-agent'

  #Container group name:
  containerGroupName: 'super-agents'

  #VSTS_ACCOUNT
  VSTS_ACCOUNT: 'returngis'

  #VSTS_TOKEN
  VSTS_TOKEN: $(PAT)

  # The name of the pool where your agent will live
  agentPool: 'dynamic-pool'


stages:
- stage: Create_Agent_Stage
  displayName: Create an agent dynamically in Azure Container Instances
  jobs:
  - job: Create_Agent_Job
    displayName: Create an agent
    pool:
      vmImage: $(vmImageName)
    steps: 
    - task: [email protected]
      displayName: Azure CLI
      inputs:
        azureSubscription: $(azureSubscription)
        scriptType: bash
        scriptLocation: inlineScript
        inlineScript: |
            echo 'Creating my agent in $(resourceGroupName)'
            az group create -n $(resourceGroupName) -l $(location)
            az container create -g $(resourceGroupName) -n $(containerGroupName) --image $(containerImageName) --environment-variables 'VSTS_ACCOUNT'=$(VSTS_ACCOUNT) 'VSTS_TOKEN'=$(VSTS_TOKEN) 'VSTS_POOL'=$(agentPool)
- stage: Use_the_agent
  displayName: 'Use the agent for your tasks'
  jobs:
  - job: Use_Agent_Job
    pool: 
      name: $(agentPool)
    steps:
      - task: [email protected]
        displayName: 'Here you can do your things'
        inputs:        
          targetType: inline
          script: |            
            whoami
            sudo apt-get update        
    continueOnError: true
        
- stage: Delete_Agent_Stage
  displayName: 'Delete the agent'
  jobs:
  - job: Delete_Agent_Jobs
    pool: 
      vmImage: $(vmImageName)
    steps:
    - task: [email protected]
      displayName: 'Delete $(resourceGroupName) resource group'
      inputs:
        azureSubscription: $(azureSubscription)
        scriptType: bash
        scriptLocation: inlineScript
        inlineScript: |          
          az group delete --name $(resourceGroupName) --yes

Como puedes ver, mi pipeline tiene 3 fases:

  1. Create Agent Stage: utilizo un pool hosted de Azure DevOps para lanzar la creación de mi agente en Azure Container Instances. Lo más sencillo es utilizar los contenedores que Microsoft provee, donde sólo debes pasarle las variables de entorno VSTS_ACCOUNT, el cual es el nombre de tu cuenta de Azure DevOps, VSTS_TOKEN que es un Personal Access Token (PAT), que puedes crear desde el apartado de Seguridad de tu cuenta, y VSTS_POOL que es el nombre del pool al cual quieres asociar este agente, en mi caso dynamic-pool.
  2. Use the agent: En esta fase ya puedo utilizar mi nuevo agente, asociando dynamic-pool al parámetro pool. En este punto indico que siempre debe continuar (imagínate que la build no compila o cualquier otro error)
  3. Delete the agent: Por último, para evitar costes innecesarios, elimino el resource group donde está alojado mi agente.

Lo normal es que entre la primera fase y la segunda se acondicione el nuevo agente con la configuración deseada para tu build.

¡Saludos!