Based on the current builds, compared to Server, Nano Server has 93 percent lower VHD size, 92 percent fewer critical bulletins and 80 percent fewer reboots!

Deploying Nano Server to Azure

I’ve been curious about Nano Server for a while now. And I recently noticed that it was available on Microsoft Azure. This post is definitely from a developers point-of-view. It goes through the steps required to create a functional Nano Server Virtual Machines (VM) on Microsoft Azure.

Nano Server is ideal for many scenarios:

  • As a “compute” host for Hyper-V virtual machines, either in clusters or not
  • As a storage host for Scale-Out File Server.
  • As a DNS server
  • As a web server running Internet Information Services (IIS)
  • As a host for applications that are developed using cloud application patterns and run in a container or virtual machine guest operating system.

    The Adventure

    Nano Server is a remotely administered server operating system (OS). Wait. Let me repeat this because it’s important… Nano Server is a remotely administered server operating system (OS). Developers, Nano Server is a server OS optimized for clouds and data centers. It’s designed to take up far less disk space, to setup significantly faster, and to require far fewer restarts than Windows Server. So why does this matter? Well it means more resources, more availability and stability for our Apps. And it also means that it’s time to learn new skills, because there is no local logon capability at all, nor does it support Terminal Services. However, we have a wide variety of options for managing Nano Server remotely, including Windows PowerShell, Windows Management Instrumentation (WMI), Windows Remote Management, and Emergency Management Services (EMS).

    The first step in our adventure is to find the Virtual Machine Image details that we will need to build into our Azure Resource Manager (ARM) Template.

    Finding The VM Image

    Login-AzureRmAccount
    
    $location = 'eastus'
      
    Get-AzureRmVMImagePublisher -Location $location `
        | Where-Object -Property PublisherName -Like MicrosoftWindowsServer
     
    $publisherName = 'MicrosoftWindowsServer'
      
    Get-AzureRmVMImageOffer -Location $location `
                          -PublisherName $publisherName
     
    $offer = 'WindowsServer'
      
    Get-AzureRmVMImageSku -Location $location `
                          -PublisherName $publisherName `
                          -Offer $offer `
          | Select-Object -Property 'Skus'
    
    Skus                                  
    ----                                  
    2008-R2-SP1                           
    2012-Datacenter                       
    2012-R2-Datacenter                    
    2016-Nano-Docker-Test                 
    2016-Nano-Server-Technical-Preview    
    2016-Technical-Preview-with-Containers
    Windows-Server-Technical-Preview
    

    The only way to manage Nano Server is through WinRM. To configure this feature we need a certificate. The next step is for those that don’t have certificates to play with.

    Creating a Self-Signed Certificate

    makecert -sky exchange -r -n "CN=nanoservexp.eastus.cloudapp.azure.com" -pe -a sha1 -len 2048 -ss My -sv brisebois-nanoservexp.pvk brisebois-nanoservexp.cer 
    
    pvk2pfx -pvk brisebois-nanoservexp.pvk -pi <put the password here> -spc brisebois-nanoservexp.cer -pfx brisebois-nanoservexp.pfx
    

    In a previous blog post about using remote PowerShell on Azure I demonstrated how to execute PowerShell on a distant Virtual Machine (VM) from Azure Automation. The method I used worked great with Azure Service Management APIs. Since then things have changed and this post is all about executing remote PowerShell on a VM that is created through the new Azure Resource Management APIs. These APIs require us to use Azure Key Vault to store the certificates that we want to inject into VMs as they are created by ARM Templates.

    The following PowerShell script will create an Azure Key Vault and add the newly created certificate as a Secret. This detail is important, because the Virtual Machine ARM Provider only supported the use secrets for certificates at the time this post was written.

    Storing a Certificate in an Azure Key Vault Secret

    Login-AzureRmAccount
    
    New-AzureRmResourceGroup -Name 'nanoexperiment' `
                             -Location 'eastus'
    
    New-AzureRmKeyVault -VaultName 'brisebois' `
                        -ResourceGroupName 'nanoexperiment' `
                        -Location 'eastus' `
                        -EnabledForTemplateDeployment `
                        -EnabledForDeployment `
                        -Sku standard 
    
    $fileName = "C:\Users\brise\Desktop\brisebois-nanoservexp.pfx"
    $fileContentBytes = get-content $fileName -Encoding Byte
    $fileContentEncoded = [System.Convert]::ToBase64String($fileContentBytes)
    
    $jsonObject = @"
    {
    "data": "$filecontentencoded",
    "dataType" :"pfx",
    "password": "<put the password here>"
    }
    "@
    
    $jsonObjectBytes = [System.Text.Encoding]::UTF8.GetBytes($jsonObject)
    $jsonEncoded = [System.Convert]::ToBase64String($jsonObjectBytes)
    
    $secret = ConvertTo-SecureString -String $jsonEncoded -AsPlainText –Force
    Set-AzureKeyVaultSecret -VaultName 'brisebois' -Name 'cert' -SecretValue $secret
    

    Deploying to Azure

    Now that we have a Nano Server SKU and a certificate stored in Azure Key Vault, it’s time to build and deploy our Nano Server ARM Template to Azure. Our next step is to create an Azure Resource Manager (ARM) Template that creates a Storage Account, Virtual Network (VNET), Network Interface Card (NIC), Public IP (PIP), Network Security Group (NSG) and a Windows Nano Server Virtual Machine instance.

    To create this template, I used Visual Studio and created a new Azure Resource Group Project. When prompted for a base template, I selected the blank template and built it piece by piece.

    WindowsNanoServerVM.json

    {
      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "StorageType": {
          "type": "string",
          "defaultValue": "Standard_LRS",
          "allowedValues": [
            "Standard_LRS",
            "Standard_GRS",
            "Standard_RAGRS"
          ]
        },
        "VMName": {
          "type": "string",
          "minLength": 1
        },
        "AdminUserName": {
          "type": "string",
          "minLength": 1
        },
        "AdminPassword": {
          "type": "securestring"
        },
        "WindowsOSVersion": {
          "type": "string",
          "defaultValue": "2016-Nano-Server-Technical-Preview"
        },
        "PIPDnsName": {
          "type": "string",
          "minLength": 1,
          "defaultValue": "pip"
        }
      },
      "variables": {
        "NsgName": "fensg",
        "vnetPrefix": "10.0.0.0/16",
        "vnetSubnet1Name": "Subnet",
        "vnetSubnet1Prefix": "10.0.0.0/24",
        "StorageName": "[concat(parameters('VMName'), 'storage')]",
        "ImagePublisher": "MicrosoftWindowsServer",
        "ImageOffer": "WindowsServer",
        "OSDiskName": "OSDisk",
        "VmSize": "Standard_D1",
        "VnetID": "[resourceId('Microsoft.Network/virtualNetworks', 'vnet')]",
        "NsgID": "[resourceId('Microsoft.Network/networkSecurityGroups/', variables('NsgName'))]",
        "SubnetRef": "[concat(variables('VnetID'), '/subnets/', variables('vnetSubnet1Name'))]",
        "StorageAccountContainerName": "vhds",
        "NicName": "[concat(parameters('VMName'), 'NetworkInterface')]",
        "PipName": "Pip"
      },
      "resources": [
        {
          "apiVersion": "2015-05-01-preview",
          "type": "Microsoft.Network/networkSecurityGroups",
          "name": "[variables('NsgName')]",
          "location": "[resourceGroup().location]",
          "tags": {
            "displayName": "NSG"
          },
          "properties": {
            "securityRules": [
              {
                "name": "winrm_rule",
                "properties": {
                  "description": "Allow WinRM",
                  "protocol": "Tcp",
                  "sourcePortRange": "*",
                  "destinationPortRange": "5986",
                  "sourceAddressPrefix": "Internet",
                  "destinationAddressPrefix": "*",
                  "access": "Allow",
                  "priority": 100,
                  "direction": "Inbound"
                }
              },
              {
                "name": "web_rule",
                "properties": {
                  "description": "Allow WEB",
                  "protocol": "Tcp",
                  "sourcePortRange": "*",
                  "destinationPortRange": "80",
                  "sourceAddressPrefix": "Internet",
                  "destinationAddressPrefix": "*",
                  "access": "Allow",
                  "priority": 101,
                  "direction": "Inbound"
                }
              }
            ]
          }
        },
        {
          "name": "vnet",
          "type": "Microsoft.Network/virtualNetworks",
          "location": "[resourceGroup().location]",
          "apiVersion": "2015-05-01-preview",
          "dependsOn": [
            "[concat('Microsoft.Network/networkSecurityGroups/', variables('NsgName'))]"
          ],
    
          "tags": {
            "displayName": "vnet"
          },
          "properties": {
            "addressSpace": {
              "addressPrefixes": [
                "[variables('vnetPrefix')]"
              ]
            },
            "subnets": [
              {
                "name": "[variables('vnetSubnet1Name')]",
                "properties": {
                  "addressPrefix": "[variables('vnetSubnet1Prefix')]",
                  "networkSecurityGroup": { "id": "[variables('NsgID')]" }
                }
              }
            ]
          }
        },
        {
          "name": "[variables('StorageName')]",
          "type": "Microsoft.Storage/storageAccounts",
          "location": "[resourceGroup().location]",
          "apiVersion": "2015-05-01-preview",
          "dependsOn": [ ],
          "tags": {
            "displayName": "Storage Account"
          },
          "properties": {
            "accountType": "[parameters('StorageType')]"
          }
        },
        {
          "name": "[variables('NicName')]",
          "type": "Microsoft.Network/networkInterfaces",
          "location": "[resourceGroup().location]",
          "apiVersion": "2015-05-01-preview",
          "dependsOn": [
            "[concat('Microsoft.Network/virtualNetworks/', 'vnet')]",
            "[concat('Microsoft.Network/publicIPAddresses/', variables('PipName'))]"
          ],
          "tags": {
            "displayName": "Nic"
          },
          "properties": {
            "ipConfigurations": [
              {
                "name": "ipconfig1",
                "properties": {
                  "privateIPAllocationMethod": "Dynamic",
                  "subnet": {
                    "id": "[variables('SubnetRef')]"
                  },
                  "publicIPAddress": {
                    "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('PipName'))]"
                  }
                }
              }
            ]
          }
        },
        {
          "name": "[parameters('VMName')]",
          "type": "Microsoft.Compute/virtualMachines",
          "location": "[resourceGroup().location]",
          "apiVersion": "2015-05-01-preview",
          "dependsOn": [
            "[concat('Microsoft.Storage/storageAccounts/', variables('StorageName'))]",
            "[concat('Microsoft.Network/networkInterfaces/', variables('NicName'))]"
          ],
          "tags": {
            "displayName": "Virtual Machine"
          },
          "properties": {
            "hardwareProfile": {
              "vmSize": "[variables('VmSize')]"
            },
            "osProfile": {
              "secrets": [
                {
                  "sourceVault": { "id": "/subscriptions/d5e6-4c5b-9be4-9e577d5b8fd0/resourceGroups/nanoexperiment/providers/Microsoft.KeyVault/vaults/brisebois" },
                  "vaultCertificates": [
                    {
                      "certificateUrl": "https://brisebois.vault.azure.net:443/secrets/cert/2c3675a3553e61cb24b7a9b231",
                      "certificateStore": "My"
                    }
                  ]
                }
              ],
              "computerName": "[parameters('VMName')]",
              "adminUsername": "[parameters('AdminUsername')]",
              "adminPassword": "[parameters('AdminPassword')]",
              "windowsConfiguration": {
                "provisionVMAgent": true,
                "enableAutomaticUpdates": true,
                "winRM": {
                  "listeners": [
                    {
                      "protocol": "Https",
                      "certificateUrl": "https://brisebois.vault.azure.net:443/secrets/cert/2c3675a3553e61cb24b7a9b231"
                    }
                  ]
                }
              }
            },
            "storageProfile": {
              "imageReference": {
                "publisher": "[variables('ImagePublisher')]",
                "offer": "[variables('ImageOffer')]",
                "sku": "[parameters('WindowsOSVersion')]",
                "version": "latest"
              },
              "osDisk": {
                "name": "OSDisk",
                "vhd": {
                  "uri": "[concat('http://', variables('StorageName'), '.blob.core.windows.net/', variables('StorageAccountContainerName'), '/', variables('OSDiskName'), '.vhd')]"
                },
                "caching": "ReadWrite",
                "createOption": "FromImage"
              }
            },
            "networkProfile": {
              "networkInterfaces": [
                {
                  "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('NicName'))]"
                }
              ]
            }
          },
          "resources": [
    
          ]
        },
        {
          "name": "[variables('PipName')]",
          "type": "Microsoft.Network/publicIPAddresses",
          "location": "[resourceGroup().location]",
          "apiVersion": "2015-05-01-preview",
          "dependsOn": [ ],
          "tags": {
            "displayName": "Pip"
          },
          "properties": {
            "publicIPAllocationMethod": "Dynamic",
            "dnsSettings": {
              "domainNameLabel": "[parameters('PipDnsName')]"
            }
          }
        }
      ],
      "outputs": {
      }
    }
    

    The parameter file allows me to use the same template files for multiple environments.

    WindowsNanoServerVM.parameters.json

    {
      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "PIPDnsName": {
          "value": "nanoservexp"
        },
        "VmName": {
          "value": "nanoservexp"
        }
      }
    }
    

    The ARM Template is deployed using Azure PowerShell. I used the following script to create a Storage Account.

    Login-AzureRmAccount
     
    # Deploy the Template to the Resource Group
    New-AzureRmResourceGroupDeployment -ResourceGroupName 'nanoexperiment' `
                                       -TemplateFile 'WindowsNanoServerVM.json' `
                                       -TemplateParameterFile 'WindowsNanoServerVM.parameters.json' `
                                       -Verbose
    
    VERBOSE: 6:04:28 PM - Create template deployment 'WindowsNanoServerVM.json'.
    VERBOSE: 6:04:32 PM - Resource Microsoft.Network/virtualNetworks 'vnet' provisioning status is succeeded
    VERBOSE: 6:04:32 PM - Resource Microsoft.Storage/storageAccounts 'nanoservexpstorage' provisioning status is succeeded
    VERBOSE: 6:04:32 PM - Resource Microsoft.Network/networkSecurityGroups 'fensg' provisioning status is succeeded
    VERBOSE: 6:04:35 PM - Resource Microsoft.Network/networkInterfaces 'nanoservexpNetworkInterface' provisioning status is succeeded
    VERBOSE: 6:04:35 PM - Resource Microsoft.Network/publicIPAddresses 'Pip' provisioning status is succeeded
    VERBOSE: 6:04:37 PM - Resource Microsoft.Compute/virtualMachines 'nanoservexp' provisioning status is running
    VERBOSE: 6:06:47 PM - Resource Microsoft.Compute/virtualMachines 'nanoservexp' provisioning status is succeeded
    
    
    DeploymentName    : WindowsNanoServerVM.json
    ResourceGroupName : nanoexperiment
    ProvisioningState : Succeeded
    Timestamp         : 2/18/2016 11:06:51 PM
    Mode              : Incremental
    TemplateLink      : 
    Parameters        : 
                        Name              Type                       Value     
                        ===============   =========================  ==========
                        storageType       String                     Standard_LRS
                        vmName            String                     nanoservexp
                        adminUserName     String                     brisebois 
                        adminPassword     SecureString                         
                        windowsOSVersion  String                     2016-Nano-Server-Technical-Preview
                        pipDnsName        String                     nanoservexp
    

    Use WinRM to Interact With Nano Server

    Windows Remote Management (WinRM) is the Microsoft implementation of WS-Management Protocol, a standard Simple Object Access Protocol (SOAP)-based, firewall-friendly protocol that allows hardware and operating systems, from different vendors, to interoperate.

    I think it’s about time to do something with our brand new Nano Server! So let’s create a PowerShell Session.

    $hostName= "nanoservexp.eastus.cloudapp.azure.com"
    $winrmPort = '5986'
    
    # Get the credentials of the machine
    $cred = Get-Credential
    
    # Connect to the machine
    $soptions = New-PSSessionOption -SkipCACheck
    Enter-PSSession -ComputerName $hostName `
                    -Port $winrmPort `
                    -Credential $cred `
                    -SessionOption $soptions `
                    -UseSSL
    

    Once the connection is established you can start to execute commands remotely.

    [nanoservexp.eastus.cloudapp.azure.com]: PS C:\> cd Packages
    
    [nanoservexp.eastus.cloudapp.azure.com]: PS C:\Packages> dir
    
        Directory: C:\Packages
    
    Mode                LastWriteTime         Length Name
    ----                -------------         ------ ----                                                                                              
    d-----       12/18/2015  10:17 AM                en-us
    -a----       10/29/2015  11:52 PM       14668155 Microsoft-NanoServer-Compute-Package.cab
    -a----       10/29/2015  11:52 PM        9158588 Microsoft-NanoServer-Containers-Package.cab
    -a----       10/29/2015  11:51 PM         117542 Microsoft-NanoServer-DCB-Package.cab 
    -a----       10/29/2015  11:53 PM      130448756 Microsoft-NanoServer-Defender-Package.cab
    -a----       10/29/2015  11:51 PM        1718550 Microsoft-NanoServer-DNS-Package.cab
    -a----       10/29/2015  11:51 PM         512929 Microsoft-NanoServer-DSC-Package.cab 
    -a----       10/29/2015  11:52 PM        8578261 Microsoft-NanoServer-FailoverCluster-Package.cab
    -a----       10/29/2015  11:51 PM         620742 Microsoft-NanoServer-Guest-Package.cab
    -a----       10/29/2015  11:51 PM        1922047 Microsoft-NanoServer-IIS-Package.cab
    -a----       10/29/2015  11:51 PM         168468 Microsoft-NanoServer-NPDS-Package.cab
    -a----       10/29/2015  11:53 PM       27306848 Microsoft-NanoServer-OEM-Drivers-Package.cab
    -a----       10/29/2015  11:52 PM        9931218 Microsoft-NanoServer-Storage-Package.cab
    -a----       10/29/2015  11:49 PM         615099 Microsoft-OneCore-ReverseForwarders-Package.cab
    -a----       10/29/2015  11:46 PM          52406 Microsoft-Windows-Server-SCVMM-Compute-Package.cab
    -a----       10/29/2015  11:46 PM         881258 Microsoft-Windows-Server-SCVMM-Package.cab
    

    So here you have it, a functional Nano Server instance on Microsoft Azure. Use the comments below to share about what you’re doing with Nano Server.

    Resources

    One response to Deploying Nano Server to Microsoft Azure

    1. 

      Excellent post! reading a few recent posts – very useful!
      Many thanks!

      Like

    Leave a comment

    This site uses Akismet to reduce spam. Learn how your comment data is processed.