feat(fastpass): Talos cluster provisioning and bootstrap

- Terraform: VM provisioning, Unifi DHCP, Technitium DNS
- talhelper: cluster config for 6-node Talos cluster
- Cilium 1.19.4 CNI with Talos-compatible security context
- docs: city-hall setup guide and bootstrap runbook
This commit is contained in:
2026-05-17 16:08:19 -05:00
parent 9f3ac95d8d
commit 97e9889251
16 changed files with 1205 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
# ─── Talos Node Resource ─────────────────────────────────────────────────────
# Creates a Proxmox VM booting from the Talos ISO.
# No cloud-init, no template clone — Talos is configured via the Talos API
# after boot using talhelper-generated machine configs from city-hall.
#
# Boot sequence:
# 1. Terraform creates VM (stopped) + DHCP reservation
# 2. Unifi assigns reserved IP when VM starts
# 3. talhelper applies machine config via Talos API (port 50000)
# 4. Cluster bootstraps; ISO is no longer needed after first boot
# (Talos installs itself to disk on first config apply)
resource "proxmox_virtual_environment_vm" "node" {
name = var.hostname
vm_id = var.vm_id
node_name = var.target_node
description = "Talos ${var.node_role} node — fastpass cluster. Managed by mk-labs Terraform."
tags = ["managed", "talos", "fastpass", var.node_role]
bios = "ovmf"
# ─── EFI Disk ─────────────────────────────────────────────────────────────
# Required for OVMF/UEFI boot. Stores EFI variables.
efi_disk {
datastore_id = var.datastore
file_format = "raw"
type = "4m"
}
# ─── Boot: Talos ISO ──────────────────────────────────────────────────────
# Talos boots from ISO, applies machine config via API, then installs
# itself to the disk. After first boot the ISO is not needed.
cdrom {
file_id = var.iso_file_id
interface = "ide2"
}
boot_order = ["scsi0", "ide2"]
# ─── Disk ─────────────────────────────────────────────────────────────────
# Single disk — Talos manages its own partition layout.
# No separate data disk; persistent app storage comes from Pure Storage CSI.
disk {
datastore_id = var.datastore
size = var.disk_size
interface = "scsi0"
file_format = "raw"
ssd = true
discard = "on"
}
# ─── CPU ──────────────────────────────────────────────────────────────────
cpu {
cores = var.cpu_cores
type = "x86-64-v2-AES"
}
# ─── Memory ───────────────────────────────────────────────────────────────
memory {
dedicated = var.memory_mb
}
# ─── Network ──────────────────────────────────────────────────────────────
# Single NIC on VLAN 71 (Server Trusted).
# MAC is deterministic via Terraform so Unifi DHCP reservation matches.
network_device {
bridge = var.bridge
model = "virtio"
}
# ─── QEMU Guest Agent ─────────────────────────────────────────────────────
# Requires the qemu-guest-agent Talos system extension (set in talhelper).
agent {
enabled = true
}
# ─── Start Behavior ───────────────────────────────────────────────────────
# VM starts immediately — Talos needs to boot to accept machine configs.
# Unifi DHCP reservation is created before this resource so the IP is
# already reserved when the NIC first broadcasts a DHCP request.
started = true
# ─── Lifecycle ────────────────────────────────────────────────────────────
lifecycle {
ignore_changes = [
description,
tags,
# cdrom can be ejected manually after first boot without Terraform drift
cdrom,
# node_name changes after HA migrates VMs to their intended hosts
node_name,
]
}
}

View File

@@ -0,0 +1,18 @@
# ─── Node Outputs ────────────────────────────────────────────────────────────
# Consumed by the cluster root module to wire up DHCP reservations and
# DNS records without hardcoding MAC addresses.
output "vm_id" {
description = "Proxmox VM ID."
value = proxmox_virtual_environment_vm.node.vm_id
}
output "mac_address" {
description = "MAC address of the primary network interface. Used to create the Unifi DHCP reservation."
value = proxmox_virtual_environment_vm.node.network_device[0].mac_address
}
output "hostname" {
description = "Node hostname. Passed through for convenience when building DNS records."
value = proxmox_virtual_environment_vm.node.name
}

View File

@@ -0,0 +1,8 @@
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = ">= 0.74.0"
}
}
}

View File

@@ -0,0 +1,71 @@
# ─── Provider Authentication ─────────────────────────────────────────────────
# Passed through from the cluster root module — not declared here since
# provider config lives in the root module's providers.tf.
# ─── Node Identity ───────────────────────────────────────────────────────────
variable "hostname" {
type = string
description = "Node hostname (e.g., space-mountain). Used as the Proxmox VM name."
}
variable "vm_id" {
type = number
description = "Proxmox VM ID. Convention: {third_octet}{fourth_octet:03d} (e.g., 10.1.71.66 → 71066)."
}
variable "node_role" {
type = string
description = "Role of the node in the cluster. Used for tagging."
validation {
condition = contains(["controlplane", "worker"], var.node_role)
error_message = "node_role must be 'controlplane' or 'worker'."
}
}
# ─── Proxmox Placement ───────────────────────────────────────────────────────
variable "target_node" {
type = string
description = "Proxmox node to place this VM on (e.g., main-street-usa, tomorrowland, fantasyland)."
}
variable "datastore" {
type = string
description = "Proxmox storage pool for the VM disk."
default = "liberty-tree"
}
# ─── Boot Image ──────────────────────────────────────────────────────────────
variable "iso_file_id" {
type = string
description = "Proxmox file ID for the Talos ISO (e.g., local:iso/talos-v1.9.5-metal-amd64.iso). Output from proxmox_virtual_environment_download_file."
}
# ─── Hardware ────────────────────────────────────────────────────────────────
variable "cpu_cores" {
type = number
description = "Number of vCPU cores."
}
variable "memory_mb" {
type = number
description = "RAM in megabytes."
}
variable "disk_size" {
type = number
description = "OS disk size in gigabytes. Talos manages its own partition layout."
}
# ─── Network ─────────────────────────────────────────────────────────────────
variable "bridge" {
type = string
description = "Proxmox network bridge."
default = "vmbr0"
}