Removes every live current-fact reference to src/frontend/Makefile targets or Makefile-specific fixed paths across 11 docs (steering, frontend/CLAUDE.md, root CLAUDE.md, deployment docs, and two skill docs), replacing with the Meson-based equivalents established by tasks 1-3.2: /xce-build apiserver, meson test --suite apiserver, direct tests/regression/run.sh invocation. Round-1 review rejected the first pass: xce-deploy-ha/SKILL.md had over-corrected by deleting all mention of deploy-ha.sh's still-real /tmp/apiserver legacy fallback (that script's discovery logic is unchanged, unlike run-resource-verify.sh's, which genuinely lost its fallback in task 3.1 -- xce-test-resources/SKILL.md's identical- looking edit was correct for that reason). Fixed by restoring an accurate, Makefile-free description of the real fallback at all four touched sites. Re-reviewed and approved. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XYdtrEWWZfKRRGED7raEYg
301 lines
26 KiB
Markdown
301 lines
26 KiB
Markdown
# XCE Deployment Guide
|
|
|
|
Covers building and running all four daemons (controller, worker, scheduler, apiserver) and using `xceadmin` to author and push cluster configuration. Local dev/test deployment is fully automated; production process supervision (systemd/Docker/etc.) is **not** shipped in this repo — see [Production Gaps](#production-gaps) before treating anything here as a production runbook.
|
|
|
|
## Component Overview
|
|
|
|
```
|
|
xceadmin (Python CLI) --config push (TLS)--> controller (C, HA cluster, 1..N nodes)
|
|
^ ^
|
|
TLS join | | TLS join
|
|
| |
|
|
worker (C) scheduler (C)
|
|
^
|
|
CGO-linked libxce client (TCP, TLS, HA-aware)
|
|
|
|
|
apiserver (Go, REST) <-- xcecli (Go CLI, REST)
|
|
```
|
|
|
|
- **controller** (`xce_controller`) — the only stateful daemon. Runs as 1 or 3+ nodes for HA (Raft-style election + replication). All other components are its clients.
|
|
- **worker** (`xcecd`) — one per compute host, runs jobs, joins a controller.
|
|
- **scheduler** (`xce_scheduler`) — job-to-node placement engine, joins a controller.
|
|
- **apiserver** (`xce_apiserver`) — Go REST API, joins the controller cluster as a client role (`HostRole_apiserver`) via a CGO-linked `libxce` client, not a plain HTTP backend call.
|
|
- **xcecli** — Go CLI, talks to `apiserver` over REST, never touches the controller directly.
|
|
- **xceadmin** — Python CLI, the only tool that compiles and pushes *cluster configuration* (`cluster.bin`) to the controller. Separate concern from the four daemons above.
|
|
|
|
**Every** connection into the controller is TLS-encrypted — controller↔controller HA replication, and every `libxce`-client connection (worker→controller, scheduler→controller, apiserver→controller, xceadmin→controller). The controller runs a single `iomgr` bound to one server `SSL_CTX` (`myapp.c`); `endpoint_accept()`/`endpoint_connect()` in `src/lib/netio.c` have no plaintext code path at all — every accepted or outgoing socket is wrapped in `SSL_new()` before use. What's actually missing is **client authentication and identity verification**, not encryption: TLS verification is `SSL_VERIFY_NONE` everywhere in this codebase (server and client contexts alike, no client certificates), so any of these connections buys you encryption-in-transit, not authentication of who's on the other end.
|
|
|
|
## Building
|
|
|
|
Two different Nix entry points, for two different jobs — don't conflate them:
|
|
|
|
- **`nix develop --command` — single-component dev builds.** `/xce-build` is not a separate build system; it's `nix develop ./src --command bash src/tools/xce-build.sh <component> <profile>` under the hood, i.e. the hermetic dev shell driving meson/ninja for exactly one component. This is the reproducible way to build just `controller`, just `worker`, etc.
|
|
- **`nix build` — whole-stack production packaging.** A packaged Nix derivation, all-or-nothing (see Production Gaps item 6) — it always produces every daemon in one `result/`, never a single one.
|
|
|
|
```bash
|
|
# Dev loop, per component (debug + ASAN, runs tests) — wraps `nix develop ./src --command ...`
|
|
/xce-build lib # libxce only
|
|
/xce-build controller
|
|
/xce-build worker
|
|
/xce-build scheduler
|
|
/xce-build apiserver # also builds xcecli in the same pass (same meson step)
|
|
/xce-build xcecli # xcecli alone, if you don't need apiserver
|
|
/xce-build xceadmin
|
|
/xce-build # all components
|
|
|
|
# Profiles: /xce-build [component] [profile]
|
|
# asan (default) -> xce.build/ debug+ASAN, runs tests
|
|
# debug -> build.dbg/ -O0 -g3, no sanitizer, gdb-friendly
|
|
# release -> build.release/ -O3, no sanitizer, build-only
|
|
|
|
# Production packaging (Nix, whole stack only)
|
|
nix build .#xcecore # controller + worker + scheduler + apiserver + xceadmin + AppImage, one derivation
|
|
nix build .#xcecore_debug # same, debug + ASAN (this flake's default package)
|
|
nix build .#xcecli # xcecli, as its own separate Nix *package* derivation (Go-only, no C toolchain) — `/xce-build xcecli` above gets you the same binary without going through `nix build` at all
|
|
```
|
|
|
|
`nix build .#xcecore` is all-or-nothing — there is no per-daemon Nix target. It produces one `result/bin/` containing `xce_controller`, `xcecd` (+ `xcecd_worker`, `xcecd_virtual`), `xce_scheduler`, `xce_apiserver`, the `xceadmin` venv wrapper, and `xce` (the AppImage dispatcher script, see below). `nix build .#xcecli` is the one genuinely separate target.
|
|
|
|
**AppImage** (`xce-x86_64.AppImage`, from `nix build .#xcecore`) wraps `src/tools/appimage/entry.sh`, a thin per-daemon dispatcher — it runs exactly **one** daemon per invocation, never the whole stack:
|
|
```bash
|
|
./xce-x86_64.AppImage controller --myname ctrl-1 --peers "ctrl-1=h1:9990,ctrl-2=h2:9990,ctrl-3=h3:9990" ...
|
|
./xce-x86_64.AppImage worker -c /etc/xce/worker.yaml
|
|
./xce-x86_64.AppImage scheduler --peers node1=h1:9990
|
|
./xce-x86_64.AppImage apiserver -d /opt/xce/apiserver
|
|
./xce-x86_64.AppImage shell # interactive bash with all binaries + haproxy/glow/less on PATH
|
|
```
|
|
Run it once per daemon (likely on different hosts) to stand up a cluster.
|
|
|
|
## xceadmin — Cluster Configuration
|
|
|
|
`xceadmin` is the only supported way to turn operator-authored YAML into the `cluster.bin` binary the controller loads, and to push it to a running cluster. It speaks the same raw TLS + Cap'n Proto wire protocol controllers use with each other (no client certificate — `verify_mode=ssl.CERT_NONE`, matching the controller's own outbound posture).
|
|
|
|
### Install
|
|
|
|
```bash
|
|
# Dev, editable, fast iteration (must run inside `nix develop ./src`)
|
|
cd src/xceadmin && pip install -e .
|
|
|
|
# Production / CI (what /xce-build and `nix build .#xcecore` actually use)
|
|
/xce-build xceadmin
|
|
```
|
|
`/xce-build xceadmin` always does a clean, non-editable `pip install` into an out-of-tree venv (`--system-site-packages`, inheriting nix-provided `pycapnp`/`pyyaml`/`jsonschema`), runs the full pytest suite, syncs the bundled schemas fresh from `src/schema/`, and produces a standalone `xceadmin` wrapper script — this is what ships in the AppImage and `nix build` output.
|
|
|
|
**This venv only works from inside `nix develop ./src` (or the AppImage's bundled env) — it is not a portable, standalone venv.** `--system-site-packages` means `pip install` sees `click`/`pycapnp`/`pyyaml`/`jsonschema` already satisfied by nix's site-packages and skips vendoring its own copies — deliberately, since `pycapnp` is ABI-sensitive to the exact `capnproto` C library version, and letting pip resolve/build its own would risk a version mismatch that crashes at runtime. If you `source .venv/bin/activate` (or run the `xceadmin` binary directly) outside `nix develop`, none of those packages are on `sys.path` and you'll hit `ModuleNotFoundError: No module named 'click'` (or similar) immediately. Either stay inside `nix develop ./src` when invoking the built `xceadmin`, or use the AppImage, which bundles a working environment on `PATH` already.
|
|
|
|
### Usage
|
|
|
|
```bash
|
|
# Compile YAML config into a binary, print its SHA-256 to stderr
|
|
xceadmin config build /path/to/configs -o cluster.bin
|
|
xceadmin config build /path/to/configs -o - # to stdout instead of a file
|
|
|
|
# Push a compiled config to a running controller (any node — primary or standby; redirects to primary automatically, up to 3 hops)
|
|
xceadmin config push cluster.bin --controller 127.0.0.1:9991 # host:port; defaults to localhost:9991
|
|
|
|
# Check whether the controller would accept it, without committing
|
|
xceadmin config push --dry-run cluster.bin --controller 127.0.0.1:9991
|
|
```
|
|
|
|
`config build` reads a fixed set of source files from the given directory — `xce.yaml`, `hosts.yaml`, `queues.yaml`, `resources.yaml`, `users.yaml`, `limits.yaml`, `scaler.yaml` (a missing file → empty section, not an error) — validates each against a bundled JSON schema, runs hard-fail business checks (reserved names, duplicates) and warn-only checks (undefined cross-references), then maps everything into a `ClusterConfig` Cap'n Proto message. Building twice from identical input is byte-for-byte deterministic (no wall-clock/hostname ever read). Fields that are **never** settable from YAML: `port`, hash-table-size tuning, `xstrings`, `candidates` (peer membership), `configGeneration` (controller-assigned).
|
|
|
|
`config push` exits non-zero unless the controller's reply reports `ackedCount > 0` and no error string; `--dry-run` exits non-zero on any rejection reason the controller returns.
|
|
|
|
### Known limitations (as of this writing)
|
|
|
|
- **~~`config push` against a real controller fails with a client-side decode crash~~** — **Fixed 2026-07-30.** `decode_reply()` (`src/xceadmin/src/xceadmin/discovery.py:46-77`) now unwraps the outer `Request{opcode: OPCode_reply, body.replymsg: RequestReply{...}}` envelope before decoding the inner `RequestReply`. Both `_redirect_target()` and `push_config()` go through `decode_reply()`; the old bare-`RequestReply` decode path is gone. `decode_reply()` validates `body.which() == "replymsg"` and raises `ReplyDecodeError` on anything else. Live-verified against a real deployed controller.
|
|
- **~~`config push --dry-run`'s "OK" result is not meaningful evidence of anything~~** — **Fixed 2026-08-09 (bug-1506/1507).** The controller gained a real `OPCode_probeConfigTag` handler (`config_repl.c:handle_probe_config_tag`) that returns the current `config_version_tag` through the standard `Request`/`RequestReply` envelope. `probe_config()` now wraps the probe in a `Request` envelope and goes through `follow_redirects` → `decode_reply` (same as `push_config`), so `accepted=True` means the controller actually replied with a valid tag, and `--dry-run` reports the real generation number from the cluster.
|
|
- No `config diff`, no rollback, no version history command. `xceadmin` today has exactly two subcommands: `config build` and `config push [--dry-run]`.
|
|
- No client authentication — the trust model is "same posture as controller-to-controller TLS," i.e. encryption without identity verification.
|
|
|
|
## Deploying the Controller
|
|
|
|
The controller is the only daemon with real config-file/CLI complexity for cluster topology.
|
|
|
|
```bash
|
|
xce_controller \
|
|
--myname ctrl-1 \
|
|
--peers "ctrl-1=host1:9990,ctrl-2=host2:9990,ctrl-3=host3:9990" \
|
|
--workdir /var/lib/xce/controller \
|
|
--certdir /var/lib/xce/certs \
|
|
--logfile /var/log/xce/ctrl-1.log \
|
|
--vcpus 4
|
|
```
|
|
|
|
- Omit `--peers` for a single-node, non-HA controller.
|
|
- `--workdir` holds per-node `conf/` (cluster config) and `data/` (WAL + checkpoint) subdirectories.
|
|
- `--certdir` must contain `xce.crt`/`xce.key` — this is the **only** TLS material in the whole stack, and the controller loads it into the single server `SSL_CTX` used for **every** inbound connection it accepts: controller↔controller HA traffic *and* every worker/scheduler/apiserver/xceadmin client join. Generate a self-signed pair for local/dev use:
|
|
```bash
|
|
openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
|
|
-keyout /var/lib/xce/certs/xce.key -out /var/lib/xce/certs/xce.crt -subj "/CN=xce"
|
|
```
|
|
TLS verification is `SSL_VERIFY_NONE` everywhere in this codebase — no hostname/identity check is enforced regardless of CA, so a CA-signed cert buys you encryption, not authentication.
|
|
- Exactly one node (conventionally the first started) needs its `conf/` seeded with an initial `cluster.bin` (via `xceadmin config build`) before first boot; other nodes receive config over the wire via config-push once joined.
|
|
- **WAL and snapshot retention in `data/` is fully automatic — no manual purging needed.** Every checkpoint trims WAL segments below a replication-safe floor (a segment is never deleted while any standby still needs it to catch up) and rotates old snapshots down to `snap_keep_n` most recent (default 2 — current + one previous, kept as a last-known-good fallback if a checkpoint is later found corrupt). `snap_keep_n` is tunable via the cluster config's `PersistConfig.snapKeepN` field, not a CLI flag.
|
|
|
|
### Local dev cluster (fully automated)
|
|
|
|
```bash
|
|
/xce-deploy-ha localhost:9991 localhost:9992 localhost:9993
|
|
```
|
|
This is the `xce-deploy-ha` skill wrapping `src/tools/deploy-ha.sh`, which does everything above automatically: generates certs, builds an initial config from `src/configs/*.yaml` via `xceadmin`, and writes `scripts/start.sh ctrl-N [extra flags]` / `scripts/cleanup.sh` under the deploy directory. Start nodes with a short stagger for clean election ordering:
|
|
```bash
|
|
scripts/start.sh ctrl-1 -vvv &
|
|
sleep 0.5
|
|
scripts/start.sh ctrl-2 -vvv &
|
|
sleep 0.5
|
|
scripts/start.sh ctrl-3 -vvv &
|
|
```
|
|
(`-vvv` is a **positional** arg to `start.sh`, not `VERBOSE=-vvv` — the skill's own docs have this wrong.)
|
|
|
|
`scripts/cleanup.sh` wipes logs and every node's `data/`, plus `conf/` for nodes 2..N — ctrl-1's config and all certs survive a cleanup, giving a clean restart without regenerating TLS material or re-authoring config.
|
|
|
|
## Deploying the Worker
|
|
|
|
```bash
|
|
xcecd -c /opt/xce/conf/worker.yaml
|
|
# or, without a config file:
|
|
xcecd -m controller-host -p 9990 --peers "ctrl-1=h1:9990,ctrl-2=h2:9990" -l /var/log/xce
|
|
```
|
|
|
|
- **Must run as root** — `check_user()` exits immediately otherwise.
|
|
- Default config path is `/opt/xce/conf/worker.yaml` if `-c` is omitted (and it must exist, or the daemon exits).
|
|
- `-c`, if given, **wins over** `-m`/`-p` (those flags are ignored with a warning).
|
|
- Minimal `worker.yaml`:
|
|
```yaml
|
|
workerconf:
|
|
management_list: ["ctrl-1-host"]
|
|
controller_port: 9990
|
|
log_level: "info"
|
|
log_dir: "/var/log/xce"
|
|
```
|
|
- `--peers "[name=]host:port,..."` enables HA-aware controller discovery: the worker tries each candidate in turn (every 5s by default) until one accepts the join.
|
|
- Worker↔controller traffic is TLS-encrypted (via the shared `libxce` client, `xcecli_ctx_init()` → `xce_ctx_init()`), same posture as everything else in the stack: no client certificate, `SSL_VERIFY_NONE`.
|
|
|
|
## Deploying the Scheduler
|
|
|
|
```bash
|
|
xce_scheduler --peers "ctrl-1=controller-host:9990" -l /var/log/xce/scheduler.log -v
|
|
```
|
|
|
|
- No config file at all — pure CLI (`argp`).
|
|
- **Gotcha**: the default controller target is hard-coded to `localhost:9990`. There is no `-m`/`-p` equivalent — the *only* way to point the scheduler at any other host or port, even a single non-HA controller, is `--peers`. Forgetting `--peers` silently targets `localhost:9990` regardless of where your controller actually is.
|
|
- `-v` is repeatable for more verbose logging; default log file is `/tmp/xce_scheduler.log`.
|
|
- TLS-encrypted to the controller, same as the worker (same shared `libxce` client path).
|
|
- Scheduling policies (fcfs, limit, parallel/sequential-job allocators) are compiled into the binary through the picker plugin interface (`builtin_fcfs` is the registered default), not dynamically loaded.
|
|
|
|
### Session recording (live cluster)
|
|
|
|
Every normal-mode scheduler can record its scheduling sessions to a `.replay` file for later offline replay/debugging — off by default, no overhead when disabled:
|
|
|
|
```bash
|
|
xce_scheduler --peers "ctrl-1=controller-host:9990" --record-dir /var/log/xce/scheduler-recordings -v
|
|
```
|
|
|
|
- `--record-dir DIR` sets where recordings are written (default `/var/log/xce/`). This is the **only** way `record_dir` is provisioned — there is no per-request override.
|
|
- Recording itself is controlled at runtime, not via a startup flag, through the controller:
|
|
```bash
|
|
xceadmin scheduler record start [FILENAME] --controller 127.0.0.1:9991 # filename optional, auto-generated if omitted
|
|
xceadmin scheduler record status --controller 127.0.0.1:9991
|
|
xceadmin scheduler record stop --controller 127.0.0.1:9991
|
|
```
|
|
These go through a new `OPCode_schedAdmin` request the controller pass-through-forwards to its attached scheduler (`xceadmin` → controller → scheduler → reply, same primary-redirect behavior as `config push`). The controller holds no recording state itself.
|
|
- `FILENAME` (if given) must be a bare filename — no `/` and no `..` segment. The scheduler resolves it under `--record-dir` and rejects anything that would escape that directory (`"invalid record file name"`).
|
|
- A `.replay` file is a single self-contained Cap'n Proto binary stream (header + snapshot + interleaved events/decisions + periodic checkpoints) — inspect one offline, no running cluster needed:
|
|
```bash
|
|
xceadmin scheduler dump /var/log/xce/scheduler-recordings/some-session.replay
|
|
```
|
|
|
|
### Simulation mode (offline debugging, no controller connection)
|
|
|
|
`--sim` runs the scheduler as a **standalone replay tool** against a previously recorded (or hand-built) `.replay` file — zero connection to any controller, safe to run against a production recording on a laptop:
|
|
|
|
```bash
|
|
xce_scheduler --sim /path/to/session.replay --sim-speed 0 # replay as fast as possible
|
|
xce_scheduler --sim /path/to/session.replay --sim-verify # replay + diff against the recorded decisions
|
|
xce_scheduler --sim /path/to/session.replay --sim-output /tmp/resim.replay --sim-alt-events /path/to/edited-events.replay # "what-if": substitute events, record the new decisions
|
|
xce_scheduler --sim /path/to/session.replay --interactive # step through it interactively (REPL)
|
|
```
|
|
|
|
- `--sim-speed`: `0` = as-fast-as-possible, `1` = real-time (default), `N>1` = `N`x real-time.
|
|
- `--sim-verify` re-derives decisions from the recorded events and asserts they match the decisions the file already recorded — the feature's core "deterministic replay" regression check, usable ad hoc against any recording.
|
|
- `--sim-alt-events FILE` swaps in a different event stream after the initial snapshot for what-if branching; `--sim-output FILE` records the resulting decisions as a new `.replay` file.
|
|
- `--interactive` opens a REPL (`step`, `continue`, breakpoints, state inspection, snapshot capture) driving the *same* simulation run a batch `--sim` would — REPL and batch produce byte-identical decisions for the same input, only the pacing differs.
|
|
- Simulation mode never opens a socket to any controller and never touches `--peers`/`--record-dir` — see `.kiro/specs/scheduler-core/design.md`'s Boundary Commitments for the full isolation contract.
|
|
|
|
## Deploying apiserver
|
|
|
|
```bash
|
|
apiserver -d /opt/xce/apiserver
|
|
```
|
|
|
|
`-d <top_dir>` is **mandatory** despite the `--help` text implying a default — omitting it exits immediately. `<top_dir>/config/` must already exist and contain:
|
|
|
|
| File | Purpose |
|
|
|---|---|
|
|
| `app.yaml` | HTTP(S) port, JWT secret/expiry, rate limits, CORS, IP whitelist, and the `backend:` block (`address`, `port`, `from`, `key`, optional `peers` for HA) pointing at the controller. |
|
|
| `auth.yaml` | Auth provider selection (`config_file` \| `pam` \| `ldap` \| `oauth2`); for `config_file`, embeds usernames/passwords/roles directly. |
|
|
| `rbac.yaml` | Permission catalog + role→permission mappings (domains: `self`, `self_ug`, etc.). User→role binding comes from `auth.yaml`, not this file. |
|
|
| `logger.yaml` | zap logger level/encoding/rotation. |
|
|
|
|
Both `app.yaml` and `logger.yaml` are JSON-Schema-validated at startup (fail-fast on malformed config).
|
|
|
|
### Build gotcha — apiserver needs real CGO flags, not a bare `go build`
|
|
|
|
A bare `go build` in `src/frontend/apiserver` (no `CGO_CFLAGS`/`CGO_LDFLAGS` set) is not a supported way to produce a working apiserver binary. The real controller-connection code (`backend/clib.go`) is CGO and needs those flags to find `libxce`'s headers. Depending on your environment this either fails to compile, or — if `CGO_ENABLED=0` is ambient — silently links `backend/stub.go` instead, producing a binary where every backend call returns `errNoCGO: backend requires CGO`. There is no startup warning; the HTTP server comes up fine and only fails at request time.
|
|
|
|
**Use `/xce-build apiserver` (or `nix build .#xcecore`)** for anything that needs to actually talk to a controller — it computes the correct `CGO_CFLAGS`/`CGO_LDFLAGS` from pkg-config and produces `xce_apiserver` under `src/xce.build/frontend/apiserver/` (or the equivalent path for other profiles). `/xce-build apiserver` also runs the pure-Go unit-test loop (`CGO_ENABLED=0` is *intentional* there); a manual `go build`/`go test` invocation is not the canonical path.
|
|
|
|
### Relationship to the controller
|
|
|
|
apiserver is a **separate OS process**, network-connected to the controller — not embedded, not in-process. It joins the controller's cluster protocol as `HostRole_apiserver` via a CGO-linked `libxce` client (the same join mechanism workers/schedulers use), configured entirely by `app.yaml`'s `backend:` block. If `backend.address` is empty, apiserver logs "running in stub mode" and serves HTTP, but every job/host/queue/cluster handler will fail. This connection is TLS-encrypted, same as every other `libxce` client (no client certificate, `SSL_VERIFY_NONE`) — do not confuse it with apiserver's own separate HTTPS termination for its REST API, covered next.
|
|
|
|
### TLS
|
|
|
|
apiserver can self-terminate HTTPS (`app.yaml`'s `security.https.enabled` + `cert_file`/`key_file`, default a checked-in self-signed dev cert). A reverse-proxy pattern (sample `config/nginx.https.conf`, HAProxy mentioned in steering docs) is the intended production direction but is **not wired into any startup automation** — you'd have to set it up yourself.
|
|
|
|
## Putting It Together: A Full Local Cluster
|
|
|
|
```bash
|
|
# 1. Build everything
|
|
/xce-build all
|
|
|
|
# 2. Bring up a 3-node HA controller cluster
|
|
/xce-deploy-ha localhost:9991 localhost:9992 localhost:9993
|
|
# (start ctrl-1, then ctrl-2/ctrl-3 with a 0.5s stagger, per the deploy script)
|
|
|
|
# 3. Start a worker pointed at the cluster
|
|
xcecd -m localhost -p 9991 --peers "ctrl-1=localhost:9991,ctrl-2=localhost:9992,ctrl-3=localhost:9993" -l /tmp/xce-worker
|
|
|
|
# 4. Start a scheduler pointed at the cluster
|
|
xce_scheduler --peers "ctrl-1=localhost:9991,ctrl-2=localhost:9992,ctrl-3=localhost:9993" -v
|
|
|
|
# 5. Start apiserver (needs its own config/ dir prepared first — copy from src/frontend/apiserver/config/,
|
|
# at minimum set backend.address/port to match the controller and use auth.yaml.example, not the checked-in auth.yaml)
|
|
apiserver -d /path/to/apiserver-topdir
|
|
|
|
# 6. Use xcecli against apiserver, or xceadmin against the controller directly
|
|
xcecli job submit myjob.json
|
|
xceadmin config push cluster.bin --controller localhost:9991
|
|
```
|
|
|
|
## Production Gaps
|
|
|
|
Be aware of these before treating anything above as a production runbook — they are honest gaps in this repo today, not documented-but-hidden features:
|
|
|
|
1. **No process supervision is shipped.** No systemd unit, no Dockerfile, no docker-compose, no supervisord config exists anywhere in this repo for any of the four daemons. A systemd-unit *sketch* exists in `src/docs/config-replication-design.md` but has a wrong binary name (`xcecontroller` vs the real `xce_controller`) and specifies `Type=notify`, which the controller does not support (it never calls `sd_notify`) — using it as written will hang at startup and time out. If you adapt it, switch to `Type=simple` and fix the binary name.
|
|
2. **`src/frontend/apiserver/config/auth.yaml` ships plaintext dev credentials checked into git**: four accounts total — `admin`/`admin` and `op7x2k9`/`CHANGE_ME_dev_only` (both role `cluster_admin`), plus `user1`/`user1` and `user2`/`user2` (role `basic_user`). Treat all of them as exposed by default — don't ship the checked-in file as-is; copy `auth.yaml.example` and set real credentials/an external auth provider instead.
|
|
3. **All controller-bound traffic is encrypted but not authenticated.** Controller↔controller HA replication and every `libxce` client (worker→controller, scheduler→controller, apiserver→controller, xceadmin→controller) run over TLS, but `SSL_VERIFY_NONE` is used throughout — no client certificates, no hostname/identity verification. This buys confidentiality and tamper-resistance in transit, not authentication of the peer; anyone who can reach the port and complete a TLS handshake is treated as trusted the moment the transport-level join succeeds.
|
|
4. **apiserver's own TLS uses a checked-in self-signed cert with no CA trust story**; `xcecli` hardcodes `InsecureSkipVerify: true` against it. A reverse-proxy-terminated-TLS model is sketched (nginx sample config) but not automated. (This is apiserver's REST-facing HTTPS, separate from its TLS-encrypted `libxce` connection to the controller in item 3.)
|
|
5. **`xceadmin config push` against a real cluster** — the decode-crash bug is fixed (2026-07-30, `decode_reply()` unwraps the `Request` envelope), but `--dry-run` still has no real controller-side handler (see Known Limitations item 2).
|
|
6. **`nix build .#xcecore` (the packaged derivation) is all-or-nothing** — there's no per-daemon Nix package target. For a single component, use `/xce-build <component>`, which itself runs inside the Nix dev shell (`nix develop ./src --command ...`) rather than through `nix build`.
|
|
|
|
## Quick Reference
|
|
|
|
| Daemon | Binary | Config | Controller-pointing flag | TLS |
|
|
|---|---|---|---|---|
|
|
| Controller | `xce_controller` | none (CLI only) | `--peers` (omit for single-node) | Yes, all inbound (peers + clients), no client cert |
|
|
| Worker | `xcecd` | `worker.yaml` (or CLI) | `-m`/`-p` (config file wins) or `--peers` | Yes, no client cert |
|
|
| Scheduler | `xce_scheduler` | none (CLI only) | `--peers` (**required** even for a single non-default controller) | Yes, no client cert |
|
|
| apiserver | `xce_apiserver` | `config/{app,auth,rbac,logger}.yaml` | `app.yaml`'s `backend:` block | To controller: yes, no client cert. Own REST API: optional, self-terminated |
|
|
| xceadmin | `xceadmin` (Python) | none — takes YAML source dir | `--controller host:port` | Yes, no client cert |
|