Lesson Contents
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
andapt upgrade
on many Ubuntu servers.
- run
- 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.
- Run
- 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.
- Red Hat 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: Interact 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.
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
- Debian-based:
- Windows:
- Use Windows Subsystem for Linux (WSL) and run
sudo apt install ansible
- Use Windows Subsystem for Linux (WSL) and run
- 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: "/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: "/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.