Pages

2.13.2017

Current Trends in DC Networking - Ansible Basics

In the last post we covered the install and a quick introduction to Ansible. Today I will go over the basics of Ansible and how to build and run playbooks.

Ansible is very powerful and flexible. Configuration and usage gets deep quick. The best way I have learned to dig in, is to start simple and build from there. So that is what we will do today, build a basic playbook and run some commands on our remote switches.


Hosts file 


To manage devices Ansible must first know which devices to use. This is done with a hosts file. Think of it as an inventory of all devices you are working with in the network. The default location of the hosts file is /etc/ansible/hosts but it can be located anywhere, you just need to point Ansible to it when running playbooks. Best practice is to place the hosts file in the project directory.

The hosts file is pretty straight forward but has a number of options to organize your hosts. Here is the hosts file we will be using for our lab, located at  ~/dc-network/hosts

[eos]
arista-spine1 role=spine
arista-spine2 role=spine
arista-leaf1 role=leaf
arista-leaf2 role=leaf
arista-leaf3 role=leaf

[cumulus]
cumulus-spine1 role=spine
cumulus-spine1 role=spine
cumulus-leaf1 role=leaf
cumulus-leaf2 role=leaf
cumulus-leaf3 role=leaf

Note: Name resolution is being used in my lab so I can use host names instead of IP addresses. 

The hosts file is broken into two groups [eos] and [cumulus]. I have also assigned the variable role to each device to further organize into leafs and spines.

Hosts files can get complex depending on your needs and environment. To dig deeper check out the Ansible documentation or read through the /etc/ansible/hosts file for examples.

Playbooks


Now that we have our hosts file setup we can start working with our switches. Ansible uses playbooks to either pull information from devices or install and configure services on devices.

Playbooks are written in YAML and the two main components of a playbook are plays and task.

In general, plays organize what devices will be used in the playbook and task are the actions taken against these devices.

Let’s build our first playbook to help understand this better.

Create a new file called playbook.yml in the dc-network directory. Open the file and add the following:

---

- name: Play One - Inventory
  hosts: all
  gather_facts: no
  connection: local

--- is used to start a YAML file.

name: description of play

hosts: devices from the hosts file used in the play. Here we use all.
Other options include a single device (arista-spine1), group name from the hosts file (eos), and variables.

gather_facts: gather device information for use later in the playbook.

connection: how Ansible connects to a device. For most network gear we use local.

Lets add some tasks.

---

- name: Play One - Inventory
  hosts: all
  gather_facts: no
  connection: local

  tasks:

    - name: Inventory
      debug: var=inventory_hostname

    - name: Device Role
      debug: var=role

Under the play, add a tasks section and then start each task with -name. Also notice the indentation of two spaces.

name: is just like in the play. It’s a description of the task

debug: is used to output information. In the first task we are calling the built in variable inventory_hostname. This will print each device of the hosts file that is used in this play.

In the second play we are calling the variable role that we defined in our hosts file.


Let’s run the playbook and see what we get.

To run a playbook, we use the command ansible-playbook and specify the playbook we want to run. So we run ansible-playbook playbook.yml.

PLAY [Play One - Inventory] ****************************************************

TASK [Inventory] ***************************************************************
ok: [arista-spine1] => {
    "inventory_hostname": "arista-spine1"
}
ok: [arista-spine2] => {
    "inventory_hostname": "arista-spine2"
}
ok: [arista-leaf1] => {
    "inventory_hostname": "arista-leaf1"
}
ok: [arista-leaf2] => {
    "inventory_hostname": "arista-leaf2"
}
ok: [arista-leaf3] => {
    "inventory_hostname": "arista-leaf3"
}
ok: [cumulus-spine1] => {
    "inventory_hostname": "cumulus-spine1"
}
ok: [cumulus-leaf1] => {
    "inventory_hostname": "cumulus-leaf1"
}
ok: [cumulus-leaf2] => {
    "inventory_hostname": "cumulus-leaf2"
}
ok: [cumulus-leaf3] => {
    "inventory_hostname": "cumulus-leaf3"
}

TASK [Device Role] *************************************************************
ok: [arista-spine1] => {
    "role": "spine"
}
ok: [arista-leaf1] => {
    "role": "leaf"
}
ok: [arista-spine2] => {
    "role": "spine"
}
ok: [arista-leaf3] => {
    "role": "leaf"
}
ok: [arista-leaf2] => {
    "role": "leaf"
}
ok: [cumulus-spine1] => {
    "role": "spine"
}
ok: [cumulus-leaf1] => {
    "role": "leaf"
}
ok: [cumulus-leaf2] => {
    "role": "leaf"
}
ok: [cumulus-leaf3] => {
    "role": "leaf"
}

PLAY RECAP *********************************************************************
arista-leaf1               : ok=2    changed=0    unreachable=0    failed=0   
arista-leaf2               : ok=2    changed=0    unreachable=0    failed=0   
arista-leaf3               : ok=2    changed=0    unreachable=0    failed=0   
arista-spine1              : ok=2    changed=0    unreachable=0    failed=0   
arista-spine2              : ok=2    changed=0    unreachable=0    failed=0   
cumulus-leaf1              : ok=2    changed=0    unreachable=0    failed=0   
cumulus-leaf2              : ok=2    changed=0    unreachable=0    failed=0   
cumulus-leaf3              : ok=2    changed=0    unreachable=0    failed=0   
cumulus-spine1             : ok=2    changed=0    unreachable=0    failed=0

Alright lots of fun information. We can see the single play with two task ran. Each task then outputs (echos) the results. A play recap is shown at the end and both tasks show OK with nothing changed or failed. This is good!

We now have a basic playbook working, but its not connecting to any of our switches and doing anything of value. In order to connect to switches we must first understand modules.

Modules


Playbook tasks call modules to communicate with a device or software package. Examples would be different modules for Arista, Cisco and Juniper. There are also modules for server side install such as Nginx, Apache and much more. Ansible has a built in library of modules but you can also build your own or download community shared modules.

Github and Ansible Galaxy are great places to find community built modules. I also suggest checking out the official Ansible module page.

So lets see what we can do here. To keep this simple, we will poll the Arista spine1 switch for interface status using the ntc_show_command module

---

  - name: Arista
    hosts: arista-spine1
    gather_facts: no
    connection: local

    tasks:

      - name: Interface status
        ntc_show_command:
          connection=ssh
          command='show interfaces status'
          platform=arista_eos
          host={{ inventory_hostname }}
          username=admin
          password=admin
          template_dir='/home/that1guy15/dc-network/ntc-ansible/ntc-templates/templates'
        register: result

      - debug: var=result

Each module is different in the options it needs and syntax required. Ansible provides usage documentation for their accepted modules here. You can also use ansible-doc <module>  to see its usage. Similar to the man pages in Linux.

Running this playbook, we get the following output:

that1guy15@ubuntu-lab2:~/dc-network$ ansible-playbook playbook.yml 

PLAY [Arista] ******************************************************************

TASK [Interface status] ********************************************************
ok: [arista-spine1]

TASK [debug] *******************************************************************
ok: [arista-spine1] => {
    "result": {
        "changed": false, 
        "response": [
            {
                "duplex": "full", 
                "name": "Leaf-1", 
                "port": "Et1", 
                "speed": "unconf", 
                "status": "connected", 
                "type": "EbraTestPhyP", 
                "vlan": "routed"
            }, 
            {
                "duplex": "full", 
                "name": "Leaf-2", 
                "port": "Et2", 
                "speed": "unconf", 
                "status": "connected", 
                "type": "EbraTestPhyP", 
                "vlan": "routed"
            }, 
            {
                "duplex": "full", 
                "name": "Leaf-3", 
                "port": "Et3", 
                "speed": "unconf", 
                "status": "connected", 
                "type": "EbraTestPhyP", 
                "vlan": "routed"
            }, 
            {
                "duplex": "a-full", 
                "name": "", 
                "port": "Ma1", 
                "speed": "a-1G", 
                "status": "connected", 
                "type": "10/100/1000", 
                "vlan": "routed"
            }
        ], 
        "response_list": []
    }
}

PLAY RECAP *********************************************************************
arista-spine1              : ok=2    changed=0    unreachable=0    failed=0   

Here we are getting clean structured output of each interface instead of raw output like you will see with other network modules.

The output is not visually clean as you normally see from the CLI but its structured using JSON which means we can take this data and use it however we want in others areas of the playbook or applications.



Now that we have the basics of Ansible and working with playbooks, its time start building up these two labs! This is where we really start to see the power of Ansible.

Hopefully this helped you get more comfortable with Ansible. As always feedback is always welcome.