docs.fermihdi.io — admin-guide.md

/ › hd › 2.0.4 › md › install

Download admin-guide.mdView raw

HD — Administrator's Guide

Everything needed to stand up an HD instance and keep it standing: what the system is, what it is made of, what it needs from your hardware and network, and every option the installer offers.

This documents HD v2.0.3. Every image it names is cr.fermihdi.io/hd/<service>:2.0.3.

It assumes you administer Linux hosts and containers. It assumes nothing about FermiHDI. If you only want the commands, README.md beside this file is the short version; everything here is the reasoning behind it.


What HD is

HD is a record store built for one thing: taking in enormous volumes of small, uniform records and answering questions about them without the kernel getting in the way. Network packets bypass the kernel through DPDK; disk writes bypass the filesystem through SPDK, straight to NVMe queues. The work runs on cores nothing else is allowed to touch.

That single design choice is behind almost every requirement in this guide. Poll mode threads spin on dedicated cores and must not be preempted. Buffers are allocated up front in hugepages and must not be swapped. Devices are taken from the kernel and handed to the process. None of it can be arranged after a container starts, which is why installation is two distinct acts — preparing a host, then running containers on it — and why the first one changes the kernel command line and usually needs a reboot.

One instance is a deployment: one DMS, one or more Storage Clusters, and the edge and access services around them. Everything in it trusts one certificate authority and registers with one DMS.


What it is made of

Component Language Does
DMS-Core Go The authority. Registers nodes, issues their configuration, holds the topology, the schema and the license
DMS-CA Go The certificate authority. Signs every identity in the deployment
DMS-OPA-Engine Go Compiles and serves the policy bundles the Proxy enforces
DMS-OPA-Adapter Go Optional. Polls an external OPA control plane and hands bundles to the engine
DMS-WebUX Astro + Node The admin UI, and the only thing that can apply a license interactively
SCC C++ / HD engine Storage Cluster Controller. The gateway in front of a set of Storage Nodes: fans writes out to them, load-balances queries across them
Storage Node (SN) C++ / HD engine Where records live. DPDK on the wire, SPDK to the NVMe
BloomRF filter node C++ / HD engine Optional, at most one per storage cluster. Takes the SCC's ingest broadcast exactly as an SN does and keeps a small filter per batch instead of the records, so at query time the SCC can leave unread the batches that cannot match. Holds no device and persists nothing: its store is rebuilt from the stream
Ingester C++ / HD engine Reads a message bus, parses records without allocating, and streams them to an SCC. Kafka is what the installer wires; the ingester itself also reads RabbitMQ and object stores
HD Proxy Go The query front door. Enforces policy, rewrites queries it must restrict, scatters them to the SCCs and gathers the results
hd_graphql / hd_mcp Go + the SDK GraphQL and MCP front ends onto the Proxy
hd_sql Go + the SDK A read-only SQL front end onto the Proxy: PostgreSQL wire, Arrow Flight SQL and HTTP from one engine. Run from its published image beside the Proxy; the cluster file has no role for it
Data explorer — Optional web UI over the Proxy
OTEL collector — Where every service sends traces and metrics

Two shapes of service, and the difference decides everything about placement:


How it fits together

graph LR
    subgraph DMS["DMS plane"]
        CA[DMS-CA]
        CORE[DMS-Core]
        OPA[OPA engine]
        UX[WebUX]
        OTEL[OTEL collector]
        CORE <--> CA
        OPA --> CORE
        UX --> CORE
    end

    subgraph SC["Storage Cluster"]
        SCC[SCC]
        SN1[(SN)]
        SN2[(SN)]
        FLT[BloomRF filter node]
        SCC --> SN1
        SCC --> SN2
        SCC -. records, and which batches cannot match .-> FLT
    end

    BUS[(Kafka)] --> ING[Ingester]
    ING == records ==> SCC
    CLIENT[SDK / SQL / GraphQL / MCP] --> PROXY[HD Proxy]
    PROXY == queries ==> SCC
    SN1 -. results .-> CLIENT

    ING -. register .-> CORE
    SCC -. register .-> CORE
    SN1 -. register .-> CORE
    FLT -. register .-> CORE
    PROXY -. register .-> CORE

Three planes, and a node works out its own address on each by matching its interfaces against the CIDRs you declare:

Plane Carries Typical
Control Registration, configuration, cluster coordination 10.147.0.0/24
Data Records in, results out — the FHDWP wire protocol 10.150.0.0/24
Observability Traces, metrics, health 10.146.0.0/24

They can share one interface on a small deployment. On a multi-homed host they must not be left unset, or each node picks an interface by itself and half of them will pick differently.

The three flows:

  1. Ingest. Kafka → Ingester (parses, slices into fixed-size records) → SCC over FHDWP → fanned out to every SN in the cluster. Each SN holds a full replica of the cluster's dataset. A cluster with a filter node fans the same stream out to it too; it verifies each batch's hash with the SCC as an SN does, then keeps a filter per batch and discards the records.
  2. Query. Client → a front door that authenticates the caller and sets X-Forwarded-* headers → Proxy, which evaluates policy against those headers and rewrites the query rather than refusing it — nullifying forbidden fields, masking enforced identities — then scatters it to every SCC in parallel. Each SCC fans out to its SNs. The records do not come back through the Proxy: the SNs stream them to the client over the data plane, and the Proxy answers with a manifest of which batches were found and which SCC, if any, timed out (HTTP 207). A query that returns 207 is a partial answer, not a failure. An SCC with a filter node first asks it which of the batches in the window cannot match the query's predicates, and leaves those unread; the Proxy's manifest lists them as excluded. The filter is allowed no false negatives, and every client checks: a batch the filter excluded that delivers a record anyway is logged as an error by the SDK.
  3. Control. Every node presents a bootstrap token to DMS-Core at startup, gets a signed certificate and its runtime configuration back — record size, which SCC to talk to, which devices to use — and reports health afterwards. A node holds no configuration of its own. What you set in the environment is identity: who am I, where is the DMS, what token proves it.

Identity, trust and licensing

Nothing in an HD deployment talks to anything else without mutual TLS, and every certificate in it descends from one chain:

FermiHDI Root CA
  └── Intermediate CA            (issued to you)
        └── DMS-CA ICA           (this deployment's signing certificate)
              ├── dms-core, dms-webux, the OPA engine
              └── every SCC, SN, filter node, Ingester and Proxy that registers

There are two ways to get the certificates in place, and choosing between them is the first real decision of an install:

Pre-provisioned (what this installer does). You already hold the ICA certificate and key, DMS-Core's certificate, the WebUX's certificates and the license. The installer puts them in place and everything comes up ready.

Day-0 bootstrap. The DMS-CA generates its own key and a CSR, you send that CSR to FermiHDI, and the license that comes back carries the ICA certificate issued against it. Applying that license through the WebUX installs the ICA and DMS-Core's identity out of it. A license supplied as an environment variable does not do this — it is verified and stored, and nothing else. If all you have is a license, install without the certificates and apply it through the WebUX afterwards.

Renewal. A licence carries a term, the DMS-CA's ICA certificate carries an expiry, and a node certificate the DMS-CA signs is good for 30 days, never past the ICA's own expiry. All three renew without a rebuild and without a restart:

One requirement catches people either way: DMS-Core's certificate must carry, in its SAN, the address the nodes are given. Nodes verify what they connect to. A certificate naming only dms-core works inside the DMS's own network and fails every registration from outside it.


Ports

Port Service Spoken by Leaves the host?
6986 DMS-Core southbound, mTLS every node registering, and telemetry heartbeats yes
6986 SCC and SN control plane SCC ↔ SN, Proxy → SCC yes
6987 Data plane (FHDWP) Ingester → SCC, SCC → SN, SN → client yes
6988/udp NORM multicast SCC data plane
6997 Filter node, batch pruning (gRPC) SCC → filter node control plane
5432 / 31337 / 8081 hd_sql: PostgreSQL wire, Arrow Flight SQL, HTTP SQL clients, through the front door yes, when it is run
6985 DMS-Core internal management WebUX and DMS-CA only no
6985 DMS-CA signing API DMS-Core only no
6985 OPA renewal listener DMS-CA → OPA engine no
6984 DMS-Core Day-0 bootstrap DMS-CA, during bootstrap only no
8080 Health and metrics, plain HTTP your orchestrator host-local
8181 OPA engine REST internal no
3000 WebUX, HTTPS the DMS Traefik in front of it no
8081 / 8082 WebUX and Traefik dashboard, as published operators loopback by default
4317 / 4318 OTEL collector, gRPC and HTTP every service in the deployment yes

Two ports called 6985 in the same deployment is deliberate — DMS-Core and DMS-CA each own one, and they never share a network namespace. It is also why the DMS runs on its own container networks rather than the host's.


What you need before you start

Hosts

Per engine instance, the floor is a hard one: below it the resource controller refuses to start and the process exits.

Role Cores the installer allocates Refused below 2 MB pages / instance 1 GB pages
SCC 6 6 300 (600 MB) 2
Storage Node 9 9 4140 (8.1 GB) 4 per NVMe device
Filter node 2 2 300 —
Ingester 4 2 300 —
GraphQL 2 1 300 —
MCP 2 1 300 —
Proxy 1 1 — —
DMS 1 1 — —

The 2 MB figure is the role's engine arena plus 88 MB of mbuf pools and EAL overhead. The Storage Node's is large because its arena scales with transmit shard count. The 1 GB pool exists only for SPDK's NVMe DMA — an SN whose storage is all kernel-path needs none of it.

Also reserve 5 GB per host for the kernel, page cache and everything outside the engine arenas. fermihdi-configure and validate-cluster both compute against that figure, and the validator refuses a host whose pools would not leave it.

Two placement rules the validator enforces, and the reasons they exist:

Firmware and kernel

Network

Two front doors

Nothing in HD authenticates a person. The Proxy, the query services, Data Explore and the DMS WebUX trust the X-Forwarded-User, -Email, -Groups and -Role headers on the requests that reach them, and refuse or admit on those alone. Two reverse proxies set them: the DMS front door, through which administrators reach the WebUX, and the query front door, through which clients and the query services reach the Proxy. Both are yours to provide: the lab and the reference deployment use Traefik with a forward-auth step, but any ingress that authenticates against your identity provider, sets the headers and strips client-supplied ones will do, with or without a forward-auth mechanism. The query front door also presents a client certificate to the Proxy, whose CA the Proxy is given as FERMIHDI_NORTHBOUND_CA_PEM; the DMS front door is never given a DMS-issued certificate. Neither the Ansible layer nor the chart deploys either front door; docs/examples/ansible_example is the reference for both. Until they exist, keep the WebUX and the Proxy on a loopback or a management network, which is where the installer publishes them by default. The contract in full, service by service, is on the front doors page.

Software on each host

On the machine you install from

Credentials and material

What Needed for Where it goes
Registry username and password pulling images FERMIHDI_REGISTRY_PASSWORD
Bootstrap tokens every node's first handshake FERMIHDI_BOOTSTRAP_TOKEN_<NODE>
Trust bundle verifying the DMS FERMIHDI_CA_CERT_PEM
DMS-CA ICA certificate + key signing identities file, or FERMIHDI_DMS_CA_CERT_PEM / _KEY_PEM
DMS-Core certificate + key its TLS identity FERMIHDI_DMS_CORE_CERT_PEM / _KEY_PEM
WebUX server and client certificates the admin UI FERMIHDI_DMS_WEBUX_*
Encryption and identity keys DMS-Core's stored state and the license FERMIHDI_DMS_ENCRYPTION_KEY / _IDENTITY_KEY
License admitting nodes file, or FERMIHDI_DMS_LICENSE_PAYLOAD
Dataset schema what a record looks like file, named in cluster.yaml

The cluster file

One file describes the whole installation. Every tool here reads it: the validator, the host preparer, the Ansible inventory, the Kubernetes generator. It holds no secrets — only the names of the variables and files they come from.

version: 1
cluster_name: hd-prod-1

registry:
  url: cr.fermihdi.io
  username: deploy
  password_env: FERMIHDI_REGISTRY_PASSWORD   # read at run time, never stored

dms:
  url: https://10.147.0.5:6986        # every node is handed this verbatim
  bootstrap_token_env: FERMIHDI_BOOTSTRAP_TOKEN
  license_file: certs/license.json    # optional, all four of these
  ica_cert_file: certs/dms-ca.crt
  ica_key_file: certs/dms-ca.key
  schema_file: certs/dataset_schema.json

images:
  tag: "2.0.3"
  scc: cr.fermihdi.io/hd/scc          # per role; the tag above applies
  dms-core: cr.fermihdi.io/hd/dms-core

networks:
  control_plane: 10.147.0.0/24
  data_plane: 10.150.0.0/24
  observability: 10.146.0.0/24

hosts:
  - name: sc-host-1
    address: 10.147.0.10              # what Ansible connects to
    ssh_user: root
    ssh_key: ~/.ssh/fermihdi_deploy
    roles:
      - type: scc                     # scc sn bloomrf ingester mcp graphql proxy dms
        count: 1
        dpdk: true                    # optional; POSIX networking otherwise
        instances:
          - name: sc-1-scc
            cpuset: "2-7"             # MASTER_CPUSET: the poll cores
            container_cpuset: "0-1"   # overflow only, must not overlap
            numa_node: 0
            nic: { pci: "0000:31:00.0", driver: vfio, kind: nic, iommu_group: 12 }
      - type: sn
        count: 1
        dpdk: true
        instances:
          - name: sc-1-sn-1
            cpuset: "8-18"
            container_cpuset: "0-1"
            storage:
              - { pci: "0000:61:00.0", driver: vfio, kind: nvme, iommu_group: 40 }
    host_prep:                        # what fermihdi-prepare applies
      hugepages_2m: 4440
      hugepages_1g: 6
      numa: [{ node: 0, hugepages_2m: 4440, hugepages_1g: 6, memory_mb: 262144 }]
      memory_total_mb: 262144
      iommu: true
      isolcpus: "2-18"                # also applied as rcu_nocbs and nohz_full
      vfio_bind: ["0000:31:00.0", "0000:61:00.0"]
      grub: { apply: true }           # backs up, validates, never reboots

Three things to know about the file's shape:

Full field reference: schema/cluster.schema.json, which is what the validator checks against.


A deployment you can read: the sandbox

Before building one, it is worth running one. tests/sandbox_dms is a complete HD instance on a single host — the whole DMS plane, a Storage Cluster, the edge and access services, a Kafka bus with traffic generators, and Grafana over the top. It is what FermiHDI ships for evaluation and benchmarking, and it is the deployment this installer was built from: where a document and the sandbox disagree about how a service is configured, the sandbox is right, because it is the one that runs every day.

cd tests/sandbox_dms
sudo bash sandbox_host_setup.sh     # hugepages, vfio binding, GRUB advice
# reboot if it asks, then
bash start_sandbox.sh               # or --num-sns 2, --standalone, -t 2.0.3

It picks the image variant from the host by default — the bare tag is built for x86-64-v4 and faults on a CPU without AVX-512 — so --type is only needed to override that: ubuntu for the thin development images, metrics for the SCC and SN builds with metering compiled in.

It has its own equivalents of the four steps in this guide, and reading them next to each other is the fastest way to understand what the installer does:

Sandbox This installer Both do
configure_sandbox.py fermihdi-discover + fermihdi-configure NUMA-aware core allocation, storage device mapping, GRUB advice
sandbox_host_setup.sh fermihdi-prepare hugepages, hugetlbfs mounts, vfio-pci binding
docker-compose.override.yml cluster.yaml the placement decisions, written down
compose.yml ansible/ and helm/fermihdi actually running the containers
generate_certs_and_license.sh your PKI, or FermiHDI's the certificate chain and a license

Three things in it are worth reading directly:

Where the sandbox is deliberately not production

Copying from it wholesale is the one mistake it invites:

The sandbox A real deployment
One host, Docker bridge networks with fixed IPs Real hosts, real interfaces, the plane CIDRs you declare
mock-forward-auth accepts everything and returns admin Your SSO in front of the Proxy and the WebUX
A self-signed chain and a 30-day license generated locally Your PKI, and a license issued to you
Storage nodes on tmpfs or a file, in the default profile NVMe bound to VFIO
Every service on one machine, sharing cores Placement computed per host, poll cores isolated

The full guide to running it — sizing, benchmarking, profiling, every option of start_sandbox.sh — is tests/sandbox_dms/sandbox_guide.md.

Installing

Four steps, in the same order however many hosts you have:

fermihdi-discover  →  fermihdi-configure  →  validate-cluster  →  fermihdi-prepare
     what is here      what it should run      is that coherent      make it so

Only the last one changes anything, and only when told to twice — a bare run is a dry run. Then the containers go on top.

On Talos Linux, the first and last of those four have API-driven twins (fermihdi-talos-discover, fermihdi-talos-prepare) because a Talos machine has no shell to run anything on, and the middle two are used unchanged. The order and the reasoning are identical; what differs is that the kernel-argument half of host_prep becomes a boot image rather than a GRUB edit, so applying it reboots the machine. TALOS.md is the whole of that path.

Path A — one host, by hand

install/fermihdi-discover                        # read-only inventory
install/fermihdi-configure                       # interactive; writes ./cluster.yaml
install/validate-cluster cluster.yaml            # refuses what will not work
sudo install/fermihdi-prepare cluster.yaml       # dry run: prints what would change
sudo install/fermihdi-prepare cluster.yaml --apply
# reboot if it says to, then:
install/fermihdi-prepare cluster.yaml --verify

Then deploy the containers with the Ansible layer pointed at that one host, or by hand from the environment table in ansible/README.md.

Path B — several hosts, with Ansible

Run fermihdi-configure once per host, appending each to one file:

install/fermihdi-configure -o cluster.yaml                    # first host
install/fermihdi-configure --append cluster.yaml              # each one after

Then, from the machine that can reach them all:

cd install/ansible
export FERMIHDI_REGISTRY_PASSWORD=... FERMIHDI_CA_CERT_PEM="$(cat bundle.pem)" ...
ansible-playbook site.yml           # prepare every host, the DMS, then the nodes

There is no inventory file — it is generated from cluster.yaml, so there is nothing to keep in step. One group per role, so --limit fermihdi_sn means exactly what it says.

Playbook Does
prepare.yml validates the file, then fermihdi-prepare --apply on every host
verify.yml fermihdi-prepare --verify everywhere; changes nothing
dms.yml the DMS plane, on the host whose block declares a dms role
deploy.yml the node containers each host's block calls for
site.yml all three, in the order the nodes need

--check on prepare.yml is a real dry run. -e fermihdi_reboot=true lets it reboot, one host at a time — a cluster that reboots all of its Storage Nodes at once is a cluster that has lost quorum.

Everything a deployment might change lives in ansible/group_vars/all.yml: the message bus the Ingester reads, the forward-auth service in front of the WebUX, the collector, per-role core splits, image namespace, data directories. It is meant to be read.

Path C — Kubernetes

install/fermihdi-k8s cluster.yaml -o values.yaml
helm install fermihdi install/helm/fermihdi -f values.yaml

The generator maps roles to workloads, instance counts to replicas, cpuset widths to CPU requests, and the engine model to memory and hugepage requests. What cannot cross — DPDK, device passthrough, the cpuset split, the 1 GB pool — is written into the generated file's header rather than dropped quietly.

Option Does
-o PATH write the values file
--secrets PATH also write a 0600 values file holding keys and tokens
--manifests render the chart and print the manifests instead
--chart PATH render a chart other than the bundled one
--domain, --storage-class ingress domain, storage class for every PVC

A cluster file with no dms role is read as having an external DMS: the host from dms.url goes into the reference Services the nodes resolve DMS-Core through, and the DMS components switch off. See helm/fermihdi/README.md for what differs from bare metal.

The filter node

A storage cluster runs with or without one, and the common case is without. Adding one is three steps beyond the role in the cluster file, and their order matters:

  1. Provision it in the DMS, as a BloomRF Filter Node in the SCC's storage cluster, before it starts: on the WebUX's Nodes page, Provision New Node, exactly as for a Storage Node, with Bootstrap Token as the security mode. Export the token the dialog shows as the node's own variable, FERMIHDI_BOOTSTRAP_TOKEN_SC_1_BLOOMRF. (A script can do the same against the DMS management API: POST /v1/nodes/provision with node_type bloomrf, the instance_id, the sc_id and generate_token.)

  2. Deploy it before, or together with, the SCC. The SCC takes its relay list once, when its data plane starts, so the DMS holds that list back until every provisioned Storage Node and filter node in the cluster is ready. Provision only what you will run: a provisioned filter node that never starts holds the whole cluster's ingest with it.

  3. Choose the mode, or leave it. Once a filter node is provisioned the DMS pushes active to the cluster's SCC and its exclusions are applied. Name passive on the deployment record to have the SCC consult the filter and record what it would exclude without acting on it, or off to stop consulting it: the Query filter setting on the deployment's card on the WebUX's Deployments page, which pushes the choice to every SCC of the instance at once. The Proxy reports the mode in force in every query's manifest.

To add one to a cluster that is already running, do the three steps and then restart the SCC; to remove one, decommission it in the DMS and restart the SCC. What the node does, how big to make it and how to read its metrics are in the filter node page of the manual.

Every script, every option

Command Options
fermihdi-discover -o FILE, --format=tsv, --check-deps
fermihdi-configure -o FILE, --append FILE, --facts FILE
validate-cluster [cluster.yaml]
fermihdi-prepare --apply, --verify, --host NAME, --no-grub
fermihdi-k8s -o, --secrets, --manifests, --chart, --domain, --storage-class

fermihdi-prepare exits 0 when done or when there was nothing to do, 1 on error, and 2 when it applied something that needs a reboot — which is what makes it safe to drive from a playbook.

The environment

Read on the machine running the install, never written to a host:

# always
export FERMIHDI_REGISTRY_PASSWORD=...
export FERMIHDI_CA_CERT_PEM="$(cat dms-ca-bundle.pem)"
export FERMIHDI_BOOTSTRAP_TOKEN_SC_1_SN_1=...     # per node: name, upper-cased,
export FERMIHDI_BOOTSTRAP_TOKEN=...               # non-alphanumerics to _
                                                  # ... or one shared token

# deploying the DMS as well
export FERMIHDI_DMS_CA_CERT_PEM=... FERMIHDI_DMS_CA_KEY_PEM=...
export FERMIHDI_DMS_CORE_CERT_PEM=... FERMIHDI_DMS_CORE_KEY_PEM=...
export FERMIHDI_DMS_WEBUX_CERT_PEM=... FERMIHDI_DMS_WEBUX_KEY_PEM=...
export FERMIHDI_DMS_WEBUX_FE_CERT_PEM=... FERMIHDI_DMS_WEBUX_FE_KEY_PEM=...
export FERMIHDI_DMS_ENCRYPTION_KEY=... FERMIHDI_DMS_IDENTITY_KEY=...
export FERMIHDI_DMS_FE_TOKEN=... FERMIHDI_BOOTSTRAP_TOKEN_DMS_OPA_ENGINE=...
export FERMIHDI_DMS_LICENSE_PAYLOAD=...           # or dms.license_file
export FERMIHDI_DMS_ICA_CHAIN_PEM=...             # optional

The DMS-CA's certificate and key, and the license, can equally be named as files in cluster.yaml — fermihdi-configure asks for them on a DMS host. A file named there wins over the environment, and the run says which it used.

ansible-playbook dms.yml refuses to start with any required one missing, and names every one it could not find.


After the install

Check it came up. ansible-playbook verify.yml re-checks every host against its definition — the pass to run after a reboot, and the first one to run when a node misbehaves for reasons nobody can place. A host that lost its hugepages to a kernel upgrade looks fine until the reactors fail to reserve their arenas.

Apply a license, if you installed without one: through the WebUX. That path also installs the DMS-CA's ICA and DMS-Core's identity out of the license, which the environment variable does not.

Add a node. Run fermihdi-configure --append cluster.yaml on the new host, validate, prepare.yml --limit new-host, then deploy.yml --limit new-host. Nothing else needs to know: the node registers itself.

Add a filter node to a running cluster: provision it in the DMS, deploy it, wait for it to report ready, then restart the SCC — an SCC takes its relay list once, when its data plane starts.

Upgrade. Change images.tag in cluster.yaml and re-run deploy.yml. The containers are recreated with the new image; host preparation is unaffected. Do the DMS first if the release notes say the wire protocol moved.

What is stateful, and therefore what to back up:

Path Holds
/opt/fermihdi/dms/dms-core/storage the node registry, deployments, the license
/opt/fermihdi/dms/dms-ca/storage issued-certificate state
an SN's storage devices the records themselves — every SN in a cluster holds a full replica

Reboots stay yours. The tooling never takes a host down on its own; when a change needs one it says so and exits 2.


When something is wrong

Symptom Usually
A node registers, then nothing reaches it Plane CIDRs unset or wrong: it reported an address on the wrong interface
Every registration fails TLS DMS-Core's certificate SAN does not carry the address in dms.url
Nodes register and are never admitted No license, or one that admits fewer nodes than you have
DMS-Core exits at startup An unparseable licence payload, or no FERMIHDI_DMS_ENCRYPTION_KEY
A reactor fails to reserve memory Hugepages missing — check verify.yml, then the kernel command line after any upgrade
Throughput collapses under load, no errors A poll core inside container_cpuset: the runtime shim is preempting it
SPDK fails at attach, looking like a driver fault The container was handed the wrong /dev/vfio node — IOMMU groups move under a kernel upgrade
Services healthy, no traces anywhere OTEL_EXPORTER_OTLP_ENDPOINT unset. FERMIHDI_OTEL_URL is a routing probe and configures no exporter
The WebUX answers 500 on every request A forward-auth middleware pointed at an authenticator that is not there
The engine dies immediately in Kubernetes No IPC_LOCK: a container's default 64 KB memlock stops it pinning its arena
Ingest never starts after a filter node was provisioned The DMS holds the SCC's targets until every provisioned SN and filter node is ready. Start the filter node, or decommission it
The filter node is up and excludes nothing; queries report bloomrf_mode: off The SCC's data plane started before the node was ready, or the deployment record names off. Restart the SCC once the node reports ready
A query hangs to its timeout on a batch the manifest lists as excluded The client waited for a batch nothing will stream. Clients on the current SDK drop excluded batches from their wait; upgrade the client

docker logs <container> is worth reading first — the services log startup and errors to stdout, and everything else to the collector.


Further reading

Where What
README.md this directory, in brief
ansible/README.md the multi-host layer, and every secret it needs
helm/fermihdi/README.md Kubernetes, and what differs there
TALOS.md installing onto Talos Linux, which has no shell and no SSH
schema/cluster.schema.json every field of the cluster file
../docs/architecture.md the system in depth
../docs/system_operators_manual.md lifecycles and recovery
../docs/filter_node.md the BloomRF filter node: modes, sizing, verification
../docs/webux_guide.md the DMS WebUX, page by page: licence, deployments, nodes, policy
../docs/front_door.md the two front doors: what they authenticate, the identity headers, what you provide
../docs/ingestion.md the ingester: sources, file formats, how batches are formed
../docs/querying.md the query services: SQL, GraphQL, MCP, and the SDK
../docs/fhdwp_network_guide.md the wire protocol
../tests/sandbox_dms/ a complete working deployment to read
Download admin-guide.mdView raw