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
- Microsoft Announces Nano Server for Modern Apps and Cloud
- Getting Started with Nano Server
- Nano Server Blog
- IIS on Nano Server
- The Nano Server Channel
- Remotely Managing Nano Server with PowerShell Desired State Configuration
- Azure Quickstart Templates
- Deploys a Windows VM and Configures a WinRM Https listener
- Certificates overview for Azure Cloud Services
- How to: Create Your Own Test Certificate
- Get started with Azure Key Vault
- Push a certificate onto a Windows VM
- WinRM on a Windows VM
- Pvk2Pfx (Pvk2Pfx.exe) is a command-line tool copies public key and private key information contained in .spc, .cer, and .pvk files to a Personal Information Exchange (.pfx) file.
Excellent post! reading a few recent posts – very useful!
Many thanks!
LikeLike