Skip to content

v0.2.11

Choose a tag to compare

@bradpenney bradpenney released this 27 Oct 00:20
· 4 commits to main since this release
v0.2.11
fc3fba3

v0.2.11 — a volume survives losing a machine, and the platform checks it

On 2026-10-23 every replica of every Longhorn volume was on one hypervisor. The settings promised replicas would reach the other one; nothing checked, and nothing made it happen. Losing that machine would have meant a restore, not a survival.

Three gaps lined up. The replica anti-affinity was per node, and three nodes on one machine satisfy it. No node carried a zone: the hypervisor was declared under invariant-platform.io/hypervisor, which Longhorn ignores, and the standard key had been left out on the belief that a kubelet may not set *.kubernetes.io/ labels on itself. That is false for this key — topology.kubernetes.io/zone is on the kubelet allow list. And with every node in one zone, Longhorn placed replicas by free disk.

Fixed

  • bug-200 — every node now registers with topology.kubernetes.io/zone=<hypervisor>, rendered in the same --labels flag as invariant-platform.io/hypervisor and with the same value, so the two keys cannot describe different domains. Kubelet applies it only when it creates the Node, so nodes built before this release need a one-time label (below).

Added

  • A storage line in posture-check. Every volume must hold a usable replica on every hypervisor, judged on where the replicas are rather than on how the cluster is configured — a correct label with replicas still piled up fails. A stopped replica of a detached volume counts; a failed one, or one on a node that no longer exists, does not. One finding per volume short of a machine, naming the volume and how many machines it reaches. Fewer than two hypervisors, or no volumes, is information and uncounted (ADR-199). 18 → 19.
  • substrate gate checks the zone label, separately from the hypervisor label, and prints the one-time remedy. It is not one of the roll's between-node checks, so it cannot halt a roll part-way.

Changed — YAML is now parsed in safe Rust

  • serde_yaml_ng → serde-saphyr (ADR-204). The first build of this release stopped at the RustSec gate: unsafe-libyaml, the parser underneath serde_yaml_ng, is unmaintained (RUSTSEC-2024-0449, assigned 2026-10-12) — a machine translation of libyaml's C into unsafe Rust that will not be fixed. Rather than ignore the advisory, the parser is gone: serde-saphyr is written in safe Rust, and every YAML call goes through one module (substrate_core::yaml).
  • Booleans are strict: an unquoted yes, no, on or off stays a string, as it did before.
  • On the real configuration the old and new parsers produce identical plans for every deploy. The two YAML files substrate writes — Grafana's alert provisioning and the merged kubeconfig — carry the same data with their keys in sorted order, so the next deploy-observability restarts Grafana once.
  • The advisory gate now runs on every push and daily, not only after a release exists, so the next new advisory is a red build on main rather than a release with no binary.

Security — RUSTSEC-2026-0387

  • p256 0.13 → 0.14 (with x509-cert 0.3, sha2 0.11). crypto-bigint 0.5, underneath p256 0.13, could run secret-dependent branches in operations meant to be constant-time — a timing side channel on the key that signs the just-in-time admin certificate requests. Published 2026-10-23, after this release's first build; fixed in crypto-bigint 0.7.6+. The generated key and certificate request were verified independently with OpenSSL (signature, subject, P-256, key matches request).

This release was first published without assets and has been rebuilt in place under the same number: nothing was ever installed from it.

Operator notes

  • The matching cluster-side change is in substrate_config: replica-zone-soft-anti-affinity: "true" (deliberately soft — with three replicas and two zones a hard rule can never place the third) and replica-auto-balance: "best-effort", which moves replicas that are already lopsided.
  • Nodes built before v0.2.11 need the zone label once, through break-glass: kubectl label node <node> topology.kubernetes.io/zone=<hypervisor>. The next roll makes it permanent.
  • install.sh v0.2.11 on each host, then substrate deploy-posture --apply from the host that reaches the others.

187 tests.