Cluster Stability Assessment & Path to 9.5
Assessment snapshot β 2026-09-23, taken immediately after the reliability-remediation programme (off-site DR, HA hardening, Vault Raft HA, tested restore drills, and the Authentik database cutover to CloudNativePG). This page is the honest, evidence-based stability rating of the platform as it stands today plus the concrete plan to close the remaining gaps.
Score: ~8.5 / 10 β for what it actually isβ
A from-scratch, bare-metal, 6-node k3s platform on 5 second-hand ThinkPads + one 2012 MacBook Pro, driven entirely by GitOps. That is genuinely high for its class. Two honest caveats frame the number:
- The 8.5 is a grade as a self-hosted / portfolio platform. Graded as "run a regulated bank on this," it is more like a 6.5 β capped by single-site, single-control-plane, and consumer hardware.
- It is 8.5 now. Before this week's remediation it was closer to a 6 (single Vault, single ingress, no off-site data, untested restore, single-primary SSO DB).
What earns the score (the strong parts)β
- Day-to-day it stays up and self-heals. Longhorn auto-failover, Argo CD
selfHeal, KEDA + health probes, node-problem-detector. Incidents happen, but the platform recovers. - Detection is excellent. SLO recording/alerting rules, a dead-man's switch (healthchecks.io), and out-of-band SES alerting β it tells you when something breaks, even mid-incident.
- HA now exists where it counts. Vault 3-node Raft, ingress 2Γ + PodDisruptionBudgets, Alertmanager 2Γ, Cilium operator 2Γ, and the SSO database (Authentik) on a 2-node CloudNativePG cluster with off-site PITR.
- Recoverable. Off-site Cloudflare R2 backups (volume data + Kine control-plane state) + a tested restore drill + a scripted control-plane rebuild β the difference between "hope" and DR.
- Strong guardrails. Gatekeeper deny policies, signed images + SBOM, default-deny NetworkPolicies, CODEOWNERS-gated GitOps.
What caps it below ~9 (honest, structural)β
- Single k3s control plane (SQLite/Kine on
set-hog). Its loss was made a bounded, drilled event (tested restore + scripted rebuild) β but it is still one node. True embedded-etcd HA is deferred. - Single site + single power/network domain. A power blip, or the controller (NAT + MinIO + Cloudflare tunnel) going down, still hits everything at once.
- Aging, heterogeneous hardware β especially
swift-mac(a 2012 MacBook), a documented weak link. Consumer disks/RAM, no ECC, no redundant power. - Single ingress VIP β 2 controller replicas now sit behind it, but it is one L2-announced address.
:::note These are physics, not engineering-quality gaps The remaining points are not about how the platform is built (that discipline is already high) β they are the cost of one site, one control-plane node, and old hardware. Those are expensive, not hard, to fix. :::
Path to ~9.5 β Resolution Plan
Three moves close the structural gaps. Each is scoped with options, a recommendation, effort, cost, and risk. They are independent and can be done in any order; #2 is the cheapest immediate win.
1. Remove the control-plane SPOF β 3-server embedded-etcd HAβ
Goal: eliminate the single k3s control plane so the loss of one server node no longer stops the API.
Hard constraint: k3s has no in-place SQLiteβembedded-etcd migration and you cannot add a second server to a SQLite datastore. So HA is a planned rebuild, not a config toggle.
| Option | What | Effort | Downtime | Recommendation |
|---|---|---|---|---|
| A β 3-server embedded-etcd HA | Promote 3 nodes to servers (--cluster-init on the first, --server join on the other two), front the API with a Cilium L2 VIP (or kube-vip) and repoint cilium k8sServiceHost to it. Odd quorum of 3 tolerates 1 server loss. | High | A maintenance window (workers keep running; API blips during the rebuild) | β Target when 3 capable server nodes are free |
| B β Single server + tested restore (current) | Keep one server; rely on the off-site Kine backup + the scripted restore-control-plane.yml drill. Converts a catastrophic SPOF into a bounded-RTO event. | Low (done) | n/a | Interim floor β already in place |
| C β External HA datastore | Point Kine at a replicated external Postgres. | Medium | n/a | β Not recommended β just moves the SPOF unless the DB is itself HA |
Migration approach for A (no in-place path): treat it as a rebuild β most cluster state is reproducible from Git; the irreplaceable part is Kine (Secrets, CRD instances). Snapshot (Velero + Kine backup), stand up the 3-server etcd control plane, rejoin the workers, and rehydrate from Git + Longhorn. Validate against the existing restore drill first.
Prerequisites: 3 nodes with enough headroom to run control-plane components; the VIP wired into the Cilium L2 pool. Risk: Medium (full control-plane rebuild) β do it in a window with the restore drill proven.
2. Retire / de-risk the 2012 MacBook (swift-mac)β
Goal: remove the documented weak link from the critical path. swift-mac has a history of false
ReadonlyFilesystem signals, overload, and Longhorn attach wedges.
| Option | What | Effort | Cost | Recommendation |
|---|---|---|---|---|
| A β De-weight from Longhorn | In Longhorn, disable scheduling of new replicas on swift-mac (or drop its disk allocation / use node tags) so critical volumes (DBs, Vault) never place a replica there. Keep it as compute for stateless / non-critical pods. | Low | β¬0 | β Do now β immediate risk cut, zero spend |
| B β Replace with a modern node | Swap in another ThinkPad / mini-PC / NUC (MAAS-provision + join). | Medium | ~β¬100β300 (used) | β Best long-term, when budget allows |
| C β Remove entirely | Cordon β drain β delete; run on 5 nodes. | Low | β¬0 | Only if capacity is comfortable without it |
Recommendation: A now (de-weight β no cost, immediate), then B when a replacement is available; C if the cluster has capacity headroom without it. Risk: Low β de-weighting only changes replica placement; Longhorn rebalances non-disruptively.
:::tip β
Done β 2026-09-23 (Option A)
swift-mac set allowScheduling: false + evictionRequested: true in Longhorn. All critical
DB/Vault replicas migrated off (incl. ktayl-prod Postgres); no new replicas will land there. Two
re-creatable volumes (harbor-registry, Prometheus TSDB) finish draining in the background. swift-mac
remains a k8s compute node. (Longhorn Node CR is runtime state, not GitOps-tracked β re-apply the
patch if the node is ever reset.)
:::
3. Add a second failure domain β genuine DRβ
Goal: survive loss of the whole site / power / network domain. This is the DORA Art. 11β12
off-site-DR candidate already pre-vetted in cloud-adoption.md.
| Option | What | Effort | Cost | Recommendation |
|---|---|---|---|---|
| A β OCI always-free ARM node | Stand up an OCI free ARM VM in a different provider/region as (i) an off-site restore target and/or (ii) a standby k3s node β a second failure domain for DR. | Medium | Free (OCI always-free) | β Target β realises the DORA Art. 11β12 story |
| B β Off-site backup only (current) | Off-site data via the R2 mirror (volumes + Kine + Velero objects). Data DR, not compute DR. | Low (done) | ~R2 free tier | Floor β already in place |
| C β Full second-site / warm standby | A second cluster kept in sync. | High | β¬ | β Overkill for this platform |
Recommendation: A β an OCI free node as a real second failure domain (restore target + optional
standby), documented as the DORA Art. 11β12 realisation; B is already the baseline floor. Stay within
the β¬10/mo/provider cap and free-tier-first rule from cloud-adoption.md. Risk: Low β additive,
off-cluster; no impact on the running platform.
:::note β Decision accepted, provisioning gated β 2026-09-23 (Option A)
ADR written: minicloud-cloud adr-0001-oci-dr-second-failure-domain
β decision + design (A1.Flex always-free, Tailscale-join, restore-target-first) recorded. Provisioning
is blocked on prerequisites the owner must supply: an OCI tenancy, an API key in Vault
secret/platform/oci, a region with A1.Flex capacity, and an SSH key. Once those exist, the OpenTofu
module + budget alert + tofu apply follow (see the ADR's Apply plan).
:::
4. External observability anchor β detect total-site failureβ
Goal: a failure signal that leaves the single failure domain. The in-cluster monitoring stack (Prometheus/Grafana/Alertmanager) and CoreDNS all run inside the site β so a control-plane-node, LAN, ISP or power loss takes the monitors down with the platform, and no alert escapes. A cluster cannot report its own death; the fix is a black-box probe from outside.
Design: a small always-free external heartbeat probes the public URLs (through Cloudflare β tunnel β ingress β app) on a schedule, records state, alerts on the UPβDOWN transition, and serves a public status page that stays up even when all six laptops are down. It covers the failure scenarios the in-cluster stack structurally cannot β control-plane-node down, DNS down, ingress-node down, total site/power loss. It is a read-only observer with zero coupling to the platform (its exit test is a true no-op on the cluster), so it can never become a new SPOF.
Two hosting options were weighed β reuse the OCI DR node (Option A, simplest) vs AWS serverless
(Lambda + EventBridge + DynamoDB + CloudFront β recommended, because a monitor's value is independence
and hosting it on the DR target couples the two most safety-critical externals). This anchors on the
always-free tier per the 3-tier free model now codified in cloud-adoption.md (permanent architecture
β always-free only; 12-month tiers + credits are second-copies and bursts, each with a written exit test).
:::note π‘ Decision proposed, provisioning not started β 2026-09-26
ADR written: minicloud-cloud adr-0002-external-heartbeat-status-page
β design + option analysis + security items + exit test recorded. Awaiting owner approval of the
option (B recommended), a public hostname (status.devandre.sbs), and the probe target list; then the
Lambda handler + aws-heartbeat.tf + tofu plan follow. β¬0 steady-state (AWS always-free), inside the
existing β¬15 AWS budget.
:::
Summary β the 9.5 checklistβ
| Move | Removes | Effort | Cost | Priority |
|---|---|---|---|---|
| 3-server embedded-etcd control plane | Control-plane SPOF | High | β¬0 (needs 3 nodes) | When hardware allows |
De-weight / replace swift-mac | Weak-hardware link | Low β Medium | β¬0 β ~β¬200 | β De-weight done (2026-09-23); replace later |
| OCI second failure domain | Single-site / single-power SPOF | Medium | Free | β ADR accepted; provisioning gated on OCI account/creds |
None of these are engineering-quality fixes β the build discipline is already there. They buy down the physical risks (one site, one control-plane node, old hardware) that separate a strong self-hosted platform from a genuinely resilient one.