035 Project 35: Proxmox Multi-Node Scheduler
035 Build a Proxmox Multi-Node Scheduler
Schedule VM placement across nodes by free memory and CPU load.
fetch node stats -> score nodes -> choose best node -> create/migrate plan
Full main.go
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"sort"
)
type NodeStat struct {
Node string `json:"node"`
CPU float64 `json:"cpu"`
Mem float64 `json:"mem"`
MaxMem float64 `json:"maxmem"`
}
type apiResp struct {
Data []NodeStat `json:"data"`
}
func score(n NodeStat) float64 {
freeMemRatio := 1 - (n.Mem / n.MaxMem)
cpuHeadroom := 1 - n.CPU
return freeMemRatio*0.7 + cpuHeadroom*0.3
}
func main() {
base := os.Getenv("PVE_BASE_URL")
token := os.Getenv("PVE_TOKEN")
if base == "" || token == "" {
fmt.Println("set PVE_BASE_URL and PVE_TOKEN")
os.Exit(2)
}
req, _ := http.NewRequest(http.MethodGet, base+"/api2/json/nodes", nil)
req.Header.Set("Authorization", "PVEAPIToken="+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var out apiResp
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
panic(err)
}
sort.Slice(out.Data, func(i, j int) bool { return score(out.Data[i]) > score(out.Data[j]) })
fmt.Println("placement order:")
for _, n := range out.Data {
fmt.Printf("node=%s score=%.3f cpu=%.2f mem=%.0f/%.0f\n", n.Node, score(n), n.CPU, n.Mem, n.MaxMem)
}
}Step-by-Step Explanation
- Collect node/resource metrics from API sources.
- Compute deterministic scores per target.
- Rank candidates by score and policy constraints.
- Produce dry-run placement/migration decisions.
- Apply gradually with canary and rollback plan.
Code Anatomy
- Data collection stage fetches capacity and load.
- Scoring stage turns metrics into comparable values.
- Decision stage emits ranked scheduling actions.
Learning Goals
- Build scheduling logic that is explainable and auditable.
- Balance utilization, reliability, and operational safety.
- Prepare for production-grade orchestration workflows.