Automate & Orchestrate Azure

With the increasing use of Windows Azure in organizations, I see there is need to automate and integrate release management process to provisioning & deployment process of Azure services. I’m targeting my post to the folks who want to orchestrate cloud service deployment to Azure into release management process of their organization.

Provisioning/Deploying of cloud services to Azure is a multistep, asynchronous process and long running process. To automate the deployment we need to program using with Azure Management  APIs. There are multiple use cases for deployment that includes upgrade, update etc. and each one has different steps in the process of deployment.  I’ll be using the XecMe framework for my solution which simplicity. There is a good amount of simple configuration needed in app.config file, hence it is advisable to reference from Nuget.org

Since the deployment is a long running asynchronous process most of the time the process will need to wait for the operation to complete. To make the process reliable and resilient, it is important to save the intermittent states after each step in the process. Doing it on a thread is not a likely option due to possible the state loss if the process crash while deployment in progress. Let us put some guidelines before we start

  • There is a separate subscription for each application/portfolio/cost center. This will also ensure a separate invoice for each application/portfolio/cost center.
  • There is a dedicated subscription for Orchestration and releases storage account for queues (for requests) and blobs (for packages and config). Depending on whether you host the Orchestration on Azure or on premises, you may or may not need the cloud service associated.
  • The releases storage account of Orchestration has subscription-id container to hold the release packages for that subscription. Each blob in the container can be tied back to the change request in release management system.
  • Single shared Azure Management Certificate across all the subscriptions used for Orchestration

Given below if the diagram showing the high level architecture of how the different components are inter connected.

HL Arch

High-level architecture depicting the data flow between release management process and orchestration

SA is the Storage Account under the Orchestration subscription. As per the above diagram there will be a blob container (with the name as subscription-id) for each of the subscription from subscription 1 through subscription NCS is the cloud service which is optional only in case the orchestration is hosted on Azure. If you have on-premise orchestration, you will not need it.

The Orchestration

Orchestration can be an on-premise Windows Service process or a Worker Role in cloud service under Orchestration subscription. The Orchestration uses task based design approach, i.e. each of the activity within the orchestration a atomic task. To maintain the state of the orchestration, storage queues are used in release storage account.

Each task will have an input queue, an output queue and an error queue associated with it. Each task is also responsible to monitor the status for completion before putting the request to the subsequent task. Diagram below depict a typical task configuration.

This represents a task in the orchestration

This represents a task in the orchestration

From release management process, we have a input (request) queue where the release management process puts the deployment requests that has reference to service package & service configuration blobs (releases) subscription-id container. The output (response) queue where the Orchestration puts the updated request object for the release management process.

So let me enlist the different tasks that I’ll create for the service deployment.

  1. Validate Service - This task will read the request from input (release-management-request) queue, validate the request and place it in the output (validation-output) queue for the next task. If there is a validation error in the request, it updates the request with the error code and message and places the request in the error (response-to-release-management) queue
  2. Cloud Service – This task will take the request from input (validation-output) queue and will Create, Update or Delete the Cloud Service as per the request. It will monitor the status of the request to complete before putting it to the subsequent task on output (cloud-service-output) queue. In case of failure, it updates the request with the error code and message from Azure and places the request on the error (response-to-release-management) queue.
  3. Storage ServiceThis task will take the incoming request from input (cloud-service-service) queue and Create, Update or Delete the Storage Account. It will monitor the status of the request to complete before putting it to the subsequent task  in output (storage-service-output) queue. In case of failure, it updates the request with the error code and message from Azure and places the request on the error (response-to-release-management) queue.
  4. Deployment ServiceThis task will take the incoming request  from input (storage-service-output) and Create, Upgrade, Swap, Change Config, Update Status or Delete the deployment. It will monitor the status of the request to complete before putting it to the subsequent task queue. In case of failure, it updates the request with the error code and message from Azure and places the request on the error (response-to-release-management) queue.

Using the above identified tasks, lets orchestrate them using the design pattern Chain of Responsibility. Below given is the design diagram of configuring the above defined task.

Pipeline

Tasks

Each of the above tasks the following things in common

  • Reading the input queue
  • Writing into output queue
  • Writing into error queue
  • Rewriting the updated request to the input queue
  • Checking the status of current operation

We can create a BaseTask for all the task, this base task should implement ITask

public abstract class BaseTask : ITask
{
    private CloudStorageAccount _storageAccount;
    private CloudQueue _inputQ, _outputQ, _errorQ;
    protected Api _api;

    protected virtual void FollowUp(Request req)
    { 
        // No follow up task
        req.CurrentRequestId = null; 
    }

    protected virtual ExecutionState Process(Request req)
    { return ExecutionState.Executed; }

    public virtual ExecutionState OnExecute(ExecutionContext context)
    {
        Request req = ReadInputQueue();

        ///Indicate that there is no work, can take a break
        if (req == null)
            return ExecutionState.Idle;

        ///Request is in progress check the status
        if (!string.IsNullOrEmpty(req.CurrentRequestId))
        {
            Entities.OperationStatus os = _api.GetRequestStatus(req.SubscriptionId, req.CurrentRequestId);
            switch (os.Status)
            {
                case Entities.RequestStatus.Failed: /// Put the request in the error queue
                    req.Error = new Azure.Entities.Requests.Error() { Code = os.Error.Code, Message = os.Error.Message, RequestId = req.CurrentRequestId };
                    req.CurrentRequestId = null;
                    WriteToErrorQueue(req);
                    break;
                case Entities.RequestStatus.InProgress: /// Skip this and it will be invisible for some time
                    return ExecutionState.Executed;
                case Entities.RequestStatus.Succeeded:/// Put it to next guy in the pipeline
                    FollowUp(req);
                    if(req.CurrentRequestId == null)
                        WriteToOutputQueue(req);
                    break;
                default:
                    throw new InvalidOperationException(string.Format("Unexpected status {0} returned by the Azure for Operation {1}", os.Status, req.CurrentRequestId));
            }
            return ExecutionState.Executed;
        }

        try
        {
            return Process(req);
        }
        catch (WebException webEx)
        {
            req.CurrentRequestId = webEx.Response.Headers["x-ms-request-id"];
            using (Stream stream = webEx.Response.GetResponseStream())
            using(TraceStream reader = new TraceStream(stream))
            {
                //string error = reader.ReadToEnd();
                XmlSerializer ser = new XmlSerializer(typeof(Azure.Entities.Error));
                Azure.Entities.Error err = (Azure.Entities.Error)ser.Deserialize(reader);
                req.Error = new Entities.Requests.Error()
                {
                    Code = err.Code,
                    Message = err.Message,
                    RequestId = req.CurrentRequestId
                };
            }
            WriteToErrorQueue(req);
            return ExecutionState.Executed;
        }
        catch(Exception e)
        {
            req.Error = new Entities.Requests.Error()
            {
                Code = "Unhandled exception from Azure Management Portal",
                Message = e.Message,
                RequestId = req.CurrentRequestId
            };
            WriteToErrorQueue(req);
            return ExecutionState.Executed;
        }

    }

    public virtual void OnStart(ExecutionContext context)
    {
        _storageAccount = CloudStorageAccount.Parse(ConfigurationManager.ConnectionStrings["StorageAccount"].ConnectionString);
        CloudQueueClient client = _storageAccount.CreateCloudQueueClient();
        _inputQ = client.GetQueueReference(context.Parameters["input"]);
        _inputQ.CreateIfNotExists();

        _outputQ = client.GetQueueReference(context.Parameters["output"]);
        _outputQ.CreateIfNotExists();

        _errorQ = client.GetQueueReference(context.Parameters["error"]);
        _errorQ.CreateIfNotExists();

        X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadOnly);
        X509Certificate2Collection coll = store.Certificates.Find(X509FindType.FindByThumbprint, context.Parameters["cert"], false);
        if (coll.Count == 1)
        {
            _api = new Api(coll[0]);
        }
        else
        {
            throw new ArgumentException("Azure Management Certificate could not be found");
        }
    }

    public virtual void OnStop(ExecutionContext context)
    {
    }

    public virtual void OnUnhandledException(Exception e)
    {
        if (e is WebException)
        {
            WebException webEx = (WebException)e;
            Trace.TraceError("Error while executing the task: {0}", webEx);
        }
        Trace.TraceError("Error while executing the task: {0}", e);
    }

    protected string GetSharedAccessSignature(string containerName)
    {
        var container = _storageAccount.CreateCloudBlobClient().GetContainerReference(containerName);
        container.CreateIfNotExists();
        return container.GetSharedAccessSignature(new SharedAccessBlobPolicy()
        {
            Permissions = SharedAccessBlobPermissions.Read,
            SharedAccessExpiryTime = DateTime.UtcNow.AddHours(5)// 5 hours the share access key will be available for Azure to access the packages
        });
    }

    protected Request ReadInputQueue()
    {
        CloudQueueMessage message = _inputQ.GetMessage(TimeSpan.FromSeconds(5));
        if (message != null)
        {
            Request retVal = message.GetValue();
            retVal.MessageId = message.Id;
            retVal.PopReceipt = message.PopReceipt;
            return retVal;
        }
        return null;
    }

    protected void WriteToOutputQueue(Request request)
    {
        CloudQueueMessage message = new CloudQueueMessage((string)null);

        ///Remove the current request id as its been already checked by current task
        request.CurrentRequestId = null;
        message.PutValue(request);
        _outputQ.AddMessage(message);

            //Delete the request from the input queue
        DeleteMessageFromInputQueue(request.MessageId, request.PopReceipt, request.RequestId);
    }

    private void DeleteMessageFromInputQueue(string msgId, string popReceipt, string reqId)
    {
        try
        {
            //Delete the request from the input queue
            _inputQ.DeleteMessage(msgId, popReceipt);
        }
        catch (Exception e)
        {
            Trace.TraceError("Error while deleting the message from input queue for request: {0} with error\n{1}", reqId, e.ToString());
        }
    }

    protected void RewriteToInputQueue(Request request)
    {
        CloudQueueMessage message = new CloudQueueMessage((string)null);
        message.PutValue(request);
        _inputQ.AddMessage(message);

        //Delete the request from the input queue
        DeleteMessageFromInputQueue(request.MessageId, request.PopReceipt, request.RequestId);
    }

    protected void WriteToErrorQueue(Request request)
    {
        CloudQueueMessage message = new CloudQueueMessage((string)null);
        request.CurrentRequestId = null;
        message.PutValue(request);
        _errorQ.AddMessage(message);

        //Delete the request from the input queue
        DeleteMessageFromInputQueue(request.MessageId, request.PopReceipt, request.RequestId);
    }

    protected void CopyProperties(List src, List dest)
    {
        for (int i = 0; i < src.Count; i++)
        {
            Azure.Entities.Requests.ExtendedProperty s = src[i];
            dest.Add(new Entities.ExtendedProperty() { Name = s.Name, Value = s.Value});
        }
    }

    protected string LoadConfigFromUrl(string url)
    {
        StringBuilder sb = new StringBuilder();
        CloudBlobClient client = _storageAccount.CreateCloudBlobClient();
        ICloudBlob blob = client.GetBlobReferenceFromServer(new Uri(url));
        using (MemoryStream ms = new MemoryStream())
        {
            blob.DownloadToStream(ms);
            return Convert.ToBase64String(ms.ToArray(), Base64FormattingOptions.None);
        }
    }
}

Rest of the tasks should be be inheriting from the BaseTask to implement specific function pertaining to the task.

Validate Service

This task validate the request from Release Management Process. It does some basic checks like subscription Id exists and is in valid format. We can add more validations as we progress.

public class RequestValidation : BaseTask
{
    public override ExecutionState OnExecute(ExecutionContext context)
    {
        Request req = ReadInputQueue();

        ///Indicate that there is no work, can take a break
        if (req == null)
            return ExecutionState.Idle;

        if (string.IsNullOrEmpty(req.SubscriptionId))
        {
            req.Error = new Error() { RequestId = "-1", Code = "-1", Message = "Subscription Id is not provided for the request" };
            WriteToErrorQueue(req);
        }
        else
        {
            WriteToOutputQueue(req);
        }
        return ExecutionState.Executed;
        }
    }
}

Cloud Service

This task Creates, Updates or Deletes the Cloud Service, it also waits until the task is completed which is part of the base class implementation

public class CloudService : BaseTask
{
    protected override ExecutionState Process(Request req)
    {
        ///If there is no Hosted Service part in the request, just move it to next guy in the pipeline
        if (req.HostedService == null)
        {
            WriteToOutputQueue(req);
            return ExecutionState.Executed;
        }

        if (string.IsNullOrEmpty(req.HostedService.ServiceName))
        {
            req.Error = new Error() { RequestId = "-1", Code = "-1", Message = "Service name is missing for the Hosted Service request " + req.SubscriptionId };
            WriteToErrorQueue(req);
        }

        switch (req.HostedService.Operation)
        {
            case HostedServiceBase.OperationType.Create:
                {
                    CreateHostedService chs = (CreateHostedService)req.HostedService;
                    Entities.Hosted.CreateHostedService r = new Entities.Hosted.CreateHostedService()
                    {
                        Description = chs.Description,
                        Label = chs.Label,
                        ServiceName = chs.ServiceName,
                        AffinityGroup = chs.AffinityGroup,
                        Location = chs.Location
                    };
                    CopyProperties(chs.Properties, r.ExtendedProperties);
                    req.CurrentRequestId = _api.Hosting.CreateHostedService(req.SubscriptionId, r);
                    break;
                }
            case HostedServiceBase.OperationType.Update:
                {
                    UpdateHostedService uhs = (UpdateHostedService)req.HostedService;
                    Entities.Hosted.UpdateHostedService r = new Entities.Hosted.UpdateHostedService()
                    {
                        Description = uhs.Description,
                        Label = uhs.Label,
                    };
                    CopyProperties(uhs.Properties, r.ExtendedProperties);
                    req.CurrentRequestId = _api.Hosting.UpdateHostedService(req.SubscriptionId, r, uhs.ServiceName);
                }
                break;
            case HostedServiceBase.OperationType.Delete:
                req.CurrentRequestId = _api.Hosting.DeleteHostedService(req.SubscriptionId, req.HostedService.ServiceName);
                break;
            default:
                throw new InvalidOperationException(string.Format("Unknown operation {0} for the Hosted Service for subscription {1}", req.HostedService.Operation, req.SubscriptionId));
        }

        ///Monitor the operation until completed
        RewriteToInputQueue(req);
        return ExecutionState.Executed;
    }
}

Storage Service

This task Create, Update or Delete the storage account for a subscription. It will wait until the task is completed.

public class StorageService : BaseTask
{
    protected override ExecutionState Process(Request req)
    {
        ///If there is no Storage Service part in the request, just move it to next guy in the pipeline
        if (req.StorageService == null)
        {
            WriteToOutputQueue(req);
            return ExecutionState.Executed;
        }

        if (string.IsNullOrEmpty(req.StorageService.ServiceName))
        {
            req.Error = new Error() { RequestId = "-1", Code = "-1", Message = "Service name is missing for the Storage Service request " + req.SubscriptionId };
            WriteToErrorQueue(req);
        }

        switch (req.StorageService.Operation)
        {
            case StorageBase.OperationType.Create:
                {
                    CreateStorage scr = (CreateStorage)req.StorageService;
                    CreateStorageService r = new CreateStorageService()
                    {
                        ServiceName = scr.ServiceName,
                        Description = scr.Description,
                        Label = scr.Label,
                        GeoReplicationEnabled = scr.GeoReplicationEnabled,
                        AffinityGroup = scr.AffinityGroup
                    };
                    req.CurrentRequestId = _api.Storage.CreateHostedService(req.SubscriptionId, r);
                }
                break;
            case StorageBase.OperationType.Update:
                {
                    UpdateStorage usr = (UpdateStorage)req.StorageService;
                    UpdateStorageService r = new UpdateStorageService()
                    {
                        Description = usr.Description,
                        Label = usr.Label,
                        GeoReplicationEnabled = usr.GeoReplicationEnabled
                    };
                    req.CurrentRequestId = _api.Storage.UpdateStorageService(req.SubscriptionId, r, usr.ServiceName);
                }
                break;
            case StorageBase.OperationType.Delete:
                {
                    DeleteStorage dsr = (DeleteStorage)req.StorageService;
                    req.CurrentRequestId = _api.Storage.DeleteStorageService(req.SubscriptionId, dsr.ServiceName);
                }
                break;
            default:
                throw new InvalidOperationException(string.Format("Unknown operation {0} for the Hosted Service for subscription {1}", req.StorageService.Operation, req.SubscriptionId));
        }
        return ExecutionState.Executed;
    }
}

Deployment Service

The final step is to deploy the package to the Cloud Service.

public class DeploymentService : BaseTask
{
    protected override ExecutionState Process(Request req)
    {
        ///If there is no Deployment part in the request, just move it to next guy in the pipeline
        if (req.Deployment == null)
        {
            WriteToOutputQueue(req);
            return ExecutionState.Executed;
        }

        switch (req.Deployment.Operation)
        {
            case DeploymentBase.OperationType.Deploy:
                {
                    Azure.Entities.Requests.Deployment d = (Entities.Requests.Deployment)req.Deployment;
                    CreateDeployment r = new CreateDeployment()
                    {
                        Configuration = this.LoadConfigFromUrl(d.Configuration),
                        Label = d.Label,
                        Name = d.Name,
                        PackageUrl = string.Concat(d.PackageUrl,"&", this.GetSharedAccessSignature(req.SubscriptionId)),
                        StartDeployment = d.StartDeployment,
                        TreatWarningsAsError = d.TreatWarningsAsError
                    };
                    CopyProperties(d.Properties, r.ExtendedProperties);
                    req.CurrentRequestId = _api.Hosting.CreateDeployment(req.SubscriptionId, r, d.ServiceName, d.DeploymentSlot);
                }
                break;
            case DeploymentBase.OperationType.Upgrade:
                {
                    Azure.Entities.Requests.Upgrade u = (Azure.Entities.Requests.Upgrade)req.Deployment;
                    UpgradeDeployment r = new UpgradeDeployment()
                    {
                        Configuration = this.LoadConfigFromUrl(u.Configuration),
                        Force = u.Force,
                        Label = u.Label,
                        Mode = u.Mode,
                        PackageUrl = string.Concat(u.PackageUrl,"&", this.GetSharedAccessSignature(req.SubscriptionId)),
                        RoleToUpgrade = u.RoleToUpgrade
                    };
                    CopyProperties(u.Properties, r.ExtendedProperties);
                    if (string.IsNullOrEmpty(u.DeploymentName))
                        req.CurrentRequestId = _api.Hosting.UpgradeDeployment(req.SubscriptionId, r, u.ServiceName, u.DeploymentSlot);
                    else
                        req.CurrentRequestId = _api.Hosting.UpgradeDeployment(req.SubscriptionId, r, u.ServiceName, u.DeploymentName);
                }
                break;
            case DeploymentBase.OperationType.Swap:
                {
                    Azure.Entities.Requests.Swap s = (Azure.Entities.Requests.Swap)req.Deployment;
                    Azure.Entities.Hosted.Swap r = new Entities.Hosted.Swap()
                    {
                        Production = s.Production,
                        SourceDeployment = s.SourceDeployment
                    };
                    req.CurrentRequestId = _api.Hosting.SwapDeployment(req.SubscriptionId, r, s.ServiceName);
                }
                break;
            case DeploymentBase.OperationType.Change:
                {
                    ChangeConfig c = (ChangeConfig)req.Deployment;
                    ChangeConfiguration r = new ChangeConfiguration()
                    {
                        Configuration = this.LoadConfigFromUrl(c.Configuration),
                        Mode = c.Mode,
                        TreatWarningsAsError = c.TreatWarningsAsError
                    };
                    CopyProperties(c.Properties, r.ExtendedProperties);
                    if (string.IsNullOrEmpty(c.DeploymentName))
                        req.CurrentRequestId = _api.Hosting.ChangeDeploymentConfig(req.SubscriptionId, r, c.ServiceName, c.DeploymentSlot);
                    else
                        req.CurrentRequestId = _api.Hosting.ChangeDeploymentConfig(req.SubscriptionId, r, c.ServiceName, c.DeploymentName);
                }
                break;
            case DeploymentBase.OperationType.UpdateStatus:
                {
                    Azure.Entities.Requests.UpdateDeploymentStatus uds = (Azure.Entities.Requests.UpdateDeploymentStatus)req.Deployment;
                    Azure.Entities.Hosted.UpdateDeploymentStatus r = new Entities.Hosted.UpdateDeploymentStatus()
                    {
                        Status = uds.Status
                    };

                    if (string.IsNullOrEmpty(uds.DeploymentName))
                        req.CurrentRequestId = _api.Hosting.UpdateDeploymentStatus(req.SubscriptionId, r, uds.ServiceName, uds.DeploymentSlot);
                    else
                        req.CurrentRequestId = _api.Hosting.UpdateDeploymentStatus(req.SubscriptionId, r, uds.ServiceName, uds.DeploymentName);
                }
                break;
            case DeploymentBase.OperationType.Delete:
                {
                    DeleteDeployment dd = (DeleteDeployment)req.Deployment;
                    if (string.IsNullOrEmpty(dd.DeploymentName))
                        req.CurrentRequestId = _api.Hosting.DeleteDeployment(req.SubscriptionId, dd.ServiceName, dd.DeploymentSlot);
                    else
                        req.CurrentRequestId = _api.Hosting.DeleteDeployment(req.SubscriptionId, dd.ServiceName, dd.DeploymentName);
                }
                break;
            default:
                throw new InvalidOperationException(string.Format("Unknown operation {0} for the Deployment Service for subscription {1}", req.Deployment.Operation, req.SubscriptionId));
        }

        return ExecutionState.Executed;
    }
}

In a single request, you are allowed to perform multiple tasks.

Storage & Access

The release storage account that hold the packages and the deployment requests belongs to Orchestration. To have proper access to all the parties involved in automating the deployment including the Azure we need to lay some rules. This rules may vary in your implementation and is greatly dictated by organization

  1. Orchestration process uses the primary key for the release storage account
  2. The secondary key for the release storage account is used by Release Management Process for uploading the packages and configuration into the blob and putting the deployment request into the request queue.
  3. For the Azure to access the package & config off the blob, Shared Access Signing is used for the URLs

Security Considerations

Since Orchestration needs Azure Management Certificate for the API calls to work, the certificate needs be uploaded to the Cloud Service that host this Orchestration engine. Alternately if the Orchestration engine is hosted on-premises then the hosting server should have the certificate.

Configuration Options

XecMe give you lot of options without any code changes. Based on the load (number of deployment requests) you expect for the deployment requests and the box the Orchestration engine will deployed on, we could configure the task differently.

Timer

The task can be configured to use Timer task enabling the tasks to run periodically irrespective of how many pending requests are in queue. You can use this approach when the number of the requests is not too high. You can configure 30 seconds or 60 seconds.

<timerTaskRunner name="cloud serivce" taskType="Azure.Deployment.Tasks.CloudService, Azure.Deployment, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
                         interval="1000">
  <parameters>
    <parameter name="input" value="input-cloud-service" />
    <parameter name="output" value="input-storage-service" />
    <parameter name="error" value="response" />
    <parameter name="cert" value="‎b8 6e 79 16 20 f7 59 f1 7b 8d 25 e3 8c a8 be 32 e7 d5 ea c2" />
  </parameters>
</timerTaskRunner>

Parallel

The task can be configured to use Parallel task  enabling the tasks to run immediately if there are pending requests in the queue. If there was an idle (no work) the it can sleep for configured amount of time. You can also configure more than 1 thread to do task.

<parallelTaskRunner name="cloud serivce" taskType="Azure.Deployment.Tasks.CloudService, Azure.Deployment, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
                         minInstances="1" maxInstances="1" idlePollingPeriod="2000">
  <parameters>
    <parameter name="input" value="input-cloud-service" />
    <parameter name="output" value="input-storage-service" />
    <parameter name="error" value="response" />
    <parameter name="cert" value="‎b8 6e 79 16 20 f7 59 f1 7b 8d 25 e3 8c a8 be 32 e7 d5 ea c2" />
  </parameters>
</parallelTaskRunner>

Deployment Options

XecMe at rescue we have multiple deployment options without any code changes. I’ll illustrate 2 options for the deployment.

On-Premises (Windows Service)

For on-premises deployment of Azure orchestrater, we can use Windows Service. XecMeHost.exe part of XecMe framework, can serve as Windows Service host. For instance, let’s assume that we want to install the service from f:\AzAuto. Copy all the assemblies into this folder and run the command given below to install the service

f:\AzAuto\XecMeHost -n:"My Azure Automation" -p:"f:\AzAuo" 
-c:"f:\AzAuto\Deployment.dll.config" -i

The above command installs My Azure Automation windows service, there are other parameters like -a for account type, -id for specifying User Account, -m for Start Type.

Azure Cloud Service (Worker Role)

For deploying the orchestrater as Worker Role in a Cloud Service, we can piggyback cloud service we used for the storage account. Given below is the Worker Role sample, fairly simple.

public class OrchestratorRole : RoleEntryPoint
{
    public override void Run()
    {
        TaskManager.Start(new TaskManagerConfig());
        TaskManager.WaitTasksToComplete();
    }

    public override bool OnStart()
    {
        // Set the maximum number of concurrent connections 
        ServicePointManager.DefaultConnectionLimit = 12;

        // For information on handling configuration changes
        // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.

        return base.OnStart();
    }
}

We also need to add the configuration needed to host the tasks. You can configure 2 very small to small instances based on your requirement for Orchestrater.

You can download the code for this project from AzEzAuto

Shailesh Lolam

Shailesh Lolam is an architect, designer & developer, he started career with Perl and then VC++, VB, ASP and the list went on. He experiences involve solutioning, architecting, consulting, heterogeneous system integrations & building large complex enterprise systems. He is passionate of optimizing, automating business processes and systems through technology enablement to benefit business & to reduce the cost.

More Posts - Website

Follow Me:
TwitterLinkedIn

%d bloggers like this: