Skip to main content

Fabric on Kubernetes

Run Zyvor Fabric (control plane + FluxVM) as in-cluster workloads — the same packaging UX as Ragnarok (manifests, Helm, remote ctr import), adapted for Fabric’s privilege model.

Fabric is not a restricted Deployment app. It needs hostNetwork, KVM, and host networking privileges — the same reasons docker-compose.yml uses network_mode: host and /dev/kvm.

This guide covers running fabricd + FluxVM on Kubernetes. The separate operator installs a controller that reconciles VirtualMachine CRs against an already-running fabricd API.


Architecture​

┌─ node (privileged PSS namespace: zyvor-fabric) ─────────────────────┐
│ │
│ DaemonSet fluxvm hostNetwork · /dev/kvm · :7788 │
│ ▲ │
│ │ REST 127.0.0.1:7788 │
│ DaemonSet zyvor-fabricd hostNetwork · nftables · :9095 │
│ ▲ │
│ │ NodePort 30095 (also host :9095) │
│ Service zyvor-fabricd │
└─────────────────────────────────────────────────────────────────────┘
ComponentKindWhy
zyvor-fabricdDaemonSet, hostNetworknftables/rtnetlink on the real host; reach FluxVM on loopback
fluxvmDaemonSet, hostNetwork, privilegedKVM + cgroup/netns like compose
ServiceNodePort 30095 → 9095Lab UI/API (avoids Ragnarok 30061/30062)
Secretzyvor-fabric-secretsadmin-username, admin-password, jwt-secret (K8s Secret — not systemd files)

Web UI is baked into the zyvor-fabricd image (no separate frontend pod).


Requirements​

  • Kubernetes node(s) with /dev/kvm
  • Namespace with Pod Security privileged (enforced in k8s/base/namespace.yaml)
  • Rootful podman or docker on the machine that builds images
  • kubectl access to the cluster
  • For FluxVM image builds: sibling checkouts ../FluxVM and ../guestkit (or set FLUXVM_DIR / GUESTKIT_DIR)

Quick paths​

From your laptop (build happens on the server):

# Full: rsync → build images → k3s ctr import → apply → smoke
./scripts/deploy k8s sus@HOST

# Re-apply manifests / rollout only
./scripts/deploy k8s sus@HOST --quick

# Tear down
./scripts/deploy k8s sus@HOST --uninstall

Equivalents:

./scripts/deploy-k8s-remote.sh sus@HOST
./scripts/deploy-k8s-all-remote.sh sus@HOST --quick

After success:

URLNotes
http://HOST:30095/NodePort (UI + API)
http://HOST:9095/hostNetwork bind
http://HOST:30095/healthLiveness smoke
http://HOST:30095/readyzReadiness (store + FluxVM)

Admin login for new Kubernetes deployments (from Secret):

FieldDefault
Usernameadmin
Passwordgenerated (unless FABRIC_ADMIN_PASSWORD / ZYVOR_FABRICD_ADMIN_PASSWORD set, or FABRIC_LAB_DEFAULTS=1 → Admin@321)

Retrieve:

kubectl -n zyvor-fabric get secret zyvor-fabric-secrets \
-o jsonpath='{.data.admin-password}' | base64 -d; echo

Override / rotate:

FABRIC_LAB_DEFAULTS=1 ./scripts/k8s-set-admin-secret.sh --apply --restart
# or:
FABRIC_ADMIN_PASSWORD='Secret!' ./scripts/k8s-set-admin-secret.sh --apply --restart

B. Local kubectl​

# Images already in the cluster runtime
make k8s-deploy

# Build first (needs FluxVM/guestkit siblings for full stack)
BUILD_IMAGES=true ./scripts/deploy-k8s.sh

Creates namespace, ConfigMap, secret (if missing), both DaemonSets, and the NodePort Service.

make k8s-undeploy # delete namespace zyvor-fabric

C. Helm​

make helm-lint
make helm-template

helm upgrade --install zyvor-fabric ./charts/zyvor-fabric \
--namespace zyvor-fabric --create-namespace \
--set security.adminUsername=admin \
--set security.jwtSecret="$(openssl rand -base64 32)" \
--set fabricd.image.tag=local \
--set fluxvm.image.tag=local
# Leave security.adminPassword empty → chart generates a random password.
# Do not --set security.adminPassword=Admin@321 (install fails).
# Retrieve: kubectl -n zyvor-fabric get secret zyvor-fabric-secrets \
# -o jsonpath='{.data.admin-password}' | base64 -d; echo

Useful values (see charts/zyvor-fabric/values.yaml):

ValueDefaultPurpose
fabricd.image.repository / tagzyvor-fabricd / localControl-plane image
fluxvm.image.repository / tagzyvor-fabric-fluxvm / localFluxVM image
fabricd.service.nodePort30095Lab NodePort
fabricd.hostPath/var/lib/zyvor-fabricdPersistent data on node
security.adminUsernameadminSeeded admin username
security.adminPassword"" (random)Empty → randAlphaNum; Admin@321 is rejected
security.existingSecret""Use a pre-created Secret instead

Credentials (Kubernetes Secret)​

Fabric on Kubernetes does not use systemd’s /var/lib/zyvor-fabricd/.admin_password file by default (bare-metal systemd deploys still write that file). The DaemonSet reads credentials from Secret zyvor-fabric-secrets:

KeyDefault (new deploy)
admin-usernameadmin
admin-passwordrandom (or env / FABRIC_LAB_DEFAULTS=1 → Admin@321)
jwt-secretlab placeholder / random on first create
# Apply / replace secret (generates password unless env / FABRIC_LAB_DEFAULTS=1)
./scripts/k8s-set-admin-secret.sh --apply --restart

# Or from manifests (replace REPLACE_ME first — do not ship a known default)
kubectl apply -f k8s/base/secret.yaml

# Retrieve password
kubectl -n zyvor-fabric get secret zyvor-fabric-secrets \
-o jsonpath='{.data.admin-password}' | base64 -d; echo

Password is seeded into auth.db only when the DB has no users. After changing the Secret password in lab, wipe the hostPath DB then restart:

sudo rm -f /var/lib/zyvor-fabricd/auth.db
kubectl -n zyvor-fabric rollout restart daemonset/zyvor-fabricd

Environment variables (remote deploy)​

VariablePurpose
FABRIC_ADMIN_PASSWORDAdmin password for Secret (explicit; preferred for prod)
ZYVOR_FABRICD_ADMIN_PASSWORDSame as above (alternate env name)
FABRIC_LAB_DEFAULTS=1Use convenient lab password Admin@321 (never silent without this)
FABRIC_ADMIN_USERNAMEAdmin username for Secret (default admin)
FORCE_SECRET=1Recreate zyvor-fabric-secrets on deploy
FABRIC_SKIP_FLUXVM=1Skip FluxVM image sync/DaemonSet
FLUXVM_DIR / GUESTKIT_DIRPaths to siblings for image build
IMAGE_TAGImage tag (default local)
DEPLOY_HOST / DEPLOY_USERDefaults for host/user
NODE_PORTOverride NodePort smoke check (default 30095)
ZYVOR_FABRICD_PUBLIC_URLExternal base URL for OpenStack catalog (e.g. http://NODE_IP:30095)
ZYVOR_FABRICD_LISTENOverride daemon bind (usually set by the chart / DaemonSet)

Verify​

kubectl -n zyvor-fabric get pods,svc,ds
kubectl -n zyvor-fabric logs -l app=zyvor-fabricd --tail=50

curl -sf http://NODE_IP:30095/health
curl -sf http://NODE_IP:30095/readyz | jq '{ok, store, fluxvm_ok: .fluxvm.ok}'
# Login
curl -sf -X POST http://NODE_IP:30095/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"YOUR_PASSWORD"}'

DaemonSet probes (base + Helm chart):

PodLivenessReadiness
zyvor-fabricdGET /healthGET /readyz
FluxVMGET /healthzGET /readyz (HTTP 503 when not ready)

On the node, FluxVM should answer:

curl -sf http://127.0.0.1:7788/healthz
curl -sf http://127.0.0.1:7788/readyz | jq .
curl -sf http://127.0.0.1:7788/v1/vms

Platform chart vs operator​

InstallLocationRole
Platformk8s/base/, charts/zyvor-fabricRuns fabricd + FluxVM
Operatoroperator/charts/zyvor-fabricd-operatorCRD → Fabric REST API

Operator env:

ZYVOR_FABRICD_URL=http://NODE_IP:30095
# same node: http://127.0.0.1:9095

http://zyvor-fabricd.zyvor-fabric.svc:9095 is not a reliable cross-node address when pods use hostNetwork. Prefer NodePort or the node IP.


Privilege model​

Both DaemonSets use:

  • hostNetwork: true
  • privileged: true (plus capability list matching compose)
  • hostPath for /var/lib/zyvor-fabricd, /var/lib/fluxvm, /run/fluxvm, /run/netns, /dev/kvm

Do not enforce restricted PSS on zyvor-fabric.


Relation to other deploy modes​

ModeEntryWhen
systemd bare metal./scripts/deploy remote USER@HOSTProduction hosts without K8s
Docker / Podmanmake docker-up · DOCKER.mdLocal eval
Kubernetes./scripts/deploy k8s … · HelmLab k3s / in-cluster control plane
Operator onlyoperator/charts/…GitOps VMs against existing fabricd

Layout​

k8s/base/
namespace.yaml
secret.yaml.example
fabricd-configmap.yaml
fluxvm-daemonset.yaml
fabricd-daemonset.yaml
fabricd-service.yaml
charts/zyvor-fabric/
Chart.yaml
values.yaml
templates/
scripts/deploy-k8s.sh
scripts/deploy-k8s-remote.sh
scripts/deploy-k8s-all-remote.sh

Makefile targets: k8s-deploy, k8s-undeploy, helm-lint, helm-template.


Troubleshooting​

SymptomCheck
fluxvm CrashLoop / ImagePullBackOffImage imported? /dev/kvm present? Build with FluxVM+guestkit siblings
fabricd can't reach FluxVMBoth must be hostNetwork on the same node; URL http://127.0.0.1:7788
Health 000 on NodePortkubectl -n zyvor-fabric get svc,pods -o wide; try host :9095
PSS / admission errorsNamespace must be privileged
Port clash with RagnarokFabric uses 30095; Ragnarok uses 30061/30062

See also​

DaemonSets that run FluxVM should mount host /sys/fs/bpf, raise memlock, and ship configs/fluxvm-dataplane.toml as /etc/fluxvm.toml when enabling mode = "ebpf".