Replace the dmacvicar/libvirt provider + resources with the modern bpg/proxmox provider. The dev cluster moves off the memory-constrained local libvirt host onto the Proxmox VE box. - versions.tf: pin bpg/proxmox ~> 0.66; provider reads auth from env (PROXMOX_VE_*, token never a tofu var) and normalizes a schemeless PROXMOX_VE_ENDPOINT (prepend https://) to avoid the :8006 HTTP 301. - variables.tf: Proxmox placement vars (node=proxmox, datastore=Storage, image_datastore=local, bridge=vmbr0); LAN 192.168.1.0/24 gw .1 DNS .18; node_ips .50/.51/.52; sizing 6 vCPU / 16384 MiB / 40+100 GiB; cpu_type host; Cilium VIP range .53-.60. - main.tf: proxmox_virtual_environment_download_file (cloud image -> local), per-node user-data snippet (proxmox_virtual_environment_file), and proxmox_virtual_environment_vm per node via name-keyed for_each (imported OS disk virtio0, blank data disk virtio1, native initialization ip_config + dns + user_data_file_id). - remove moved.tf (libvirt-specific count->for_each state migration). Authoring only; no apply. Traces R-ENV-01, R-PLAT-01. Co-Authored-By: Claude Opus 4.8 <[email protected]>
150 lines
5.5 KiB
Terraform
150 lines
5.5 KiB
Terraform
# main.tf — 3 cloud-init'd Proxmox VE VMs (k3s-dev-1/2/3), each with an imported
|
|
# OS disk (from the Debian cloud image) and a blank Longhorn data disk (R-ENV-01).
|
|
#
|
|
# 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").
|
|
#
|
|
# Provider: bpg/proxmox. VMs land on the Proxmox node `var.proxmox_node`, disks on
|
|
# `var.datastore` (ZFS "Storage"), NIC on `var.bridge` (vmbr0), static IPs via the
|
|
# native cloud-init `initialization` block. The rich first-boot baseline (users,
|
|
# packages, kernel modules, Longhorn mount, swap-off) is delivered as a cloud-init
|
|
# user-data SNIPPET (see the snippets note in README / operator action below).
|
|
|
|
locals {
|
|
# Per-node identity/addressing, KEYED BY NODE NAME (map, not list).
|
|
# for_each over this map gives every node a stable, name-based state address
|
|
# (…vm.node["k3s-dev-2"]) instead of a positional one. Positional count indexes
|
|
# are brittle under -target/-replace: a single-node destroy on a count-indexed
|
|
# resource can cascade to siblings (F1 finding). Name keys are individually
|
|
# addressable, so one node can be replaced/destroyed without disturbing the others.
|
|
nodes = {
|
|
for i in range(var.node_count) : "${var.node_name_prefix}${i + 1}" => {
|
|
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
|
|
}
|
|
}
|
|
|
|
# Ordered (node1..nodeN) list + primary view for outputs that need positional
|
|
# access — the resource for_each uses the map above; these preserve ordering for
|
|
# the Ansible inventory (primary = cluster-init server, rest = secondaries).
|
|
node_list = [for i in range(var.node_count) : local.nodes["${var.node_name_prefix}${i + 1}"]]
|
|
primary = local.nodes["${var.node_name_prefix}1"]
|
|
}
|
|
|
|
# --- base OS cloud image (downloaded once to `local`, imported per node) -----
|
|
# content_type "iso" stores it on the ISO datastore; the .img file name lets
|
|
# Proxmox accept the qcow2 (it still reads it as qcow2 on disk import).
|
|
resource "proxmox_virtual_environment_download_file" "debian_cloud_image" {
|
|
content_type = "iso"
|
|
datastore_id = var.image_datastore
|
|
node_name = var.proxmox_node
|
|
url = var.image_source
|
|
file_name = var.image_file_name
|
|
overwrite = false
|
|
}
|
|
|
|
# --- per-node cloud-init user-data snippet ----------------------------------
|
|
# bpg's native initialization.user_account only sets user/keys/password; it can't
|
|
# carry packages/write_files/runcmd. To preserve the full k3s/Cilium/Longhorn
|
|
# baseline we render the existing user-data.yaml.tftpl and ship it as a SNIPPET,
|
|
# referenced by the VM's initialization.user_data_file_id.
|
|
#
|
|
# OPERATOR ACTION: the `snippets` content type must be enabled on var.image_datastore
|
|
# (Datacenter -> Storage -> local -> Content -> add "Snippets"), else this file
|
|
# resource fails. See README "Operator prerequisites".
|
|
resource "proxmox_virtual_environment_file" "user_data" {
|
|
for_each = local.nodes
|
|
content_type = "snippets"
|
|
datastore_id = var.image_datastore
|
|
node_name = var.proxmox_node
|
|
|
|
source_raw {
|
|
file_name = "${each.value.name}-user-data.yaml"
|
|
data = templatefile("${path.module}/../cloud-init/user-data.yaml.tftpl", {
|
|
hostname = each.value.name
|
|
fqdn = each.value.fqdn
|
|
username = var.login_user
|
|
ssh_authorized_key = var.ssh_authorized_key
|
|
})
|
|
}
|
|
}
|
|
|
|
# --- the VMs ----------------------------------------------------------------
|
|
resource "proxmox_virtual_environment_vm" "node" {
|
|
for_each = local.nodes
|
|
name = each.value.name
|
|
node_name = var.proxmox_node
|
|
tags = ["k3s", "k3s-dev", "opentofu"]
|
|
|
|
description = "k3s dev node ${each.value.name} (${each.value.fqdn}) — provisioned by OpenTofu."
|
|
on_boot = true
|
|
stop_on_destroy = true
|
|
|
|
# `host` CPU type passes through the physical CPU so nested virt is available for
|
|
# KubeVirt in Phase 5.
|
|
cpu {
|
|
cores = var.vcpu
|
|
type = var.cpu_type
|
|
}
|
|
|
|
memory {
|
|
dedicated = var.memory_mib
|
|
}
|
|
|
|
# qemu-guest-agent is installed by cloud-init; lets tofu/Proxmox read guest state.
|
|
agent {
|
|
enabled = true
|
|
}
|
|
|
|
operating_system {
|
|
type = "l26"
|
|
}
|
|
|
|
# OS disk (virtio0 -> /dev/vda, boot): a copy of the imported cloud image, grown
|
|
# to os_disk_gib. Longhorn data disk (virtio1 -> /dev/vdb): blank, data_disk_gib.
|
|
# virtio* keeps the guest device names (/dev/vda, /dev/vdb) the cloud-init
|
|
# fs_setup/mount for /var/lib/longhorn already expects.
|
|
disk {
|
|
datastore_id = var.datastore
|
|
import_from = proxmox_virtual_environment_download_file.debian_cloud_image.id
|
|
interface = "virtio0"
|
|
size = var.os_disk_gib
|
|
discard = "on"
|
|
}
|
|
|
|
disk {
|
|
datastore_id = var.datastore
|
|
interface = "virtio1"
|
|
size = var.data_disk_gib
|
|
discard = "on"
|
|
}
|
|
|
|
network_device {
|
|
bridge = var.bridge
|
|
}
|
|
|
|
# Static addressing + DNS come from cloud-init (bpg generates the network-config),
|
|
# so the libvirt netplan template is gone. User-data (the baseline) is the snippet.
|
|
initialization {
|
|
datastore_id = var.datastore
|
|
|
|
ip_config {
|
|
ipv4 {
|
|
address = "${each.value.ip}/${var.network_prefix}"
|
|
gateway = var.network_gateway
|
|
}
|
|
}
|
|
|
|
dns {
|
|
servers = [var.dns_server]
|
|
}
|
|
|
|
user_data_file_id = proxmox_virtual_environment_file.user_data[each.key].id
|
|
}
|
|
|
|
# Boot from the OS disk only.
|
|
boot_order = ["virtio0"]
|
|
}
|