· ansible cisco network automation

Network Automation with Ansible Playbooks

Following on from my previous post, I want to take a look at playbooks, the mechanism Ansible uses for building and managing task automation. Playbooks are text files that declare the tasks you want carried out in a series of plays. This text file can then easily be re-used, shared, and version controlled.

Okay, so previously we had an inventory file that listed the hosts in our lab network.

[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

Let’s add our first playbook. Playbooks are written as yaml files, a format equivalent to json or xml but intended to be more human readable. Be warned, yaml is very particular about whitespace. If we create a new file alongside out playbook called get_inventory_info.yaml, we’ll use it to get the output from a show inventory command.

---
- name: Get Inventory Information
  hosts: routers
  tasks:
    - name: Send Show Inventory Command
      ios_command:
        host: "{{ ansible_host }}"
        username: "{{ ansible_user }}"
        password: "{{ ansible_password }}"
        commands:
          - show inventory
      register: inventory
    - debug: var=inventory

So, what’s going on here?

To run this we use the ansible-playbook command, along with the name of our playbook and the -i flag to specify our inventory.

ansible-playbook get_inventory_info.yaml -i inventory

OK, let’s go…

PLAY [Get Inventory Information] ***********************************************

TASK [setup] *******************************************************************
fatal: [router-one]: UNREACHABLE! => {"changed": false, "msg": "SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh", "unreachable": true}
fatal: [router-two]: UNREACHABLE! => {"changed": false, "msg": "SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh", "unreachable": true}
fatal: [router-three]: UNREACHABLE! => {"changed": false, "msg": "SSH Error: data could not be sent to the remote host. Make sure this host can be reached over ssh", "unreachable": true}

PLAY RECAP *********************************************************************
router-one                : ok=0    changed=0    unreachable=1    failed=0
router-two               : ok=0    changed=0    unreachable=1    failed=0
router-three             : ok=0    changed=0    unreachable=1    failed=0

Arggh, whaa? This is because we’re dealing with Cisco pets not server cattle. As I mentioned before, Ansible is trying to run some Python code on our hosts, which is not going to happen with Cisco IOS, we need to run the code on our local machine. So let’s add the following to our inventory file:

[routers:vars]
ansible_connection=local
ansible_user=vagrant
ansible_password=vagrant

That line there, ansible_connection=local, should take care of this problem for us.

PLAY [Get Inventory Information] ***********************************************

TASK [setup] *******************************************************************
ok: [router-one]
ok: [router-one]
ok: [router-two]

TASK [Send Show Inventory Command] *********************************************
ok: [router-one]
ok: [router-one]
ok: [router-two]

TASK [debug] *******************************************************************
ok: [router-two] => {
    "output": {
        "changed": false,
        "stdout": [
            "NAME: \"CISCO2921/K9\", DESCR: \"CISCO2921/K9 chassis, Hw Serial#: FCZ1545706V, Hw Revision: 1.0\"\nPID: CISCO2921/K9      , VID: V05 , SN: FCZ1545706V\n\nNAME: \"C2921/C2951 AC Power Supply\", DESCR: \"C2921/C2951 AC Power Supply\"\nPID: PWR-2921-51-AC    , VID: V02 , SN: DCA1536K3WX\n\n"
        ],
        "stdout_lines": [
            [
                "NAME: \"CISCO2921/K9\", DESCR: \"CISCO2921/K9 chassis, Hw Serial#: FCZ1545706V, Hw Revision: 1.0\"",
                "PID: CISCO2921/K9      , VID: V05 , SN: FCZ1545706V",
                "",
                "NAME: \"C2921/C2951 AC Power Supply\", DESCR: \"C2921/C2951 AC Power Supply\"",
                "PID: PWR-2921-51-AC    , VID: V02 , SN: DCA1536K3WX",
                "",
                ""
            ]
        ]
    }
}
ok: [router-two] => {
    "output": {
        "changed": false,
        "stdout": [
            "NAME: \"CISCO2921/K9\", DESCR: \"CISCO2921/K9 chassis, Hw Serial#: FCZ192570GZ, Hw Revision: 1.0\"\nPID: CISCO2921/K9      , VID: V08 , SN: FCZ192570GZ\n\nNAME: \"C2921/C2951 AC Power Supply\", DESCR: \"C2921/C2951 AC Power Supply\"\nPID: PWR-2921-51-AC    , VID: V03 , SN: QCS19060PYS\n\n"
        ],
        "stdout_lines": [
            [
                "NAME: \"CISCO2921/K9\", DESCR: \"CISCO2921/K9 chassis, Hw Serial#: FCZ192570GZ, Hw Revision: 1.0\"",
                "PID: CISCO2921/K9      , VID: V08 , SN: FCZ192570GZ",
                "",
                "NAME: \"C2921/C2951 AC Power Supply\", DESCR: \"C2921/C2951 AC Power Supply\"",
                "PID: PWR-2921-51-AC    , VID: V03 , SN: QCS19060PYS",
                "",
                ""
            ]
        ]
    }
}
ok: [router-three] => {
    "output": {
        "changed": false,
        "stdout": [
            "NAME: \"CISCO2921/K9\", DESCR: \"CISCO2921/K9 chassis, Hw Serial#: FCZ192560EG, Hw Revision: 1.0\"\nPID: CISCO2921/K9      , VID: V08 , SN: FCZ192560EG\n\nNAME: \"C2921/C2951 AC Power Supply\", DESCR: \"C2921/C2951 AC Power Supply\"\nPID: PWR-2921-51-AC    , VID: V03 , SN: QCS19060RV1\n\n"
        ],
        "stdout_lines": [
            [
                "NAME: \"CISCO2921/K9\", DESCR: \"CISCO2921/K9 chassis, Hw Serial#: FCZ192560EG, Hw Revision: 1.0\"",
                "PID: CISCO2921/K9      , VID: V08 , SN: FCZ192560EG",
                "",
                "NAME: \"C2921/C2951 AC Power Supply\", DESCR: \"C2921/C2951 AC Power Supply\"",
                "PID: PWR-2921-51-AC    , VID: V03 , SN: QCS19060RV1",
                "",
                ""
            ]
        ]
    }
}

PLAY RECAP *********************************************************************
router-one                : ok=3    changed=0    unreachable=0    failed=0
router-two                : ok=3    changed=0    unreachable=0    failed=0
router-three              : ok=3    changed=0    unreachable=0    failed=0

Yessss! Job’s a good ‘un, let’s go home. Nah, not yet. We can smooth things out a bit. At the beginning of the output you can see a [setup] task being run, that is an Ansible default that gathers facts from the hosts, like operating system, ip address etc. However, because we’re using ansible_connection=local this task is just gathering facts from the local machine, 3 times in this case! Totally useless. So we can add gather_facts: no to our playbook to avoid this time waster. We also don’t need all of that output so, as this is json we can be a bit more selective about it by using debug: var=inventory.stdout_lines[0]. Ansible has a provider argument which can be a dictionary object which defines the connection to the host, meaning we can separate this information from our playbooks, and not have to repeat it in every playbook. Our playbook now looks like this:

---
- name: Get Inventory Information
  gather_facts: no
  hosts: routers
  tasks:
    - name: Send Show Inventory Command
      ios_command:
        provider: "{{ provider }}"
        commands:
          - show inventory
      register: inventory
    - debug: var=inventory.stdout_lines[0]

To store group variables, such as provider, we can use a directory called group_vars. This directory name is pre-defined by Ansible. Within it we create a yaml file named after the group, routers. While we’re at it, if we’re creating a routers variable file, we can move the variables out of our inventory into it. So, in group_vars/routers.yaml we have…

---
ansible_connection: local
ansible_user: vagrant
ansible_password: vagrant
provider:
  host: "{{ ansible_host }}"
  username: "{{ ansible_user }}"
  password: "{{ ansible_password }}"

Really, we shouldn’t be writing our password down, should we? So let’s delete the ansible_password: vagrant line and use the command line --ask-pass flag instead.

Our inventory file now looks like this, just fyi:

[routers]
192.168.0.1
192.168.0.2
192.168.0.3

I’ve removed the hostnames, just because, it doesn’t really change anything, except the names in the output. If we run ansible-playbook get_inventory_info.yaml -i inventory --ask-pass we get a prompt for the password…

SSH password:

PLAY [Get Inventory Information] ***********************************************

TASK [Send Show Inventory Command] *********************************************
ok: [192.168.0.2]
ok: [192.168.0.3]
ok: [192.168.0.1]

TASK [debug] *******************************************************************
ok: [192.168.0.1] => {
    "inventory.stdout_lines[0]": [
        "NAME: \"CISCO2921/K9\", DESCR: \"CISCO2921/K9 chassis, Hw Serial#: FCZ1545706V, Hw Revision: 1.0\"",
        "PID: CISCO2921/K9      , VID: V05 , SN: FCZ1545706V",
        "",
        "NAME: \"C2921/C2951 AC Power Supply\", DESCR: \"C2921/C2951 AC Power Supply\"",
        "PID: PWR-2921-51-AC    , VID: V02 , SN: DCA1536K3WX",
        "",
        ""
    ]
}
ok: [192.168.0.2] => {
    "inventory.stdout_lines[0]": [
        "NAME: \"CISCO2921/K9\", DESCR: \"CISCO2921/K9 chassis, Hw Serial#: FCZ192570GZ, Hw Revision: 1.0\"",
        "PID: CISCO2921/K9      , VID: V08 , SN: FCZ192570GZ",
        "",
        "NAME: \"C2921/C2951 AC Power Supply\", DESCR: \"C2921/C2951 AC Power Supply\"",
        "PID: PWR-2921-51-AC    , VID: V03 , SN: QCS19060PYS",
        "",
        ""
    ]
}
ok: [192.168.0.3] => {
    "inventory.stdout_lines[0]": [
        "NAME: \"CISCO2921/K9\", DESCR: \"CISCO2921/K9 chassis, Hw Serial#: FCZ192560EG, Hw Revision: 1.0\"",
        "PID: CISCO2921/K9      , VID: V08 , SN: FCZ192560EG",
        "",
        "NAME: \"C2921/C2951 AC Power Supply\", DESCR: \"C2921/C2951 AC Power Supply\"",
        "PID: PWR-2921-51-AC    , VID: V03 , SN: QCS19060RV1",
        "",
        ""
    ]
}

PLAY RECAP *********************************************************************
192.168.0.1                : ok=2    changed=0    unreachable=0    failed=0
192.168.0.2                : ok=2    changed=0    unreachable=0    failed=0
192.168.0.3                : ok=2    changed=0    unreachable=0    failed=0

There, that’s a bit clearer. If we want we can add a few commands in to our playbook. Let’s change the name of it to get_device_information.yaml and change it to this:

---
- name: Get Device Information
  gather_facts: no
  hosts: routers
  tasks:
    - name: Send Show Commands
      ios_command:
        provider: "{{ provider }}"
        commands:
          - show inventory
          - sh run | inc hostname
          - sh ver | inc image
          - sh ip int brief
      register: info
    - debug: var=info.stdout_lines

Run it. ansible-playbook get_device_info.yaml -i inventory --ask-pass and a brief moment later…

SSH password:

PLAY [Get Device Information] **************************************************

TASK [Send Show Commands] ******************************************************
ok: [192.168.0.3]
ok: [192.168.0.1]
ok: [192.168.0.2]

TASK [debug] *******************************************************************
ok: [192.168.0.1] => {
    "info.stdout_lines": [
        [
            "NAME: \"CISCO2921/K9\", DESCR: \"CISCO2921/K9 chassis, Hw Serial#: FCZ1545706V, Hw Revision: 1.0\"",
            "PID: CISCO2921/K9      , VID: V05 , SN: FCZ1545706V",
            "",
            "NAME: \"C2921/C2951 AC Power Supply\", DESCR: \"C2921/C2951 AC Power Supply\"",
            "PID: PWR-2921-51-AC    , VID: V02 , SN: DCA1536K3WX",
            "",
            ""
        ],
        [
            "hostname router-one"
        ],
        [
            "System image file is \"flash0:c2900-IMAGE-ONE.SPA.154-3.M3.bin\""
        ],
        [
            "Interface                  IP-Address      OK? Method Status                Protocol",
            "Embedded-Service-Engine0/0 unassigned      YES NVRAM  administratively down down    ",
            "GigabitEthernet0/0         192.168.0.1     YES NVRAM  up                    up      ",
            "GigabitEthernet0/1         10.10.10.11     YES manual down                  down    ",
            "GigabitEthernet0/2         unassigned      YES NVRAM  administratively down down    "
        ]
    ]
}
ok: [192.168.0.3] => {
    "info.stdout_lines": [
        [
            "NAME: \"CISCO2921/K9\", DESCR: \"CISCO2921/K9 chassis, Hw Serial#: FCZ192560EG, Hw Revision: 1.0\"",
            "PID: CISCO2921/K9      , VID: V08 , SN: FCZ192560EG",
            "",
            "NAME: \"C2921/C2951 AC Power Supply\", DESCR: \"C2921/C2951 AC Power Supply\"",
            "PID: PWR-2921-51-AC    , VID: V03 , SN: QCS19060RV1",
            "",
            ""
        ],
        [
            "hostname router-three"
        ],
        [
            "System image file is \"flash0:c2900-IMAGE-ONE.SPA.154-3.M3.bin\""
        ],
        [
            "Interface                  IP-Address      OK? Method Status                Protocol",
            "Embedded-Service-Engine0/0 unassigned      YES NVRAM  administratively down down    ",
            "GigabitEthernet0/0         192.168.0.3     YES NVRAM  up                    up      ",
            "GigabitEthernet0/1         10.10.10.13     YES manual down                  down    ",
            "GigabitEthernet0/2         unassigned      YES NVRAM  administratively down down    "
        ]
    ]
}
ok: [192.168.0.2] => {
    "info.stdout_lines": [
        [
            "NAME: \"CISCO2921/K9\", DESCR: \"CISCO2921/K9 chassis, Hw Serial#: FCZ192570GZ, Hw Revision: 1.0\"",
            "PID: CISCO2921/K9      , VID: V08 , SN: FCZ192570GZ",
            "",
            "NAME: \"C2921/C2951 AC Power Supply\", DESCR: \"C2921/C2951 AC Power Supply\"",
            "PID: PWR-2921-51-AC    , VID: V03 , SN: QCS19060PYS",
            "",
            ""
        ],
        [
            "hostname router-two"
        ],
        [
            "System image file is \"flash0:c2900-IMAGE-ONE.SPA.154-3.M3.bin\""
        ],
        [
            "Interface                  IP-Address      OK? Method Status                Protocol",
            "Embedded-Service-Engine0/0 unassigned      YES NVRAM  administratively down down    ",
            "GigabitEthernet0/0         192.168.0.2     YES NVRAM  up                    up      ",
            "GigabitEthernet0/1         10.10.10.12     YES manual down                  down    ",
            "GigabitEthernet0/2         unassigned      YES NVRAM  administratively down down    "
        ]
    ]
}

PLAY RECAP *********************************************************************
192.168.0.1                : ok=2    changed=0    unreachable=0    failed=0
192.168.0.2                : ok=2    changed=0    unreachable=0    failed=0
192.168.0.3                : ok=2    changed=0    unreachable=0    failed=0

Lovely, now we can go home.

There’s more to do here, such as saving this output to file & sending configuration changes, but we can do that later.

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