Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Quick start

Two paths. The first needs no cluster and takes about a minute; the second is the real thing.

Without a cluster, in 60 seconds

--static-routes swaps the API server for a YAML file and changes nothing else about the serving path, which makes it the fastest way to look at the data plane on its own.

Start two throwaway upstreams that say which one they are:

for u in a b; do
    mkdir -p /tmp/ramjet-$u/api /tmp/ramjet-$u/healthz
    echo "upstream-$u /"        > /tmp/ramjet-$u/index.html
    echo "upstream-$u /api"     > /tmp/ramjet-$u/api/index.html
    echo "upstream-$u /healthz" > /tmp/ramjet-$u/healthz/index.html
done
(cd /tmp/ramjet-a && python3 -m http.server 9001) &
(cd /tmp/ramjet-b && python3 -m http.server 9002) &

Then run the daemon against the example route table shipped in the repository:

cargo run -p ramjet-ingressd -- \
    --static-routes crates/ramjet-ingressd/examples/dev-routes.yaml
ramjet-ingressd 0.1.0 — 5 backend(s), 6 endpoint(s), 5 route(s), 0 certificate(s), default backend set
  config   crates/ramjet-ingressd/examples/dev-routes.yaml
  http     0.0.0.0:8080
  https    disabled
  http3    disabled
  admin    0.0.0.0:10254
  probes   http://0.0.0.0:10254/healthz  http://0.0.0.0:10254/readyz  http://0.0.0.0:10254/metrics
  admin    http://0.0.0.0:10254/admin/generations  http://0.0.0.0:10254/admin/routes
 INFO audit: 5 routes added, 3 hosts added, 1 mirror added, default backend now fallback (gen 0→0)

https is disabled because this file declares no certificates and no explicit --https was given. The audit line is the same record every publish gets in Kubernetes mode — dev mode has exactly one generation and nothing is special-cased for it.

And drive it:

curl -H 'Host: shop.example.com' http://127.0.0.1:8080/          # upstream-a
curl -H 'Host: shop.example.com' http://127.0.0.1:8080/api/      # leastConn
curl -H 'Host: sub.example.com'  http://127.0.0.1:8080/          # wildcard
curl -H 'Host: anything.else'    http://127.0.0.1:8080/          # default backend

# `always` forces the canary regardless of the weight; `never` keeps it away.
curl -H 'Host: shop.example.com' -H 'x-canary: always' \
     http://127.0.0.1:8080/api/                                  # upstream-b

curl http://127.0.0.1:10254/readyz
curl http://127.0.0.1:10254/metrics

Ctrl-C (or SIGTERM) drains in-flight requests and exits.

Listeners default to :8080 plaintext, :8443 TLS, and :10254 admin. In dev mode, without an explicit --https or --no-https, the TLS listener is skipped when the configuration declares no certificates — a listener that fails every handshake is not a useful default.

The route file

crates/ramjet-ingressd/examples/dev-routes.yaml is annotated end to end. The shape:

# Answers any request that matches no rule at all. Without this, those are 404s.
defaultBackend: fallback

backends:
  - name: web
    policy: roundRobin           # roundRobin | leastConn | random
    endpoints:
      - 127.0.0.1:9001           # short form means weight 1

  - name: api
    policy: leastConn
    endpoints:
      - 127.0.0.1:9001
      - address: 127.0.0.1:9002  # long form, for a bigger pod
        weight: 2                # or for draining one with `weight: 0`

routes:
  - host: shop.example.com
    path: /healthz
    pathType: Exact              # Exact | Prefix | ImplementationSpecific
    backend: web

  - host: shop.example.com
    path: /api
    pathType: Prefix
    backend: api
    canary:
      backend: api-next
      weight: 20
      header: x-canary

  - host: "*.example.com"        # replaces exactly one label
    path: /
    pathType: Prefix
    backend: web

  - path: /status                # no host: every name not claimed above
    pathType: Prefix
    backend: web

# tls:
#   - host: shop.example.com
#     cert: /tmp/dev-cert.pem
#     key: /tmp/dev-key.pem

This is not a production configuration format, and nothing else in the tree parses YAML. The Kubernetes path builds tables from API objects directly; it does not render configuration and read it back, which is exactly the round trip that makes ingress-nginx’s behaviour hard to predict from its inputs.

The two modes are mutually exclusive by nature — a file and an API server are two writers for one route table, and letting both write would make the winner a race.

On a cluster, with Helm

helm install ramjet deploy/chart/ramjet-ingress \
  --namespace ramjet-system --create-namespace

That is a hostNetwork DaemonSet serving :80 and :443 on every node — the shape that works on a cluster with nothing underneath it. It also installs a ServiceAccount, a ClusterRole and binding, a ClusterIP Service, a separate ClusterIP Service for the admin port, and an IngressClass named ramjet whose controller is ramjet.dev/ingress. On a cloud, add the preset for your provider (see Deployment), which goes back to a Deployment on 8080/8443 behind a LoadBalancer Service.

Point workloads at it with ingressClassName: ramjet:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop
spec:
  ingressClassName: ramjet
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 80

Or set ingressClass.isDefaultClass=true to catch Ingresses that name no class at all.

Deployment has a values preset and a rendered, Helm-free manifest for each supported provider, and answers the question that decides most of that configuration: where the client’s IP address comes from.

Before you scale it

replicas: 1 is hard-coded in the chart, and there is no values entry to find at 3am. There is no leader election yet: status writeback reads a Service’s address and server-side-applies it to every managed Ingress, and a second replica would do the same work against the same objects with the same field manager on its own schedule. Scale by making the one replica bigger, or use --no-status-update if you must run more. See Limitations.

Readiness, before you debug a slow rollout

/readyz returns 503 until a route table has actually been compiled from the API server. A replica that has finished starting but not finished its first list is deliberately kept out of the Service, because an empty table would 404 everything sent to it. /healthz, which the liveness probe uses, answers as soon as the process does.

Watching it live

The admin port reports counters, and the question you usually have is about rates. ramjet-top polls /admin/routes, /admin/generations and /metrics, differences the counters, and draws them.

cargo run -p ramjet-top                 # the local admin port
ramjet-top 10.0.0.5:10254               # somewhere else
ramjet-top --once                       # one aligned table, for scripts and CI

See Observability for the keybindings and what the numbers mean.