Files
orca/docs/ingress.md
T
Jon Chery 289e5cf6e1 docs(P02): CLI reference + jobspec reference + ingress guide
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---
2026-08-05 20:57:05 +00:00

6.7 KiB

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:

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:

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:

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:

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:

health:
  check_type: http
  interval: 5s
  timeout: 1s
  unhealthy_threshold: 2

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/ 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