Chapter 5. Using Ansible for Network Automation

This chapter focuses on using Ansible to automate network tasks. So far, all the examples you’ve seen in this book have had the same focus. This chapter, however, goes into detail about the most common activities network folks carry out.

This chapter shows how to interact with files by reading and writing. It also looks at interactions with several network components—virtual machines, routers, switches, and the cloud—as well as finally APIs.

This chapter finishes with real case studies that show how Ansible has been used to achieve tasks that would otherwise have been very difficult or time-consuming.

You can, of course, use other tools to accomplish the same things you can accomplish with Ansible, but as you will see, using Ansible can give you quick automation wins.

After you read this chapter, you should be able to automate most of your network use cases, with special focus on the ones mentioned in Chapter 1, “Types of Network Automation.”

Interacting with Files

At one point or another, you must either read from or write to a file. For example, with reporting or monitoring, you gather information and save it for future use. Another example of reading from files is comparing two text configurations when one of them was previously stored in a file. Doing these types of tasks with Ansible is simple, as you will see in the following sections.

Reading

In order to read a file, the first information you need to know is where the file resides. It can be located locally on the control node or remotely in another node. The method you use to read a file depends on the file’s location.

To retrieve local files, you can use the lookup plug-in, as shown in Example 5-1. This example shows a file (backup_configs.txt) from which you can retrieve the contents. From the playbook’s execution, you can see the contents of the file displayed.

Example 5-1 Using Ansible to Retrieve a Local File’s Contents

$ tree
.
├── backup_configs.txt
└── file.yml

$ cat backup_configs.txt
hostname POD4-DC-01
system mtu 9100%

$ cat file.yml
---
- hosts: all

  tasks:
    - debug: msg="the value is {{lookup('file', 'backup_configs.txt') }}"

$ ansible-playbook -i "localhost,” file.yml

PLAY [all]
*******************************************************************************

TASK [debug]
*******************************************************************************
ok: [localhost] => {
    "msg": "the value is hostname POD4-DC-01
system mtu 9100”
}

PLAY RECAP
*******************************************************************************
localhost               : ok=1    changed=0    unreachable=0    failed=0    skipped=0
rescued=0    ignored=0

Pretty simple, right? You just have to define the path to the file you want to read; in this case, it is in the same directory, so you simply specify the name. Notice in the output in Example 5-1 that new lines are specifies with .

The lookup plug-in does not allow you to retrieve files remotely. You should use slurp instead. Example 5-2 shows a playbook for this workflow. There is a file in a remote host (very_important_file.txt), and you can retrieve its contents from the local machine by using Ansible’s slurp module. Note that in this example, the IP address 52.57.45.235 is the remote host, and you would have to use your own remote host and provide its credentials on the inventory file.

Example 5-2 Using Ansible to Retrieve a Remote File’s Content

[email protected] $ cat very_important_file.txt
this is very important information

$ cat remote_file.yml
---
- hosts: all

  tasks:
    - name: Retrieve remote file
      slurp:
        src: ~/very_important_file.txt
      register: remote_file

    - debug: msg="{{ slurpfile['content'] | b64decode }}” #DECODE CONTENT

$ ansible-playbook -i inv.yml remote_file.yml
PLAY [all]
*******************************************************************************

TASK [Retrieve remote file]
***********************************************************************
ok: [52.57.45.235]

TASK [debug]
*******************************************************************************
ok: [52.57.45.235] => {
    "msg": "this is very important information
”
}

PLAY RECAP
*******************************************************************************
52.57.45.235               : ok=2    changed=0    unreachable=0    failed=0    skipped=0
rescued=0    ignored=0

As you can see, it is not very complicated to read a remote file. You must specify the remote machine as a host in your hosts file, and you specify the file path on the remote host just as you do for a local lookup. A caveat to keep in mind is that the returned content is in base-64 and must be decoded, as shown in Example 5-2. This is the only possible return value for the slurp Ansible module.

Writing

Now that you know how to read from files, you’re ready to learn what you do when you want to save information in one.

The easiest way to write to a file is by using the copy Ansible module without populating the source variable. You also use this module to copy files. However, you can use the content property to write information inside a file. In Example 5-3, the playbook writes all the gathered Ansible facts to file (facts.txt) in the remote host.

Example 5-3 Using Ansible to Write to a Remote File

$ cat create_file.yml
---
- hosts: all
  gather_facts: true

  tasks:
    - name: Creating a file with host information
      copy:
        dest: "facts.txt”
        content: |
         {{ ansible_facts }}

$ ansible-playbook create_file.yml -i "52.57.45.235,”

PLAY [all]
*************************************************************************************************************

TASK [Gathering Facts]
*************************************************************************************************
ok: [52.57.45.235]

TASK [Creating a file with host information]
***************************************************************************
changed: [52.57.45.235]

PLAY RECAP
****************************************************************************************
*********************
52.57.45.235                  : ok=2    changed=1    unreachable=0    failed=0
skipped=0    rescued=0    ignored=0

$ cat facts.txt
{"fibre_channel_wwn": [], "module_setup": true, "distribution_version": "2",
"distribution_file_variety": "Amazon", "env": {"LANG": "C", "TERM": "xterm-256color",
"SHELL": "/bi
n/bash", "XDG_RUNTIME_DIR": "/run/user/1000", "SHLVL": "2", "SSH_TTY": "/dev/pts/3",
"_": "/usr/bin/python",
####OUTPUT OMITTED####

You can see that you need to specify the destination file path and name—in this example, the default path (which you can omit) and the name facts.txt. Finally, we refer in the content field the ansible_facts variable. You can see the result of the execution in Example 5-3: Gathered facts are written to the named file in the remote host.

If you want to write to a file on the local machine with information from the remote host, you can register the output in a variable and use the delegate_to keyword on the copy task. This keyword indicate the host on which to execute the task.

In Example 5-4, Ansible runs gather_facts on the remote host and the task Creating a file with host information on the localhost.

Example 5-4 Using Ansible to Write to a Local File

$ cat create_local_file.yml
---
- hosts: all
  gather_facts: true

  tasks:
    - name: Creating a file with host information
      copy:
        dest: "facts.txt"
        content: |
         {{ ansible_facts }}
      delegate_to: localhost

$ ansible-playbook create__local_file.yml -i "52.57.45.235,”
####OUTPUT OMITTED####

$ cat facts.txt
{"fibre_channel_wwn": [], "module_setup": true, "distribution_version": "2",
"distribution_file_variety": "Amazon", "env": {"LANG": "C", "TERM": "xterm-256color",
"SHELL": "/bi
n/bash", "XDG_RUNTIME_DIR": "/run/user/1000", "SHLVL": "2", "SSH_TTY": "/dev/pts/3",
"_": "/usr/bin/python",
####OUTPUT OMITTED####

Although facts.txt has the same content as in Example 5-3, it resides on the local machine (that is, the one where the playbook was run instead of the managed node).

Interacting with Devices

If you are a network engineer, most of your time is probably spent interacting with devices such as routers, switches, firewalls, controllers, servers, and virtual machines. As you have seen in previous chapters, Ansible can connect to most device platforms. Depending on the connection’s target, you might need to use a different module. You can connect to these devices to make configuration changes or simply to gather output that may also influence the module used.

If you are not sure what use cases you can automate, refer to Chapter 1, which describes common device interaction use cases.

Networking (Routers, Switches, Firewalls)

As mentioned in Chapter 4,“Ansible Basics,” networking devices commonly do not support Python. Ansible interacts with devices differently, depending on whether they support Python, but it supports most devices. This section is divided by the method Ansible uses to interact with the devices; SSH, RESTCONF or NETCONF.

Using SSH

As part of your automation workflows, you need data, such as the status of your OSPF neighbors and the RADIUS server connection. Ansible automatically collects some information as part of its initial fact gathering. We can see this by executing a simple playbook against a Cisco Catalyst 9000 switch. You can use the variable ansible_facts, as highlighted in Example 5-5, playbook to print the automatically gathered data.

Example 5-5 Using an Ansible Playbook to Print Host Facts

$ cat gather_facts.yaml
---
- name: Ansible Playbook to gather information from IOS Devices
  hosts: ios
  gather_facts: true

  tasks:
    - name: print facts
      debug:
        msg: "{{ ansible_facts }}”

Example 5-6 shows an example of output from the playbook’s execution. You can see that a lot of information is captured here, although it is not all the information available from the switch. Keep in mind that this example omits some of the output and so does not show all the collected fields.

Example 5-6 Executing an Ansible Playbook to Print Host Facts

$ ansible-playbook gather_facts.yaml -i inventory.yml

PLAY [Ansible Playbook to gather information from IOS Devices] *********************

TASK [Gathering Facts] *********************************************************
ok: [switch_1]

TASK [print facts] *************************************************************
ok: [switch_1] => {
    "msg": {
        "discovered_interpreter_python": "/usr/local/bin/python",
        "net_all_ipv4_addresses": [
            "3.6.11.21",
            "192.168.8.54",
            "192.168.8.58",
            "192.168.8.65",
            "192.168.8.69",
            "192.168.8.73",
            "192.168.8.77",
            "192.168.0.4”
        ],
        "net_all_ipv6_addresses": [],
        "net_api": "cliconf",
        "net_filesystems": [
            "flash:"
        ],
        "net_filesystems_info": {
            "flash:": {
                "spacefree_kb": 6697044.0,
                "spacetotal_kb": 11087104.0
            }
        },
        "net_gather_network_resources": [],
        "net_gather_subset": [
            "interfaces",
            "default",
            "hardware"
        ],
        "net_hostname": "POD2-GB-01",
        "net_image": "flash:packages.conf",
        "net_interfaces": {
            "AppGigabitEthernet1/0/1": {
                "bandwidth": 1000000,
                "description": null,
                "duplex": null,
                "ipv4": [],
                "macaddress": "f87b.2053.b2a9",
                "mediatype": "App-hosting port",
                "mtu": 9100,
                "operstatus": "up",
                "type": "App-hosting Gigabit Ethernet”
            ####OMITTED OTHER INTERFACES####
        }
     ####OUTPUT OMITTED####
    }
}

PLAY RECAP *********************************************************************
switch_1                   : ok=2    changed=0    unreachable=0    failed=0    skipped=0
rescued=0    ignored=0

If you need some specific information that is not captured by ansible_facts, you can use the ios_command module and specify which command output you want to capture, as shown in Example 5-7. This module is intended for Cisco IOS devices, but there are modules for other vendors and other operating systems. You can also use this approach if you simply need a very small portion of the Ansible gathered facts and you do not want to process all that gathered information. In such a case, you would disable the facts gathering, as also shown in Example 5-7.


Note

Cisco modules are not natively available with Ansible installation. They must be installed manually. One option is for this installation is to use ansible-galaxy.


Example 5-7 Using an Ansible Playbook to Capture Output of a show Command

$ cat ntp.yaml
---
- name: Ansible Playbook to get the NTP status
  gather_facts: no
  hosts: ios

  tasks:
    - name: Get NTP status
      cisco.ios.ios_command:
        commands: show ntp status
      register: ntp_stat

    - name: print facts
      debug:
        msg: "{{ ntp_stat }}”

Example 5-8 shows an example of the execution of the playbook from Example 5-7. The variable stdout stores the contents of the command output.

Example 5-8 Executing the Ansible Playbook to Print NTP Status

$ ansible-playbook ntp.yaml -i inventory.yml

PLAY [Ansible Playbook to get the NTP status] **********************************

TASK [Get NTP status] **********************************************************
ok: [switch_1]

TASK [print facts] *************************************************************
ok: [switch_1] => {
    "msg": {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/local/bin/python"
        },
        "changed": false,
        "failed": false,
        "stdout": [
            "Clock is synchronized, stratum 3, reference is 3.6.0.254      
nominal
freq is 250.0000 Hz, actual freq is 249.9977 Hz, precision is 2**10
ntp uptime is
103916600 (1/100 of seconds), resolution is 4016
reference time is E391F022.326E9818
(17:23:46.197 UTC Sat Dec 26 2020)
clock offset is -1.3710 msec, root delay is 11.61
msec
root dispersion is 30.90 msec, peer dispersion is 1.09 msec
loopfilter state is
'CTRL' (Normal Controlled Loop), drift is 0.000009103 s/s
system poll interval is 1024,
last update was 1746 sec ago.”
        ],
        "warnings": []
    }
}

PLAY RECAP *********************************************************************
switch_1                   : ok=2    changed=0    unreachable=0    failed=0    skipped=0
rescued=0    ignored=0

After you get the content in a variable, in this case stdout, you could extract any specific needed part by using the parsing techniques described in Chapter 3, “Using Data from Your Network” (for example, regex).


Tip

When creating regex to capture a specific part of a text string, be mindful of trying it on all possibilities that the original source might output. Regex are sensitive, and failing to account for all possibilities happens often, especially for corner cases.


Now you know how to gather information from a device by using Ansible, but how do you configure one? You use the configuration module. To see how this is done, let’s look at the configuration of an interface in a Cisco Catalyst 9000. Example 5-9 uses the ios_config module to send two CLI commands (description and speed). Furthermore, it uses the parents keyword to instruct the module to insert these commands under the interface TenGigabitEthernet1/1/3.

Example 5-9 Ansible Playbook to Configure an IOS Interface by Using ios_config

---
- name: Ansible Playbook to configure an interface
  hosts: ios

  tasks:
    - name: configure interface
      cisco.ios.ios_config:
        lines:
        - description Ansible interface
        - speed 1000
        parents: interface TenGigabitEthernet1/1/3

Note

The ios_config module is intended for Cisco IOS devices, just as ios_command is. There are equivalent modules for other vendors and software. Ansible has modules for many networking vendors, including Cisco, Juniper, Arista, and F5.


Using the ios_config module is a generic way of achieving a configuration, and you must provide the CLI commands you want to execute. There are specific modules for some configurations, and you can configure specific features without knowing the CLI syntax required. The playbook in Example 5-10 achieves the same goal as the one in Example 5-9, configuring a description and a specific speed for the interface TenGigabitEthernet1/1/3; however, it uses a different module, the ios_interface module. With this module, you do not pass the CLI commands but simply pass variables to the Ansible module.

Example 5-10 Ansible Playbook to Configure an IOS Interface Using ios_interface

---
- name: Ansible Playbook to configure an interface
  hosts: ios

  tasks:
    - name: Replaces interface configuration
      cisco.ios.ios_interfaces:
        config:
        - name: TenGigabitEthernet1/1/3
          description: Ansible interface
          speed: 1000
        state: replaced

Note

If a module is available for your use case, it is typically a good idea to use it due to the abstraction it provides from the CLI syntax, which can be different for different software versions.


Using NETCONF

So far, all the Ansible modules covered in this chapter for configuring and retrieving data from network devices have used SSH. If you have already adopted or are in the process of adopting newer management protocols, such as NETCONF (refer to Chapter 2, “Data for Network Automation”), you can still use Ansible as your automation tool.

When you use NETCONF, there are the three modules to consider:

netconf_get

netconf_config

netconf_rpc

The netconf_get Ansible module allows you to retrieve data from NETCONF-enabled devices. It is worth noting that this module can display the results in multiple data formats (for example, JSON, XML). The playbook shown in Example 5-11 attempts to retrieve the running configuration of the interface GigabitEthernet1/0/13 and display it in JSON format. This example uses the same Cisco Catalyst 9000 device accessed with SSH earlier in this chapter.

Example 5-11 Ansible Playbook to Retrieve the Running Configuration Using NETCONF

$ cat nc_get_running.yaml
---
- hosts: all
  connection: netconf
  gather_facts: no

  tasks:
    - name: Get configuration and state data from the running datastore
      ansible.netcommon.netconf_get:
        source: running
        filter: <interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-
interfaces"><interface><name>GigabitEthernet1/0/13</name></interface></interfaces>
        display: json
      register: result

    - name: Print the running configuration
      ansible.builtin.debug:
        msg: "{{ result }}”

Example 5-12 shows an example execution of the playbook from Example 5-11. It is worth noting that the playbook is completely agnostic of the network device you are accessing, as long as it has a running datastore and the corresponding YANG module used in the filter.

Example 5-12 Executing the Ansible Playbook to Retrieve the Running Configuration Using NETCONF

$ ansible-playbook nc_get_running.yaml -i inventory.yml
PLAY [all]
**********************************************************************************

TASK [Get configuration and state data from the running datastore]
**********************************************************************************
ok: [10.78.54.124]

TASK [Print the running configuration]
**********************************************************************************
ok: [10.78.54.124] => {
    "msg": {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python"
        },
        "changed": false,
        "failed": false,
        "output": {
            "data": {
                "interfaces": {
                    "interface": {
                        "enabled": "true",
                        "ipv4": "",
                        "ipv6": "",
                        "name": "GigabitEthernet1/0/13",
                        "type": "ianaift:ethernetCsmacd”
                    }
                }
            }
        },
        "stdout": "",
        "stdout_lines": [],
        "warnings": []
    }
}

PLAY RECAP
**********************************************************************************
10.78.54.124               : ok=2    changed=0    unreachable=0    failed=0    skipped=0
rescued=0    ignored=0

The netconf_config Ansible module, as its name indicates, is used to make configuration changes. All you must do is provide the configuration in the content property, as shown in Example 5-13. This example sets the new IP address 10.3.0.1/24 and adds a new description for the interface GigabitEthernet1/0/13.

Example 5-13 Ansible Playbook to Configure an Interface Using NETCONF

---
- hosts: all
  connection: netconf
  gather_facts: no

  tasks:
    - name: configure interface IP and description
      ansible.netcommon.netconf_config:
        content: |
                  <config>
                     <interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
                        <interface>
                           <name>GigabitEthernet1/0/13</name>
                           <description>new description</description>
                           <ipv4 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip">
                              <address>
                                 <ip>10.3.0.1</ip>
                                 <netmask>255.255.255.0</netmask>
                              </address>
                           </ipv4>
                        </interface>
                     </interfaces>
                  </config>

Note

Keeping your YANG configurations outside your playbooks in separate files is recommended as it improves a playbook’s readability.


You may need to perform actions related to NETCONF that are neither configuration nor data retrieval actions. This is where the netconf_rpc Ansible module comes in. It is a generic module that is capable of calling different types of remote procedure calls (RPCs). The following are some examples of possible actions:

• Copying the running configuration to the startup configuration

• Retrieving schemas

• Locking/unlocking datastores

In the networking world, one the most common actions is saving a configuration in NVRAM. You can achieve this with the playbook shown in Example 5-14. This playbook is built for IOS XE, and a different device type might require a different xmlns value, which is the XML namespace (refer to Chapter 2).

Example 5-14 Ansible Playbook to Save the Running Configuration Using NETCONF

---
- hosts: all
  connection: netconf
  gather_facts: no

  tasks:
    - name: copy running to startup
      ansible.netcommon.netconf_rpc:
        xmlns: "http://cisco.com/yang/cisco-ia"
        rpc: save-config

By using this module, you can specify any type of RPC method, including get. However, you should use it only when your action is not defined in a more specific module, such as the previously mentioned netconf_config and netconf_get modules.

Using RESTCONF

Another next-generation management protocol introduced in Chapter 2 is RESTCONF. If this is the protocol you are using to manage your devices, you are in luck because it is also fully supported by Ansible modules.

There are two important RESTCONF modules to keep in mind, and their names are very similar to the names of the NETCONF modules:

restconf_get

restconf_config

You use restconf_get to obtain information from RESTCONF-enabled devices. You cannot specify any HTTP verb with this module; the only one supported is GET.

Example 5-15 shows a playbook that retrieves the configuration of the interface GigabitEthernet1/0/13. It does so by specifying the URI in the path variable, the output format in the output variable, and the type of data to retrieve in the content variable (which can take the value config, nonconfig, or all).


Note

For a refresher on URI encoding rules for RESTCONF, refer to Chapter 2.


Example 5-15 Ansible Playbook to Retrieve a Configuration Using RESTCONF

$ cat rc_get.yaml
---
- hosts: all
  connection: httpapi
  gather_facts: no

  vars:
    ansible_httpapi_port: 443
    ansible_httpapi_use_ssl: yes
    ansible_network_os: restconf
    ansible_httpapi_validate_certs: false

  tasks:
    - name: Get Interface Ge1/0/13 Configuration
      restconf_get:
        content: config
        output: json
        path: /data/ietf-interfaces:interfaces/interface=GigabitEthernet1%2F0%2F13
      register: cat9k_rest_config

    - debug: var=cat9k_rest_config

There are plenty of interesting things to notice in the playbook in Example 5-15. One of them is that the connection specified is not RESTCONF but httpapi, which tells Ansible to use HTTP. The combination of this connection type with the variable ansible_network_os being assigned the value restconf is what defines a RESTCONF connection for a host.

You have probably noticed three other variables related to the httpapi connection type. You can specify them according to your needs in your inventory file for each host. This is done directly in the playbook in Example 5-15 for ease of visualization only. These variables mandate the use of HTTPS using port 443 but do not validate the certificates in use as this example uses self-signed certificates. Example 5-16 shows the execution of this playbook on a single device, a Catalyst 9000. You can see that, by default, the module restconf_get does not return anything (note the task Get Interface Ge1/0/13 Configuration). If you want to see output, you must register the result to a variable and print it; as you have seen in previous examples, you can use the register and debug keywords for this.

Example 5-16 Executing the Ansible Playbook to Retrieve a Configuration Using RESTCONF

$ ansible-playbook rc_get.yml -i inventory.yml

PLAY [all] *********************************************************************

TASK [Get Interface Ge1/0/13 Configuration]
*********************************************************************
ok: [10.78.54.124]

TASK [debug] *********************************************************************
ok: [10.78.54.124] => {
    "cat9k_rest_config": {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python"
        },
        "changed": false,
        "failed": false,
        "response": {
            "ietf-interfaces:interface": {
                "enabled": true,
                "ietf-ip:ipv4": {},
                "ietf-ip:ipv6": {},
                "name": "GigabitEthernet1/0/13",
                "type": "iana-if-type:ethernetCsmacd”
            }
        },
        "warnings": []
    }
}

PLAY RECAP *********************************************************************
10.78.54.124               : ok=2    changed=0    unreachable=0    failed=0    skipped=0
rescued=0    ignored=0

Building on the previous examples, you can use the restconf_config module to configure, edit, or delete values on a RESTCONF-enabled device. This module is more versatile than the restconf_get module, as you can specify HTTP verbs such as the following:

• POST

• PUT

• PATCH

• DELETE


Note

Chapter 2 describes use cases for the HTTP verbs.


Example 5-17 shows how to configure the interface GigabitEthernet1/0/13 with the IP address 10.0.0.1/24 and a description. From the playbook’s execution, you can see the difference from Example 5-16, where the interface was empty. Because this example uses the HTTP verb PATCH, every nonspecified previously configured attribute remains unchanged.

Example 5-17 Ansible Playbook to Configure a Device Using RESTCONF

$ cat rc_configure.yaml
---
- hosts: all
  connection: httpapi
  gather_facts: no

  vars:
    ansible_httpapi_port: 443
    ansible_httpapi_use_ssl: yes
    ansible_network_os: restconf
    ansible_httpapi_validate_certs: false

  tasks:
    - name: Configure IP address
      ansible.netcommon.restconf_config:
        path: /data/ietf-interfaces:interfaces/interface=GigabitEthernet1%2F0%2F13
        method: patch
        content: |
                  {
                  "ietf-interfaces:interface": {
                    "name": "GigabitEthernet1/0/13",
                    "ietf-ip:ipv4": {
                          "address": [
                              {
                                  "ip": "10.0.0.1",
                                  "netmask": "255.255.255.0"
                              }
                          ]
                      }
                    "description": "New description"
                    }
                  }

    - name: Get Interface Ge1/0/13 Configuration
      restconf_get:
        content: config
        output: json
        path: /data/ietf-interfaces:interfaces/interface=GigabitEthernet1%2F0%2F13
      register: cat9k_rest_config

    - debug: var=cat9k_rest_config

$ ansible-playbook rc_configure.yaml -i inventory.yml

PLAY [all] *********************************************************************

TASK [Configure IP address]
*********************************************************************
changed: [10.78.54.124]

TASK [Get Interface Ge1/0/13 Configuration]
*********************************************************************
ok: [10.78.54.124]

TASK [debug] *********************************************************************
ok: [10.78.54.124] => {
    "cat9k_rest_config": {
        "changed": false,
        "failed": false,
        "response": {
            "ietf-interfaces:interface": {
                "description": "New description",
                "enabled": true,
                "ietf-ip:ipv4": {
                    "address": [
                        {
                            "ip": "10.0.0.1",
                            "netmask": "255.255.255.0”
                        }
                    ]
                },
                "ietf-ip:ipv6": {},
                "name": "GigabitEthernet1/0/13",
                "type": "iana-if-type:ethernetCsmacd"
            }
        }
    }
}

PLAY RECAP *********************************************************************
10.78.54.124               : ok=3    changed=1    unreachable=0    failed=0    skipped=0
rescued=0    ignored=0

Tip

If you have doubts or difficulties creating content payloads, keep in mind that an easy is way is to configure what you need manually on a device and execute a GET operation. You can use the result to configure any other device.


Computing (Servers, Virtual Machines, and Containers)

Computing is a broad domain encompassing a big range of platforms, in this section we divide in two major sections: Servers and Virtual machines, and Containers.

For Servers and Virtual machines, we will cover actions executed directly on them (such as installing software), but also interactions with the platforms that support the virtualization, for example VMware vCenter. The same happens for containers, where we cover individual container technologies like Docker and container orchestration platforms like Kubernetes.

For each section we show you example playbooks to interact with these components and sample executions of those playbooks.

Servers and Virtual machines

Servers are part of networks, and although they are not always managed by the network team, there is always a server to connect to. Servers come in different flavors, and they use different syntax, which can be a management burden for someone who is not a seasoned systems administrator. Common server-related tasks include installing software, collecting outputs, and expanding storage space. Ansible can help you address servers in a scalable manner.

When working with servers, you can use modules for the package manager of your system (for example, yum, apt-get). Example 5-18 shows a playbook that installs the latest version of httpd by using the yum Ansible module.

Example 5-18 Ansible Playbook to Install httpd Using the yum Module

$ cat httpd.yml
---
- name: Install the latest version of Apache httpd
  yum:
    name: httpd
    state: latest

In the event that you need to do an operation that is not available in a module, you can use the command module and specify the command you need in its raw form. This is similar to the generic ios_config module you saw earlier in this chapter, but it is for servers instead of network devices.

The playbook in Example 5-19 does the same thing as the one in Example 5-18: It installs httpd. However, it uses the command module instead of the yum module. In a typical scenario, you would not use the command module because yum exists as an Ansible module, and it is the preferred option.

Example 5-19 Ansible Playbook to Install httpd Using the command Module

$ cat httpd.yml
---
- hosts: all

  tasks:
    - name: Install httpd using yum
      command: yum install httpd
      register: result

    - debug:
        msg: "{{ result }}”

Example 5-20 shows an example of the execution of the playbook in Example 5-19. Notice that the return message indicates that httpd is already installed. This is because the playbooks in Example 5-18 and Example 5-19 have already been run on the same host. You can see from this example the intelligence that is embedded in playbooks by default. If you ran the playbook in Example 5-19 and http had not been installed before, stdout would show the installation log.


Note

To execute the examples in this section, you need a Linux host defined in your inventory file.


Example 5-20 Executing the Ansible Playbook to Install httpd Using the command Module

$ ansible-playbook -i inv.yml httpd.yml

PLAY [all]
**********************************************************************************

TASK [Gathering Facts]
**********************************************************************************
ok: [10.10.10.1]

TASK [Install httpd using yum]
**********************************************************************************
changed: [10.10.10.1]

TASK [debug]
**********************************************************************************
ok: [10.10.10.1] => {
    "msg": {
        "changed": true,
        "cmd": [
            "yum",
            "install",
            "httpd"
        ],
        "delta": "0:00:00.720593",
        "end": "2020-12-27 09:33:49.812601",
        "failed": false,
        "rc": 0,
        "start": "2020-12-27 09:33:49.092008",
        "stderr": "",
        "stderr_lines": [],
        "stdout": "Loaded plugins: extras_suggestions, langpacks, priorities, update-
motd
Package httpd-2.4.46-1.amzn2.x86_64 already installed and latest version
Nothing
to do",
        "stdout_lines": [
            "Loaded plugins: extras_suggestions, langpacks, priorities, update-motd",
            "Package httpd-2.4.46-1.amzn2.x86_64 already installed and latest version",
            "Nothing to do”
        ],
        "warnings": []
    }
}

PLAY RECAP
*******************************************************************************
10.10.10.1               : ok=3    changed=1    unreachable=0    failed=0    skipped=0
rescued=0    ignored=0

So far, the playbooks in Examples 5-18 and 5-19 have shown you how to automate the installation of httpd, but you still need to know which type of operating system you are connecting to in order to issue the correct command for the corresponding package manager. This can be a management burden.

The playbook in Example 5-21 addresses this potential burden by collecting information about the operating system family (using ansible_facts) and uses this data to decide which package manager to use.


Note

Example 5-21 shows a useful application of the when statement, which is covered in the Chapter 4.


Example 5-21 Ansible Playbook to Install httpd Independently of the Operating System

---
- hosts: all
  gather_facts: true

  tasks:
    - name: Install the latest version of Apache on RedHat
      yum:
        name: httpd
        state: latest
      when: ansible_os_family == "RedHat”

    - name: Install the latest version of Apache on Debian
      apt:
        name: httpd
      when: ansible_os_family == "Debian”

A playbook like the one in Example 5-21 would be created once in a collaboration between you and the computing team. In the future, you would not have to worry again about different operating system syntaxes when executing changes.

Many of the facts that Ansible collects from computing systems are useful in common system administrator tasks, including the following examples:

• Mounted disk utilization

• Virtual memory available

• Network interface properties

You can use these metrics to derive actions, such as mounting another virtual disk or deleting files when you are running out of space. Example 5-22 shows an example of a playbook that achieves this.

Example 5-22 Using Ansible to Verify and Possibly Free Disk Space

---
- hosts: all
  gather_facts: true

  tasks:
    - name: Verify available disk space
      assert:
        that:
            - item[0].mount != '/' or {{ item[0].mount == '/' and item[0].size_available
> (item[0].size_total|float * 0.5) }}
      loop:
        - "{{ ansible_mounts }}"
      ignore_errors: yes
      register: disk_free

    - name: free disk space
      command: "rm -rf SOMEFILE"
      when: disk_free is failed

Let us go over what the playbook in Example 5-22 does. The first task iterates over the mounted volumes (gathered using Ansible facts) and asserts whether the size available in the root partition is over 50%. You must ignore errors on this task because you do not want the playbook to stop if the assertion is false. You want to register the result to a variable and free disk space on the second task if more than 50% is occupied.

So far, all the examples in this section, have involved connecting to operating systems directly. However, plenty of computing tasks revolve around infrastructure virtualization. Ansible is a great tool for automating these tasks.

Common tasks in VMware environments include the following:

• Creating/modifying virtual machines

• Creating/modifying port groups

• Configuring virtual/distributed switches

• Performing VMotion across hosts

• Creating templates and/or snapshots

Example 5-23 shows an Ansible playbook that creates a new virtual machine (VM) from a template. This template, template-ubuntu, would need to have been created previously. A VM template is a copy of a virtual machine that includes VM disks, virtual devices, and settings. Using one is a great way to replicate virtual machines; however, how to use VM templates is beyond the scope of this book.

Example 5-23 Ansible Playbook to Deploy a VM from a VM Template

---
- hosts: localhost
  gather_facts: no
  vars:
    vcenter_server: "10.1.1.1"
    vcenter_user: "[email protected]"
    vcenter_pass: "password"
    datacenter_name: "Datacenter"
    cluster_name: "Cluster01"

  tasks:
  - name: Clone the template
    vmware_guest:
      hostname: "{{ vcenter_server }}"
      username: "{{ vcenter_user }}"
      password: "{{ vcenter_pass }}"
      validate_certs: False
      name: web
      template: template-ubuntu
      datacenter: "{{ datacenter_name }}"
      folder: /{{ datacenter_name }}/vm
      cluster: "{{ cluster_name }}"
      datastore: "iscsi-datastore01"
      networks:
      - name: VM Network
        ip: 10.0.0.5
        netmask: 255.255.255.0
        gateway: 10.0.0.254
        type: static
        dns_servers: 10.0.0.1
      state: poweredon
      wait_for_ip_address: yes
    delegate_to: localhost

Example 5-23 uses the module vmware_guest to create the VM. Like the Cisco Ansible module, it must be installed manually. This module is paramount for the VMware environment because it can also reconfigure VMs, delete VMs, or trigger actions (for example, reboots, restarts, suspends). The parameter that defines what is to be executed is state. There are many possible states; Table 5-1 lists and describes them.

Table 5-1 vmware_guest State Choices

Images

This example defines the authentication credentials on the playbook itself for ease of understanding. However, in a production scenario, you would separate the credentials from the playbook (using something like Ansible Vault, which is covered in Chapter 4). The playbook in Example 5-23 specifies the virtual machine name (web), along with the data center (Datacenter), the folder within the data center, the cluster (Cluster01), the datastore (iscsi-datastore01), and some guest network information. Notice that this module runs locally on the control node.

After the VM is created, you can continue automating by connecting to the VM using the defined IP address and provisioning it using another task in the same playbook. Or you can trigger a snapshot, as shown in Example 5-24.

Example 5-24 Ansible Playbook to Trigger a VM Snapshot and Perform VMotion on It

---
- hosts: localhost
  gather_facts: no
  vars:
    vcenter_server: "10.1.1.1"
    vcenter_user: "[email protected]"
    vcenter_pass: "password"
    datacenter_name: "Datacenter"
    cluster_name: "Cluster01"

  tasks:
  - name: Create VM snapshot
    vmware_guest_snapshot:
      hostname: "{{ vcenter_server }}"
      username: "{{ vcenter_user }}"
      password: "{{ vcenter_pass }}"
      datacenter: "{{ datacenter_name }}"
      folder: /{{ datacenter_name }}/vm
      name: web
      state: present
      snapshot_name: snap1
      description: snapshot_after_creation
    delegate_to: localhost

  - name: Perform vMotion of virtual machine
    vmware_vmotion:
      hostname: '{{ vcenter_server }}'
      username: '{{ vcenter_user }}'
      password: '{{ vcenter_pass }}'
      vm_name: web
      destination_host: ESXI01
    delegate_to: localhost

Although Example 5-24 is a playbook on its own, you can see from the variables in use that it targets the VM created in Example 5-23. Example 5-24 creates a VM snapshot of the web VM named snap1 and then performs VMotion to a different host (ESXI01).


Note

Although the focus here is VMware, there are plenty of Ansible modules for other virtualization platforms, including Red Hat Virtualization (RHV), XenServer, OpenStack, and Vagrant.


Containers

Another virtualization technique involves the use of containers. Containerization is one of the latest trends in the industry.

We do not go into detail about what containers are or why would you use them. We focus here on how to use virtualization with them. If you are using Docker containers, you can manage them with Ansible. Ansible is not the most appropriate tool for this, as there are better container orchestration solutions, but it can be useful when you are developing end-to-end playbooks.

Example 5-25 shows how to create an Nginx container named web_server by using the docker_container Ansible module.

Example 5-25 Ansible Playbook to Create an Nginx Container

---
- hosts: all

  tasks:
    - name: Run a nginx container
      docker_container:
        name: web_server
        image: nginx

You can do other Docker-related tasks such as building containers and pushing them to a repository, as shown in Example 5-26. This example shows how to retrieve a Dockerfile from the ./nginx path, build the container, and push it to the repository repo/nginx with the tag v1.

Example 5-26 Ansible Playbook to Build an Nginx Container

---
- hosts: all

  tasks:
    - name: Build an image and push
      docker_image:
        build:
          path: ./nginx
        name: repo/nginx
        tag: v1
        push: yes
        source: build

If you are using containers but are not using Docker directly and instead are using an orchestrator such as Kubernetes, you need different Ansible modules. Example 5-27 shows a playbook that uses the k8s module to create a namespace, if it is not already present, and deploys two Nginx containers in that newly created namespace in a Kubernetes cluster.

Example 5-27 Ansible Playbook to Create an Application in a Specific Namespace

---
- hosts: all

  tasks:
    - name: create namespace
      k8s:
        name: my-namespace
        api_version: v1
        kind: Namespace
        state: present
    - name: deploy a web server
      k8s:
        api_version: v1
        namespace: my-namespace
        definition:
          kind: Deployment
          metadata:
            labels:
              app: nginx
            name: nginx
          spec:
            replicas: 2
            selector:
              matchLabels:
                app: nginx
            template:
              metadata:
                labels:
                  app: nginx
              spec:
                containers:
                  - name: my-webserver
                    image: repo/nginx

Note

If you are not familiar with the Kubernetes naming convention, Example 5-27 might look intimidating. From an Ansible perspective, it is just another module where you must populate the needed variables.


Cloud (AWS, GCP)

As mentioned in Chapter 1, the cloud is often an extension of networks today. For some enterprises, the public cloud is their whole network, and they have decommissioned their on-premises equipment. Netflix is an example of a customer that solely uses the public cloud.

Ansible can also help you manage the cloud, although Terraform would be a better fit due to its focus on provisioning. These tools come in handy especially when you have several cloud providers and want to abstract the syntax from the operator, just as we did for operating systems earlier in this chapter.

This section focuses on interacting with cloud components. If your goal is interacting with components in the public cloud—for example, a virtual machine or a file—you can use the same syntax and playbooks shown for those component types in previous sections of this chapter.

Each public cloud provider has a different virtual machine service. For example, if you want to create a VM in Amazon Web Services (AWS), the service is called EC2, in Google Cloud Platform (GCP), the service is called Compute Engine, and in Microsoft Azure, it is called Virtual Machines.

Example 5-28 shows how to create a virtual machine in AWS by using the EC2 service. This example only allows the user to choose the subnet where the VM should be created, along with the AMI (Amazon machine image), which is the operating system. In this example, the AMI ami-03c3a7e4263fd998c represents Amazon Linux 2 AMI (64-bit x86).

You can modify this example and parametrize the inputs you want the operator to be able to change. The subnet, AMI, and key must already exist in your cloud environment in order to be referenced by the playbook.


Note

Having playbooks with static values helps achieve standardization and compliance (such as when all your infrastructure must have some type of characteristic—for example, the same access key). Standardization and compliance are very important for maintaining a manageable cloud environment.


Example 5-28 Ansible Playbook to Create an EC2 Virtual Machine

---
- hosts: all
  vars:
    subnet: "subnet-72598a0e”
    ami: "ami-03c3a7e4263fd998c”

  tasks:
    - amazon.aws.ec2:
        key_name: key
        instance_type: t2.micro
        image: "{{ ami }}”
        wait: yes
        vpc_subnet_id: "{{ subnet }}”
        assign_public_ip: yes

Note

This type of playbook runs locally from your control node.


Ansible has modules available for all major cloud providers. Example 5-29 shows a playbook that creates a 50 GB storage disk for GCP. As in the EC2 case in Example 5-28, all referenced resources in Example 5-29 (source_image, zone, project, serviceaccount) must already exist, or the playbook’s execution will fail.

Example 5-29 Ansible Playbook to Create a GCP Disk

---
- hosts: all
  vars:
    gcp_cred_file: "/tmp/test-project.json"

  tasks:
      - name: create a disk
        gcp_compute_disk:
          name: disk-instance01
          size_gb: 50
          source_image: projects/ubuntu-os-cloud/global/images/family/ubuntu-2004-lts
          zone: "us-central1-a"
          project: "633245944514"
          auth_kind: "serviceaccount"
          service_account_file: "{{ gcp_cred_file }}"
          state: present
        register: disk

You can fully provision a cloud environment by chaining multiple tasks in an Ansible playbook or executing several playbooks in a row. Example 5-30 expands on Example 5-28 by creating an SSH key and firewall rules before creating the virtual machine on AWS. Furthermore, it saves the newly created key (key-private.pem) locally so you can access the virtual machine later.

Example 5-30 Ansible Playbook to Provision Resources on AWS

---
- hosts: all
  vars:
    subnet: "subnet-72598a0e"
    ami: "ami-03c3a7e4263fd998c"

  tasks:
    - name: Create an EC2 key
      ec2_key:
        name: "ec2-key”
      register: ec2_key

    - name: Save private key
      copy: content="{{ ec2_key.key.private_key }}” dest="./key-private.pem” mode=0600
      when: ec2_key.changed

    - name: Create security group
      ec2_group:
        name: "ec2_security_group"
        description: "ec2 security group"
        rules:
          - proto: tcp  # ssh
            from_port: 22
            to_port: 22
            cidr_ip: 0.0.0.0/0
          - proto: tcp  # http
            from_port: 80
            to_port: 80
            cidr_ip: 0.0.0.0/0
          - proto: tcp  # https
            from_port: 443
            to_port: 443
            cidr_ip: 0.0.0.0/0
        rules_egress:
          - proto: all
            cidr_ip: 0.0.0.0/0
  register: firewall

    - amazon.aws.ec2:
        key_name: ec2-key
        instance_type: t2.micro
        image: "{{ ami }}"
        wait: yes
        vpc_subnet_id: "{{ subnet }}"
        assign_public_ip: yes
        group_id: "{{ firewall.group_id }}”

When you and your team use playbooks, you no longer need to access the clouds’ graphical interfaces, and you reduce human error and increase speed and agility. Automation is a cloud best practice that is recommended by all cloud vendors.


Note

Keep in mind that Terraform is also a great tool for cloud resources provisioning.


Interacting with APIs

Chapter 2 covers APIs and their value when it comes to gathering data. Ansible can interact with APIs to enable you to automate actions based on collected data.

Some modules automatically use APIs as the connection mechanism (for example, the Cisco FTD module). However, if you need to connect to custom APIs, you can use the uri module. It is a built-in module in Ansible that facilitates interaction with HTTP/S web services.

Example 5-31 shows an API that requires you to acquire a token with your user credentials and use that token in your future requests. It uses the DNA Center API in a sandbox environment.

Example 5-31 Ansible Playbook to Get All Network Devices from DNAC

---
- hosts: all

  tasks:
    - name: Get login Token
      uri:
        url: "https://sandboxdnac.cisco.com/api/system/v1/auth/token"
        method: POST
        user: "devnetuser"
        password: "Cisco123!"
        force_basic_auth: yes
      register: token_response

    - name: Get network devices
      uri:
        url: "https://sandboxdnac.cisco.com/api/v1/network-device/"
        method: GET
        headers:
          x-auth-token: "{{ token_response.json.Token }}”
      register: devices_response

    - name: print response
      debug:
        msg: "{{ devices_response }}”

Note

Chapter 2 shows this same API call with curl.


The first task in Example 5-31 (Get login Token) issues a POST request with the user credentials. The second task (Get network devices) uses the returned token in the headers to issue a GET request for configured devices on the system.

Try out this example yourself by saving the playbook in Example 5-31 as API.yml and executing the following command (with SSH enabled on your localhost):

$ ansible-playbook -i “localhost,” API.yml

You can also use this same module, uri, to interact with websites and crawl their content, even though websites are not APIs. The playbook in Example 5-32 is similar to the one in Example 5-31, but instead of using an API URL, it provides the URL of Cisco’s website. You can execute this example by using the same syntax as previously mentioned. In the content variable named result, you can see an HTML text representation document of the page.

Example 5-32 Ansible Playbook to Crawl Website Content

---
- hosts: all
  gather_facts: no

  tasks:
    - name: Crawl Cisco.com website
      uri:
        url: https://www.cisco.com
        return_content: yes
      register: result

    - name: Print content
      debug:
        msg: "{{ result }}”

The preferred way to crawl web pages is actually by using the lookup function, as shown in Example 5-33, instead of the uri module.

Example 5-33 Ansible Playbook to Crawl Website Content Using lookup

---
- hosts: all
  gather_facts: no

  tasks:
    - name: Crawl Cisco.com website
      debug: msg="{{ lookup('url', 'https://www.cisco.com') }}”

Note

You run API playbooks run from your control node.


Although you have seen ways of building playbooks to connect to custom API endpoints, the preferred way is to use modules, as shown throughout this chapter. Use the uri module only when strictly necessary.

To spark your use case imagination, the following are some of the common components in the networking world that have modules available:

• GitHub

• ServiceNow

• Jenkins

• Splunk

• Slack

Case Studies

This section presents three case studies based on real customer requests. They show how Ansible can be used to make configuration changes and enable global-scale automation.

Some of these use cases have code examples, but note that these use cases are simply combinations of the techniques you have learned in this chapter and in Chapter 4. This section shows how you can solve global-scale networking challenges by intelligently combining Ansible modules into playbooks.

Configuration Changes Across 100,000 Devices

Customer XYZ has an installed base throughout Europe that entails nearly 100,000 devices. This network has been running for well over 10 years, and it includes a multitude of different vendors and platforms. It also includes the following components:

• Firewalls

• Wireless LAN controllers

• Switches and routers

• Servers

All these devices are accessed using TACACS authentication.

XYZ’s security team identified a necessity: It needed to modify the TACACS key, as it was the same for every device and had never changed since the network was initially created. Also, the team thought it would be good to be able to rotate this key every 6 months. They were facing European compliance issues, so these necessities had to be addressed.

We were involved in automating the process, as manually changing so many thousands of devices did not seem feasible. Ansible was chosen as the automation tool.

The workflow we developed had many stages, including a number of verifications. Losing access to one of these devices involved a repair cost because some of them were in remote locations and not easily accessible. The workflow steps were as follows:

Step 1. Collect the device configuration.

Step 2. Validate the TACACS authentication.

Step 3. Parse the source IP address for the TACACS server.

Step 4. Modify the TACACS key on the device.

Step 5. Modify the device entry on the TACACS server, using the source IP address.

Step 6. Validate the TACACS authentication.

We used a different connection mechanism for each component: We had Ansible modify the TACACS server by using an API, and we used SSH for the devices.

The biggest difficulties we faced were related to the sparseness of the installed base. The playbook had to identify what device type it was connecting to in order to be able to correctly make the change. For this, we used a dynamic inventory script that parsed a third-party device inventory database containing information regarding what operating system was running on each device.

We successfully changed all devices in record time: one month. By using the same playbooks, XYZ is now able to rotate its keys whenever necessary.

The playbook in Example 5-34 is a scaled-down version of the playbooks we used. It is specifically for NX-OS. This example can be seen as an end-to-end example that touches several components.

We highlight the main tasks in Example 5-34: changing the TACACS configuration on the device and on the TACACS server. However, you can see that most of the beginning of the playbook uses regex along with gathered facts to identify relevant information (for example, the configured source IP address) that is required for these key tasks that occur later in the playbook.

Example 5-34 Ansible Playbook to Update TACACS Key for NX-OS

- name: Ansible Playbook to change TACACS keys for NXOS Devices
  hosts: nxos
  connection: local
  gather_facts: yes
  vars:
    tacacs_key: 'VerySecretKey1'
    tacacs_server_ip1: "10.0.0.1"
    tacacs_source_regexp: "{{ 'ip tacacs source-interface ([aA-
zZ]+\-?[aA-zZ]+\d\/?\d?\/?\d?\/?:?\d?\.?\d?\d?\d?\d?)\s?' }}"

  roles:
    - TACACSrole

  tasks:
    - name: Scan running-config for TACACS information
      set_fact:
        tacacs_source_interface: "{{ ansible_facts.net_config |
regex_findall(tacacs_source_regexp, multiline=True) }}"

    - name: Retrieve TACACS VRF section
      cisco.nxos.nxos_command:
        commands: show running-config | section ^aaa
      register: conf_aaa

    - name: Retrieve TACACS VRF line
      set_fact:
        conf_aaa_vrf: "{{ conf_aaa.stdout.0 | regex_findall('use-vrf \w*',
multiline=True) }}"

    - name: Register TACACS VRF
      set_fact:
        conf_aaa_vrf_name: "{{ (conf_aaa_vrf | first).split()[1] }}"
      when: conf_aaa_vrf | length > 0

    - name: Assert that 'ip tacacs source-interface is configured'
      assert:
        that:
          - "{{ tacacs_source_interface | length }} == 1"

    - name: Scan running-config for tacacs source-interface IP address
      set_fact:
        tacacs_source_ip: "{{
ansible_facts.net_interfaces[tacacs_source_interface[0]].ipv4.address  }}"
      when: ansible_facts.net_interfaces.Traceback is not defined

    - name: Assert that we learned the tacacs source IPv4 address
      assert:
        that:
          - tacacs_source_ip is defined

    - name: Change tacacs server 1 configuration
      cisco.nxos.nxos_aaa_server_host:
        state: present
        server_type: tacacs
        key: "{{ tacacs_key }}"
        address: "{{ tacacs_server_ip1 }}"

    - name: Add/Update network device in ISE-1
      ise_network_device:
        ise_node: "{{ ise_node }}"
        ers_user: "{{ ers_user }}"
        ers_pass: "{{ ers_pass }}"
        device: "{{ inventory_hostname }}"
        tacacs_shared_secret: "{{ tacacs_key }}"
        ip_addresses:
          - "{{ tacacs_source_ip }}"
        presence: present

    - name: login to device and test aaa on ISE-1 for vrf
      cisco.nxos.nxos_command:
        commands: test aaa server tacacs+ {{ tacacs_server_ip1 }} vrf {{
conf_aaa_vrf_name }} {{ ansible_user }} {{ ansible_password }}
        wait_for: result[0] contains authenticated
        retries: 1
      when: conf_aaa_vrf_name is defined

   - name: Login to the device and save the running-config to startup-config
     cisco.nxos.nxos_command:
       commands:
       - "write memory”

The actual playbooks used at XYZ were more complex because there are multiple ways of configuring TACACS, and we also had to automatically detect which one was being used. Furthermore, there were two TACACS servers configured per device, rather than a single one, because loss of connectivity to a device would require an operator to travel—in some cases over 200 km—to connect locally to the device’s console. Example 5-34 shows that, with the correct chaining of Ansible tasks, amazing automation is possible.

Quality of Service Across a Heterogenous Installed Base

Quality of service (QoS) is often a pain to configure. The configuration is highly dependent on the platform as well as the operating system. In addition, there is complexity in translating commands from one platform to another, even within the same vendor; translating across vendors is even more complex.

Customer ABC has a heterogenous installed base with a couple different platforms and two vendors. Due to bandwidth constraints, ABC decided to apply QoS to its network. We were tasked with addressing this in a more than 2000 devices across 100 sites.

Initially, we planned to apply QoS manually. We prepared configurations for each platform and scheduled several maintenance windows to gradually apply them.

The first maintenance window came around, and during the verification steps, traffic flows seemed off. After spending a night troubleshooting, we came to the realization that some of the QoS policies were mismatched. We decided to use Ansible playbooks to configure the devices.

We built an inventory file from a management system that was already in place at ABC. We also built a playbook to verify, upon connecting to a device, whether the facts gathered match that management system’s view. This extra verification step was a needed safeguard because it meant that, without a lot of effort, we can quickly identify inconsistencies that would later cause issues and possible network downtime. We ran this playbook for all devices for which we intended to change the QoS configurations. We did not develop automation for the change on the management system for incorrect device attributes; we did this manually for the identified devices.

With a consolidated inventory, the QoS workflow had three phases and an optional fourth:

Step 1. Gather information on what was configured on the devices.

Step 2. Push the compiled configurations to the devices.

Step 3. Verify the configurations.

Step 4. (Optional) Roll back the configuration.

In the first step, we gathered information on what was configured on the devices. Using that information, we generated the reverse CLI in order to delete the old QoS configuration remnants from the device. It is paramount to avoid leftover configurations. Furthermore, we merged this information with the CLI configuration we had previously built. The result of this phase was configuration files that would replace the current device QoS configuration with the intended QoS configuration.

The second step was to push the previously compiled configuration from step 1 to the actual devices.

The third step was to verify that the configurations were correctly committed to memory and also run more in-depth tests, such as for QoS queues.

The fourth step was optional and triggered only if the verifications from the third step failed.

The playbooks we developed used conditional sentences to match the configuration to the platform and operating system, along with the Ansible modules to use. They were similar to in the one shown in Example 5-21.

The Ansible solution provided helped us finish the project without any other misconfiguration, and it also provided a tool that the customer can use to provision new network devices without the fear of applying incorrect QoS settings. Furthermore, we continue to use these playbooks in customer deployments today.

Disaster Recovery at a European Bank

A bank in a country in Europe has many data centers. Due to its business nature, the bank has strict requirements when it comes to availability, reliability, and disaster recovery. The data center technology stack for this bank consisted of a software-defined networking solution, Cisco ACI, and the compute was virtualized using VMware technologies.


Note

Cisco APIC (Application Policy Infrastructure Controller) is the controller for the Application Centric Infrastructure (ACI) software-defined networking solution for data centers.


One of the bank’s applications is not distributed due to security concerns, which means it runs on a single data center. The bank engaged us to investigate how to prevent this application from being affected by a failure in that data center.

Several options were considered, but all of them included extending the application to several data centers, such as stretched subnets. These options were not acceptable based on the client’s network architecture principles.

However, we found another solution. Instead of providing high availability to this application, we could provide it with disaster recovery. This means that, in the case of a failure, we could start it somewhere else (in another data center). We went a step further and decided to provide disaster recovery for most of the non-redundant applications in that data center. As you might guess, we accomplished this by using Ansible.

We developed some playbooks that fell into two categories:

• Saving the data center state

• Deploying a data center “copy”

Saving the data center state involved playbooks run on the data center where the non-redundant applications live. We decided to run them once at night because the bank’s infrastructure does not change often; however, the playbooks can be run as often as needed.

These playbooks capture the state of the network and computing infrastructure—that is, which IP addresses the applications have, in which VLANs they run, gateway IP addresses, and so on. We extracted this information from the bank’s VMware vCenter and Cisco APIC controller by using Ansible modules.

We then saved the infrastructure state to two databases (for high availability). This allowed us to get a picture of what was running the day before in the data center. A database is not required, however; it would be possible to save the retrieved values to any storage format (for example, text files).

For deploying a data center “copy,” we developed a playbook to read from the database and deploy those concepts. This playbook is run manually in the event of a disaster in the other data center. It copies all the infrastructure that was running there the day before.

What enabled us to do this was not only network automation using Ansible. Importantly, the customer had a software-defined networking data center where all the network infrastructure was represented in a text format. Although the copying we did would have been possible in a traditional data center, connecting to a multitude of devices to gather their state and replicate that on a possibly different topology would have been much more complicated. In addition, vCenter has a central picture of the environment, and that enabled the copy on the computing side.

Using only Ansible, we were able to create a disaster recovery solution with a maximum recovery point objective (RPO) of 24 hours (or less, if the first playbook is run more often) and recovery time objective (RTO) of around 15 minutes.

Plenty of enhancements could be made to this approach. For example, it would be possible to create an automated way of triggering the data center “copy” in the event of an outage. You saw some monitoring tools capable of triggers in Chapter 3. However, it is important to avoid overautomating. You should automate only what brings you benefit and meets your business goals. You will learn more about this in Chapter 7, “Automation Strategies.”

Summary

This chapter ties the Ansible concepts you learned in Chapter 5 to actual network automation tasks. It goes over interactions with common components such as the following:

• Files

• Different device types (routers, switches, virtual machines, servers)

• Containers and Kubernetes

• Public cloud components

• APIs

This chapter also presents case studies where Ansible was successfully used to automate processes in projects with thousands of devices. You have seen how Ansible can help you reduce human error, improve agility and speed, and reuse previous work in networking tasks.

Take a moment to think about what configuration, monitoring, or other types of tasks you do regularly. Based on what you have read in this chapter, think about how you would automate them.

Review Questions

You can find answers to these questions in Appendix A, “Answers to Review Questions.”

1. True or false: When retrieving a remote file’s content, you can use the Ansible lookup module.

a. True

b. False

2. When using the Ansible slurp module, what is the output format?

a. Base-64

b. Binary

c. YAML

d. JSON

3. You want to copy the result of a task’s execution to a file in the local execution machine. You are using the copy module, but it is saving the file in the remote host. What module do you use to enhance your solution and save the result locally?

a. slurp

b. delegate_to

c. when

d. msg

4. You want to retrieve the IP addresses of all interfaces in a Cisco network router. Can you achieve this using only ansible_facts?

a. Yes

b. No

5. You want to execute several commands on Linux servers from your company after they are physically installed. Which module is the most appropriate?

a. command

b. slurp

c. ios_config

d. yum

6. You are tasked with installing Ansible in an automated fashion in several hosts. These hosts have different operating systems. Which variable from ansible_facts can you use to correctly choose the installation procedure?

a. os_family

b. nodename

c. ipv4

d. architecture

7. You must interact with custom software made in-house in your company. There is no Ansible module for it. Do you need to use an automation tool other than Ansible?

a. Yes, you must use a different tool.

b. No, you can still use Ansible.

8. You are tasked with crawling your company website’s content using Ansible. Which is the preferred way to achieve this task?

a. Use the uri module

b. Use the command module

c. Use the lookup function

d. Use the copy module

9. Your company uses NETCONF as its management protocol. You are tasked with translating some of your previously developed curl scripts to Ansible. Which Ansible module would you use to retrieve the configuration?

a. netconf_config

b. netconf_rpc

c. restconf_get

d. netconf_get

10. You must delete from your VMware environment hundreds of unused virtual machines that are currently shut down. You decide to use Ansible to automate this task. Which state should you set in the module for this effect?

a. present

b. absent

c. shutdownguest

d. poweredoff

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
44.199.241.53