Week 3 ‐ Introduction to Terraform - AlanK23/Sec-350-01 GitHub Wiki

Me and lab partner never really talked about who uses what port so we'll figure that out eventually later


Terraform Docker Code

Completed Terraform configuration that defines a full Docker stack using the student_name variable for resource isolation. All resources use the ${var.student_name}- prefix. The stack is applied and the container is verified running.

main.tf

terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0"
    }
    libvirt = {
      source  = "dmacvicar/libvirt"
      version = "~> 0.9"
    }
  }
}

provider "docker" {}

variable "student_name" {
  type        = string
  description = "Your student identifier (e.g., jsmith, mgarcia)"

  validation {
    condition     = can(regex("^[a-z]{2,10}$", var.student_name))
    error_message = "Student name must be 2-10 lowercase letters."
  }
}

variable "host_port" {
  description = "Port on the host (coordinate with your partner)"
  type        = number
  default     = 8080
}

resource "docker_image" "nginx" {
  name = "nginx:latest"
}

resource "docker_network" "lan" {
  name = "${var.student_name}-sys350-lan"
}

resource "docker_volume" "web_data" {
  name = "${var.student_name}-nginx-html"
}

resource "docker_container" "web_server" {
  name  = "${var.student_name}-web-server"
  image = docker_image.nginx.image_id

  ports {
    internal = 80
    external = var.host_port
  }

  networks_advanced {
    name = docker_network.lan.name
  }

  volumes {
    volume_name    = docker_volume.web_data.name
    container_path = "/usr/share/nginx/html"
    read_only      = false
  }
}

output "container_ip" {
  value = docker_container.web_server.network_data[0].ip_address
}

Terraform apply

  • I have it on port 8080 for now

Terraform libvirt Code

Completed Terraform configuration that defines a full libvirt stack using the student_name variable for resource isolation. All resources use the ${var.student_name}- prefix. The stack is applied and the VM is verified running.

main-libvirt.tf

provider "libvirt" {
  uri = "qemu:///system"
}

variable "vm_memory" {
  description = "VM memory in KiB"
  type        = number
  default     = 1048576 # 1 GB
}

variable "vm_vcpu" {
  description = "Number of virtual CPUs"
  type        = number
  default     = 4
}

resource "libvirt_network" "lan" {
  name      = "${var.student_name}-lan"
  autostart = true

  forward = {
    mode = "nat"
  }

  ips = [
    {
      address = "192.168.100.1"
      prefix  = 24
      family  = "ipv4"

      dhcp = {
        ranges = [
          {
            start = "192.168.100.100"
            end   = "192.168.100.254"
          }
        ]
      }
    }
  ]
}

resource "libvirt_pool" "default" {
  name = "${var.student_name}-default"
  type = "dir"

  target = {
    path = "/var/lib/libvirt/images"
    permissions = {
      owner = 64055
      group = 64055
      mode  = "0750"
    }
  }
}

resource "libvirt_volume" "ubuntu_base" {
  name = "ubuntu-base.qcow2"
  pool = "${var.student_name}-default"
  target = {
    format = {
      type = "qcow2"
    }
  }

  create = {
    content = {
      url = "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
    }
  }

  depends_on = [libvirt_pool.default]
}

resource "libvirt_volume" "ubuntu_disk" {
  name     = "${var.student_name}-ubuntu-server.qcow2"
  pool     = libvirt_pool.default.name
  capacity = 42949672960 # 40 GB

  target = {
    format = {
      type = "qcow2"
    }
  }

  backing_store = {
    path = libvirt_volume.ubuntu_base.path
    format = {
      type = "qcow2"
    }
  }
}

resource "libvirt_domain" "ubuntu_server" {
  name   = "${var.student_name}-ubuntu-server"
  memory = var.vm_memory
  vcpu   = var.vm_vcpu
  type   = "kvm"

  os = {
    type         = "hvm"
    type_arch    = "x86_64"
    type_machine = "q35"
  }

  devices = {
    disks = [
      {
        source = {
          volume = {
            pool   = "${var.student_name}-default"
            volume = libvirt_volume.ubuntu_disk.name
          }
        }
        target = {
          bus = "virtio"
          dev = "vda"
        }
        driver = {
          type = "qcow2"
        }
      }
    ]

    interfaces = [
      {
        type  = "network"
        model = { type = "virtio" }
        source = {
          network = {
            network = libvirt_network.lan.name
          }
        }
      }
    ]

    graphics = [
      {
        vnc = {
          auto_port = true
          listen    = "127.0.0.1"
        }
      }
    ]
  }

  running = true
}

output "vm_network" {
  value = libvirt_network.lan.name
}

output "vm_name" {
  value = libvirt_domain.ubuntu_server.name
}

libvirt config up


Git Version Control

Git repository initialized in the Terraform project directory. All .tf files and .gitignore committed with descriptive messages. Code pushed to a remote repository (VM #3 or GitHub). State file and .terraform/ directory excluded via .gitignore.

  • Pushed to this github

Bugs/Fixes

When applying for the libvirt configuration, I got an error that went something like:

Could not open '/var/lib/libvirt/images/ubuntu-base.qcow2': Permission denied

The fix was stopping and disabling the service apparmor


Alan's Terraform Instructions

main.tf

terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0"
    }
    libvirt = {
      source  = "dmacvicar/libvirt"
      version = "~> 0.9"
    }
  }
}

provider "docker" {}

variable "student_name" {
  type        = string
  description = "Your student identifier (e.g., jsmith, mgarcia)"

  validation {
    condition     = can(regex("^[a-z]{2,10}$", var.student_name))
    error_message = "Student name must be 2-10 lowercase letters."
  }
}

variable "host_port" {
  description = "Port on the host (coordinate with your partner)"
  type        = number
  default     = 8081
}

resource "docker_image" "nginx" {
  name = "nginx:latest"
}

resource "docker_network" "lan" {
  name = "${var.student_name}-sys350-lan"
}

resource "docker_volume" "web_data" {
  name = "${var.student_name}-nginx-html"
}

resource "docker_container" "web_server" {
  name  = "${var.student_name}-web-server"
  image = docker_image.nginx.image_id

  ports {
    internal = 80
    external = var.host_port
  }

  networks_advanced {
    name = docker_network.lan.name
  }

  volumes {
    volume_name    = docker_volume.web_data.name
    container_path = "/usr/share/nginx/html"
    read_only      = false
  }
}

output "container_ip" {
  value = docker_container.web_server.network_data[0].ip_address
}

main-libvirt.tf

provider "libvirt" {
  uri = "qemu:///system"
}

variable "vm_memory" {
  description = "VM memory in KiB"
  type        = number
  default     = 1048576 # 1 GB
}

variable "vm_vcpu" {
  description = "Number of virtual CPUs"
  type        = number
  default     = 4
}

resource "libvirt_network" "lan" {
  name      = "${var.student_name}-lan"
  autostart = true

  forward = {
    mode = "nat"
  }

  ips = [
    {
      address = "192.168.101.1"
      prefix  = 24
      family  = "ipv4"

      dhcp = {
        ranges = [
          {
            start = "192.168.101.100"
            end   = "192.168.101.254"
          }
        ]
      }
    }
  ]
}

resource "libvirt_pool" "default" {
  name = "${var.student_name}-default"
  type = "dir"

  target = {
    path = "/var/lib/libvirt/images/${var.student_name}"
    permissions = {
      owner = 64055
      group = 64055
      mode  = "0750"
    }
  }
}

resource "libvirt_volume" "ubuntu_base" {
  name = "${var.student_name}-ubuntu-base.qcow2"
  pool = libvirt_pool.default.name

  target = {
    format = {
      type = "qcow2"
    }
  }

  create = {
    content = {
      url = "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
    }
  }

  depends_on = [libvirt_pool.default]
}

resource "libvirt_volume" "ubuntu_disk" {
  name     = "${var.student_name}-ubuntu-server.qcow2"
  pool     = libvirt_pool.default.name
  capacity = 42949672960 # 40 GB

  target = {
    format = {
      type = "qcow2"
    }
  }

  backing_store = {
    path = libvirt_volume.ubuntu_base.path
    format = {
      type = "qcow2"
    }
  }
}

resource "libvirt_domain" "ubuntu_server" {
  name   = "${var.student_name}-ubuntu-server"
  memory = var.vm_memory
  vcpu   = var.vm_vcpu
  type   = "kvm"

  os = {
    type         = "hvm"
    type_arch    = "x86_64"
    type_machine = "q35"
  }

  devices = {
    disks = [
      {
        source = {
          volume = {
            pool   = libvirt_pool.default.name
            volume = libvirt_volume.ubuntu_disk.name
          }
        }
        target = {
          bus = "virtio"
          dev = "vda"
        }
        driver = {
          type = "qcow2"
        }
      }
    ]

    interfaces = [
      {
        type  = "network"
        model = { type = "virtio" }
        source = {
          network = {
            network = libvirt_network.lan.name
          }
        }
      }
    ]

    graphics = [
      {
        vnc = {
          auto_port = true
          listen    = "127.0.0.1"
        }
      }
    ]
  }

  running = true
}

output "network_address" {
  value = "Network '${libvirt_network.lan.name}' CIDR: 192.168.101.0/24, Gateway: 192.168.101.1"
}

output "vm_name" {
  value = libvirt_domain.ubuntu_server.name
}

Confirm the host is clear before applying

sudo ss -tlnp | grep 8081
virsh pool-list --all
virsh net-list --all
ls -la /var/lib/libvirt/images/

Set my variables

nano terraform.tfvars
student_name = "alank"
host_port    = 8081

Initialize and apply

terraform init
terraform plan
terraform apply

Verify the Docker stack

docker ps
curl -I http://localhost:8081

Verify the libvirt stack

virsh list --all
virsh net-list --all
virsh pool-list --all

Confirm all resources are tracked in state

terraform state list

Demonstrate the destroy and apply cycle

terraform destroy -target=docker_container.web_server
docker ps
terraform apply -target=docker_container.web_server
docker ps
.terraform/
terraform.tfstate
terraform.tfstate.backup
terraform.tfstate.*.backup
*.tfvars
!example.tfvars
crash.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json

Format and validate

terraform fmt
terraform validate

Tear down

terraform destroy