Docs / Networks, storage, plans

Running KLYRN VM: networks, storage, plans and customers

The install guide gets a controller and a node running. Nothing can be sold yet: a machine needs somewhere to get an address, somewhere to put a disk, a size to be, and somebody to belong to. This covers those four, in the order they have to be done.

Migration from SolusVM is its own subject, with its own guide: vm.klyrn.com/migrate/ says what observe mode does and does not touch, and what is read from the source panel.

Everything below can be done in the panel. The API paths are given because a provider automating this needs them, and because they say exactly what the panel is doing.


1. A network

A guest with no network is a guest nobody can reach. Create this before a plan, because a plan sells addresses out of it.

Three kinds:

KindWhat it isWhen
natA host bridge with a private subnet, masqueraded by the hostA lab, an internal tier, or anything that does not need to be reachable from outside
bridgedGuests attach to a bridge that already exists on the node and carries a real segmentA provider with a VLAN from their upstream. Not accepted yet: implemented and never proved on a real provider VLAN, so it is refused rather than offered on trust
routedThe host routes each guest's address to itThe ordinary shape at OVH, Hetzner and most dedicated-server providers: a block routed to one machine

POST /api/v1/networks

{
  "name": "public",
  "kind": "nat",
  "cidr": "10.100.0.0/24",
  "default": true
}

For a NAT network the gateway is the first usable address and the pool is everything after it, minus anything reserved. Nothing else is needed.

The routed case, which is the one most providers are in

A routed block is not a subnet the host sits inside. The upstream sends the whole block to one machine and that machine answers for every address in it. Two things have to be right and only one of them is obvious.

The gateway is outside the guest's own prefix. A guest holds a /32 and its gateway is the host's public address, which is not in that /32. That needs a link-scope route before a default route can be installed at all, and it is the part that is always forgotten: without it the guest boots with no network and the fault looks like DHCP.

gateway_mode: "onlink" is that shape. gateway_mode: "subnet" is the other one, where the guest really is inside the subnet.

IPv6 needs proxy NDP on the uplink, not on the bridge, because the upstream router solicits there, and unlike IPv4's proxy_arp, enabling it proxies nothing by itself: every delegated address needs its own neighbour proxy entry. A guest without them holds a correct address, has a correct default route, answers from the host, and is invisible from the internet.

KLYRN does all of this. It is written down because a provider debugging their own upstream needs to know which layer is theirs.

Pools

A pool is the range a network hands out.

POST /api/v1/networks/{id}/pools

{
  "name": "public-v4",
  "cidr": "203.0.113.0/24",
  "gateway": "203.0.113.1",
  "range_start": "203.0.113.40",
  "range_end": "203.0.113.80",
  "dns": ["9.9.9.9", "149.112.112.112"],
  "reserved": ["203.0.113.43"],
  "topology": "routed",
  "gateway_mode": "onlink"
}

range_start defaults to the first usable address after the gateway and range_end to the last usable one, so a provider handing over a whole block can leave both out.

Reservations are addresses inside the range that KLYRN will never hand out: the load balancer, the old mail host, anything that is already using one. Only reservations inside the range count towards the pool's figures, because the structural addresses (network, broadcast, gateway) have already been taken off the top, and counting one twice would show a provider a third of their block as spoken for before a guest existed.

IPv6 is delegated rather than handed out one address at a time: a /60 becomes /64s, one row each, and the guest holds the whole /64.

What you do not have to configure

Anti-spoofing is applied by the node before a guest's first frame, is re-applied when a guest restarts, and is reconciled every 30 seconds. A guest that changes its MAC is cut off entirely. There is a residual 0.12s between a tap appearing and its chain binding during which IPv6 is unconfined; it is stated here rather than hidden.


2. Storage

A pool belongs to ONE node. A three-node estate has at least three pools, even if they are all called the same thing.

DriverWantsNotes
dirA path on a filesystemqcow2 files. Simple, and thin by virtue of qcow2
lvm-thinA volume group and a thin pool inside itSnapshots are LVM's. Faster, and the space accounting is the host's

POST /api/v1/nodes/{id}/storage-pools

{
  "name": "klyrn-local",
  "driver": "dir",
  "path": "/var/lib/klyrn-vm/volumes",
  "reserve_percent": 10,
  "max_overcommit": 2
}

reserve_percent or reserve_bytes is what the scheduler will not promise. Set one. A pool that fills is a node that cannot start a guest, and the host needs room to breathe before that.

max_overcommit is how much more the pool may promise than it has, which is only meaningful for thin storage: 2 means 2 TB of disks may be sold on 1 TB of pool. A provider who does not want that leaves it at 1.

The node reports what it actually finds, and a pool that is configured and not present says so rather than being invented. A pool whose volume group is a different one under the same name is a conflict and is named as such, because adopting it as it stands would point every guest at the wrong volumes.


3. Plans

A plan is what a customer buys. It is also the only thing that decides how much of a node a machine may take, so it is the file every capacity question is answered from.

{
  "slug": "s2",
  "name": "S2",
  "vcpus": 2,
  "memory_mib": 4096,
  "disk_gib": 50,
  "ipv4_count": 1,
  "ipv6_prefix": 64,
  "cpu_mode": "baseline",
  "enabled": true
}

cpu_mode is host-passthrough, baseline or host-model. It decides two things: what the guest reads as its processor, and what it can be moved to without a restart.

  • host-passthrough, the default since 1.0.148: the host processor itself. The guest reads its real name (AMD Ryzen 5 3600 6-Core Processor) and has every instruction it has. It moves live to a host with the same processor; to any other it moves with one restart and runs on that host's processor there.
  • baseline: a standard model from the host's maker, chosen on the node from what QEMU says it can run. AMD hosts give EPYC-IBPB with svm off, which the guest reads as AMD EPYC Processor (with IBPB); Intel hosts give Haswell-noTSX-IBRS. It moves live between hosts whose processors differ, as long as they share a maker, and gives up their newer instructions.
  • host-model: libvirt's closest named model to the host, plus the features it can add. It moves live to a host with the same or a newer processor.

Baseline used to be the default, on the reasoning that a customer must be movable off a failing host. Two things changed that. KVM cannot carry a running machine between makers at all (measured, AMD to Intel), so no model could make that move live; and a move with one restart now works between any two hosts. Meanwhile the baseline list was Intel's, and on an AMD host it fell to Nehalem: a guest on a Ryzen 5 3600 read "Intel Core i7 9xx (Nehalem Class Core i7)" and had no AVX or AES-NI.

A machine's mode can be changed later from its Hardware section (admin, POST /vms/{id}/cpu).

A guest always reads its processor's base clock (its timestamp counter runs at it): 3.60 GHz on a Ryzen 5 3600, in lscpu and in Task Manager alike. KVM gives a guest no boost clock to read. The work runs at whatever the core is running at, up to the boost; the node's page shows both clocks.

The traffic allowance

"traffic_gib": 2000,
"traffic_direction": "outbound",
"traffic_action": "throttle",
"traffic_throttle_mbps": 10,
"traffic_from": "2026-10"

traffic_gib: 0 sells unmetered transfer, which is what every plan sold before allowances existed and therefore what an upgrade leaves them selling. Nothing changes under a customer because the product gained a feature.

traffic_from is the first month the allowance is enforced in, and it is the field that matters most when adding an allowance to a plan that already has machines on it. Leave it empty and the allowance is recorded and not enforced. Set it to next month and nobody is in breach of a limit that did not exist yesterday.

traffic_action is report, throttle or suspend. Suspend needs a person to reverse it. Throttle drops the port to traffic_throttle_mbps until the month turns; a 10 Mbit/s cap measures at about 9.65 Mbit/s on real hardware.

The backup schedule

"backup_schedule": "daily",
"backup_hour": 3,
"backup_keep": 7

backup_schedule is off, daily or weekly, and off is what an upgrade leaves a plan doing. backup_hour is UTC: pick your own quiet period, this product will not pick one for you. backup_weekday is 0 for Sunday.

backup_keep: 0 keeps every backup and is the default. Deleting a customer's backup is the only thing here that cannot be undone, and a provider who has not chosen a number has not asked for deletion.


4. Customers

Three roles:

RoleReaches
adminEverything, including nodes, networks, storage, plans, resellers and settings
staffEvery customer resource. No platform: nodes, networks, resellers and settings are refused
customerTheir own projects and the machines in them, and nothing else

POST /api/v1/users creates one, and gives it a default project. A machine belongs to a project, and a project belongs to a person, so a machine is never an orphan.

{"email": "freya@northwind.test", "name": "Freya", "role": "customer"}

A customer created through the provisioning API (WHMCS and anything else speaking it) is made the same way, with a generated password, by POST /api/v1/provision/services.

Doing something about an account

ActionPathNotes
SuspendPOST /api/v1/admin/users/{id}/suspendA reason is required, and the person is shown that sentence on the sign-in screen, so write it for them. It ends every session and every support session inside the account, because a suspension that leaves an open tab working is a message rather than a suspension. It does not touch their machines: suspending a person is not the same as stopping their service, and the two are different decisions with different consequences
RestorePOST /api/v1/admin/users/{id}/restore
Sign out everywherePOST /api/v1/admin/users/{id}/sign-outEvery session, not this one
Set a passwordPOST /api/v1/admin/users/{id}/password
Support sessionPOST /api/v1/admin/users/{id}/impersonateBelow

A support session is how an administrator sees what a customer sees. It is scoped to reading: inside one, every list and every ownership check answers as the customer, and the mutating half of the API is refused by an allow-list rather than by a flag somebody could forget to check. It is recorded in the audit trail as the operator AND the customer, so the trail never says a customer did something an operator did, and the customer sees in their own activity that an operator was in their account. They are not told after the fact by you.

API tokens

A token with no scopes carries its owner's authority. A scoped token can only narrow, and the scopes are: read (every GET), vm:power, vm:create, vm:console, node:read, provision, admin.

Give a monitoring box read. Give a billing robot provision. Give a customer's CI vm:power and it can restart their machines and read nothing else.


The order, one more time

  1. A network, and a pool inside it.
  2. A storage pool on each node.
  3. Plans, with an allowance and a backup schedule if you sell them.
  4. Customers, or a provisioning integration that makes them for you.

klyrn-vm doctor checks all four, and says what is missing rather than what is broken, in those words:

no network is defined
no storage pool is known on any node
  a machine's disk has to come from somewhere. Connect a node, or
  define a pool on one
no plan is defined
no node can currently fit it (4 vCPU, 8192 MiB); the roomiest node
  has 2 vCPU and 3.1 GB free

The last one is the check worth running after every plan change. A plan nothing can be placed on sells a machine that cannot be built, and the customer finds out, not you.

This page is generated from the guide that ships in the product's repository, so it describes the release it was built from. What changed in each release.