Host agent
The Aicerine host agent (aicerine-agent) is a small static binary installed on each
nova-compute host. It measures what the host and its VMs actually consume, including the
steal each VM suffers, and pushes it to your appliance. It is read-only and
outbound-only by construction.
The agent takes no part in applying a plan. Ratios and live migrations go from the appliance to the Nova and Placement APIs, under the appliance's own credential: a compromised agent cannot move a VM.
The agent is written in Rust. Like the appliance, it is proprietary: its source code is not published. Its wire contract is part of the versioned Aicerine protocol.
What exists today
| Part | Status |
|---|---|
| Agent core: host and VM collectors, derivation, buffer, mTLS client, self-reporting | Built, tested on recorded host trees |
| Wire contract (telemetry protocol 0.1) | Built, with conformance fixtures |
Appliance telemetry role (mTLS listener, storage, hourly rollups) | Built, tested against a real PostgreSQL |
| Bus collector (Nova notifications relay) | Built, with an end-to-end test against RabbitMQ 3.13; replacing the appliance's current ingest role comes after a lab run |
| Packaging | .deb and .rpm built locally, not published; container image and DaemonSet written, image not built |
| Appliance console view of the fleet | Not built |
Optional libvirt enrichment (read-only socket, no libvirt library) | Written, not yet run in the lab |
A 24-hour run on a real DevStack host is still to be done.
What it reads
Every 10 seconds the agent reads files, nothing else:
- The host:
/proc/stat,/proc/loadavg,/proc/pressure/*,/proc/meminfo,/proc/diskstats,/proc/net/dev,/proc/uptimeand/sys. CPU by mode including steal, pressure stall information (PSI), memory, hugepages, physical disks and NICs. - Each VM: every
qemu-system-*process in/proc. The instance UUID comes from the process's own-uuidargument, so the join with Nova needs neither Nova nor libvirt. CPU, memory, block IO and PSI come from the process's cgroup (v2); the steal a VM really suffers comes from the scheduler wait time of its vCPU threads. - No libvirt dependency, no plugin, no exporter, no runtime. The binary is static and links no shared library. An optional collector reads libvirt's read-only socket, with a fixed list of read calls, to map network interfaces to VMs.
What it sends
Every 30 seconds, one compressed batch over a single outbound mutual-TLS connection:
- Host and VM samples, timestamped at the source. Ratios carry the last value, the mean and the maximum over the window; counters are raw, and a counter that goes backwards (VM restart, reboot) is flagged, never interpolated.
- Its own footprint and latency, in every batch: memory, CPU, descriptors, the duration of each stage (collect, serialize, compress, send), batch sizes, drops, errors and clock offset. Nobody has to take its lightness on trust: the appliance records it per host.
- Relayed Nova notifications, from one agent instance per message bus only. That
instance reads the
novaversioned notifications from RabbitMQ and relays them to the appliance in the same channel. Hypervisors never hold the RabbitMQ credential.
The batch carries a hash of the machine identity, never an IP address and never a guest
name. Per-VM network counters come from the tap interfaces; the appliance maps them to
instances.
What it never does
- No inbound port. The only exception is an optional, off-by-default Prometheus exposition of the agent's own block, bound to loopback unless you decide otherwise.
- No disk writes. No spool, no log file, no state file. The buffer is in memory, bounded to 10 minutes and about 8 MiB, whichever comes first (10 minutes up to about 100 VMs, about 80 seconds at 300); beyond that the oldest data is dropped and counted.
- No command path. The appliance can only ask the agent to go slower or to pause a collector. It cannot make it run, fetch or change anything.
- No guest introspection. Nothing is read from inside a VM.
- No calls to Nova, Keystone or Placement. The agent holds no OpenStack credential.
Footprint
The budget is contractual: the agent reports the numbers below about itself, and the unit file enforces them.
| Resource | Budget | Enforced by |
|---|---|---|
| Memory (RSS) | 15 MiB or less (target 8); bus instance 30 MiB or less | MemoryMax=48M |
| CPU | 0.5 % of one core or less on average at 100 VMs; peak 5 % or less | CPUQuota=5% |
| Disk | Binary 8 MiB or less; no writes | ProtectSystem=strict, no state directory |
| Network | One persistent outbound connection; compressed batches | Bounded batch size |
| Descriptors | 32 or less | LimitNOFILE=64 |
Status: measured on a developer machine, lab run pending. The bench below uses simulated hosts and 90-second runs; the run on real hypervisors has not happened yet.
| VMs on the host | RSS (max) | CPU (mean, share of one core) | Network (gzip, 30 s pushes) | Disk writes |
|---|---|---|---|---|
| 10 | 1.6 MiB | 0.03 % | about 0.1 KiB/s | 0 |
| 50 | 2.0 MiB | 0.11 % | about 0.3 KiB/s | 0 |
| 100 | 2.5 MiB | 0.25 % | about 0.5 KiB/s | 0 |
| 300 | 10.4 MiB | 0.51 % | about 1.5 KiB/s | 0 |
The CPU and memory budgets are set at 100 VMs, and that line is within them. The 300-VM line is shown for information.
Configuration
One file, /etc/aicerine/agent.toml, unknown keys rejected, no secret in environment
variables, no auto-discovery:
| Key | Purpose |
|---|---|
endpoint | The appliance's telemetry listener (https) |
cert, key, ca | The host's client certificate, its key, and the agents' CA |
interval | Collection period in seconds (default 10) |
push_interval | Push period in seconds (default 30) |
collectors | host, vms, notifications (bus instance), libvirt (optional) |
libvirt_socket | Read-only libvirt socket, for the optional collector |
amqp_url, amqp_ca, broker_name, queue | Bus instance only |
prometheus_listen | Off by default |
log_level | JSON lines on stderr |
Packaging targets
| Target | Form | Status |
|---|---|---|
| Bare hosts (Debian, Ubuntu, RHEL) | .deb / .rpm and a hardened systemd unit, dedicated user | Packages built locally, not published |
| Kolla-Ansible | Minimal container image (static binary only), host PID namespace, read-only mounts | Dockerfile written, image not built |
| OpenStack on Kubernetes (RHOSO, OpenStack-Helm) | The same image as a DaemonSet | Manifest written, image not built; needs an exception, see below |
Security
- One certificate per host, signed by a dedicated agents' CA, separate from the CA used by the optional Nova weigher and from the operators' sign-in: a credential opens one surface only.
- A separate certificate for the bus instance. Only a certificate marked as such may relay notifications.
- Revocation by removing the host on the appliance, which then answers 403.
- Least privilege: a dedicated unprivileged user, an empty capability set, no new
privileges. Everything it reads is world-readable on a default
/proc. - Reading qemu processes. The packaged default is a plain unprivileged user with no
capability: everything the agent reads is world-readable on a default
/proc. Hosts that mount/procwithhidepidneedCAP_SYS_PTRACE(read-only use) as a documented drop-in. Group membership of the qemu owner was rejected because it would grant access to VM disk images. To be confirmed on a real host in the lab run. - Supply chain: no C library, pinned dependencies, advisory and audit checks in CI. Signed, reproducible binaries and an SBOM will follow with the release pipeline.
The bus account on RabbitMQ needs a topic permission limited to the versioned notifications;
without it, read access to the nova exchange would expose Nova's RPC traffic. See
Security.
Open points
- GPU. Not measured in this version. It comes with the first operator who has GPUs.
- Per-VM network without libvirt.
tapinterfaces carry no VM identity in/proc; the appliance maps them from Nova's notifications. This works only while those notifications arrive. - OpenShift Pod Security. The agent needs the host's
/proc,/sysand PID namespace, which therestrictedprofile forbids. The DaemonSet needs a namespace labelledprivileged; we document the exception rather than hide it. - Hosts without qemu (Ironic, VMware through Nova, Hyper-V): only the host collectors run.