Blog |Follow Nick on Mastodon| About
 

Anisble documentation or examples for Cisco devices appear to be a bit hit-n-miss, so I'm documenting my "Hello World Primer" with hope it'll be helpful to others; as this is quite long it will be two posts, one for getting started with a simple "show clock" and a 2nd for making changes. Part two making changes is available here.

TLDR; Some example files are on github: https://github.com/linickx/ansible-cisco

To get started, you need ansible installed on your Laptop/PC, I'm running OSX with homebrew so installation is a simple brew install ansible.

Getting Started - A sane ansible.cfg

Once anisble is installed, the first thing you need an inventory/hosts's file... a lot of the linux examples will get you to start fiddling about with /etc/ansible, firstly this folder doesn't exist on OSX and secondly this is a bad idea on linux as this is a system wide change.

IMHO a better start is to create an ansible directory in your home: mkdir ~/ansible;cd ~/ansible
In your personal ansible directory, create ansible.cfg; in this file you can set a local inventory file and some sane defaults, mine looks like this:

linickx:ansible $ cat ansible.cfg
[defaults]
hostfile = ./inventory.txt
retry_files_enabled = False
timeout = 5
host_key_checking = False
#log_path= ./log.txt
linickx:ansible $

As you can see I have a few other thing set, firstly my local hosts file (inventory.txt) and secondly ansible fails it creates a local retry file, I don't want these. Timeout is ssh timeout, use whatever you like, something between 1 and 10 works for me.

host_key_checking has security implications. The above is set to False because this is my personal test machine, in Production I would set this to True as I want to know if an SSH MitM is happening.

Finally, notice the #, this is a comment; I have commented out the log_path directive, if you enable it, ansible will log all screen output to a file, useful for searching for something instead of scrolling through your terminal.

The Inventory File - Your personal list of devices

The inventory defines the devices you want to manage, anisble expects all devices to have resolvable FQDNs, which is unlikely in most enterprises for Cisco devices, if you're a fan of remembering IP addresses you can have a simple list of IP's but if you're like me an like to know the names of devices you can do something like this...

linickx:ansible $ cat inventory.txt

[all:vars]
# OSX / Homebrew hack, as I have python3 installed.
ansible_python_interpreter=/usr/local/Cellar/python/2.7.13/bin/python2.7

[ios_devices]
r1 ansible_host=10.10.10.135
r2 ansible_host=10.10.10.136

[ios_devices_enable]
r3 ansible_host=10.10.10.137

linickx:ansible $

In my inventory file, the [] are group names, I have two custom groups [ios_devices] and [ios_devices_enable], you can call these anything you want, my naming structure will make more sense as this post continues.
All devices belong to a default [all] group that doesn't need to be defeined in the file, but in my config file I have [all:vars] this means: set the following variables for the all group; I have python3 installed for my other python projects, so ansible_python_interpreter is a hack to point ansible to the correct version of python (Python 3 is not supported - yet - for ansible)
For each device listed in my file (r1,r2,r3, etc) I have ansible_host set to an IP address, that's because my test devices do not have resolvable FQDNs.

Hello World! ... or Show Clock play

Ansible is primarily about Play Books, a Play Book is a list of tasks (instructions) that you want to perform on one or many devices; the idea is that you create repeatable plays to standardise configuration and such in your environment.

For my Hello World! example, I'm not going to make a change but show you how to run show clock on many devices (using the ios_command module), in ansible world this is a play.
Start by creating a file called show_clock.yml with the following contents ( including the first --- ):

---
- hosts: ios_devices*
  gather_facts: no
  connection: local

  tasks:
  - name: Include Login Credentials
    include_vars: secrets.yml

  - name: Define Provider
    set_fact:
      provider:
        host: "{{ ansible_host }}"
        username: "{{ creds['username'] }}"
        password: "{{ creds['password'] }}"

  - name: RUN 'Show Clock'
    ios_command:
      provider: "{{ provider }}"
      commands:
        - show clock
    register: clock

  - debug: var=clock.stdout_lines

This playbook has three tasks: 1. Include Login Credentials, i.e. Read login credentials from a file 2. Define Provider, i.e. Use the credentials in something called a "fact" that is named "provider" 3. Run show clock

Read login credentials from a file

You'll need a credential file, so create secrets.yml with contents like this (update as appropriate):

---
creds:
  username: nick
  password: my_password
  auth_pass: my_enable

Something called a "fact" that is named "provider"

The provider is what tells ansible how to connect to a device, all networking equipment needs one, so the provider includes the ip address and credentials, a fact is a device specific variable, so this task is assigning connectivity details to the device. Anything inside {{ }} is a variable.

Run show clock ...

To execute the Play Book or play, you use the ansible-playbook command, like this...

linickx:ansible $ ansible-playbook show_clock.yml 

PLAY [ios_devices*] ************************************************************

TASK [Include Login Credentials] ***********************************************
ok: [r1]
ok: [r2]
ok: [r3]

TASK [Define Provider] *********************************************************
ok: [r2]
ok: [r1]
ok: [r3]

TASK [RUN 'Show Clock'] ********************************************************
ok: [r1]
ok: [r3]
ok: [r2]

TASK [debug] *******************************************************************
ok: [r1] => {
    "clock.stdout_lines": [
        [
            "18:58:24.812 UTC Mon Mar 27 2017"
        ]
    ]
}
ok: [r2] => {
    "clock.stdout_lines": [
        [
            "18:58:23.490 UTC Mon Mar 27 2017"
        ]
    ]
}
ok: [r3] => {
    "clock.stdout_lines": [
        [
            ".18:58:25.064 UTC Mon Mar 27 2017"
        ]
    ]
}

PLAY RECAP *********************************************************************
r1                         : ok=4    changed=0    unreachable=0    failed=0   
r2                         : ok=4    changed=0    unreachable=0    failed=0   
r3                         : ok=4    changed=0    unreachable=0    failed=0   

linickx:ansible $ 

What happened there then?!

In my ansible-playbook show_clock.yml you'll see the three tasks ran against three routers; in my show_clock.yml the first line hosts: ios_devices* says run against any wildcard group, starting with ios_devices, which in my inventory.txt is both ios_devices and ios_devices_enable.

Notice how there where four tasks not three, the -debug entry was an unnamed task which simply outputs the results of the previous task :)

But storing credentials in a file is INSECURE!!

Anyone that's been on linickx.com before will find that example of storing a username/password in a plain text file highly out of character, so lets look at how to prompt for the credentials instead; create show_clock_prompt.yml with the following:

---
- hosts: ios_devices
  gather_facts: no
  connection: local

  vars_prompt:
  - name: "mgmt_username"
    prompt: "Username"
    private: no
  - name: "mgmt_password"
    prompt: "Password"

  vars:
    provider:
      host: "{{ ansible_host }}"
      username: "{{ mgmt_username }}"
      password: "{{ mgmt_password }}"

  tasks:
  - name: RUN 'Show Clock'
    ios_command:
      provider: "{{ provider }}"
      commands:
        - show clock
    register: clock

  - debug: var=clock.stdout_lines

Notice a few changes, firstly no *, this play will be run just against one inventory group [ios_devices], secondly one-ish task: RUN 'Show Clock'. In this example, before running any tasks we prompt for some input and save the provider variables, once we have those we can then run the tasks. Run the play.. ansible-playbook show_clock_prompt.yml:

linickx:ansible $ ansible-playbook show_clock_prompt.yml
Username: nick
Password:

PLAY [ios_devices] *************************************************************

TASK [RUN 'Show Clock'] ********************************************************
ok: [r2]
ok: [r1]

TASK [debug] *******************************************************************
ok: [r1] => {
    "clock.stdout_lines": [
        [
            "19:12:37.863 UTC Mon Mar 27 2017"
        ]
    ]
}
ok: [r2] => {
    "clock.stdout_lines": [
        [
            "19:12:39.742 UTC Mon Mar 27 2017"
        ]
    ]
}

PLAY RECAP *********************************************************************
r1                         : ok=2    changed=0    unreachable=0    failed=0
r2                         : ok=2    changed=0    unreachable=0    failed=0

linickx:ansible $

This time, we're prompted for credentials and less tasks take place.

Download the files from github!

My ansible files are on github, feel free to download them: https://github.com/linickx/ansible-cisco

Once you're happy with this, check out Primer 2 making changes .

 

 
Nick Bettison ©