A distributed orchestrator for flintlock
microVMs. Brigade speaks flintlock's exact gRPC MicroVM interface (drop-in), decides
which cluster member should run each microVM, and forwards the request to that host's
local flintlockd.
flintlock is a single-host agent — one flintlockd per bare-metal node, with no
scheduling, host selection, or cross-host awareness. Brigade fills that gap: clients
talk to Brigade as if it were a flintlock, and Brigade schedules across the fleet.
Day 1 (topology A): a Brigade node runs on every flintlock host, meshed via
distributed Erlang (libcluster). Host liveness = Erlang node liveness; scheduling and
state ride the mesh with no external dependencies. Each Brigade node calls its local
flintlockd over localhost:9090.
client --(flintlock gRPC)--> any Brigade node
| singleton scheduler picks a host
v
Brigade@hostC --localhost:9090--> flintlockd (hostC)
Everything host-facing is behind a behaviour (Store, HostDriver, Scheduler.Strategy,
HostRegistry) so a future topology B (central control plane dialing remote hosts)
can be added without touching the core.
- Drop-in gRPC — Brigade implements flintlock's
MicroVMservice verbatim (protos vendored + pinned underproto/, gRPC-only; the gateway is not run).uidis pass-through: flintlock assigns it, Brigade storesuid -> host. - Singleton scheduler — a Horde-managed cluster singleton serializes placement, so concurrent creates can never overcommit a host. Capacity = declared total − OS reserve − committed (from the store) − in-flight (in memory). Placement is filter → score (least-loaded spread by default).
- State — Mnesia (
ram_copies), the source of truth for placement and for answeringListMicroVMs. - Failure handling — dual-liveness (Erlang
nodedown+ a TCP health probe of the local flintlock); a dead node's hosts gounreachableand their capacity is released (no auto-reschedule); a per-host reconciler (~30s, state-sync only) adopts orphans, marks lost VMs, and resolves ambiguous creates. Placement is quorum-gated: a minority partition refuses new placements (split-brain guard) while reads stay available. - Auth — mirrors flintlock's basic-auth token on both edges (north: clients → Brigade; south: Brigade → flintlock), with optional mTLS south.
- Observability —
:telemetryevents + a Prometheus/metricsendpoint, plus a JSON/statusand/healthz.
- Elixir 1.18 / OTP 27 (managed via mise:
mise install). - A running
flintlockdon each host (localhost:9090) for real use. Tests use an in-memory fake, so no hardware is required.
mise exec -- mix deps.get
mise exec -- mix run --no-halt # boots the app with config/config.exsEndpoints (defaults):
| Port | What |
|---|---|
| 9091 | Brigade gRPC (north edge — point flintlock clients here) |
| 9600 | GET /status (JSON cluster view), GET /healthz |
| 9568 | GET /metrics (Prometheus) |
All settings live under config :brigade (see config/config.exs). Key ones:
| Key | Default | Meaning |
|---|---|---|
grpc_port |
9091 |
north-edge gRPC listen port |
flintlock_endpoint |
localhost:9090 |
local flintlockd (south edge) |
host |
8 vcpu / 16 GB, 1 vcpu / 2 GB reserve | this host's declared capacity, labels, providers |
scheduler_strategy |
LeastLoaded |
placement strategy |
min_cluster_size |
1 |
quorum for placement (set to majority in real clusters) |
auth_token |
nil |
north-edge basic-auth token (nil = disabled) |
flintlock_auth_token / flintlock_tls |
nil |
south-edge auth / mTLS |
cluster_topologies |
[] |
libcluster config (e.g. Gossip for bare-metal LAN) |
reconcile_interval_ms |
30_000 |
per-host reconcile cadence |
Labels prefixed brigade.scheduling/ on a MicroVMSpec become placement constraints
(matched against host labels); all labels are still passed through to flintlock.
Pushing a semver tag runs .github/workflows/release.yml, which publishes two
artifacts (version taken from the tag, v stripped):
git tag v1.2.3 && git push origin v1.2.3 # -rc/-beta suffixes → prerelease-
OTP tarball —
mix releaseoutput attached to the GitHub Release. Bundles ERTS, built onubuntu-latest(glibc, linux/amd64); the target host must match that OS/arch. Unpack and runbin/brigade start. -
Container image — pushed to
ghcr.io/liquidmetal-dev/brigade:1.2.3(plus:1.2and, for stable tags,:latest):docker run --rm ghcr.io/liquidmetal-dev/brigade:1.2.3 version
Prod settings are read from the environment at boot via config/runtime.exs
(BRIGADE_GRPC_PORT, BRIGADE_AUTH_TOKEN, FLINTLOCK_ENDPOINT, …); unset vars fall
back to the config/config.exs defaults above.
mise exec -- mix test # hermetic suite (fake flintlock, no hardware)
mise exec -- mix test --include distributed # + real multi-node peer testsThe fake flintlock (test/support/fake_flintlock.ex) is an in-memory gRPC MicroVM
server used as the drop-in conformance oracle. The overcommit property test hammers
concurrent creates and asserts no host is ever overcommitted; the distributed tests form
a real Erlang mesh and assert exactly one scheduler + failover.
Milestones M0–M4 complete: contract + scaffold, single-node happy path, cluster + singleton scheduler, failure handling, and hardening (auth, observability, docs).
Not yet implemented (deferred): topology B (remote hosts), auto-reschedule of cattle VMs, bin-pack strategy, grpc-gateway HTTP/JSON, live migration, distributed tracing.