Files
home-network-k3s-impl/infra/ansible/playbooks/node-health.yml
T
jakman85andClaude Opus 5 e623d27e2d feat(ansible): parameterise target groups so prod reuses the roles unchanged (R-ENV-02)
roles/k3s_server, roles/cilium and every node-host playbook hardcoded the dev group
names k3s_primary/k3s_secondary/k3s_servers, so the prod inventory groups could not be
selected at all. That broke the R-ENV-02 promise that prod reuses the SAME roles, and
blocked PROD-PROVISIONING.md §6 outright.

TWO KNOBS, deliberately separate — Ansible templates a play's `hosts:` BEFORE any host
context exists, so group_vars is unreadable there, while roles run WITH host context:
  * which hosts a play RUNS ON — k3s_hosts, k3s_primary_hosts/k3s_secondary_hosts
    (k3s.yml's split), vpn_egress_gateway_hosts; each `| default(<dev group>)`, set by -e
    only. Follows node-health.yml's established `<name>_hosts | default(dev)` pattern.
  * which group a ROLE means by "my primary"/"my servers" — k3s_primary_group,
    k3s_servers_group in group_vars/all.yml (dev), overridden in group_vars/k3s_prod.
    Follows the k3s_api_host convention already in this repo. No per-run flag.

DEV IS UNCHANGED — the key evidence. --list-hosts/--list-tasks/--syntax-check captured
for all 9 playbooks at HEAD and after: sorted host lists IDENTICAL for all 9, same
primary/secondary split, same 6-play site.yml. (Raw --list-hosts PRINT ORDER varies run
to run; proven to be Ansible's own set ordering by running UNMODIFIED HEAD cilium.yml
four times -> 3,1,2 / 1,3,2 / 1,2,3 / 1,2,3. Hence sorted comparison.) --list-tasks
differs in exactly two files, only by added TAGS annotations and one new assert; no task
reordered, renamed or removed. Var resolution verified per environment with -c local:
dev -> k3s_primary/192.168.1.50/sans .50,.49,127.0.0.1,.51,.52; prod ->
k3s_prod_primary/192.168.2.10/vip .9/sans .10,.9,127.0.0.1,.11,.12.

BREAKS THE SMARTMONTOOLS CIRCULAR DEPENDENCY. node_baseline tasks are now tagged
packages/system/disks, so the package install can run alone:
  ansible-playbook playbooks/node-baseline.yml -e k3s_hosts=k3s_prod --tags packages
Skipping is the guard — no disks-tagged task is selected, so the
BLOCKED-pending-disk-decision paths are never templated, let alone passed to mkfs.
Belt-and-braces, a new disks-tagged assert fires first in a FULL run and names §2
explicitly instead of failing obscurely at mkfs. `tags: never` was deliberately NOT used
(it would change the default run).

FOOTGUN FOUND AND FIXED: roles/cilium, cluster_verify and flux_bootstrap all hardcoded
.cache/dev.kubeconfig — the exact file infra/devcontainer/dev.sh exports as $KUBECONFIG.
Once prod became targetable, a prod cilium/verify run would have SILENTLY OVERWRITTEN it,
leaving the operator's kubectl quietly answering from PROD. Now cluster_kubeconfig_name
(dev.kubeconfig -> prod.kubeconfig). Dev path byte-identical.

KNOWN, DOCUMENTED, NOT FIXED HERE: flux-bootstrap.yml retargets but roles/flux_bootstrap
shells out to bootstrap/flux/bootstrap.sh with a hardcoded --path=clusters/dev — a
bootstrap/ subtree change, not a target-group change (§6, cross-referenced to §7).
vpn.egress.gateway.yml is parameterised but prod has no egress group yet and would need
its own vaulted PIA credentials. --check against prod is still impossible until the prod
vault exists — even the packages-only run needs it.

Nothing was run against the dev cluster: --list-hosts/--list-tasks/--syntax-check, plus
debug/assert with -c local, only.

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

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-08-31 23:53:36 -05:00

33 lines
1.9 KiB
YAML

---
# Node hardware health — READ-ONLY inspection of a node's disks, memory, CPU, thermals,
# network and pressure-stall state. Installs nothing, changes nothing, safe to run at any
# time against dev or prod (see roles/node_health for the full contract).
#
# ./dev.sh run bash -c 'cd /work/infra/ansible && \
# ansible-playbook playbooks/node-health.yml -e node_health_hosts=k3s_prod'
#
# TARGET GROUP IS A VARIABLE. This play established that pattern; the rest of the node
# playbooks (node-baseline.yml, k3s.yml, cilium.yml, kube-vip.yml, verify.yml,
# flux-bootstrap.yml) now follow it and take `k3s_hosts` / `k3s_primary_hosts` /
# `k3s_secondary_hosts` instead of the literal dev groups they used to hardcode — see
# infra/ansible/README.md "Targeting prod". This one keeps its OWN var name because it is
# a standalone diagnostic you point anywhere, not a step in the bring-up chain: it must
# stay independently targetable (`-e node_health_hosts=<group>`) even while a bring-up run
# is scoped elsewhere. Run it against prod with `-e node_health_hosts=k3s_prod`, or a
# single box with `--limit`.
#
# BECOME: left at the play level exactly as the inventory sets it. The role itself pins
# `ansible_become: false` on every unprivileged task and probes escalation once before
# attempting the only root-only check (SMART), so this play behaves correctly on dev
# (NOPASSWD sudo) and on prod (vaulted become password, or none at all) without any
# per-environment flags. See roles/node_health/tasks/main.yml for why that has to be a
# variable override rather than `become: false`.
- name: Inspect node hardware health (read-only)
hosts: "{{ node_health_hosts | default('k3s_servers') }}"
# The role's own collector supersedes ansible.builtin.setup for this purpose (setup
# exposes no thermal zones, no EDAC, no PSI, no per-disk error counters), so skip the
# fact gathering round trip entirely.
gather_facts: false
roles:
- node_health