Files
home-network-k3s-impl/infra/ansible/inventory/hosts.ini
T
jakman85andClaude Opus 5 b19c5db9ae feat(prod): node 3 onboarded — and it is NOT immune; BIOS-setting hypothesis disproven
k3s-prod-3 online at the planned 192.168.2.12 (serial 8CC015307N, same chassis/BIOS/CPU),
enabled in inventory, vaulted become confirmed, package floor installed. Health: 0 FAIL /
4 WARN / 14 PASS / 1 SKIP — the only node with zero FAILs. WD Blue WDS500G2B0A SATA SSD at
100% life, 3213 h, 0 grown bad blocks, 0 erase fails. Matched Crucial 2x16 GB pair — the only
node with symmetric memory.

*** NODE 3 IS NOT IMMUNE TO THE §0b BUG. *** Its firmware DOES publish a MAT
(MEMATTR=0xbeece018); the ABSENCE of a memattr diagnostic line means the kernel read a VALID
table, which is the opposite of immunity. There are THREE outcomes, not two: bail-out on a
garbage version >2, a valid parse with a sane size, and the catastrophic path. Seven boots across
three machines show the MAT address MOVES BOOT-TO-BOOT on every node with settings constant, and
each node has been seen on more than one path. Blast radius widens from two nodes to ALL THREE;
all three need the >= 6.13 kernel.

THE CORRUPTING BYTES ARE THE OS BOOT CHAIN, not firmware and not DRAM: garbage version
1463964749 = 0x5742544D appeared on node 2 AND node 3 — two different machines, identical bytes.
The overwriter is the shared shim/GRUB/EFI-stub/initrd placement writing into the page the
firmware pointed at. So initrd size, module set and boot device are part of the causal chain.

BIOS-SETTING HYPOTHESIS: DISPROVEN, and the three-way diff cannot support it even in principle.
All 240 attributes read on all three; firmware identical fleet-wide. The discriminating set (both
working nodes agree, broken differs) is seven attributes, of which:
  - Fast Boot is the ONLY live candidate, and only as a probability-shifter — HP documents it
    'skips some preboot initialization steps', which changes the heap top and hence where the MAT
    lands. Different mechanism from the previously-killed MRC-caching theory; that stays dead.
  - Legacy Boot Options is AFFIRMATIVELY INERT: 'Configure Legacy Support and Secure Boot' =
    'Legacy Support Disable and Secure Boot Enable' on ALL THREE, Option ROM Launch Policy =
    'All UEFI' on all three. No CSM is built on node 2 either. A naive diff gets this wrong.
  - the rest are coincidence. After Power Loss is ELIMINATED by node 3, which works and shares
    node 2's Power Off — though it is now a §1 checklist violation on TWO nodes.
FATAL CONFOUND: the 1=3!=2 partition is perfectly collinear with node 2 being the only
single-DIMM AND the only NVMe-booting node. Adding node 3 did not break the confound, it DOUBLED
it. No setting can be the cause anyway: node 2 booted good once and bad twice with settings held
constant.

EXPERIMENT DESIGN RECORDED because the obvious approach is statistically worthless: p(bad) is
known only to n=3, 95% CI ~0.09-0.99, so even 8 clean boots prove nothing. Elimination needs ~20
boots/arm; an odds shift needs ~50. Zero-touch first step: 20 reboots of node 2 unchanged, logging
MEMATTR address vs outcome — both failures were at exactly 0xbeeb0018 and all five successes
elsewhere, so if outcome maps onto a narrow address range the mechanism is settled without
touching anything.

ROLE BUG FIXED: node 3's WD drive reports a SMART threshold for only 1 of 25 attributes, and
smartctl OMITS the key rather than emitting 0 — summary.txt.j2 dereferenced a.thresh bare in five
places and the report task died, so no artifact was written. Added a thr() macro rendering 'n/a'
(deliberately not 0, which would read as a real ATA cannot-trip threshold). Would have hit any
drive of this class.

Also: three nodes, three different disk models, only one NVMe, and every node has a single disk
with no unpartitioned spare — so §2's identical-layout requirement is unmet on all three.

Serves: R-ENV-01, R-ENV-03, R-OBS-04. Task: T-7.1.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-03 22:32:25 -05:00

114 lines
7.2 KiB
INI

# Static inventory — k3s node IPs ALIGNED with infra/tofu defaults (Proxmox migration IPs),
# but the SSH target is the pfSense NAT (see NAT HAND-OFF below) — this file is
# HAND-MAINTAINED and must NOT be regenerated from `tofu output -raw ansible_inventory`.
#
# k3s_primary = the --cluster-init server (embedded-etcd founder + Cilium k8sServiceHost).
# k3s_secondary = the two servers that join it. All three are servers (HA embedded etcd).
#
# NAT HAND-OFF (LIVE): the nodes live on the Proxmox LAN at 192.168.1.50/.51/.52, and
# Ansible reaches them over the pfSense NAT at 192.168.2.102, port-forwarded as
# 2250 -> 192.168.1.50:22, 2251 -> 192.168.1.51:22, 2252 -> 192.168.1.52:22.
# Therefore `ansible_host`/`ansible_port` below are the NAT SSH target (192.168.2.102:225x),
# and each node's REAL 192.168.1.x lives in the `k3s_node_ip` var — consumed for the k3s
# --node-ip, the API/tls-san endpoint (k3s_api_host), and general in-cluster parity.
# This file is HAND-MAINTAINED for the NAT: do NOT blindly regenerate it from
# tofu output -raw ansible_inventory
# because tofu has no knowledge of the pfSense NAT and would overwrite the mapping below.
[k3s_primary]
k3s-dev-1 ansible_host=192.168.2.102 ansible_port=2250 k3s_node_ip=192.168.1.50
[k3s_secondary]
k3s-dev-2 ansible_host=192.168.2.102 ansible_port=2251 k3s_node_ip=192.168.1.51
k3s-dev-3 ansible_host=192.168.2.102 ansible_port=2252 k3s_node_ip=192.168.1.52
[k3s_servers:children]
k3s_primary
k3s_secondary
# The hypervisor/workstation host itself (target of host-prereqs.yml). Its become
# (sudo) password comes from the vaulted group_vars/hypervisor/vault.yml.
[hypervisor]
localhost ansible_connection=local
# VPN egress-gateway node(s) — run the node-level split-tunnel OpenVPN + kill-switch
# (ADR-0015, playbooks/vpn.egress.gateway.yml). Initially just k3s-dev-3 (single gateway;
# add a second host here for Cilium Egress Gateway HA). Members are already defined above
# with their ansible_host under k3s_secondary; this group only tags the egress role target.
[vpn_egress_gateway]
k3s-dev-3
# The Proxmox HYPERVISOR host itself (NOT a k3s node) — target of playbooks/backup-s3.yml,
# which stands up the off-cluster versitygw S3 backup target for Velero + CNPG. Reached DIRECTLY
# over SSH as root (its own 192.168.2.102:22, the pfSense NAT to the host's 192.168.1.9), so
# it overrides the cluster-wide `ansible_user: k3s` from group_vars/all.yml. Distinct from the
# `hypervisor` group above (that is `localhost` for host-prereqs.yml, a different machine).
[proxmox_hosts]
proxmox ansible_host=192.168.2.102 ansible_port=22 ansible_user=root ansible_ssh_private_key_file=~/.ssh/home_network
# =============================================================================
# PROD (k3s-prod-*) — Phase 7 PREP ONLY. See infra/PROD-PROVISIONING.md (T-7.1,
# especially §5/§6) and DECISIONS-LOG.md "PHASE 7 OPENED" (2026-08-31). ALL THREE
# nodes are now on the bench and SSH-reachable (`k3s-prod-1` 2026-08-31, `k3s-prod-2`
# 2026-09-01, `k3s-prod-3` 2026-09-03) — every HP EliteDesk 800 G5 Mini. This section
# is still inventory/group_vars scaffolding only: nothing beyond the read-only health
# check and the node-baseline PACKAGE floor has been run, and the operator has decided
# the CLUSTER stays uninitialized (no k3s install, no --cluster-init) until the §2 disk
# decision is closed (decision #2 in the DECISIONS-LOG entry above) — no
# single-server-etcd window, no later member-add dance.
#
# KEY DIFFERENCE FROM DEV: prod sits DIRECTLY on the 192.168.2.x LAN — no pfSense NAT
# hop in front of it. Dev's `ansible_host` above is a NAT target (192.168.2.102:225x)
# that port-forwards to the node's real 192.168.1.x address, which is why dev also
# carries a separate `k3s_node_ip` per host. Prod needs no such split: `ansible_host`
# below IS already the node's real, routable address, so `k3s_node_ip` is set to the
# SAME value (kept explicit, not omitted, only so the k3s_server role's hostvar lookup
# — see the GAP note below — has something correct to find once that gap is closed).
# `ansible_port` is the node's own real sshd (22). See group_vars/k3s_prod/vars.yml for
# the k3s_api_host override that reflects this directly-reachable topology.
#
# GROUP-NAME GAP: CLOSED 2026-08-31. The roles and playbooks used to hardcode the
# LITERAL dev groups (`hosts: k3s_servers` / `k3s_primary` / `k3s_secondary`, and
# `groups['k3s_primary']` inside roles/k3s_server + roles/cilium), so nothing could
# select the k3s_prod_* groups below. Both halves are now variables, deliberately kept
# separate because Ansible resolves them at different times:
# * WHICH HOSTS A PLAY RUNS ON — `k3s_hosts` (whole-cluster plays), `k3s_primary_hosts`
# / `k3s_secondary_hosts` (k3s.yml's split), each defaulting to the dev group.
# EXTRA VARS ONLY: a play's `hosts:` is templated before any host exists, so
# group_vars cannot be read there. Prod: `-e k3s_hosts=k3s_prod` etc.
# * WHICH GROUP A ROLE MEANS BY "MY PRIMARY"/"MY SERVERS" — `k3s_primary_group` /
# `k3s_servers_group`, set in group_vars/all.yml (dev) and group_vars/k3s_prod/vars.yml
# (prod), so roles resolve their own environment with NO per-run flag — the same
# mechanism k3s_api_host already used.
# Running with no extra vars is byte-for-byte the dev behaviour it always had. See
# infra/ansible/README.md "Targeting prod" and PROD-PROVISIONING.md §6.
# =============================================================================
[k3s_prod_primary]
k3s-prod-1 ansible_host=192.168.2.10 ansible_port=22 k3s_node_ip=192.168.2.10
[k3s_prod_secondary]
# Uncomment a node only once it is actually on the bench and answering SSH, so no play can
# target a host that isn't there. k3s-prod-2 came online 2026-09-01 (imaged identically to
# node 1: same HP EliteDesk 800 G5 Mini, same BIOS R21 02.26.00, same i7-9700T, same
# partition layout) but with a SAMSUNG PM9A1 NVMe instead of node 1's 2011 SATA C400 — the
# nodes are NOT hardware-symmetric, which matters for the §2 Longhorn/etcd disk decision.
# k3s-prod-3 came online 2026-09-03 at the planned 192.168.2.12 — same HP EliteDesk 800 G5
# Mini, same BIOS R21 02.26.00, same i7-9700T, 2x16 GB (a MATCHED Crucial pair, unlike node
# 1's mismatched Microns), full 31.1 GiB usable. It is NOT structurally immune to the §0b
# memattr bug: its firmware DOES publish a MAT (MEMATTR=0xbeece018) and the kernel merely
# happened to parse it cleanly this boot, so node 3 is on the same lottery as node 1 and
# needs the same >= 6.13 kernel. Its disk is a THIRD distinct model (WDC WDS500G2B0A SATA
# SSD) — all three nodes differ on disk, so the §2 Longhorn/etcd symmetry gap is now
# three-way, not a node-1 outlier.
# NOTE: node 3 also has wlp1s0 up on 192.168.2.195 (wifi). `ansible_host` is deliberately
# the WIRED eno1 address — the same directly-routable 192.168.2.x LAN as nodes 1 and 2.
k3s-prod-2 ansible_host=192.168.2.11 ansible_port=22 k3s_node_ip=192.168.2.11
k3s-prod-3 ansible_host=192.168.2.12 ansible_port=22 k3s_node_ip=192.168.2.12
# Umbrella group group_vars/k3s_prod/ attaches to (mirrors dev's [k3s_servers:children]
# pattern; named to match the group_vars directory so Ansible auto-loads it).
[k3s_prod:children]
k3s_prod_primary
k3s_prod_secondary