From 57ba289f05459705dd76e3b951949f9d10e24fac Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 7 Aug 2026 21:19:58 +1200 Subject: [PATCH] Update Envoy cluster discovery configuration. --- context/cluster-deployment.md | 64 ++++++++++++++++------------- context/index.yaml | 3 +- examples/cluster/envoy.yaml | 13 +++--- examples/cluster/gems.rb | 2 +- guides/cluster-deployment/readme.md | 25 ++++++----- readme.md | 10 ++--- releases.md | 4 ++ 7 files changed, 65 insertions(+), 56 deletions(-) diff --git a/context/cluster-deployment.md b/context/cluster-deployment.md index d085aad..6952c24 100644 --- a/context/cluster-deployment.md +++ b/context/cluster-deployment.md @@ -1,6 +1,6 @@ # Dynamic Clusters with Envoy -This guide explains how to run Falcon workers with independently bound endpoints and publish them dynamically to Envoy using xDS. +This guide explains how to run Falcon workers with independently bound endpoints, publish them dynamically using xDS, and balance requests according to their current load using ORCA. ## When to Use a Cluster @@ -17,7 +17,9 @@ A regular {ruby Falcon::Service::Server} binds one listener and shares it with e Each cluster worker can bind to `localhost` with port `0`, allowing the operating system to assign an available port. Falcon describes the bound resource with a {ruby Falcon::Listener}, including its name, scheme, supported protocols, and concrete addresses. -The worker registers that listener with `async-service-supervisor-envoy`. The supervisor publishes the current workers through an xDS control plane, and Envoy uses Endpoint Discovery Service (EDS) updates to maintain the upstream cluster. +The worker registers that listener with `async-service-supervisor-envoy`. The supervisor publishes the current workers and load-balancing policy through an xDS control plane. Envoy uses Cluster Discovery Service (CDS) and Endpoint Discovery Service (EDS) updates to maintain the upstream cluster. + +The supervisor also samples each worker's CPU utilization and request counter. It exposes those measurements using out-of-band Open Request Cost Aggregation (ORCA), which lets Envoy's client-side weighted-round-robin policy direct more requests to workers with more available capacity. This avoids coupling connection acceptance to a process-local token limiter while still responding to CPU-heavy work. Requests arrive at Envoy's stable listener. Envoy selects one of the discovered worker endpoints and forwards the request to it: @@ -29,14 +31,15 @@ flowchart LR Envoy[Envoy] subgraph Falcon[Falcon container] - Supervisor[Supervisor and xDS control plane] + Supervisor[Supervisor, xDS, and ORCA] Worker1[Falcon worker 1] Worker2[Falcon worker 2] end Worker1 -.->|Register endpoint| Supervisor Worker2 -.->|Register endpoint| Supervisor - Supervisor -.->|EDS over ADS on port 18000| Envoy + Supervisor -.->|Dedicated CDS and EDS streams| Envoy + Supervisor -.->|Per-worker ORCA reports| Envoy Envoy -->|HTTP on dynamic port| Worker1 Envoy -->|HTTP on dynamic port| Worker2 end @@ -48,7 +51,7 @@ Add Falcon and the Envoy supervisor integration to your `gems.rb`: ```ruby gem "falcon", "~> 0.56.0" -gem "async-service-supervisor-envoy", "~> 0.2" +gem "async-service-supervisor-envoy", "~> 0.5" ``` Define a Falcon cluster service and an accompanying supervisor in `falcon.rb`: @@ -89,16 +92,21 @@ service "supervisor" do include Async::Service::Supervisor::Environment monitors do + utilization_monitor = Async::Service::Supervisor::UtilizationMonitor.new + [ + utilization_monitor, Async::Service::Supervisor::Envoy::Monitor.new( - bind: "http://127.0.0.1:18000", + bind: "http://[::]:18000", + orca: true, + utilization_monitor: utilization_monitor, ), ] end end ``` -The Falcon service name becomes the listener name, so the corresponding Envoy EDS cluster uses `cluster` as its service name. Configure Envoy to receive aggregated discovery updates from the supervisor: +The Falcon service name becomes the listener name, so the corresponding Envoy cluster uses `cluster` as its service name. Configure Envoy to receive cluster and endpoint updates from the supervisor: ```yaml node: @@ -106,12 +114,14 @@ node: cluster: falcon-cluster dynamic_resources: - ads_config: - api_type: GRPC - transport_api_version: V3 - grpc_services: - - envoy_grpc: - cluster_name: xds_cluster + cds_config: + resource_api_version: V3 + api_config_source: + api_type: GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster static_resources: listeners: @@ -128,6 +138,7 @@ static_resources: stat_prefix: ingress_http route_config: name: local_route + validate_clusters: false virtual_hosts: - name: falcon domains: ["*"] @@ -142,16 +153,6 @@ static_resources: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router clusters: - - name: cluster - connect_timeout: 1s - type: EDS - lb_policy: ROUND_ROBIN - eds_cluster_config: - service_name: cluster - eds_config: - ads: {} - resource_api_version: V3 - - name: xds_cluster connect_timeout: 1s type: STATIC @@ -162,7 +163,7 @@ static_resources: - endpoint: address: socket_address: - address: 127.0.0.1 + address: "::1" port_value: 18000 typed_extension_protocol_options: envoy.extensions.upstreams.http.v3.HttpProtocolOptions: @@ -171,7 +172,7 @@ static_resources: http2_protocol_options: {} ``` -The `xds_cluster` connection uses HTTP/2 because ADS is served over gRPC. +The `xds_cluster` connection uses HTTP/2 because CDS and EDS are served over gRPC. The supervisor serves dedicated CDS and EDS streams together with ORCA on port `18000`; Envoy uses that as an alternative to each worker's HTTP port when opening ORCA streams. Envoy 1.39 or later is required for this alternative reporting-port configuration. ## Worker Registration @@ -179,19 +180,24 @@ When each worker starts: 1. Falcon binds the worker to an available loopback port. 2. The worker registers its concrete addresses and supported protocols with the supervisor. -3. The supervisor's Envoy monitor publishes the current worker endpoints as an EDS resource. -4. Envoy receives the resource over its Aggregated Discovery Service (ADS) connection and updates its upstream cluster. +3. The supervisor's Envoy monitor publishes the cluster policy and current worker endpoints as CDS and EDS resources. +4. Envoy receives the resources over dedicated CDS and EDS streams and updates its upstream cluster. +5. The supervisor samples worker CPU time and request totals, then streams the current load reports to Envoy using ORCA. + +The first processor and request samples establish baselines. Load-aware weights become available after the next sampling interval. If a report is temporarily unavailable, Envoy retains its normal policy fallback rather than making the worker unreachable. The listener preserves all addresses returned by the bound endpoint. This allows the same interface to describe IP sockets, Unix-domain sockets, and endpoints with additional addresses. ## Worker Restarts -If a worker exits, its supervisor connection closes and the monitor publishes an EDS update without that endpoint. Falcon restarts the worker, which binds a new available port and registers it. The monitor then publishes another update, and Envoy receives both changes over its existing ADS stream without polling or restarting. +If a worker exits, its supervisor connection closes and the monitor removes both its endpoint and ORCA report. Falcon restarts the worker, which binds a new available port and registers it. The monitor then publishes another update, and Envoy receives both changes over its existing EDS stream without polling or restarting. This lifecycle is important when ports are ephemeral or a directory may contain stale Unix-domain socket paths: consumers should use the supervisor's current endpoint state as the source of truth. ## Network Topology -Falcon and Envoy can run in the same network namespace, allowing workers to bind to loopback addresses while remaining reachable by Envoy. With Docker Compose, `network_mode: service:falcon` gives the Envoy service access to Falcon's network namespace, so `127.0.0.1` and `localhost` refer to the same loopback interface for both processes. +Falcon and Envoy can run in the same network namespace, allowing workers to bind to loopback addresses while remaining reachable by Envoy. With Docker Compose, `network_mode: service:falcon` gives the Envoy service access to Falcon's network namespace, so loopback addresses refer to the same interface for both processes. + +The configuration binds the supervisor endpoint to the IPv6 wildcard address because `localhost` worker endpoints use IPv6 in the container. Envoy connects to CDS and EDS through `::1`; for each ORCA stream it uses the worker's address with the configured supervisor port `18000`. The supervisor listener must therefore be reachable using the same address family as every published worker endpoint. Without a shared network namespace, Envoy cannot connect to worker endpoints bound to Falcon's loopback interface. In a different deployment topology, bind workers to an interface that Envoy can reach and apply the appropriate network access controls. diff --git a/context/index.yaml b/context/index.yaml index 9a34f0f..6f0277f 100644 --- a/context/index.yaml +++ b/context/index.yaml @@ -21,7 +21,8 @@ files: - path: cluster-deployment.md title: Dynamic Clusters with Envoy description: This guide explains how to run Falcon workers with independently bound - endpoints and publish them dynamically to Envoy using xDS. + endpoints, publish them dynamically using xDS, and balance requests according + to their current load using ORCA. - path: performance-tuning.md title: Performance Tuning description: This guide explains the performance characteristics of Falcon. diff --git a/examples/cluster/envoy.yaml b/examples/cluster/envoy.yaml index b42bb88..a73e1a1 100644 --- a/examples/cluster/envoy.yaml +++ b/examples/cluster/envoy.yaml @@ -3,15 +3,14 @@ node: cluster: falcon-cluster-example dynamic_resources: - ads_config: - api_type: GRPC - transport_api_version: V3 - grpc_services: - - envoy_grpc: - cluster_name: xds_cluster cds_config: - ads: {} resource_api_version: V3 + api_config_source: + api_type: GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster static_resources: listeners: diff --git a/examples/cluster/gems.rb b/examples/cluster/gems.rb index f7c2c8c..ea169b5 100644 --- a/examples/cluster/gems.rb +++ b/examples/cluster/gems.rb @@ -6,4 +6,4 @@ source "https://rubygems.org" gem "falcon", "~> 0.56.0" -gem "async-service-supervisor-envoy", "~> 0.3.0" +gem "async-service-supervisor-envoy", "~> 0.5.0" diff --git a/guides/cluster-deployment/readme.md b/guides/cluster-deployment/readme.md index 500f384..6952c24 100644 --- a/guides/cluster-deployment/readme.md +++ b/guides/cluster-deployment/readme.md @@ -38,7 +38,7 @@ flowchart LR Worker1 -.->|Register endpoint| Supervisor Worker2 -.->|Register endpoint| Supervisor - Supervisor -.->|CDS and EDS over ADS| Envoy + Supervisor -.->|Dedicated CDS and EDS streams| Envoy Supervisor -.->|Per-worker ORCA reports| Envoy Envoy -->|HTTP on dynamic port| Worker1 Envoy -->|HTTP on dynamic port| Worker2 @@ -51,7 +51,7 @@ Add Falcon and the Envoy supervisor integration to your `gems.rb`: ```ruby gem "falcon", "~> 0.56.0" -gem "async-service-supervisor-envoy", "~> 0.3" +gem "async-service-supervisor-envoy", "~> 0.5" ``` Define a Falcon cluster service and an accompanying supervisor in `falcon.rb`: @@ -114,15 +114,14 @@ node: cluster: falcon-cluster dynamic_resources: - ads_config: - api_type: GRPC - transport_api_version: V3 - grpc_services: - - envoy_grpc: - cluster_name: xds_cluster cds_config: - ads: {} resource_api_version: V3 + api_config_source: + api_type: GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster static_resources: listeners: @@ -173,7 +172,7 @@ static_resources: http2_protocol_options: {} ``` -The `xds_cluster` connection uses HTTP/2 because ADS is served over gRPC. The supervisor serves both ADS and ORCA on port `18000`; Envoy uses that as an alternative to each worker's HTTP port when opening ORCA streams. Envoy 1.39 or later is required for this alternative reporting-port configuration. +The `xds_cluster` connection uses HTTP/2 because CDS and EDS are served over gRPC. The supervisor serves dedicated CDS and EDS streams together with ORCA on port `18000`; Envoy uses that as an alternative to each worker's HTTP port when opening ORCA streams. Envoy 1.39 or later is required for this alternative reporting-port configuration. ## Worker Registration @@ -182,7 +181,7 @@ When each worker starts: 1. Falcon binds the worker to an available loopback port. 2. The worker registers its concrete addresses and supported protocols with the supervisor. 3. The supervisor's Envoy monitor publishes the cluster policy and current worker endpoints as CDS and EDS resources. -4. Envoy receives the resources over its Aggregated Discovery Service (ADS) connection and updates its upstream cluster. +4. Envoy receives the resources over dedicated CDS and EDS streams and updates its upstream cluster. 5. The supervisor samples worker CPU time and request totals, then streams the current load reports to Envoy using ORCA. The first processor and request samples establish baselines. Load-aware weights become available after the next sampling interval. If a report is temporarily unavailable, Envoy retains its normal policy fallback rather than making the worker unreachable. @@ -191,7 +190,7 @@ The listener preserves all addresses returned by the bound endpoint. This allows ## Worker Restarts -If a worker exits, its supervisor connection closes and the monitor removes both its endpoint and ORCA report. Falcon restarts the worker, which binds a new available port and registers it. The monitor then publishes another update, and Envoy receives both changes over its existing ADS stream without polling or restarting. +If a worker exits, its supervisor connection closes and the monitor removes both its endpoint and ORCA report. Falcon restarts the worker, which binds a new available port and registers it. The monitor then publishes another update, and Envoy receives both changes over its existing EDS stream without polling or restarting. This lifecycle is important when ports are ephemeral or a directory may contain stale Unix-domain socket paths: consumers should use the supervisor's current endpoint state as the source of truth. @@ -199,6 +198,6 @@ This lifecycle is important when ports are ephemeral or a directory may contain Falcon and Envoy can run in the same network namespace, allowing workers to bind to loopback addresses while remaining reachable by Envoy. With Docker Compose, `network_mode: service:falcon` gives the Envoy service access to Falcon's network namespace, so loopback addresses refer to the same interface for both processes. -The configuration binds the supervisor endpoint to the IPv6 wildcard address because `localhost` worker endpoints use IPv6 in the container. Envoy connects to ADS through `::1`; for each ORCA stream it uses the worker's address with the configured supervisor port `18000`. The supervisor listener must therefore be reachable using the same address family as every published worker endpoint. +The configuration binds the supervisor endpoint to the IPv6 wildcard address because `localhost` worker endpoints use IPv6 in the container. Envoy connects to CDS and EDS through `::1`; for each ORCA stream it uses the worker's address with the configured supervisor port `18000`. The supervisor listener must therefore be reachable using the same address family as every published worker endpoint. Without a shared network namespace, Envoy cannot connect to worker endpoints bound to Falcon's loopback interface. In a different deployment topology, bind workers to an interface that Envoy can reach and apply the appropriate network access controls. diff --git a/readme.md b/readme.md index d6be5db..ccc5b21 100644 --- a/readme.md +++ b/readme.md @@ -35,7 +35,7 @@ Please see the [project documentation](https://socketry.github.io/falcon/) for m - [Deployment](https://socketry.github.io/falcon/guides/deployment/index) - This guide explains how to deploy applications using the Falcon web server. It covers the recommended deployment methods, configuration options, and examples for different environments, including systemd and kubernetes. - - [Dynamic Clusters with Envoy](https://socketry.github.io/falcon/guides/cluster-deployment/index) - This guide explains how to run Falcon workers with independently bound endpoints and publish them dynamically to Envoy using xDS. + - [Dynamic Clusters with Envoy](https://socketry.github.io/falcon/guides/cluster-deployment/index) - This guide explains how to run Falcon workers with independently bound endpoints, publish them dynamically using xDS, and balance requests according to their current load using ORCA. - [Performance Tuning](https://socketry.github.io/falcon/guides/performance-tuning/index) - This guide explains the performance characteristics of Falcon. @@ -49,6 +49,10 @@ Please see the [project documentation](https://socketry.github.io/falcon/) for m Please see the [project releases](https://socketry.github.io/falcon/releases/index) for all releases. +### Unreleased + + - Update the Envoy cluster example to use dedicated CDS and EDS services from `async-service-supervisor-envoy` v0.5. + ### v0.56.0 - Add `Falcon::Environment::Cluster` and `Falcon::Service::Cluster` for running workers with independently bound endpoints. @@ -90,10 +94,6 @@ Please see the [project releases](https://socketry.github.io/falcon/releases/ind - Fix handling of old style supervisors from `Async::Container::Supervisor`. -### v0.54.0 - - - Introduce `Falcon::CompositeServer` for hosting multiple server instances in a single worker. - ## Contributing We welcome contributions to this project. diff --git a/releases.md b/releases.md index 5a2eb10..fcd17e9 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## Unreleased + + - Update the Envoy cluster example to use dedicated CDS and EDS services from `async-service-supervisor-envoy` v0.5. + ## v0.56.0 - Add `Falcon::Environment::Cluster` and `Falcon::Service::Cluster` for running workers with independently bound endpoints.