12 · Compose a cluster declaratively
Write one YAML, get a working fleet. up reconciles toward the declared
state and is idempotent — re-running with a delta applies only the delta.
There is no lakeshore compose command. It is lakeshore up, down,
ps, status, and logs, even though the source lives in
src/cli/compose/.
Exercises
lakeshore up— parselakeshore.yaml, diff against the control plane, apply.- The daemon-group abstraction — N daemons sharing config and queue
membership, labelled
<group>-0,<group>-1, … lakeshore ps/status— fleet state scoped by theprojectslug.lakeshore down— drain (or terminate) the fleet.- Reconciliation: state is derived from the control plane on every run. There is no lock file in v1.
Requires
- A provider that can provision — EC2, GCE, or Kube. Its credentials registered as secrets and referenced from the provider kwargs.
- A
lakeshore.yamlin the current directory. Lookup is cwd-only; there is no upward walk, though-f, --file <path>overrides it.
The file
version: 1, project, and a non-empty daemons mapping are required.
project and every queue and group name must match
^[a-z0-9][a-z0-9-]*$. Inside a daemon group, queues and count are
required — count has no default on purpose, because a silent 1 is
more footgun than convenience — and any key outside the allowed set is a
hard error naming the allowed keys.
Queue elasticity.kind in YAML is underscored: fixed,
fully_elastic, pool_with_threshold, max_count. (The
queues add --elasticity CLI flag spells the same values with hyphens.
The two surfaces are not interchangeable.)
Run
Expected output
--plan:
Applying:
A second up with nothing changed reports (no change) per group and
already present per queue.
down:
--prune-queues also removes the declared queues; without it they are
kept and the reason is printed.
How scoping works
Every daemon up spawns is stamped with compose_project: <project>.
ps, status, and down list workers with
?compose_project=<project> and match group membership by the
<group>- label prefix. Two projects in the same namespace never see
each other's daemons.
If it fails
| Symptom | Likely cause |
|---|---|
| Parse error naming a key path | The loader validates strictly. The message includes the offending path — e.g. daemons.gpu.count — required, positive integer (got null). |
up reports failures per daemon | The provider could not provision. Each failure line carries the launcher's error; providers instances <name> usually shows why. |
| Queue created but no daemons | A crash mid-reconcile. down cleans up; if it does not, lakeshore daemon kill --prefix <group>- and lakeshore queues rm <name>. |
Re-running up re-creates things | A reconciliation bug — file an issue with the --plan output from both runs. |
Status
Manual.