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.comparePatchesor ArgoignoreDifferences. - 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 →