kvm icon
Get icon
If icon
Set icon
StartVm icon
SlackIncomingWebhook icon
Log icon
VmEventTrigger icon
Expression icon

Self-healing KVM VM monitor with auto-restart

Watch a KVM libvirt domain and auto-restart it on SHUTOFF with flap protection, maintenance-aware skips, and loud failure alerts, all in Kestra.

Categories
Infrastructure

This blueprint turns Kestra into a self-healing watchdog for a critical KVM (libvirt) virtual machine. A polling trigger checks the domain state once a minute and only fires when the VM is SHUTOFF, so a healthy host never floods your execution history with no-op runs. When the VM does go down unexpectedly, the flow decides whether recovery is safe, restarts the domain, and notifies Slack. It avoids the two classic self-healing anti-patterns: silent no-op spam and blindly rebooting a crash-looping VM forever. Keywords: KVM monitoring, libvirt auto-restart, VM self-healing, flap protection, infrastructure orchestration.

How it works

  1. The watch_vm trigger (io.kestra.plugin.kvm.VmEventTrigger) polls the domain every PT1M. An io.kestra.plugin.core.condition.Expression gates execution creation on {{ trigger.state == 'SHUTOFF' }}, so a healthy VM produces zero executions.
  2. read_maintenance_flag and read_restart_counter (io.kestra.plugin.core.kv.Get, both errorOnMissing: false) load per-VM state from the KV store: an optional maintenance flag and the restart count in the current window.
  3. decide (io.kestra.plugin.core.flow.If) recovers only when the maintenance flag is not true AND the count is below max_restarts_per_window.
    • Recover path: bump_restart_counter (io.kestra.plugin.core.kv.Set, kvType: NUMBER) increments the counter with a ttl of flap_window, recover_vm (io.kestra.plugin.kvm.StartVm) boots the domain with waitForRunning: true, and alert_recovered posts a Slack summary.
    • Else path: classify either logs a maintenance skip or, on the restart ceiling, posts a <!channel> escalation and deliberately does NOT restart.
  4. The flow-level errors block (alert_recovery_failed) pages on-call if the restart itself fails.

The libvirt uri lives in pluginDefaults keyed on io.kestra.plugin.kvm, every remote call carries a retry, and concurrency.limit: 1 keeps a single recovery acting on the VM at a time.

What you get

  • Sub-minute detection and automatic restart of a downed KVM domain.
  • Flap protection: a capped restart count over a sliding TTL window that escalates instead of looping.
  • Maintenance-aware skips so planned stops are never fought by the watchdog.
  • Loud Slack escalation when recovery fails, the most urgent case.
  • A clean execution history that records only real incidents.

Who it's for

  • Platform and infrastructure engineers running libvirt/KVM hosts.
  • SREs and on-call teams who want fewer pages for transient blips and louder pages for real failures.
  • Homelab and self-hosted operators keeping a critical VM online.

Why orchestrate this with Kestra

A bare libvirt host has no native scheduler that can poll state, count restarts across a window, branch on a maintenance flag, and escalate to Slack. Kestra adds an event-style polling trigger, declarative YAML logic, per-task retry, a built-in KV store for stateful flap counting with TTL, concurrency control, and full execution lineage. The KV store turns a stateless restart into a rate-limited, self-resetting recovery loop, something a cron job or a watchdog script cannot do cleanly.

Prerequisites

  • A reachable libvirt/KVM host, addressed via a URI such as qemu+ssh://root@your-host/system or qemu:///system.
  • The monitored domain (monitored_vm) already defined on the host.
  • A Slack Incoming Webhook URL.

Secrets

  • LIBVIRT_URI: the libvirt connection URI used by the trigger and the StartVm recovery task.
  • SLACK_WEBHOOK: the Slack Incoming Webhook URL used for recovery, escalation, and failure alerts.

Quick start

  1. Add the LIBVIRT_URI and SLACK_WEBHOOK secrets to your Kestra instance.
  2. Set the monitored_vm input to the domain you want watched.
  3. Tune max_restarts_per_window and flap_window to your flap tolerance.
  4. Adjust the trigger interval if you need faster or slower polling.
  5. Save the flow; the trigger begins polling and fires only on SHUTOFF.

How to extend

  • To pause recovery during planned work, set the KV key maintenance_<monitored_vm> to true with io.kestra.plugin.core.kv.Set, then clear it afterward.
  • Add an application-readiness check after recover_vm, since RUNNING is a libvirt state, not a guest-ready signal.
  • Fan out to many domains with a ForEach over a VM list, or swap Slack for PagerDuty or email on the escalation path.
  • Feed recovery events into a dashboard or incident system for trend analysis.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.