AnsibleXLII · Ansible Beyond Linux ServersBeyond Linux servers
Why network modules run on the controller
What you'll learn
- Explain why network modules execute on the control node rather than the managed node
- Choose between the network_cli, netconf and httpapi connection types and state what each requires
- Set the inventory variables a network group needs, including ansible_network_os
- Predict where a configuration backup file lands and why that is not the device
- Recognise the controller-side resource cost that a fleet of network devices imposes
Prerequisites
Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11
Everything you know about how a task executes is wrong on a switch.
The model this course has taught so far is specific and mechanical: Ansible assembles the module into a self-contained Python payload, copies it to the managed node, runs it there with the node’s own Python interpreter, and reads back a JSON document. The module does its thinking on the target. The controller is a dispatcher.
A network device usually has no shell you can drop a file into, no general-purpose Python interpreter, and no filesystem you are invited to write to. It has a command-line interface, or an XML-over-SSH interface, or an HTTP API. None of those will run your module.
So Ansible inverts the model. For network platforms, the module runs on the control node. What crosses the wire is not a Python payload; it is the commands or API calls the module decided to send, and the raw text or structured data the device sent back.
That single inversion explains almost every difference in this part. Where
the backup file lands. Why escalation is enable and not sudo. Why
gather_facts in its usual form does nothing useful. Why a hundred
switches cost the controller far more than a hundred Linux servers. Get the
inversion, and the rest is consequence.
The documentation states it plainly
From the upstream page on how network automation differs:
Network modules do not run on the managed nodes.
and
[they] are executed on the Ansible control node, where
ansibleoransible-playbookruns.
There is no subtlety to recover here. The device is a peer you talk to, not a machine you run code on.
The connection plugin is the whole interface
Because nothing executes on the device, the connection plugin stops being a transport detail and becomes the entire integration surface. It is what knows how to log in, how to recognise a prompt, how to page through output, how to enter configuration mode, and how to tell a successful command from an error message that the device politely printed instead of exiting non-zero.
Three persistent connection types cover the field:
| Connection | What it speaks | Typical use |
|---|---|---|
ansible.netcommon.network_cli | The device CLI, over SSH | The majority of switches, routers and firewalls |
ansible.netcommon.netconf | NETCONF: XML over SSH | Platforms with a modelled configuration store |
ansible.netcommon.httpapi | The device HTTP or HTTPS API | Controllers, appliances and API-first platforms |
All three are persistent connections, and all three require you to say
which platform you are talking to. That is what ansible_network_os is for.
$ ansible-doc -t connection -lansible.builtin.local execute on controller
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 WinRMnetwork_cli, netconf and httpapi live in the ansible.netcommon
collection, and the modules for a given vendor live in that vendor’s own
collection. Both are dependencies you install and pin deliberately, the same
way this course has treated every other collection:
ansible-galaxy collection listThat matters operationally more than it sounds. A network playbook that works on one engineer’s laptop and fails on the controller with a “couldn’t resolve module/action” error is nearly always a missing collection, not a broken play.
ansible_network_os is mandatory, and it is not documentation
Set it wrong and the run does not politely correct you. ansible_network_os
selects the platform-specific machinery the connection plugin loads: the
terminal plugin that knows what this device’s prompts and error strings
look like, the cliconf plugin that knows how to enter and leave
configuration mode, and the code that knows whether this platform commits or
saves or does neither.
Point that at the wrong platform and the connection plugin will confidently
misread a real prompt as a failure, or a real error as success. The
resulting symptom — a task that hangs until timeout, or one that reports
ok having done nothing — has no obvious connection to the variable that
caused it.
A network group in inventory therefore carries more connection variables than a Linux group ever does:
# inventory/network.yml
all:
children:
core_switches:
hosts:
core-sw-01:
ansible_host: 192.0.2.21
core-sw-02:
ansible_host: 192.0.2.22
vars:
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: vendor_namespace.platform.os_name
ansible_user: automation
ansible_password: REPLACE_ME
ansible_become: true
ansible_become_method: enable
ansible_become_password: REPLACE_ME
Two notes on that block before anything else.
The credentials belong in a vault, not in the file. They are written
inline here so you can see the shape; every one of those REPLACE_ME
values is a vault-encrypted variable in real life, and the vault lessons in
Part XXI are the reference.
ansible_network_os takes a fully-qualified platform name from the
collection that supports your device. Look it up on the platform options
page rather than guessing from the product name — the string is a
collection identifier, not a brand.
Escalation is enable, not sudo
A Linux managed node escalates by running a program: sudo, su, doas.
The privilege boundary is a process boundary.
A network device escalates by changing the mode of the session you are already in. There is no second process. You are the same login; the device has decided to accept a different class of command from you.
Ansible models this with the same keywords and a different method:
ansible_become: true
ansible_become_method: enable
ansible_become_password: REPLACE_ME
The practical differences are worth stating because they trip up people who
know become well:
- It is a session property, not a task property. Once the session is
privileged it stays privileged; there is no per-task descent back to
unprivileged mode the way
become: falsegives you on Linux. - A wrong
enablesecret usually fails as a timeout, not as an authentication error, because the device prints a prompt the connection plugin was not expecting and then waits. - Not every platform has an enable mode at all. Some grant privilege at
login based on the account. On those, setting
ansible_becomeis at best a no-op and at worst a hang.
Facts do not come from setup
ansible.builtin.setup — the module gather_facts: true runs — is a Python
program that inspects a Unix-like system from the inside. It cannot run on a
device that will not execute Python, and there is nothing for it to inspect
even if it could.
Network platforms supply their own facts module in the vendor collection,
and it returns a different shape: interfaces, VLANs, neighbours, the running
configuration, the software image. Not ansible_distribution and
ansible_mounts.
The operational consequence is a small one that costs an afternoon the first time:
- name: Switch configuration audit
hosts: core_switches
gather_facts: false # this is not optional politeness
tasks:
- name: Collect platform facts the platform way
vendor_namespace.platform.os_facts:
gather_subset:
- config
register: device_facts
Leaving gather_facts: true on a network play does not merely waste time.
Depending on the platform and the connection plugin it produces a confusing
failure at the very first step of the play, before any of your logic runs,
and the error text talks about interpreters and modules rather than about
the switch.
Backups land on the controller
The configuration modules in ansible.netcommon and the vendor collections
generally accept a backup option. It is the single most useful safety
feature in network automation and it is routinely misunderstood.
backup: true does not ask the device to save a copy of its
configuration somewhere on the device. It retrieves the current
configuration and writes it to a directory on the control node, next to
your playbook, before applying the change.
That is the correct design, and it follows directly from the execution inversion: the module is running on the controller, so the controller is where it can write a file.
ls -l ./backup/
find . -name '*.cfg' -newermt '-1 hour'Three consequences to design around:
- The controller now holds device configurations. Those files contain ACLs, community strings, tunnel endpoints and sometimes hashed local credentials. They are sensitive, and Part XLVII treats the controller as the security boundary it has become.
- Nothing rotates them for you. A daily backup play against 200 devices produces 73,000 files a year in a directory nobody chose a retention policy for.
- They are your rollback. If the change goes wrong, the file written moments before is the only copy of the pre-change state you are guaranteed to have — the device may not keep one.
The controller becomes the bottleneck
On a Linux fleet, work is distributed by construction. Each managed node runs its own module, burning its own CPU. The controller marshals.
On a network fleet, every module for every device runs on one machine.
Fifty devices under the default fork limit of 5 means five concurrent
persistent connections, each with a helper process, each parsing device
output on the controller’s CPU. Raising forks to 50 to speed it up raises
the controller’s memory and CPU load fifty-fold, and there is no second
machine to absorb it.
ansible-config dump --only-changed | grep -i forksThe sizing conversation for a network estate is therefore a conversation about the controller, not about the devices. Part XXXIV’s runtime-budget lesson applies with the multiplier moved: on Linux you are usually buying parallelism cheaply, and here you are buying it out of one machine’s RAM.
Knowledge check
Knowledge check · 4 questions
Q1. A colleague argues that network automation would be simpler if Ansible just copied the module to the switch like it does for Linux. What is the accurate response?
Q2. A network play fails with an error about resolving the module or action, on the shared controller only. It works on the author machine. What is the first thing to check?
Q3. Which of these are direct consequences of network modules executing on the control node? Select all that apply.
Q4. Setting ansible_become: true with ansible_become_method: enable starts a privileged child process on the network device, in the same way sudo does on a Linux host.
Passing score: 75%. Answers are checked in this browser.