· ansible cisco

Starting out with Ansible, Cisco and Network Automation

Recently I’ve been spending more time exploring network automation, using homegrown scripts, Cisco’s APIC-EM, and Ansible. I like Ansible, and since release 2.1, they’ve started including dedicated networking modules, which is great. So, let’s get it set up and doing our work for us, yeah?

Ansible requires Python 2, and doesn’t support Python 3. It also doesn’t work with Windows as the control machine. So I’m going to install Ansible on an Ubuntu 14.04 virtual machine, though you could do the same on the more recent Ubuntu 16.04 as well.

sudo apt-get install software-properties-common
sudo apt-add-repository ppa:ansible/ansible
sudo apt-get -y update
sudo apt-get -y install ansible

If that all goes well, typing in ansible --version should show the following output:

ansible 2.1.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides

The first thing we need to do is specify our target devices that we want to connect to. This is done using an inventory file. In my lab I have 3 Cisco 2921 routers, each with a minimal configuration, enough to ssh to. So, hostname, username/secret, ip domain name, and ssh crypto. These are hooked up to a Cisco switch with no config on, and then my laptop is connected by ethernet, with a static ip address on the same 192.168.0.0/24 network, so my Ansible VM will just get NAT’d through this address. Pretty basic, but enough for these purposes.

routers

So, in a directory created for this Ansible project, I’ve created a file called hosts, without a file extension. As far as I understand, this file can be called anything, and can have a file extension if it makes your life easier, but convention has it named hosts, or sometimes inventory, and the contents have to be in an INI format. This is where we’re going to list our hosts. My hosts file contains my 3 2921 routers and looks like so:

[routers]
router-one ansible_host=192.168.0.1
router-two ansible_host=192.168.0.2
router-three ansible_host=192.168.0.3

[routers:vars]
ansible_user=vagrant
ansible_password=vagrant

Here, I have a group called routers that lists each host’s name, followed by the ansible_host variable, which is the ip address of the host. Below that are listed a couple of variables for every host in the routers group, under the heading [routers:vars]; ansible_user is the username on the host, and ansible_password is the password on the device, these correspond with this configuration on each router:

username vagrant privilege 15 secret 5 $1$lGlV$sF4MKyPYZlteNVgGHo9tL1

We can now use the ansible command line client to interact with our routers. Most Ansible guides deal with servers, and start off by using the Ansible ping module. We can’t do that here. Like most Ansible modules, the ping module works by bundling up some Python code, which it delivers to the device via SSH and executes. We can’t run Python code on our Cisco routers, so we’ll take a different approach to get started.

The raw module ‘executes a low-down and dirty SSH command’, perfect.

So, let’s run the following comand:

ansible all -m raw -a "executable='' sh run | inc hostname" -i hosts

And we get the following output:

router-one | SUCCESS | rc=0 >>
hostname router-one
Connection to 192.168.0.1 closed by remote host.


router-two | SUCCESS | rc=0 >>
hostname router-two
Connection to 192.168.0.2 closed by remote host.


router-three | SUCCESS | rc=0 >>
hostname router-three
Connection to 192.168.0.3 closed by remote host.

Success, excellent! We get back the hostname of each router, in a second or so. You may have noticed the command sent to the module includes executable='', this is because the raw module is expecting to encounter bash on a server, rather than the IOS command line. If we run it without this we get the following error:

router-three | SUCCESS | rc=0 >>
Line has invalid autocommand "/bin/sh -c 'sh run | inc hostname && sleep 0'"Connection to 192.168.0.3 closed by remote host.


router-one | SUCCESS | rc=0 >>
Line has invalid autocommand "/bin/sh -c 'sh run | inc hostname && sleep 0'"

router-two | SUCCESS | rc=0 >>
Line has invalid autocommand "/bin/sh -c 'sh run | inc hostname && sleep 0'"Connection to 192.168.0.2 closed by remote host.

There was an issue on the Ansible repo, so I’m not sure if this is something that will change in the future. Anyway, we won’t be using the raw module much, so it doesn’t really matter. This is a quick way to get facts off your devices, and you can send configuration changes this way if you really want. It’s always worth messing around on a lab router that doesn’t support critical company infrastructure, if you can, but Ansible has a better way to design and implement repeatable tasks, called Playbooks, which I’ll look at in another post.

Some further interesting sources of information:

  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket