feat: upgrades keep the VIP: kernel-bounded address, graceful restart, rolling etcd #87
No reviewers
Labels
No labels
accessibility
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Keel-Linux/keel!87
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/vip-continuity-upgrades"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The maintainer's requirement of 2026-10-10: an
apt full-upgradeon the node that holds the VIP must not drop the VIP or trigger a failover, beyond a short planned blip that is measured and documented. The same applies to the replica and to every etcd member. The units are in Keel-Linux/common (keel-overlay-vip, keel-overlay-etcd). That PR is the packaging half of this one.What an upgrade does today (
befaaea, keel-overlay-vip 0.1.1)dh_installsystemd --no-enable --no-startgives a preinst that runsdeb-systemd-invoke stop keel-vip-check.timer keel-vip.serviceon upgrade, and a postinst that never starts them again. On every node, an upgrade of the package stops the helper, its controller and the check, and leaves them stopped.--stopped(ExecStopPost) and the helper's ownfinallyremove the address from wg0, so the holder drops the VIP. The replica's controller is stopped too, so nothing claims it. This is an outage until a reboot orkeel spec apply, not a blip.keel-mesh-members,keel-mesh-syncandkeel-mesh-etcd(restart-after-upgrade). The preinst stopskeel-firewallandkeel-network-revert(--no-start) without starting them again. Both are oneshots with no ExecStop, so the nft table stays loaded. Nothing restartskeel-vip.service, so the helper keeps running the old code. Its lazy imports (vipmsginheld_of,memberlink) can then load files from the new version into the old process.deb-systemd-invoke restart etcd.serviceon every upgrade. Nothing coordinates this across nodes: run apt on two members at once and quorum is lost for the length of both restarts. On the holder, the controller asks only its local member ([::1]:2379), so the local restart stops renewals. If the restart outlasts the 10 s release window, the VIP blips. If it outlasts the 20 s TTL, there is a failover and the holder is fenced.serve'sfinally, andvipetcd.stopped()removes the address, as--stoppeddoes again. The new controller starts with nothing in memory and carries the address only after its first renewal confirmed by the majority (keepalive plus a linearizable read). The VIP is therefore down for the whole stop, start and first renewal. The lease (TTL 20 s) survives that, so there is no failover unless the restart takes longer than the TTL. The measured values are in "Measured" below.Design
valid_lft:ip -6 addr replace <vip>/128 dev wg0 valid_lft N preferred_lft 0 nodad, withN = floor(RELEASE_AFTER - age) - 1. Hereageis the time since the renewal the majority confirmed (CLOCK_BOOTTIME), and the 1 s covers the kernel's lateness: addrconf runs its check at most 0.25 s after an address expires. Each confirmed renewal (every 2 s) replaces the address with a new lifetime. If nothing renews it, the kernel removes the address before 10 s after the last confirmed renewal. That is the same deadline the controller enforced from its memory. It now holds even when no process enforces it: a controller stopped, restarting, crashed or frozen. That last case, the "one case this does not cover" in docs/vip.md, is now covered.valid_lft forever, left by an older keel) is dropped at start and at stop, as today.keel vip tend --stoppeddrops every VIP unless the unit is restarting. That covers arestartortry-restartjob queued for the unit (systemd's job list), or a helper that died ($SERVICE_RESULTother thansuccess, which Restart=always restarts). In both cases the bounded address stays, and the kernel removes it if no controller renews it. A plainsystemctl stop,keel spec applydisabling the overlay, or a shutdown still drops the VIP at once.keel mesh etcd gate stopruns as ExecStop of etcd.service, from keel-overlay-etcd's drop-in. Before etcd is sent SIGTERM, it waits until the other voters are healthy enough to keep quorum without this member. It also waits until no other member holds the restart lock, then takes it. The lock is/keel/<mesh>/etcd/restarting, written by compare-and-swap on an etcd lease (TTL 120 s).keel mesh etcd gate startedruns as ExecStartPost. It waits until the local member answers a linearizable read, then deletes its own lock. A stop cannot be refused for good, so after--wait(300 s by default) the gate says so loudly and lets the stop go ahead. At shutdown it does not wait. Apt run on two members at once therefore restarts them one after the other: the second member's postinst blocks indeb-systemd-invoke restartuntil the first is healthy again. etcd itself hands its leadership to another member on SIGTERM.keel mesh upgrade-checkis read-only and exits 1 when this node should not be upgraded now. It names each unhealthy voter, the member holding the restart lock, and whether this node holds a VIP. An operator, or a fleet tool, runs it beforeapt full-upgrade.--no-stop-on-upgrade, a postinsttry-restart keel-vip.serviceon upgrade, a trigger on keel's Python files (an upgrade of keel try-restarts the helper, so the new code runs), andRestartSec=2. keel-overlay-etcd ships the etcd.service drop-in. keel's own units need no change for the VIP.keel vip promoteon the replica (about 3 to 4 s of planned downtime, measured by the vip job's (b)), andkeel mesh upgrade-checkbefore each node.The first upgrade into this version still blips once on the holder. The old helper's in-memory
finallydrops the address before the new code runs, and the new controller carries it again after its first renewal.Tests
--stoppedon a restart, a crash and a stop, the fallback endpoints, and the gate and upgrade-check against a fake etcd.tests/test_vip_upgrade_netns.py(CI job vip-upgrade / trixie) runs on the vip job's nodes and links: 250 ms ±25 ms and 2% loss, measured first. Scenarios: (a) try-restart of keel-vip.service on the holder (three times) and on the replica, and a SIGKILL of the holder's helper; (b) etcd restarted through the gate on C, then on the holder A, then on B; (c) C held down, B's gate refuses for its wait whileupgrade-checknames C, then lets B through once C is back; (d) the holder's controller frozen with SIGSTOP, so the kernel drops the address within the release window and B claims after the lease. The checks: no failover (the counter's epoch does not move), never two holders, and a ping gap from C under 1 s for each upgrade-style restart..debs, version-bumped in CI, is in common'sinstall / trixie(tests/overlay-install.bats).Measured (vip-upgrade / trixie, 250 ms ±25 ms with 2% loss)
The longest gap in C's answers to the VIP, which it pings every 50 ms:
befaaea(red run)upgrade-checkon A named C, both as unhealthy and as holding the restart lock. Once C was back, B's gate passed in 5 to 7 s.Common PR: Keel-Linux/common#46. Its
install / trixiejob builds this branch's keel. Merge this PR first, then re-pin common#46 to the merge commit.