AnsibleXLI · Service and Application DeploymentService and Application Deployment
The limits of Ansible as a deployment tool
What you'll learn
- Name the four capabilities a push-based convergence tool structurally cannot provide
- Explain why a rolling deploy is always a period of mixed versions and what that requires of the application
- Identify the concrete signals that a fleet has outgrown a push-based deployer
- Place the hand-off to an image pipeline, an orchestrator or a traffic layer correctly
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 in this part has been about making a push-based deployment as safe as it can be. This lesson is about where that ceiling is.
The four limits below are not missing features waiting for a release. They follow from what Ansible is: a tool that connects to hosts, applies a described state, and exits. A tool that exits cannot maintain anything, and a tool with no data path cannot route anything.
Knowing the ceiling is what stops you building an increasingly elaborate playbook to reach something the shape of the tool does not reach.
Limit one: no atomic cut-over
A rolling deploy is, by construction, an interval during which some
hosts run version N and some run N+1. There is no serial value that
removes the interval; serial: 1 maximises it, and a single batch
covering the fleet is a simultaneous outage rather than a cut-over.
The consequence is a requirement on the application, not on the playbook: version N and N+1 must be able to coexist behind the same load balancer, sharing the same database, consuming the same queues, for the duration of the rollout.
| Coexistence requirement | What breaks it |
|---|---|
| Shared database schema | a rename or a drop in the same release |
| Shared cache | a serialisation format change with the same key namespace |
| Shared queue | a message format the old consumer cannot parse |
| Sticky sessions | a session structure the other version cannot read |
| Internal APIs between services | a breaking change deployed in one direction only |
None of these are visible in the playbook, and all of them produce errors during the rollout window that disappear once it completes — which is the worst diagnostic signature available, because by the time anyone investigates, the evidence has resolved itself.
Limit two: no traffic control
Ansible can call a load balancer’s API. It has no data path, so it cannot do anything a load balancer will not do on instruction.
The practical boundary: canary by host is available; canary by traffic is not.
- name: Canary the new release on one host
hosts: appservers
serial: 1
max_fail_percentage: 0
tasks:
- name: Deploy and validate
ansible.builtin.include_tasks: deploy-one-host.ymlOn a twenty-host pool with round-robin balancing, that canary receives about 5% of traffic — not because you specified 5%, but because 1/20 is what it happens to be. On a five-host pool the same play sends 20% of production traffic to an untested build.
What a traffic-aware system offers instead: shift 1% of requests, hold, observe error rate against a baseline, shift 5%, hold, shift 25%, revert automatically on a regression. Weight is a dial rather than a consequence of arithmetic, and it can be turned back in seconds without touching a host.
That capability lives in the load balancer, the service mesh or the ingress layer. Ansible’s role is to configure those systems, which it does well. It cannot be one.
Limit three: no automatic revert
A failed health gate stops the play. It does not undo anything.
This is worth stating precisely because “rollback” is often described as if the tool provides it. What Ansible provides is:
- a
block/rescue/alwaysstructure, which runs tasks you wrote on failure; - a stop-the-run failure policy, which prevents further batches;
- an idempotent role that can be rerun with a previous version pinned.
What it does not provide is a stored previous state to return to. There
is no transaction, no snapshot, no automatic inverse of a converged
change. A rescue block that “rolls back” is a set of forward tasks
that happen to install an older version — which works, and is a rollback
only to the extent that the older version can be installed and the
configuration and schema still fit it.
- name: Deploy with a scripted fallback
block:
- name: Deploy the new version
ansible.builtin.include_tasks: deploy-one-host.yml
rescue:
- name: Reinstall the previously recorded version
ansible.builtin.apt:
name: 'myapp={{ myapp_previous_release }}'
state: present
allow_downgrade: true
notify: Restart myapp
- name: Announce that this host was reverted, and fail the batch anyway
ansible.builtin.fail:
msg: >-
{{ inventory_hostname }} failed its health gate and was reverted
to {{ myapp_previous_release }}. The run is stopping; the fleet
is in a mixed state and needs a decision.The deliberate fail at the end matters. A rescue that succeeds marks
the host as recovered and the play continues to the next batch —
which is almost never what you want after a canary failed. Recovering
the host and stopping the run are two separate decisions, and the second
one has to be written.
Limit four: no continuous reconciliation
A run is a point in time. After ansible-playbook exits, nothing
maintains the state it applied.
A host that drifts an hour later stays drifted until the next run. A host that was unreachable during the deploy is simply not deployed, and nothing remembers to come back for it. An instance created by autoscaling ten minutes after the play finished comes up running whatever its image carries — usually the previous release — and does not appear in any recap.
The signals you have outgrown it
These are concrete, and any two of them together are a strong case for changing tools rather than improving the playbook.
Deploy frequency approaches run duration. A rolling play across 300
hosts takes ninety minutes. If you deploy hourly, runs overlap, and two
concurrent ansible-playbook processes converging the same fleet on
different versions is a state nothing in this course can make safe.
You need traffic-percentage canaries. Not “one host first” but “1% of requests, then 5%”. This is a routing capability and no playbook provides it.
You need automatic revert on an SLO regression. A control loop, as above.
Your fleet scales in and out during deployments. The autoscaling gap becomes structural rather than occasional.
You are rebuilding a scheduler. If the playbook has grown logic for where a workload should run, how many replicas there should be, and what to do when a host disappears — that is an orchestrator, and there are good ones.
Hosts are cattle with no state. If every host is identical, disposable and rebuilt rather than repaired, the convergence machinery is overhead. Bake an image and replace instances.
Knowledge check
Knowledge check · 4 questions
Q1. Why can no choice of serial value give a rolling Ansible deployment an atomic cut-over?
Q2. A team wants deployments to revert automatically if the error rate degrades in the ten minutes after a release. Why is this a poor fit for Ansible?
Q3. Which of these are genuine signals that a fleet has outgrown a push-based deployer? Select all that apply.
Q4. A rescue block that reinstalls the previous version constitutes a rollback, because Ansible restores the state the host was in before the play ran.
Passing score: 75%. Answers are checked in this browser.