We recently started to use Elasticsearch for search within our application. Since we wanted an infrastructure where we could provision multiple services to a cluster we decided to build a VM cluster with docker hosts on each VM.
To be able to quickly reproduce the cluster we decided to automate this task. That is to create the Azure parts needed and the deployment of docker containers. You can do this using Powershell, Azure CLI, kubernetes, mesos etc. We grabbed the chance to use the .NET Azure Management API - so we had the full disposal of C# for a range of custom tasks.
The end result was a .Net command line application that takes a json file with a list of actions, and spin up x number of virtual machines with docker installed and the containers needed. For now we spin up Nginx as a reverse proxy and Elasticsearch behind it.
So this is what we automated:
At Azure level: creating/updating
a virtual network for the VMs
storage account for VM harddisks
cloud service for the VMs
uploading ssh keys for use with Linux VMs
virtual machines, including – using resource extensions to install the docker host – adding custom disks to the VMs – adding load balancers with a probe
and a hole range of custom tasks for setting up the docker hosts and their storage. You could use Puppet/Chef for this - but in order not to bring in another tool we did it with simple bash scripts.
If this is something you want to do then here is an overview of what we did. To get started all you need to do is to include a range of nuget management packages to a project. They are all starting with the name “Microsoft.WindowsAzure.Management” and then a postfix for each area you can manage, ie .Compute, .Network, .WebSites and so on.
Configuring the network
Since we want the cluster to be on its own network then we need to setup a local network. We can use the NetworkManagementClient for this task. However since the API is limited to getting/setting xml we must do a little messy XDocument to add a network. But first let us look at an example of the xml:
Here we have a network defined by the VirtualNetworkSite node named “test” and to be hosted in the “North Europe” datacenter. We allocate an overall address space 10.0.0.0/8 and a subnet 10.0.0.0/11. That is basically all we need (if you want the full overview of the schema then look here. So let us look at an example of how this can be done:
The purpose of this method is to either create or update a local network based on the settings parameter passed in. So first we get a NetworkManagementClient, and then we attempt to get the configuration xml. However, if no networks have been defined we will get a Not Found http status in return. In that case we will just use a xml template defined in BaseNetworkConfigurationXml. Then we do some xml-exercise to either update or add nodes/attributes. Finally we call SetConfigurationAsync to store the new configuration (hopefully you will not need to change your network configuration in parallel - since what would break pretty easily).
Creating a storage account
We need a storage account to place the VMs on - and in our case we need to add additional disks to the VMs.
Here we use the StorageManagementClient to either create or update the account. Here’s an example of how that could be done:
// Update method omitted ... pretty similar to the Create part
We start by looking up the storage account. If it does not exist we call the Create method where the real work is done. The remaining part of RunAsync just creates a list of containers specified in the settings we pass in. Only really interesting thing in Create is that you have to specify an account type, ie. “Standard_LRS”. You can see the list of options [here] (https://msdn.microsoft.com/en-us/library/azure/ee460802.aspx).
Create a cloud service to host the VMs
The virtual machines will run inside a cloud service. We use ComputeManagementClient to create it. Basically all we have to supply is a name for the service, and a location (or affinity group).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
publicstaticasync Task RunAsync(
CertificateCloudCredentials credentials,
Action<string, LogType> log,
UpdateCloudServiceSettings settings)
{
/* .. excluded a range of code guards **/
using (ComputeManagementClient client = CloudContext.Clients.CreateComputeManagementClient(credentials))
We first try to lookup (based on the thumbprint) if the certificate is already there. If not we upload it using CreateAsync. It must be exported to Pfx first. So now we have all the parts ready for creating the virtual machines.
Creating the VMs
When creating the virtual machines the interesting part is that we want to use the Docker Resource Extension. This is something you get for free when you use the Azure CLI (not in Powershell at the time I wrote this code). There is more coding to this, so let us break up the process.
if (e.Response.StatusCode != HttpStatusCode.NotFound)
{
throw;
}
}
...
VMs are placed in a deployment in the cloud service (you have a staging and a production slot in the deployment). So first step we do is to lookup the deployment the VM is to be added to. Now depending on whether we are creating the first VM, any subsequent one, or updating existing we will have to use different parts of the API. Let us take a look at how to create the first VM. A VM is a Role in the API. So to create a VM we could do this:
Here in the CreateRole method the virtualMachine parameter is configuration settings for the VM. The Role class contains the basic information about the VM like the name, machine size. The OSVirtualHardDisk refers to the OS image we want to base the VM on. Since we want the cluster to be resilient to Azure downtime we have the ability to specify an AvailabilitySet.
When specifying the role we provide an image the VM should be created with. Here is the helper function GetOsVirtualHardDisk:
MediaLink = new Uri(string.Format(CultureInfo.InvariantCulture, StorageUrl, virtualMachine.StorageAccount, virtualMachine.StoragePath)),
SourceImageName = virtualMachine.ImageName
};
}
Here we refer to a standard image (ie b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04-LTS-amd64-server-20140618.1-en-us-30GB).
To deploy the docker host we need to specify a resource extension. This is what is covered in the CreateResourceExtensions method. Perhaps it may also be useful to see the json that is specified for this method
The net result is that we need to specify an ResourceExtensionReference instance for docker where,
the Name is “DockerExtension”
Publisher is “MSOpenTech.Extensions”
ReferenceName is “DockerExtension”
and a version eg. “0.3” and two sets of key-value parameters – one is key = “dockerPort” with eg value = “4243” – and then the certificates to secure docker
As you can see from the code above we have a configurator configuration for parameters that requires special handling. So to configure the docker resource extension we use this configurator.
Basically the certificate part is just a question of providing a json value with base64 encodings for the certificates.
Back to CreateRole besides creating data disks which is just providing a list of DataVirtualHardDisk instances. Then the roles ConfigurationSets must be specified. This is done in