Andrew Mercer
on this page

21.1 kubectl access

Download the kubeconfig from Support → Download KubeConfig (or from Rancher for imported clusters). Everything is CRDs:

kubectl get vm,vmi -A
kubectl get virtualmachineimages.harvesterhci.io -A
kubectl get network-attachment-definitions -A
kubectl get settings.harvesterhci.io
kubectl get volumes.longhorn.io -n longhorn-system

21.2 Terraform provider

The official provider is harvester/harvester (pin to the current release from the Terraform registry):

terraform {
  required_providers {
    harvester = {
      source  = "harvester/harvester"
      version = "~> 0.6"   # pin to the current release
    }
  }
}

provider "harvester" {
  kubeconfig = pathexpand("~/.kube/harvester.yaml")
}

resource "harvester_image" "ubuntu" {
  name         = "ubuntu-2404"
  namespace    = "harvester-public"
  display_name = "ubuntu-24.04-cloudimg"
  source_type  = "download"
  url          = "https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
}

resource "harvester_clusternetwork" "data" {
  name = "data"
}

resource "harvester_vlanconfig" "data" {
  name                 = "data-all"
  cluster_network_name = harvester_clusternetwork.data.name
  uplink {
    nics        = ["enp65s0f0", "enp65s0f1"]
    bond_mode   = "802.3ad"
    mtu         = 9000
  }
}

resource "harvester_network" "vlan100" {
  name                 = "vlan100-servers"
  namespace            = "default"
  vlan_id              = 100
  cluster_network_name = harvester_clusternetwork.data.name
  route_mode           = "auto"
  depends_on           = [harvester_vlanconfig.data]
}

resource "harvester_ssh_key" "andrew" {
  name       = "andrew"
  namespace  = "default"
  public_key = file("~/.ssh/id_ed25519.pub")
}

resource "harvester_cloudinit_secret" "web" {
  name      = "web-cloudinit"
  namespace = "default"
  user_data = <<-EOT
    #cloud-config
    package_update: true
    packages: [qemu-guest-agent, nginx]
    runcmd:
      - systemctl enable --now qemu-guest-agent nginx
  EOT
}

resource "harvester_virtualmachine" "web" {
  count        = 3
  name         = "web-${count.index + 1}"
  namespace    = "default"
  hostname     = "web-${count.index + 1}"
  cpu          = 2
  memory       = "4Gi"
  run_strategy = "RerunOnFailure"
  machine_type = "q35"
  ssh_keys     = [harvester_ssh_key.andrew.id]
  tags = {
    app = "web"
  }

  network_interface {
    name           = "nic-1"
    network_name   = harvester_network.vlan100.id
    wait_for_lease = true
  }

  disk {
    name        = "rootdisk"
    type        = "disk"
    size        = "40Gi"
    bus         = "virtio"
    boot_order  = 1
    image       = harvester_image.ubuntu.id
    auto_delete = true
  }

  cloudinit {
    user_data_secret_name = harvester_cloudinit_secret.web.name
  }
}

output "web_ips" {
  value = [for vm in harvester_virtualmachine.web : vm.network_interface[0].ip_address]
}

Attribute names occasionally change between provider releases — validate with terraform validate against the docs for your pinned version.

The Rancher2 Terraform provider (rancher/rancher2) covers the Rancher side: cloud credentials, rancher2_machine_config_v2 with harvester_config, and rancher2_cluster_v2 for guest clusters.

resource "rancher2_machine_config_v2" "workers" {
  generate_name = "apps-workers"
  harvester_config {
    vm_namespace = "default"
    cpu_count    = "8"
    memory_size  = "32"
    disk_info = jsonencode({
      disks = [{ imageName = "harvester-public/ubuntu-2404", size = 100, bootOrder = 1 }]
    })
    network_info = jsonencode({
      interfaces = [{ networkName = "default/vlan100-servers" }]
    })
    ssh_user  = "ubuntu"
    user_data = file("${path.module}/cloud-init/rke2-node.yaml")
  }
}

21.3 GitOps for VMs

Because VMs, images and networks are CRDs, Fleet (from Rancher, targeting the imported Harvester cluster) or Argo CD can manage them. Practical notes:

  • Ignore fields the controllers mutate (status, some annotations, MAC addresses) via Fleet diff.comparePatches or Argo ignoreDifferences.
  • Manage images and networks via GitOps confidently; for VMs, the Terraform provider is often more ergonomic because of the volumeClaimTemplates annotation.

21.4 Ansible

kubernetes.core.k8s can apply Harvester CRDs; combine with dynamic inventory built from kubectl get vmi -o json (guest IPs reported by the guest agent) to configure guests after creation.


← Guest Kubernetes clusters on Harvester · Index · Multi-tenancy and RBAC →