Repository navigation
feat: upgrades keep the VIP: kernel-bounded address, graceful restart, rolling etcd - #87
Merged
Merged
Conversation
added 4 commits
October 8, 2026 06:08
tests/test_vip_upgrade_netns.py, the CI job vip-upgrade / trixie: on the vip job's nodes and links, keel-vip.service try-restarted on the holder and the replica and its helper killed, etcd restarted on each member in turn through keel-overlay-etcd's gate, a second member's restart held back while the first is down, and the holder's controller frozen. No failover, never two holders, and a gap in the VIP's answers under 1 s for each upgrade-style restart. Red on befaaea: it measures today's behaviour.
The holder carries the VIP with a valid_lft of what is left of the 10 s release time after its last renewal the majority confirmed, so the kernel removes it by then whatever becomes of the controller. The helper stopping, and ExecStopPost on a restart job or a helper that died, leave such an address in place; a new controller extends it only once it renewed the same lease, and drops an address with no lifetime. The controller renews through every member of the cluster record after its own, so the holder renews while its own etcd restarts. keel mesh etcd gate stop|started, for keel-overlay-etcd's drop-in of etcd.service: a stop waits until every other voter is healthy and takes a restart lock on a lease; a start releases it once the member answers a linearizable read. keel mesh upgrade-check says whether this node may be upgraded now. docs/vip.md, Upgrading a pair without downtime.
3 tasks
added 2 commits
October 8, 2026 06:44
… waits for the controller's exec addrconf removes an expired address a quarter of a second late at most, so 2 s of slack took a second of the release time from every renewal. The vip job read the controller's capabilities from systemd's executor before it exec'd the controller.
marcos-mendez
pushed a commit
to Keel-Linux/common
that referenced
this pull request
Oct 8, 2026
marcos-mendez
marked this pull request as ready for review
October 8, 2026 07:08
marcos-mendez
pushed a commit
to Keel-Linux/common
that referenced
this pull request
Oct 8, 2026
marcos-mendez
pushed a commit
to Keel-Linux/common
that referenced
this pull request
Oct 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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.