Network Automation with Ansible for Absolute Beginners

This lab marks a significant step forward. Up until now every playbook has been running against Cisco devices only. In this session we introduce a Juniper vSRX into the topology and write a single playbook that configures NTP on both Cisco and Juniper devices simultaneously – using when statements to ensure each vendor receives the correct module and commands, and using Jinja2 templating with loop to iterate through a list of NTP servers rather than hardcoding them one at a time.

The lab also introduces best practice host file structure using separate group variable files and host variable files, which is how real production Ansible environments are managed.

The scenario is realistic. Your head of networks wants to explore multi-vendor interoperability before introducing Juniper into the core. Your task is to demonstrate what Ansible can do across vendors, introduce best practice variable file structure, and deliver a working multi-vendor NTP configuration and validation workflow in a single set of playbooks.

The lab works through three tasks.

The first task is restructuring the host file by removing all inline variables and moving them into dedicated group_vars and host_vars files. As your inventory grows, keeping variables directly in the hosts file becomes unmanageable quickly. The correct approach is to create a host_vars directory with individual YAML files named after each host, and a group_vars directory with files named after each group. Ansible automatically looks in these locations when running a playbook – it checks host_vars first for a match on the specific hostname, then falls back to group_vars if nothing is found, and only falls back to the hosts file itself as a last resort. This lookup order is important to understand and is covered in detail.

This restructuring also solves a problem that would have caused failures later in the lab. Cisco devices use ansible_connection: network_cli while Juniper devices use ansible_connection: netconf. If you set the connection type in the playbook itself you are forced to pick one or the other. By moving the connection type into separate variable files per group, each device type picks up its own correct connection method automatically and you can omit the connection line from the playbook entirely.

The second task is writing the NTP validation playbook before adding any NTP configuration. Running a show playbook first confirms the baseline state and means you have a tool ready to re-run after the configuration playbook to verify changes. The validation playbook runs show ntp config, show ntp associations and show ntp status against Cisco devices using the cisco.ios.ios_command module, and show ntp associations and show ntp status against the Juniper device using the junipernetworks.junos.junos_command module. Both sets of tasks sit inside the same playbook. The when statement – when: ansible_network_os == “cisco.ios.ios” for Cisco tasks and when: ansible_network_os == “junos” for Juniper tasks – ensures each task only executes against the devices it applies to and is skipped cleanly on the others. Output is registered to separate named variables, cisco_ntp and junos_ntp, and displayed using the debug module with stdout_lines.

The third task is writing the NTP configuration playbook using vendor-specific global modules rather than generic ios_config commands. Cisco provides the cisco.ios.ios_ntp_global module and Juniper provides the junipernetworks.junos.junos_ntp_global module. Both follow the same structure – a config block with a peers list – which makes the playbook consistent across vendors even though the underlying modules are different. Rather than hardcoding the NTP server addresses inside each vendor task, we declare them once as a playbook-level variable – ntp_servers – as a YAML list at the top of the playbook. Each vendor task then loops through that list using loop with item referenced inside a Jinja2 template, meaning you only need to update the server list in one place to change the NTP configuration across every device in your estate regardless of vendor. When statements ensure each module only runs against the correct device type.

We run the configuration playbook and then re-run the validation playbook to confirm NTP is now active on all devices. The Juniper device shows a stratum value confirming it has successfully reached the NTP server, and the Cisco devices show full NTP associations.

By the end of this lab you will understand how to structure group_vars and host_vars files correctly and why it matters, how to use when statements based on ansible_network_os to run vendor-specific tasks inside a single multi-vendor playbook, how to declare playbook-level variables as lists and loop through them using Jinja2 templating, how to use vendor-specific NTP global modules for Cisco and Juniper rather than raw config commands, and how to handle mixed connection types across vendors without breaking your playbooks.

All host files, variable files and playbooks from this lab are available on the GitHub repo lab5 linked in the course resources.

Lab 5: Ansible multi vendor Configuration for Cisco and Juniper | Loops and With Statements
Scroll to top