Before we install anything or write our first playbook, I want to spend a bit of time on why network automation matters and why Ansible is a good place to start. If you already know why you're here, feel free to skim this chapter, but I think it's worth understanding the problem before jumping into the tool.
The Way We've Always Done It
For most of us, managing a network looks something like this. You open a terminal, SSH into a device, run a few show commands, paste in some config from a text file, verify it, save it, and move on to the next device. Repeat that for as many devices as you need to change.
There's nothing wrong with this approach when you're dealing with a handful of devices. I've done it for years, and so have most network engineers. The problems start when the network grows.
Imagine you need to change the NTP servers on 200 switches or configure Radius authentication on hundreds of devices. Doing that manually means logging into each switch, removing the old servers, adding the new ones, and saving the config. Even if each device only takes two minutes, that's almost seven hours of repetitive work. And that's assuming nothing goes wrong.
I started using Ansible many years ago, and it all began with a simple task. I had to configure new syslog servers on a number of Cisco ASA firewalls. I did it by hand, logging into each firewall one by one and pasting in the same config. It worked, but I didn't enjoy it at all.
Some time later, I had to do the same thing again, but this time for SNMP. That's when I got fed up and started looking for a better way to do it. This is where my automation journey really started, and Ansible was the first tool I picked up.
Problems with Manual Changes
Time is the obvious problem, but it's not the only one.
- Human error - When you're doing the same task over and over, it's easy to make a typo, paste the wrong config, or log into the wrong device. Most of us have caused an outage at some point because of a simple mistake.
- Inconsistency - Over time, devices that should have the same config start to drift apart. One switch has a slightly different SNMP setting, another has an old NTP server that was never removed. Nobody knows which one is correct.
- No record of what changed - Unless you're very disciplined, it's hard to know exactly what was changed, when, and by whom.
- Hard to review - Before a change, someone usually reviews a text file or a change request. That review doesn't guarantee that what's actually pushed matches what was approved.
What Is Network Automation?
Network automation simply means using tools or code to do the tasks we would normally do by hand. That's it. It doesn't have to be complicated, and you don't need to automate everything on day one.
Some of the most useful things you can automate are actually read-only tasks, such as:
- Backing up device configs every night
- Collecting show command output from hundreds of devices
- Checking that every device has the correct NTP, SNMP, or AAA settings
- Running pre and post checks before and after a change
These are a great place to start because they can't break anything, and they give you something useful straight away.
I also want to mention that automation isn't about replacing network engineers. You still need to understand OSPF, BGP, VLANs, and everything else. Automation just takes the repetitive work off your plate, so you can spend more time on the things that actually need your attention.
Where Does Ansible Fit?
There are plenty of ways to automate a network. You can write Python scripts using libraries like Netmiko or NAPALM, use frameworks like Nornir, or use tools like Terraform for certain platforms. They're all good options, and you'll probably end up using more than one of them over time.
Ansible is an open-source automation tool, now maintained by Red Hat. It was originally built to manage servers, but over the years it has gained very good support for network devices from most of the major vendors.
With Ansible, you describe what you want to do in a file called a playbook, which is written in YAML. Ansible then connects to your devices and runs those tasks for you. Here's a very simple example that adds two NTP servers to a group of Arista switches.
---
- name: Configure NTP servers
hosts: arista_switches
gather_facts: false
tasks:
- name: Add NTP servers
arista.eos.eos_config:
lines:
- ntp server 10.10.10.10
- ntp server 10.10.10.11
Don't worry about understanding every line right now, we'll go through all of this in later chapters. The point is that it's readable even if you've never seen Ansible before.
Why Ansible for Network Engineers?
Here are the main reasons I think Ansible is a good starting point.
- You don't need to be a programmer - Playbooks are written in YAML, which is much easier to read and write than Python. You can be productive without knowing how to write code.
- It's agentless - You don't need to install anything on your network devices. Ansible connects using SSH or the device's API, which is how you already manage them anyway.
- Multi-vendor support - Cisco, Arista, Juniper, Palo Alto, Fortigate and many others provide Ansible collections, so you can use one tool across your whole network.
- Large community - Because Ansible is so widely used, it's easy to find examples, documentation, and help when you get stuck.
- Your server team probably already uses it - In many organizations, Ansible is already used by the server or cloud teams. Using the same tool makes it easier to work together and share knowledge.
- Easy to share and track - Playbooks are just text files, so you can store them in Git, review changes before running them, and see the full history of what changed.
Where Ansible Falls Short
To be fair, Ansible isn't perfect, and I don't want to pretend it is.
- It can be slow - For very large networks, Ansible can feel slow compared to tools like Nornir or Terraform.
- Complex logic gets messy - YAML is great for simple tasks, but once you need a lot of conditions, loops, and data manipulation, playbooks can become hard to read. At that point, Python is often the better option.
- Debugging can be frustrating - Error messages aren't always clear, especially when you're starting out.
None of these are reasons to avoid Ansible. They're just things to be aware of. For most network engineers getting into automation, Ansible covers the majority of day-to-day tasks, and it's a great way to build the habits and mindset you'll need for any automation tool later on.
Prerequisites
To follow along, you'll of course need some network devices in your lab. I wouldn't recommend testing any of this on live production devices. Obviously, you don't want to break things, and I don't want to get the blame for it. You can use EVE-NG, GNS3, Containerlab, Netlab, CML, or whatever works best for you.
You'll also need a control node. The control node is simply the machine where Ansible is installed and where you run your playbooks from. Ansible connects from the control node to your network devices, runs the tasks, and reports back the results. Nothing needs to be installed on the network devices themselves.
The control node can be your laptop, a MacBook, or a Linux VM. Ansible runs natively on Linux and macOS. If you're on Windows, Ansible doesn't run natively, but you can use WSL (Windows Subsystem for Linux), which gives you a Linux environment inside Windows and works perfectly fine for this.
A few other things you'll need:
- Network reachability - Your control node needs to reach the management IPs of your lab devices over SSH (or HTTPS for API-based platforms like Palo Alto).
- A text editor - Any editor will do, but I recommend VS Code. It has good YAML support and extensions that make writing playbooks much easier.
- Basic Linux knowledge - You don't need to be an expert, but you should be comfortable moving around directories, editing files, and running commands from the terminal.
- Networking knowledge - This series assumes you already understand the basics like VLANs, interfaces, and routing protocols. We're here to automate them, not learn them from scratch.
Setting up Your Lab
To follow along with this book, you'll need some network devices to work with. It doesn't matter which platform you use. EVE-NG, GNS3, Cisco CML, Containerlab, or Netlab will all work fine, so pick whichever you're most comfortable with. Just please don't test any of this on production devices.
Personally, I use Containerlab and Netlab for all my labs, and that's what I'll be using throughout this book. I've already covered both of them in detail on my blog, so rather than repeating everything here, I'll leave the links below if you want to set up the same environment.
- https://www.packetswitch.co.uk/containerlabs-intro/
- https://www.packetswitch.co.uk/netlab-the-fastest-way-to-build-network-labs/
- https://www.packetswitch.co.uk/network-labs-on-a-budget/
If you already have a lab up and running, you can skip ahead to the next section.
The Control Node
The control node is the machine where Ansible is installed and where you run your playbooks from. When you run a playbook, Ansible connects from the control node to your network devices, runs the tasks, and shows you the results. You don't need to install anything on the network devices themselves.
The control node can be your laptop, a MacBook, or a Linux VM. In my case, I use my MacBook as the control node, so that's what I'll be using throughout this book. We'll cover how to install Ansible on Linux, macOS, and Windows in the next section.
The most important thing is that your control node can reach the management IPs of your network devices. For most devices, Ansible connects over SSH. For API-based platforms like Palo Alto, it connects over HTTPS instead.
Before moving on, it's worth doing a quick check from your control node. Make sure you can ping each device and SSH into it manually.
ping 192.168.100.11
ssh admin@192.168.100.11
If you can log in to your devices from the control node, Ansible will be able to as well. If you can't, it's much easier to fix it now than to troubleshoot it later through Ansible.
Installing Ansible
Before we install anything, there are two things worth explaining. The difference between ansible-core and the ansible package, and why we'll use a Python virtual environment.
ansible-core vs ansible
When you install Ansible using pip, you'll see two options.
- ansible-core - The main engine of Ansible, with a small set of built-in modules. It doesn't include any vendor modules.
- ansible - Includes ansible-core plus a large set of community and vendor collections, including Cisco, Arista, Juniper and many more.
For this book, I'm going to install the ansible package because it includes most of the collections we need. If you prefer to keep things minimal, you can install ansible-core and only add the collections you need, which we'll cover in the next section.
Python Virtual Environment
Ansible is written in Python and installed using pip. Rather than installing it system-wide, I recommend using a Python virtual environment. A virtual environment is simply an isolated folder with its own Python packages, so whatever you install there won't interfere with other Python packages on your system.
This also makes it easy to have different versions of Ansible for different projects, and if something breaks, you can just delete the folder and start again.
Create a Project Directory
Before installing anything, let's create a directory for our project. This is where we'll keep everything related to Ansible, including the virtual environment, inventory, playbooks, and config files. You can create it anywhere you like, such as your home directory. I'm going to call mine ansible_v1.
mkdir ansible_v1
cd ansible_v1
From now on, all the commands in this chapter should be run from inside this directory. When we create the virtual environment in the next step, it will be created as a folder called venv inside the project directory.
I like keeping the virtual environment inside the project directory because everything stays in one place. If you ever want to start fresh, you can just delete the venv folder and create it again without affecting anything else.
Linux
On Ubuntu or Debian, first make sure Python, pip, and the venv module are installed.
sudo apt update
sudo apt install python3 python3-pip python3-venv
Next, create a virtual environment, activate it, and install Ansible.
python3 -m venv venv
source venv/bin/activate
pip install ansible
Once the virtual environment is activated, you'll see (venv) at the start of your prompt. Every time you open a new terminal, you'll need to activate it again using source venv/bin/activate. To exit the virtual environment, simply run deactivate.
macOS
macOS comes with Python, but I recommend installing a newer version using Homebrew.
brew install python
From there, the steps are the same as Linux.
python3 -m venv venv
source venv/bin/activate
pip install ansible
Windows
Ansible doesn't run natively on Windows. Instead, you can use WSL (Windows Subsystem for Linux), which gives you a full Linux environment inside Windows. Open PowerShell as Administrator and run the following command.
wsl --install
This installs WSL with Ubuntu by default. Once it's done, restart your computer, open Ubuntu from the Start menu, and follow the Linux steps above.
Example from My MacBook
Here's what this looks like on my MacBook. I created a folder called ansible_v1 for this book, and everything is installed inside it.
ansible_v1 ❯ python --version
zsh: command not found: python
ansible_v1 ❯ python3 --version
Python 3.14.4
ansible_v1 ❯ python3 -m venv venv
ansible_v1 ❯ source venv/bin/activate
ansible_v1 ❯ pip install ansible
You might have noticed that running python returned an error, while python3 worked fine. This is because of the old Python 2 vs Python 3 split. For many years, python referred to Python 2 and python3 referred to Python 3. Python 2 is no longer supported, and on macOS and most modern Linux distributions, the python command either doesn't exist or isn't set up by default. So, outside of a virtual environment, I always use python3.
Once you activate the virtual environment, both python and python3 point to the same Python version inside the virtual environment, so you can use either one. The same applies to pip and pip3.
Verify the Installation
To confirm everything is installed correctly, run the following command.
ansible --version
ansible --version
ansible [core 2.21.4]
config file = None
configured module search path = ['/Users/suresh/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /Users/suresh/Documents/code/ebooks/ansible_ebook/ansible_v1/venv/lib/python3.14/site-packages/ansible
ansible collection location = /Users/suresh/.ansible/collections:/usr/share/ansible/collections
executable location = /Users/suresh/Documents/code/ebooks/ansible_ebook/ansible_v1/venv/bin/ansible
python version = 3.14.4 (main, Apr 7 2026, 13:13:20) [Clang 21.0.0 (clang-2100.0.123.102)] (/Users/suresh/Documents/code/ebooks/ansible_ebook/ansible_v1/venv/bin/python3.14)
jinja version = 3.1.6
pyyaml version = 6.0.3 (with libyaml v0.2.5)
You should see the Ansible version, the Python version, and the location of the config file being used. Make sure this command works before moving on to the next section. If you get stuck, feel free to reach out to me, and I'm happy to help.
Creating Your First Project
We already have our project directory and the virtual environment. Now we need to add a few files so Ansible knows which devices to connect to and what to do on them. To get a basic project working, you only need three files.
- ansible.cfg - Project settings
- inventory - The list of devices and how to connect to them
- playbook - The tasks you want to run
Here's what the project directory will look like once we're done.
ansible_v1/
├── venv/
├── ansible.cfg
├── inventory.yml
└── show_version.yml
ansible.cfg
The ansible.cfg file holds the settings for your project. Ansible looks for this file in the following order and uses the first one it finds.
- ANSIBLE_CONFIG - An environment variable pointing to a specific config file
- ansible.cfg - In the current directory, where you're running the command from
- ~/.ansible.cfg - In your home directory
- /etc/ansible/ansible.cfg - The system-wide config file
Ansible only uses one config file and doesn't merge settings from multiple files. Since the current directory is checked before the home directory and the system-wide file, we create ansible.cfg inside the project directory, so the settings only apply to this project.
[defaults]
inventory = inventory.yml
host_key_checking = False
- inventory - Tells Ansible where to find the inventory file, so you don't have to specify it every time you run a playbook.
- host_key_checking - When you SSH into a device for the first time, you're usually asked to accept its host key. Ansible can't answer that prompt, so the connection fails. Setting this to
Falseskips the check. This is fine for a lab, but in production, you'd want to keep it enabled.
If you run ansible --version again from inside the project directory, you'll see that the config file now points to this ansible.cfg.
Inventory
The inventory file tells Ansible which devices to manage and how to connect to them. Ansible supports both INI and YAML formats for inventory. I prefer YAML, so that's what we'll use in this book.
---
all:
hosts:
r1:
ansible_host: 192.168.202.101
ansible_network_os: cisco.ios.ios
ansible_connection: ansible.netcommon.network_cli
ansible_user: admin
ansible_password: admin
The structure of the inventory looks simple since we only have a single device. all is a built-in group that includes every device in the inventory, and every YAML inventory starts with it. Under hosts, we list our devices. In this case, we only have one device, r1. If we had more devices, we would simply add them under hosts, just like r1.
Under r1, we define its IP address and how Ansible should connect to it.
- ansible_host - The management IP of the device
- ansible_network_os - Tells Ansible what type of device it is
- ansible_connection - How Ansible connects to the device.
network_climeans it connects over SSH and runs CLI commands, just like you would. - ansible_user and ansible_password - The credentials used to log in
Storing passwords in plain text isn't a good idea, but it keeps things simple for now. We'll fix this later using Ansible Vault. We'll also cover inventory, including groups, in much more detail in its own chapter.
Privileged EXEC Mode
When you log in to a Cisco device, you usually land in one of two modes. If you see a prompt ending with >, such as r1>, you're in user EXEC mode, and you need to type enable before you can run most show commands or make any config changes. If you see a prompt ending with #, such as r1#, you're already in privileged EXEC mode.
In my lab, the admin user logs straight into privileged EXEC mode, so Ansible doesn't need to do anything extra. But in a lot of networks, users land in user EXEC mode first. If that's the case for you, Ansible won't be able to run certain commands or enter config mode, and your playbook will fail.
To fix this, you need to tell Ansible to run enable after logging in, just like you would manually. You can do this by adding three more variables to your inventory.
---
all:
hosts:
r1:
ansible_host: 192.168.202.101
ansible_network_os: cisco.ios.ios
ansible_connection: ansible.netcommon.network_cli
ansible_user: admin
ansible_password: admin
ansible_become: true
ansible_become_method: enable
ansible_become_password: admin
- ansible_become - Tells Ansible that it needs to elevate its privileges after logging in.
- ansible_become_method - How to elevate the privileges. For network devices, this is
enable. - ansible_become_password - The enable password. You only need this if your device has an enable password or secret configured.
If you can log in manually and you're taken straight to the # prompt, you don't need this. But if you see a > prompt, or your playbooks fail when entering config mode, try adding these variables.
Playbook
Finally, we need a playbook. Create a file called show_version.yml with the following content.
---
- name: Quick test
hosts: all
gather_facts: false
tasks:
- name: Run show version
cisco.ios.ios_command:
commands:
- show version | incl RELEASE
register: output
- name: Display output
ansible.builtin.debug:
var: output.stdout_lines
This playbook connects to every device in the inventory, runs show version, saves the output, and then prints it to the screen. Again, don't worry about understanding every line for now, we'll go through playbooks in detail later.
To run a playbook, we use the ansible-playbook command followed by the name of the playbook file. Make sure you're inside the project directory and that your virtual environment is activated.
ansible-playbook show_version.yml
If everything is set up correctly, you should see something like this.
ansible-playbook show_version.yml
PLAY [Quick test] *************************************
TASK [Run show version] *******************************
ok: [r1]
TASK [Display output] *********************************
ok: [r1] => {
"output.stdout_lines": [
[
"Cisco IOS Software [Dublin], Linux Software (X86_64BI_LINUX-ADVENTERPRISEK9-M), Version 17.12.1, RELEASE SOFTWARE (fc5)"
]
]
}
PLAY RECAP ********************************************
r1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
If you see this output, congratulations, you've just run your first Ansible playbook against a network device. The output might look like a lot at first, but it follows a specific structure every time, so let's go through it.
- PLAY - Shows the name of the play that's running. This is the
namewe defined at the top of the playbook,Quick test. - TASK - Showing the task name and the result for each device. We have two tasks,
Run show versionandDisplay output. - ok: [r1] - The task ran successfully on
r1and didn't change anything on the device. Since we're only running a show command, we'd expect to seeokhere. - Debug output - Under the
Display outputtask, we see the output ofshow version. Each line of the command output is shown as a separate item in a list. You might notice there's a list inside another list. This is becauseios_commandcan run multiple commands, and each command gets its own list. Since we only ran one command, there's only one.
At the end, you'll see the PLAY RECAP, which is a summary of what happened on each device.
- ok - The number of tasks that ran successfully without making changes
- changed - The number of tasks that made a change on the device
- unreachable - Ansible couldn't connect to the device
- failed - The number of tasks that failed
- skipped, rescued, and ignored - We'll cover these later in the book
In our case, both tasks ran successfully, and nothing was changed on the device, so we see ok=2 and changed=0.
You may also notice a warning saying ansible-pylibssh not installed, falling back to paramiko. This simply means Ansible couldn't find ansible-pylibssh and used paramiko instead. It's safe to ignore, and if you want to get rid of it, just install ansible-pylibssh.
Common Issues
If the playbook fails, here are some of the most common reasons. Adding -vvv to the end of the command gives you much more detail about what's going wrong.
- Host key checking errors - Make sure
host_key_checking = Falseis set inansible.cfg, and that you're running the playbook from inside the project directory. - Authentication failed - Double-check the username and password in your inventory. Try logging in manually with SSH using the same credentials.
- Connection timeout - Make sure the IP address in
ansible_hostis correct and that your control node can reach the device. - ansible.cfg is ignored on WSL - If your project is on the Windows file system (under
/mnt/c), Ansible ignores theansible.cfgfile for security reasons. Move the project into your Linux home directory instead. - SSH library not installed - If you didn't install
ansible-pylibssh, or it failed to install, Ansible falls back toparamiko. Ifparamikoisn't installed either, you'll see the following error.
fatal: [r1]: FAILED! => {"changed": false, "msg": "paramiko is not installed: No module named 'paramiko'"}
To fix this, install either ansible-pylibssh or paramiko inside your virtual environment, then run the playbook again.
pip install ansible-pylibssh
pip install paramiko
On macOS, especially on Apple Silicon (M-chip) Macs, you may run into issues when trying to install ansible-pylibssh.
pip install ansible-pylibssh
ERROR: Could not build wheels for ansible-pylibssh,
which is required to install pyproject.toml-based projects
To fix this, first install libssh using Homebrew, then install ansible-pylibssh using the following command.
brew install libssh
CFLAGS="-I $(brew --prefix)/include -I ext -L $(brew --prefix)/lib -lssh" pip install ansible-pylibssh
Wrapping Up
That's it for this chapter. We set up the control node, installed Ansible inside a virtual environment, created our first project, and ran a playbook against a real network device.
If you've managed to get this far, that's very good progress. Getting the environment set up and making that first connection to a device is often the hardest part when you're starting out. From here on, it's mostly about learning how to do more with what you already have.
Again, don't worry too much if some of the files and terms in this chapter didn't fully make sense. We'll cover the inventory, playbooks, modules, and everything else in much more detail in the coming chapters.


