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]>
145 lines
4.2 KiB
Terraform
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
|
|
}
|
|
}
|