Dynamic Deployment with Composite Windows Azure Webrole

In this blog post I am talking about how one can build a scaleable solution on Windows Azure for website deployments using Composite Windows Azure Webrole.

I will be writing about this demo I put up last week, its a 50sec video where I in a console application is typing a name and little as 20 sec later a Composite C1 Website is online at the name.opencms.s-innovations.net. The demo took around (4 days) 30hours to create, this includes research time and getting access to all the needed resources related to windows azure and dns.

Windows Azure

The great thing about Windows Azure is that it gives me as developer all the tools and infrastructure to build a system that can scale, making opportunities for great ideas to reach out and not just be something that’s running on your localhost dev machine or a shared hosting provider. Windows Azure gives me the power to just click a button and the solution will be running with more power and able to meet the demand. Of cause one first need a great idea that give value to other people, maybe this is the first step to something big.

Console Applicaiton

In the video I am using the Windows Azure Service Bus with a queue to post messages to from the console application. This is shown in the following code snippet.

                    package = new DeployPackage
                    {
                        DataCenterId = DataCenter.WestEurope.DataCenterId,
                        WebsiteZipUrl = url,
                        DeployPackageName = name,
                    };

                    db.DeployPackages.Add(package);                    

                    var message = new BrokeredMessage(package);
                    message.Properties.Add(Constants.DataCenterName, Constants.WestEuropeDataCenterName);

                    Task.WaitAll(db.SaveChangesAsync(), Client.SendAsync(message));

In the code snippet a DeployPackage is consisting of a url for a zipfile to deploy, a name for the dns record and a datacenterid for what datacenter to deploy to in Windows Azure. I left out a few database checks that the name is not already taken.

Cloud Service Worker Role

A worker role, running with two instances to ensure uptime, then listening to the Queue as shown in this code snippet.

                Client.OnMessageAsync(async (message) =>
                {

                    try
                    {

                        var package = message.GetBody<DeployPackage>();

                        Trace.TraceInformation("Body: " + package + "\n" +
                            "MessageID: " + message.MessageId + "\n" +
                            "DataCenter: " +
                           message.Properties[Constants.DataCenterName]);

If everything success the message is marked for completion and deleted, if anything fails one of the two instances will pick it up and try again. It’s now the worker roles responsibility to download the zip provided in the url, unzip it and upload it to the blob storage account.

I am using our Composite Windows Azure Webrole (CWAW) that can deploy and host websites dynamic from blob storage account.

Windows Azure Management Libraries

The more interesting part is now that the worker role needs to find a CWAW cloud service that can host the website and for this I am using the new (in prerelease) Windows Azure Management Libraries to find a deployed CWAW, obtain its ip, and then by a rest API call to dnsmadeeasy I add a record. 

                        X509Certificate2 cert = LoadCertificate(
                         StoreName.My,
                            StoreLocation.LocalMachine, CloudConfigurationManager.GetSetting("Composite.WindowsAzure.OpenCMS.Thumbprint"));

                        CertificateCloudCredentials cerd = new CertificateCloudCredentials(
                            CloudConfigurationManager.GetSetting("Composite.WindowsAzure.OpenCMS.SubscriptionId"), cert);                

                        using (var dataCenter = CloudContext.Clients.CreateManagementClient(cerd))
                        {
                            using (var compute = CloudContext.Clients.CreateComputeManagementClient(cerd))
                            {
                                var ctoken = new CancellationToken();
                                var hostedServices = await compute.HostedServices.ListAsync(ctoken);

                                var opencmsServices = hostedServices.Select(serv =>
                                {
                                    var match = Regex.Match(serv.ServiceName, "opencms-(.*)-(.*)");

                                    return new
                                    {
                                        Service = serv,
                                        IsMatch = match.Success,
                                        DataCenterId = match.Success ? match.Groups[1].Value : "",
                                        Instance = match.Success ? int.Parse(match.Groups[2].Value) : 0,
                                    };
                                }).Where(s => s.IsMatch).ToList();

In this example I created the cloud service myself, in production we will be using WAML to deploy the packages if none is deployed in the needed datacenter or either a max limit or resource monitoring indicates the CWAW is not able to handle more websites. In this example a certificate is used to talk with the management API.

                                var production = await compute.Deployments.GetBySlotAsync(opencmsServices.First().Service.ServiceName,
                                    DeploymentSlot.Production, ctoken);                               

                                var settings = new CompositeAzureSettings(
                                    new AzureXmlServiceConfigurationSettingProvider(XDocument.Parse(production.Configuration)));

                                var task1 = Task.Run(() =>
                                {
                                    var store = new AzureStorageStore(settings, dirs);
                                    WebsiteSettingsController controller = new WebsiteSettingsController(store, dirs);

                                    var website = new WebsiteSetting(name: package.DeployPackageName,
                                         storeName: string.Format("cwaw-{0}", package.DeployPackageName),
                                         writeBackToStore: true);
                                    website.Bindings.Add(new WebsiteBinding
                                    {
                                        Hostname = string.Format("{0}.opencms.s-innovations.net", package.DeployPackageName),
                                        Port = 80
                                    });

                                    controller.Add(website);
                                });

                                var task2 = Task.Run(() =>
                                {
                                    DnsMadeEasyClient dnsMadeEasy = new DnsMadeEasyClient(apiKey:DnsMadeEasyKey,
                                         secretKey:DnsMadeEasySecret);
                                    var domains = dnsMadeEasy.GetDomains().ToList();

                                    var record = dnsMadeEasy.CreateRecord(domains[0], package.DeployPackageName, "A",
                                        production.RoleInstances.SelectMany(endpoint => endpoint.InstanceEndpoints)
                                        .FirstOrDefault(ep => ep.Port == 80).VirtualIPAddress);
                                });

                                Task.WaitAll(task1, task2);
                              
                                await new HttpClient().GetStringAsync(string.Format("http://{0}.opencms.s-innovations.net", package.DeployPackageName));

In the last snippet you see the two tasks of the worker role. Task1 is using code from Composite.WindowsAzure version 1.0.0.5, that will be on nuget next week, to instruct the CWAW to setup the website. The WAML is used to get the configuration xml file containing connection settings for blob storage and container names. Task2 is setting up dns. 

Conlussion

This was a proof of concept of creating a dynamic deployment solution for composite c1 websites on windows azure. For production more validation and such will be needed. The reader may also notice how I just take the first domain when talking with dnsmadeeasy - at this time the dnsmadeeasy service was just used for this demo and therefore there was no other domains on the service.

So hopefully you will notice how opportunities becomes easier to carry out in life with the new windows azure management libraries, using just c# code to manage your windows azure subscription.



comments powered by Disqus