Automate VM deployment with Azure + Docker

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<NetworkConfiguration xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="ht
tp://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/Ser
viceHosting/2011/07/NetworkConfiguration">
<VirtualNetworkConfiguration>
<Dns />
<VirtualNetworkSites>
<VirtualNetworkSite name="test" Location="North Europe">
<AddressSpace>
<AddressPrefix>10.0.0.0/8</AddressPrefix>
</AddressSpace>
<Subnets>
<Subnet name="Subnet">
<AddressPrefix>10.0.0.0/11</AddressPrefix>
</Subnet>
</Subnets>
</VirtualNetworkSite>
</VirtualNetworkSites>
</VirtualNetworkConfiguration>
</NetworkConfiguration>

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:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public static async Task RunAsync(CertificateCloudCredentials credentials, Action<string, LogType> log, UpdateNetworkSettings settings)
{
// .. excluded a bunch of code guards
using (
NetworkManagementClient client = CloudContext.Clients.CreateVirtualNetworkManagementClient(credentials))
{
string configurationXml = BaseNetworkConfigurationXml;
try
{
NetworkGetConfigurationResponse networkConfiguration = await client.Networks.GetConfigurationAsync();
configurationXml = networkConfiguration.Configuration;
}
catch (CloudException e)
{
if (e.Response.StatusCode != System.Net.HttpStatusCode.NotFound)
{
throw;
}
}
XDocument document = XDocument.Parse(configurationXml);
XNamespace ns = @"http://schemas.microsoft.com/ServiceHosting/2011/07/NetworkConfiguration";
if (document.Root == null)
{
throw new InvalidOperationException("No network configuration element in the xml");
}
XElement configuration = document.Root.Element(ns + VirtualNetworkConfigurationElementName);
if (configuration == null)
{
configuration = new XElement(ns + VirtualNetworkConfigurationElementName);
document.Root.Add(configuration);
}
XElement sites = configuration.Element(ns + VirtualNetworkSitesElementName);
if (sites == null)
{
sites = new XElement(ns + VirtualNetworkSitesElementName);
configuration.Add(sites);
}
XElement site =
sites.Elements(ns + VirtualNetworkSiteElementName)
.FirstOrDefault(s => s.Attribute("name") != null && s.Attribute("name").Value == settings.Name);
if (site == null)
{
site = new XElement(ns + VirtualNetworkSiteElementName);
sites.Add(site);
}
else if (!settings.UpdateExisting)
{
return;
}
site.SetAttributeValue("name", settings.Name);
site.SetAttributeValue("Location", settings.Location);
List<XElement> subnets = settings.Subnets.Select(subnetDefinition =>
new XElement(ns + SubnetElementName, new XAttribute("name", subnetDefinition.Name),
new XElement(ns + AddressPrefixElementName, new XText(subnetDefinition.Addresses)))).ToList();
site.ReplaceNodes(new XElement(ns + AddressSpaceElementName,
new XElement(ns + AddressPrefixElementName, new XText(settings.AddressSpace))),
new XElement(ns + SubnetsElementName, subnets));
await client.Networks.SetConfigurationAsync(new NetworkSetConfigurationParameters(document.ToString()));
}
}
}

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:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
private const string BlobConnectionString = "DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}";
public static async Task RunAsync(
CertificateCloudCredentials credentials,
Action<string, LogType> log,
UpdateStorageAccountSettings settings)
{
/* .. excluded a range of code guards **/
using (StorageManagementClient client = CloudContext.Clients.CreateStorageManagementClient(credentials))
{
string name = settings.Name.ToLowerInvariant();
StorageAccountGetResponse existingStorageAccount = null;
try
{
existingStorageAccount = await client.StorageAccounts.GetAsync(name);
}
catch (CloudException e)
{
if (e.Response.StatusCode != HttpStatusCode.NotFound)
{
throw;
}
}
if (existingStorageAccount == null)
{
await Create(settings, name, client);
}
else
{
await Update(settings, name, client);
}
if (settings.Containers != null && settings.Containers.Count > 0)
{
StorageAccountGetKeysResponse storageKeys = await client.StorageAccounts.GetKeysAsync(name);
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
string.Format(CultureInfo.InvariantCulture, BlobConnectionString, name, storageKeys.PrimaryKey));
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
foreach (string container in settings.Containers)
{
log(string.Format(
CultureInfo.InvariantCulture,
@"Checking/Creating container {0} in storage account {1}",
container, name), LogType.Information);
CloudBlobContainer containerReference = blobClient.GetContainerReference(container);
await containerReference.CreateIfNotExistsAsync();
}
}
}
}
private static async Task Create(UpdateStorageAccountSettings settings, string name, StorageManagementClient client)
{
StorageAccountCreateParameters createParameters = new StorageAccountCreateParameters {
AccountType = settings.Type,
Name = name,
Label = name.ToBase64(),
Description =
settings.Description,
};
if (!string.IsNullOrWhiteSpace(settings.AffinityGroup))
{
createParameters.AffinityGroup = settings.AffinityGroup;
}
else
{
createParameters.Location = settings.Location;
}
await client.StorageAccounts.CreateAsync(createParameters);
}
// 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
public static async Task RunAsync(
CertificateCloudCredentials credentials,
Action<string, LogType> log,
UpdateCloudServiceSettings settings)
{
/* .. excluded a range of code guards **/
using (ComputeManagementClient client = CloudContext.Clients.CreateComputeManagementClient(credentials))
{
HostedServiceGetResponse existingService = null;
try
{
existingService = await client.HostedServices.GetAsync(settings.Name);
}
catch (CloudException e)
{
if (e.Response.StatusCode != HttpStatusCode.NotFound)
{
throw;
}
}
if (existingService == null)
{
HostedServiceCreateParameters parameters = new HostedServiceCreateParameters {
Description = settings.Description,
Label = settings.Name.ToBase64(),
ServiceName = settings.Name
};
if (!string.IsNullOrWhiteSpace(settings.AffinityGroup))
{
parameters.AffinityGroup = settings.Name;
}
else
{
parameters.Location = settings.Location;
}
await client.HostedServices.CreateAsync(parameters);
}
else
{
await client.HostedServices.UpdateAsync(
existingService.ServiceName,
new HostedServiceUpdateParameters { Description = settings.Description });
}
}
}

Uploading SSH certificate for the linux VMs

We want our SSH to use a certificate and disable password based authentication. So we upload an SSL certificate like this (a pem file):

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
public static async Task RunAsync(
CertificateCloudCredentials credentials,
Action<string, LogType> log,
UploadCertificateSettings settings)
{
/* .. excluded a range of code guards **/
using (ComputeManagementClient client = CloudContext.Clients.CreateComputeManagementClient(credentials))
{
ServiceCertificateGetResponse certificate = null;
X509Certificate2 certificateToUpload = new X509Certificate2(settings.File);
try
{
certificate = await client.ServiceCertificates.GetAsync(
new ServiceCertificateGetParameters(
settings.ServiceName,
"sha1",
certificateToUpload.Thumbprint));
}
catch (CloudException e)
{
if (e.Response.StatusCode != HttpStatusCode.NotFound)
{
throw;
}
}
if (certificate == null)
{
log("Uploading certificate", LogType.Information);
byte[] certificateContent = certificateToUpload.Export(X509ContentType.Pfx);
await
client.ServiceCertificates.CreateAsync(
settings.ServiceName,
new ServiceCertificateCreateParameters
{
CertificateFormat = CertificateFormat.Pfx,
Data = certificateContent
});
}
else
{
log("Certificate already uploaded", LogType.Information);
}
}
}

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static async Task RunAsync(
CertificateCloudCredentials credentials,
Action<string, LogType> log,
UpdateVirtualMachinesInCloudServiceSettings settings)
{
/* .. excluded a range of code guards **/
using (ComputeManagementClient client = CloudContext.Clients.CreateComputeManagementClient(credentials))
{
DeploymentGetResponse deployment = null;
try
{
// Check if the deployment exists
deployment = await client.Deployments.GetByNameAsync(
settings.ServiceName, settings.ServiceName);
}
catch (CloudException e)
{
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:

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
47
48
49
50
51
private static async Task CreateFirst(ComputeManagementClient client, UpdateVirtualMachinesInCloudServiceSettings settings, VirtualMachine virtualMachineSettings)
{
Role role = CreateRole(virtualMachineSettings);
VirtualMachineCreateDeploymentParameters createParameters = new VirtualMachineCreateDeploymentParameters
{
DeploymentSlot = DeploymentSlot.Production,
Label = settings.ServiceName,
Name = settings.ServiceName,
Roles = new List<Role> { role },
LoadBalancers = CreateLoadBalancers(settings.LoadBalancers)
};
if (!string.IsNullOrWhiteSpace(settings.VirtualNetworkName))
{
createParameters.VirtualNetworkName = settings.VirtualNetworkName;
}
await client.VirtualMachines.CreateDeploymentAsync(settings.ServiceName, createParameters);
}
private static Role CreateRole(VirtualMachine virtualMachine)
{
Role role = new Role
{
ProvisionGuestAgent = true,
RoleName = virtualMachine.Name,
Label = virtualMachine.Name,
ConfigurationSets = new List<ConfigurationSet>(),
RoleSize = virtualMachine.Size,
RoleType = VirtualMachineRoleType.PersistentVMRole.ToString(),
OSVirtualHardDisk = GetOsVirtualHardDisk(virtualMachine)
};
if (!string.IsNullOrEmpty(virtualMachine.AvailabilitySet))
{
role.AvailabilitySetName = virtualMachine.AvailabilitySet;
}
if (virtualMachine.ResourceExtensions != null)
{
role.ResourceExtensionReferences = CreateResourceExtensions(virtualMachine.ResourceExtensions);
}
if (virtualMachine.DataDisks != null)
{
role.DataVirtualHardDisks = CreateDataDisks(virtualMachine.DataDisks);
}
ConfigureMachine(virtualMachine, role.ConfigurationSets);
ConfigureNetwork(virtualMachine.NetworkConfiguration, role.ConfigurationSets);
return role;
}

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:

1
2
3
4
5
6
7
private static OSVirtualHardDisk GetOsVirtualHardDisk(VirtualMachine virtualMachine)
{
return new OSVirtualHardDisk {
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"resourceExtensions": [
{
"name": "DockerExtension",
"publisher": "MSOpenTech.Extensions",
"referenceName": "DockerExtension",
"version": "0.3",
"state": "enable",
"useConfigurator": true,
"resourceExtensionParameterValues": [
{
"key": "certificateDirectory",
"value": "{certificateDirectory}"
},
{
"key": "dockerPort",
"value": "4243"
}
]
}
],
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
47
48
49
private static IList<ResourceExtensionReference> CreateResourceExtensions(
IEnumerable<ResourceExtensionReferenceSettings> referenceSettings)
{
List<ResourceExtensionReference> references = new List<ResourceExtensionReference>();
foreach (ResourceExtensionReferenceSettings settings in referenceSettings)
{
IList<ResourceExtensionParameterValue> resourceExtensionParameterValues;
if (!settings.UseConfigurator)
{
resourceExtensionParameterValues =
settings.ResourceExtensionParameterValues.Select(
values =>
new ResourceExtensionParameterValue
{
Key = values.Key,
Type = values.Type,
Value = values.Value
}).ToList();
}
else
{
bool hasConfigurator = ResourceExtensionConfigurators.ContainsKey(settings.Name);
if (!hasConfigurator)
{
throw new InvalidOperationException(
string.Format(
CultureInfo.InvariantCulture,
"Unknown resource extension {0} - found no registered configurator. Cannot continue.",
settings.Name));
}
resourceExtensionParameterValues =
ResourceExtensionConfigurators[settings.Name](settings.ResourceExtensionParameterValues);
}
ResourceExtensionReference reference = new ResourceExtensionReference
{
Name = settings.Name,
Publisher = settings.Publisher,
ReferenceName = settings.ReferenceName,
State = settings.State,
Version = settings.Version,
ResourceExtensionParameterValues = resourceExtensionParameterValues
};
references.Add(reference);
}
return references;
}

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.

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public static class DockerExtensionConfigurator
{
private const string CertificateDirectoryParameterName = "certificateDirectory";
private const string DockerPortParameterName = "dockerPort";
private const string ParameterNotFound = "Resource extension parameter {0} must be specified";
private const string FileNotFound = "File {0} not found";
private const string CaCertFileName = "ca.pem";
private const string ServerCertFileName = "server-cert.pem";
private const string ServerKeyFileName = "server-key.pem";
/// <summary>
/// Configure docker extension
/// </summary>
public static IList<ResourceExtensionParameterValue> Configure(
ICollection<ResourceExtensionParameterValueSettings> parameters)
{
List<ResourceExtensionParameterValue> convertedParameters = new List<ResourceExtensionParameterValue>();
ResourceExtensionParameterValue certificates = GetCertificates(parameters);
ResourceExtensionParameterValue dockerPort = GetDockerPort(parameters);
convertedParameters.Add(certificates);
convertedParameters.Add(dockerPort);
return convertedParameters;
}
private static ResourceExtensionParameterValue GetDockerPort(IEnumerable<ResourceExtensionParameterValueSettings> parameters)
{
ResourceExtensionParameterValueSettings dockerPort =
parameters.FirstOrDefault(parameter => parameter.Key == DockerPortParameterName);
if (dockerPort == null || string.IsNullOrWhiteSpace(dockerPort.Value))
{
throw new InvalidOperationException(
string.Format(CultureInfo.InvariantCulture, ParameterNotFound, DockerPortParameterName));
}
JObject value = new JObject(new JProperty("dockerport", dockerPort.Value));
return new ResourceExtensionParameterValue {
Type = "Public",
Key = "ignored",
Value = value.ToString(Formatting.None)
};
}
private static ResourceExtensionParameterValue GetCertificates(IEnumerable<ResourceExtensionParameterValueSettings> parameters)
{
ResourceExtensionParameterValueSettings certificateDirectory =
parameters.FirstOrDefault(parameter => parameter.Key == CertificateDirectoryParameterName);
if (certificateDirectory == null || string.IsNullOrWhiteSpace(certificateDirectory.Value))
{
throw new InvalidOperationException(
string.Format(CultureInfo.InvariantCulture, ParameterNotFound, CertificateDirectoryParameterName));
}
string caFileName = Path.Combine(certificateDirectory.Value, CaCertFileName);
string serverCertFileName = Path.Combine(certificateDirectory.Value, ServerCertFileName);
string serverKeyFileName = Path.Combine(certificateDirectory.Value, ServerKeyFileName);
CheckFileExists(caFileName);
CheckFileExists(serverCertFileName);
CheckFileExists(serverKeyFileName);
JObject value = new JObject(
new JProperty("ca", GetFileAsBase64(caFileName)),
new JProperty("server-cert", GetFileAsBase64(serverCertFileName)),
new JProperty("server-key", GetFileAsBase64(serverKeyFileName)));
ResourceExtensionParameterValue certificates = new ResourceExtensionParameterValue
{
Key = "ignored",
Value = value.ToString(Formatting.None),
Type = "Private"
};
return certificates;
}
private static string GetFileAsBase64(string fileName)
{
return Convert.ToBase64String(File.ReadAllBytes(fileName));
}
private static void CheckFileExists(string fileName)
{
if (!File.Exists(fileName))
{
throw new FileNotFoundException(
string.Format(CultureInfo.InvariantCulture, FileNotFound, fileName),
fileName);
}
}
}

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

1
2
3
4
...
ConfigureMachine(virtualMachine, role.ConfigurationSets);
ConfigureNetwork(virtualMachine.NetworkConfiguration, role.ConfigurationSets);
...

This first one configures the user name, password, hostname, timezone etc.

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
47
private static void ConfigureMachine(VirtualMachine virtualMachine, IList<ConfigurationSet> configurationSets)
{
ConfigurationSet machineConfiguration = new ConfigurationSet
{
ResetPasswordOnFirstLogon = false,
EnableAutomaticUpdates = false,
ComputerName = virtualMachine.Name,
AdminUserName = virtualMachine.AdminUserName,
AdminPassword = virtualMachine.AdminPassword,
HostName = virtualMachine.Name,
SubnetNames = new List<string>(),
};
configurationSets.Add(machineConfiguration);
if (!string.IsNullOrWhiteSpace(virtualMachine.Timezone))
{
machineConfiguration.TimeZone = virtualMachine.Timezone;
}
if (virtualMachine.Type.ToLowerInvariant() == WindowsProvisioningConfiguration)
{
...
}
else
{
machineConfiguration.ConfigurationSetType = ConfigurationSetTypes.LinuxProvisioningConfiguration;
machineConfiguration.DisableSshPasswordAuthentication = false;
machineConfiguration.UserName = virtualMachine.AdminUserName;
machineConfiguration.UserPassword = virtualMachine.AdminPassword;
if (virtualMachine.Ssh != null)
{
if (string.IsNullOrWhiteSpace(virtualMachine.Ssh.CertificateFile)
|| !File.Exists(virtualMachine.Ssh.CertificateFile))
{
throw new InvalidOperationException("SSH certificate file does not exist or is not specified");
}
machineConfiguration.SshSettings =
new Microsoft.WindowsAzure.Management.Compute.Models.SshSettings();
machineConfiguration.DisableSshPasswordAuthentication = true;
X509Certificate2 certificate = new X509Certificate2();
certificate.Import(virtualMachine.Ssh.CertificateFile);
machineConfiguration.SshSettings.PublicKeys.Add(
new SshSettingPublicKey(
certificate.Thumbprint,
string.Format(CultureInfo.InvariantCulture, "//home/{0}//.ssh//authorized_keys", virtualMachine.AdminUserName)));
}
}
}

If we have specified ssh in the configuration then we refer to the previously uploaded certificate. Then for the network,

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
private static void ConfigureNetwork(VirtualMachineNetworkConfiguration network, IList<ConfigurationSet> configurationSets)
{
ConfigurationSet networkConfiguration = new ConfigurationSet {
ConfigurationSetType = ConfigurationSetTypes.NetworkConfiguration,
SubnetNames = new List<string>()
};
configurationSets.Add(networkConfiguration);
if (!string.IsNullOrWhiteSpace(network.Ip))
{
networkConfiguration.StaticVirtualNetworkIPAddress = network.Ip;
}
if (network.SubnetNames != null)
{
List<string> names = network.SubnetNames.ToList();
networkConfiguration.SubnetNames = names;
}
if (network.Endpoints != null)
{
foreach (EndpointSetting endpoint in network.Endpoints)
{
InputEndpoint inputEndpoint = new InputEndpoint {
Port = endpoint.Port,
LocalPort = endpoint.LocalPort,
EnableDirectServerReturn = endpoint.EnableDirectServerReturn,
Protocol = endpoint.Protocol
};
if (endpoint.AclRules != null)
{
inputEndpoint.EndpointAcl = new EndpointAcl {
Rules = endpoint.AclRules.Select(rule =>
new AccessControlListRule {
Action = rule.Action,
Description = rule.Description,
Order = rule.Order,
RemoteSubnet = rule.RemoteSubnet}).ToList()
};
}
if (!string.IsNullOrWhiteSpace(endpoint.Name))
{
inputEndpoint.Name = endpoint.Name;
}
if (!string.IsNullOrWhiteSpace(endpoint.LoadBalancedEndpointSetName))
{
inputEndpoint.LoadBalancedEndpointSetName = endpoint.LoadBalancedEndpointSetName;
}
if (!string.IsNullOrWhiteSpace(endpoint.LoadBalancerName))
{
inputEndpoint.LoadBalancerName = endpoint.LoadBalancerName;
}
if (endpoint.LoadBalancerProbe != null)
{
inputEndpoint.LoadBalancerProbe =
new Microsoft.WindowsAzure.Management.Compute.Models.LoadBalancerProbe {
IntervalInSeconds = endpoint.LoadBalancerProbe.IntervalInSeconds,
Path = endpoint.LoadBalancerProbe.Path,
Port = endpoint.LoadBalancerProbe.Port,
Protocol = endpoint.LoadBalancerProbe.Protocol == "http" ? LoadBalancerProbeTransportProtocol.Http : LoadBalancerProbeTransportProtocol.Tcp,
TimeoutInSeconds = endpoint.LoadBalancerProbe.TimeoutInSeconds
};
}
networkConfiguration.InputEndpoints.Add(inputEndpoint);
}
}
}

Here we setup the subnet, ip, endpoints - with endpoint security and possibly load balancer with a probe.

The End

That is basically what we did for the Azure part. The docker container deployment is another story.