Repository navigation
docs(design): bring your own cluster and per-cluster state - #1288
leninmehedy wants to merge 20 commits into
Conversation
Add a design for proving a cluster was installed by us before trusting it, for using an operator-supplied cluster through the Kubernetes API only, and for keeping state per cluster under state/hosts/local and state/clusters/<uid>. Covers the identity gate, the state layout and migration, API-only external mode, local development with Kind, and the effect on CI lanes. Refs #1287 Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
…install Describe the state layout in terms of the per-component files from #1231 and #1245: hosts/local holds all component files, and clusters/<uid> holds only the cluster-scoped ones. Rewrite the migration as a move of component files that runs after the per-component split. List the component files that mix host and cluster fields, archive cluster state at uninstall, and make a stale-state install stop and ask the operator to clean up instead of moving state automatically. Order the delivery plan after #1235. Refs #1287 Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
Add a Lease per component per cluster, in the component's namespace, taken by mutating commands in both provisioned and external mode so two operators cannot change the same component at once. Build it on client-go leaderelection, wrapped in internal/kube/lease, with fail-fast holder details and context cancellation when the lease is lost. Add it to the goals, decisions, delivery plan and open items. Refs #1287 Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
…nd field guard Add a small hosts/local/host.yaml identity file (name, machine ID, the cluster this host provisioned) that the identity gate reads first, and a global provisioner.yaml for the provisioner and state model versions, so a machine with only external clusters (for example a Mac) still has a home for the data startup migrations depend on. Move those fields out of the host-level system file proposed in #1245. Drop the legacy backfill in favour of a catalog checksum check, explain the external-then-local case and how a cluster is identified to a human, and show the layout with the profile, firewall and system files. Rewrite the host-bound field section: same component files in cluster directories, a writer guard for host-only fields, and the profile kept with its target. Add non-goals for the cluster-side lock, and tidy the migration risks, delivery plan, decisions and open items to match. Refs #1287 Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
Add `kube cluster connect` as the one command that registers a cluster (test the connection, read the cluster ID, check permissions, bind the alias), with `list` and `info`, and implicit registration for non-interactive runs. Exempt `connect` and `list` from the identity gate. Add a prior-art section on hiero-ledger/solo (alias to context map, in-cluster state ConfigMap and validator, lease) with what we use and what we avoid, and note the in-cluster state shape and cluster grouping as follow-ups. Refs #1287 Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
State that a cluster passing the provisioned proof registers without `kube cluster connect` (at install for new clusters, on the gate's first successful pass for older installs), so the unregistered-cluster rule applies only to external clusters. Describe `connect` as an optional eager registration for older installs. Refs #1287 Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
State what the design gives CI (fewer KVM VMs, isolated parallel jobs, no privileged runners for most tests, external mode tested on every PR) and what it does not cover (host-level tests, the tag split, block node storage). Give the order: record a baseline, start the Kind lane with API-only tests, add block node, and build the host-level lane in parallel. Refs #1287 Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
…ters Make KVM a nightly job that a maintainer enables by hand for a PR that needs a real VM, instead of starting it from path filters. Path filters may later suggest it but do not start it. Refs #1287 Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
Replace the manual-only wording: KVM starts automatically when a PR touches files only a real VM can check (using the existing paths-filter job), by hand through a dispatch or label for anything else, and nightly as a safety net. Add the starting candidate paths and the cost trade-off of a broad filter. Refs #1287 Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
…ommand needs Separate local evidence, recorded identity (cluster ID, server address and CA fingerprint) and live identity, so a broken local cluster is still recognised as ours and never treated as external. Replace the overlapping outcome table with ordered rules that cover a wrong machine ID, a rebuilt cluster, an unreachable API and unreadable evidence. Replace the exempt command list with a per-command need (none, connect, install, host, api, host+api), so uninstall and the host-level commands work on a broken cluster. Record the identity on the first successful pass for older installs, and add a threat-model note. Refs #1287 Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
Clarify that only api/host+api needs run the full outcome table and the host need is an offline-only check. Match recorded identity on the CA fingerprint, with the server address as fallback only. Give provisioner.yaml a cli and daemon section so each binary records its own version and both detect skew. Note that fencing needs ctx-cancellable steps and that the gate reads state through the #1235 loader. Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
Reshape the section 5.2 table into a precondition plus three branches (target matches recorded identity, nothing recorded, not our cluster), so the "first match wins" precedence and the split-brain reasoning read off the structure. Stable rule numbers are kept for cross-references. Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
|
|
||
| 1. Never trust a cluster as ours unless we can prove we installed it on this machine. Check this as early as possible, and stop if the proof fails. | ||
| 2. Let an operator explicitly point the CLI at any cluster (`--kubeconfig`) and use it for API-level work: install, upgrade and uninstall of block node, consensus node and the other cluster components. | ||
| 3. Keep state separate per cluster, so switching between clusters, or forgetting a flag, can never mix or reset state. |
There was a problem hiding this comment.
given solo-provisioner is a tool to be run in a single node cluster, should we avoid the complexity of running with multiple clusters? I see the benefit in trying to parallelise tests by using multiple clusters, but I wonder how worth it those tests would be if they are all sharing the same state file.
There was a problem hiding this comment.
given solo-provisioner is a tool to be run in a single node cluster
This assumption is now relaxed a bit in the sense that someone can deploy resources in remote/external/Kind clusters with no access to the underlying bare-metal (gated under --experimental feature). This is primarily for various testing scenarios in local-dev/CI/CITR so on. We are adding proper external cluster support (with locks and in-cluster audit records) rather than leaving a risky loophole (currently available) or closing it altogether.
if they are all sharing the same state file
When we use multiple clusters, the state files will be separate for each cluster (state managedment will be refactored to support this). So they are not sharing the same state file.
/opt/solo/weaver/state/
└── clusters/
├── <cluser-1-uid>/
│ ├── cluster.yaml # cluster-scoped components only
│ ├── blocknode.yaml
│ ├── consensus.yaml
│ └── teleport.yaml
├── <cluser-2-uid>/
│ ├── cluster.yaml # cluster-scoped components only
│ ├── blocknode.yaml. # cluster-2 has blocknode only
└── index.yaml # complete view of every known cluster
Please let me know if you see any other issues
…experimental Add a top banner and a decision making external mode (--kubeconfig against a cluster weaver did not provision) a provisioner-test-only feature, not for production and not encouraged in operators' CI or clusters. Reuse the existing common.GateExperimental gate (feature external-cluster): external mode and kube cluster connect require --experimental or SOLO_PROVISIONER_ENABLE_EXTERNAL_CLUSTER and are refused otherwise. Reframe goal 2, section 7, the Kind section and the CI section to match. Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
|
Thanks Lenin for another great design, a few thoughts:
|
"Identity" was overloaded across the host, the cluster, the recorded host-to-cluster binding and the target. Rename the gate to the provenance gate and adopt one noun per thing: HostIdentity (machine-id), ClusterIdentity (kube-system UID), provision record (what this host recorded about the cluster it provisioned), Target, and Origin (the provisioned/external verdict; a refusal is a stop, not an origin). Add a Terms table to section 5. Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
Add section 5.0 (startup order): the three state tiers, the full pre-run sequence, and why the provenance gate is pinned between the filesystem migrations it depends on and the cluster migrations that depend on it. Add section 8.1 (startup migrations split around the gate): ScopeStartup stays filesystem-only, a new ScopeStartupCluster runs cluster-touching migrations after the gate (provisioned-only, skipped in external mode), and the three Cilium migrations move there. Add a FAQ (section 15) covering the recurring questions: gate-after-migrations, no cluster work in ScopeStartup, not a security boundary, provenance vs identity, the narrow-only allowed-origins allowlist, external mode being test-only, behaviour with no cluster, and how commands opt in. Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
…onal Rule ids (P.1, A.4, ...) appear in logs, hints and the doc, so a new rule takes the next free number in its branch and keeps its evaluation position in the table rather than renumbering existing ids. The numeric suffix is allocation order, not evaluation order. Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
|
Thanks @alex-au for the feedback. Below are my responses to your questions.
Good question. I have renamed Identity -> Provenance since Identity term is overloaded.
flowchart TD
cmd["Command, e.g. block node install<br/>declares need: host+api"] --> prerun["Shared pre-run<br/>root PersistentPreRunE"]
prerun --> fsmig["Filesystem migrations<br/>ScopeStartupFilesystem"]
fsmig --> rpg["RunProvenanceGate<br/>common helper"]
rpg --> collect
subgraph prov ["internal/provenance"]
collect["defaultCollector.Collect<br/>reads host + cluster into Input"] --> gate["Gate(need, Input)<br/>need policy check"]
gate --> eval["Evaluate(Input)<br/>P.1 then branch A/B/C"]
eval --> allow["Allowlist (modes.go)<br/>narrow only"]
end
gate -. policy stop .-> stop
eval -. rule stop .-> stop
allow -->|stop / not allowed| stop["Stop: doctor message + hint"]
allow -->|external| clustermig["Cluster migrations<br/>ScopeStartupCluster (api / host+api)"]
allow -->|provisioned| hostmig["Host migrations<br/>ScopeStartupHost"]
hostmig --> clustermig
clustermig --> bll["BLL handles the intent<br/>command proceeds"]
I have updated the design doc based on my current findings/understanding. We may discover few more ambiguity as we implement, but it is hard to predict all such ambiguity unless we get into the trench. One more thought, this design doc gives us a north star, but it is not a full proof solution doc yet. Rather, we need to keep it as a live design doc and update as we identify new blockers. I have added a FAQ section so that we can track various questions and decisions/answers that helped us to evolve it.
Current thought is that we trigger error and stop, just keep it simple in the first MVP. We then let the user determine what to do since we cannot recover from such error automatically.
Commands that need to observe the cluster (api / host+api) would block if API is down. It must not block the offline recovery/uninstall commands (need = 'host') — those don’t observe the cluster, so a down API shouldn’t stop them.
I don't have a concrete idea on how to differentiate Therefore, the provisioner state was originally designed as a cached state since that's easier to reason about, but with the tradeoff of keeping it in sync. Idea was that if all resources are managed through solo-provisioner, we can sync the cached state as our last known state. So the kubernetes cluster is the
Having said, that we can still support Please let me know if I missed anything.
Each cluster will have a new configmap ( |
Rewrite decision 4: all state is a last-known cache, there is no desired state. Reality (cluster + host) is the source of truth and every field is re-derivable; reconciliation has three outcomes (match, confirmed-drift, unobservable-error), where unobservable is never treated as missing (the #1193 distinction). Add two FAQ entries covering it. Fold in resource ownership (section 7): ownership is authoritative on the resources themselves via app.kubernetes.io/managed-by: weaver plus ownerReferences; uninstall deletes only labeled resources and never touches unlabeled ones (so a foreign Helm release is left alone). weaver-remote-config is the audit log, not the ownership authority, and is written under the per-component lease. Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
Match the implementation (PR #1298): section 8.1 and 5.0 now describe ScopeStartupFilesystem (pre-gate, state only), ScopeStartupHost (post-gate, host-system, host-verified/offline) and ScopeStartupCluster (post-gate, cluster API, provisioned + api need). Add the pre-run ordering, the version-record-after- full-set rule, and that self-install runs filesystem + host migrations only. Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
Add a living story breakdown derived from the delivery plan (section 11) and the spike: five milestones with dependencies, macOS-vs-VM testability, MVP flags, the critical path (per-component state work gates the state layout), and what can proceed in parallel now. Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
alex-au
left a comment
There was a problem hiding this comment.
Thanks Lenin for clearing up all of my feedbacks.
One small concern about 4th item - resource ownership: Helm already uses app.kubernetes.io/managed-by: Helm to track its resources. Not sure setting it to weaver would interfere with Helm? Perhaps we can consider using a separate weaver specific label and keep Helm's own ownership metadata intact.
Design doc for #1287. No code changes.
IMPORTANT: This external cluster support is for provisioner test only feature. We don't encourage anyone to use it in their CI or cluster
Summary
admin.conf, IP).--kubeconfig,--context,--cluster-name, and an explicitsolo-provisioner kube cluster connect(withlistandinfo). External mode only talks to the Kubernetes API.hosts/local(with ahost.yamlidentity file),clusters/<uid>, and a globalprovisioner.yaml. It builds on the per-component state files in refactor(state): split state.yaml into per-component files with a migration #1231 / refactor(state): split state.yaml into per-component files with a migration #1235 and state: split machine.yaml by owner and flush it only from commands that mutate it #1245.leaderelection.~/.solo-weaver/state).Please review
Refs #1287