128 lines
4.7 KiB
HCL
128 lines
4.7 KiB
HCL
locals {
|
|
server_node = [for name, n in var.nodes : n if n.role == "server"][0]
|
|
server_ip = local.server_node.ip
|
|
prefix_length = split("/", var.network_cidr)[1]
|
|
}
|
|
|
|
# A dedicated pool, separate from libvirt's own `default` — same
|
|
# "isolated from anything else on the host" motivation as the network
|
|
# below, and it's the fix for the storage-permissions problem
|
|
# docs/02-k3s.md walks through by hand ("Storage: why a raw path in ~
|
|
# doesn't work"): a `dir` pool is a libvirtd-managed location, so volumes
|
|
# created in it come out owned correctly for the `libvirt-qemu` process
|
|
# that actually runs each VM, regardless of what user (`k8s`) asked for it.
|
|
resource "libvirt_pool" "k3s" {
|
|
name = var.storage_pool
|
|
type = "dir"
|
|
path = var.storage_pool_path
|
|
}
|
|
|
|
# Downloaded once and shared as a read-only backing image for every node's
|
|
# own disk below (libvirt_volume.node's base_volume_id) — the same
|
|
# copy-on-write relationship docs/02-k3s.md explains in detail
|
|
# ("Building the base image..."). Terraform only re-downloads this if
|
|
# base_image_url changes; it doesn't redo it on every apply.
|
|
resource "libvirt_volume" "base" {
|
|
name = "k3s-base.qcow2"
|
|
pool = libvirt_pool.k3s.name
|
|
source = var.base_image_url
|
|
format = "qcow2"
|
|
}
|
|
|
|
# A dedicated, isolated NAT network so this project can't collide with
|
|
# anything else already using the host's default libvirt network. DHCP is
|
|
# off — every node gets a static IP via cloud-init instead.
|
|
resource "libvirt_network" "k3s" {
|
|
name = "k3s-homelab"
|
|
mode = "nat"
|
|
domain = "k3s.local"
|
|
addresses = [var.network_cidr]
|
|
|
|
dhcp {
|
|
enabled = false
|
|
}
|
|
|
|
dns {
|
|
enabled = true
|
|
}
|
|
}
|
|
|
|
# One overlay disk per node, all backed by the single shared base image
|
|
# above — `size` here is the overlay's logical capacity (what the guest
|
|
# OS sees), not how much host disk it actually consumes; qcow2 overlays
|
|
# start small and grow only as the node writes data.
|
|
resource "libvirt_volume" "node" {
|
|
for_each = var.nodes
|
|
name = "${each.key}.qcow2"
|
|
pool = libvirt_pool.k3s.name
|
|
base_volume_id = libvirt_volume.base.id
|
|
size = each.value.disk_gb * 1024 * 1024 * 1024
|
|
format = "qcow2"
|
|
}
|
|
|
|
# The NoCloud seed ISO for each node — this resource is the Terraform
|
|
# equivalent of `virt-install --cloud-init` in docs/02-k3s.md, just
|
|
# built from two separate templates instead of one hand-assembled file:
|
|
resource "libvirt_cloudinit_disk" "node" {
|
|
for_each = var.nodes
|
|
name = "${each.key}-cloudinit.iso"
|
|
pool = libvirt_pool.k3s.name
|
|
|
|
# "what should exist on this machine" — which cloud-init template
|
|
# (server vs agent) depends on each.value.role, so a server node and an
|
|
# agent node run entirely different runcmd payloads despite sharing this
|
|
# one resource block. See cloud-init/server.yaml.tpl and agent.yaml.tpl.
|
|
user_data = templatefile("${path.module}/cloud-init/${each.value.role}.yaml.tpl", {
|
|
hostname = each.key
|
|
ssh_public_key = var.ssh_public_key
|
|
k3s_token = var.k3s_token
|
|
server_ip = local.server_ip
|
|
k8s_api_hostname = var.k8s_api_hostname
|
|
})
|
|
|
|
# "how should this machine's network be set up" — static, unlike stage
|
|
# 2's DHCP-based VM (docs/02-k3s.md), because this network
|
|
# (libvirt_network.k3s below) deliberately runs no DHCP server at all;
|
|
# every node needs to know its own address up front instead of waiting
|
|
# on a lease.
|
|
network_config = templatefile("${path.module}/cloud-init/network-config.yaml.tpl", {
|
|
ip = each.value.ip
|
|
prefix_length = local.prefix_length
|
|
gateway = var.gateway_ip
|
|
})
|
|
}
|
|
|
|
# The VM itself — everything above this point (pool, base image, per-node
|
|
# overlay, cloudinit ISO, network) exists only to be wired together here.
|
|
resource "libvirt_domain" "node" {
|
|
for_each = var.nodes
|
|
name = each.key
|
|
vcpu = each.value.vcpu
|
|
memory = each.value.memory
|
|
|
|
cloudinit = libvirt_cloudinit_disk.node[each.key].id
|
|
|
|
network_interface {
|
|
network_id = libvirt_network.k3s.id
|
|
# false because this network runs no DHCP (see libvirt_network.k3s) —
|
|
# the node gets its address from cloud-init's network_config instead,
|
|
# so waiting on a DHCP lease here would just block forever.
|
|
wait_for_lease = false
|
|
}
|
|
|
|
disk {
|
|
volume_id = libvirt_volume.node[each.key].id
|
|
}
|
|
|
|
# Serial console only, no display — this box doesn't need a GUI hop for
|
|
# a couple of small demo VMs.
|
|
console {
|
|
type = "pty"
|
|
target_type = "serial"
|
|
target_port = "0"
|
|
}
|
|
|
|
# Terraform brings all VMs up in parallel; the agent cloud-init script
|
|
# (cloud-init/agent.yaml.tpl) retries the join until the server's API is
|
|
# reachable, so node boot order doesn't matter.
|
|
}
|