autumn-plugin-pingora 0.3.0

Pingora reverse proxy for Autumn: routes, load balancing, health checks, app fallback, metrics and graceful drain
Documentation

autumn-plugin-pingora

A Pingora reverse proxy for Autumn, in one line.

The proxy has its own port. It sends matched requests to upstream pools. It sends all other requests to your Autumn app.

use autumn_plugin_pingora::{PingoraPlugin, Route};

#[autumn_web::main]
async fn main() {
    autumn_web::app()
        .routes(autumn_web::routes![index])
        .plugin(PingoraPlugin::new().route(
            Route::new("billing")
                .path_prefix("/billing")
                .upstreams(["10.0.0.7:8080", "10.0.0.8:8080"])
                .strip_prefix(true),
        ))
        .run()
        .await;
}

Run the example: cargo run --example gateway.

Features

  • Routes by host (api.example.com, *.example.com) and path prefix (on a segment boundary). The host decides first, as in nginx. Then the longest prefix wins.
  • Fallback to the Autumn app, or 404.
  • Round-robin or consistent-hash (client IP) load balancing.
  • TCP health checks. No healthy upstream gives 503.
  • Connect retries on another upstream. A failed connect gives 502. An upstream timeout gives 504.
  • X-Forwarded-For, -Proto and -Host. The proxy replaces client values and removes other identity headers (X-Real-IP, …), except from peers in trusted_proxies.
  • Paths with dot segments (also encoded, ..;, ..%2f) or backslashes get 400.
  • Body limit (413), connection limit, per-IP connection limit.
  • A pingora indicator in /actuator/health (readiness group).
  • pingora_proxy_* metrics in /actuator/prometheus.
  • Graceful drain on Autumn's shutdown signal.
  • autumn routes lists the proxied prefixes (method PROXY).

Configuration

[pingora] in autumn.toml. Profiles ([profile.<name>.pingora], autumn-<name>.toml) and AUTUMN_PINGORA__<KEY> env variables apply on top. autumn 0.9 turns on server.strict_config in prod, and a [profile.prod.pingora] table then stops the boot. Put prod values in autumn-prod.toml. Code (configure, route, bind) applies last. A bad value stops boot.

[pingora]
enabled = true
bind = ""                          # "": 127.0.0.1:8080 in dev/test, 0.0.0.0:8080 else
fallback = "app"                   # "app" or "none" (404)
trusted_proxies = []               # peers whose X-Forwarded-* values stay
connect_timeout_ms = 5000
read_timeout_ms = 60000            # upstream and client body; 0: off
write_timeout_ms = 60000           # upstream and client response; 0: off
max_retries = 1                    # connect retries on another upstream
health_check_interval_ms = 5000    # 0: off
health_check_timeout_ms = 1000
max_request_body_bytes = 10485760  # 0: off
max_connections = 10000            # 0: off; at most 1000000
max_connections_per_ip = 0         # 0: off; keep 0 behind a load balancer
shutdown_grace_ms = 10000          # capped to fit server.shutdown_timeout_secs
metrics = true

[[pingora.routes]]
name = "billing"                   # unique; used in metrics
host = ""                          # "" (any), "api.example.com" or "*.example.com"
path_prefix = "/billing"
upstreams = ["10.0.0.7:8080", "billing.internal:8080"]
strip_prefix = true
upstream_host = ""                 # "": keep the client Host
selection = "round_robin"          # or "consistent"

AUTUMN_PINGORA__ROUTES replaces all routes with a TOML array:

AUTUMN_PINGORA__ROUTES='[{ name = "api", path_prefix = "/api", upstreams = ["10.0.0.9:80"] }]'

Metrics

Name Type Labels
pingora_proxy_up gauge —
pingora_proxy_connections_active gauge —
pingora_proxy_upstreams gauge route (one per resolved address)
pingora_proxy_upstreams_healthy gauge route
pingora_proxy_requests_total counter route, status (2xx, …, error)
pingora_proxy_upstream_errors_total counter route

route is a route name, fallback (the Autumn app) or unmatched (no route, or a path that the proxy rejects). Pingora rejects malformed requests before routing. They are not counted.

Behind the proxy

With fallback = "app", the app sees the proxy (127.0.0.1) as the client. Tell Autumn to trust it:

[security.trusted_proxies]
trust_forwarded_headers = true
ranges = ["127.0.0.1/32"]

The plugin warns at boot when this is missing. Do not confuse this with [pingora] trusted_proxies: that list controls which peers the proxy itself trusts.

Limits of v0.1

  • No TLS. Put the proxy behind a TLS terminator, or on a private network. fallback = "app" with [server.tls] stops boot.
  • HTTP/1.1 only on the client side. No h2c.
  • No cache, rate limits or custom filters.
  • Hostnames resolve once, at boot.
  • Slow clients: Pingora waits up to 60 s for each read of the request header. Use max_connections_per_ip, or a load balancer with its own limits, against slowloris clients.
  • Linux and macOS only (Pingora).

Design

See the plan (brainstorming, reverse brainstorming, six hats, AC) and the ADRs.

License

Apache-2.0.