Files
home-network-k3s-impl/infra/tofu/main.tf
T
jakman85andClaude Opus 4.8 f2a7e42068 Phase 1 (author-only): node provisioning IaC — tofu + cloud-init + Ansible (T-1.1/1.2/1.3)
OpenTofu libvirt module (k3s-dev NAT 10.73.0.0/24, 3x 6GiB/4vCPU VMs, 20G OS +
40G thin data, host-passthrough), cloud-init baseline (k3s/Cilium/Longhorn
prereqs, static net), Ansible k3s-HA + Cilium (kube-proxy replacement, Gateway
API, Hubble; flannel/kube-proxy/Traefik disabled). PM fix: escape ${...} in a
cloud-init template comment (tofu templatefile parse). tofu fmt/init/validate =
Success; ansible --syntax-check + yamllint pass. NOTHING executed (no apply).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-16 01:21:00 -05:00

145 lines
4.2 KiB
Terraform

# main.tf — dedicated NAT network + 3 cloud-init'd VMs, each with a thin OS disk
# (backed by the cloud image) and a blank thin Longhorn data disk (Phase 1 T-1.1).
#
# Node lifecycle stage 1 ("Create") per docs/02. cloud-init (infra/cloud-init/)
# does stage 2 ("Baseline"); Ansible (infra/ansible/) does stage 3 ("Cluster-join").
locals {
# 1 GiB in bytes; libvirt volume sizes are bytes.
gib = 1073741824
# Per-node identity/addressing, indexed 0..node_count-1.
nodes = [
for i in range(var.node_count) : {
name = "${var.node_name_prefix}${i + 1}"
ip = var.node_ips[i]
fqdn = "${var.node_name_prefix}${i + 1}.${var.node_domain}"
is_first = i == 0
}
]
}
# --- dedicated NAT network (10.73.0.0/24, gw .1) ---------------------------
# DHCP is OFF: nodes get static IPs from cloud-init network-config, so the
# reserved VIP range (.240-.250) can never be leased to something else.
resource "libvirt_network" "k3s_dev" {
name = var.network_name
mode = "nat"
addresses = [var.network_cidr]
autostart = true
dhcp {
enabled = false
}
dns {
enabled = true
local_only = true
}
}
# --- base OS volume (downloaded/copied once, shared as a backing file) ------
resource "libvirt_volume" "base" {
name = "${var.cluster_name}-base.qcow2"
pool = var.pool_name
source = var.image_source
format = "qcow2"
}
# --- per-node OS disk: thin qcow2 backed by the base image ------------------
resource "libvirt_volume" "os" {
count = var.node_count
name = "${local.nodes[count.index].name}-os.qcow2"
pool = var.pool_name
base_volume_id = libvirt_volume.base.id
size = var.os_disk_gib * local.gib
format = "qcow2"
}
# --- per-node data disk: blank thin qcow2 for Longhorn ----------------------
resource "libvirt_volume" "data" {
count = var.node_count
name = "${local.nodes[count.index].name}-data.qcow2"
pool = var.pool_name
size = var.data_disk_gib * local.gib
format = "qcow2"
}
# --- cloud-init seed (user-data + meta-data + static network-config) --------
# Templates live in the sibling infra/cloud-init/ subtree (T-1.2).
resource "libvirt_cloudinit_disk" "seed" {
count = var.node_count
name = "${local.nodes[count.index].name}-seed.iso"
pool = var.pool_name
user_data = templatefile("${path.module}/../cloud-init/user-data.yaml.tftpl", {
hostname = local.nodes[count.index].name
fqdn = local.nodes[count.index].fqdn
username = var.login_user
ssh_authorized_key = var.ssh_authorized_key
})
meta_data = templatefile("${path.module}/../cloud-init/meta-data.yaml.tftpl", {
hostname = local.nodes[count.index].name
fqdn = local.nodes[count.index].fqdn
})
network_config = templatefile("${path.module}/../cloud-init/network-config.yaml.tftpl", {
ip = local.nodes[count.index].ip
prefix = var.network_prefix
gateway = var.network_gateway
})
}
# --- the VMs ----------------------------------------------------------------
resource "libvirt_domain" "node" {
count = var.node_count
name = local.nodes[count.index].name
memory = var.memory_mib
vcpu = var.vcpu
# host-passthrough so nested virt is available for KubeVirt in Phase 5.
cpu {
mode = var.cpu_mode
}
# qemu-guest-agent is installed by cloud-init; lets tofu read the lease/agent.
qemu_agent = true
cloudinit = libvirt_cloudinit_disk.seed[count.index].id
# IP + hostname come from cloud-init (DHCP is off on this network), so we do NOT
# set addresses/hostname here — the provider couples those to DHCP reservations.
network_interface {
network_id = libvirt_network.k3s_dev.id
wait_for_lease = false
}
# OS disk first (vda, boot), Longhorn data disk second (vdb).
disk {
volume_id = libvirt_volume.os[count.index].id
}
disk {
volume_id = libvirt_volume.data[count.index].id
}
console {
type = "pty"
target_port = "0"
target_type = "serial"
}
console {
type = "pty"
target_port = "1"
target_type = "virtio"
}
graphics {
type = "spice"
listen_type = "address"
autoport = true
}
}