Provisioning Azure Resources with Ansible

joshduffney

Josh Duffney

Posted on January 15, 2020

Provisioning Azure Resources with Ansible

Ansible is an agentless configuration management tool. Instead of using an agent to communicate with target machine Ansible relies on remote management protocols such as SSH and WinRM. Not depending on an agent for connectivity has it's pros can cons. While you do not have to install an agent, you do have to setup the remote management protocol. In this tutorial you'll be provisioning a Windows Server virtual machine by using PowerShell and a custom script extension Deployed by Ansible to Azure. By the end of the tutorial, you will be able to connect to the Azure virtual machine with Ansible using WinRM.

Table Of Contents

Prerequisites

In order to follow along with this tutorial you'll need your Ansible environment connected Azure. All the required resources for an Azure virtual machine must also be deployed prior to following this tutorial. Both of these prerequisites are covered previously in this series.

Windows Remote Management

Windows remote management (WinRM) is a management protocol used by Windows to remotely communicate with another server. Ansible uses this protocol to communicate to Windows targets. In order to use WinRM you must configure the Ansible server to support WinRM traffic and configure the Windows host. The Windows host configuration depends on your environment and how you want to configure the WinRM listener.

Adding WinRM Support to Ansible

In order for Ansible to communicate over WinRM requires the pywinrm packaged be installed on the Ansible server. You can install it by running the command pip install "pywinrm>=0.3.0". The pywinrm package is all that is required to be installed on the Ansible server.

pip install "pywinrm>=0.3.0"
Enter fullscreen mode Exit fullscreen mode

Read more about Windows Remote Management

Configuring the WinRM Listener

The configuration of a WinRM listener has two main pieces to configure. The port it uses to communicate with and the authentication option used. In this tutorial you'll be configuring the WinRM listener to use port 5986 and you will authenticate with NTLM. Using port 5986 requires the use of certificates for encryption. There are several ways to deploy the certificates to the windows host, but you'll be using self signed certificates in this tutorial. Ansible provides a PowerShell script that will configure all of this for you. It will be with an Azure custom vm extension to automate the configuration through Ansible.

Read more about Setting up a Windows Host.

ConfigureRemotingForAnsible.ps1

The ConfigureRemotingForAnsible.ps1 script is intended for training and development purposes only and should not be used in a production environment, since it enables settings (like Basic authentication) that can be inherently insecure.

Location: /examples/scripts/ConfigureRemotingForAnsible.ps1

GitHub logo ansible / ansible

Ansible is a radically simple IT automation platform that makes your applications and systems easier to deploy and maintain. Automate everything from code deployment to network configuration to cloud management, in a language that approaches plain English, using SSH, with no agents to install on remote systems. https://docs.ansible.com.

PyPI version Docs badge Chat badge Build Status Ansible Code of Conduct Ansible mailing lists Repository License Ansible CII Best Practices certification

Ansible

Ansible is a radically simple IT automation system. It handles configuration management, application deployment, cloud provisioning ad-hoc task execution, network automation, and multi-node orchestration. Ansible makes complex changes like zero-downtime rolling updates with load balancers easy. More information on the Ansible website.

Design Principles

  • Have an extremely simple setup process with a minimal learning curve.
  • Manage machines quickly and in parallel.
  • Avoid custom-agents and additional open ports, be agentless by leveraging the existing SSH daemon.
  • Describe infrastructure in a language that is both machine and human friendly.
  • Focus on security and easy auditability/review/rewriting of content.
  • Manage new remote machines instantly, without bootstrapping any software.
  • Allow module development in any dynamic language, not just Python.
  • Be usable as non-root.
  • Be the easiest IT automation system to use, ever.

Use Ansible

You can install a released version of Ansible with pip or a package manager. See our installation guide for…

Setup a Windows Host with Ansible

Before Ansible can communicate with the Azure Windows virtual machine hosted in Azure the WinRM listener must be configured. In this tutorial you are configuring the WinRM listener to use port 5986 which uses self signed certificates for encryption. You will also use NTLM for your authentication as this virtual machine is not yet part of an Active Directory domain. All of the configuration is handled by a PowerShell script called ConfigureRemotingForAnsible.ps1. The next step is to have Ansible run that script on the Azure virtual machine. Delegating that task to the Azure virtual machine isn't an option because WinRM is not yet configured. However, Azure offers a custom script extensions resource that will allow you to delegate the task of running the ConfigureRemotingForAnsible.ps1 to Azure.

Using Azure VM Extension to Enable HTTPS WinRM Listener

Ansible's azure_rm_virtualmachineextension module allows you to create virtual machine custom script extensions. This module requires that you specify a name, resource_group, virtual_machine_name, and publisher. These parameters are all used to target the correct Azure virtual machine. The parameters; virtual_machine_extension_type, type_handler_version, and auto_upgrade_minor_version define and configure the extension being created. Which in this tutorial is a CustomScriptExtension. The settings parameter is where you instruct the CustomScriptExtension on what to do.

The problem you are attempting to solve with the custom script extension is to run the ConfigureRemotingForAnsible.ps1 on the newly deploy Azure virtual machine. Before the PowerShell script can be run, you'll have to download it. The Azure customs script extension allows you to do that by using the fileUris property. Once downloaded the script needs to be executed. You will use the commandToExecute property to specify the executable and the parameters for the executable. In this example the executable is PowerShell. The parameters used are ExecutionPolicy and File. ExecutionPolicy is set to Unrestricted which will allow you to run the script without modifying PowerShell's execution policies on the machine. File specifies the file name of the script to be executed. It assumes a relative path that fileUris used to download the script. Which means you do not need to specify the full path. These settings are stored in JSON and will be used by the settings parameter of the Ansible task.

Read more about Azure Custom Script Extensions.

{
    "fileUris": ["https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"],
    "commandToExecute": "powershell -ExecutionPolicy Unrestricted -File ConfigureRemotingForAnsible.ps1"
}
Enter fullscreen mode Exit fullscreen mode

To create an custom script extension resource in Azure you'll use the azure_rm_virtualmachineextension Ansible module. In order to create it, you must specify a name for the custom script extensions winrm-extension, a resource group ansible_rg, the virtual machine the extension will be attached to winWeb01, the publisher Microsoft.Compute, virtual machine extensions type CustomScriptExtension, type handler version 1.9, settings for the custom script extension using JSON, and define if the extension should auto upgrade minor versions.

    - name: create Azure vm extension to enable HTTPS WinRM listener
      azure_rm_virtualmachineextension:
        name: winrm-extension
        resource_group: ansible-rg
        virtual_machine_name: winWeb01
        publisher: Microsoft.Compute
        virtual_machine_extension_type: CustomScriptExtension
        type_handler_version: '1.9'
        settings: '{"fileUris": ["https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"],"commandToExecute": "powershell -ExecutionPolicy Unrestricted -File ConfigureRemotingForAnsible.ps1"}'
        auto_upgrade_minor_version: true
Enter fullscreen mode Exit fullscreen mode

This task will create a custom script extension for the Azure virtual machine winWeb01 that downloads the ConfigureRemotingForAnsible.ps1 script and then executes it using PowerShell.

Getting Azure vm Public Ip Address Info

Creating the custom script extension that runs ConfigureRemotingForAnsible.ps1 is all that you need to setup the WinRM listener. However, you will not be able to continue with configuring the virtual machine until the listener is responding. Ansible has the ability to wait for a connection, but before you can do that you'll need the public Ip address of the virtual machine. You can gather this information from Azure with Ansible using the azure_rm_publicipaddress_info module. All you need to specify is the resource group name and the name of the public ip address resource. To store the output of the module you'll use an Ansible feature called register. Register will store the output from the azure_rm_publicipaddress into a variable called publicipaddresses.

    - name: Get facts for one Public IP
      azure_rm_publicipaddress_info:
        resource_group: ansible-rg
        name: webPublicIP
      register: publicipaddresses
Enter fullscreen mode Exit fullscreen mode

Set Public Ip Address Fact

The variable publicipaddresses contains a lot more than just the public Ip address. It contains other information about the public ip address you don't need. Such as the allocation method, location, and sku.

publicipaddresses variable

{
"publicipaddresses": [
    {
        "allocation_method": "static", 
        "dns_settings": {}, 
        "etag": "W/\"3784787f-6752-46ff-8640-bf1fa3efcf0e\"", 
        "id": "/subscriptions/FAKEsubID-12341234adfasdf2-34341-131dfasd3/resourceGroups/ansible-rg/providers/Microsoft.Network/publicIPAddresses/webPublicIP", 
        "idle_timeout": 4, 
        "ip_address": "40.12.123.45", 
        "ip_tags": {}, 
        "location": "eastus", 
        "name": "webPublicIP", 
        "provisioning_state": "Succeeded", 
        "sku": "Basic", 
        "tags": null, 
        "type": "Microsoft.Network/publicIPAddresses", 
        "version": "ipv4"
    }
]
}
Enter fullscreen mode Exit fullscreen mode

Since the publicipaddresses variable is a JSON object you can query it to get just the information you need. You can accomplish this by using the set_fact Ansible module, which allows you to populate variables at run time. Ansible also provides several methods you can use to filter variables and other data. Since the variable you're working with is JSON, you'll use the JSON Query Filter. Using set_fact you'll set a create a new variable called publicipaddress. The value will be set by querying the publicipaddress variable. The JSON query takes the first item in the publicipaddress array and selects the ip_address property from that arrary. The ip_address property containers the public ipv4 Ip address of the virtual machine. With the public Ip address store in a variable the last task to create is a wait for connection task which waits for the custom script extension to setup WinRM.

    - name: set public ip address fact
      set_fact: publicipaddress="{{ publicipaddresses | json_query('publicipaddresses[0].ip_address')}}"
Enter fullscreen mode Exit fullscreen mode

Waiting for a WinRM Connection

When authoring Infrastructure as Code documents, you'll often encounter scenarios that require you to wait for a connection to become available. Some examples of that are after a virtual machine reboots or in this tutorial waiting for a script to execute that configures remote management of the virtual machine. Ansible provides a module that allows you to do this called wait_for. wait_for has a lot of flexibility but has requires three basic pieces of information. The port to communicate on the host to attempt to connect to and the timeout in seconds.

In this tutorial you'll wanting to connect to an Azure virtual machine on port 5986. The host information has been populated into a variable called publicipaddress and the timeout you'll set it to 600 seconds or 10 minutes.

    - name: wait for the WinRM port to come online
      wait_for:
        port: 5986
        host: '{{ publicipaddress }}'
        timeout: 600
Enter fullscreen mode Exit fullscreen mode

Provisioning Azure Resources

In this final step the only thing left is to chain the Ansible tasks together in a playbook and execute the playbook. The Ansible playbook contains two sections hosts and tasks. hosts specifies where and how to run the playbook. localhost defines the machine to run the playbook on. Which is the Ansible server. Setting the connection to local executes the playbook locally on the Ansible server vs. running the playbook over SSH or WinRM.

The tasks section defines all the plays Ansible will execute and the order in which they are executed. This playbook starts off by deploying an Azure Custom Script Extension to a virtual machine to configure WinRM. After that it gathers information about the virtual machine's public Ip address and sets an Ansible variable called publicipaddress containing the public ip address of the Azure virtual machine. The last tasks waits for a connection to the virtual machine on port 5986. If it does not succeed after 600 seconds it will timeout and fail. This step is used to verify the Azure Custom Script Extension was successful.

provisionWindowsVirtualMachine.yaml

---
- hosts: localhost
  connection: local

  tasks:
    - name: create Azure vm extension to enable HTTPS WinRM listener
      azure_rm_virtualmachineextension:
        name: winrm-extension
        resource_group: ansible-rg
        virtual_machine_name: winWeb01
        publisher: Microsoft.Compute
        virtual_machine_extension_type: CustomScriptExtension
        type_handler_version: '1.9'
        settings: '{"fileUris": ["https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"],"commandToExecute": "powershell -ExecutionPolicy Unrestricted -File ConfigureRemotingForAnsible.ps1"}'
        auto_upgrade_minor_version: true

    - name: Get facts for one Public IP
      azure_rm_publicipaddress_info:
        resource_group: ansible-rg
        name: webPublicIP
      register: publicipaddresses

    - name: set public ip address fact
      set_fact: publicipaddress="{{ publicipaddresses | json_query('publicipaddresses[0].ip_address')}}"

    - name: wait for the WinRM port to come online
      wait_for:
        port: 5986
        host: '{{ publicipaddress }}'
        timeout: 600
Enter fullscreen mode Exit fullscreen mode
ansible-playbook provisionWindowsVirtualMachine.yaml
Enter fullscreen mode Exit fullscreen mode

provision azure resources ansible gif

Conclusion

All configuration management tools require some sort of provisioning to be done in order to connect to remote machines. Even agentless configuration management tool such as Ansible require some provisioning in order to communicate with the machines using a remote management protocol. However, it is possible to define the provisioning steps and task in Ansible by leveraging automation provided by other platforms. Doing so allows you to keep the entire configuration in one place, such as an Ansible playbook. Keeping it all in one place or within one tool keeps it simple and clean.

πŸ’– πŸ’ͺ πŸ™… 🚩
joshduffney
Josh Duffney

Posted on January 15, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related