Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bfe5b1d
Add Falcon cluster service environment
samuel-williams-shopify Jun 26, 2026
ed8d01f
Fix cluster worker shutdown
samuel-williams-shopify Jul 20, 2026
82a23d4
Exercise cluster health checks
samuel-williams-shopify Jul 21, 2026
5e87351
Use container signal handling for cluster workers
samuel-williams-shopify Jul 21, 2026
1da949f
Expose cluster worker bindings
samuel-williams-shopify Jul 22, 2026
6443323
Preserve all cluster binding addresses
samuel-williams-shopify Jul 22, 2026
93c7e57
Add cluster Unix socket example
samuel-williams-shopify Jul 22, 2026
921b033
Describe cluster worker listeners
samuel-williams-shopify Jul 23, 2026
5952685
Expose cluster listener protocol names.
samuel-williams-shopify Jul 24, 2026
fdc06d3
Merge remote-tracking branch 'origin/main' into review/pr-356
samuel-williams-shopify Jul 24, 2026
44bd510
Move cluster change to unreleased section
samuel-williams-shopify Jul 24, 2026
269b1c3
Use ephemeral TCP ports in cluster example
samuel-williams-shopify Jul 24, 2026
e5ba892
Run cluster example behind Envoy
samuel-williams-shopify Jul 24, 2026
4c9445b
Share listener lifecycle across Falcon services
samuel-williams-shopify Jul 31, 2026
5389c19
Use positional listener argument
samuel-williams-shopify Jul 31, 2026
a8b444f
Close listeners directly
samuel-williams-shopify Jul 31, 2026
6000688
Use latest supervisor Envoy integration
samuel-williams-shopify Jul 31, 2026
8c99f68
Use Async HTTP cluster client
samuel-williams-shopify Jul 31, 2026
de2a202
Document dynamic Envoy clusters
samuel-williams-shopify Jul 31, 2026
11818db
Clarify cluster example reference
samuel-williams-shopify Jul 31, 2026
5a547cd
Make cluster guide self-contained
samuel-williams-shopify Jul 31, 2026
21f2d09
Remove trailing whitespace
samuel-williams-shopify Jul 31, 2026
9260900
Add inline cluster configuration
samuel-williams-shopify Jul 31, 2026
3c17a37
Isolate cluster Docker example.
samuel-williams-shopify Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions examples/cluster/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
ARG RUBY_VERSION=4.0
FROM ruby:${RUBY_VERSION}

WORKDIR /code

ENV BUNDLE_GEMFILE=/code/gems.rb

COPY . .

RUN bundle install

CMD ["bundle", "exec", "async-service", "falcon.rb"]
32 changes: 32 additions & 0 deletions examples/cluster/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2026, by Samuel Williams.

require "async/http/internet/instance"

url = ENV.fetch("ENVOY_URI", "http://127.0.0.1:10000")
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 20
workers = {}

Sync do
until workers.size == 2 || Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
begin
Async::HTTP::Internet.get(url) do |response|
if response.success?
worker_id = response.headers["x-worker-id"]
workers[worker_id] ||= response.read
end
end
rescue Errno::ECONNREFUSED, EOFError
# Envoy may still be connecting to the xDS control plane.
end

sleep(0.1) unless workers.size == 2
end
end

abort "Envoy did not route requests to both workers." unless workers.size == 2

workers.each_value{|body| puts(body)}
31 changes: 31 additions & 0 deletions examples/cluster/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
services:
falcon:
image: cluster-falcon
build:
context: .
environment:
CONSOLE_OUTPUT: XTerm
ports:
- "10000:10000"

envoy:
image: envoyproxy/envoy:v1.32-latest
command: ["envoy", "-c", "/etc/envoy/envoy.yaml", "--log-level", "info"]
network_mode: "service:falcon"
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml:ro
depends_on:
falcon:
condition: service_started

client:
image: cluster-falcon
command: ["bundle", "exec", "ruby", "client.rb"]
environment:
ENVOY_URI: http://127.0.0.1:10000
network_mode: "service:falcon"
depends_on:
envoy:
condition: service_started
profiles:
- client
68 changes: 68 additions & 0 deletions examples/cluster/envoy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
node:
id: falcon-cluster-example
cluster: falcon-cluster-example

dynamic_resources:
ads_config:
api_type: GRPC
transport_api_version: V3
grpc_services:
- envoy_grpc:
cluster_name: xds_cluster

static_resources:
listeners:
- name: listener_http
address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: falcon
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: cluster
http_filters:
- name: envoy.filters.http.router
typed_config:
"@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
load_assignment:
cluster_name: xds_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 18000
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options: {}
51 changes: 51 additions & 0 deletions examples/cluster/falcon.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env async-service
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2026, by Samuel Williams.

require "async/service/supervisor"
require "async/service/supervisor/envoy"
require "falcon/environment/cluster"

service "cluster" do
include Falcon::Environment::Cluster
include Async::Service::Supervisor::Envoy::Supervised

count 2

def url
"http://localhost:0"
end

middleware do
rack_application = proc do |_env|
worker_id = Process.pid.to_s
body = "Hello from worker #{worker_id}!\n"

[
200,
{
"content-type" => "text/plain",
"content-length" => body.bytesize.to_s,
"x-worker-id" => worker_id,
},
[body],
]
end

Falcon::Server.middleware(rack_application, cache: false)
end
end

service "supervisor" do
include Async::Service::Supervisor::Environment

monitors do
[
Async::Service::Supervisor::Envoy::Monitor.new(
bind: "http://127.0.0.1:18000",
),
]
end
end
9 changes: 9 additions & 0 deletions examples/cluster/gems.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2026, by Samuel Williams.

source "https://rubygems.org"

gem "falcon", "~> 0.56.0"
gem "async-service-supervisor-envoy", "~> 0.2.0"
33 changes: 33 additions & 0 deletions examples/cluster/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Cluster with Envoy

This example runs a two-worker Falcon cluster behind Envoy using Docker Compose. Falcon publishes each worker's dynamically bound endpoint to Envoy through the supervisor's xDS control plane.

See the [Dynamic Clusters with Envoy](../../guides/cluster-deployment/readme.md) guide for the architecture, endpoint registration lifecycle, and deployment considerations.

## Requirements

- Docker with Compose support.

## Usage

From this directory, build and start Falcon and Envoy:

```shell
$ docker compose up --build --detach
```

Run the client through Compose:

```shell
$ docker compose run --rm client
Hello from worker 12!
Hello from worker 13!
```

The client waits for Envoy and confirms that requests reach both workers.

Stop and remove the containers:

```shell
$ docker compose down
```
2 changes: 1 addition & 1 deletion falcon.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Gem::Specification.new do |spec|

spec.add_dependency "async"
spec.add_dependency "async-container", "~> 0.20"
spec.add_dependency "async-http", "~> 0.75"
spec.add_dependency "async-http", "~> 0.97"
spec.add_dependency "async-http-cache", "~> 0.4"
spec.add_dependency "async-service", "~> 0.19"
spec.add_dependency "async-utilization", "~> 0.3"
Expand Down
2 changes: 1 addition & 1 deletion gems.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

# gem "fiber-profiler"

gem "async-service-supervisor"
gem "async-service-supervisor", "~> 0.18"

group :maintenance, optional: true do
gem "bake-modernize"
Expand Down
Loading
Loading