Ansible - KeepAliveD

3 minute read

Refreshed June 2026: module names are now FQCN, the handler uses ansible.builtin.service, and the health check in the template is wired in with a track_script block (the original defined a vrrp_script but never referenced it, so it never actually ran). The example check also moved off Quagga, which is end of life and superseded by FRRouting, to HAProxy. I trimmed the oversized kitchen-sink group_vars from the original down to just the KeepAliveD variables so the post stays on topic. Rendered output below is from ansible-core 2.19.

Background

The goal is to dynamically generate a KeepAliveD failover configuration across two Ubuntu nodes acting as routers and load balancers, so a single VIP floats between them with VRRP. KeepAliveD is still the standard tool for this, so the approach holds up; only the Ansible syntax around it needed a cleanup.

Tenant Variables

A fictitious tenant defined in a vars file. Trimmed to what the KeepAliveD config actually uses:

---
tenant_name: tenant_1

tenant_vips:
  - 10.10.10.100
  - 10.10.10.101
  - 10.10.10.102
  - 10.10.10.103
  - 10.10.10.104

The Playbook

Template out keepalived.conf and notify a handler to restart the service:

---
- hosts: all
  vars_files:
    - ../vars/tenant_1.yml
  tasks:
    - name: tenant_configs | config | setting up keepalived vips
      ansible.builtin.template:
        src: ../templates/etc/keepalived/keepalived.conf.j2
        dest: /etc/keepalived/keepalived.conf
        owner: root
        group: root
        mode: "0644"
      run_once: true
      notify: restart keepalived

  handlers:
    - name: restart keepalived
      ansible.builtin.service:
        name: keepalived
        state: restarted

The Template

The template builds the VRRP instance from the variables. The vrrp_script defines a health check, and the track_script block inside the instance is what actually activates it. In the original this wiring was missing, so the check was defined but never used. Here the check watches HAProxy, the service the VIP fronts.

## {{ ansible_managed }}

vrrp_script chk_haproxy {
    script "/usr/bin/killall -0 haproxy"   # verify the process is alive
    interval 2                             # check every 2 seconds
    weight 2                               # add 2 points of priority if OK
}

vrrp_instance tenant_1 {
    state MASTER
    interface {{ keepalived_vip_int }}
    virtual_router_id {{ keepalived_router_id }}
    priority {{ keepalived_router_pri }}
    advert_int 1
    virtual_ipaddress {
        {{ keepalived_vip }}
    }
    virtual_ipaddress_excluded {
{% for vip in tenant_vips %}
        {{ vip }}
{% endfor %}
    }
    track_script {
        chk_haproxy
    }
    notify_master {{ notify_master_script }}
    notify_backup {{ notify_backup_script }}
}

Rendered Output

With the KeepAliveD variables below applied, the template renders to:

## Managed by Ansible

vrrp_script chk_haproxy {
    script "/usr/bin/killall -0 haproxy"   # verify the process is alive
    interval 2                             # check every 2 seconds
    weight 2                               # add 2 points of priority if OK
}

vrrp_instance tenant_1 {
    state MASTER
    interface eth0
    virtual_router_id 23
    priority 101
    advert_int 1
    virtual_ipaddress {
        10.10.10.4
    }
    virtual_ipaddress_excluded {
        10.10.10.100
        10.10.10.101
        10.10.10.102
        10.10.10.103
        10.10.10.104
    }
    track_script {
        chk_haproxy
    }
    notify_master /opt/scripts/master.sh
    notify_backup /opt/scripts/backup.sh
}

The KeepAliveD Variables

The variables that drive the template. The per-node priority and virtual_router_id normally live in host_vars so each node differs:

keepalived_vip: 10.10.10.4                     # on the same network as the primary interface
keepalived_vip_int: "{{ pri_bind_interface }}" # the interface the VIP binds to
keepalived_router_id: 23                       # unique per VRRP domain on a subnet
keepalived_router_pri: 101                     # per-node; higher wins MASTER (host_vars)
notify_master_script: /opt/scripts/master.sh
notify_backup_script: /opt/scripts/backup.sh
notify_fault_script: /opt/scripts/fault.sh

What Else Has Changed Since 2015

  • Quagga is end of life. The original tied the health check to Quagga’s zebra. If you still run a software router on these nodes, it is FRRouting now, which also ships a zebra daemon, so the same killall -0 pattern works if you point it there.
  • Networking moved to netplan. On modern Ubuntu the /etc/network/interfaces and interfaces.d files the original leaned on are replaced by netplan. The KeepAliveD config itself is unaffected.
  • KeepAliveD itself is unchanged in shape. VRRP instances, VIPs, and tracked scripts work the same, which is why this post needed a tune-up rather than a rewrite.

Conclusion

KeepAliveD is still how you float a VIP between two nodes. Template the config from your tenant data, wire the health check in with track_script, and let a handler restart the service on change.

Enjoy!

Comments