TofuLibVirt/PLAN.md

11 KiB

LibVirt + OpenTofu VM Provisioning Plan

Overview

This plan outlines how to use LibVirt (virtualization management toolkit) and OpenTofu (Infrastructure as Code tool) to provision and manage virtual machines declaratively.

LibVirt provides a unified API for managing different hypervisors (KVM, QEMU, Xen, etc.), while OpenTofu allows you to define infrastructure using declarative configuration files.

Prerequisites

System Requirements

  • Linux host system (Ubuntu/Debian/RHEL/CentOS)
  • KVM/QEMU support (check with lscpu | grep Virtualization)
  • Sufficient RAM and storage for VMs
  • Root/sudo access

Software Installation

1. Install LibVirt and KVM

# Ubuntu/Debian
sudo apt update
sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager

# RHEL/CentOS/Fedora
sudo dnf install qemu-kvm libvirt libvirt-daemon-system libvirt-clients bridge-utils virt-manager

# Start and enable libvirt service
sudo systemctl start libvirtd
sudo systemctl enable libvirtd

# Add user to libvirt group
sudo usermod -aG libvirt $USER

2. Install OpenTofu

# Download and install OpenTofu
curl -fsSL https://get.opentofu.org/install-opentofu.sh | sh

# Or using package manager (Ubuntu/Debian)
sudo snap install opentofu --classic

# Verify installation
tofu version

3. Verify LibVirt Connection

# Test libvirt connection
virsh list --all

# Check available storage pools
virsh pool-list --all

# Check available networks
virsh net-list --all

Project Structure

vm-infrastructure/
├── main.tf                 # Main OpenTofu configuration
├── variables.tf            # Input variables
├── outputs.tf             # Output values
├── terraform.tfvars       # Variable values
├── cloud-init/            # Cloud-init configuration files
│   ├── user-data.yaml
│   └── meta-data.yaml
├── images/                # VM base images
└── README.md             # Project documentation

Configuration Files

1. Main Configuration (main.tf)

terraform {
  required_providers {
    libvirt = {
      source  = "dmacvicar/libvirt"
      version = "~> 0.7.0"
    }
  }
}

# Configure the LibVirt Provider
provider "libvirt" {
  uri = "qemu:///system"
}

# Create a storage pool
resource "libvirt_pool" "vm_pool" {
  name = var.pool_name
  type = "dir"
  path = var.pool_path
}

# Download Ubuntu cloud image
resource "libvirt_volume" "base_image" {
  name   = "ubuntu-22.04-base.qcow2"
  pool   = libvirt_pool.vm_pool.name
  source = "https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img"
  format = "qcow2"
}

# Create cloud-init disk
resource "libvirt_cloudinit_disk" "cloudinit" {
  name           = "cloudinit.iso"
  pool           = libvirt_pool.vm_pool.name
  user_data      = file("${path.module}/cloud-init/user-data.yaml")
  meta_data      = file("${path.module}/cloud-init/meta-data.yaml")
}

# Create VM volumes from base image
resource "libvirt_volume" "vm_disk" {
  count          = var.vm_count
  name           = "vm-${count.index + 1}-disk.qcow2"
  pool           = libvirt_pool.vm_pool.name
  base_volume_id = libvirt_volume.base_image.id
  size           = var.disk_size
}

# Create VMs
resource "libvirt_domain" "vm" {
  count  = var.vm_count
  name   = "vm-${count.index + 1}"
  memory = var.memory
  vcpu   = var.vcpu

  cloudinit = libvirt_cloudinit_disk.cloudinit.id

  network_interface {
    network_name   = "default"
    wait_for_lease = true
  }

  disk {
    volume_id = libvirt_volume.vm_disk[count.index].id
  }

  console {
    type        = "pty"
    target_port = "0"
    target_type = "serial"
  }

  graphics {
    type        = "spice"
    listen_type = "address"
    autoport    = true
  }
}

2. Variables (variables.tf)

variable "pool_name" {
  description = "Name of the storage pool"
  type        = string
  default     = "vm-pool"
}

variable "pool_path" {
  description = "Path to the storage pool"
  type        = string
  default     = "/var/lib/libvirt/images/vm-pool"
}

variable "vm_count" {
  description = "Number of VMs to create"
  type        = number
  default     = 3
}

variable "memory" {
  description = "Memory allocation for each VM (MB)"
  type        = number
  default     = 2048
}

variable "vcpu" {
  description = "Number of vCPUs for each VM"
  type        = number
  default     = 2
}

variable "disk_size" {
  description = "Disk size for each VM (bytes)"
  type        = number
  default     = 21474836480  # 20GB
}

3. Outputs (outputs.tf)

output "vm_ips" {
  description = "IP addresses of the created VMs"
  value = {
    for i, vm in libvirt_domain.vm : vm.name => vm.network_interface[0].addresses[0]
  }
}

output "vm_names" {
  description = "Names of the created VMs"
  value       = libvirt_domain.vm[*].name
}

4. Variable Values (terraform.tfvars)

pool_name = "my-vm-pool"
pool_path = "/var/lib/libvirt/images/my-vm-pool"
vm_count  = 3
memory    = 4096  # 4GB
vcpu      = 2
disk_size = 32212254720  # 30GB

5. Cloud-Init Configuration

User Data (cloud-init/user-data.yaml)

#cloud-config
users:
  - name: ubuntu
    groups: sudo
    shell: /bin/bash
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    ssh_authorized_keys:
      - ssh-rsa AAAAB3NzaC1yc2EAAAA... # Your SSH public key

packages:
  - curl
  - wget
  - git
  - htop

package_update: true
package_upgrade: true

# Set timezone
timezone: UTC

# Enable SSH
ssh_pwauth: false

# Run commands on first boot
runcmd:
  - systemctl enable ssh
  - systemctl start ssh
  - echo "VM provisioned successfully" > /tmp/provision-complete

Meta Data (cloud-init/meta-data.yaml)

instance-id: vm-instance
local-hostname: vm-host

Step-by-Step Workflow

1. Initial Setup

# Create project directory
mkdir vm-infrastructure
cd vm-infrastructure

# Create subdirectories
mkdir cloud-init images

# Generate SSH key if needed
ssh-keygen -t rsa -b 4096 -C "your-email@example.com"

2. Create Configuration Files

Create all the configuration files listed above in their respective locations.

3. Initialize OpenTofu

# Initialize the project
tofu init

# Validate configuration
tofu validate

# Plan the deployment
tofu plan

4. Deploy VMs

# Apply the configuration
tofu apply

# Confirm with 'yes' when prompted

5. Verify Deployment

# Check VM status
virsh list --all

# Get VM IP addresses
tofu output vm_ips

# SSH into a VM
ssh ubuntu@<vm-ip-address>

6. Management Commands

# Show current state
tofu show

# Update configuration (modify .tf files then)
tofu plan
tofu apply

# Destroy infrastructure
tofu destroy

Advanced Features

Custom Networks

# Create custom network
resource "libvirt_network" "vm_network" {
  name      = "vm-network"
  mode      = "nat"
  domain    = "vm.local"
  addresses = ["192.168.100.0/24"]

  dhcp {
    enabled = true
  }

  dns {
    enabled = true
  }
}

# Use custom network in VM
resource "libvirt_domain" "vm" {
  # ... other configuration ...
  
  network_interface {
    network_id     = libvirt_network.vm_network.id
    wait_for_lease = true
  }
}

Multiple VM Types

# Web servers
resource "libvirt_domain" "web_servers" {
  count  = 2
  name   = "web-${count.index + 1}"
  memory = 2048
  vcpu   = 2
  # ... other configuration ...
}

# Database server
resource "libvirt_domain" "db_server" {
  name   = "database"
  memory = 4096
  vcpu   = 4
  # ... other configuration ...
}

Storage Volumes

# Additional data volume
resource "libvirt_volume" "data_volume" {
  count = var.vm_count
  name  = "vm-${count.index + 1}-data.qcow2"
  pool  = libvirt_pool.vm_pool.name
  size  = 53687091200  # 50GB
}

# Attach to VM
resource "libvirt_domain" "vm" {
  # ... other configuration ...
  
  disk {
    volume_id = libvirt_volume.data_volume[count.index].id
  }
}

Best Practices

1. Resource Organization

  • Use consistent naming conventions
  • Group related resources in modules
  • Use variables for configurable values
  • Document your infrastructure

2. State Management

  • Store state files securely (consider remote backends)
  • Use state locking to prevent concurrent modifications
  • Backup state files regularly

3. Security

  • Use SSH keys instead of passwords
  • Configure proper firewall rules
  • Keep base images updated
  • Use cloud-init for secure initial configuration

4. Performance

  • Size VMs appropriately for workload
  • Use thin provisioning for storage
  • Monitor resource usage
  • Consider CPU topology for multi-vCPU VMs

Troubleshooting

Common Issues

1. Permission Denied

# Ensure user is in libvirt group
sudo usermod -aG libvirt $USER
# Logout and login again

# Check libvirt service status
sudo systemctl status libvirtd

2. Network Issues

# Check default network
virsh net-list --all
virsh net-info default

# Start default network if stopped
virsh net-start default
virsh net-autostart default

3. Storage Pool Issues

# Check storage pools
virsh pool-list --all

# Create default pool if missing
virsh pool-define-as default dir - - - - /var/lib/libvirt/images
virsh pool-build default
virsh pool-start default
virsh pool-autostart default

4. VM Won't Start

# Check VM configuration
virsh dumpxml vm-name

# Check libvirt logs
sudo journalctl -u libvirtd -f

# Validate domain XML
virsh validate vm-name

Debug Commands

# OpenTofu debugging
export TF_LOG=DEBUG
tofu plan

# LibVirt debugging
export LIBVIRT_DEBUG=1
virsh list

# Check VM console
virsh console vm-name

Monitoring and Maintenance

VM Monitoring

# Check VM stats
virsh domstats vm-name

# Monitor VM performance
virsh dominfo vm-name
virsh vcpuinfo vm-name

Backup Strategy

# Backup VM configuration
virsh dumpxml vm-name > vm-name.xml

# Backup VM disk
cp /var/lib/libvirt/images/vm-disk.qcow2 /backup/location/

Updates

# Update base images regularly
tofu apply -replace="libvirt_volume.base_image"

# Update OpenTofu provider
tofu init -upgrade

Conclusion

This plan provides a comprehensive approach to using LibVirt and OpenTofu for VM provisioning. The Infrastructure as Code approach ensures reproducible, version-controlled virtual infrastructure that can be easily managed and scaled.

Key benefits:

  • Reproducible: Infrastructure defined in code
  • Scalable: Easy to add/remove VMs
  • Version Controlled: Track infrastructure changes
  • Automated: Minimal manual intervention required
  • Consistent: Standardized VM configurations

Start with the basic configuration and gradually add advanced features as needed. Always test changes in a development environment before applying to production.