ACI and Ansible

ACI is made for automation. There are a lot of blog posts about automating ACI out there, but this is some documentation of my own progress on this matter.

I’m no complete beginner in the field of automation. When I stage an ACI environment for a customer I use several scripts that automate almost 95% of the process for me. However, these scripts are home grown and one of the issues I encounter is the lack of portability to other engineers (as I know the scripts by heart and know which things work and which do not). Though I’ve included a lot of documentation within the scripts themselves it remains a problem.

Another issue I encounter is the maintenance required. I don’t often have the time to verify all scripts when a new version of ACI is available, and since the deprecation of Python 2.7 in favor of python 3 there have been some issues.

So, I’m looking for a more standardized way of doing stuff. Something which is easier to explain to other engineers (and customers), and doesn’t require me to update/modify etc.

So, when you start looking at industry standards you quickly arrive at either Ansible or Terraform. Both appear very interesting. I chose Ansible for my first forays into these tools as it appears to have a bigger community. However, I do like the concept of Terraform, so it will remain on my to do list to look in to. Maybe I will choose Terraform over Ansible after I’ve tested them both.

For now, let’s look into Ansible.

Installation

I’ve chosen to install a dedicated virtual machine on my laptop for now to take my first steps into this wonderful world. This enables me to delete the VM when I need a clean slate. Installing Ansible on any machine that already has a different role would make this more difficult.

I chose to install a Debian VM as I love Debian. As far as I’ve been able to discern there’s no real benefit of one Linux distribution over another. This means that my (or your) personal preference is key.

I did find out that Debian stable does have Ansible in its repository. However, being Debian and all this is an older version (2.7.7), whereas the most current recommended version of Ansible is 2.9 (2.10 has been released a short while ago).

This means that I have to add a different repository.

As a prerequisite I installed some tools that could help:

apt-get install software-properties-common vim yamllint ansible-lint

The Ansible documentation provides the right steps to install Ansible: https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#installing-ansible-on-debian

Getting the ACI collection

Part of the installation is getting the ACI collection. These are the Ansible plugins that relate directly to ACI.

I performed these as a regular user instead of root as the collection will be installed by default in the active users' home directory.

ansible-galaxy collection install cisco.aci

This command requires Ansible 2.9 or newer, so if you installed Ansible directly from the Debian repository this command will likely fail.

Using Ansible to configure ACI

Ok, so now you have installed everything you need to use Ansible to configure ACI. We need to start using it.

Ansible gives you the possibility to define hosts in groups so you can perform the changes on multiple devices. When using Ansible to configure ACI this isn’t strictly necessary, but it can be useful. To do this we have to edit the Ansible hosts file. This can be found at /etc/ansible/hosts

You create a group by putting a name between square brackets. Below that you just enter the hostname or IP address of the device. For ACI this can be as simple as the following example:

[APIC]
192.168.12.11

You can specify more parameters, such as the user and the type of connection. This is done using ansible_user=username and ansible_connection=local. The local connection is required for ACI management as the scripts are executed locally on the Ansible host and not on the managed device.

That’s about it in regards to the configuration. All the rest can be done using playbooks. A playbook has a fixed structure, so keep that in mind.

Before you can create a playbook it is a good idea to know how to find help. What information do you need to supply for a playbook to work.

The right command for that is ansible-doc. At least the Cisco ACI Ansible collection comes with extensive documentation, but without the ansible-doc command it’s difficult to find. When you have installed the collection the right command should be as follows:

ansible-doc cisco.aci.<module-name>

An example would be:

ansible-doc cisco.aci.aci_interface_policy_lldp

This shows the documentation for the aci_interface_policy_lldp module. Personally I love the examples in the documentation. That’s sufficient for most of my use cases. I just copy the example and modify it to suit my needs.

Below is a working example of a playbook.

---
- name: ACI Link Level Management
  hosts: APIC
  connection: local
  gather_facts: no
  vars:
    username: ansible
    password: ansible123!

  tasks:
    - name: Create link level policies
      cisco.aci.aci_interface_policy_link_level:
        host: '{{ inventory_hostname }}'
        user: '{{ username }}'
        password: '{{ password }}'
        validate_certs: false
        link_level_policy: "{{ item.name }}"
        auto_negotiation: "{{ item.auto }}"
        speed: "{{ item.speed }}"
        state: present
      loop:
        - { name: 'Speed_100M', auto: 'off', speed: '100M' }
        - { name: 'Speed_1G', auto: 'off', speed: '1G' }
        - { name: 'Speed_10G', auto: 'off', speed: '10G' }
        - { name: 'Speed_100G', auto: 'off', speed: '100G' }
        - { name: 'Speed_25G', auto: 'off', speed: '25G' }
        - { name: 'Speed_40G', auto: 'off', speed: '40G' }
        - { name: 'Speed_Auto', auto: 'on', speed: 'inherit' }
...

Let’s zoom in to the specifics. All playbooks begin with --- and end with .... This just defines the begin and end of the playbook. Then we define a name and some details.

The line starting with hosts refers to the /etc/ansible/hosts file. The word refers to the group of hosts that I defined there. The connection is local and gather_facts is set to no. This is required for ACI as it does not support this.

Furthermore you can define some global variables. In this case I defined the username and password as global variables because I don’t want to edit them anytime I’d use this playbook for a different fabric. For more secure environments you can also authenticate based on SSH keyspairs. Which might be interesting for you.

After these global settings you have to create a Task. You don’t have to name these, but it is recommended as it will help you understand the progress of your playbook. Especially when you have long playbooks this can be very useful.

In the example I call the link level policy module from the cisco.aci collection. This module requires some input such as the hostname, username and password. Another thing you might want to add is the validate_certs item. By default the certificate will be validated, but ACI comes with a self signed certificate. Unless you installed a official certificate this will cause an error.

Then depending on the module you have to provide the configuration. In this example I created a loop to create multiple link level policies.

Running a playbook

Running a playbook is simple. Just run the command ansible-playbook <name of playbook>.

For example:

michael@Ansible-VM:~$ ansible-playbook LLP.yml

PLAY [ACI Link Level Management] ****************************************************

TASK [Create link level policies] ***************************************************
ok: [192.168.12.11] => (item={u'auto': u'off', u'speed': u'100M', u'name': u'Speed_100M'})
ok: [192.168.12.11] => (item={u'auto': u'off', u'speed': u'1G', u'name': u'Speed_1G'})
ok: [192.168.12.11] => (item={u'auto': u'off', u'speed': u'10G', u'name': u'Speed_10G'})
ok: [192.168.12.11] => (item={u'auto': u'off', u'speed': u'100G', u'name': u'Speed_100G'})
ok: [192.168.12.11] => (item={u'auto': u'off', u'speed': u'25G', u'name': u'Speed_25G'})
ok: [192.168.12.11] => (item={u'auto': u'off', u'speed': u'40G', u'name': u'Speed_40G'})
ok: [192.168.12.11] => (item={u'auto': u'on', u'speed': u'inherit', u'name': u'Speed_Auto'})
[WARNING]: Platform linux on host 192.168.12.11 is using the discovered Python
interpreter at /usr/bin/python, but future installation of another Python
interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html
for more information.

TASK [Create CDP Interface Policies] ************************************************
ok: [192.168.12.11] => (item={u'state': u'true', u'name': u'CDP_Enabled'})
ok: [192.168.12.11] => (item={u'state': u'false', u'name': u'CDP_Disabled'})

TASK [Remove CDP Interface Policy] **************************************************
ok: [192.168.12.11]

TASK [Create LLDP Interface Policies] ***********************************************
ok: [192.168.12.11] => (item={u'state': u'true', u'name': u'LLDP_Enabled'})
ok: [192.168.12.11] => (item={u'state': u'false', u'name': u'LLDP_Disabled'})

TASK [Remove LLDP Interface Policy] *************************************************
ok: [192.168.12.11]

TASK [Add port channel interface policies] ******************************************
ok: [192.168.12.11] => (item={u'name': u'LACP_Active', u'mode': u'active'})
ok: [192.168.12.11] => (item={u'name': u'LACP_Passive', u'mode': u'passive'})
ok: [192.168.12.11] => (item={u'name': u'MAC_Pinning', u'mode': u'mac-pin'})
ok: [192.168.12.11] => (item={u'name': u'Static_Channel', u'mode': u'off'})

PLAY RECAP **************************************************************************
192.168.12.11              : ok=6    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

The above is the result of a playbook which creates some default policies. Since I often stage new ACI fabrics this can save me a lot of time.

Final thoughts

Ansible will help me a lot when automating the deployment of ACI fabrics. I will be able to use default playbooks for all the standard stuff and modified playbooks for customer specific configuration.

Some care is required when assembling playbooks. The playbooks are run top down, and a task might fail if a required object hasn’t been created first. Say you want to create an EPG, but haven’t created the Application Profile yet, the deployment will fail. So you need to take care that the playbook is built in the right order when there are any dependencies. As far as I know Terraform is able to solve this issue, but I haven’t looked into Terraform yet.

Another point of attention is the availability of modules. There are a lot of modules already there, but it’s far from complete. It stands to reason that the tasks which are performed most often have modules available for them, and the stuff that should only be done once doesn’t. That’s fine for all those companies managing an ACI environment, but it doesn’t help me to save time and get known good results. Of course it is possible to build your own modules. The downside of this is that the maintenance of the code again finds its way in to my responsibilities. Not having the responsibility for maintaining the code is one of the biggest benefits (for me) of using a platform like Ansible.