Set up continuous deployment with Chocolatey

Note

Azure Automation State Configuration will be retired on September 30, 2027, please transition to Azure Machine Configuration by that date. For more information, see the blog post announcement. The Azure Machine Configuration service combines features of DSC Extension, Azure Automation State Configuration, and the most commonly requested features from customer feedback. Azure Machine Configuration also includes hybrid machine support through Arc-enabled servers.

Caution

Azure Automation DSC for Linux has retired on 30 September 2023.

In a DevOps world, there are many tools to assist with various points in the continuous integration pipeline. Azure Automation State Configuration is a welcome new addition to the options that DevOps teams can employ.

Azure Automation is a managed service in Azure that allows you to automate various tasks using runbooks, nodes, and shared resources, such as credentials, schedules, and global variables. Azure Automation State Configuration extends this automation capability to include PowerShell Desired State Configuration (DSC) tools. Here's a great overview.

This article demonstrates how to set up Continuous Deployment (CD) for a Windows computer. You can easily extend the technique to include as many Windows computers as necessary in the role, for example, a website, and go from there to more roles.

Continuous Deployment for IaaS VMs

At a high level

There's quite a bit going on here, but fortunately it can be broken down into two main processes:

  • Writing code and testing it, then creating and publishing installation packages for major and minor versions of the system.
  • Creating and managing VMs that install and execute the code in the packages.

Once both of these core processes are in place, it's easy to automatically update the package on your VMs as new versions are created and deployed.

Component overview

Package managers such as apt-get are well known in the Linux world, but not so much in the Windows world. Chocolatey is a package manager for Windows. Scott Hanselman's blog post about Chocolatey is a great introduction. Chocolatey allows you to use the command line to install packages from a central repository onto a Windows operating system. You can create and manage your own repository, and Chocolatey can install packages from any number of repositories that you designate.

PowerShell DSC is a PowerShell tool that allows you to declare the configuration that you want for a machine. For example, if you want Chocolatey installed, IIS installed, port 80 opened, and version 1.0.0 of your website installed, the DSC Local Configuration Manager (LCM) implements that configuration. A DSC pull server holds a repository of configurations for your machines. The LCM on each machine checks in periodically to see if its configuration matches the stored configuration. It can either report status or attempt to bring the machine back into alignment with the stored configuration. You can edit the stored configuration on the pull server to cause a machine or set of machines to come into alignment with the changed configuration.

A DSC resource is a module of code that has specific capabilities, such as managing networking, Active Directory, or SQL Server. The Chocolatey DSC Resource knows how to access a NuGet Server, download packages, install packages, and perform other tasks. There are many other DSC Resources in the PowerShell Gallery. You install these modules on your Azure Automation State Configuration pull server for use by your configurations.

Resource Manager templates provide a declarative way of generating resources for your infrastructure such as:

  • networks and subnets
  • network security
  • routing,
  • load balancers,
  • NICs, VMs, and others

For a comparison of the Resource Manager deployment model (declarative) with the Azure classic deployment model (imperative), see Azure Resource Manager vs. classic deployment. This article includes a discussion of the core resource providers: compute, storage, and network.

One key feature of a Resource Manager template is its ability to install a VM extension during the VM provisioning. A VM extension has specific capabilities, such as running a custom script, installing anti-virus software, and running a DSC configuration script. There are many other types of VM extensions.

Quick trip around the diagram

Starting at the top, you write your code, build it, test it, then create an installation package. Chocolatey can handle various types of installation packages, such as MSI, MSU, ZIP. And you have the full power of PowerShell to do the actual installation if Chocolatey's native capabilities aren't up to it. Put the package into some place reachable - a package repository. This usage example uses a public folder in an Azure blob storage account, but it can be anywhere. Chocolatey works natively with NuGet servers and a few others for management of package metadata. This article describes the options. The usage example uses NuGet. A Nuspec is metadata about your packages. The Nuspec information is compiled into a NuPkg and stored on a NuGet server. When your configuration requests a package by name and references a NuGet server, the Chocolatey DSC resource on the VM grabs the package and installs it. You can also request a specific version of a package.

In the bottom left of the picture, there's an Azure Resource Manager template. In this usage example, the VM extension registers the VM with the Azure Automation State Configuration pull server as a node. The configuration is stored in the pull server twice: once as plain text and once compiled as a MOF file. In the Azure portal, the MOF represents a node configuration, as opposed to a simple configuration.

It's relatively simple to create the Nuspec, compile it, and store it in a NuGet server. The next step to continuous deployment requires the following one-time tasks:

  • Set up the pull server
  • Register your nodes with the server
  • Create the initial configuration on the server

You only have to refresh the configuration and node configuration on the pull server when you upgrade and deploy packages to the repository.

If you're not starting with a Resource Manager template, there are PowerShell commands to help you register your VMs with the pull server. For more information, see Onboarding machines for management by Azure Automation State Configuration.

About the usage example

The usage example in this article starts with a VM from a generic Windows Server 2012 R2 image from the Azure gallery. You can start from any stored image and then tweak from there with the DSC configuration. However, changing configuration that is baked into an image is much harder than dynamically updating the configuration using DSC.

You don't have to use a Resource Manager template and the VM extension to use this technique with your VMs. And your VMs don't have to be on Azure to be under CD management. Just install Chocolatey and configure the LCM on the VM so it knows where the pull server is.

When you update a package on a VM that's in production, you need to take that VM out of rotation while the update is installed. How you do this varies widely. For example, with a VM behind an Azure Load Balancer, you can add a Custom Probe. While updating the VM, have the probe endpoint return a 400. The tweak necessary to cause this change can be inside your configuration, as can the tweak to switch it back to returning a 200 once the update is complete.

Full source for this usage example is in this Visual Studio project on GitHub.

Step 1: Set up the pull server and Automation account

Run the following commands in an authenticated (Connect-AzAccount -Environment AzureChinaCloud) PowerShell session:

New-AzResourceGroup -Name MY-AUTOMATION-RG -Location MY-RG-LOCATION-IN-QUOTES
$newAzAutomationAccountSplat = @{
    ResourceGroupName = 'MY-AUTOMATION-RG'
    Location = 'MY-RG-LOCATION-IN-QUOTES'
    Name = 'MY-AUTOMATION-ACCOUNT'
}
New-AzAutomationAccount @newAzAutomationAccountSplat

Step 2: Make VM extension tweaks to the Resource Manager template

Details for VM registration (using the PowerShell DSC VM extension) provided in this Azure Quickstart Template. This step registers your new VM with the pull server in the list of State Configuration Nodes. Part of this registration is specifying the node configuration to be applied to the node. This node configuration doesn't have to exist yet in the pull server, but you need to choose the name of the node and the name of the configuration. For this example, the node is isvbox and the configuration name is ISVBoxConfig. The node configuration name you specify in DeploymentTemplate.json is ISVBoxConfig.isvbox.

Step 3: Add required DSC resources to the pull server

The PowerShell Gallery can install DSC resources into your Azure Automation account. Navigate to the resource you want and select Deploy to Azure Automation.

PowerShell Gallery example

Another technique recently added to the Azure portal allows you to pull in new modules or update existing modules. The select the Browse Gallery icon to see the list of modules in the gallery, drill into details, and import into your Automation account. You can use this process to keep your modules up to date. Also, the import feature checks dependencies with other modules to ensure nothing gets out of sync.

There's also a manual approach, used only once per resource, unless you want to upgrade it later. For more information on authoring PowerShell integration modules, see Authoring Integration Modules for Azure Automation.

Note

The folder structure of a PowerShell integration module for a Windows computer is a little different from the folder structure expected by the Azure Automation.

  1. Install Windows Management Framework v5 (not needed for Windows 10).

  2. Install the integration module.

    Install-Module -Name MODULE-NAME`    <—grabs the module from the PowerShell Gallery
    
  3. Copy the module folder from C:\Program Files\WindowsPowerShell\Modules\MODULE-NAME to a temporary folder.

  4. Delete samples and documentation from the main folder.

  5. Zip the main folder, naming the ZIP file with the name of the folder.

  6. Put the ZIP file into a reachable HTTP location, such as blob storage in an Azure Storage account.

  7. Run the following command.

    $newAzAutomationModuleSplat = @{
        ResourceGroupName = 'MY-AUTOMATION-RG'
        AutomationAccountName = 'MY-AUTOMATION-ACCOUNT'
        Name = 'MODULE-NAME'
        ContentLinkUri = 'https://STORAGE-URI/CONTAINERNAME/MODULE-NAME.zip'
    }
    New-AzAutomationModule @newAzAutomationModuleSplat
    

The included example implements these steps for cChoco and xNetworking.

Step 4: Add the node configuration to the pull server

There's nothing special about the first time you import your configuration into the pull server and compile. All later imports or compilations of the same configuration look exactly the same. Each time you update your package and need to push it out to production you do this step after ensuring the configuration file is correct - including the new version of your package. Here's the configuration file ISVBoxConfig.ps1:

Configuration ISVBoxConfig
{
    Import-DscResource -ModuleName cChoco
    Import-DscResource -ModuleName xNetworking

    Node 'isvbox' {

        cChocoInstaller installChoco
        {
            InstallDir = 'C:\choco'
        }

        WindowsFeature installIIS
        {
            Ensure = 'Present'
            Name   = 'Web-Server'
        }

        xFirewall WebFirewallRule
        {
            Direction    = 'Inbound'
            Name         = 'Web-Server-TCP-In'
            DisplayName  = 'Web Server (TCP-In)'
            Description  = 'IIS allow incoming web site traffic.'
            Enabled       = 'True'
            Action       = 'Allow'
            Protocol     = 'TCP'
            LocalPort    = '80'
            Ensure       = 'Present'
        }

        cChocoPackageInstaller trivialWeb
        {
            Name      = 'trivialweb'
            Version   = '1.0.0'
            Source    = 'MY-NUGET-V2-SERVER-ADDRESS'
            DependsOn = '[cChocoInstaller]installChoco','[WindowsFeature]installIIS'
        }
    }
}

The following New-ConfigurationScript.ps1 script was modified to use the Az PowerShell module:

$importAzAutomationDscConfigurationSplat = @{
    ResourceGroupName = 'MY-AUTOMATION-RG'
    AutomationAccountName = 'MY-AUTOMATION-ACCOUNT'
    SourcePath = 'C:\temp\AzureAutomationDsc\ISVBoxConfig.ps1'
    Published = -Published
    Force = -Force
}
Import-AzAutomationDscConfiguration @importAzAutomationDscConfigurationSplat

$startAzAutomationDscCompilationJobSplat = @{
    ResourceGroupName = 'MY-AUTOMATION-RG'
    AutomationAccountName = 'MY-AUTOMATION-ACCOUNT'
    ConfigurationName = 'ISVBoxConfig'
}
$jobData = Start-AzAutomationDscCompilationJob @startAzAutomationDscCompilationJobSplat

$compilationJobId = $jobData.Id

$getAzAutomationDscCompilationJobSplat = @{
    ResourceGroupName = 'MY-AUTOMATION-RG'
    AutomationAccountName = 'MY-AUTOMATION-ACCOUNT'
    Id = $compilationJobId
}
Get-AzAutomationDscCompilationJob @getAzAutomationDscCompilationJobSplat

Step 5: Create and maintain package metadata

For each package that you put into the package repository, you need a Nuspec that describes it. It must be compiled and stored on your NuGet server. For more information, see [Create a NuGet package using nuget.exe CLI].

You can use MyGet.org as a NuGet server. You can buy this service, but thee is a free starter SKU. For instructions on installing your own NuGet server for your private packages, see the documentation on Nuget.org.

Step 6: Tie it all together

Each time a version passes QA and is approved for deployment, the package is created, and nuspec and nupkg are updated and deployed to the NuGet server. You must update the configuration (step 4) with the new version number. Then, send it to the pull server and compile it.

From that point on, it's up to the VMs that depend on that configuration to pull the update and install it. Each of these updates is simple - just a line or two of PowerShell. For Azure DevOps, some of them are encapsulated in build tasks that you can chain together in a build. This article provides more details. This GitHub repo details the available build tasks.

Next steps