Ansible Azure Examples | Querying Azure VMs with Ansible

How can we connect to Azure using Ansible?

Ansible is a versatile configuration management tool that has truly taken the world by storm offering strong integrations with cloud providers giving provisioning tools like terraform a definite run for their money. We could very easily connect to our resources in the Azure cloud using Ansible. But this involves creating a service principal for ansible in Azure Active Directory.

I recently came across a task wherein I needed to fetch configuration details about a virtual machine in the Azure cloud but due to security restrictions I could not get a service principal created for ansible. So, I choose a different route. Instead of using the Azure specific modules that Ansible has built in, I relied on embedding Azure CLI commands in the shell module in my ansible playbook, used a variable for the hostname and went about writing the required azure cli commands I needed to fetch the information from the virtual machine.

Advertisement

We could even use this methodology for creating and deleting virtual machines in the Azure cloud. But a major shortcoming of this approach is that the commands being executed would not be idempotent which is one of the cornerstones that configuration management software are built on. As a pre-requisite we need the azure cli installed on the system we’d like to run our ansible playbook from and the az login command should also be already executed to ensure that we’ve logged in to our Azure account.

In this article, we’ll install the pre-requisite azure cli on a Linux system which already has ansible installed on it and then execute the az login command to authenticate to the Azure portal.

 

Installing Azure CLI on Linux

Step 1: Import the Microsoft repository key

Execute the following command to import the microsoft repository key which we will use in next step:

[root@ansible-demo ~]# rpm --import https://packages.microsoft.com/keys/microsoft.asc

 

Step 2: Create local azure-cli repository information

The below command creates the azure-cli.repo file and populates it with the required Azure CLI repository location.

[root@ansible-demo ~]# echo -e "[azure-cli]
> name=Azure CLI
> baseurl=https://packages.microsoft.com/yumrepos/azure-cli
> enabled=1
> gpgcheck=1
> gpgkey=https://packages.microsoft.com/keys/microsoft.asc" | sudo tee /etc/yum.repos.d/azure-cli.repo
[azure-cli]
name=Azure CLI
baseurl=https://packages.microsoft.com/yumrepos/azure-cli
enabled=1
gpgcheck=1
gpgkey=https://packages.microsoft.com/keys/microsoft.asc

 

Step 3: Install azure-cli package

The Azure cli repository should now appear when we run the yum repolist command.

[root@ansible-demo ~]# yum repolist
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: download.cf.centos.org
 * epel: d2lzkl7pfhq30w.cloudfront.net
 * extras: download.cf.centos.org
 * nux-dextop: li.nux.ro
 * updates: download.cf.centos.org
azure-cli                                                                                                                           | 3.0 kB  00:00:00
azure-cli/primary_db                                                                                                                |  64 kB  00:00:00
repo id                                                     repo name                                                                                status
azure-cli                                                   Azure CLI                                                                                   108
base/7/x86_64                                               CentOS-7 - Base                                                                          10,072
epel/x86_64                                                 Extra Packages for Enterprise Linux 7 - x86_64                                           13,674
extras/7/x86_64                                             CentOS-7 - Extras                                                                           500
nux-dextop/x86_64                                           Nux.Ro RPMs for general desktop use                                                       2,724
updates/7/x86_64                                            CentOS-7 - Updates                                                                        2,751
xrdp                                                        xrdp                                                                                      2,724
repolist: 32,553
[root@ansible-demo ~]#

Now that we are certain that the repository is available, let’s install the azure cli binary using yum.

Advertisement
[root@ansible-demo ~]# yum install azure-cli -y
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: download.cf.centos.org
 * epel: d2lzkl7pfhq30w.cloudfront.net
 * extras: download.cf.centos.org
 * nux-dextop: li.nux.ro
 * updates: download.cf.centos.org
Resolving Dependencies
--> Running transaction check
---> Package azure-cli.x86_64 0:2.28.0-1.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved
====================================================================
 Package          Arch           Version             Repository         Size
====================================================================
Installing:
 azure-cli        x86_64         2.28.0-1.el7        azure-cli           45 M

Transaction Summary
====================================================================
Install  1 Package

Total download size: 45 M
Installed size: 595 M
Downloading packages:
azure-cli-2.28.0-1.el7.x86_64.rpm                             |  45 MB  00:00:00
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : azure-cli-2.28.0-1.el7.x86_64                                                                                                           1/1
  Verifying  : azure-cli-2.28.0-1.el7.x86_64                                                                                                           1/1

Installed:
  azure-cli.x86_64 0:2.28.0-1.el7

Complete!

Run the Azure CLI with the az command. To sign in, use az login command.

 

Step 4: Run the login command.

Now that we have successfully installed the Azure CLI package, we’ll authenticate to our Azure account with it so that we may interact with our resources inside the Azure cloud via the Azure CLI.

When we run the az login command it will display a code to authenticate to the Azure portal. We need to open the URL https://microsoft.com/devicelogin and provide the code.

Once the code is accepted we are prompted for our credentials. After successfully authentication, the portal will display the following message.

Ansible Azure Examples | Querying Azure VMs with Ansible

 

On the command line, the az login command would display some information about our account like the subscription and logged in user as shown below.

[root@ansible-demo ~]# az login
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code HTTE53Q4C to authenticate.
[
  {
    "cloudName": "AzureCloud",
    "homeTenantId": "3617ef9b-98b4-40d9-ba43-e1ed6709cf0d",
    "id": "964df7ca-3ba4-48b6-a695-1ed9db5723f8",
    "isDefault": true,
    "managedByTenants": [],
    "name": "cloud-demo ",
    "state": "Enabled",
    "tenantId": "3617ef9b-98b4-40d9-ba43-e1ed6709cf0d",
    "user": {
      "name": "demouser@example.com",
      "type": "user"
    }
  }
]

We can now run azure cli commands to interact with our resources in the azure portal.

 

Ansible playbook setup to query VM information in Azure

Given below is a glimpse of my playbook folder structure.

[root@ansible-demo Ansible_to_Azure]# pwd
/root/Ansible_to_Azure

[root@ansible-demo Ansible_to_Azure]# ls -ltrR
.:
total 20
-rw-r--r--. 1 root root 1447 Oct 19 17:01 info.json
-rw-r--r--. 1 root root 4374 Oct 19 17:01 get_info.yml
drwxr-xr-x. 2 root root   20 Oct 19 17:01 templates
-rw-r--r--. 1 root root   10 Oct 19 17:01 inventory

./templates:
total 4
-rw-r--r--. 1 root root 1293 Oct 19 17:01 info.j2

Here,

Advertisement
  • Inventory file : This is where we will add the hostname of the VM that we wish to query. Note we are not using dynamic inventories at this time.
  • Get_info.yml: This is the actual ansible playbook that we are going to execute.
  • Templates: This directory contains a jinja2 template file that will be used to store and format the information retrieved by the playbook. The name of the template file is info.j2.
  • Info.json: This is the output file generated from the jinja2 template file that we house in the templates directory. The output from the jinja2 template is in json format and hence the name info.json.

 

The get_info.yml playbook

Azure VMs have a plethora of information that can be retrieved using the Azure CLI. In this playbook, we’ve retrieved the following information:

  • CPU
  • Memory allocated
  • Region/AZ
  • Public IP
  • Private IP
  • Network Interface Card name
  • Number of disks
  • Disk type
  • Size of each disk
  • Disk IOPS
  • Caching on disk
  • Network security group rules

Here is the content of the get_info.yml playbook.

[root@ansible-demo Ansible_to_Azure]# cat get_info.yml
---
- name: Query information about Azure
  hosts: all
  gather_facts: no
  vars:
   rg: 1-7b72c7b6-playground-sandbox-2
   region: eastus2

  tasks:
  - name: Get VM Size
    shell: "az vm show -g {{ rg }} -n {{ansible_hostname}} --query 'hardwareProfile.vmSize'"
    register: vm_size
    delegate_to: localhost

  - name: Store vm size as a fact
    set_fact:
     VM_Size: "{{ vm_size.stdout_lines[0] }}"

  - name: Get cpu and memory information of VM
    shell: "az vm list-sizes --location {{ region }} | grep -A1 -B1 {{ VM_Size }}| awk '{print $2}'"
    register: out
    delegate_to: localhost

  - name: Get Public IP address of VM
    shell: "az vm show -d -g {{ rg }} -n {{ansible_hostname}} --query publicIps -o tsv"
    register: publicip
    delegate_to: localhost

  - name: Get Private IP address of VM
    shell: "az vm show -d -g {{ rg }} -n {{ansible_hostname}} --query privateIps -o tsv"
    register: privateip
    delegate_to: localhost

  - name: Get allocated disk count
    shell: "az vm show -d -g {{ rg }} -n {{ansible_hostname}} | grep -c Microsoft.Compute/disks"
    register: diskcount
    delegate_to: localhost

  - name: Get disk names and stroage account types
    shell:  az vm show --resource-group {{ rg }} --name {{ansible_hostname}} --query "storageProfile.dataDisks[][{Name:name,StorageAccountType:managedDisk}]"
    register: disk_info
    delegate_to: localhost

  - name: Set disk info as a fact
    set_fact:
     disk_list: "{{disk_info.stdout}}"

  - name: Get size of each disk
    shell: az vm show -g {{ rg }} -n {{ansible_hostname}} --query "storageProfile.dataDisks[][{Name:name,DiskSizeGB:diskSizeGb}]" -o table | awk -F, 'NR > 2 {print $2, $4}' | tr -d ')])'
    register: disk_size_list
    delegate_to: localhost

  - name: Set fact for disk sizes
    set_fact: disk_size_lists="{{disk_size_list.stdout_lines}}"

  - name: Get VM disk caching information
    shell: az vm show -g {{rg}} -n {{ansible_hostname}} --query "storageProfile.dataDisks[][{Name:name,Caching:caching}]" -o table | awk -F, 'NR>2 {print $2, $4}' | tr -d ")])'"
    register: disk_caching
    delegate_to: localhost

  - name: Set Disk caching information as a fact
    set_fact: disk_cache="{{disk_caching.stdout_lines}}"

  - name: Get VM region and Availability zone
    shell: az vm show -g {{rg}} -n {{ansible_hostname}} --query "[location, zones]" -o tsv | tr '\n' ' '
    register: az_info
    delegate_to: localhost

  - name: set fact for region and AZ
    set_fact: az_region_info="{{az_info.stdout}}"

  - name: Get Default NSG details
    shell: az network nsg list --resource-group {{rg}} --query "[].defaultSecurityRules[].{Name:name, Accecc:access, Direction:direction, DestAddr:destinationAddressPrefix, SourceAddr:sourceAddressPrefix,DestPort:destinationPortRange, SourcePort:sourcePortRange }" -o tsv
    register: def_nsg_out
    delegate_to: localhost

  - name: Set fact for default NSG rules
    set_fact: def_nsg_out_fact="{{def_nsg_out.stdout}}"

  - name: Get User defined NSG rule details
    shell: az network nsg list --resource-group {{rg}} --query "[].securityRules[].{Name:name, Accecc:access, Direction:direction, DestAddr:destinationAddressPrefix, SourceAddr:sourceAddressPrefix,DestPort:destinationPortRange, SourcePort:sourcePortRange }" -o tsv
    register: ud_nsg_out
    delegate_to: localhost

  - name: Set fact for User defined NSG rules
    set_fact: ud_nsg_out_fact="{{ud_nsg_out.stdout}}"

  - name: Get VM NIC name
    shell: "az vm nic list -g {{ rg }}  --vm-name {{ansible_hostname}} --query [].id | grep subscriptions | awk -F/ '{print $NF}'"
    register: nicname
    delegate_to: localhost

  - name: Set fact for NIC name
    set_fact: nic_name="{{nicname.stdout}}"

  - name: Set fact for disk count
    set_fact: disk_count="{{diskcount.stdout}}"

  - name: Set fact for Public IP
    set_fact:
     public_ip: "{{publicip.stdout_lines[0]}}"

  - name: Set fact for Private IP
    set_fact:
     private_ip: "{{privateip.stdout_lines[0]}}"

  - name: Set fact for CPU
    set_fact:
     num_cpu: "{{ out.stdout_lines[2] | regex_replace(',', '') }}"

  - name: Set fact for Memory
    set_fact:
     mem_mb: "{{ out.stdout_lines[0] | regex_replace(',', '') }}"


  - name: Populate template file with info
    template:
     src: info.j2
     dest: /root/ansible_azure/info.json
    delegate_to: localhost

We will not be diving deep into the details of each task defined inside the playbook (you can read our ansible tutorial to learn more) but will instead elaborate on the general theme of the playbook.

The tasks using the shell module are running different Azure CLI commands on the virtual machine. We are using the “delegate_to: localhost” flag because these commands are being executed on the localhost. We have used a lot of set_fact tasks because these allow us to use variables to store the value of registered output variables in the tasks and then use them in the jinja2 template.

In the last task of the playbook, the variables used in the jinja2 template are populated with their actual values and stored in the file /root/ansible_azure/info.json.

 

The info.j2 template file

The template file contains a lot of information and uses loop and conditional constructs to filter out the desired information. We’ve also made heavy use of jinj2 filters replace and split in the template file to filter out the data. Given below content of the file.

[root@ansible-demo Ansible_to_Azure]# cat templates/info.j2
{
 "Number of CPUs": "{{ num_cpu }}",
 "Memory in MB": "{{mem_mb}}",
 "region/AZ":"{{az_region_info}}"
 "Public IP address": "{{public_ip}}",
 "Private IP address": "{{private_ip}}",
 "Number of disks": "{{disk_count}}",
 "VM NICname is": "{{nic_name}}",
 "Disk Storage type:" {
 {% for disk  in disk_list -%}
 "{{ disk[0].Name}}":"{{ disk[0].StorageAccountType.storageAccountType }}"
{% endfor %}
 }
 "Disk IOPS" {
  {% for disk  in disk_list -%}
  {% if disk[0].StorageAccountType.storageAccountType  == 'Premium_LRS' %}
  "{{ disk[0].Name}}":"120",
  {% elif disk[0].StorageAccountType.storageAccountType  == 'StandardSSD_LRS' %}
  "{{ disk[0].Name}}":"500",
  {% else %}
  "{{ disk[0].Name}}":"500"
  {% endif %}
  {% endfor %}
 }
 "Disk Size:" {
 {% for disk_s in disk_size_lists -%}
 "{{disk_s.split(' ')[1]| replace("'",'') }}":"{{disk_s.split(' ')[-1]}}"
 {% endfor %}
 }
 "Disk Cache:" {
 {% for disk_c in disk_cache -%}
 "{{disk_c.split(' ')[1]}}":"{{disk_c.split(' ')[-1]}}"
 {% endfor %}
 }
 "Default NSG details" {
  "Name Access Direction DestAddr SourceAddr DestPort SourcePort",
  "{{def_nsg_out_fact| replace("\t",' ')}}",
 }
 "User DEfined NSG details" {
  "Name Access Direction DestAddr SourceAddr DestPort SourcePort",
  "{{ud_nsg_out_fact| replace("\t",' ')}}",
 }
}

To run the playbook type the following command:

ansible-playbook -i inventory info.yml

 

The resulting info.json file has the following content.

[root@ansible-demo Ansible_to_Azure]# cat info.json
{
 "Number of CPUs": "2",
 "Memory in MB": "8192",
 "region/AZ":"eastus2 2"
 "Public IP address": "20.190.193.149",
 "Private IP address": "10.0.0.4",
 "Number of disks": "5",
 "VM NICname is": "azuredemo851",
 "Disk Storage type:" {
 "azuredemo_DataDisk_0":"Premium_LRS"
"azuredemo_DataDisk_1":"StandardSSD_LRS"
"azuredemo_DataDisk_2":"Standard_LRS"
"azuredemo_DataDisk_3":"Premium_LRS"
 }
 "Disk IOPS" {
    "azuredemo_DataDisk_0":"120",
      "azuredemo_DataDisk_1":"500",
      "azuredemo_DataDisk_2":"500"
      "azuredemo_DataDisk_3":"120",
     }
 "Disk Size:" {
 "azuredemo_DataDisk_0":"8"
 "azuredemo_DataDisk_1":"32"
 "azuredemo_DataDisk_2":"64"
 "azuredemo_DataDisk_3":"4"
  }
 "Disk Cache:" {
 "azuredemo_DataDisk_0":"ReadOnly"
 "azuredemo_DataDisk_1":"ReadWrite"
 "azuredemo_DataDisk_2":"ReadWrite"
 "azuredemo_DataDisk_3":"None"
  }
 "Default NSG details" {
  "Name Access Direction DestAddr SourceAddr DestPort SourcePort",
  "AllowVnetInBound Allow Inbound VirtualNetwork VirtualNetwork * *
AllowAzureLoadBalancerInBound Allow Inbound * AzureLoadBalancer * *
DenyAllInBound Deny Inbound * * * *
AllowVnetOutBound Allow Outbound VirtualNetwork VirtualNetwork * *
AllowInternetOutBound Allow Outbound Internet * * *
DenyAllOutBound Deny Outbound * * * *",
 }
 "User DEfined NSG details" {
  "Name Access Direction DestAddr SourceAddr DestPort SourcePort",
  "SSH Allow Inbound * * 22 *
Port_8080 Allow Inbound * * 8080 *",
 }
}

 

Summary

In this article, we shared a practically tested method of querying information about a virtual machine in Azure using an Ansible playbook. The playbook itself contains multiple Azure CLI commands which we encourage you to check out individually. Also, the way the playbook and the jinja2 template have been written and formatted should help you in writing playbooks in future where there is a similar requirement.

 

References

We referred to the official Microsoft documentation for installing Azure CLI on our VM and also for adding queries to our Azure CLI commands. Links to both have been shared below.
Install the Azure CLI on Linux
How to query Azure CLI command output using a JMESPath query

 

Didn't find what you were looking for? Perform a quick search across GoLinuxCloud

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

Leave a Comment