Replikate 1.2 — Two Hubs, One Spoke, No Fights
The Part That Comes After “It Works”
A few days ago Replikate 1.1 learned to cross clusters: annotate a source with target-clusters and it replicates into other clusters, not just other namespaces. That post ended with an honest list of edges. 1.2 is the release where I went back and filed them down — the correctness and safety work that turns “it works in the demo” into “I’d point production at it.”
None of this is a headline feature. All of it is the difference between a tool you try and a tool you trust.
Two Hubs, One Spoke
The sharpest edge: what happens when two Replikate hubs both replicate into the same spoke cluster? In 1.1, they’d fight — each hub’s pruning and conflict logic keyed only on the source’s name and namespace, which collide across hubs. Hub A writes a copy; hub B, not knowing better, prunes it. Clobber war.
1.2 stamps every cross-cluster copy with an origin-cluster label — the hub’s own cluster identity. Pruning and the conflict guard are now scoped to it: a hub only ever deletes or overwrites copies it owns. Two hubs can target one spoke and simply coexist — the first writer keeps its copy, the second gets a clear Conflict event instead of a silent overwrite. Copies written by 1.1 (which predate the label) are adopted rather than orphaned, so the upgrade is quiet.
A Guard That Can’t Be Fooled by a URL
1.1 already refused a credential that pointed the hub at itself — replicating into yourself would make the controller mistake its own local copies for remote ones and delete them. But it identified “itself” by comparing API-server URLs, and the same cluster can be reached by more than one URL (an external load balancer, an IP instead of a DNS name). A credential that took the scenic route slipped past the guard.
1.2 identifies a cluster the way Kubernetes itself does — by the UID of its kube-system namespace, which is stable no matter how you dial in. The guard now vets a candidate before it’s ever registered (so a bad credential is never briefly live), fails closed if it can’t read the candidate’s identity, and reads the hub’s own identity with retries at startup so a momentary blip doesn’t quietly switch the whole guard off. For something whose failure mode is “deletes your data,” fail-safe beats fail-convenient.
Register a Spoke, Fan Out Now
Small quality-of-life fix with a nice payoff: in 1.1, adding a new spoke meant waiting up to a reconcile interval before sources noticed and fanned out to it. 1.2 wires a notification — the moment a spoke is registered and healthy, the sources targeting it are re-driven immediately. A side benefit falls out of the same mechanism: cross-cluster sources get re-checked on a short cycle, a lightweight remote resync that keeps spoke copies fresh until full remote drift correction lands. And a typo’d cluster name no longer spins forever retrying — it waits quietly for that name to actually show up.
Same Rule As Always
The rule hasn’t changed since 1.0: I don’t tag a release on a path that’s never run. All of this rides on the two-real-control-planes integration test from 1.1, extended to cover the new behavior, plus a round of adversarial self-review that caught (and fixed, before merge) an upgrade regression and a data-loss race in my own first cut. The boring safety work is exactly the work most worth reviewing twice.
Still On The List
1.2 is single-hub-friendly-now-multi-hub-safe, but a spoke copy still lands in the source’s own namespace — no remote selector fan-out yet, and no per-target namespace override. Those are the next features, both additive. The resync above is a deliberate stand-in for real remote drift correction until it arrives.
Get It
Replikate 1.2.0 is BSD 3-Clause at github.com/cwolsen7905/replikate, with spoke setup in deploy/spoke-rbac.yaml and the full history in the CHANGELOG. If you’re already running cross-cluster on 1.1, 1.2 is a quiet upgrade that makes it a lot harder to hurt yourself.