Build the anvil K3s cluster across foundry and two Raspberry Pis #10
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/foundry-k3s-cluster-4057a8"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Replaces the single-node K3s server on
foundrywith a three-node cluster namedanvil, spanningfoundryand the new Raspberry Pi hostsfoundry-whiteandfoundry-black.Topology
foundry-white--cluster-init, embedded etcd)foundryfoundry-blackAll three nodes run Ubuntu, so this needs no OS-family compatibility work — the existing
distribution == "Ubuntu"asserts all still pass and the Tailscale role is unchanged. Only the k3s role's architecture clause was widened.etcd lives on the SSD-backed node because its fsync-per-transaction pattern is the one workload a microSD card cannot sustain. Ingress lives on the microSD node precisely because it is an agent: no etcd, and klipper-lb is pure DNAT.
The part that carries real risk
Traefik now ships with the cluster, the opposite of the previous setup. That is safe only because ServiceLB is confined to
foundry-blackvia thesvccontroller.k3s.cattle.io/enablelblabel. An unlabelled cluster treats every node as eligible, and a klipper-lb pod onfoundryinstalls a DNAT rule in the host'snatPREROUTING chain that intercepts packets before they reach Caddy's listening socket.That failure is silent — every service keeps reporting healthy while
plex/git/bitwarden.emkaytec.comgo dark. Three independent mitigations:k3s_traefik_enabled=falsefor the initial build,foundry-blackjoins first so it is labelled beforefoundryappears, and the documented rebuild sequence gates on a label check.Other notable decisions
inventory-pull.ymlmirrors the structure, and the k3s role asserts group membership because drift there fails safe but silently.k3s.ymlmoves aftercaddy.ymlso a K3s outage never stops foundry's hourly pull from converging Docker and Caddy.ansible-pull, where there is no SSH path to the server. Note it is write-once: K3s derives the bootstrap encryption key from it.foundryis torn down and rebuilt, not converted. Its 120 GiB LV is reused — the uninstall script empties a mounted data directory while leaving the mount and fstab entry intact.New roles
raspberry_pi— memory cgroup, swap, clock sync, microSD write reduction. Thecmdline.txtedit is a token set-union, never a substitution, because mangling that single-line vfat file makes the board unbootable. An unconditional runtime gate proves the controller is live regardless of how it got enabled.auto_updates— unattended-upgrades on the Pis only. Automatic reboot is off while the cluster has one etcd member and one ingress node. Verifies the effective apt configuration viaapt-config dumpplus a dry run, not the rendered file.k3s_teardown— never imported byfoundry.yml, double confirmation, and a guard against the uninstall script's silent no-op when a secondk3s*.serviceexists.Verification
yamllint,ansible-inventory --listand syntax checks on all four playbooks pass; CI now covers the two non-imported playbooks and validates the inventory.ansible-lintreports zero findings in the new roles.Beyond that, a throwaway harness (not committed) exercised the expressions static checks cannot reach. It caught two real bugs, both fixed here: the origins verification counted the apt config key itself rather than its entries, and the swap fstab regex keyed off the mount point rather than the filesystem-type field — which would have silently left Ubuntu's
/swap.imgenabled. It also confirmed the cgroup token union is idempotent, the endpoint-rewrite andnoatimeregexes are correct and re-runnable, and all threeconfig.yamlvariants render as valid YAML with no baredisable:key.Archive metadata was checked against live sources rather than assumed:
ports.ubuntu.comservesresolutearm64 withOrigin: Ubuntu, the security pocket isSuite: resolute-security/Codename: resolute(so Ubuntu's origin:archive shorthand is the correct form), and Tailscale publishes aresolutesuite with a keyring.Before merging
group_vars/all/99-private.ymlneedsk3s_api_endpoint(foundry-white's MagicDNS name) andk3s_extra_tls_sans. Without them kubectl works over the LAN only; with the endpoint set but missing from the SANs, kubectl fails TLS verification.