You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/concepts.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -469,7 +469,7 @@ All conditions carry `observedGeneration` so consumers can tell whether a condit
469
469
470
470
### Observed member version
471
471
472
-
`spec.version` is *intent* — it pins the image tag (`v<version>`) and drives the restore version-compat gate. What etcd is **actually running** is a separate, observed fact. Once a member's Pod is Ready, the member controller reads the running version from that member's own etcd endpoint (the Maintenance `Status` RPC) and records it in `EtcdMember.status.version` (surfaced as the `Running` print column). The read is best-effort: a dial or RPC failure leaves the previous value in place and never affects `Ready` — readiness stays driven by Pod readiness and member-ID discovery alone.
472
+
`spec.version` is *intent* — it pins the image tag (`v<version>`), which is also the etcd image whose `etcdutl` the restore agent runs. What etcd is **actually running** is a separate, observed fact. Once a member's Pod is Ready, the member controller reads the running version from that member's own etcd endpoint (the Maintenance `Status` RPC) and records it in `EtcdMember.status.version` (surfaced as the `Running` print column). The read is best-effort: a dial or RPC failure leaves the previous value in place and never affects `Ready` — readiness stays driven by Pod readiness and member-ID discovery alone.
473
473
474
474
When the observed version diverges from the member's intended `spec.version`, the member surfaces `VersionDrifted=True/VersionMismatch`; when they agree it is `False/VersionMatched`; when intent is not yet known (`spec.version` empty) the condition is left unset. This condition is **informational only** — the operator does not act on it (it never keys reconciliation off the observed value). It exists so intent-vs-reality drift is *detectable rather than assumed*, which is the prerequisite for safely reconsidering a per-cluster image/version override.
475
475
@@ -491,11 +491,11 @@ Snapshot integrity note: a `Maintenance.Snapshot` stream carries no appended has
491
491
492
492
### Restore (`spec.bootstrap.restore`)
493
493
494
-
Restore is a first-bootstrap-only path, not a controller that mutates a running cluster. When `spec.bootstrap.restore.source` is set, the cluster controller stamps the `RestoreSpec` onto the bootstrap **seed** `EtcdMember` (only the seed — scale-up members join the live cluster normally). The member controller's `buildPod` then prepends a `restore` init container (the operator image, `manager restore-agent`) that shares the etcd data volume. Before etcd starts, the agent fetches the snapshot (S3 download / PVC read) and runs `etcdutl` `snapshot.Restore` into the data dir, using the seed's exact identity — member name, `--initial-cluster`, cluster token, peer URL — so etcd accepts the rebuilt data dir.
494
+
Restore is a first-bootstrap-only path, not a controller that mutates a running cluster. When `spec.bootstrap.restore.source` is set, the cluster controller stamps the `RestoreSpec` onto the bootstrap **seed** `EtcdMember` (only the seed — scale-up members join the live cluster normally). The member controller's `buildPod` then prepends two init containers that share the etcd data volume: `install-tools`(operator image) copies the operator binary onto a shared volume, and `restore` runs that binary (`manager restore-agent`) — but from the **target etcd image**, so it reaches the version-matched `etcdutl` bundled there. Before etcd starts, the agent fetches the snapshot (S3 download / PVC read) and execs `etcdutlsnapshot restore` into the data dir, using the seed's exact identity — member name, `--initial-cluster`, cluster token, peer URL — so etcd accepts the rebuilt data dir.
495
495
496
496
The init container is idempotent: it no-ops if the data dir already contains a `member/` directory, so Pod restarts after first boot leave live data untouched and never re-download. Because `spec.bootstrap` is CEL-immutable post-create, the restore intent can't be added to or changed on a live cluster — restore happens once, at birth, or not at all. A restored cluster gets a fresh etcd cluster ID: it is a new cluster seeded with old data, not a continuation.
497
497
498
-
The rebuild uses the `etcdutl` vendored into the operator image, whose on-disk storage format is minor-version-specific. So restore requires `spec.version` to match that `etcdutl`'s minor (currently etcd **3.6.x**): the agent reads `spec.version` (passed as `ETCD_VERSION`) and **fails the restore early** with an actionable message if the major.minor differs, rather than rebuilding a data dir an older etcd would fail to boot. Restoring into a different minor means using an operator build whose `etcdutl` matches. (Non-restore clusters are unaffected — this gate only fires on the restore path.)
498
+
The rebuild's `etcdutl` is the one bundled in the target etcd image (`v<spec.version>`), not one compiled into the operator. Since a data dir's on-disk storage format is minor-version-specific, running the `etcdutl` that ships with the very etcd that will boot on the result keeps the two in lockstep by construction — so restore works for any etcd version the operator supports, not only the operator's own minor. That lockstep is `etcdutl`↔etcd only: the snapshot's own origin version is neither recorded nor checked, so restoring a snapshot taken from a newer etcd into an older `spec.version` remains unsupported (see the [restore runbook](operations.md#restoring-a-cluster-from-a-snapshot)). The two init containers exist to bridge two distroless images that share no binaries: the etcd image has `etcdutl` but no way to copy it out, so `install-tools` brings the operator binary to the etcd image instead.
499
499
500
500
This idempotency relies on the data dir being **persistent**. Restore is therefore rejected (by CEL) together with `spec.storage.medium: Memory`: a tmpfs data dir is wiped on every Pod restart, which would defeat the `member/`-exists guard and silently re-restore the original snapshot — reverting any writes since the restore, or breaking a multi-member cluster whose other members already moved past the restored cluster ID. Restore onto memory-backed storage is unsupported; use a PVC-backed cluster.
0 commit comments