Skip to content

Make one_vip optional under HA so RAFT can run behind an external leader-aware load balancer #259

Description

@jallyn18

Summary

Under HA (force_ha: true, or more than one front end), one-deploy requires one_vip and
unconditionally installs the built-in floating-VIP RAFT hooks. There is no supported way to run
HA without the VIP.

That blocks a legitimate, documented topology: front ends on different subnets, fronted by an
external load balancer.
OpenNebula's VIP is an L2 mechanism (ip address add + gratuitous
arping in raft/vip.sh) and cannot migrate across subnets, so a multi-subnet / multi-DC HA
cluster must route to the current leader with an external LB instead. RAFT itself does not need
the VIP — each server is reached at its own <host>:2633.

Request: make one_vip optional so that, when it is omitted, HA still forms but the VIP hooks
are simply not written.

The collection already half-supports this

Three of the code paths that touch one_vip already tolerate it being undefined — so this request
is small and consistent with patterns already in the tree, not a new concept:

  1. Leader detectionroles/opennebula/leader/tasks/main.yml already falls back to the first
    front end:
    {{ one_vip | d(federation.groups.frontend[0]) }}
  2. ONEGATE endpointroles/opennebula/server/tasks/config.yml already defaults the endpoint
    host to the first front end when there is no VIP:
    _default: "{{ 'http://' ~ (one_vip | d(_host)) ~ ':5030' }}"
    _host:    "{{ hostvars[federation.groups.frontend[0]].ansible_host }}"
  3. VIP-vars integrity — the second assertion in roles/precheck/pre_reboot/tasks/integrity.yml
    ("Check if all vip related settings are provided") already permits one_vip, one_vip_cidr and
    one_vip_if to be all undefined together.

What actually blocks it (two places)

  1. roles/precheck/pre_reboot/tasks/integrity.yml — the first assertion ("Check if
    one_vip/force_ha settings are valid") requires one_vip to be defined whenever HA is in play, so
    it cannot be omitted.
  2. roles/opennebula/server/tasks/config.yml — the when: use_ha is true block templates the
    raft/vip.sh RAFT_LEADER_HOOK/RAFT_FOLLOWER_HOOK and monitord's MONITOR_ADDRESS from
    one_vip / one_vip_if / one_vip_cidr with no guard, so with HA enabled and one_vip omitted
    the play fails on the undefined variables.

Proposed change

1. roles/precheck/pre_reboot/tasks/integrity.yml — relax the first assertion so a missing
one_vip is always valid, while still catching a VIP configured without HA:

 - name: Check if one_vip/force_ha settings are valid
   ansible.builtin.assert:
-    that: ((one_vip is defined) and ((_frontend_count | int > 1) or (_force_ha is true)))
-          or
-          ((one_vip is undefined) and ((_frontend_count | int == 1) and (_force_ha is false)))
-    fail_msg: Please either define one_vip in the inventory, add more Front-ends or enable force_ha.
+    # one_vip is optional: HA can run without a floating VIP behind an external
+    # leader-aware load balancer (e.g. multi-subnet front ends, where an L2 VIP
+    # cannot migrate). A VIP still only makes sense with HA, so keep that guard.
+    that: (one_vip is undefined)
+          or
+          ((_frontend_count | int > 1) or (_force_ha is true))
+    fail_msg: one_vip is set but HA is not enabled — add more Front-ends or set force_ha, or omit one_vip.
   vars:
     _frontend_count: >-
       {{ federation.groups.frontend | count }}
     _force_ha: >-
       {{ force_ha | d(false) | bool }}

2. roles/opennebula/server/tasks/config.yml — guard the VIP-hook / monitord block on a
defined one_vip:

-- when: use_ha is true
+# Only write the vip.sh RAFT hooks + monitord MONITOR_ADDRESS when a VIP exists.
+# Without one_vip, HA still forms (RAFT SERVER config lives in master.yml / slave.yml)
+# and monitord keeps its per-host default.
+- when:
+    - use_ha is true
+    - one_vip is defined
   block:
     - name: Configure oned (RAFT)
       opennebula.deploy.cfgtool:
         dest: /etc/one/oned.conf
         parser: One
         actions:
           - put:
               path: [RAFT_LEADER_HOOK, COMMAND]
               value: '"raft/vip.sh"'
           # ... unchanged ...

Why this is safe

  • RAFT / HA is not disabled. The consensus SERVER configuration is written in master.yml /
    slave.yml, not in this block — the guarded block only writes the VIP hooks and the monitord
    MONITOR_ADDRESS. Skipping it leaves HA fully intact; it just omits the floating IP.
  • monitord keeps its per-host default MONITOR_ADDRESS when the block is skipped, which is
    what you want with no VIP.
  • Fully backward compatible. When one_vip is defined the when: is true and the assertion
    passes exactly as before — behaviour is byte-for-byte unchanged for existing VIP deployments.

Evidence

We have run precisely this two-line change on a 3-node RHEL 10 HA front end with no one_vip,
front ends on separate subnets behind an external leader-aware LB:

  • HA zone forms and elects a leader.
  • Induced-failover recovery measured at ~4.7s election time.
  • No vip.sh hooks and no floating IP present anywhere.

Suggested test

A molecule assertion that, with one_vip undefined and force_ha: true (or >1 front end), the
converge succeeds and onezone show reports a formed zone with an elected leader — i.e. HA without
a VIP.

Environment

  • Collection: opennebula.deploy, release-1.4.1, consumed unmodified apart from evaluating
    the change above.
  • Target: RHEL 10 front ends on separate subnets, fronted by an external (F5 GTM) leader-aware LB.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions