If like me you have been guilty of hard coding all of your network configuration in Ansible variables like this …
|
|
… then this post is for you. Not that I am saying that is wrong, but it does mean if you want to the network range you are using them you also have to update it in four other places.
Working with network ranges dynamically in Ansible
Instead of hardcoding subnet addresses, we can leverage Ansible’s powerful filters and plugins to calculate network and subnet information dynamically. This approach enhances the flexibility and maintainability of your playbooks.
A Basic Playbook
Let’s start by creating a playbook that calculates subnet addresses based on a given network range and subnet sizes.
|
|
Breaking Down the Playbook
The playbook is designed to generate and display network and subnet information based on a given CIDR range. Let’s break it down:
Playbook Header:
- Name: “Network and Subnet Information Playbook”
- Hosts: localhost (runs on the local machine)
- gather_facts: true (collects system information)
Variables:
- cidr_range: Defines the main network CIDR (10.0.0.0/24)
- subnets: A list of 4 subnets, each with a name and CIDR size (27 bits)
Tasks:
The playbook contains several tasks that generate and display network information.
Tasks
Generate network information:
- Creates a ’network_info’ fact containing the CIDR range and available IP addresses
- Uses the ‘ansible.utils.ipaddr’ filter to calculate the number of available IPs
Generate subnet information:
- Creates a ‘subnet_info’ list containing each subnet’s name and CIDR
- Uses ‘ansible.utils.ipsubnet’ to calculate subnet CIDRs
- Utilizes a loop with zip_longest to iterate over subnets
Calculate detailed subnet information:
- Expands on ‘subnet_info’ to create ‘subnet_details’
- Adds network, netmask, broadcast, first IP, and last IP for each subnet
- Uses various ‘ansible.utils.ipaddr’ filters to calculate these values
Display network information:
- Creates a formatted output string with network overview
- Uses ansible.builtin.debug to display the information
Display detailed subnet information:
- Creates a formatted output string with detailed subnet information
- Uses ansible.builtin.debug to display the information
Understanding the ansible.utils.ipsubnet function
The ‘ansible.utils.ipsubnet’ function is used to calculate subnet information based on a given network and subnet size. In this playbook, it’s used as follows:
|
|
- cidr_range: The base network (10.0.0.0/24)
- item.0.cidr_size: The size of the subnet (27 in this case)
- item.1: The index of the current subnet (0, 1, 2, 3)
This function calculates the CIDR for each subnet within the main network. For example:
- Subnet 1: 10.0.0.0/27
- Subnet 2: 10.0.0.32/27
- Subnet 3: 10.0.0.64/27
- Subnet 4: 10.0.0.96/27
Understanding the ansible.utils.ipaddr function
The ‘ansible.utils.ipaddr’ function is a versatile filter for IP address manipulation. In this playbook, it’s used in several ways, as you can see from the examples below the ‘ansible.utils.ipaddr’ function accepts various options to perform different operations on IP addresses and networks, making it a powerful tool for network-related tasks in Ansible playbooks.
Calculating available IPs
|
|
This calculates the total number of IP addresses in the CIDR range.
Getting network address
|
|
This extracts the network address from a CIDR.
Getting netmask
|
|
This calculates the subnet mask for a given CIDR.
Getting broadcast address
|
|
This calculates the broadcast address for a subnet.
Calculating first and last usable IPs
|
|
These calculate the first and last usable IP addresses in a subnet.
Running the Playbook
Running the site.yml
playbook using the following:
ansible-playbook site.yml
Gives the following output:
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit
localhost does not match 'all'
PLAY [Network and Subnet Information Playbook] *************************************
TASK [Gathering Facts] *************************************************************
ok: [localhost]
TASK [Generate network information] ************************************************
ok: [localhost]
TASK [Generate subnet information] *************************************************
ok: [localhost] => (item=[{'name': 'subnet1', 'cidr_size': 27}, 0])
ok: [localhost] => (item=[{'name': 'subnet2', 'cidr_size': 27}, 1])
ok: [localhost] => (item=[{'name': 'subnet3', 'cidr_size': 27}, 2])
ok: [localhost] => (item=[{'name': 'subnet4', 'cidr_size': 27}, 3])
TASK [Calculate detailed subnet information] ***************************************
ok: [localhost] => (item={'name': 'subnet1', 'cidr': '10.0.0.0/27'})
ok: [localhost] => (item={'name': 'subnet2', 'cidr': '10.0.0.32/27'})
ok: [localhost] => (item={'name': 'subnet3', 'cidr': '10.0.0.64/27'})
ok: [localhost] => (item={'name': 'subnet4', 'cidr': '10.0.0.96/27'})
TASK [Display network information] *************************************************
ok: [localhost] => {
"msg": [
"===============================",
"Network Information",
"===============================",
"CIDR Range: 10.0.0.0/24",
"Available IP Addresses: 254",
"",
"Subnet Overview:",
"- subnet1: 10.0.0.0/27",
"- subnet2: 10.0.0.32/27",
"- subnet3: 10.0.0.64/27",
"- subnet4: 10.0.0.96/27",
""
]
}
TASK [Display detailed subnet information] ****************************************
ok: [localhost] => {
"msg": [
"===============================",
"Detailed Subnet Information",
"===============================",
"subnet1:",
" CIDR: 10.0.0.0/27",
" Network: 10.0.0.0",
" Netmask: 255.255.255.224",
" Broadcast: 10.0.0.31",
" First Usable IP: 10.0.0.1",
" Last Usable IP: 10.0.0.30",
"",
"subnet2:",
" CIDR: 10.0.0.32/27",
" Network: 10.0.0.32",
" Netmask: 255.255.255.224",
" Broadcast: 10.0.0.63",
" First Usable IP: 10.0.0.33",
" Last Usable IP: 10.0.0.62",
"",
"subnet3:",
" CIDR: 10.0.0.64/27",
" Network: 10.0.0.64",
" Netmask: 255.255.255.224",
" Broadcast: 10.0.0.95",
" First Usable IP: 10.0.0.65",
" Last Usable IP: 10.0.0.94",
"",
"subnet4:",
" CIDR: 10.0.0.96/27",
" Network: 10.0.0.96",
" Netmask: 255.255.255.224",
" Broadcast: 10.0.0.127",
" First Usable IP: 10.0.0.97",
" Last Usable IP: 10.0.0.126",
""
]
}
PLAY RECAP ************************************************************************
localhost: ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
As you can see, we are getting quite alot of information about the network with only telling Ansible the CIDR range to use and the number of subnets and their size.
Deploying an Azure Virtual Network
This Ansible playbook automates the creation of an Azure Virtual Network (VNet) and multiple subnets. It leverages the same network calculation concepts as our previous example, applying them to real Azure resources. The playbook creates a VNet with a specified CIDR range and dynamically generates subnets based on given parameters. It uses Azure Resource Manager modules to interact with Azure, and concludes by displaying detailed information about the created network infrastructure - which you probably would never include in your own Playbooks - I have included it here so we can see the output.
This automation streamlines the process of setting up complex network configurations in Azure, ensuring consistency and reducing manual errors.
The Playbook itself looks like the following:
The Azure Vnet Playbook
|
|
Running the Playbook
Running the azure.yml
playbook using the following:
ansible-playbook site.yml
Gives the following output:
PLAY [Create Azure Virtual Network and Subnets] ************************************
TASK [Ensure resource group exists] ************************************************
changed: [localhost]
TASK [Calculate subnet CIDRs] ******************************************************
ok: [localhost] => (item=[{'name': 'subnet1', 'cidr_size': 27}, 0])
ok: [localhost] => (item=[{'name': 'subnet2', 'cidr_size': 27}, 1])
ok: [localhost] => (item=[{'name': 'subnet3', 'cidr_size': 27}, 2])
ok: [localhost] => (item=[{'name': 'subnet4', 'cidr_size': 27}, 3])
TASK [Create virtual network] ******************************************************
changed: [localhost]
TASK [Create subnets] ***************************************************************
changed: [localhost] => (item={'name': 'subnet1', 'cidr': '10.0.0.0/27'})
changed: [localhost] => (item={'name': 'subnet2', 'cidr': '10.0.0.32/27'})
changed: [localhost] => (item={'name': 'subnet3', 'cidr': '10.0.0.64/27'})
changed: [localhost] => (item={'name': 'subnet4', 'cidr': '10.0.0.96/27'})
TASK [Gather VNet facts] ************************************************************
ok: [localhost]
TASK [Gather VNet facts] *************************************************************
ok: [localhost]
TASK [Calculate detailed subnet information] *****************************************
ok: [localhost] => (item={'id': '/subscriptions/ce7aa0b9-3545-4104-99dc-d4d082339a05/resourceGroups/rg-ansible-demo-uks/providers/Microsoft.Network/virtualNetworks/vnet-ansible-demo-uks/subnets/subnet1', 'name': 'subnet1', 'provisioning_state': 'Succeeded', 'address_prefix': '10.0.0.0/27', 'address_prefixes': None, 'network_security_group': None, 'route_table': None})
ok: [localhost] => (item={'id': '/subscriptions/ce7aa0b9-3545-4104-99dc-d4d082339a05/resourceGroups/rg-ansible-demo-uks/providers/Microsoft.Network/virtualNetworks/vnet-ansible-demo-uks/subnets/subnet2', 'name': 'subnet2', 'provisioning_state': 'Succeeded', 'address_prefix': '10.0.0.32/27', 'address_prefixes': None, 'network_security_group': None, 'route_table': None})
ok: [localhost] => (item={'id': '/subscriptions/ce7aa0b9-3545-4104-99dc-d4d082339a05/resourceGroups/rg-ansible-demo-uks/providers/Microsoft.Network/virtualNetworks/vnet-ansible-demo-uks/subnets/subnet3', 'name': 'subnet3', 'provisioning_state': 'Succeeded', 'address_prefix': '10.0.0.64/27', 'address_prefixes': None, 'network_security_group': None, 'route_table': None})
ok: [localhost] => (item={'id': '/subscriptions/ce7aa0b9-3545-4104-99dc-d4d082339a05/resourceGroups/rg-ansible-demo-uks/providers/Microsoft.Network/virtualNetworks/vnet-ansible-demo-uks/subnets/subnet4', 'name': 'subnet4', 'provisioning_state': 'Succeeded', 'address_prefix': '10.0.0.96/27', 'address_prefixes': None, 'network_security_group': None, 'route_table': None})
TASK [Display detailed VNet and Subnet information] **********************************
ok: [localhost] => {
"msg": [
"===============================",
"Virtual Network Information",
"===============================",
"Name: vnet-ansible-demo-uks",
"Resource Group: rg-ansible-demo-uks",
"Location: uksouth",
"Address Space: 10.0.0.0/24",
"",
"===============================",
"Detailed Subnet Information",
"===============================",
"subnet1:",
" CIDR: 10.0.0.0/27",
" Network: 10.0.0.0",
" Netmask: 255.255.255.224",
" Broadcast: 10.0.0.31",
" First Usable IP: 10.0.0.1",
" Last Usable IP: 10.0.0.30",
"",
"subnet2:",
" CIDR: 10.0.0.32/27",
" Network: 10.0.0.32",
" Netmask: 255.255.255.224",
" Broadcast: 10.0.0.63",
" First Usable IP: 10.0.0.33",
" Last Usable IP: 10.0.0.62",
"",
"subnet3:",
" CIDR: 10.0.0.64/27",
" Network: 10.0.0.64",
" Netmask: 255.255.255.224",
" Broadcast: 10.0.0.95",
" First Usable IP: 10.0.0.65",
" Last Usable IP: 10.0.0.94",
"",
"subnet4:",
" CIDR: 10.0.0.96/27",
" Network: 10.0.0.96",
" Netmask: 255.255.255.224",
" Broadcast: 10.0.0.127",
" First Usable IP: 10.0.0.97",
" Last Usable IP: 10.0.0.126",
""
]
}
PLAY RECAP ***************************************************************************
localhost: ok=8 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
As you can see, we have the VNet and subnets all created as well.
Extending the Playbook further
We could extend the playbook further, for example we could deploy an Azure Application Gateway into one of the subnets and get its static internal IP address by figuring out the forth IP address in subnet1
by using:
|
|
Summary
In this post, we’ve explored the power of dynamic network configuration in Ansible, moving beyond hardcoded values to create more flexible and maintainable playbooks. We started with a local playbook that calculates subnet information based on a given CIDR range, utilizing Ansible’s powerful ansible.utils.ipsubnet
and ansible.utils.ipaddr
functions.
We then applied these concepts to a real-world scenario, creating an Azure Virtual Network with multiple subnets. This approach not only simplifies our code but also makes it more adaptable to changes in network requirements.
Key takeaways include:
- Using
ansible.utils.ipsubnet
to dynamically calculate subnet CIDRs - Leveraging
ansible.utils.ipaddr
for detailed IP address manipulation - Applying these techniques to Azure VNet and subnet creation
- Extending the playbook to include advanced resources like Azure Application Gateway
By adopting these practices, you can create more robust, flexible, and error-resistant network configurations in your Ansible playbooks. Whether you’re working with local environments or cloud providers like Azure, these techniques will help you manage network infrastructure more effectively. I would encourage you to experiment with these methods in your own projects. As you become more comfortable with dynamic network calculations, you’ll find that your infrastructure-as-code becomes more powerful and adaptable to changing requirements.