Concepts

Dynamic Resource Scheduling

Automatic VM placement across Proxmox cluster nodes based on available memory. The DRS scheduler queries node resources at decision time, filters out offline or overloaded hosts, and selects the best fit.

When you create a VM, you shouldn't have to think about which physical host runs it. Manual placement doesn't scale. It leads to uneven resource distribution, overloaded nodes, and idle capacity sitting right next to them. The scheduler makes this decision for you.

This is what VMware calls DRS (Distributed Resource Scheduler) and what OpenStack Nova handles with its filter-and-weight scheduler. flex.plane takes a simpler, stateless approach.

How scheduling works

When a VM is created within a zone, the scheduler decides which host runs it. The process is straightforward:

  1. Gather candidates. The scheduler queries all hosts in the target zone and filters out nodes that are offline.
  2. Check resources. Nodes that don't have enough available memory for the requested compute profile are eliminated.
  3. Pick the best fit. From the remaining candidates, the scheduler selects the node with the most available memory.
new-vm
32 GiB RAM
scheduler
pve-fra-01
200 / 256 GiB56 GiB free
pve-fra-02
80 / 256 GiB176 GiB free
selected
pve-fra-03
160 / 256 GiB96 GiB free
pve-fra-04
120 / 128 GiBoffline

If you explicitly specify a host when creating a VM, the scheduler is bypassed entirely and the VM is placed on the host you chose (assuming it has sufficient resources).

mutation {
  createVM(
    zone: "eu-central"
    # omit `host` to let the scheduler decide
    name: "web-server"
    options: {
      image: "ubuntu-2404"
      computeProfile: "medium-profile-id"
      storageProfile: "standard-profile-id"
      sshPublicKey: "ssh-ed25519 AAAA..."
      interfaces: [{ network: "100" }]
      user: "admin"
      password: "initial-password"
      backup: false
    }
  ) {
    id
    name
    host { id name }
  }
}

The current strategy is least-loaded memory. Memory is the hard constraint in virtualization. You can overcommit CPU (a VM using 10% of its allocated cores still shares the physical CPU), but you can't overcommit memory without risking OOM kills. Placing VMs where memory headroom is largest keeps your Proxmox cluster balanced and avoids pressure on any single node.

Stateless by design

Like everything else in flex.plane, the scheduler has no persistent state. It queries node resources from Proxmox at decision time, evaluates candidates, and returns a placement decision. If the orchestrator restarts, scheduling works immediately. There's no state to recover, no history to rebuild, no cache to warm up.

This means the scheduler always works with current reality. A node that was idle five minutes ago but just received a large VM will show its updated resource usage on the next scheduling decision. No stale data, no drift. For more on why this matters, see Stateless Orchestrator.

Scheduling constraints (planned)

The scheduler today handles the most common case well: spread VMs across your cluster by available memory. But real-world deployments have more nuanced requirements. These constraints are planned for future releases.

ConstraintPurposeExample
Node selectorPin a VM to a specific hostGPU passthrough, hardware licensing
AffinityCo-locate VMs on the same hostApp server + sidecar cache
Anti-affinityKeep VMs on separate hostsDatabase primary + replica
TaintsMark nodes to repel general workloadsReserve GPU nodes for GPU VMs
TolerationsAllow a VM to run on tainted nodesGPU workload on a GPU-tainted node

The vocabulary is borrowed from Kubernetes. If you've worked with nodeSelector, pod affinity, or taints and tolerations in Kubernetes, the concepts are the same, adapted for VMs instead of pods.

Scheduling constraints will be stored as Proxmox VM tags using a flxp/ prefix, keeping the metadata on the workload itself rather than in an external store. Tags survive migrations, are visible in the Proxmox UI, and don't depend on flex.plane being available.

Rebalancing and live migration (planned)

Initial placement handles where a VM starts. But workloads shift over time. A VM that was idle at creation might later consume significant resources, creating hotspots. Rebalancing detects these imbalances and migrates VMs to restore balance.

The core metric is simple, inspired by ProxLB's "balanciness" approach: measure the difference between the most-loaded and least-loaded node in a cluster. When this delta exceeds a threshold, the rebalancer identifies VMs to migrate and moves them to less-loaded nodes, converging the cluster toward an even distribution.

Rebalancing is a future capability. It requires live migration support, safety mechanisms (don't migrate too many VMs at once, respect constraints), and careful testing. The scheduler's pluggable interface is designed with this in mind. New strategies slot in without changing the placement pipeline.

The scheduler intentionally starts simple. Least-loaded memory placement covers the majority of use cases. Advanced constraints like affinity, taints, and rebalancing are additive. Each can be introduced independently without changing how basic scheduling works.