Intro

This is part two of a series on Anisble for network engineers. In this part of the series we will take the playbook from part one and convert it into roles. Roles in Ansible allow for easy re-use and sharing of code and it is important to understand them as they will use role based playbooks for the rest of the series.

Role Directory Structure

To get setup for using roles we will need to create a few directories and files under the ~/ansible directory. Once this step is completed we will have the following directory structure.

cmd
# tree ~/ansible
ansible
├── ansible.cfg
├── configure-devices.yml
├── group_vars
│   └── network
├── host_vars
│   ├── lab-leaf-01
│   ├── lab-leaf-02
│   ├── lab-spine-01
│   └── lab-spine-02
├── inventory
└── roles
    ├── base
    ├── interfaces
    └── ospf

Directories

First off lets create the group_vars and roles directories under the ~/ansible directory. In the next step we will create the basic , intefaces , and ospf directories under the roles directory.

Roles

When creating a role there is a nice command that helps build out the directory structure.

ansible init <role-name> --offline will create a role with the following directories and files based on the recommended best practices.

cmd
<role-name>
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

Now use the ansible init <role-name> --offline command to create three roles.

cmd
ansible-galaxy init roles/base --offline
ansible-galaxy init roles/interfaces --offline
ansible-galaxy init roles/ospf --offline

Configuration

Now that the role directories are created we need to tell Ansible where to find them. Add roles_path=roles under the [defaults] section.

file
# ~/ansible/ansible.cfg
[defaults]
hostfile=inventory
host_key_checking=False
retry_files_enabled=False
ask_pass=True
gather_facts=False
roles_path=roles

[privilege_escalation]
become=True
become_method=sudo
become_ask_pass=True

Group Variables

The group_vars directory is where you define variables specific to groups of managed nodes in the inventory file. Create a variable file called network in the group_vars directory and move the network group variables from the inventory file to the network file.

ansible_user variable moved to group_vars/network .

file
# ~/ansible/group_vars/network
---
ansible_user: cumulus

The inventory file should now look like this.

file
# ~/ansible/inventory
[server]
lab-centos-01

[spine]
lab-spine-01
lab-spine-02

[leaf]
lab-leaf-01
lab-leaf-02

[network:children]
spine
leaf

Template

Next move the ~/ansible/templates/quagga.conf.j2 file to the ~/ansible/roles/ospf/templates/ directory and delete the ~/ansible/templates/ directory.

Plays

After that move the plays from the playbook into their respective roles paying attention to the indentation.

Plays 1 - 3

file
# ~/ansible/roles/base/tasks/main.yml
---
- name: Configure hostname in hosts file
  lineinfile: dest=/etc/hosts
              regexp='^127\.0\.1\.1'
              line='127.0.1.1 lab-spine-01'
              state=present

- name: Configure hostname
  hostname: name="{{ inventory_hostname }}"

- name: Configure DNS
  lineinfile: dest=/etc/resolv.conf
              regexp='^server'
              line='server 10.200.0.100'
              state=present

Plays 4 - 5

file
# ~/ansible/roles/interface/tasks/main.yml
---
- name: Configure front panel ports
  cl_interface: name="{{ item.int_name }}"
                ipv4="{{ item.ip_address }}"
                alias_name="{{ item.description }}"
  with_items:
    "{{ interfaces }}"

- name: Add network interfaces to /etc/network/interfaces
  blockinfile:
    dest: /etc/network/interfaces
    block: |

      auto {{ item.int_name }}
      iface {{ item.int_name }}
        address {{ item.ip_address }}

    insertafter: "^# ansible managed"
    marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.int_name }}"
  with_items:
    "{{ interfaces }}"

  notify: reload_networking

Plays 6 - 8

file
# ~/ansible/roles/ospf/tasks/main.yml
---
- name: Enable zebra and ospf
  lineinfile: dest=/etc/quagga/daemons
              regexp="{{ item.target }}"
              line="{{ item.result }}"
              state=present
  with_items:
    - { target: '^zebra=', result: zebra=yes }
    - { target: '^ospfd=', result: ospfd=yes }

- name: Ensure quagga is started on boot
  systemd: name=quagga
  enabled=true
  state=started
  notify: restart_quagga

- name: Configure OSPF
  template: src=../templates/quagga.conf.j2
  dest=/etc/quagga/Quagga.conf
  notify: restart_quagga

Handlers

Next move the handlers from the playbook to their respective roles again paying attention to the indentation.

reload_networking handler

file
# ~/ansible/roles/interfaces/handlers/main.yml
---
- name: reload_networking
  systemd: name=networking
           state=restarted

restart_quagga handler

file
# ~/ansible/roles/ospf/handlers/main.yml
---
- name: restart_quagga
  systemd: name=quagga
           state=restarted

Playbook

Finally edit the configure-devices.yml playbook as follows.

file
# ~/ansible/configure-devices.yml
---
- name: Configure devices
  hosts: network
  roles:
    - base
    - interfaces
    - ospf

Testing

Now lets execute the playbook and make sure the environment builds as expected.

cmd
ansible-playbook configure-devices.yml
SSH password:
SUDO password[defaults to SSH password]:

# output
PLAY [Configure devices] *******************************************************

TASK [setup] *******************************************************************
changed: [lab-leaf-01]
changed: [lab-leaf-02]
changed: [lab-spine-01]
changed: [lab-spine-02]

TASK [base : Configure hostname in hosts file] *********************************
changed: [lab-leaf-02]
changed: [lab-spine-01]
changed: [lab-spine-02]
changed: [lab-leaf-01]

TASK [base : Configure hostname] ***********************************************
changed: [lab-leaf-02]
changed: [lab-leaf-01]
changed: [lab-spine-02]
changed: [lab-spine-01]

TASK [base : Configure DNS] ****************************************************
changed: [lab-spine-01]
changed: [lab-spine-02]
changed: [lab-leaf-02]
changed: [lab-leaf-01]

TASK [interfaces : Configure front panel ports] ********************************
changed: [lab-spine-02] => (item={u'int_name': u'swp1', u'ip_address': u'10.100.0.1/31', u'description': u'to lab-spine-01 swp1'})
changed: [lab-spine-01] => (item={u'int_name': u'swp1', u'ip_address': u'10.100.0.0/31', u'description': u'to lab-spine-02 swp1'})
changed: [lab-leaf-02] => (item={u'int_name': u'swp1', u'ip_address': u'10.100.0.5/31', u'description': u'to lab-spine-01 swp2'})
changed: [lab-leaf-01] => (item={u'int_name': u'swp1', u'ip_address': u'10.100.0.3/31', u'description': u'to lab-spine-01 swp1'})
changed: [lab-leaf-02] => (item={u'int_name': u'swp2', u'ip_address': u'10.100.0.9/31', u'description': u'to lab-spine-02 swp2'})
changed: [lab-spine-01] => (item={u'int_name': u'swp2', u'ip_address': u'10.100.0.2/31', u'description': u'to lab-leaf-01 swp1'})
changed: [lab-leaf-01] => (item={u'int_name': u'swp2', u'ip_address': u'10.100.0.7/31', u'description': u'to lab-spine-02 swp1'})
changed: [lab-spine-02] => (item={u'int_name': u'swp2', u'ip_address': u'10.100.0.6/31', u'description': u'to lab-leaf-01 swp2'})
changed: [lab-spine-02] => (item={u'int_name': u'swp3', u'ip_address': u'10.100.0.8/31', u'description': u'to lab-leaf-02 swp2'})
changed: [lab-spine-01] => (item={u'int_name': u'swp3', u'ip_address': u'10.100.0.4/31', u'description': u'to lab-leaf-02 swp1'})

TASK [interfaces : Add network interfaces to /etc/network/interfaces] **********
changed: [lab-spine-01] => (item={u'int_name': u'swp1', u'ip_address': u'10.100.0.0/31', u'description': u'to lab-spine-02 swp1'})
changed: [lab-leaf-01] => (item={u'int_name': u'swp1', u'ip_address': u'10.100.0.3/31', u'description': u'to lab-spine-01 swp1'})
changed: [lab-spine-02] => (item={u'int_name': u'swp1', u'ip_address': u'10.100.0.1/31', u'description': u'to lab-spine-01 swp1'})
changed: [lab-leaf-02] => (item={u'int_name': u'swp1', u'ip_address': u'10.100.0.5/31', u'description': u'to lab-spine-01 swp2'})
changed: [lab-leaf-01] => (item={u'int_name': u'swp2', u'ip_address': u'10.100.0.7/31', u'description': u'to lab-spine-02 swp1'})
changed: [lab-spine-01] => (item={u'int_name': u'swp2', u'ip_address': u'10.100.0.2/31', u'description': u'to lab-leaf-01 swp1'})
changed: [lab-leaf-02] => (item={u'int_name': u'swp2', u'ip_address': u'10.100.0.9/31', u'description': u'to lab-spine-02 swp2'})
changed: [lab-spine-02] => (item={u'int_name': u'swp2', u'ip_address': u'10.100.0.6/31', u'description': u'to lab-leaf-01 swp2'})
changed: [lab-spine-01] => (item={u'int_name': u'swp3', u'ip_address': u'10.100.0.4/31', u'description': u'to lab-leaf-02 swp1'})
changed: [lab-spine-02] => (item={u'int_name': u'swp3', u'ip_address': u'10.100.0.8/31', u'description': u'to lab-leaf-02 swp2'})

TASK [ospf : Enable zebra and ospf] ********************************************
changed: [lab-leaf-01] => (item={u'target': u'^zebra=', u'result': u'zebra=yes'})
changed: [lab-spine-02] => (item={u'target': u'^zebra=', u'result': u'zebra=yes'})
changed: [lab-spine-01] => (item={u'target': u'^zebra=', u'result': u'zebra=yes'})
changed: [lab-leaf-02] => (item={u'target': u'^zebra=', u'result': u'zebra=yes'})
changed: [lab-leaf-01] => (item={u'target': u'^ospfd=', u'result': u'ospfd=yes'})
changed: [lab-spine-02] => (item={u'target': u'^ospfd=', u'result': u'ospfd=yes'})
changed: [lab-leaf-02] => (item={u'target': u'^ospfd=', u'result': u'ospfd=yes'})
changed: [lab-spine-01] => (item={u'target': u'^ospfd=', u'result': u'ospfd=yes'})

TASK [ospf : Ensure quagga is started on boot] *********************************
changed: [lab-leaf-02]
changed: [lab-leaf-01]
changed: [lab-spine-01]
changed: [lab-spine-02]

TASK [ospf : Configure OSPF] ***************************************************
changed: [lab-leaf-02]
changed: [lab-spine-02]
changed: [lab-leaf-01]
changed: [lab-spine-01]

PLAY RECAP *********************************************************************
lab-leaf-01                : ok=10    changed=10    unreachable=0    failed=0
lab-leaf-02                : ok=10    changed=10    unreachable=0    failed=0
lab-spine-01               : ok=10    changed=10    unreachable=0    failed=0
lab-spine-02               : ok=10    changed=10    unreachable=0    failed=0
Note
The task header now has an element showing the role the task belongs to: TASK [base : Configure hostname in hosts file]

Feel free to also run the ansible ad-hoc test commands from part one.

Outro

There you have it, the playbook from part one is now converted to a role based structure that can be reused within many playbooks and/or shared on Ansible Galaxy. In part three of the series we will move on to configuring Juniper network devices.