Files
home-network-k3s-impl/infra/ansible/inventory/group_vars/all.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

52 lines
3.0 KiB
YAML

---
# Shared connection + cluster facts for every k3s node.
# cloud-init created this login user (tofu var login_user); it has NOPASSWD sudo.
ansible_user: k3s
# --- environment group NAMES (R-ENV-02: one set of roles, scoped overrides) ---------
# The GROUP NAMES a role uses to answer "who is MY environment's primary / MY
# environment's servers?". Roles must never hardcode `groups['k3s_primary']`, or they
# only ever work against dev. These two are the dev values; group_vars/k3s_prod/vars.yml
# overrides them with k3s_prod_primary / k3s_prod, so a host in the prod group resolves
# its own primary automatically — exactly the way k3s_api_host below is overridden there.
#
# These are GROUP NAMES (keys into `groups`), NOT host patterns. The play-level target
# pattern is a separate, extra-vars-only knob (`k3s_hosts` / `k3s_primary_hosts` /
# `k3s_secondary_hosts`, see playbooks/*.yml) because Ansible templates a play's `hosts:`
# before it has any host context and so cannot see group_vars at all.
k3s_primary_group: k3s_primary
k3s_servers_group: k3s_servers
# Filename of the kubeconfig the controller-side roles (cilium, cluster_verify,
# flux_bootstrap) fetch from the primary into infra/ansible/.cache/. Scoped per
# environment so pointing those plays at prod can NEVER overwrite the dev kubeconfig
# that infra/devcontainer/dev.sh exports as $KUBECONFIG — an operator's `kubectl get
# nodes` silently answering from prod would be a genuinely dangerous mix-up.
# group_vars/k3s_prod/vars.yml overrides this to prod.kubeconfig. (.cache/ is gitignored;
# a kubeconfig is a credential.)
cluster_kubeconfig_name: dev.kubeconfig
# The kube-apiserver endpoint the whole stack agrees on: the primary's REAL node IP.
# NOTE: ansible_host is now the pfSense NAT SSH target (192.168.2.102:225x), NOT the node
# IP — so we read the real 192.168.1.x from k3s_node_ip instead. Used for k3s --tls-san /
# secondary --server AND for Cilium k8sServiceHost so the Ansible bootstrap and the Flux
# HelmRelease stay in PARITY (docs/02 contract).
k3s_api_host: "{{ hostvars[groups[k3s_primary_group][0]]['k3s_node_ip'] }}"
k3s_api_port: 6443
# Control-plane HA VIP floated by kube-vip (roles/kube_vip). A stable failover
# endpoint for the kube-apiserver, fronted by an operator pfSense port-forward at
# 192.168.1.49:6443. Shared here so BOTH kube_vip AND the k3s_server tls-san list
# agree on the address (the serving cert must carry this IP or TLS via the VIP
# fails to validate).
kube_vip_address: "192.168.1.49"
# Controller (dev container) reaches the kube-API DIRECTLY via the pfSense NAT
# (192.168.2.50:6443 -> kube-vip VIP 192.168.1.49:6443) — no SSH tunnel. TLS is
# validated against the VIP via tls-server-name (the VIP IP is in the serving cert's
# tls-san list, so TLS through the NAT validates). Only the controller kubeconfig uses
# these; k8sServiceHost (in-cluster) stays the real node IP.
cluster_controller_api_server: "https://192.168.2.50:6443"
cluster_controller_tls_server_name: "{{ kube_vip_address }}"