AnsibleLI · Custom Modules and Tool SelectionChoosing the right tool
When Ansible is the wrong tool
What you'll learn
- Name five problem classes Ansible is structurally unsuited to, and the failure each produces
- Recognise the symptoms of a fit problem before it becomes an architecture
- Choose the tool class that fits each of the five, without abandoning Ansible for the parts it does well
- Defend a "this is the wrong tool" judgement with a specific mechanism rather than a preference
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
Part II listed what Ansible does not do: no daemon, no continuous enforcement, no state database, no cross-host dependency graph, no rollback, no guarantee about unreachable hosts.
This lesson takes those absences and turns them into a usable judgement, because a reader who can say “this is the wrong tool, and here is the mechanism that makes it wrong” has something the documentation will never give them. Documentation describes what a tool does. Nobody upstream is incentivised to write down what it should not be asked to do.
Five problem classes follow. In each, the mismatch is structural — it is not a missing feature that a future release might add, and it is not fixable by writing the playbook better.
1. High-frequency application events
The requirement. Something happens — a message arrives, a deployment completes, a threshold is crossed — and a response must run. Possibly hundreds of times an hour.
Why Ansible does not fit. Ansible is a run-to-completion push tool. There is no daemon listening for anything. A playbook starts, does work, and exits. Nothing is watching between runs.
What you build if you ignore this. A polling loop. A cron entry or timer runs a playbook every minute; the playbook checks whether the condition holds and acts if it does. Which gives you:
- Latency measured in minutes, bounded below by your polling interval and above by the run duration.
- Startup cost per poll. Inventory parsing, fact gathering and connection setup happen every time, whether or not there is anything to do. At one-minute intervals over three hundred hosts this is a substantial standing load on the controller for mostly-empty runs.
- Overlapping runs. The run takes longer than the interval, a second starts, and now two runs are converging the same hosts simultaneously. Part XXXV’s fleet-scheduling discussion applies, and the locking is your problem.
- No event identity. The poll sees a state, not an event. Two occurrences between polls are indistinguishable from one.
What to reach for. An event-driven system. Ansible’s own answer is Event-Driven Ansible with rulebooks, which listens to sources and triggers playbooks on matched conditions — the right shape, and it is still a playbook at the end so the blast-radius discipline from this course applies unchanged. Part XXXIX covers it. For application-level events, the answer is more often the application’s own queue, webhook handler or message consumer.
2. Very large data transfers
The requirement. Move tens or hundreds of gigabytes to or from many hosts: a dataset, a media library, a database dump.
Why Ansible does not fit. The copy module reads the file on the
controller and transfers it over the connection, per host. There is no
resume, no delta transfer, no deduplication and no peer-to-peer
distribution. forks gives you parallelism bounded by the controller’s
CPU, memory and — critically — its uplink, which every one of those
transfers shares.
What you build if you ignore this. A run that takes eleven hours, saturates the controller’s network interface, and fails at hour nine on host 212 with a connection reset. There is no resume: rerunning starts every incomplete transfer again from zero. Meanwhile the controller cannot do anything else, because its uplink is full.
What to reach for.
ansible.posix.synchronizewrapsrsync, which gives you delta transfer and resume. This is the correct in-Ansible answer for medium data and it removes most of the pain.- An artefact store or object storage the hosts pull from, with Ansible orchestrating the pull rather than carrying the bytes. This is the right shape at scale: the transfer is N hosts pulling from a service built for it, in parallel, without the controller in the path.
- A CDN or peer-to-peer distribution for genuinely large fleets.
3. Full infrastructure provisioning with state
The requirement. Create and manage the infrastructure itself — networks, load balancers, virtual machines, DNS records — including knowing what exists and destroying what should not.
Why Ansible does not fit. There is no state file. Ansible has cloud modules and they work, but each one answers “make this thing exist” independently. Nothing anywhere records the set of resources this automation is responsible for.
What you build if you ignore this. Creation works fine — the modules are idempotent and a rerun does not duplicate resources. The failure arrives at deletion and drift:
- Removing a resource from the playbook does nothing. The resource keeps existing and keeps costing money, because no component knows it was ever managed.
- “What does this automation own?” is unanswerable except by reading the playbooks and hoping they are complete.
- Manual changes are invisible. A colleague resizes an instance in the console; nothing detects it, because there is nothing to compare against.
state: absentrequires you to remember. You must explicitly list what to destroy, which means maintaining by hand exactly the inventory a state file would have maintained for you.
What to reach for. A tool with a state file — Terraform, OpenTofu, Pulumi, CloudFormation — for the resources, and Ansible for what runs on them. This split is the mainstream architecture and it is not a compromise: provisioning wants declarative state reconciliation, configuration wants ordered convergence, and they are different problems.
4. Complex orchestration with conditional state machines
The requirement. A multi-step workflow with branches, waits, approvals, compensating actions and the ability to resume from where it stopped. Migrations and complex deployments look like this.
Why Ansible does not fit. A playbook is a sequence. when:,
block/rescue and meta: end_play give you conditionals and local
error handling, but there is no workflow state, no resumption point and
no way to ask “which step is this run on”.
What you build if you ignore this. The playbook grows a variable
called phase, a set of when: phase == 'x' conditions, and a state
file it reads at the start and writes at the end of each block. That is a
workflow engine, implemented in YAML, by you, with:
- No resumability. A run that fails at step 7 of 12 restarts from step 1 unless you built the resume logic — and the resume logic is the hard part of a workflow engine.
- Conditions nobody can review. Part LII lesson 6 is about exactly
this: when the
when:chains are deep enough, “which hosts does this task touch, in which circumstances” is unanswerable without running it. - No visibility. No UI, no status, no “where is this now” for an operator watching a two-hour migration.
What to reach for. A workflow engine — Argo Workflows, Temporal, Step Functions, or the workflow feature of AWX/Automation Platform, which is explicitly designed to chain job templates with conditional branches. In every case, the individual steps are still playbooks. You are not replacing Ansible; you are putting something above it that owns the sequencing.
5. Real-time monitoring
The requirement. Know the current state of the fleet, continuously, with alerting.
Why Ansible does not fit. A playbook run is a sample. It tells you what was true on the hosts it reached, at the moment it reached them. Between runs there is nothing.
What you build if you ignore this. A “monitoring” playbook on a five-minute timer that checks services and reports failures. Its properties:
- Five-minute blind spots, and an outage shorter than the interval is invisible.
- No history. Each run is independent; there is no series to graph, no trend, no baseline.
- Unreachable is ambiguous. Part XXIII established that an unreachable host is not a failed host. Your monitor cannot tell a dead machine from a firewall change from a slow SSH handshake.
- The monitor is a fleet-wide SSH storm every five minutes, and it competes with real automation for controller capacity.
What to reach for. A monitoring system: Prometheus, Zabbix, Nagios, or your platform’s native offering. Agents or exporters that run continuously and are designed to be sampled cheaply.
Where Ansible does belong in this story: deploying and configuring that monitoring, and running audits — point-in-time compliance checks, which are genuinely a sampling problem and which Part XXXVI builds on check mode. An audit that runs nightly and reports drift is a good use. The same playbook on a five-minute timer, alerting, is a bad monitor.
Knowledge check
Knowledge check · 5 questions
Q1. A team schedules a playbook every minute to respond to application events. Beyond latency, what is the most significant structural problem?
Q2. Which failures follow from using Ansible as the system of record for cloud infrastructure? Select all that apply.
Q3. Because a playbook can check service health on every host, a playbook on a short timer is an acceptable substitute for a monitoring system.
Q4. A migration playbook has grown a `phase` variable, a set of when: phase == conditions, and a state file it reads at the start and writes at the end of each block. What has been built?
Q5. A playbook distributes a 40 GB dataset to 200 hosts with the copy module and takes eleven hours, failing partway through. What is the best structural fix?
Passing score: 75%. Answers are checked in this browser.