Skip to content

Architecture and Decisions

Ownership

flowchart TB
    Git[Public Git repository] --> CI[Credential-free validation]
    Git --> Controller[Trusted administrator workstation]
    Controller -->|Terraform: verified HTTPS API| Proxmox[Proxmox VE 9 on PN51]
    Proxmox -->|Cloud-init: key and static address| Guests[Four Ubuntu 26.04 guests]
    Controller -->|Ansible: verified SSH| Guests
    Guests --> Desktop[Ubuntu Desktop and guest baseline]
    Guests --> Cluster[K3s: one server and three workers]
    Controller -->|Helm through verified Kubernetes API| Monitoring[Prometheus, Grafana, Alertmanager]
    Cluster --> Monitoring

GitHub Actions validates source and scans Git history. It cannot reach the homelab and receives no Proxmox token, SSH key, vault password, or kubeconfig. The separate documentation publisher only writes the documentation branch.

Terraform owns VM identity, CPU, RAM, disks, virtual displays, static addressing, the imported image, and VM firewall rules. Cloud-init owns the initial ubuntu account and authorized public keys. Ansible waits for cloud-init to finish, then owns the desktop, guest baseline, K3s service, and monitoring release.

The private Ansible inventory comes from Terraform output. CIDRs and the cluster DNS address travel through that inventory rather than being duplicated in group variables. Helm release credentials are not stored in Terraform state.

Physical and Logical Topology

flowchart TB
    Admin[Trusted administration subnet] -->|22 and server 6443 only| Bridge[Existing private bridge or VLAN]
    subgraph PN51[PN51: one physical failure domain]
        Host[Proxmox: 8 GiB RAM reserved]
        Bridge --> Server[server-1: Desktop, K3s, single-member etcd]
        Bridge --> Worker1[worker-1: Desktop and K3s agent]
        Bridge --> Worker2[worker-2: Desktop and K3s agent]
        Bridge --> Worker3[worker-3: Desktop and K3s agent]
        Server <-->|6443, 10250, UDP 8472| Worker1
        Server <-->|6443, 10250, UDP 8472| Worker2
        Server <-->|6443, 10250, UDP 8472| Worker3
        Worker1 --- LocalStorage[Node-local persistent volumes]
        Worker2 --- LocalStorage
        Worker3 --- LocalStorage
    end
    LocalStorage --> OffHost[Operator-managed off-host backups]
    Server --> OffHost

All node pairs also exchange VXLAN and kubelet traffic where required. The diagram abbreviates those links. No etcd ports or NodePort range are exposed between VMs: this is not a multi-server etcd cluster, and applications are not published by default.

Why These Choices

Decision Reason Cost
Proxmox rather than nested local virtualization Dedicated VM lifecycle, bridge networking, console, backups, API imports One manual, potentially destructive host installation
Full Desktop on all VMs Meets the desktop requirement with inspectable virtual consoles Larger memory, disk, update, and attack-surface budget than Server
Cloud image plus Ansible Checksummed base and repeatable package state without interactive installers First desktop installation needs network access and time
K3s rather than kubeadm Bundled runtime, CNI, storage bootstrap, and lower operational overhead K3s-specific configuration and upgrade procedures
One server and three workers Leaves three workload nodes and a simple recovery model API outage on server restart; no control-plane HA
Embedded etcd Native scheduled snapshots and a documented restore path One member is not redundant storage
Proxmox VM firewall Filters outside guest container networking Requires the datacenter firewall enabled and verified
Local-path storage Small footprint and no pretend redundancy on one NVMe disk A data-bearing VM cannot be replaced without data work
Vault and existing SSH keys No credential generation or Kubernetes authentication in Terraform Vault password and backups remain operator responsibilities

Three control-plane VMs on this same host would tolerate a guest failure, but not a PN51 power, network, or disk failure. For useful HA, add independent physical hosts and storage before increasing control-plane count.

Reconciliation and Recovery

Normal configuration runs do not perform unconditional package upgrades, reboot nodes, rotate credentials, or restart SSH/K3s. Templates and checksums notify handlers only when their inputs change. Workers are updated serially and drained before K3s changes; disruption budgets are respected.

Worker replacement is intentionally separate from reconciliation. Its helper checks node-local volumes, checks the remaining nodes, validates the complete Terraform plan, and asks for an exact typed confirmation. Control-plane replacement requires snapshot recovery.

The design is declarative at its ownership boundaries, not a promise that state can be thrown away. Preserve the Terraform state, vault, original server token, etcd snapshots, and application backups outside the PN51.

See the input reference and security boundaries.