Multi-Vendor Network Automation: A Practical Guide to Ansible and Jinja2

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

The Chaos of the Multi-Vendor Network

Last year, our infrastructure team hit a breaking point. We were managing a backbone of 40 Juniper MX routers, 150 Cisco Catalyst switches, and 72 remote branch offices running Mikrotik CCRs. Every time we needed to provision a new VLAN or update an NTP server, three different engineers had to log into three separate CLI environments. The syntax for a Cisco description differs from a Juniper description, which is worlds apart from a Mikrotik comment.

Simple typos caused frequent outages. A missed commit on a Juniper box or a forgotten write memory on a Cisco switch meant configurations vanished after a reboot. Manual management wasn’t just slow; it was a liability. We needed to stop treating our routers like pets and start treating them like code.

Why Manual CLI and Custom Scripting Hit a Ceiling

Our first attempt at a solution involved custom Python scripts using Paramiko and Netmiko. These libraries are powerful, but we quickly found ourselves maintaining over 1,400 lines of fragile boilerplate code. We were spending more time handling SSH timeouts and vendor-specific error parsing than actually managing the network. We weren’t building a network; we were building a software application just to keep the lights on.

The real bottleneck was the lack of an abstraction layer. In a mixed environment, your “intent”—such as setting a DNS server to 8.8.8.8—is universal. However, the implementation is fragmented across vendors. We needed a tool that defined the desired state of the network without requiring us to be syntax experts for every single operating system.

Choosing the Right Tooling: Python vs. Ansible

We evaluated three primary paths:

  • Manual CLI: No upfront cost, but high operational risk and zero scalability.
  • Custom Python Scripts: Highly flexible, but they require professional-grade programming skills that junior admins often lack.
  • Ansible: Agentless, uses human-readable YAML, and features a massive ecosystem of Network Modules maintained directly by vendors.

Ansible won because it separates data (like IP addresses and VLAN IDs) from logic (the specific commands needed to apply them). This separation is where the combination of Ansible Network Modules and Jinja2 templates becomes a critical advantage.

The Production Workflow: Modules + Templates

After six months in a live environment, our workflow has settled into a clean, three-tier architecture. This approach shifts your focus from “how do I type this?” to “what should the network look like?”

1. Defining a Multi-Vendor Inventory

Ansible needs to know which driver to use for each device. We define this in our hosts.ini. By setting the ansible_network_os variable, we tell Ansible exactly which module set to load for each hardware type.

[campus_switches]
sw-cisco-01 ansible_host=10.0.1.10 ansible_network_os=cisco.ios.ios
sw-juniper-01 ansible_host=10.0.1.20 ansible_network_os=junipernetworks.junos.junos
sw-mikrotik-01 ansible_host=10.0.1.30 ansible_network_os=community.general.routeros

2. Abstracting Configurations with Jinja2

We avoid vendor-specific playbooks by using Jinja2 templates. We create one YAML data structure and map it to the specific syntax of each vendor. For example, consider a standard system banner.

First, we define the variables in group_vars/all.yml:

system_banner: "Authorized Access Only. All activities are logged."
snmp_community: "itfromzero_readonly"

Then, we create specific templates. Here is the Cisco version (templates/cisco_ios.j2):

banner motd ^
{{ system_banner }}
^

And the Juniper version (templates/juniper_junos.j2):

set system login message "{{ system_banner }}"

3. The Playbook: Where Everything Connects

The playbook uses specialized config modules to push the rendered templates. These modules are idempotent. They check the current state and only send commands if the device configuration doesn’t match your template.

- name: Deploy Multi-Vendor System Config
  hosts: campus_switches
  gather_facts: false
  tasks:
    - name: Push Cisco Configuration
      cisco.ios.ios_config:
        src: templates/cisco_ios.j2
      when: ansible_network_os == 'cisco.ios.ios'

    - name: Push Juniper Configuration
      junipernetworks.junos.junos_config:
        load: merge
        src: templates/juniper_junos.j2
      when: ansible_network_os == 'junipernetworks.junos.junos'

    - name: Push Mikrotik Configuration
      community.general.routeros_command:
        commands:
          - /system identity set name={{ inventory_hostname }}
          - /snmp community set [find default=yes] name={{ snmp_community }}
      when: ansible_network_os == 'community.general.routeros'

Hard-Won Lessons from the Field

Moving to this model wasn’t perfectly smooth. If you are starting this journey, keep these four points in mind:

  • Trust but verify with check_mode: Always run playbooks with --check first. This performs a dry run, showing you the exact diff of what would change without touching the production traffic.
  • Fact gathering is a performance killer: Network devices are slow. Disable gather_facts by default unless you specifically need hardware serial numbers. This single change reduced our execution time by nearly 40%.
  • Lock down your secrets: Never store SSH passwords or SNMP strings in plain text. Use ansible-vault to encrypt sensitive variables. It takes five minutes to set up and prevents major security leaks.
  • Standardize your naming conventions: Automation works best when interface names follow a strict pattern. If one switch uses GigabitEthernet0/1 and another uses ge-0/0/0, your Jinja2 logic will quickly become a mess of nested if-statements.

The Results: Infrastructure as Code

The biggest transformation wasn’t the software—it was our mindset. We no longer “log into switches.” Instead, we modify a YAML file, submit a Git Pull Request, and let a CI/CD pipeline trigger the Ansible playbook. This creates a perfect audit trail of every change.

By leveraging Ansible’s specialized modules, we slashed our deployment time for new branch offices from two days to just twenty minutes. More importantly, we eliminated the human error responsible for 90% of our previous downtime. If you are still managing multi-vendor environments manually, you are essentially waiting for a disaster. Start small by automating something simple, like your DNS settings. The benefits will be obvious by the end of the week.

Share: