Using Ansbile for Junos Configuration Automation


Managing networking equipment can be difficult. Many organizations have hundreds or even thousands of routers, switches, and firewalls deployed, which can be quite a hassle to manage.

In the past network engineers programmed devices one by one – maybe using manually curated templates to create some level of consistency. But with this model, mistakes are almost inevitable, causing some devices to have slightly different configurations than others.

Modern organizations use configuration management and automation tools such as Ansible, Puppet, and Chef for centralizing their management of their Linux systems infrastructure – isn’t it time these technologies are applied to these organization’s underlying network infrastructure?

Ansible and a Juniper provided Python library, PyEZ are one possible method of bringing this type of automation to the networking world.

Ansible can be used to create Juniper configuration files via Jinja2 templates. Jinja2 is a templating language, which allows the user to create templates which can be populated dynamically with variables defined on a host or group basis.

Using templates, you can ensure all of your equipment has the exact same configuration. This can help reduce downtime due to incorrectly configured equipment, and allows you to know the state of all of your equipment.

These templates are written just like normal Junos code, but with the addition of logic statements (for, while, if, etc) and variables.

Here is a simple example of Jinja2 code, which replaces the system login banner configuration with another that includes the variable {{hostname}}.

system {
 login {
 replace: message "This switch is named {{hostname}}";

Once your templates have been populated, Ansible can call the Juniper PyEZ Library to push these configuration files via NETCONF, a protocol used to “install, manipulate, and delete the configuration of network devices.”

Essentially NETCONF allows a user to program a network device without creating a interactive ssh session.

One of my favorite things about Junos is its “load replace” command – which allows the user to replace parts of the configuration and allowing idempotence, meaning no matter how many times we “load replace” we always end up with the same configuration.

Using Ansible and PyEZ, networking equipment configurations can be automatically generated and deployed centrally for ease of management and ensure an entirely consistent environment!

Getting Started

Great! So how should we get started with Ansible and PyEZ? I would begin by reading the excellent Juniper documentation here.

I’ve created a test environment which you can clone to try it out on my github account here. Keep reading if you’d like to test it out!

First we need to create a Python virtual environment with the following command.

virtualenv --system-site-packages --python /usr/bin/python2.7 ~/venv/ansible

Clone the git repository here into the ~/venv folder, which will pull down my test environment.

Once you have your virtual environment set up, we’ll need to activate it with the following command. You should see the name of the virtual environment appear in front of your bash prompt once you have activated it.

source ~/venv/ansible/bin/activate

Next we need to install the dependencies, using the Python package manager, pip.

pip install -r ~/venv/ansible_junos/requirements.txt

Once we have the dependencies, we can run the actual Ansible playbook.

The example below is a simple playbook with two tasks.

The first task creates folders to store the generated configurations, and then calls a role (a sort of sub playbook) which generates the configuration from the Jinja2 template we saw above.

The second task takes the compiled Jinja2 code, and uses the PyEZ library to actually load the configuration on devices defined in the hostfile via NETCONF.

- name: Prepare Environment
 hosts: all
 connection: local
 gather_facts: no

 - name: Create dir for compiled configurations
 file: path=compiled/{{ inventory_hostname }}/ state=directory
 always_run: yes
 changed_when: False
 register: baseconfdir
 - base
 - name: Create empty diffs folder
 file: path=diffs state=directory
 always_run: yes
 changed_when: False
 - base

- name: push config
 hosts: all
 - Juniper.junos
 - banner
 connection: local
 gather_facts: no

 - name: Load the compiled configuration via "load replace"
 host={{ inventory_hostname }}
 user={{ user }}
 file=compiled/{{ inventory_hostname }}/{{ inventory_hostname }}.conf
 diffs_file=diffs/{{ inventory_hostname }}.diff

The command to run the playbook is inside of the Makefile. The Makefile allows us to store many scripts in one file, calling one or more from a simple command.

 rm -f ./diffs/*; rm -rf ./compiled/*; rm -rf ./logs/*

deploy_compare: clean
 ansible-playbook -i lab.hosts deploy.yml -e user=$(USER) --check

deploy: clean
 ansible-playbook -i lab.hosts deploy.yml -e user=$(USER)

To compile the Jinja2 code and deploy it to the device, we use the “deploy” line of the Makefile like this:

make deploy

The deploy line first calls “clean” which removes all diffs, logs, and compiled configs. Then it calls ansible-playbook to run the deploy.yml playbook shown above, specifying the inventory file and the username to use when logging into the devices.

You’ll need to modify the hostfile with the address of your device, and enable netconf on the device as well before you try this at home.

Thats it! I hope this helps you get started with Ansible and PyEZ!