En el día a día de nuestro trabajo siempre nos encontramos, de una forma u otra, con esos procesos lotes que requieren un tiempo mayor a otras tareas del negocio: facturación, renderización de imágenes, generación de informes (nóminas, cálculos de notas, cartas) y un largo etcétera. Son ese tipo de tareas nocturas, que llevan tiempo en procesar o que necesitan un alto rendimiento de computación. Para este tipo de escenarios es para lo que está pensado Azure Batch, que permite de manera programática definir un conjunto de recursos en Azure encargados de ejecutar trabajos bajo demanda o programados, sin la necesidad de configurar un clúster HPC, máquinas virtuales, etcétera.
Antes de ver un ejemplo hay algunos conceptos básicos que deberías de conocer dentro del servicio:
- Pool: se trata del contenedor que gestionará los nodos y recibirás los jobs a gestionar.
- Nodos: son las máquinas virtuales que procesarán las tareas.
- Job: Se trata del trabajo al que irán asociadas las tareas a realizar.
- Tareas: pueden ser desde una linea de comando con un echo hasta lanzar un ejecutable que realice el proceso más complejo. La tarea no tiene por qué estar desarrollada en .NET. De hecho, en este ejemplo te mostraré cómo lanzar un jar como tarea.
Para probar el servicio, lo primero que necesitas es crear una cuenta de Azure Batch. Para ello, accede a https://portal.azure.com > New > Compute > Batch Service.

Para este ejemplo, he creado un proyecto con la librería en .NET, que proporciona el servicio, con el que serás capaz de generar un job que procese diferentes tareas:
- Llamada echo.
- Recuperar los paquetes instalados a través de Chocolatey en el nodo.
- Conocer el valor de la variable de entorno java_home.
- Lanzar un ejecutable en Java que realice una serie de acciones.
Como ves, la mayoría de ellas son demasiado simples, pero es importante que conozcas cómo configurar este surtido de tareas para que los nodos sean capaces de procesarlas.
Lo primero que voy a hacer es crear un método asíncrono, CreateTasksAsync, que sirva de punto de entrada a toda la configuración del servicio:
static void Main(string[] args) { try { CreateTasksAsync().Wait(); } catch (AggregateException aggregateException) { foreach (var exception in aggregateException.InnerExceptions) { Console.WriteLine(exception.ToString()); Console.WriteLine(); } } Console.WriteLine("Press return to exit..."); Console.ReadLine(); }
Como lo ideal es que el proceso sea asíncrono, se ha dividido en el código las tareas de configuración, creación y espera. CreateTaskAsync se encarga de la configuración:
private static async Task CreateTasksAsync() { //1. Connect with you Azure Batch account BatchSharedKeyCredentials credentials = new BatchSharedKeyCredentials( Settings.Default.BatchURL, Settings.Default.AccountName, Settings.Default.AccountKey); using (var batchClient = await BatchClient.OpenAsync(credentials)) { //2. Add a retry policy batchClient.CustomBehaviors.Add(RetryPolicyProvider.LinearRetryProvider(TimeSpan.FromSeconds(10), 3)); //3. Create a job id var jobId = string.Format("MyTasks-{0}", DateTime.Now.ToString("yyyyMMdd-HHmmss")); try { //4. Submit the job await SubmitJobAsync(batchClient, jobId); //5. Wait for the job to complete await WaitForJobAndPrintOutputAsync(batchClient, jobId); } finally { Console.WriteLine("Press enter to delete the job"); Console.ReadLine(); if (!string.IsNullOrEmpty(jobId)) { Console.WriteLine("Deleting job: {0}", jobId); batchClient.JobOperations.DeleteJob(jobId); } } } }
- Lo primero es recuperar los valores de la cuenta de Azure Batch que acabas de crear. En este ejemplo están almacenados en un archivo Settings y puedes localizarlos en el portal:
- Se añade una política de reintento cada 10 segundos con tres reintentos.
- Se genera un id para el job que se va a configurar.
- Se pasa el cliente y el id al siguiente paso, que es la creación, a través del método SubmitJobAsync.
- Comienza la espera de la finalización del proceso, haciendo uso de un último método llamado WaitForJobAndPrintOutputAsync.
Respecto a los dos últimos pasos, vamos a ver en qué consiste el SubmitJobAsync:
private static async Task SubmitJobAsync(BatchClient batchClient, string jobId) { Console.WriteLine("Creating the job {0}", jobId); //1. Create a job CloudJob job = batchClient.JobOperations.CreateJob(); job.Id = jobId; //2. Define a start tasks for the nodes //Start task: Installing Chocolatey - A Machine Package Manager var startTask = new StartTask() { ResourceFiles = new List<ResourceFile>() { new ResourceFile("https://returngisbatch.blob.core.windows.net/resources/install-choco.bat", "install-choco.bat") }, CommandLine = "install-choco.bat", WaitForSuccess = true, //Specifies if other tasks can be scheduled on a VM which has not run the start task RunElevated = true }; //3. For this job, ask the Azure Batch service to automatically create a pool of VMs when the job is submitted job.PoolInformation = new PoolInformation { AutoPoolSpecification = new AutoPoolSpecification { AutoPoolIdPrefix = "returngis", PoolSpecification = new PoolSpecification { TargetDedicated = 3, OSFamily = "4", VirtualMachineSize = "small", StartTask = startTask }, KeepAlive = false, PoolLifetimeOption = PoolLifetimeOption.Job } }; //4. Commit job to create it in the service await job.CommitAsync(); //5. Define my tasks var commandLine = "cmd /c echo Hello world from the Batch Hello world sample!"; Console.WriteLine("Task #1 command line: {0}", commandLine); await batchClient.JobOperations.AddTaskAsync(jobId, new CloudTask("taskHelloWorld", commandLine)); var commandChoco = "cmd /c choco list --local-only"; Console.WriteLine("Task # 2 command line: {0}", commandChoco); await batchClient.JobOperations.AddTaskAsync(jobId, new CloudTask("taskchoco", commandChoco)); var echoJavaHome = "cmd /c echo %java_home%"; Console.WriteLine("Task # 3 command line: {0}", echoJavaHome); await batchClient.JobOperations.AddTaskAsync(jobId, new CloudTask("taskecho", echoJavaHome)); var jarTask = new CloudTask("jartask", "cmd /c java -jar AzureStorage-0.0.1.jar"); Console.WriteLine("Task # 4 command line: {0}", "java -jar AzureStorage-0.0.1.jar"); jarTask.ResourceFiles = new List<ResourceFile>(); jarTask.ResourceFiles.Add(new ResourceFile("https://returngisbatch.blob.core.windows.net/resources/AzureStorage-0.0.1.jar", "AzureStorage-0.0.1.jar")); await batchClient.JobOperations.AddTaskAsync(jobId, jarTask); }
Este método es el que tiene toda la carga de la creación tanto del pool como de cada una de las tareas que se van a crear:
- Crea el job con el id que has elegido durante la parte de configuración.
- Se ha definido una start task para los nodos ¿esto que significa? Cuando hice un repaso de las tareas que ibamos a lanzar, pudiste ver que dos de ellas están haciendo uso de Chocolatey y Java, lo cual no está disponible en los nodos que ejecutarán las tareas por defecto. Azure Batch te permite definir tareas de arranque para preparar los nodos antes de la ejecución de las tareas, con el fin de configurar e instalar el software necesario para poder ejecutarlas. Para este escenario, he creado un .bat con la información necesaria para instalar Chocolatey y también el JDK de Java.
@powershell -NoProfile -ExecutionPolicy Bypass -Command "iex((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" choco install jdk8 --force --yes --acceptlicense --verbose exit /b 0
Tanto este archivo, install-choco.bat, como el jar que utilizaré para una de las tareas, están alojados en una cuenta de Azure, con el modo de acceso Blob en el container.
Azure Batch – Resource Files – Microsoft Azure Storage Explorer – Public Read Access Blob - Existen diferentes formas de crear un pool. En este caso estamos haciéndolo de tal manera que cuando el job se envie al servicio se generará el mismo y cuando este haya completado todas las tareas el pool se eliminará. Se puede ver que se definen el número de máquinas que inicialmente se crearán (TargetDedicated = 3), la familia del SO a 4 (1 = Windows Server 2008, 2 = Windows Server 2008 R2, 3 = Windows Server 2012 y 4 = Windows Server 2012 R2), el tamaño de las máquinas y se asocia la start task que definiste anteriormente.
- Se hace un commit del job en el servicio con la configuración que acabas de establecer.
- Añades cada una de las tareas que te prometí en el primer punto. La última, que lanza un ejecutable de Java, es la que difiere del resto, ya que es necesario utilizar un ResourceFile como parte de la tarea e invocarlo dentro del apartado command line.
Por último queda la fase de espera, donde pasamos al último método del ejemplo, WaitForJobAndPrintOutputAsync:
private static async Task WaitForJobAndPrintOutputAsync(BatchClient batchClient, string jobId) { Console.WriteLine("Waiting for all tasks to complete on job: {0} ...", jobId); //1. Use a task state monitor to monitor the status of your tasks var taskStateMonitor = batchClient.Utilities.CreateTaskStateMonitor(); List<CloudTask> myTasks = await batchClient.JobOperations.ListTasks(jobId).ToListAsync(); //2. Wait for all tasks to reach the completed state. bool timedOut = await taskStateMonitor.WhenAllAsync(myTasks, TaskState.Completed, TimeSpan.FromMinutes(15)); if (timedOut) { throw new TimeoutException("Timed out waiting for tasks."); } //3. Dump task output foreach (var task in myTasks) { Console.WriteLine("Task {0}", task.Id); //4. Read the standard out of the task NodeFile standardOutFile = await task.GetNodeFileAsync(Constants.StandardOutFileName); var standardOutText = await standardOutFile.ReadAsStringAsync(); Console.WriteLine("Standard out: "); Console.WriteLine(standardOutText); Console.WriteLine(); } }
- Se crea un objeto del tipo TaskStateMonitor que te ayudará a esperar todas las tareas del listado que le pases como parámetro.
- Este es el punto de la aplicación donde se requerirá la mayor paciencia, ya que el monitor estará esperando a que todas las tareas estén en el estado Completed dentro de 15 minutos. Dependiendo del tipo de tarea habría que aumentar el tiempo de timeout de la espera.
- Si todo ha ido bien, por último se mostrará por pantalla el output, del archivo stdout.txt de la tarea, de cada una de las tareas procesadas. Existe otro archivo por tarea, stderr.txt, que almacena los errores que han ocurrido durante la ejecución de la misma.
Mientras que estás esperando, lo único que puedes a través de la consola es lo siguiente:

O esto a través del portal:

Hay una herramienta que te recomiendo: Azure Batch Explorer. Con ella serás capaz de ver qué es lo que ocurre en el servicio, en qué estado está el pool, los nodos, tareas, etcétera.

Además tiene una opción muy interesante que es el Heatmap, que te permite ver el estado de los nodos: si están a la espera, ejecutando algo, iniciandose o si ocurrió algún error.

También te permite gestionar de forma sencilla la creación de un usuario y el RDP para el acceso remoto a cualquiera de los nodos.

Una vez que ha finalizado el proceso podrás ver el resultado en la consola y finalizar el job.

En este enlace puedes ver las cuotas y límites del servicio. Por ahora sólo está disponible el procesamiento de tareas sobre máquinas Windows, aunque ya se han visto videos donde se está trabajando el uso de Linux.
Aquí tienes el proyecto del ejemplo en mi cuenta de GitHub, por si fuera de utilidad.
¡Saludos!