Ansible Network Templates
Introduction
Network configuration management can be complex and error-prone when done manually. Ansible Network Templates provide a powerful solution by allowing you to create standardized configuration templates that can be applied across multiple network devices. This approach ensures consistency, reduces human error, and dramatically speeds up the deployment process.
In this guide, we'll explore how to use Jinja2 templates with Ansible to create reusable, dynamic network configurations for various network devices like routers, switches, and firewalls.
What are Ansible Network Templates?
Ansible Network Templates combine Ansible's automation capabilities with Jinja2's templating language to create dynamic configuration files for network devices. Rather than manually configuring each device or writing static configuration files, you can create templates that:
- Contain variables that get replaced with actual values at runtime
- Include conditional logic to handle different device types or requirements
- Loop through collections of data to generate repetitive configurations
- Inherit from other templates to build complex configurations from simpler components
Getting Started with Jinja2 Templates
Jinja2 Basics
Jinja2 is a modern and designer-friendly templating language for Python. Ansible uses Jinja2 for its template module. Here's a quick overview of Jinja2 syntax:
- Variables:
{{ variable_name }}
- Control structures:
{% if condition %}...{% endif %}
- Loops:
{% for item in items %}...{% endfor %}
- Comments:
{# This is a comment #}
Let's start with a simple example:
hostname {{ inventory_hostname }}
interface Loopback0
ip address {{ loopback_ip }} 255.255.255.255
{% for interface in interfaces %}
interface {{ interface.name }}
description {{ interface.description }}
ip address {{ interface.ip_address }} {{ interface.subnet_mask }}
{% if interface.shutdown %}
shutdown
{% endif %}
{% endfor %}
Creating Your First Network Template
Let's create a basic template for configuring a Cisco router. First, create a template file named cisco_router.j2
:
! Basic configuration for {{ inventory_hostname }}
! Generated by Ansible on {{ ansible_date_time.date }}
hostname {{ inventory_hostname }}
{% if domain_name is defined %}
ip domain-name {{ domain_name }}
{% endif %}
! Configure interfaces
{% for interface in interfaces %}
interface {{ interface.name }}
description {{ interface.description | default('Configured by Ansible') }}
ip address {{ interface.ip }} {{ interface.netmask }}
{% if interface.state == "up" %}
no shutdown
{% else %}
shutdown
{% endif %}
{% endfor %}
! Configure routing
{% if routing_protocol == "ospf" %}
router ospf {{ ospf_process_id }}
{% for network in ospf_networks %}
network {{ network.address }} {{ network.wildcard }} area {{ network.area }}
{% endfor %}
{% elif routing_protocol == "eigrp" %}
router eigrp {{ eigrp_as }}
{% for network in eigrp_networks %}
network {{ network }}
{% endfor %}
{% endif %}
! End of configuration
Using Templates in Ansible Playbooks
Now that we have our template, let's create a playbook that uses it:
---
- name: Configure Network Devices
hosts: routers
gather_facts: no
vars:
domain_name: example.com
routing_protocol: ospf
ospf_process_id: 1
ospf_networks:
- address: 192.168.1.0
wildcard: 0.0.0.255
area: 0
- address: 10.0.0.0
wildcard: 0.0.0.255
area: 0
tasks:
- name: Generate device configurations
template:
src: templates/cisco_router.j2
dest: "configs/{{ inventory_hostname }}.cfg"
- name: Deploy configurations to devices
ios_config:
src: "configs/{{ inventory_hostname }}.cfg"
Variables and Inventory
For the templates to work properly, you need to define variables. This can be done in several ways:
- In the inventory file:
[routers]
router1 ansible_host=192.168.1.1
router2 ansible_host=192.168.1.2
[routers:vars]
domain_name=example.com
routing_protocol=ospf
- In host or group variable files:
Create a file in host_vars/router1.yml
:
interfaces:
- name: GigabitEthernet0/0
description: WAN Interface
ip: 203.0.113.1
netmask: 255.255.255.0
state: up
- name: GigabitEthernet0/1
description: LAN Interface
ip: 192.168.1.1
netmask: 255.255.255.0
state: up
Advanced Template Techniques
Template Filters
Jinja2 provides filters that allow you to transform data. Here are some useful ones:
{{ interface.description | default('No description') }} {# Provides a default value #}
{{ interface.name | upper }} {# Converts to uppercase #}
{{ ip_address | ipaddr('network') }} {# Extracts network address using the ipaddr filter #}
{{ vlans | join(', ') }} {# Joins list elements with a comma and space #}
Template Loops with Conditional Logic
You can combine loops with conditionals for more complex templates:
{% for vlan in vlans %}
vlan {{ vlan.id }}
name {{ vlan.name }}
{% endfor %}
{% for interface in interfaces %}
interface {{ interface.name }}
{% if interface.type == 'access' %}
switchport mode access
switchport access vlan {{ interface.vlan }}
{% elif interface.type == 'trunk' %}
switchport mode trunk
switchport trunk allowed vlan {{ interface.allowed_vlans | join(',') }}
{% endif %}
{% endfor %}
Using Include Statements
You can break larger templates into smaller, reusable components:
{# Main template: router_config.j2 #}
{% include 'partials/header.j2' %}
{% include 'partials/interfaces.j2' %}
{% include 'partials/routing.j2' %}
{% include 'partials/security.j2' %}
Real-World Examples
Example 1: Multi-Vendor Configuration Management
Let's create a more practical example where we configure devices from different vendors:
---
- name: Configure Network Devices
hosts: all
gather_facts: no
tasks:
- name: Generate Cisco IOS configuration
template:
src: templates/{{ ansible_network_os }}_config.j2
dest: "configs/{{ inventory_hostname }}.cfg"
when: ansible_network_os == 'ios'
- name: Generate Juniper JUNOS configuration
template:
src: templates/{{ ansible_network_os }}_config.j2
dest: "configs/{{ inventory_hostname }}.cfg"
when: ansible_network_os == 'junos'
- name: Deploy Cisco configurations
ios_config:
src: "configs/{{ inventory_hostname }}.cfg"
when: ansible_network_os == 'ios'
- name: Deploy Juniper configurations
junos_config:
src: "configs/{{ inventory_hostname }}.cfg"
format: text
when: ansible_network_os == 'junos'
The Cisco template (ios_config.j2
):
hostname {{ inventory_hostname }}
{% for acl in access_lists %}
ip access-list extended {{ acl.name }}
{% for rule in acl.rules %}
{{ rule.permission }} {{ rule.protocol }} {{ rule.source }} {{ rule.destination }}
{% endfor %}
{% endfor %}
The Juniper template (junos_config.j2
):
system {
host-name {{ inventory_hostname }};
{% if domain_name is defined %}
domain-name {{ domain_name }};
{% endif %}
}
{% for prefix_list in prefix_lists %}
policy-options {
prefix-list {{ prefix_list.name }} {
{% for prefix in prefix_list.prefixes %}
{{ prefix }};
{% endfor %}
}
}
{% endfor %}
Example 2: Automating VLAN Configuration
Here's a template for configuring VLANs on multiple switches:
{# vlan_config.j2 #}
{% for vlan in vlans %}
vlan {{ vlan.id }}
name {{ vlan.name }}
{% endfor %}
{% for interface in access_ports %}
interface {{ interface.name }}
description {{ interface.description }}
switchport mode access
switchport access vlan {{ interface.vlan_id }}
spanning-tree portfast
{% endfor %}
{% for interface in trunk_ports %}
interface {{ interface.name }}
description {{ interface.description }}
switchport mode trunk
switchport trunk allowed vlan {{ interface.allowed_vlans | join(',') }}
switchport trunk native vlan {{ interface.native_vlan }}
{% endfor %}
And the corresponding playbook:
---
- name: Configure VLANs on Switches
hosts: switches
gather_facts: no
tasks:
- name: Generate VLAN configurations
template:
src: templates/vlan_config.j2
dest: "configs/{{ inventory_hostname }}_vlans.cfg"
vars:
vlans:
- id: 10
name: Data
- id: 20
name: Voice
- id: 30
name: Management
access_ports:
- name: GigabitEthernet0/1
description: Office PC
vlan_id: 10
- name: GigabitEthernet0/2
description: IP Phone
vlan_id: 20
trunk_ports:
- name: GigabitEthernet0/48
description: Uplink to Core
allowed_vlans: [10, 20, 30]
native_vlan: 1
- name: Deploy VLAN configurations
ios_config:
src: "configs/{{ inventory_hostname }}_vlans.cfg"
Network Configuration Workflow with Templates
Let's visualize a complete workflow for network configuration management using Ansible templates:
Best Practices for Ansible Network Templates
- Keep Templates Modular: Break complex templates into smaller, reusable components.
- Use Version Control: Store your templates in a version control system like Git.
- Test Templates Before Deployment: Use Ansible's check mode (
--check
) to preview changes. - Document Templates: Add comments explaining the purpose and usage of each template.
- Use Consistent Naming Conventions: Create a logical naming scheme for templates and variables.
- Validate Variables: Check if required variables are defined before using them.
- Handle Errors Gracefully: Use conditional checks to prevent template rendering errors.
- Create Backup Configurations: Always backup current configurations before applying changes.
Summary
Ansible Network Templates provide a powerful way to manage network configurations at scale. By combining Ansible's automation capabilities with Jinja2's flexible templating language, you can create dynamic, reusable configurations that work across multiple vendors and device types.
The key benefits of using templates include:
- Consistency across your network infrastructure
- Reduced human error
- Faster deployment and updates
- Easier maintenance and troubleshooting
- Better documentation and change tracking
As your network grows in size and complexity, templates become essential for efficient management and operations.
Exercises
- Create a basic template for configuring NTP on a Cisco router with at least three NTP servers.
- Develop a template for configuring SNMP with different communities for read-only and read-write access.
- Build a template that configures a router's interfaces based on a CSV file containing interface names, IP addresses, and descriptions.
- Create a multi-vendor template that works for both Cisco IOS and Arista EOS devices.
- Develop a playbook that uses templates to generate complete router configurations, including routing protocols, NTP, SNMP, and access control lists.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)