A Tenant (tenants.k7.katakate.org/v1alpha1, cluster-scoped) claims namespaces, dedicated nodes, a default sandbox size, quotas and per-VM limits. Concepts and semantics: Tenants, roles and OIDC.
Tenants#
| Method | Path | Role |
|---|---|---|
| GET | /api/v1/tenants | operator, admin |
| GET | /api/v1/tenants/{name} | operator, admin |
| POST | /api/v1/tenants | admin |
| PATCH | /api/v1/tenants/{name} | admin |
| DELETE | /api/v1/tenants/{name}[?cascade=true] | admin |
spec.namespaces(required, ≥ 1): created if missing and labelledk7.katakate.org/tenant. A namespace another tenant already claims is400.spec.nodes: the tenant’s sandboxes land only on these nodes (selector + toleration). Dedicate them with the node routes below so other tenants stay off.spec.defaultSize(cpu,memory,disk): filled into creates without limits. Production mode requires CPU and memory on every sandbox, so set it or make callers passlimits.spec.quotas(sandboxes,vcpu,memory,disk,snapshotStorage): checked at the Deployment and VolumeSnapshot creates under a per-tenant Lease, so concurrent creates on several API replicas cannot overshoot. Usage is reserved (a paused sandbox keeps its vCPU/memory/disk). Over-quota:403 QuotaExceeded.status.usageis rewritten every 60 s by the leader replica.spec.limits.maxSandboxSize: a request above it is400.limits.network(egressBps/ingressBps, bits/s) is enforced on kfd and kql;limits.diskIOis accepted by the CRD but no backend enforces it yet — a tenant that sets it (ornetwork/ adiskquota on k7d / k7d-fc) gets400 LimitNotEnforceableon create rather than running unlimited.PATCHreplaces whole sections;nullremoves one.DELETErefuses a tenant that still has sandboxes unlesscascade=true.
For a single-namespace tenant K7 also writes a Kubernetes ResourceQuota (k7-tenant-quota) as a backstop against kubectl apply bypassing the API; multi-namespace tenants have none.
Usage (showback)#
Endpoint: GET /api/v1/usage?from=&to=&tenant=&group_by=
tenant,namespace,backend,stateReturns rows of sandbox_seconds, vcpu_seconds, memory_gib_seconds, egress_bytes, disk_gib_seconds and snapshot_gib_seconds per group, read from the Prometheus at metering.prometheusUrl (k7 install --monitoring or --metering-prometheus-url). Without one the route answers 400 naming the key; an unreachable Prometheus is 502 PrometheusUnavailable. K7 keeps no usage database — the CloudEvents usage stream is the record for billing. See Observability and metering.
Node dedication#
| Method | Path | Body | Role |
|---|---|---|---|
| POST | /api/v1/nodes/{name}/dedicate | {"tenant": "acme"} | operator, admin |
| POST | /api/v1/nodes/{name}/undedicate | — | operator, admin |
Dedicating writes the k7.katakate.org/tenant label and a NoSchedule taint on the node and adds it to Tenant.spec.nodes; undedicating reverses all three. {name} is the Kubernetes node name (k7 nodes list); an unknown node or tenant is 404.
Remote install#
| Method | Path | Role |
|---|---|---|
| POST | /api/v1/install | admin |
| GET | /api/v1/install/{operation} | admin |
Off by default (404). k7 install --api-remote-install (development mode only) deploys the k7-node-installer DaemonSet in k7-system, configured from the first master and pinned to the digest in images.lock.yaml. The API never runs Ansible: POST takes exactly {"nodes": ["name", …]}, labels those nodes into the installer pool and returns 202 with an operation id; GET reports each node pending / running / succeeded / failed with the installer’s own failure line.
Any other body key (playbook, image, inventory, config) is 400; an unknown node 404; a K3s server node without --node-installer-allow-server-restart 409; a DaemonSet whose image drifted from the pin 500. SDK: Client.install(nodes) / install_status(operation).
