Files
orca/examples/full-stack/README.md
T
Jon Chery 2c26a6d54f docs(P03): full-stack examples — 5 jobspecs + rendered artifacts + walkthrough
P03 — full-stack example with ingress configured (REQ-094; gate C-20).

examples/full-stack/:
- web-app.md: kind Service, process runtime, 3 replicas, Unix socket
  (default R-007), rolling update, constraints (node.role==web),
  affinity (zone==a weight 80), lifecycle hooks (post_start/pre_stop).
- api.md: kind Service, process runtime, 2 replicas, TCP opt-in
  (service.bind: 127.0.0.1, R-007), canary update with manual promote,
  constraints (node.role==api, node.cpus>=2), env vars.
- worker.md: kind Job, process runtime, one-shot, timeout 300s, env
  vars, lifecycle hooks (register/drain).
- log-shipper.md: kind Service (DaemonSet workaround — parser gap),
  process runtime, constraints (node.role==logs), env vars. Notes the
  v0.9 parser gap (schedule: block not wired) in a callout.
- postgres.md: kind Service, process runtime, 1 replica, blue-green
  update, volumes with replication (replicate:peer-b,peer-c via
  Syncthing), constraints (node.role==db, node.cpus>=4, node.memory>=8192).
- rendered/: Traefik dynamic YAML (web-app, api) + systemd units
  (web-app, api, log-shipper) showing what Orca generates on target nodes.
- README.md: end-to-end walkthrough (init -> node join -> capacity set
  -> ns create -> job run -> list --watch -> inspect rendered -> verify
  ingress -> drain/rollback). Cross-links to docs/ingress.md.
- examples_test.go: Go test that parses + validates all 5 jobspecs
  against the current parser and schema validators (gate C-20).

All 5 jobspecs pass jobspec.ParseFile + schema.ValidatorFor(kind).

---ci---
project: orca
phase: 3
milestone: v0.10
status: execute
---/ci---
2026-08-05 20:59:33 +00:00

6.1 KiB

Full-Stack Example with Ingress

This directory contains a complete multi-service stack deployed with Orca, including Traefik ingress configuration. Each file is a valid Orca jobspec (.md frontmatter) that passes the v0.9 parser and schema validators.

Stack overview

File Kind Runtime Ingress Description
web-app.md Service process Unix socket (default) Frontend HTTP server, 3 replicas, rolling update
api.md Service process TCP 127.0.0.1:9090 (R-007 opt-in) Backend API, 2 replicas, canary update
worker.md Job process none One-shot batch worker with lifecycle hooks
log-shipper.md Service process Unix socket (metrics) Log shipper on a dedicated node
postgres.md Service process Unix socket Database with volume replication, blue-green update

Rendered artifacts

The rendered/ directory shows what Orca generates on the target nodes when you submit these jobspecs:

File Description
traefik-dynamic-web-app.yaml Traefik dynamic config for the web-app Service
traefik-dynamic-api.yaml Traefik dynamic config for the api Service (TCP bind)
systemd-web-app.service Systemd unit for the web-app alloc
systemd-api.service Systemd unit for the api alloc (with TCP bind marker)
systemd-log-shipper.service Systemd unit for the log-shipper alloc

Walkthrough

Prerequisites

  • Orca installed (orca version works)
  • 2+ Linux nodes reachable over SSH (for multi-node scheduling)
  • Traefik installed on the lead node (watches /etc/traefik/dynamic/)

Step 1: Initialize the cluster

# On the operator laptop
orca init

This creates ~/.orca/ (or /root/.orca with --system), bootstraps the CA, generates the server cert, auto-detects the OS, and registers a localhost node.

Step 2: Join remote nodes

# Join a Proxmox node (v0.9 canonical SSH-push path)
orca node join --type proxmox --host 192.168.1.100 --ssh-user root

# Join a second node
ORCA_PROXMOX_PASSWORD=secret orca node join --type proxmox --host 192.168.1.101

Step 3: Declare node capacity

The CLI-side scheduler uses capacity declarations for bin-packing:

orca node capacity set --cpu 4000 --memory 8192 --disk 100000 --node 192.168.1.100
orca node capacity set --cpu 4000 --memory 8192 --disk 100000 --node 192.168.1.101

Step 4: Create a namespace

orca ns create prod --parent _defaults

This creates ~/.orca/prod/ with db/, jobs/, alloc/, and ns.md.

Step 5: Submit the stack

orca job run web-app.md
orca job run api.md
orca job run worker.md
orca job run log-shipper.md
orca job run postgres.md

Each orca job run parses the .md jobspec, validates it against the schema, schedules it via the CLI-side bin-packing scheduler, and generates the systemd + Traefik artifacts on the target node via SSH-push.

Step 6: Observe placements

orca job list --watch

# Output:
# ID                                   NAME          STATUS   EXIT
# abc-123...                           web-app       running  0
# def-456...                           api           running  0
# ghi-789...                           worker        complete 0
# jkl-012...                           log-shipper   running  0
# mno-345...                           postgres      running  0

Step 7: Inspect rendered artifacts

After submission, the target nodes have:

/etc/systemd/system/orca-v1-web-app.service       # systemd unit
/etc/systemd/system/orca-v1-api.service           # systemd unit (TCP bind)
/etc/traefik/dynamic/orca-web-app.yaml            # Traefik dynamic config
/etc/traefik/dynamic/orca-api.yaml                # Traefik dynamic config
/run/orca/alloc-web-app-0/port-http.sock          # Unix socket (R-007 default)

See the rendered/ directory in this example for the exact file contents.

Step 8: Verify ingress

Traefik watches /etc/traefik/dynamic/ and atomically reloads when a file changes (write-tmp + rename, gate C-10). The web-app is reachable at https://<cluster-domain>/web-app and the API at https://<cluster-domain>/api.

Health checks (/healthz on each backend) ensure Traefik only routes to healthy instances.

Step 9: Drain and rollback

To drain a service (stop traffic, keep the workload running):

# Orca writes a Traefik config with weight:0 on every backend
# (RenderDrain). Traefik stops sending traffic.

To roll back, re-submit the normal jobspec — Orca writes the non-drained Traefik config and Traefik resumes routing.

Ingress model

See docs/ingress.md for the full Traefik ingress reference. Key points:

  • kind: Service implies a Traefik route (D-175).
  • Default bind is a Unix socket at /run/orca/alloc-<id>/port-<name>.sock (R-007).
  • service.bind: 127.0.0.1 opts in to TCP (loopback only).
  • One Traefik dynamic file per Service at /etc/traefik/dynamic/orca-<name>.yaml.
  • Atomic reload via write-tmp + rename (gate C-10).
  • Drain sets weight: 0 per backend.

Validation

All jobspecs in this directory are validated by a Go test:

go test ./examples/full-stack/ -v -run TestExamplesValidate

This test parses each .md file with jobspec.ParseFile and validates it against schema.ValidatorFor(kind) — ensuring every field used in the examples exists in the current WorkloadSpec struct and passes the per-kind validators (gate C-20).

v0.11 forward

The following are not yet implemented in v0.9 and will land in v0.11:

  • DaemonSet schedule: block: the parser does not yet populate the schedule: frontmatter block (v0.9 parser gap). The log-shipper example uses kind: Service with count: 1 and a node.role constraint as a workaround.
  • Secret resolution: env: { KEY: { from: "secret:..." } } is parsed but not resolved to EnvironmentFile=/LoadCredential= until v0.11-P03.
  • Transactional update execution: the update: block's plan is computed but not executed transactionally until v0.11-P10.
  • Socket activation: real socket unit files land in v0.11-P08.