terraform: add parameterized multi-VM deployment system
Implements Phase 1 of the OpenTofu deployment plan: - Replace single-VM configuration with locals-based for_each pattern - Support multiple VMs in single deployment - Automatic DHCP vs static IP detection - Configurable defaults with per-VM overrides - Dynamic outputs for VM IPs and specifications New files: - outputs.tf: Dynamic outputs for deployed VMs - vms.tf: VM definitions using locals.vms map Updated files: - variables.tf: Added default variables for VM configuration - README.md: Comprehensive documentation and examples Removed files: - vm.tf: Replaced by new vms.tf (archived as vm.tf.old, then removed) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# OpenTofu Configuration for Proxmox
|
# OpenTofu Configuration for Proxmox
|
||||||
|
|
||||||
This directory contains OpenTofu configuration for managing Proxmox VMs.
|
This directory contains OpenTofu configuration for managing Proxmox VMs using a parameterized, multi-VM deployment system.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
@@ -29,9 +29,169 @@ This directory contains OpenTofu configuration for managing Proxmox VMs.
|
|||||||
tofu plan
|
tofu plan
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Defining VMs
|
||||||
|
|
||||||
|
All VMs are defined in the `vms.tf` file in the `locals.vms` map. Each VM can specify custom configurations or use defaults from `variables.tf`.
|
||||||
|
|
||||||
|
### Example: DHCP VM
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
vms = {
|
||||||
|
"simple-vm" = {
|
||||||
|
cpu_cores = 2
|
||||||
|
memory = 2048
|
||||||
|
disk_size = "20G"
|
||||||
|
# No "ip" field = DHCP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: Static IP VM
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
vms = {
|
||||||
|
"web-server" = {
|
||||||
|
ip = "10.69.13.50/24"
|
||||||
|
cpu_cores = 4
|
||||||
|
memory = 4096
|
||||||
|
disk_size = "50G"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: Minimal VM (all defaults)
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
vms = {
|
||||||
|
"test-vm" = {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: Multiple VMs
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
vms = {
|
||||||
|
"vm1" = {
|
||||||
|
ip = "10.69.13.50/24"
|
||||||
|
}
|
||||||
|
"vm2" = {
|
||||||
|
ip = "10.69.13.51/24"
|
||||||
|
cpu_cores = 4
|
||||||
|
memory = 8192
|
||||||
|
}
|
||||||
|
"vm3" = {
|
||||||
|
# DHCP
|
||||||
|
cpu_cores = 2
|
||||||
|
memory = 2048
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
Each VM in the `vms` map supports the following fields (all optional):
|
||||||
|
|
||||||
|
| Field | Description | Default |
|
||||||
|
|-------|-------------|---------|
|
||||||
|
| `ip` | Static IP with CIDR (e.g., "10.69.13.50/24"). Omit for DHCP | DHCP |
|
||||||
|
| `gateway` | Network gateway (used with static IP) | `10.69.13.1` |
|
||||||
|
| `cpu_cores` | Number of CPU cores | `2` |
|
||||||
|
| `memory` | Memory in MB | `2048` |
|
||||||
|
| `disk_size` | Disk size (e.g., "20G", "100G") | `"20G"` |
|
||||||
|
| `target_node` | Proxmox node to deploy to | `"pve1"` |
|
||||||
|
| `template_name` | Template VM to clone from | `"nixos-25.11.20260128.fa83fd8"` |
|
||||||
|
| `storage` | Storage backend | `"local-zfs"` |
|
||||||
|
| `bridge` | Network bridge | `"vmbr0"` |
|
||||||
|
| `vlan_tag` | VLAN tag | `13` |
|
||||||
|
| `ssh_public_key` | SSH public key for root | See `variables.tf` |
|
||||||
|
| `nameservers` | DNS servers | `"10.69.13.5 10.69.13.6"` |
|
||||||
|
| `search_domain` | DNS search domain | `"home.2rjus.net"` |
|
||||||
|
|
||||||
|
Defaults are defined in `variables.tf` and can be changed globally.
|
||||||
|
|
||||||
|
## Deployment Commands
|
||||||
|
|
||||||
|
### Deploy All VMs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tofu apply
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deploy Specific VM
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tofu apply -target=proxmox_vm_qemu.vm[\"vm-name\"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Destroy Specific VM
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tofu destroy -target=proxmox_vm_qemu.vm[\"vm-name\"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### View Deployed VMs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tofu output vm_ips
|
||||||
|
tofu output deployment_summary
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plan Changes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tofu plan
|
||||||
|
```
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
After deployment, OpenTofu provides two outputs:
|
||||||
|
|
||||||
|
**vm_ips**: IP addresses and SSH commands for each VM
|
||||||
|
```
|
||||||
|
vm_ips = {
|
||||||
|
"vm1" = {
|
||||||
|
ip = "10.69.13.50"
|
||||||
|
ssh_command = "ssh root@10.69.13.50"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**deployment_summary**: Full specifications for each VM
|
||||||
|
```
|
||||||
|
deployment_summary = {
|
||||||
|
"vm1" = {
|
||||||
|
cpu_cores = 4
|
||||||
|
memory_mb = 4096
|
||||||
|
disk_size = "50G"
|
||||||
|
ip = "10.69.13.50"
|
||||||
|
node = "pve1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Edit `vms.tf` to define your VMs in the `locals.vms` map
|
||||||
|
2. Run `tofu plan` to preview changes
|
||||||
|
3. Run `tofu apply` to deploy
|
||||||
|
4. Run `tofu output vm_ips` to get IP addresses
|
||||||
|
5. SSH to VMs and configure as needed
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
- `main.tf` - Provider configuration and test data source
|
- `main.tf` - Provider configuration
|
||||||
- `variables.tf` - Variable definitions
|
- `variables.tf` - Variable definitions and defaults
|
||||||
|
- `vms.tf` - VM definitions and deployment logic
|
||||||
|
- `outputs.tf` - Output definitions for deployed VMs
|
||||||
- `terraform.tfvars.example` - Example credentials file
|
- `terraform.tfvars.example` - Example credentials file
|
||||||
- `terraform.tfvars` - Your actual credentials (gitignored)
|
- `terraform.tfvars` - Your actual credentials (gitignored)
|
||||||
|
- `vm.tf.old` - Archived single-VM configuration (reference)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- VMs are deployed as full clones (not linked clones)
|
||||||
|
- Cloud-init handles initial networking configuration
|
||||||
|
- QEMU guest agent is enabled on all VMs
|
||||||
|
- All VMs start on boot by default
|
||||||
|
- IPv6 is disabled
|
||||||
|
- Destroying VMs removes them from Proxmox but does not clean up DNS entries or NixOS configurations
|
||||||
|
|||||||
24
terraform/outputs.tf
Normal file
24
terraform/outputs.tf
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Dynamic outputs for all deployed VMs
|
||||||
|
|
||||||
|
output "vm_ips" {
|
||||||
|
description = "IP addresses and SSH commands for deployed VMs"
|
||||||
|
value = {
|
||||||
|
for name, vm in proxmox_vm_qemu.vm : name => {
|
||||||
|
ip = vm.default_ipv4_address
|
||||||
|
ssh_command = "ssh root@${vm.default_ipv4_address}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output "deployment_summary" {
|
||||||
|
description = "Summary of deployed VMs with their specifications"
|
||||||
|
value = {
|
||||||
|
for name, vm in proxmox_vm_qemu.vm : name => {
|
||||||
|
cpu_cores = vm.cpu[0].cores
|
||||||
|
memory_mb = vm.memory
|
||||||
|
disk_size = vm.disks[0].virtio[0].virtio0[0].disk[0].size
|
||||||
|
ip = vm.default_ipv4_address
|
||||||
|
node = vm.target_node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,3 +20,78 @@ variable "proxmox_tls_insecure" {
|
|||||||
type = bool
|
type = bool
|
||||||
default = true
|
default = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Default values for VM configurations
|
||||||
|
# These can be overridden per-VM in vms.tf
|
||||||
|
|
||||||
|
variable "default_target_node" {
|
||||||
|
description = "Default Proxmox node to deploy VMs to"
|
||||||
|
type = string
|
||||||
|
default = "pve1"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "default_template_name" {
|
||||||
|
description = "Default template VM name to clone from"
|
||||||
|
type = string
|
||||||
|
default = "nixos-25.11.20260128.fa83fd8"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "default_ssh_public_key" {
|
||||||
|
description = "Default SSH public key for root user"
|
||||||
|
type = string
|
||||||
|
default = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAwfb2jpKrBnCw28aevnH8HbE5YbcMXpdaVv2KmueDu6 torjus@gunter"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "default_storage" {
|
||||||
|
description = "Default storage backend for VM disks"
|
||||||
|
type = string
|
||||||
|
default = "local-zfs"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "default_bridge" {
|
||||||
|
description = "Default network bridge"
|
||||||
|
type = string
|
||||||
|
default = "vmbr0"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "default_vlan_tag" {
|
||||||
|
description = "Default VLAN tag"
|
||||||
|
type = number
|
||||||
|
default = 13
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "default_gateway" {
|
||||||
|
description = "Default network gateway for static IP VMs"
|
||||||
|
type = string
|
||||||
|
default = "10.69.13.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "default_nameservers" {
|
||||||
|
description = "Default DNS nameservers"
|
||||||
|
type = string
|
||||||
|
default = "10.69.13.5 10.69.13.6"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "default_search_domain" {
|
||||||
|
description = "Default DNS search domain"
|
||||||
|
type = string
|
||||||
|
default = "home.2rjus.net"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "default_cpu_cores" {
|
||||||
|
description = "Default number of CPU cores"
|
||||||
|
type = number
|
||||||
|
default = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "default_memory" {
|
||||||
|
description = "Default memory in MB"
|
||||||
|
type = number
|
||||||
|
default = 2048
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "default_disk_size" {
|
||||||
|
description = "Default disk size"
|
||||||
|
type = string
|
||||||
|
default = "20G"
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
# Example VM configuration - clone from template
|
|
||||||
# Before using this, you need to:
|
|
||||||
# 1. Upload the NixOS image to Proxmox
|
|
||||||
# 2. Restore it as a template VM (e.g., ID 9000)
|
|
||||||
# 3. Update the variables below
|
|
||||||
|
|
||||||
variable "target_node" {
|
|
||||||
description = "Proxmox node to deploy to"
|
|
||||||
type = string
|
|
||||||
default = "pve1"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "template_name" {
|
|
||||||
description = "Template VM name to clone from"
|
|
||||||
type = string
|
|
||||||
default = "nixos-25.11.20260128.fa83fd8"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "ssh_public_key" {
|
|
||||||
description = "SSH public key for root user"
|
|
||||||
type = string
|
|
||||||
default = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAwfb2jpKrBnCw28aevnH8HbE5YbcMXpdaVv2KmueDu6 torjus@gunter"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Example test VM
|
|
||||||
resource "proxmox_vm_qemu" "test_vm" {
|
|
||||||
name = "nixos-test-tofu"
|
|
||||||
target_node = var.target_node
|
|
||||||
|
|
||||||
# Clone from template
|
|
||||||
clone = var.template_name
|
|
||||||
|
|
||||||
# Full clone (not linked)
|
|
||||||
full_clone = true
|
|
||||||
|
|
||||||
# Boot configuration
|
|
||||||
boot = "order=virtio0"
|
|
||||||
scsihw = "virtio-scsi-single"
|
|
||||||
|
|
||||||
# VM settings
|
|
||||||
cpu {
|
|
||||||
cores = 2
|
|
||||||
}
|
|
||||||
memory = 2048
|
|
||||||
|
|
||||||
# Network
|
|
||||||
network {
|
|
||||||
id = 0
|
|
||||||
model = "virtio"
|
|
||||||
bridge = "vmbr0"
|
|
||||||
tag = 13
|
|
||||||
}
|
|
||||||
|
|
||||||
# Disk settings
|
|
||||||
disks {
|
|
||||||
virtio {
|
|
||||||
virtio0 {
|
|
||||||
disk {
|
|
||||||
size = "20G"
|
|
||||||
storage = "local-zfs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Start on boot
|
|
||||||
start_at_node_boot = true
|
|
||||||
|
|
||||||
# Agent
|
|
||||||
agent = 1
|
|
||||||
|
|
||||||
# Cloud-init configuration
|
|
||||||
ciuser = "root"
|
|
||||||
sshkeys = var.ssh_public_key
|
|
||||||
ipconfig0 = "ip=dhcp"
|
|
||||||
nameserver = "10.69.13.5 10.69.13.6"
|
|
||||||
searchdomain = "home.2rjus.net"
|
|
||||||
|
|
||||||
# Skip IPv6 since we don't use it
|
|
||||||
skip_ipv6 = true
|
|
||||||
|
|
||||||
rng {
|
|
||||||
source = "/dev/urandom"
|
|
||||||
period = 1000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output "test_vm_ip" {
|
|
||||||
value = proxmox_vm_qemu.test_vm.default_ipv4_address
|
|
||||||
}
|
|
||||||
114
terraform/vms.tf
Normal file
114
terraform/vms.tf
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# VM Definitions
|
||||||
|
# Define all VMs to deploy in the locals.vms map below
|
||||||
|
# Omit fields to use defaults from variables.tf
|
||||||
|
|
||||||
|
locals {
|
||||||
|
# Define VMs here
|
||||||
|
# Each VM can override defaults by specifying values
|
||||||
|
# Omit "ip" field for DHCP, include it for static IP
|
||||||
|
vms = {
|
||||||
|
# Example DHCP VM (uncomment to deploy):
|
||||||
|
# "example-dhcp-vm" = {
|
||||||
|
# cpu_cores = 2
|
||||||
|
# memory = 2048
|
||||||
|
# disk_size = "20G"
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Example Static IP VM (uncomment to deploy):
|
||||||
|
# "example-static-vm" = {
|
||||||
|
# ip = "10.69.13.50/24"
|
||||||
|
# cpu_cores = 4
|
||||||
|
# memory = 4096
|
||||||
|
# disk_size = "50G"
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Example Minimal VM using all defaults (uncomment to deploy):
|
||||||
|
# "minimal-vm" = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Compute VM configurations with defaults applied
|
||||||
|
vm_configs = {
|
||||||
|
for name, vm in local.vms : name => {
|
||||||
|
target_node = lookup(vm, "target_node", var.default_target_node)
|
||||||
|
template_name = lookup(vm, "template_name", var.default_template_name)
|
||||||
|
cpu_cores = lookup(vm, "cpu_cores", var.default_cpu_cores)
|
||||||
|
memory = lookup(vm, "memory", var.default_memory)
|
||||||
|
disk_size = lookup(vm, "disk_size", var.default_disk_size)
|
||||||
|
storage = lookup(vm, "storage", var.default_storage)
|
||||||
|
bridge = lookup(vm, "bridge", var.default_bridge)
|
||||||
|
vlan_tag = lookup(vm, "vlan_tag", var.default_vlan_tag)
|
||||||
|
ssh_public_key = lookup(vm, "ssh_public_key", var.default_ssh_public_key)
|
||||||
|
nameservers = lookup(vm, "nameservers", var.default_nameservers)
|
||||||
|
search_domain = lookup(vm, "search_domain", var.default_search_domain)
|
||||||
|
# Network configuration - detect DHCP vs static
|
||||||
|
ip = lookup(vm, "ip", null)
|
||||||
|
gateway = lookup(vm, "gateway", var.default_gateway)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deploy all VMs using for_each
|
||||||
|
resource "proxmox_vm_qemu" "vm" {
|
||||||
|
for_each = local.vm_configs
|
||||||
|
|
||||||
|
name = each.key
|
||||||
|
target_node = each.value.target_node
|
||||||
|
|
||||||
|
# Clone from template
|
||||||
|
clone = each.value.template_name
|
||||||
|
full_clone = true
|
||||||
|
|
||||||
|
# Boot configuration
|
||||||
|
boot = "order=virtio0"
|
||||||
|
scsihw = "virtio-scsi-single"
|
||||||
|
|
||||||
|
# VM settings
|
||||||
|
cpu {
|
||||||
|
cores = each.value.cpu_cores
|
||||||
|
}
|
||||||
|
memory = each.value.memory
|
||||||
|
|
||||||
|
# Network
|
||||||
|
network {
|
||||||
|
id = 0
|
||||||
|
model = "virtio"
|
||||||
|
bridge = each.value.bridge
|
||||||
|
tag = each.value.vlan_tag
|
||||||
|
}
|
||||||
|
|
||||||
|
# Disk settings
|
||||||
|
disks {
|
||||||
|
virtio {
|
||||||
|
virtio0 {
|
||||||
|
disk {
|
||||||
|
size = each.value.disk_size
|
||||||
|
storage = each.value.storage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start on boot
|
||||||
|
start_at_node_boot = true
|
||||||
|
|
||||||
|
# Agent
|
||||||
|
agent = 1
|
||||||
|
|
||||||
|
# Cloud-init configuration
|
||||||
|
ciuser = "root"
|
||||||
|
sshkeys = each.value.ssh_public_key
|
||||||
|
nameserver = each.value.nameservers
|
||||||
|
searchdomain = each.value.search_domain
|
||||||
|
|
||||||
|
# Network configuration - DHCP or static IP
|
||||||
|
ipconfig0 = each.value.ip != null ? "ip=${each.value.ip},gw=${each.value.gateway}" : "ip=dhcp"
|
||||||
|
|
||||||
|
# Skip IPv6 since we don't use it
|
||||||
|
skip_ipv6 = true
|
||||||
|
|
||||||
|
# RNG device for better entropy
|
||||||
|
rng {
|
||||||
|
source = "/dev/urandom"
|
||||||
|
period = 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user