diff --git a/filter_plugins/net_utils.py b/filter_plugins/net_utils.py index 082f34d..5eecace 100644 --- a/filter_plugins/net_utils.py +++ b/filter_plugins/net_utils.py @@ -1,3 +1,6 @@ +import ipaddress +from operator import attrgetter + import dns.name @@ -5,9 +8,33 @@ class FilterModule: def filters(self): return { "remove_domain_suffix": remove_domain_suffix, + "ipaddr_sort": ipaddr_sort, } def remove_domain_suffix(name): parent = dns.name.from_text(name).parent() return parent.to_text() + + +def ipaddr_sort(addrs, types, unknown_after=True): + check_types = { + "global": attrgetter("is_global"), + "link-local": attrgetter("is_link_local"), + "loopback": attrgetter("is_loopback"), + "multicast": attrgetter("is_multicast"), + "private": attrgetter("is_private"), + "reserved": attrgetter("is_reserved"), + "site_local": attrgetter("is_site_local"), + "unspecified": attrgetter("is_unspecified"), + } + + def addr_weight(addr): + if isinstance(addr, str): + addr = ipaddress.ip_address(addr.split("/")[0]) + for index, ty in enumerate(types): + if check_types[ty](ipaddress.ip_address(addr)): + return index + return len(types) if unknown_after else -1 + + return sorted(addrs, key=addr_weight) diff --git a/playbooks/keepalived.yml b/playbooks/keepalived.yml new file mode 100755 index 0000000..e8239a1 --- /dev/null +++ b/playbooks/keepalived.yml @@ -0,0 +1,32 @@ +#!/usr/bin/env ansible-playbook +--- +- hosts: + - isp-1.rtr.infra.auro.re + - isp-2.rtr.infra.auro.re + vars: + keepalived__virtual_router_id: 80 + keepalived__interface: ens18 + keepalived__virtual_addresses: + client-0: + - 100.64.0.1/27 + - 2a09:6841::/56 + - fe80::1/10 + client-1: + - 100.64.0.33/27 + - 2a09:6841:0:100::/56 + - fe80::1/10 + client-2: + - 100.64.0.65/27 + - 2a09:6841:0:100::/56 + - fe80::1/10 + client-3: + - 100.64.0.97/27 + - 2a09:6841:0:200::/56 + - fe80::1/10 + client-4: + - 100.64.0.129/27 + - 2a09:6841:0:300::/56 + - fe80::1/10 + roles: + - keepalived +... diff --git a/roles/keepalived/defaults/main.yml b/roles/keepalived/defaults/main.yml new file mode 100644 index 0000000..ee034f3 --- /dev/null +++ b/roles/keepalived/defaults/main.yml @@ -0,0 +1,7 @@ +--- +keepalived__virtual_addresses: {} +keepalived__notify_master: [] +keepalived__notify_backup: [] +keepalived__notify_fault: [] +keepalived__max_auto_priority: -1 +... diff --git a/roles/keepalived/handlers/main.yml b/roles/keepalived/handlers/main.yml new file mode 100644 index 0000000..df390cb --- /dev/null +++ b/roles/keepalived/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Reload keepalived + systemd: + name: keepalived.service + state: reloaded +... diff --git a/roles/keepalived/tasks/main.yml b/roles/keepalived/tasks/main.yml new file mode 100644 index 0000000..6330901 --- /dev/null +++ b/roles/keepalived/tasks/main.yml @@ -0,0 +1,28 @@ +--- +- name: Install keepalived + apt: + name: keepalived + +- name: Configure keepalived + template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + owner: root + group: root + mode: "{{ item.mode }}" + loop: + - src: keepalived.conf.j2 + dest: /etc/keepalived/keepalived.conf + mode: u=rw,g=,o= + - src: notify.sh.j2 + dest: /etc/keepalived/notify.sh + mode: u=rwx,g=,o= + notify: + - Reload keepalived + +- name: Enable and start keepalived + systemd: + name: keepalived + enabled: true + state: started +... diff --git a/roles/keepalived/templates/keepalived.conf.j2 b/roles/keepalived/templates/keepalived.conf.j2 new file mode 100644 index 0000000..c99ae10 --- /dev/null +++ b/roles/keepalived/templates/keepalived.conf.j2 @@ -0,0 +1,92 @@ +{{ ansible_managed | comment }} + +global_defs { + dynamic_interfaces + script_user root + enable_script_security + vrrp_version 3 +{% if keepalived__max_auto_priority is defined %} + max_auto_priority {{ keepalived__max_auto_priority | int }} +{% endif %} +} + +{% +set ipv4_enabled = + keepalived__ipv4_enabled + | default(keepalived__virtual_addresses.values() + | flatten | ansible.utils.ipv4) +%} +{% +set ipv6_enabled = + keepalived__ipv6_enabled + | default(keepalived__virtual_addresses.values() + | flatten | ansible.utils.ipv6) +%} + +{% if ipv4_enabled and ipv6_enabled %} +vrrp_sync_group group { + group { +{% if ipv4_enabled %} + instance_v4 +{% endif %} +{% if ipv6_enabled %} + instance_v6 +{% endif %} + } + notify_master "/etc/keepalived/notify.sh master" + notify_backup "/etc/keepalived/notify.sh backup" + notify_fault "/etc/keepalived/notify.sh fault" +} +{% endif %} + +{% if ipv4_enabled %} +vrrp_instance instance_v4 { + virtual_router_id {{ keepalived__virtual_router_id | int }} + interface {{ keepalived__interface }} + state BACKUP + priority 250 + nopreempt + advert_int 1 + accept + virtual_ipaddress { +{% for dev, addresses in keepalived__virtual_addresses.items() %} +{% for address in addresses %} +{% if address | ansible.utils.ipv4 %} + {{ address }} dev {{ dev }} +{% endif %} +{% endfor %} +{% endfor %} + } +{% if not (ipv4_enabled and ipv6_enabled) %} + notify_master "/etc/keepalived/notify.sh master" + notify_backup "/etc/keepalived/notify.sh backup" + notify_fault "/etc/keepalived/notify.sh fault" +{% endif %} +} +{% endif %} + +{% if ipv6_enabled %} +vrrp_instance instance_v6 { + virtual_router_id {{ keepalived__virtual_router_id | int }} + interface {{ keepalived__interface }} + state BACKUP + priority 250 + nopreempt + advert_int 1 + accept + virtual_ipaddress { +{% for dev, addresses in keepalived__virtual_addresses.items() %} +{% for address in addresses | ipaddr_sort(["link-local"]) %} +{% if address | ansible.utils.ipv6 %} + {{ address }} dev {{ dev }} +{% endif %} +{% endfor %} +{% endfor %} + } +{% if not (ipv4_enabled and ipv6_enabled) %} + notify_master "/etc/keepalived/notify.sh master" + notify_backup "/etc/keepalived/notify.sh backup" + notify_fault "/etc/keepalived/notify.sh fault" +{% endif %} +} +{% endif %} diff --git a/roles/keepalived/templates/notify.sh.j2 b/roles/keepalived/templates/notify.sh.j2 new file mode 100644 index 0000000..4f58259 --- /dev/null +++ b/roles/keepalived/templates/notify.sh.j2 @@ -0,0 +1,33 @@ +#!/bin/bash + +master=( +{% for notify in keepalived__notify_master %} + {{ notify | quote }} +{% endfor %} +) + +backup=( +{% for notify in keepalived__notify_backup %} + {{ notify | quote }} +{% endfor %} +) + +fault=( +{% for notify in keepalived__notify_fault %} + {{ notify | quote }} +{% endfor %} +) + +case "$1" in + master | backup | fault) + scripts="$1[@]" + ;; + *) + echo "Usage: $0 (master|backup|fault)" >&2 + exit 1 +esac + +for script in "${!scripts}" +do + eval "${script}" +done