Repository navigation
v0.2.11
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--labelsflag asinvariant-platform.io/hypervisorand 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 gatechecks 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,onoroffstays 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-observabilityrestarts 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-bigint0.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) andreplica-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.11on each host, thensubstrate deploy-posture --applyfrom the host that reaches the others.
187 tests.