Introduction to Ansible

Ansible is an open-source configuration management tool, written in Python for configuration management, application deployment, task automation, and more. Michael DeHaan created Ansible in 2012, and it gained traction in 2013. Red Hat acquired Ansible in 2015. Nowadays, a large community still contributes to its development.

It’s one of the best tools to start with if you are new to network automation. Let me explain why:

  • Ansible uses an agentless architecture and primarily uses SSH to configure and manage different targets.
  • You don’t have to install agent software on the target you want to configure or manage:
    • All you need is SSH to get started.
  • Installation is simple and only takes a minute:
    • You can run Ansible from your computer.
    • You don’t need to install a separate server with many requirements to get going.
  • Ansible uses YAML, which is easy to read and write, even if you have never worked with it before.

The combination of not having to install agent software, being able to run Ansible from your computer, and using YAML make it a great tool to get started with network automation. You can run your first automation in a couple of minutes. You can use it to automate the things you currently do manually.

You can configure or manage many different targets. For example:

  • Linux and Windows servers.
  • Network devices such as routers or switches.
  • Cloud providers such as Amazon AWS.
  • Hypervisors such as VMware or Proxmox.
  • Docker containers.
  • Storage systems such as NetApp.
  • Database servers such as MySQL server.

Here are some of the things you can do with these targets:

  • Application deployment:
    • Install and update applications.
    • For example:
      • Install the Nginx web server on a Ubuntu server.
      • Install MySQL server on a Ubuntu server.
  • Configuration management:
    • Ensure applications use the latest configuration file.
    • For example:
      • Copy a Syslog configuration file to the Syslog server.
      • Configure Syslog and SNMP on all Cisco devices.
      • Configure a new VLAN on all Cisco switches.
  • Security compliance:
    • Automate the implementation of security policies and compliance requirements.
    • For example:
      • Check if all Cisco routers don’t use OSPF plain text authentication.
  • Cloud resource management:
    • Manage cloud infrastructure resources on Amazon AWS, Azure, and Google Cloud.
    • For example:
      • Create an AWS EC2 instance in the us-east-1 region.
      • Create an AWS RDS Mysql server in the us-west-1 region.
  • Patch management:
    • Apply patches and updates.
    • For example:
      • run apt update and apt upgrade on many Ubuntu servers.
  • Backups:
    • Make backups of configuration files.
    • For example:
      • Download the running configuration from all Cisco switches every day.
  • Image updates:
    • Install new software versions.
    • For example:
      • Run dir flash: on all Cisco switches and send an email when there is less than 10% free storage space.
      • Copy the latest IOS image to Cisco routers and reboot them.
  • Database administration:
    • Create users or databases.
    • For example:
      • Create a new database on a MySQL server.
      • Create a new MySQL user with a generated random password on a MySQL server.
      • Run mysqldump to create a backup of a MySQL database.

Ansible uses YAML-based playbooks with tasks you want to execute against one or more targets. Let me show you an example of a playbook real quick:

---
- name: Install Nginx
  hosts: WEB1
  become: yes

  tasks:
    - name: Install Nginx package
      package:
        name: nginx
        state: present

Even if you have never seen YAML or Ansible before, this is easy to read. This playbook targets a host named “WEB1” and installs the Nginx package.

Playbooks can be executed in parallel against multiple targets, saving time.

Also, Ansible focuses on idempotency, which means that even if you execute tasks multiple times, the target will remain in the same state.

In this lesson, I’ll give you an overview of the different Ansible components and some playbook examples. We’ll cover enough so you know what Ansible is and how to get started. When you learn Ansible, it’s best to start with some simple examples, and when you can get something to work, you can always add additional tasks and complexity later. There are no prerequisites to learning Ansible.

Components

Let’s start with the main components of Ansible. We’ll cover the most important ones that are required to get started.

Control Node

The control node is the machine where Ansible is installed and from which you run playbooks. This sounds like a fancy name, but it can be your local computer. Almost any operating that can run Python is able to run Ansible.

There are multiple options to run Ansible.

CLI

  • Ansible is CLI-based, so the only thing you need to do is type commands into the CLI to execute a playbook with tasks.
  • You can execute these from your local computer.
  • You can also use this in CI/CD. For example, in a GitHub Actions workflow:
    • When someone makes a change to a configuration file and commits it to a Git repository, it can execute Ansible and run the playbook.

GUI

  • When you use Ansible with a team or in an enterprise environment, running playbooks from the CLI might not be the best option:
    • Remembering CLI commands can be a pain.
    • You might need additional features such as role-based access control, task scheduling, etc.
    • Redhat has two GUI options:
      • AWX: an open-source solution.
      • Ansible Automation Platform:  a paid solution that was previously known as Ansible Tower.
    • A smaller open-source project that I’ve been enjoying is Semaphore UI. It offers a GUI for Ansible with scheduling options and is nice for small environments.

When you are just getting started, remember that there are different options, but for now, stick to running Ansible from your local computer.

Managed Nodes

A managed node is the target machine you want to configure or manage with Ansible.  It primarily uses SSH, but there are other options, such as:

  • WinRM: Used to manage Windows machines.
  • NETCONF: A protocol to manage network devices.
  • APIs: Allows Ansible to communicate with services or network devices through APIs.
  • VMWare: Interactive with VMWare vSphere.
  • PSRP: PowerShell Remoting Protocol.
  • Docker: Interact with Docker containers directly.
  • Kubernetes: Interact with Kubernetes clusters directly.

There are many other connection types that interact with managed nodes.

Inventory

The inventory is a list of managed nodes, and it can be static or dynamic:

  • Static: a static file where you list the managed node IP addresses or hostnames.
  • Dynamic: a generated inventory file from a script or external source.

You can also organize hosts into groups. For example, you could define the hostnames for all your network switches and group them under a group named “switches”. You can then execute playbooks against a group instead of individual hosts. Inventory files can be in INI format or YAML.

Playbooks

In the playbook, you define in YAML the tasks you want to run and against what managed nodes. Ansible will run each task after another. It’s also possible to define a handler, a special type of task that only runs when notified. This is useful for tasks you only want to run in specific scenarios. For example, let’s say you want to update a software package that requires a restart after updating. You could use a task for the update, and a handler to restart the service. The handler will only be notified after a successful outcome from the task. Otherwise, you would restart the service for no reason.

To make playbooks dynamic, you can use variables and templates. Variables can be used for dynamic values, and templates generate files from variables. Ansible uses Jinja templates. For example, you could use a variable that sets the hostname of a Cisco switch. Let’s say the hostname is SW1. With a template, you could create a configuration file for the switch that uses that variable to create the hostname SW1 command.

Besides playbooks, you can also run ad-hoc commands with Ansible.

Roles

Roles help to organize playbooks. For example, let’s say we have a playbook to configure a router. The playbook has two tasks:

  • Configure the hostname.
  • Configure a loopback interface.

Now, we want to configure a playbook to configure a switch. This playbook also has two tasks:

  • Configure the hostname.
  • Configure a VLAN.

We can copy the code for the task to create the hostname from our router playbook, but that’s inefficient. Also, it’s a pain to maintain because if you want to update the task, you’ll have to do it in two playbooks.

Instead, we can move the task configuring the hostname to a role. In the playbook, we only have to refer to the role instead of the task. A role can even contain more tasks. For example, you could create a “Cisco IOS device” role which has tasks you want to apply to all your Cisco devices. Such as configuring SSH, a Syslog server, SNMP server, and more. Roles are great to avoid code redundancy and keep your playbooks short and to the point.

Also, you don’t have to configure these roles from scratch. You can find a huge collection of roles at Ansible Galaxy. These are roles that are created by the community. You can download them and use them in your playbooks.

Modules

Modules are units of code that Ansible executes on managed nodes. They perform tasks such as managing files, users, packages, etc.

For example, here is a playbook:

---
- name: Install and start Apache on Ubuntu
  hosts: webservers
  become: yes
  tasks:
    - name: Ensure Apache is installed
      apt:
        name: apache2
        state: present
        update_cache: yes

    - name: Ensure Apache is started
      service:
        name: apache2
        state: started

In this playbook, apt is the module for the APT (Advanced Packaging Tool). This is the package management system for Debian-based Linux distros such as Ubuntu. Here is another example:

- name: Copy configuration file
  copy:
    src: /path/to/local/file.conf
    dest: /path/on/remote/server/file.conf
    owner: root
    group: root
    mode: '0644'

This is the copy module that lets you copy files from a source to a destination.  And the last one:

---
- name: Configure interface on Cisco IOS device
  hosts: cisco_devices
  gather_facts: no
  connection: network_cli
  vars:
    interface_name: GigabitEthernet1
    ip_address: 192.168.1.1
    subnet_mask: 255.255.255.0
  tasks:
    - name: Configure interface IP address
      ios_config:
        lines:
          - interface {{ interface_name }}
          - ip address {{ ip_address }} {{ subnet_mask }}
          - no shutdown
        save_when: changed

This uses the ios_config module you can use for Cisco IOS configurations.

Plugins

Plugins are pieces of code that modify Ansible’s core functionality. They allow you to customize how Ansible executes tasks and interacts with managed nodes. There are different plugin types:

  • Action Plugins: Extend or override the behavior of modules.
  • Callback Plugins: Alter output that appears when running Ansible or take actions on task completion.
  • Connection Plugins: Define how Ansible communicates with remote systems.
  • Filter Plugins: Transform data for templates and other Ansible operations.
  • Lookup Plugins: Retrieve data from external sources.
  • Vars Plugins: Load additional variables into a playbook.
  • Inventory Plugins: Provide dynamic sources of inventory data beyond static files.

Don’t worry about these when you are just getting started. I’ll give you an example, though, so you know what they are about. Here is a list of connection plugins:

$ ansible-doc -t connection -l
[DEPRECATION WARNING]: ansible.netcommon.napalm has been deprecated. See the plugin documentation for more details.
This feature will be removed from ansible.netcommon in a release after 2022-06-01. Deprecation warnings can be disabled
 by setting deprecation_warnings=False in ansible.cfg.
ansible.builtin.local          execute on controller
ansible.builtin.paramiko_ssh   Run tasks via Python SSH (paramiko)
ansible.builtin.psrp           Run tasks over Microsoft PowerShell Remoting Protocol
ansible.builtin.ssh            connect via SSH client binary
ansible.builtin.winrm          Run tasks over Microsoft's WinRM
ansible.netcommon.httpapi      Use httpapi to run command on network appliances
ansible.netcommon.libssh       (Tech preview) Run tasks using libssh for ssh connection
ansible.netcommon.napalm       Provides persistent connection using NAPALM
ansible.netcommon.netconf      Provides a persistent connection using the netconf protocol
ansible.netcommon.network_cli  Use network_cli to run command on network appliances
ansible.netcommon.persistent   Use a persistent unix socket for connection
community.aws.aws_ssm          connect to EC2 instances via AWS Systems Manager
community.docker.docker        Run tasks in docker containers
community.docker.docker_api    Run tasks in docker containers
community.docker.nsenter       execute on host running controller container
community.general.chroot       Interact with local chroot
community.general.funcd        Use funcd to connect to target
community.general.iocage       Run tasks in iocage jails
community.general.jail         Run tasks in jails
community.general.lxc          Run tasks in lxc containers via lxc python library
community.general.lxd          Run tasks in lxc containers via lxc CLI
community.general.qubes        Interact with an existing QubesOS AppVM
community.general.saltstack    Allow ansible to piggyback on salt minions
community.general.zone         Run tasks in a zone instance
community.libvirt.libvirt_lxc  Run tasks in lxc containers via libvirt
community.libvirt.libvirt_qemu Run tasks on libvirt/qemu virtual machines
community.okd.oc               Execute tasks in pods running on OpenShift
community.vmware.vmware_tools  Execute tasks inside a VM via VMware Tools
containers.podman.buildah      Interact with an existing buildah container
containers.podman.podman       Interact with an existing podman container
kubernetes.core.kubectl        Execute tasks in pods running on Kubernetes

This shows you all the different connection plugins Ansible can use to connect with managed nodes.

Secrets Management

Secrets are sensitive information such as API keys, tokens, usernames, passwords, etc. When using Ansible, you’ll have to work with secrets sooner or later.

There are different ways to deal with this:

  • Hardcode them in your playbooks.
  • Prompts to manually enter secrets.
  • Use a secrets manager.

Hardcoding secrets in playbooks is a bad idea. Never do this because YAML files are saved in clear text. When a playbook never leaves your computer, you might get away with it, but when you want to put your code under version control, you don’t want secrets to appear in your Git repository.

Another option is to use prompts. You could configure your playbook so that you have to supply a secret when you run it. That’s fine for something as simple as a username, but it’s annoying to do this repeatedly for passwords or API keys. Also, when you are the only one who can supply secrets, you can’t use Ansible in automation, such as with a CI/CD pipeline.

The third option is a secrets manager, a tool or service for securely storing and managing sensitive information. There are many external solutions, such as:

  • Hashicorp Vault
  • AWS Secrets Manager
  • Azure Key Vault

Ansible also has a secrets manager named Ansible Vault. This is an AES 256-bit encrypted YAML file where you can store all your secrets. It is an easy option to get started with. You define your secrets in a YAML file, and they are encrypted when you save the file. You can commit this encrypted YAML file to a Git repository and nobody will be able to read it.

Configuration

Let’s look at some examples.

Installation

Let’s start with the installation. You can use Ansible on most operating systems:

  • Linux:
    • Debian-based: sudo apt install ansible
    • CentOS-based: yum install ansible
  • Windows:
    • Use Windows Subsystem for Linux (WSL) and run sudo apt install ansible
  • MacOS:
    • brew install ansible.

That’s all it takes.

SSH

We need to use the SSH clients on our control node. If you don’t have an SSH keypair, create one:

$ ssh-keygen
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/ubuntu/.ssh/id_ed25519): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/ubuntu/.ssh/id_ed25519
Your public key has been saved in /home/ubuntu/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:e8zFT0jhPZmylkUe7XpmkG2v7PD2X9SgX2hqLwr7XwA ubuntu@ANSIBLE
The key's randomart image is:
+--[ED25519 256]--+
|            . o. |
|           . = +.|
|          E + O+ |
|           + Bo+=|
|        S   X ++=|
|         + o B.o=|
|        o + +.+=.|
|         + ..=+ .|
|        ..oo.++.+|
+----[SHA256]-----+

We now have a public and private key for SSH.

File and Folder Structure

We’ll start with some files and folders. I created an Ansible folder which has the following files and folder:

/ansible
│
├── inventory.ini                     # Inventory file containing host information.
│
├── playbooks                         # Directory containing playbook files.
│   ├── install-web-server.yml        # Playbook for web server installation.
│   ├── ospf-configure-network.yml    # Playbook to configure OSPF.

We’ll modify these files as we go along.

Ubuntu Server

We’ll start with two Ubuntu servers. I created two VMs with these IP addresses:

  • 10.65.90.1
  • 10.65.90.2

We’ll see if we can turn these into web servers.

There are two ways to authenticate:

  • Password authentication: type in the password each time you want to connect.
  • Public key authentication: use the SSH key pair.

Typing in the SSH password every time is a pain, so it’s best to use public key authentication. This is easy thanks to the ssh-copy-id command. We’ll copy our public key from the control node to the Ubuntu servers. Here’s the first one:

$ ssh-copy-id ubuntu@10.65.90.1
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "https://cdn.networklessons.com/home/ubuntu/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
(ubuntu@10.65.90.1) Password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'ubuntu@10.65.90.1'"
and check to make sure that only the key(s) you wanted were added.

And the second one:

$ ssh-copy-id ubuntu@10.65.90.2
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "https://cdn.networklessons.com/home/ubuntu/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
(ubuntu@10.65.90.2) Password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'ubuntu@10.65.90.2'"
and check to make sure that only the key(s) you wanted were added.

Now, when I try to log into these Ubuntu servers from my control node, it won’t ask me for the password anymore and uses public key authentication:

$ ssh ubuntu@10.65.90.1
Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.8.0-45-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Thu Oct 10 13:55:50 UTC 2024

  System load:  0.0               Processes:             133
  Usage of /:   28.4% of 7.19GB   Users logged in:       0
  Memory usage: 6%                IPv4 address for eth0: 10.65.90.1
  Swap usage:   0%


Expanded Security Maintenance for Applications is not enabled.

0 updates can be applied immediately.

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status


Last login: Thu Oct 10 13:47:03 2024 from 10.65.20.1
ubuntu@WEB1:~$

That takes care of the SSH part. Let’s modify our inventory.ini file:

[web]
10.65.90.1 ansible_user=ubuntu
10.65.90.2 ansible_user=ubuntu

I created a group named “web” with two entries. I also specified the username of these machines.

Let’s create a install-web-server.yml playbook:

---
- name: Install Nginx on web servers
  hosts: web
  become: yes

  tasks:
    - name: Update apt cache
      apt:
        update_cache: yes

    - name: Install Nginx
      apt:
        name: nginx
        state: present

    - name: Ensure Nginx is started and enabled
      service:
        name: nginx
        state: started
        enabled: yes

This playbook updates the APT cache and uses apt on a Ubuntu machine to install the Nginx package and then make sure the Nginx service is running. Let’s execute it:

$ ansible-playbook -i inventory.ini ./playbooks/install-web-server.yml

PLAY [Install Nginx on web servers] *********************************************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************************************************************
ok: [10.65.90.1]
ok: [10.65.90.2]

TASK [Update apt cache] *********************************************************************************************************************************************
changed: [10.65.90.2]
changed: [10.65.90.1]

TASK [Install Nginx] ************************************************************************************************************************************************
changed: [10.65.90.2]
changed: [10.65.90.1]

TASK [Ensure Nginx is started and enabled] **************************************************************************************************************************
ok: [10.65.90.2]
ok: [10.65.90.1]

PLAY RECAP **********************************************************************************************************************************************************
10.65.90.1                 : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
10.65.90.2                 : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

And that’s it. We just installed Nginx on two web servers. If you open http://10.65.90.1 in a web browser, you should see a Nginx welcome page.

We created an inventory file and a playbook, executed it, and it worked.

Cisco IOS XE

Let’s try if we can manage two Cisco IOS XE routers.

Routers

We can configure OSPF on two routers and report the neighbor adjacency to us. I’ll use this topology:

Cisco Cml Ansible Topology

These two routers are directly connected with the 192.168.12.0/24 subnet. We’ll create an Ansible playbook to configure OSPF to run on the Gi1 interfaces of these two routers. We need to be able to connect to these routers through SSH. I’m using the Gi2 interfaces and 10.65.90.0/24 subnet for this.

I’m using Cisco CSR1000v routers running Cisco IOS Software [Amsterdam], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 17.3.6, RELEASE SOFTWARE (fc2). Only the hostnames, IP addresses, and SSH server have been configured.

Configurations

Want to take a look for yourself? Here, you will find the startup configuration of each device.

R1

hostname R1
!
ip domain name NWL.LAB
!
username CISCO privilege 15 secret 9 $9$mN4AHD9Y.Inl/E$tCJgHpnSHdM5Qzt1ggx9avSlMvNWGGlikXL1Owp1PfQ
!
interface GigabitEthernet1
 ip address 192.168.12.1 255.255.255.0
!
interface GigabitEthernet2
 ip address 10.65.90.4 255.255.255.0
!
ip ssh version 2
!
line vty 0 4
 login local
 transport input ssh
!
end

R2

hostname R2
!
ip domain name NWL.LAB
!
username CISCO privilege 15 secret 9 $9$qpzd9JDDZ3.oPU$m/GnFUJCgf9H1WfrzXBk9YKIIm/01Fa.G0VEUaVdt8w
!
interface GigabitEthernet1
 ip address 192.168.12.2 255.255.255.0
!
interface GigabitEthernet2
 ip address 10.65.90.5 255.255.255.0
!
ip ssh version 2
!
line vty 0 4
 login local
 transport input ssh
!
end

Control Node

We have to do a couple of things on the control node. First, I’ll see if I can connect to the routers through SSH. Here’s R1:

$ ssh CISCO@10.65.90.4
The authenticity of host '10.65.90.4 (10.65.90.4)' can't be established.
RSA key fingerprint is SHA256:mEo+4FGXq5heYTrZYCorO42r2onbvqfNz9ijGU/8eDY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.65.90.4' (RSA) to the list of known hosts.
(CISCO@10.65.90.4) Password: 

R1#

This works. And R2:

$ ssh CISCO@10.65.90.5
The authenticity of host '10.65.90.5 (10.65.90.5)' can't be established.
RSA key fingerprint is SHA256:uFKWs7/6Nmp6emIys8X9D4c/kOCBwwiy07WwqfCH7kE.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.65.90.5' (RSA) to the list of known hosts.
(CISCO@10.65.90.5) Password: 

R2#

This works, too. I’m doing this not only to test connectivity but also so the control node saves the public keys for R1 and R2. If you don’t do this, Ansible will complain that it doesn’t trust the remote hosts. You only have to do this once.

It is possible to configure Ansible to ignore complaining about unknown hosts. That’s ok for a lab, but don’t do this for a production network. You want to make sure you are connecting to a legitimate target.

We also require Ansible-pylibssh, a Python library that provides SSH connectivity for Ansible. To install it, we first need to install the Python PIP package manager:

$ sudo apt install python3-pip

And then, we can install the ansible-pylibssh package:

$ sudo pip3 install ansible-pylibssh --break-system-packages

In our inventory.ini file, we’ll add two entries for our routers:

[routers]
R1 ansible_host=10.65.90.4 ansible_user=CISCO ansible_password=CISCO
R2 ansible_host=10.65.90.5 ansible_user=CISCO ansible_password=CISCO

[routers:vars]
ansible_connection=network_cli
ansible_network_os=ios

We added the IP addresses, usernames, and passwords for our routers. We also add two variables for the routers group:

  • ansible_connection=network_cli
  • ansible_network_os=ios

This tells Ansible to use the network_cli connection method when it wants to connect to devices in the “routers” group, and we use Cisco IOS for these devices.

In this example, I specified the password in clear text in the inventory file. This is fine for a lab, but don’t do this in production. Copying the public key from the control node to the routers is a bit more work than doing this for Linux servers, so I decided to keep it simple for now. Another option would be to use Ansible Vault for the password.

Now, we’ll create a playbook named ospf-configure-network.yml:

---
- name: Configure OSPF on Routers and Show OSPF Neighbors
  hosts: routers
  gather_facts: no
  tasks:
    - name: Ensure OSPF is configured
      ios_config:
        lines:
          - network 192.168.12.0 0.0.0.255 area 0
        parents: router ospf 1
        save_when: changed

    - name: Wait for OSPF neighbor to be FULL
      ios_command:
        commands:
          - show ip ospf neighbor
      register: ospf_neighbors
      until: "'FULL' in ospf_neighbors.stdout[0]"
      retries: 30
      delay: 10

    - name: Display OSPF neighbors
      debug:
        var: ospf_neighbors.stdout_lines

This playbook has three tasks:

  • Task 1 adds the router ospf 1 command and the network 192.168.12.0 0.0.0.255 area 0 command.
  • Task 2 runs the show ip ospf neighbor command every 10 seconds for 30 tries until the output contains “FULL”.
  • Task 3 prints the output of the show ip ospf neighbor command.

Let’s execute it:

$ ansible-playbook -i inventory.ini ./playbooks/ospf-configure-network.yml

PLAY [Configure OSPF on Routers and Show OSPF Neighbors] ************************************************************************************************************

TASK [Ensure OSPF is configured] ************************************************************************************************************************************
[WARNING]: To ensure idempotency and correct diff the input configuration lines should be similar to how they appear if present in the running configuration on
device
changed: [R1]
changed: [R2]

TASK [Wait for OSPF neighbor to be FULL] ****************************************************************************************************************************
FAILED - RETRYING: [R1]: Wait for OSPF neighbor to be FULL (30 retries left).
FAILED - RETRYING: [R2]: Wait for OSPF neighbor to be FULL (30 retries left).
FAILED - RETRYING: [R1]: Wait for OSPF neighbor to be FULL (29 retries left).
FAILED - RETRYING: [R2]: Wait for OSPF neighbor to be FULL (29 retries left).
FAILED - RETRYING: [R1]: Wait for OSPF neighbor to be FULL (28 retries left).
FAILED - RETRYING: [R2]: Wait for OSPF neighbor to be FULL (28 retries left).
FAILED - RETRYING: [R1]: Wait for OSPF neighbor to be FULL (27 retries left).
FAILED - RETRYING: [R2]: Wait for OSPF neighbor to be FULL (27 retries left).
ok: [R1]
ok: [R2]

TASK [Display OSPF neighbors] ***************************************************************************************************************************************
ok: [R1] => {
    "ospf_neighbors.stdout_lines": [
        [
            "Neighbor ID     Pri   State           Dead Time   Address         Interface",
            "192.168.12.2      1   FULL/DR         00:00:39    192.168.12.2    GigabitEthernet1"
        ]
    ]
}
ok: [R2] => {
    "ospf_neighbors.stdout_lines": [
        [
            "Neighbor ID     Pri   State           Dead Time   Address         Interface",
            "192.168.12.1      1   FULL/BDR        00:00:39    192.168.12.1    GigabitEthernet1"
        ]
    ]
}

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

There we go: We configured OSPF and received a message that the neighbor adjacency had been established.

Advantages

You have now seen some examples of Ansible for network automation. Let me give you some advantages that are specific to Ansible:

  • Easy to install: Installing Ansible takes a couple of minutes and you can use your own computer. No need to install a separate server with a huge list of requirements.
  • Agentless Architecture: No need to install agent software on target machines, which makes it easy to get started.
  • YAML syntax: YAML files are easy to read and write. You don’t need to learn a programming language.
  • Modules: There is a large collection of modules. You can configure or manage almost anything.
  • Inventory management: Configure hosts and groups in a local inventory file.
  • Secrets management: You can use Ansible Vault to store your secrets securely.

Disadvantages

Like any tool, Ansible also has some limitations and disadvantages. Let’s take a look:

  • Limited error handling: Ansible’s error handling capabilities are less sophisticated than other automation tools, making debugging more challenging.
  • Performance with Large Inventories: Ansible can experience performance issues when dealing with very large inventories, especially if the tasks are complex and involve a lot of hosts. This is because Ansible is agentless and operates over SSH, which can become a bottleneck.
  • Limited Concurrency: By default, Ansible runs in parallel but with a default fork limit (usually 5). This means it will run tasks on the first five hosts, wait, then do the next five hosts. If you need to push out changes to thousands of nodes, it can be time-consuming unless you manually adjust the forks setting.
  • No Native State Management: Unlike tools such as Terraform, Ansible does not have native state management. This can make tracking changes over time more complex, especially if you are managing dynamic inventories or complex dependencies.
  • Complexity in Error Handling and Debugging: Ansible’s error messages can sometimes be cryptic or not as informative as desired, which can complicate debugging. Additionally, handling errors gracefully can involve writing extra code.
  • YAML Syntax Limitations: While YAML is human-readable, it can also be prone to whitespace-related issues or syntax errors, leading to frustration, especially for those new to the language.
  • Limited rollback capabilities: Ansible doesn’t have built-in, comprehensive rollback features for failed operations.
  • GUI Management: Although there are graphical tools like Ansible Tower/AWX, Ansible itself is primarily command-line and YAML-based, which might not be as user-friendly for those preferring GUI-based management.

Alternatives

Because you can do so many things with Ansible, it can be helpful to understand how Ansible compares with some popular alternatives.

Shell and Bash Scripts

You can go crazy with (Power)shell and bash scripts for network automation. When you create your scripts, you’ll have to build in a lot of functionality that Ansible already does for you. For example, idempotency, managing multiple targets in parallel, error reporting, etc. Some functionality Ansible provides might be difficult to replicate in shell and bash scripts.

Shell and bash scripts are still helpful if you need to fix something quickly on a single machine without Ansible’s overhead. You can also run shell and bash scripts in Ansible tasks, so it’s possible to combine the two. This is useful when you need to do something and there is no Ansible module available.

Python

You could use Python scripts for network automation. There are some similarities with shell and bash scripts. Without libraries, you’ll have to write most of the code yourself. You’ll need to write code that connects to a target, retries the connection if needed, executes code, checks whether it was successful, retry if required, etc.

Of course there are some options that do some of the heavy lifting for you. One example is Nornir is a Python-based automation framework.

Using a programming language has the advantage of using more advanced features such as functions, classes, objects, loops, etc. YAML is limited. The disadvantage is that the learning curve of Python or any other programming language is higher than learning how to write a YAML playbook in Ansible.

Terraform

Ansible is primarily a configuration management tool, while Terraform is for infrastructure provisioning. There is overlap because they can do similar things. You can provision resources using Ansible and make some configuration changes with Terraform.

Ansible uses YAML, while Terraform uses its proprietary HashiCorp Configuration Language (HCL).

These two tools are a great combination, though. You can use Terraform for provisioning, and once your resources are up and running, you can use Ansible to configure everything. It’s even possible to run Ansible from Terraform.

Docker

You can’t compare Ansible and Docker directly because these are completely different tools, but since containers have become so popular, you see the comparison sometimes. With the move to containerization, we don’t install as many servers as we used to. Before containerization, you would install a server, all required packages, copy configuration files, etc. With containers, we only install the container engine and run containers on top of it. Docker is the most popular option, and you can create a Docker container with a Dockerfile. This file specifies what you want to install for the container and how to build it.

When it comes to Ansible, some people might argue that we don’t need Ansible as much anymore, and in a way, that is true. However, you can use Ansible for many other things besides installing servers. Of course, you can also use Ansible to create, deploy, and manage containers.

Kubernetes

When you read about containers, you’ll almost automatically run into Kubernetes. Kubernetes is a container orchestration tool. Its job is to deploy, scale, and terminate containers. When you use containers on a large scale, you won’t use a tool like Ansible for container orchestration. You can still use Ansible to manage and update the Kubernetes cluster itself.

Conclusion

And that’s it. In this lesson, you learned what Ansible is:

  • The main components:
    • Control Node
    • Managed Nodes
    • Inventory
    • Playbooks
    • Roles
    • Modules
    • Plugins
    • Secrets Management
  • How to install Ansible and run playbooks.
  • The advantages and disadvantages of Ansible.
  • A comparison of Ansible with alternative tools.

Ansible is a useful tool for many things. It has a low learning curve, installation is quick, and because you don’t need to install agents, you can get started in a couple of minutes. Keep in mind that even though you can do many things with Ansible, it’s not always the best tool for the job.

I primarily use Ansible for configuration management because that’s what it excels at. I avoid using it for infrastructure management because there are better tools such as Terraform, Amazon AWS Cloudformation, AWS CDK, etc. Ansible usually plays a role somewhere, though. For example, with Cloudformation, you still need to apply the YAML files. You can do this from the CLI or the GUI, but you can also use Ansible. The same thing with Terraform. You can use it to provision infrastructure resources, but Ansible is quite helpful for configuration once you have your infrastructure.

You now know the basics of how to get started with Ansible. Give it a try and see if you like it. Start with some simple examples and add complexity as you go.

I hope you enjoyed this lesson. If you have any questions, please leave a comment.


Ask a question or start a discussion by visiting our Community Forum