Replikate Grows Up — Instant Drift Correction, Metrics, and a Webhook
Picking Up Where the Launch Post Left Off
A couple of days ago I wrote up Replikate, a small Kubernetes controller that replicates ConfigMaps and Secrets across namespaces from a single annotation on the source object. It was an honest MVP: it did the core job well, and I ended the post by naming three limitations rather than pretending they weren’t there.
Then I kept going. Replikate is at v0.4.0 now, and the shape of the thing has changed enough that a follow-up is warranted — especially because one of those three named edges is gone, one is now understood well enough to be a scheduled fix rather than a hand-wave, and one is exactly where I said it would be.
Here’s what landed.
The Big One: Drift Correction Is Now Instant
The launch post admitted it plainly: “If you hand-edit a managed replica, it’s corrected on the next source change or the controller’s periodic resync — not instantly.” That’s the kind of caveat that sounds minor until someone kubectl edits a replicated Secret at 2am and it stays wrong until the next resync.
That’s fixed. Replikate now watches its own managed copies. Every copy carries a managed-by=replikate stamp and origin labels pointing back at the source; a change or deletion on any of them maps straight back to a reconcile of the source, which restores the copy immediately. Delete a replica and it reappears. Hand-edit one and it snaps back. No waiting for a resync.
The nice part is how little machinery it took: the copies were already labeled, so the drift watch is just a second event source feeding the same reconcile loop, filtered to Replikate’s own objects so it can’t spin into a replication loop.
Events You Can Actually See
A controller that does its work silently is a controller you can’t debug. Replikate now emits Kubernetes Events on the source object, visible right in kubectl describe:
Replicatedwhen copies are written or pruned,Skippedwhen an unmanaged object blocks a copy (more on that below),InvalidSelectorwhen the annotation’s selector doesn’t parse.
No more grepping controller logs to find out what happened to your ConfigMap.
Observability: Metrics and a ServiceMonitor
Replikate now exposes Prometheus metrics on its existing metrics endpoint:
replikate_reconcile_total{kind,result}— reconciles by kind and success/error,replikate_copy_operations_total{kind,operation}— creates, updates, adoptions, and deletes.
The Helm chart ships a metrics Service and an optional ServiceMonitor, so if you’re running the Prometheus Operator you get scrape config for free by flipping one value.
Safety Rails for Something That Writes Everywhere
A controller that can write into every namespace in your cluster earns more safety knobs:
- Namespace exclusions —
--exclude-namespaces(a Helm value too), defaulting to protectkube-system,kube-public, andkube-node-lease. Copies never land in a namespace on the exclusion list no matter what the selector says. - Chart hardening — an optional
PodDisruptionBudget, apriorityClassName, and documented pod anti-affinity, so the controller behaves during node drains and scheduling pressure.
Scale: An Index Instead of a Scan
When a new namespace appears, Replikate has to figure out which sources might need to populate it. The MVP did that by listing every ConfigMap and Secret in the cluster and filtering. Fine at small scale, wasteful at large.
Now there’s a field index keyed on “is this a source.” A namespace event resolves the affected sources in time proportional to the number of sources, not the total object count in the cluster. On a big cluster with a handful of replicated objects, that’s the difference between a targeted lookup and a full sweep.
Catch Mistakes at Apply Time: A Validating Webhook
The MVP handled an invalid selector by logging it and moving on — the source just silently didn’t replicate. Now there’s an optional validating admission webhook that rejects a ConfigMap or Secret whose sync selector doesn’t parse at apply time, so the mistake bounces back to whoever ran kubectl apply instead of disappearing into a log line.
It’s opt-in (--enable-webhook / a chart value, off by default), because webhooks mean TLS certificates and a hard dependency on the request path. When you turn it on, the chart provisions the serving certificate through cert-manager and — importantly — the webhook configuration exempts the controller’s own namespace and kube-system, so a webhook outage can never wedge its own recovery or the control plane.
Tests That Actually Run the Thing
The MVP shipped without much of a test story. That’s no longer true:
- A unit suite (controller-runtime’s fake client) covers fan-out, adoption of legacy config-syncer copies, cleanup, drift, exclusions, and metric recording.
- An
envtestintegration suite runs the real manager against a throwaway API server — real watches, real cache, real field index — and asserts selector fan-out, drift restore, and fan-out to a namespace created after the source. It runs in CI on every pull request.
The integration layer matters more than it sounds. Fake-client tests prove the logic; only a live API server proves the watches and the index are actually wired the way I think they are.
Revisiting the Three “Honest Edges”
On day one I named three limitations. Here’s the honest scorecard two days later:
-
“Intra-cluster only.” Still true — and still deliberate. Cross-cluster replication expands the annotation contract, so it belongs after a stable 1.0, not before. It’s on the roadmap, unshipped, exactly as promised.
-
“Drift is corrected on the next resync, not instantly.” Gone. This is the drift-correction watch described above. The caveat no longer applies.
-
“Two same-named sources targeting one namespace collide.” This one got more honest. Digging into it for the write-up, I found it’s worse than a naming inconvenience: two sources named
shared-configin different namespaces that both target a third don’t just collide once — they enter a clobber war, each rewriting the other’s copy on every reconcile, forever. So it’s now a scheduled fix, not a footnote: a conflict guard that detects a copy belongs to a different source, refuses to overwrite it, and emits aConflictevent — first writer wins, the loser gets a clear error instead of a silent fight. It’s the headline item for v0.5.
That last point is the one I’m happiest about. The best thing a launch post’s “limitations” section can do is turn into a bug tracker.
Where It’s Headed
Replikate is genuinely close to a 1.0. The things I said a 1.0 needs — real test coverage, observability, safety rails — are in. The remaining gates are small and specific: ship the conflict guard, and run the webhook end to end on a real cluster with cert-manager (the one path envtest can’t fake). After that, the <domain>/sync annotation contract gets declared frozen and the version number stops apologizing.
Get It
Replikate is BSD 3-Clause licensed and on GitHub at github.com/cwolsen7905/replikate, with a CHANGELOG and ROADMAP that say exactly what shipped and what’s next. Annotate a source, helm install, and stop writing the kubectl loop.
Same small tool doing one irritating thing well — just with a lot more spine than it had on Thursday.