289e5cf6e1
P02 — operator-facing documentation (REQ-091, REQ-092, REQ-093; gate C-22). docs/cli.md (REQ-091): - Full CLI command/flag reference: every command/subcommand with synopsis, flag tables (name/type/default/description), one-line examples. - Global flags (--json, --system, --config, --no-deprecation-warnings). - Output modes (text/json/watch), env vars, exit codes. - Deprecated surface callout boxes (daemon, cert, node-join-mTLS, HCL jobspec) pointing to v0.11 removal. docs/jobspec.md (REQ-092): - Markdown frontmatter schema reference: all top-level keys, block reference (runtime/ports/env-secrets/volumes/restart/update/service/ health/lifecycle/constraints/affinity/tasks), kinds matrix (Job/Service/DaemonSet required vs allowed), CEL subset grammar, body byte-exact preservation (R-015), deprecated HCL callout. docs/ingress.md (REQ-093): - Traefik ingress reference: service->Traefik mapping (D-175), R-007 socket-vs-TCP-bind semantics, generated YAML shape (routers/services/ healthCheck), atomic reload (C-10), drain (weight:0), TLS (certResolver, trust domain, step-ca), worked-example pointer to examples/full-stack/, v0.11 forward limitations. All factual claims grounded in live codebase (gate C-22). Cross-links verified to resolve. ---ci--- project: orca phase: 2 milestone: v0.10 status: execute ---/ci---
209 lines
6.7 KiB
Markdown
209 lines
6.7 KiB
Markdown
# Orca Ingress & Traefik Guide
|
|
|
|
This document explains how Orca configures ingress via Traefik dynamic
|
|
configuration. It covers the service→Traefik mapping, the R-007
|
|
socket-vs-TCP-bind model, atomic reload, drain, TLS, and a worked
|
|
example.
|
|
|
|
> **Canonical path (v0.9)**: Orca generates Traefik dynamic
|
|
> configuration files via the `TraefikEmitter`. The `kind: Service`
|
|
> workload implies a Traefik route. The emitter renders one YAML file
|
|
> per Service; Traefik watches the dynamic config directory and reloads
|
|
> atomically on change.
|
|
|
|
## The model
|
|
|
|
A `kind: Service` jobspec **implies** a Traefik route (D-175). `Job`
|
|
and `DaemonSet` do **not** carry a Traefik route by default — a
|
|
`service:` block on a `Job` is rejected by the validator.
|
|
|
|
When `orca job run` submits a `kind: Service` workload, the
|
|
`TraefikEmitter` renders a Traefik dynamic config file at:
|
|
|
|
```
|
|
/etc/traefik/dynamic/orca-<service-name>.yaml
|
|
```
|
|
|
|
This file contains:
|
|
- One **router** (`orca-<name>`) with a `PathPrefix` rule and TLS config.
|
|
- One **service** (`orca-<name>`) as a `loadBalancer` with one **server**
|
|
per port, pointing at the workload's Unix socket (or TCP port).
|
|
- A **healthCheck** stanza when the `health:` block is present.
|
|
|
|
Traefik watches `/etc/traefik/dynamic/` via `fsnotify` and reloads
|
|
whenever a file changes. Orca writes config atomically (write-tmp +
|
|
rename) so Traefik sees a single `IN_MOVED_TO` event and never observes
|
|
a half-written file.
|
|
|
|
## R-007: socket vs TCP bind
|
|
|
|
Orca workloads bind to a **Unix socket** by default, not a TCP port.
|
|
This is the R-007 security model: loopback-only by default, no network
|
|
exposure.
|
|
|
|
### Default: Unix socket
|
|
|
|
When `service.bind` is empty (default), the workload binds a Unix
|
|
socket at:
|
|
|
|
```
|
|
/run/orca/alloc-<alloc-id>/port-<port-name>.sock
|
|
```
|
|
|
|
systemd creates `/run/orca/alloc-<alloc-id>/` via
|
|
`RuntimeDirectory=orca/alloc-<alloc-id>` (mode 0750, owned by
|
|
`orca:orca`). The Traefik backend server URL is:
|
|
|
|
```yaml
|
|
servers:
|
|
- url: "unix:///run/orca/alloc-<alloc-id>/port-<port-name>.sock"
|
|
```
|
|
|
|
### TCP opt-in: `service.bind: 127.0.0.1`
|
|
|
|
When `service.bind: 127.0.0.1` is set, the workload binds a TCP port
|
|
directly (loopback only). The emitter adds an `ExecStartPre` marker to
|
|
the systemd unit so the bind mode is visible:
|
|
|
|
```ini
|
|
ExecStartPre=/bin/echo orca: bind 127.0.0.1 port <name> (tcp, R-007 opt-in)
|
|
```
|
|
|
|
`service.bind` must be a valid IP address. Empty (socket default) or
|
|
`127.0.0.1` (TCP opt-in) are the documented values; any other valid IP
|
|
is accepted but the bind happens in the process, not the emitter.
|
|
|
|
## Generated Traefik YAML
|
|
|
|
For a Service named `web` with port `http`:
|
|
|
|
```yaml
|
|
http:
|
|
routers:
|
|
orca-web:
|
|
rule: PathPrefix("/web")
|
|
service: orca-web
|
|
tls:
|
|
certResolver: orca
|
|
domains:
|
|
- main: "cluster.orca.local"
|
|
services:
|
|
orca-web:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "unix:///run/orca/alloc-<alloc-id>/port-http.sock"
|
|
healthCheck:
|
|
path: /healthz
|
|
interval: 5s
|
|
timeout: 1s
|
|
```
|
|
|
|
- One router per Service, named `orca-<service-name>`.
|
|
- Router rule: `PathPrefix("/<service-name>")`.
|
|
- TLS: `certResolver: orca`, trust domain `cluster.orca.local`
|
|
(placeholder; step-ca provisioner overrides in v0.11).
|
|
- One service per Service, named `orca-<service-name>`.
|
|
- One server per port, URL is `unix://<socket-path>`.
|
|
- `healthCheck` stanza present when `health:` block is set (required
|
|
for Service). Path is `/healthz`; interval and timeout come from the
|
|
`health:` block.
|
|
|
|
## Atomic reload (gate C-10)
|
|
|
|
Orca writes Traefik config atomically to avoid Traefik observing a
|
|
half-written file:
|
|
|
|
1. Write to `<path>.tmp` via `WriteFileIdempotent` (write + fsync).
|
|
2. `mv -f <path>.tmp <path>` (atomic POSIX rename).
|
|
|
|
Traefik's `fsnotify` watcher sees a single `IN_MOVED_TO` event and
|
|
reloads. If the new config is malformed, Traefik logs an error and
|
|
**holds last-good config** — the cluster keeps serving traffic on the
|
|
previous config.
|
|
|
|
## Drain
|
|
|
|
`RenderDrain` produces the same Traefik YAML with `weight: 0` on every
|
|
server in the load balancer:
|
|
|
|
```yaml
|
|
servers:
|
|
- url: "unix:///run/orca/alloc-<alloc-id>/port-http.sock"
|
|
weight: 0
|
|
```
|
|
|
|
Traefik stops sending traffic to the drained backend. The workload
|
|
keeps running; drain is reversible (re-submit the normal config to
|
|
restore traffic).
|
|
|
|
## TLS
|
|
|
|
- **certResolver**: `orca` (references the Traefik ACME/step-ca
|
|
certificate resolver configured in Traefik's static config).
|
|
- **Trust domain**: `cluster.orca.local` (placeholder in v0.9; step-ca
|
|
provisioner in v0.11 overrides with the real cluster trust domain).
|
|
- **SPIFFE SVIDs**: workload identity via SPIFFE SVIDs minted at submit
|
|
time via step-ca (v0.11-P01.5, gate C-08). The SVID is a URI SAN in
|
|
the workload's X.509 cert.
|
|
|
|
## Health checks
|
|
|
|
The `health:` block (required for `Service`) maps to the Traefik
|
|
`healthCheck` stanza:
|
|
|
|
```yaml
|
|
health:
|
|
check_type: http
|
|
interval: 5s
|
|
timeout: 1s
|
|
unhealthy_threshold: 2
|
|
```
|
|
|
|
→
|
|
|
|
```yaml
|
|
healthCheck:
|
|
path: /healthz
|
|
interval: 5s
|
|
timeout: 1s
|
|
```
|
|
|
|
Traefik polls each backend's `/healthz` at the configured interval. An
|
|
unhealthy backend is removed from the load balancer pool until it
|
|
passes the health check again.
|
|
|
|
## Worked example
|
|
|
|
See [examples/full-stack/](../examples/full-stack/) for a complete
|
|
multi-service stack with ingress configured:
|
|
- `web-app.md` — frontend Service (socket bind, PathPrefix route)
|
|
- `api.md` — backend API Service (TCP opt-in, `127.0.0.1` bind)
|
|
- `examples/full-stack/rendered/traefik-dynamic-web-app.yaml` — the
|
|
Traefik config Orca generates
|
|
|
|
## v0.11 forward (limitations)
|
|
|
|
The following are not yet implemented in v0.9 and will land in v0.11:
|
|
|
|
- **`service.host` / `service.route_id`**: stored on the `ServiceBlock`
|
|
but not yet consumed by the `TraefikEmitter`. The router rule is
|
|
hardcoded `PathPrefix("/<name>")`. Custom host-based routing lands in
|
|
v0.11.
|
|
- **Socket activation**: real socket-activation (socket unit files, fd
|
|
passing) lands in v0.11-P08. The current emitter renders the
|
|
`RuntimeDirectory` + socket path comments but does not create socket
|
|
units.
|
|
- **Transactional update execution**: the `update:` block's rolling/
|
|
canary/blue-green plan is computed by the emitter but not yet
|
|
executed transactionally. Transactional execution lands in
|
|
v0.11-P10.
|
|
- **SPIFFE SVID minting**: workload identity via step-ca SVIDs lands in
|
|
v0.11-P01.5 (gate C-08).
|
|
- **Secrets in env**: `env: { KEY: { from: "secret:..." } }` resolution
|
|
to `EnvironmentFile=`/`LoadCredential=` lands in v0.11-P03.
|
|
|
|
## See also
|
|
|
|
- [docs/cli.md](cli.md) — CLI reference
|
|
- [docs/jobspec.md](jobspec.md) — Jobspec reference (`service:`, `health:`, `ports:` blocks)
|
|
- [examples/full-stack/](../examples/full-stack/) — Full-stack example with ingress |