Overview
In Ansible, there is a static inventory and a dynamic inventory. In a cloud world, it is often the case that Virtual Machines are spinning up and down in response to business demands. In this case, static inventories that list the IP Addresses and/or host names will not serve the needs of your business. Dynamic inventories are the solution to this problem.
An Azure Dynamic Inventory is provided out of the box and supports multiple means of connecting and pulling Virtual Machine information. A documentation link to the Ansible Azure Dynamic Inventory is provided here.
The following are methods in which you can leverage the Ansible dynamic inventory capabilities for Azure:
- include_vm_resource_groups – Find all Virtual Machines that are located within a specific resource group
- include_vmss_resource_groups – Find all Virtual Machine Scale Set Virtual Machines located within a specific resource group
- conditional_groups – Places a host in the named group if the associated condition evaluates to true
- hostvar_expressions – Adds variables to each host found by this inventory plugin, whose values are the result of the associated expression
- keyed_groups – Places hosts in dynamically-created groups based on a variable value
- exclude_host_filters – Excludes a host from the inventory when any of these expressions is true, can refer to any vars defined on the host
This will be a multiple-part series.
Part 1
- Installing Ansible on CentOS 7.7
- Create an Azure Service Principal that we will use to allow Ansible to authenticate to Azure via Dynamic Inventories.
- Set up a basic Azure Dynamic Inventory filtering on “include_vm_resource_groups” to test pinging a VM as well as find out the name Ansible uses to refer to this Virtual Machine in order to capture Ansible hostvars.
- Capture information via the Metadata Instance Service in order to see how Ansible hostvars pulls information from the VM.
- Capture the Ansible Hostvars of an Azure Virtual Machine. These variables will then be used throughout this article series to filter what Virtual Machines we want to target.
- Install Azure CLI
- Deploy a Virtual Machine Scale Set (VMSS) into an existing VNET
- Modify our Azure Dynamic Inventory filtering for “include_vmss_resource_groups” to test connectivity to VMSS Instances (VM Nodes) within the VMSS.
- Install a Second VM
- Configure Tags on VMs in Preparation of using Keyed_Groups
- Using Keyed_Groups to filter on Tags’ Key/Value Pair
- Using Keyed_Groups to filter on OS Type using Jinja2 Standard Dot Notation
- Filter out specific VMs using exclude_host_filters
- Using Conditional_Groups to filter on specific criteria
- Using Hostvar_Expressions to make modifications to specific hostvar values
Installing Ansible on CentOS 7.7
We will be leveraging an Azure Virtual Machine named iac as our Ansible Control Node. The Operating System for our Ansible Control Node is CentOS 7.7. In order to install Ansible, run the following commands.
sudo yum check-update; sudo yum install -y gcc libffi-devel python-devel openssl-devel epel-release
sudo yum install -y python-pip python-wheel
sudo pip install ansible[azure]
Feel free to install any other software such as Python 3. The above is the documented pre-requisites from Microsoft for installing Ansible.
Creating a SSH Key Pair
On the iac machine, our Ansible Control Node, generate an SSH Key Pair. This SSH Key Pair will be used when creating additional Virtual Machines. More specficially, we’ll take the public key of our key pair, and when creating Virtual Machines or Virtual Machine Scale Sets, we’ll use this Public Key in order to ensure our Ansible Control Node has the ability to SSH into our VMs and VMSSs.
It is important to note that Azure only supports RSA SSH Keys. If you try to specify another SSH Key Type such as ED25519 and ECDSA during the creation of a VM, it will fail. That doesn’t mean you can’t go into your authorized keys file later on and add them. I know ED25519 works post-install.
Let’s create our SSH Key Pair by typing in the following commands (let the key install to the default home directory location of ~/.ssh):
Ssh-keygen -t rsa -b 2048
cat ~/.ssh/id_rsa.pub
Keep the value of the id_rsa.pub handy for all parts of this blog series.
Creating an Azure Service Principal for use with Ansible Dynamic Inventories
Ansible uses a Service Principal account to authenticate to Azure. This requires Owner RBAC privileges in the Subscription you are running as we will be assigning Contributor permissions to this Service Principal Account.
Run the following command in Azure Cloud Shell:
az ad sp create-for-rbac --name="ansible_blog" --role="Contributor" --scopes="/subscriptions/[subscription id]"
We’ll want to make note of the following values:
- appID which will be the Ansible AZURE_CLIENT_ID
- password which will be the Ansible AZURE_SECRET
- tenant which will be the Ansible AZURE_TENANT
- subscription (from the az ad sp create command that was run) which will be the Ansible AZURE_SUBSCRIPTION
Ansible can use the Service Principal to authenticate in one of two ways. The first is by using an Ansible Credentials file. The second is using environmental variables. Let’s take a look at both.
Credentials File
Create a credential file in your home directory:
mkdir ~/.azure
vi ~/.azure/credentials
Insert the following four lines into the credentials file:
[default]
subscription_id=<Service Principal Subscription ID>
client_id=<Service Principal Application ID>
secret=<Service Principal Password>
tenant=<Azure Active Directory Tenant ID>
For example:
[default]
subscription_id=959b5xxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
client_id=65ed937b-03a4-4924-9fc0-ea70686b898d
secret=58667af6-e9fe-4c8d-b193-1e6b7b00bb76
tenant=7059d938-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Environmental Variables
export AZURE_SUBSCRIPTION_ID=<Service Principal Subscription ID>
export AZURE_CLIENT_ID=<Service Principal Application ID>
export AZURE_SECRET=<Service Principal Password>
export AZURE_TENANT=<Azure Active Directory Tenant ID>
For example:
export AZURE_SUBSCRIPTION_ID=959b5xxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export AZURE_CLIENT_ID=65ed937b-03a4-4924-9fc0-ea70686b898d
export AZURE_SECRET=58667af6-e9fe-4c8d-b193-1e6b7b00bb76
export AZURE_TENANT=7059d938-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Creating our first Dynamic Inventory File using Resource Groups
The simplest Dynamic Inventory File will look for all VMs in an Azure Resource Group. We have created a VM called vmblog01 in a Resource Group called vmblog. This VM was created using the SSH Public Key Value we created earlier in the article on our iac Ansible Control Node VM. Our goal is to be able to use our Ansible Control Node to obtain information and eventually configure our Virtual Machine, vmblog01.
Create a file called ansible_azure_rm.yml in a folder called Ansible located within our home directory. It can be called anything you want as long as the file ends in azure_rm.yml. Then we’ll look for VMs that are in our vmblog Resource Group.
mkdir ~/ansible
cd ~/ansible
touch ansible_azure_rm.yml
cat << EOF > ansible_azure_rm.yml
plugin: azure_rm
include_vm_resource_groups:
- vmblog
auth_source: auto
EOF
After our ansible_azure_rm.yml file is created, run the following command:
ansible all -m ping -i ansible_azure_rm.yml
We are telling ansible to use the ping module (-m ping) using the dynamic inventory file (-i ansible_azure_rm.yml) where the dynamic inventory file is filtering for all VMs in the vmblog Azure Resource Group. In our case, vmblog01 is the only VM in the vmblog Azure Resource Group. Therefore, Ansible ends up pinging vmblog01.
Take note of the VM name that is returned, vmblog01_7178. Even though the VM Name is vmblog01, we must use vmblog01_7178 when using Ansible to get information about this VM. The reason Ansible does this is because in Azure, you can have the same VM Name as long as those VMs are in different Resource Groups. By Ansible doing this, it ensures uniqueness.This can be disabled by adding the following into your Dynamic Inventory File:
plain_host_names: yes
Obtaining Hostvars Information
Hostvars lets you access variables for another host, including facts that have been gathered about that host. It is the Hostvars that allow us to do some filtering using the Dynamic Inventory feature as you will see as you read on through this series.
In order for us to see what hostvars are available on a host, we can use the Instance Metadata Service that is available on every Azure Virtual Machine.
Obtain the Public IP Address for your VM, not your Control Node VM.
SSH into the VM:
ssh <username>@<public ip>
Because our username is eshudnow and our public IP is 23.100.235.135, our command will be:
ssh [email protected]
Contact the Instance Metadata Service using curl and jq. If you don’t have jq installed, install jq and then run curl to contact the Instance Metadata Service.
sudo yum install epel-release -y
sudo yum install jq -y
jq --version
curl -sH Metadata:true "https://169.254.169.254/metadata/instance/?api-version=2019-03-11" | jq
The results from the Instance Metadata Service are as follows:
{
"compute": {
"azEnvironment": "AzurePublicCloud",
"customData": "",
"location": "northcentralus",
"name": "vmblog01",
"offer": "CentOS",
"osType": "Linux",
"placementGroupId": "",
"plan": {
"name": "",
"product": "",
"publisher": ""
},
"platformFaultDomain": "0",
"platformUpdateDomain": "0",
"provider": "Microsoft.Compute",
"publicKeys": [
{
"keyData": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/sLLG2X42jvWs0oiiDUFAqUicALaBN7tYi6a4lDR/k3fFbH0YIUs+H4SrjfyI8c4nq6zA4WtLNKIEONhxM/YNm87j4X5QeJx4P3gvCcQ9Q6HYLtWLOC5xHHvPMPQlXnR3aP9nVM+OxqqznyC0UK31H4VdHlwm4fDnHFzYegIdnfr4TD2/u49nwI4wsPg/xyNAtZYVKDt4JbsLhg3zkk5vf8K25pUlCw+ShH+DqEXh4OZEL1uKCkLaep7j72afr/eSHvXQrqNluG8O6n72BrVAdPvjohqQyPlbo/ZX9RiclNGOrOyRaslRA5LedUCfLb63TZjuzxXo7GlKWZBm2CKF eshudnow@iac",
"path": "/home/eshudnow/.ssh/authorized_keys"
}
],
"publisher": "OpenLogic",
"resourceGroupName": "vmblog",
"resourceId": "/subscriptions/959b5d63-3786-4191-ae93-d834739215d8/resourceGroups/vmblog/providers/Microsoft.Compute/virtualMachines/vmblog01",
"sku": "7.7",
"subscriptionId": "959b5d63-3786-4191-ae93-d834739215d8",
"tags": "",
"version": "7.7.20191209",
"vmId": "82b30bfb-364f-4345-87ef-c8eee26766b2",
"vmScaleSetName": "",
"vmSize": "Standard_D2s_v3",
"zone": ""
},
"network": {
"interface": [
{
"ipv4": {
"ipAddress": [
{
"privateIpAddress": "10.0.0.7",
"publicIpAddress": ""
}
],
"subnet": [
{
"address": "10.0.0.0",
"prefix": "24"
}
]
},
"ipv6": {
"ipAddress": []
},
"macAddress": "000D3A3C6EFA"
}
]
}
}
Remember how our vmblog01 was returned earlier as vmblog01_7178 when we did an ansible all -m ping -i ansible_azure_rm.yml? It’s time to use that with our Dynamic Inventory File. Don’t forget, our Dynamic Inventory File is looking for VMs in the vmblog Resource Group which contains our vmblog01 VM.
In order to get the hostvars from Ansible itself using the Dynamic Inventory File for our vmblog01 (or according to Ansible, vmblog01_7178), run the following command:
ansible vmblog01_7178 -m debug -a 'var=hostvars' -i ansible_azure_rm.yml
The results are as follows:
vmblog01_7178 | SUCCESS => {
"hostvars": {
"vmblog01_7178": {
"ansible_check_mode": false,
"ansible_diff_mode": false,
"ansible_facts": {},
"ansible_forks": 5,
"ansible_host": "23.100.235.135",
"ansible_inventory_sources": [
"/home/eshudnow/ansible/ansible_azure_rm.yml"
],
"ansible_playbook_python": "/usr/bin/python3",
"ansible_verbosity": 0,
"ansible_version": {
"full": "2.9.2",
"major": 2,
"minor": 9,
"revision": 2,
"string": "2.9.2"
},
"group_names": [
"ungrouped"
],
"groups": {
"all": [
"vmblog01_7178"
],
"ungrouped": [
"vmblog01_7178"
]
},
"id": "/subscriptions/959b5d63-3786-4191-ae93-d834739215d8/resourceGroups/vmblog/providers/Microsoft.Compute/virtualMachines/vmblog01",
"image": {
"offer": "CentOS",
"publisher": "OpenLogic",
"sku": "7.7",
"version": "latest"
},
"inventory_dir": "/home/eshudnow/ansible",
"inventory_file": "/home/eshudnow/ansible/ansible_azure_rm.yml",
"inventory_hostname": "vmblog01_7178",
"inventory_hostname_short": "vmblog01_7178",
"location": "northcentralus",
"mac_address": "00-0D-3A-3C-6E-FA",
"name": "vmblog01",
"network_interface": "vmblog0175",
"network_interface_id": "/subscriptions/959b5d63-3786-4191-ae93-d834739215d8/resourceGroups/vmblog/providers/Microsoft.Network/networkInterfaces/vmblog0175",
"omit": "__omit_place_holder__203137d4b15f6a9b59fd201e1c3bcf22b93a550b",
"os_disk": {
"name": "vmblog01_OsDisk_1_b09056b66dbb423caa17c363a99bbc4e",
"operating_system_type": "linux"
},
"os_profile": {
"system": "linux"
},
"plan": null,
"playbook_dir": "/home/eshudnow/ansible",
"powerstate": "running",
"private_ipv4_addresses": [
"10.0.0.7"
],
"provisioning_state": "succeeded",
"public_dns_hostnames": [],
"public_ip_id": "/subscriptions/959b5d63-3786-4191-ae93-d834739215d8/resourceGroups/vmblog/providers/Microsoft.Network/publicIPAddresses/vmblog01-ip",
"public_ip_name": "vmblog01-ip",
"public_ipv4_addresses": [
"23.100.235.135"
],
"resource_group": "vmblog",
"resource_type": "Microsoft.Compute/virtualMachines",
"security_group": null,
"security_group_id": null,
"tags": {},
"virtual_machine_size": "Standard_D2s_v3",
"vmid": "82b30bfb-364f-4345-87ef-c8eee26766b2",
"vmss": {}
}
}
}
It is these hostvars that we will use as we move on to leverage more Dynamic Inventory functionality. These hostvars are values that are pulled from a portion of the data provided via the Metadata Instance Service.
As we proceed with the article series, you will see how we leverage these hostvars to do more granular filtering to dynamically find VMs we want to configure.
In Part 2, we’ll take a look at installing the Azure CLI on our Ansible Control Node, using Azure CLI to deploy a Scale Set and automatically configure the SSH Public Key from our Ansible Control Node to allow our Ansible Control Node to connect to our VMSS Instances (VM Nodes) via SSH, and how to use include_vmss_resource_groups in our Dynamic Inventory File.
Sakthi says
Thanks Elan,i have tried but not working
– name: Create a VM with managed disk
azure_rm_virtualmachine:
resource_group: “{{ resourcegroup }}”
name: “{{ virtualmachine }}”
vm_size: Standard_B1ms
managed_disk_type: Premium_LRS
admin_username: chs_admin
admin_password: “{{ password }}”
network_interfaces: “{{ virtualmachine }}NIC”
os_type: Windows
boot_diagnostics:
storage_account: “{{ bootdia }}”
enabled : yes
image:
offer: WindowsServer
publisher: MicrosoftWindowsServer
sku: 2016-Datacenter
version: latest
license_type: Windows_Server
Azure having option to enable below option
Save money
Save up to 49% with a license you already own using Azure Hybrid Benefit. Learn more
Already have a Windows Server license?
Yes No
Elan Shudnow says
It appears you’re doing it correctly. If that doesn’t work, there must be a bug. In that case, I would open a GitHub issue here: https://github.com/ansible/ansible
Sakthi says
How to enable hybrid benefit for windows azure vm using ansible playbook
any one can help on this
Elan Shudnow says
license_type: Windows_Server
license_type: Windows_Client
license_type: None
license_type means you’re using an on-premises license. If you don’t have a Hybrid On-Premises License to use, you don’t have to specify license_type. If you’re using a playbook to reverse a Hybrid Benefit license, you can specify license_type: None to remove the Hybrid license benefit.
Marc says
This is how you write a blog! Thank you for posting, this helped me alot!
Elan Shudnow says
Thank you for the positive comments! I am happy the blog helped you!