Automation

Event-Driven Ansible: Automate Your Incident Response in 2026

Teach me Ansible | 2026-08-20 | 20 min read

Stop waking up at 3 AM to run the same cleanup playbook. Event-Driven Ansible (EDA) listens for alerts, webhooks, and message streams — then triggers remediation automatically. In this guide you'll install ansible-rulebook, write your first rulebooks, and build a complete Alertmanager-to-playbook auto-remediation pipeline.

What is Event-Driven Ansible?

Traditional Ansible is imperative and human-triggered: someone runs a playbook, or a scheduler kicks it off at a fixed time. Event-Driven Ansible flips that model. Instead of asking "when should this run?", EDA asks "what happened?" — and reacts within seconds.

At its core, EDA is a long-running process (ansible-rulebook) that connects three things:

  • Event Sources - Where events come from: webhooks, Kafka topics, Prometheus Alertmanager, AWS SQS, file changes, or a simple timer
  • Rules - Conditions that match against event payloads ("severity is critical AND alertname is DiskFull")
  • Actions - What to do when a rule matches: run a playbook, run a module, post to another system, or set a fact for later rules

Why Ops Teams Are Adopting EDA in 2026

The economics of incident response have changed. Mean-time-to-remediate (MTTR) is now a board-level metric, and the "page a human, human runs a runbook" loop simply can't compete with automation that reacts in under ten seconds. Teams use EDA to:

  • Auto-remediate known failures - Disk full, service down, certificate expiring, memory leak restarts
  • Eliminate toil - The top 20% of repetitive alerts often account for 80% of pages; automate them away
  • Enrich tickets automatically - Gather diagnostics the moment an alert fires, before an engineer even looks
  • Bridge tools - Turn a ServiceNow ticket, GitHub webhook, or Kafka message into infrastructure action
  • Standardize response - The remediation is versioned YAML in git, not tribal knowledge in someone's head

EDA is still Ansible

Your existing playbooks, roles, collections, and inventory work unchanged. EDA just adds a new trigger mechanism in front of them — you're not rewriting anything.

Installing ansible-rulebook

The ansible-rulebook CLI is the open-source engine behind EDA (the same engine powers Event-Driven Ansible controller in AAP). It has one unusual dependency for a Python tool: Java, because the rule engine (Drools) runs on the JVM.

# 1. Install Java 17+ (required by the Drools rule engine)
sudo dnf install java-17-openjdk       # RHEL/Fedora
sudo apt install openjdk-17-jdk        # Ubuntu/Debian

# 2. Set JAVA_HOME (adjust path for your distro)
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk' >> ~/.bashrc

# 3. Install ansible-rulebook in a virtual environment
python3 -m venv ~/eda-venv
source ~/eda-venv/bin/activate
pip install ansible-rulebook ansible ansible-runner

# 4. Install the EDA collection (provides event source plugins)
ansible-galaxy collection install ansible.eda

# 5. Verify
ansible-rulebook --version

The ansible.eda collection ships the standard event source plugins: webhook, kafka, alertmanager, url_check, file_watch, range, and more. Community collections add sources for AWS, Azure, GitLab, and dozens of other systems.

Anatomy of a Rulebook

A rulebook is a YAML file that looks pleasantly familiar if you write playbooks. It contains one or more rulesets; each ruleset declares its event sources and a list of rules with conditions and actions.

---
- name: Respond to service events
  hosts: all                      # inventory group actions will target
  sources:
    - ansible.eda.webhook:        # event source plugin
        host: 0.0.0.0
        port: 5000

  rules:
    - name: Restart nginx when it dies
      condition: event.payload.service == "nginx" and event.payload.state == "down"
      action:
        run_playbook:
          name: playbooks/restart-nginx.yml

    - name: Log everything else
      condition: event.payload is defined
      action:
        debug:
          msg: "Unhandled event: {{ event.payload }}"

Key building blocks to understand:

  • sources - One or more plugins that inject events. Each event becomes a JSON-like structure available as event in conditions
  • condition - A boolean expression over event data. Supports and, or, not, comparison operators, in, is defined, and regex matching via is match() / is search()
  • action - Most commonly run_playbook or run_module; also set_fact, post_event, run_job_template (AAP), and debug
  • throttle - Optional per-rule rate limiting (once_within / once_after) — critical in production, more on this below

Multi-Condition Rules

Conditions can combine multiple event attributes, and all/any blocks let you correlate separate events within a time window:

rules:
  - name: Escalate only when both symptoms appear
    condition:
      all:
        - event.alert.name == "HighCPU"
        - event.alert.name == "HighLatency"
      timeout: 5 minutes
    action:
      run_playbook:
        name: playbooks/scale-out.yml

This event correlation is something cron and CI pipelines simply cannot express — the rule engine holds partial matches in memory and fires only when the full pattern completes.

Running a Rulebook

# Run with an inventory and verbose output
ansible-rulebook \
  --rulebook rulebooks/service-response.yml \
  --inventory inventory.yml \
  --verbose

# Pass secrets/settings via extra vars
ansible-rulebook -r rulebooks/service-response.yml \
  -i inventory.yml \
  --env-vars KAFKA_PASSWORD

Walkthrough: Alertmanager Disk-Full Auto-Remediation

Let's build the classic EDA win: Prometheus fires a DiskSpaceLow alert, Alertmanager forwards it to our rulebook via webhook, and a cleanup playbook runs against the affected host — before anyone gets paged.

1. The Rulebook

The ansible.eda.alertmanager source is a purpose-built webhook receiver that understands Alertmanager's payload format and can automatically extract the target host from alert labels:

---
# rulebooks/alertmanager-remediation.yml
- name: Auto-remediate Alertmanager alerts
  hosts: all
  sources:
    - ansible.eda.alertmanager:
        host: 0.0.0.0
        port: 5050
        data_alerts_path: alerts
        data_host_path: labels.instance   # which label holds the hostname
        skip_original_data: false

  rules:
    - name: Clean up disk when DiskSpaceLow fires
      condition: >
        event.alert.labels.alertname == "DiskSpaceLow" and
        event.alert.status == "firing" and
        event.alert.labels.severity in ["warning", "critical"]
      throttle:
        once_within: 10 minutes           # don't re-run while alert re-fires
        group_by_attributes:
          - event.alert.labels.instance
      action:
        run_playbook:
          name: playbooks/disk-cleanup.yml
          extra_vars:
            target_host: "{{ event.alert.labels.instance }}"
            mountpoint: "{{ event.alert.labels.mountpoint | default('/') }}"

    - name: Notify when an alert resolves
      condition: event.alert.status == "resolved"
      action:
        debug:
          msg: "Resolved: {{ event.alert.labels.alertname }} on {{ event.alert.labels.instance }}"

2. The Remediation Playbook

The playbook is ordinary Ansible — it receives target_host from the rulebook and does the boring, safe cleanup steps a human would:

---
# playbooks/disk-cleanup.yml
- name: Emergency disk cleanup
  hosts: "{{ target_host }}"
  become: yes
  vars:
    journal_keep: 3d
    tmp_age: 2d

  tasks:
    - name: Capture disk usage before cleanup
      ansible.builtin.command: df -h {{ mountpoint }}
      register: df_before
      changed_when: false

    - name: Vacuum systemd journal
      ansible.builtin.command: journalctl --vacuum-time={{ journal_keep }}

    - name: Remove old files from /tmp
      ansible.builtin.command: find /tmp -type f -mtime +2 -delete

    - name: Clean package manager cache
      ansible.builtin.command: dnf clean all

    - name: Truncate rotated logs older than 7 days
      ansible.builtin.shell: >
        find /var/log -name "*.gz" -mtime +7 -delete

    - name: Capture disk usage after cleanup
      ansible.builtin.command: df -h {{ mountpoint }}
      register: df_after
      changed_when: false

    - name: Report result
      ansible.builtin.debug:
        msg:
          - "Before: {{ df_before.stdout_lines[-1] }}"
          - "After:  {{ df_after.stdout_lines[-1] }}"

3. Point Alertmanager at the Rulebook

# alertmanager.yml (excerpt)
route:
  receiver: default
  routes:
    - receiver: eda-webhook
      matchers:
        - alertname = "DiskSpaceLow"
      continue: true          # still notify humans in parallel

receivers:
  - name: eda-webhook
    webhook_configs:
      - url: http://eda-host.example.com:5050/alerts
        send_resolved: true

Note continue: true — a good pattern while you build trust in the automation. Humans still see the alert; they just increasingly find it already fixed.

4. Test It End to End

# Start the rulebook
ansible-rulebook -r rulebooks/alertmanager-remediation.yml \
  -i inventory.yml --verbose

# Simulate an Alertmanager POST from another terminal
curl -X POST http://localhost:5050/alerts \
  -H "Content-Type: application/json" \
  -d '{
    "alerts": [{
      "status": "firing",
      "labels": {
        "alertname": "DiskSpaceLow",
        "severity": "critical",
        "instance": "web01.example.com",
        "mountpoint": "/var"
      }
    }]
  }'

You should see the rule match in the rulebook output, followed by a normal Ansible playbook run against web01.example.com.

Consuming Events from Kafka

Webhooks are great for point-to-point integrations, but at scale most 2026 event architectures run through a message bus. The ansible.eda.kafka source subscribes to a topic and turns every message into an event:

---
- name: React to application events on Kafka
  hosts: all
  sources:
    - ansible.eda.kafka:
        host: kafka-broker.example.com
        port: 9092
        topic: infra-events
        group_id: eda-remediation       # consumer group = scale out & resume
        offset: latest
        # For TLS/SASL clusters:
        # cafile: /etc/pki/tls/certs/ca.crt
        # security_protocol: SASL_SSL
        # sasl_mechanism: SCRAM-SHA-512
        # sasl_plain_username: eda
        # sasl_plain_password: "{{ KAFKA_PASSWORD }}"

  rules:
    - name: Rotate credentials on compromise event
      condition: event.body.type == "credential_leak_detected"
      action:
        run_playbook:
          name: playbooks/rotate-secrets.yml
          extra_vars:
            affected_service: "{{ event.body.service }}"

    - name: Rebuild node on hardware fault
      condition: event.body.type == "hardware_fault" and event.body.component == "disk"
      action:
        run_playbook:
          name: playbooks/drain-and-rebuild.yml
          extra_vars:
            node: "{{ event.body.hostname }}"

Using a consumer group_id gives you two production superpowers for free: you can run multiple rulebook instances that share the topic load, and if the rulebook restarts, Kafka replays from the last committed offset so events aren't lost.

EDA vs Cron vs CI Pipelines

All three trigger automation — but they answer very different questions:

  • Trigger model — Cron runs on a schedule whether anything happened or not. CI pipelines run on code changes (push, merge, tag). EDA runs on operational events, the moment they occur.
  • Latency — Cron's worst case is a full interval (a 15-minute cron means up to 15 minutes of downtime before response). CI adds queue and runner spin-up time. EDA reacts in seconds.
  • Context — Cron jobs run blind and must re-discover state ("is the disk actually full?"). EDA receives the full event payload — hostname, mountpoint, severity — and passes it straight into extra_vars.
  • Correlation — Only EDA can express "fire when event A and event B occur within 5 minutes." Cron and CI have no concept of relating multiple occurrences.
  • Efficiency — A cron check every minute is 1,440 executions a day, nearly all no-ops. EDA runs exactly as many times as there are matching events.
  • Best fit — Keep cron for genuinely periodic work (backups, report generation). Keep CI for deploying code. Use EDA for incident response, drift reaction, and cross-system integration.

They compose nicely

A common 2026 pattern: CI deploys the app, Prometheus watches it, and EDA handles the operational fallout. Each tool stays in its lane.

Gotchas and How to Avoid Them

1. The Java Dependency

The most common "why won't it start" issue. ansible-rulebook embeds the Drools rule engine via JPY, which needs a JDK (not just a JRE) at runtime. If you see errors about jpy or JAVA_HOME:

# Confirm a JDK 17+ is present and JAVA_HOME is exported
java -version
echo $JAVA_HOME

# In containers, use the official image which bundles everything:
podman run -it --rm quay.io/ansible/ansible-rulebook:latest \
  ansible-rulebook --version

Remember to set JAVA_HOME in your systemd unit or container spec too — it won't inherit your shell profile.

2. Event Flooding

Alertmanager re-sends firing alerts every few minutes by design. A Kafka topic can burst thousands of messages. Without protection, one flapping alert triggers a stampede of identical playbook runs. Defend in layers:

  • Throttle in the rule - throttle.once_within with group_by_attributes (as shown above) deduplicates per host
  • Group in Alertmanager - Tune group_wait, group_interval, and repeat_interval so EDA sees fewer duplicates
  • Guard in the playbook - First task: check whether the condition still exists; end the play if it doesn't

3. Idempotency Is Non-Negotiable

Assume every remediation playbook will run twice for the same incident — duplicate webhooks, Kafka redelivery, and alert re-fires guarantee it. Your playbooks must be safe to re-run: use state-declaring modules instead of raw commands where possible, add creates:/removes: guards to commands, and never write playbooks whose second run makes things worse (e.g., blindly appending to config files).

4. Other Traps Worth Knowing

  • Condition syntax is not Jinja2 - Rule conditions use the EDA expression language; don't wrap them in curly braces. (Action arguments like extra_vars, by contrast, do support Jinja2 substitution.)
  • Unmatched events vanish silently - During development, add a catch-all debug rule so you can see payload shapes; remove or demote it in production
  • Long playbooks block throughput - Each run_playbook occupies a worker; a 20-minute remediation can back up the event queue. Keep remediations short, or hand off to AAP job templates with run_job_template
  • Webhook sources have no auth by default - Anyone who can reach the port can inject events. Use the token-validating webhook options, TLS, and firewall rules

Production Tips

  1. Run rulebooks under systemd or Kubernetes - ansible-rulebook is a long-lived daemon; give it Restart=always, health monitoring, and log shipping like any other service
  2. Start in observe mode - Deploy new rules with debug actions first. Watch matched events for a week, then swap in run_playbook once you trust the conditions
  3. Keep humans in the loop initially - Use Alertmanager's continue: true so pages still go out while automation matures; retire the page only after the fix rate proves itself
  4. Version everything together - Rulebooks, playbooks, and inventory belong in the same git repo so a rule and its remediation can never drift apart
  5. Secure your secrets - Pass Kafka passwords and webhook tokens via --env-vars or Ansible Vault, never inline in the rulebook YAML
  6. Audit every action - Log which event triggered which playbook with what extra_vars. When automation fixes something at 3 AM, the morning review needs the paper trail
  7. Set blast-radius limits - Cap concurrent remediations and add a circuit breaker: if the same rule fires more than N times per hour, stop and page a human — something deeper is wrong
  8. Consider AAP's EDA controller at scale - When you outgrow a handful of rulebook daemons, Event-Driven Ansible controller adds a UI, RBAC, credential management, and centralized activation monitoring

Conclusion

Event-Driven Ansible closes the loop that monitoring opened years ago: we've long been great at detecting problems, and EDA finally makes us equally fast at fixing them. Start small — one noisy, well-understood alert like disk space, one battle-tested cleanup playbook, one rulebook with throttling — and expand as trust grows.

Within a few sprints, the alerts your team used to dread become line items in an audit log, and your on-call rotation gets noticeably quieter. That's the real promise of EDA in 2026: not replacing operators, but letting them sleep through the incidents that never needed them in the first place.

Pro Tip

Mine your incident history before writing rules. Pull the last 90 days of pages, sort by frequency, and automate the top three alert types first — they're almost always simple, repetitive, and safe to remediate. That's where EDA pays for itself in the first month.