Azure Batch loves Linux

Hace tiempo te conté cómo de sencillo es utilizar Azure Batch para lanzar procesos por lotes en un clúster de máquinas en Microsoft Azure. Recientemente se ha habilitado la posibilidad de lanzar tareas también en Linux, aprovechando las máquinas virtuales de la plataforma. En este post te quiero mostrar cómo configurar un job que utilice este sistema operativo.

Para este artículo he utilizado un par de tareas muy simples en Phyton que te ayuden a comprender cómo configurar el job y el pool en el servicio.

import os
if __name__ == '__main__':
print("simpletask.py listing files:")
for item in os.listdir('.'):
print(item)

simpletask.py únicamente lista todos aquellos archivos que estén en el directorio.

import fnmatch
import os

rootPath = '/'
pattern = '*.pdf'

for root, dirs, files in os.walk(rootPath):
for filename in fnmatch.filter(files, pattern):
print( os.path.join(root, filename))

find_files.py busca a nivel raíz todos los archivos con extensión pdf.

En este ejemplo he utilizado .NET para configurar el entorno, pero también es posible generarlo utilizando el SDK para Python.

using Microsoft.Azure.Batch;
using Microsoft.Azure.Batch.Auth;
using Microsoft.Azure.Batch.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace BatchOnLinux
{
    class Program
    {
        static void Main(string[] args)
        {
            //Install-Package Azure.Batch

            Console.WriteLine("******************* STARTING *************************");
            Console.WriteLine();

            try
            {
                GenerateTasks().Wait();
            }
            catch (AggregateException aggregateException)
            {
                foreach (var exception in aggregateException.InnerExceptions)
                {
                    Console.WriteLine(exception.ToString());
                    Console.WriteLine();
                }
            }

            Console.WriteLine("DONE");
            Console.ReadLine();

        }

        private static async Task GenerateTasks()
        {
            //create batch client
            var credentials = new BatchSharedKeyCredentials(Settings.Default.BatchURL, Settings.Default.AccountName, Settings.Default.AccountKey);

            using (var batchClient = await BatchClient.OpenAsync(credentials))
            {
                var jobId = "linux-job";

                await SubmitJobAsync(batchClient, jobId);
                await WaitForJobAndPrintOutputAsync(batchClient, jobId);
            }
        }


        private static async Task SubmitJobAsync(BatchClient batchClient, string jobId)
        {
            //Debug
            Console.WriteLine("******************* SETTINGS *************************");
            Console.WriteLine("VM Size: {0}", Settings.Default.VmSize);
            Console.WriteLine("Node count: {0}", Settings.Default.NodesCount);
            Console.WriteLine();


            Console.WriteLine("***************** CREATING JOB {0} *********************", jobId);
            Console.WriteLine();

            //Obtain a collection of all available node agent SKUs.
            //This allows us to select from a list of supported 
            //VM image/node agent combinations.
            var nodeAgentSkus = batchClient.PoolOperations.ListNodeAgentSkus().ToList();

            //Define a delegate specifying properties of the VM image
            //that we wish to use

            Console.WriteLine("Looking for a Ubuntu Server 14.04 image reference...");
            Console.WriteLine();

            Func<ImageReference, bool> isUbuntu1404 = imageRef =>
                 imageRef.Publisher == "Canonical" &&
                 imageRef.Offer == "UbuntuServer" &&
                 imageRef.SkuId.Contains("14.04");

            //Obtain the first node agent SKU in the collection that matches
            //Ubuntu Server 14.04. Note that there are one or more image
            //references associated with this node agent SKU.
            var ubuntuAgentSku = nodeAgentSkus.First(sku => sku.VerifiedImageReferences.Any(isUbuntu1404));

            //Select an ImageReference from those available for node agent.
            var imageReference = ubuntuAgentSku.VerifiedImageReferences.First(isUbuntu1404);

            //Create auto pool
            var job = batchClient.JobOperations.CreateJob(jobId,
            new PoolInformation
            {
                AutoPoolSpecification = new AutoPoolSpecification
                {
                    AutoPoolIdPrefix = "linux-",
                    PoolSpecification = new PoolSpecification
                    {
                        TargetDedicated = int.Parse(Settings.Default.NodesCount),
                        VirtualMachineSize = Settings.Default.VmSize,
                        VirtualMachineConfiguration = new VirtualMachineConfiguration(imageReference: imageReference, nodeAgentSkuId: ubuntuAgentSku.Id)
                    },

                    KeepAlive = false,
                    PoolLifetimeOption = PoolLifetimeOption.Job
                }
            });

            //4. Commit job
            await job.CommitAsync();

            //5. Define tasks

            //simple task
            var pythonTask = new CloudTask("python-task", "python simple_task.py");
            pythonTask.ResourceFiles = new List<ResourceFile>();
            pythonTask.ResourceFiles.Add(new ResourceFile("https://batchgis.blob.core.windows.net/resourcefiles/simple_task.py", "simple_task.py"));
            await batchClient.JobOperations.AddTaskAsync(job.Id, pythonTask);

            //find files
            var findFilesTask = new CloudTask("find-files-python-task", "python find_files.py");
            findFilesTask.ResourceFiles = new List<ResourceFile>();
            findFilesTask.ResourceFiles.Add(new ResourceFile("https://batchgis.blob.core.windows.net/resourcefiles/find_files.py", "find_files.py"));
            await batchClient.JobOperations.AddTaskAsync(job.Id, findFilesTask);

        }

        private static async Task WaitForJobAndPrintOutputAsync(BatchClient batchClient, string jobId)
        {
            Console.WriteLine("***************** WAIT FOR ALL TASKS TO COMPLETE ON JOB {0} *********************", jobId);
            Console.WriteLine();

            //Use a task state monitor to monitor the status of your tasks
            var taskStateMonitor = batchClient.Utilities.CreateTaskStateMonitor();

            //Get all your tasks for the job
            var cloudTasks = await batchClient.JobOperations.ListTasks(jobId).ToListAsync();

            //Wait for all tasks to reach the completed state
            bool timeOut = await taskStateMonitor.WhenAllAsync(cloudTasks, TaskState.Completed, TimeSpan.FromMinutes(30));

            if (timeOut)
            {
                throw new Exception("Timed out waiting for tasks!");
            }

            //Show the output
            foreach (var task in cloudTasks)
            {
                Console.WriteLine("Task Id: {0}", task.Id);
                NodeFile standardOutFile = await task.GetNodeFileAsync(Constants.StandardOutFileName);
                var standardOutText = standardOutFile.ReadAsStringAsync();
                Console.WriteLine("Standard out: ");
                Console.WriteLine(standardOutText.Result);
                Console.WriteLine();
            }

            Console.WriteLine("Please press Enter to terminate the job");
            Console.ReadLine();

            var job = batchClient.JobOperations.GetJob(jobId);

            await job.TerminateAsync();

            Console.WriteLine("Job terminated");
            Console.WriteLine();
        }
    }
}

Como puedes ver la configuración es similar a la que te mostré en un post anterior, solo que en este caso debes utilizar máquinas virtuales para el uso de Linux de la siguiente forma:

  • Es necesario localizar dentro de las imágenes aquella que se corresponda con la distribución que quieres utilizar. En este caso he elegido Ubuntu Server 14.04. A día de hoy no están soportadas todas las distros (solo ubuntu y centOS) pero se irán incluyendo nuevas en el futuro.
  • Debes utilizar el objeto VirtualMachineConfiguration para indicar la imagen seleccionada y el id del agente que se corresponde con dicha distribución.

Esta es la configuración que necesitas para el archivo Settings:

Azure Batch - Settings
Azure Batch – Settings

Una vez finalizado el proceso obtendrás una salida como la siguiente:

Azure Batch Linux - Output
Azure Batch Linux – Output

¡Saludos!