Skip to content

Feature Status

Fabrizio Salmi edited this page Sep 7, 2026 · 4 revisions

Feature status

The README lists what the daemon is meant to do. This page says what it does today. It is maintained by hand, so check the issue tracker if something here looks stale.

Current release: v2.0.3.

If you are running v2.0.2, upgrade

v2.0.2 can shrink a container until the kernel kills a process inside it. The page-cache exclusion it introduced subtracted the whole file-backed total from memory.stat, and on cgroup v2 that total includes shared memory, which is swap-backed and cannot be reclaimed on a container with no swap.

Measured on a 512 MB container holding 200 MB in /dev/shm: genuinely 39.9% full, reported as 0.41%, shrunk to its 256 MB floor, then oom_kill 1 on the next ordinary allocation while the daemon still reported 0.79%. Fixed in v2.0.3 by subtracting only file minus shmem.

The workloads affected are ordinary: /run is tmpfs in every systemd container, and PostgreSQL's shared buffers are shared memory. If you cannot upgrade now, set memory_exclude_cache: false, which restores the pre-2.0.2 accounting in which containers never scale down. That is the opposite failure and the safe one.

Works

Vertical scaling of CPU and memory. The core loop: read usage, compare against thresholds, adjust cores and memory within limits. Measured on Proxmox VE 9.1: a container saturated at one core, raised to three, went from 97% of one core to 200% of a core of real throughput. This is the feature that earns the project its place.

Two things to know about it. The reported CPU percentage is normalised by core count, so adding cores lowers the number without the workload changing. And a container cannot reach a max_cores that is not an exact number of increments away from where it is: the increment is discarded rather than clamped when it would overshoot, so a container at three with an increment of two never reaches a ceiling of four.

Tiers, ignore_lxc exclusion, off-peak energy mode, notifications over SMTP, Gotify and Uptime Kuma, and the JSON metrics log all sit on that same path.

CPU core pinning, since v2.0.3. See below for the one interaction that surprises people.

Works, with a caveat you must read

Pinning turns off CPU scaling for that tier

Proxmox derives a container's affinity from cores only when the configuration carries no explicit cpuset line. Setting cpu_pinning writes one, so from the container's next start cores no longer controls anything, while the daemon goes on computing increments and logging "Increase Cores" for a value the hypervisor has stopped reading.

Verified on PVE 9.1: cores: 1 with a pin of 0-1 gave a container that reported two CPUs after a restart. Use cpu_pinning or CPU scaling on a given tier, not both.

l3:0 and numa:0 are usually not a restriction

On a single-socket host with one L3 domain and one NUMA node, which is most machines, both resolve to every online CPU. No warning is emitted. Check the logged group line before assuming a pin is confining anything.

Boost mode

It works, and a transient failure makes it permanent. The reconcile pass that runs at startup treats a pct config that returns nothing as "the container no longer exists" and drops the boost record, and a dropped record is never reverted: the elevated allocation silently becomes the new baseline. Verified by injection, with the container running and only that one command failing.

What cpu_pinning accepts

p-cores and e-cores (hybrid Intel only, read from the perf PMU at /sys/devices/cpu_core/cpus and cpu_atom/cpus), l3:N for one L3 cache domain (a CCD on AMD), numa:N for one NUMA node, all, or an explicit range such as 0-7 or 0,2,4-6. Explicit ranges are validated against the host's online CPUs since v2.0.3.

Clearing a pin written by the old bug

Before v2.0.3, p-cores resolved to every core and wrote that. The fix does not remove an existing cpuset: deleting an operator's line because their configuration has a typo was judged worse than leaving it and saying so.

pct set has no --cpuset option, so a stale pin is cleared by editing the file:

grep -n 'cpuset' /etc/pve/lxc/*.conf        # what is actually set
# then delete the lxc.cgroup2.cpuset.cpus line from the container's .conf

A cpuset.cpus line appearing after a [snapshot-name] header is inert and can be ignored.

Documented but not implemented

The Proxmox REST API backend. The configuration keys exist and validate, and the code is in the tree, but nothing in the running daemon imports it: every operation goes through pct. Setting backend: api changes no behaviour. This matters most in the security guide, which suggested the API backend as the way to run non-root; doing that today produces a daemon that cannot act at all. Tracked as #56.

Backup and --rollback. The backup helper is reachable only from a function nothing calls, so no backup file is ever written and --rollback restores nothing while reporting success. Confirmed on a live node: after a run that resized a container, the configured backup directory did not exist. Tracked as #88.

Secret masking in logs. The filter is attached to the root logger, and in Python a logger's filters do not apply to records propagated from child loggers. Every module here uses its own logger, so nearly all output bypasses it. Tracked as #90.

Experimental, with verified defects: horizontal scaling

Two defects found by inspection and fixed: #70, every clone received the same static IP, and #71, the documented min_instances key was read as min_containers and discarded.

What remains, verified on a live node rather than by reading:

  • Group membership is lost on restart. It lives in memory and is never persisted, so a restarted daemon does not know about the clones it created.
  • It then retries a colliding id forever. Not knowing the clone exists, it recomputes the next id, arrives at one already in use, and pct clone fails. The grace period is recorded only after a successful scale-out, so nothing throttles the retry: it fires every poll.
  • Each failed attempt leaves an LVM snapshot. The snapshot is taken before the clone and never removed on failure. Four accumulated in ninety seconds of retrying; at the default five-minute interval that is 288 per day on the source container, growing without bound, on thin-provisioned storage.
  • Scale-in stops without destroying, so ids are never released.

Pilot it on a node you are watching, and check pct listsnapshot <source> after the first restart.

In flight: autoscaling groups

A larger redesign tracked as ASG-1 to ASG-14 (#56 to #69): a backend abstraction that is actually connected, node placement, instance lifecycle, health checks, replacement of unhealthy instances, and a controller loop whose state is reconciled from the cluster rather than held in memory. The design note is docs/design/asg-horizontal-scaling.md.

Nothing to configure yet. It is listed here so that "when will horizontal scaling be solid" has a visible answer.