Harvester
:::tip Complete ✅ Three-node Harvester v1.7.1-govt.2 cluster running on nuc-01/02/03. Post-install bootstrap (namespaces, CA injection, LoadBalancers) still pending — see Post-Install Bootstrap below. :::
Bare-Metal Install
Overview
Three Intel NUC nodes (nuc-01, nuc-02, nuc-03) form a Harvester HCI cluster via network boot. nuc-01 creates the cluster and waits for the VIP to become reachable before nuc-02 and nuc-03 join. The entire install is unattended: iPXE fetches the Harvester ISO and a per-node YAML config from nuc-00's web server, and the nodes bootstrap themselves.
The cluster VIP is 10.0.0.100. Once it is reachable over HTTPS, the cluster is up.
Prerequisites
Before booting any Harvester node:
- Hauler store loaded and
hauler store serve registryrunning on nuc-00 port5000(Harvester pulls its container images from here during install) - Harvester iPXE assets unpacked at
/srv/www/htdocs/harvester/v1.7.1-amd64-govt.2/ - Per-node config YAML files generated and placed at
/srv/www/htdocs/harvester/harvester/ - DHCP host entries configured in
/etc/dhcpd.conffor the MAC addresses of nuc-01, nuc-02, and nuc-03 so each gets a predictable IP
Do not boot nuc-02 or nuc-03 before nuc-01's cluster creation is complete and the VIP
10.0.0.100 is reachable. Joining nodes that find no cluster to join enter a broken state.
Install procedure
Step 1 — Boot nuc-01 (create cluster)
Power on nuc-01 and select network boot (F12 on NUC). The iPXE menu appears and either auto-selects or offers the option Deploy Harvester to nuc-01 (create cluster). The node installs Harvester, writes the cluster config, and reboots. This takes approximately 10–15 minutes.
Step 2 — Wait for the cluster VIP
From nuc-00, poll until the API is reachable:
until curl -sk https://10.0.0.100/ping | grep -q "pong"; do
echo "Waiting for Harvester VIP..."; sleep 15
done
echo "Harvester cluster is up"
Do not proceed until this returns successfully.
Step 3 — Boot nuc-02 (join)
Power on nuc-02 and select network boot. The iPXE menu selects Join nuc-02 to cluster.
The node installs and joins. Wait for the node to appear Ready in the Harvester UI
(https://10.0.0.100 → Hosts).
Step 4 — Boot nuc-03 (join)
Repeat for nuc-03. Once all three nodes show Ready, bare-metal install is complete.
Regenerating configs
If node configs need to be regenerated (e.g., after a MAC address change or config correction):
bash /srv/www/htdocs/carbide-enclave.kubernerdes.com/infra/nuc-00/srv/www/htdocs/harvester/harvester/generate-harvester-configs.sh
The script writes new YAML files to /srv/www/htdocs/harvester/harvester/ and can be re-run
safely. Config files are served over HTTP — no restart of any service is needed after regeneration.
Post-Install Bootstrap
After the three-node cluster is up and you have the Harvester kubeconfig, run
30_bootstrap-harvester.sh to configure namespaces, inject the internal CA certificate, and
apply the three service LoadBalancers.
Prerequisites
- Harvester cluster formed and reachable at
https://10.0.0.100 - Harvester kubeconfig downloaded to
~/.kube/carbide-enclave-harvester.kubeconfig(Harvester UI: Support → Download KubeConfig, or copy/etc/rancher/rke2/rke2.yamlfrom nuc-01) - step-ca running on nuc-00 with the root certificate served at
http://10.0.0.10/step/carbide-enclave-root-ca.crt kubectlin PATH on nuc-00
Run the script
KUBECONFIG=~/.kube/carbide-enclave-harvester.kubeconfig \
bash /srv/www/htdocs/carbide-enclave.kubernerdes.com/scripts/30_bootstrap-harvester.sh
The script is idempotent — safe to re-run if a step fails partway through.
What it does
1. VM namespaces
Creates three namespaces that map to workload tiers. These appear as Projects in the Harvester UI and are where OpenTofu places VMs.
| Namespace | Workload |
|---|---|
vms-rancher | RKE2 management cluster VMs (rancher-01/02/03) |
vms-observability | Observability stack VMs (Grafana, Prometheus) |
vms-apps | General application VMs |
2. Internal CA injection
Patches the additional-ca cluster setting (settings.harvesterhci.io) with the step-ca
root certificate. Harvester's controller distributes this cert to every node's system trust
store. Without this, Harvester cannot verify the TLS certificate served by Harbor, causing
image pulls to fail with certificate errors.
The root certificate is fetched from the bastion at runtime:
http://10.0.0.10/step/carbide-enclave-root-ca.crt
3. LoadBalancers
Applies IPPool and LoadBalancer resources that give each service tier a pinned VIP.
Harvester's built-in load balancer controller (backed by kube-vip) manages these.
| LB name | VIP | Ports | Backend selector |
|---|---|---|---|
rancher-api | 10.0.0.30 | 6443, 9345, 80, 443 | tag.harvesterhci.io/role: rke2-server |
observability-ingress | 10.0.0.40 | 80, 443 | harvesterhci.io/vmNamePrefix: observability |
apps-ingress | 10.0.0.50 | 80, 443 | harvesterhci.io/vmNamePrefix: apps |
Source manifests: infra/harvester/lb-apps.yaml, infra/harvester/lb-observability.yaml
(the rancher-api LB manifest was applied in an earlier step).
Backend selector: labeling VMs
Harvester's LB controller matches backend VMs by comparing the backendServerSelector labels
against labels on VirtualMachine objects in the same namespace.
For vms-rancher, the RKE2 VMs must carry the tag role: rke2-server. Set it in OpenTofu:
resource "harvester_virtualmachine" "rancher" {
# ...
labels = {
"tag.harvesterhci.io/role" = "rke2-server"
}
}
For vms-observability and vms-apps:
harvesterhci.io/vmNamePrefix: observability # or: apps
The harvesterhci.io/vmNamePrefix label is not automatically set based on the VM name — it
must be applied explicitly on each VM via OpenTofu or the Harvester UI. Alternatively, replace
it with a tag.harvesterhci.io/ label and update the backendServerSelector in the manifest
to match.
Verify
After the script completes, confirm all resources are in place:
export KUBECONFIG=~/.kube/carbide-enclave-harvester.kubeconfig
# LoadBalancer VIPs — look for ALLOCATED-ADDRESS = 10.0.0.30 / .40 / .50
kubectl get loadbalancers.loadbalancer.harvesterhci.io -A
# IP pools
kubectl get ippools.loadbalancer.harvesterhci.io
# Namespaces
kubectl get namespaces vms-rancher vms-observability vms-apps
# CA cert injected — should print subject and validity dates
kubectl get settings.harvesterhci.io additional-ca \
-o jsonpath='{.value}' \
| openssl x509 -noout -subject -issuer -dates
All four commands should return clean output with no errors before proceeding.
Next step
Provision the RKE2 management cluster VMs via OpenTofu: RKE2 Cluster