ruvoy 0.3.0-aarch64-linux → 0.4.0-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +73 -7
- data/lib/ruvoy/4.0.0/libruvoy_fiber.so +0 -0
- data/lib/ruvoy/envoy_config.rb +56 -11
- data/lib/ruvoy/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 569b932c2661eaffd11fde7921044f4eadeb6e79dc4c951ed293c664d7b778bc
|
|
4
|
+
data.tar.gz: bcbe0a8668f8d396d9b64779077c23d914a7de5678bd56edc3e197bcf9012f0d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: faabfaa8fdfe5dcc4512613a340514ad68856535b78e419b9451a5afaa0fecd6b76e4e6f0b1e0e9712620c3f7e6c255eb5e068ffbc79b597f7ef2e8181534ad5
|
|
7
|
+
data.tar.gz: 9f3695b1f964180948a46529a3168942856e545486762f90b55361c64d1fb42e07172c613833a61cf64aa53150531988bb8ea67e8a3c2bc7c66a8c5d559eb75e
|
data/README.md
CHANGED
|
@@ -53,17 +53,20 @@ Usage: ruvoy [options] [config.ru]
|
|
|
53
53
|
--print-config Print the generated Envoy configuration and exit
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
To run it under an Envoy you configure yourself, `--print-config` prints
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
To run it under an Envoy you configure yourself, `--print-config` prints what
|
|
57
|
+
Ruvoy would have used: the `ruvoy` bootstrap extension, which names the rackup
|
|
58
|
+
(see [Lifecycle](#lifecycle)), and a listener whose rack filter joins it. The
|
|
59
|
+
filter's own config is an optional JSON object (see
|
|
59
60
|
[Envoy context and upstream calls](#envoy-context-and-upstream-calls)).
|
|
60
61
|
|
|
61
62
|
## Status
|
|
62
63
|
|
|
63
64
|
The data path is implemented and covered by suites that run against a real
|
|
64
65
|
Envoy in CI. Configuration reload has been driven through a running Envoy, and
|
|
65
|
-
a
|
|
66
|
-
threads
|
|
66
|
+
a three-hour soak of 5.7 million requests, 22,800 WebSocket sessions and 359
|
|
67
|
+
listener updates left live memory, file descriptors, threads and p99 latency
|
|
68
|
+
flat; the resident set still rose about 3% an hour, held by the allocator and
|
|
69
|
+
Ruby's heap rather than by anything live. Interfaces may change.
|
|
67
70
|
|
|
68
71
|
## Features
|
|
69
72
|
|
|
@@ -213,7 +216,6 @@ JSON object:
|
|
|
213
216
|
|
|
214
217
|
```json
|
|
215
218
|
{
|
|
216
|
-
"rackup": "/srv/app/config.ru",
|
|
217
219
|
"context": { "metadata_namespaces": ["acme.tenant"] },
|
|
218
220
|
"upstream": { "clusters": ["users"], "timeout_ms": 15000, "max_response_bytes": 1048576 }
|
|
219
221
|
}
|
|
@@ -250,7 +252,8 @@ What Envoy's module interface can report sets some limits:
|
|
|
250
252
|
|
|
251
253
|
### `ruvoy.upstream`
|
|
252
254
|
|
|
253
|
-
Calls a cluster Envoy was configured with and returns
|
|
255
|
+
Calls a cluster Envoy was configured with and returns the status, the headers
|
|
256
|
+
and the whole body as a String:
|
|
254
257
|
|
|
255
258
|
```ruby
|
|
256
259
|
status, headers, body = env["ruvoy.upstream"].call(
|
|
@@ -293,9 +296,72 @@ Envoy applies no flow control to it, so a reader that falls more than
|
|
|
293
296
|
With `protocol: "websocket"`, the response upgrades the client too and relays
|
|
294
297
|
both ways.
|
|
295
298
|
|
|
299
|
+
Calls and streams carry the request's trace context on: `traceparent`,
|
|
300
|
+
`tracestate`, `x-request-id` and the B3 headers, unless the call sets them.
|
|
301
|
+
|
|
296
302
|
[`examples/extensions`](examples/extensions) runs both against a stand-in
|
|
297
303
|
service, including two calls from one request overlapping.
|
|
298
304
|
|
|
305
|
+
## Lifecycle
|
|
306
|
+
|
|
307
|
+
The `ruvoy` bootstrap extension owns the Ruby runtime: it names the rackup and
|
|
308
|
+
the terms the runtime serves it on, and every listener's rack filter joins it.
|
|
309
|
+
A rack filter configured without it is refused.
|
|
310
|
+
|
|
311
|
+
```yaml
|
|
312
|
+
bootstrap_extensions:
|
|
313
|
+
- name: ruvoy
|
|
314
|
+
typed_config:
|
|
315
|
+
"@type": type.googleapis.com/envoy.extensions.bootstrap.dynamic_modules.v3.DynamicModuleBootstrapExtension
|
|
316
|
+
dynamic_module_config: { name: ruvoy_fiber, do_not_close: true }
|
|
317
|
+
extension_name: ruvoy
|
|
318
|
+
extension_config:
|
|
319
|
+
"@type": type.googleapis.com/google.protobuf.StringValue
|
|
320
|
+
value: '{"rackup": "/srv/app/config.ru"}'
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
| Setting | Default | |
|
|
324
|
+
|---|---|---|
|
|
325
|
+
| `rackup` | | The application. Required. |
|
|
326
|
+
| `max_inflight_requests` | 1024 | Requests in Ruby at once; one more is refused with 503. |
|
|
327
|
+
| `shutdown_timeout_ms` | 10000 | How long shutdown waits for fibers still running. |
|
|
328
|
+
| `diagnostics` | false | Adds the `x-ruvoy-*` diagnostic headers to responses. |
|
|
329
|
+
|
|
330
|
+
- Envoy becomes ready only once the application has loaded and the hooks
|
|
331
|
+
registered with `Ruvoy::Lifecycle.before_ready { }` have run. An application
|
|
332
|
+
that fails to load stops Envoy.
|
|
333
|
+
- `Ruvoy::Lifecycle.on_drain { }` runs when Envoy begins draining, while
|
|
334
|
+
requests are still served: close persistent connections, stop background
|
|
335
|
+
work.
|
|
336
|
+
- Envoy's shutdown stops the runtime: fibers still running are cancelled and
|
|
337
|
+
given `shutdown_timeout_ms` to finish.
|
|
338
|
+
- The admin interface reaches Ruby: `/ruvoy` (overview), `/ruvoy/gc` (`GET`
|
|
339
|
+
for `GC.stat`, `POST` to collect, `?compact=1` to compact too),
|
|
340
|
+
`/ruvoy/fibers` (live fibers with backtraces) and `/ruvoy/config`. Ruby
|
|
341
|
+
answers from its own thread; what it does not answer within two seconds gets
|
|
342
|
+
504.
|
|
343
|
+
- `Ruvoy::Lifecycle.settings` holds the settings above.
|
|
344
|
+
- Envoy's allocator keeps freed memory unless its `memory_allocator_manager`
|
|
345
|
+
sets `bytes_to_release`; without it the resident set only grows. The
|
|
346
|
+
generated configuration returns 1 MiB a second, and a hand-written one
|
|
347
|
+
should set it too.
|
|
348
|
+
|
|
349
|
+
## Statistics
|
|
350
|
+
|
|
351
|
+
Ruvoy reports through Envoy's statistics, so the numbers reach whatever sink
|
|
352
|
+
Envoy has, the admin interface's `/stats/prometheus` among them.
|
|
353
|
+
|
|
354
|
+
- `requests_total`, `rejected_total{reason}`, `responses_total{outcome}` and
|
|
355
|
+
`duration_ms`: requests, and how those that reached the application ended.
|
|
356
|
+
- `inflight_requests`, `reactor_idle_ms` and `resident_bytes`: the bootstrap
|
|
357
|
+
extension reads them once a second on the main thread, so the request path
|
|
358
|
+
never publishes anything and an idle proxy reports an idle runtime.
|
|
359
|
+
- `upstream_calls_total{cluster, outcome}`: calls by how the application saw
|
|
360
|
+
them end, `completed`, `refused`, `timed_out`, `overflow`, `too_large`,
|
|
361
|
+
`reset` or `abandoned`. `upstream_duration_ms{cluster}` times a `call` to its
|
|
362
|
+
whole response and a `stream` to its head. Status codes and Envoy's own
|
|
363
|
+
timeouts are in the cluster's statistics.
|
|
364
|
+
|
|
299
365
|
## Scaling
|
|
300
366
|
|
|
301
367
|
Each Ruvoy process runs exactly one CRuby VM, so Ruby work in one process is
|
|
Binary file
|
data/lib/ruvoy/envoy_config.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
3
4
|
require "yaml"
|
|
4
5
|
|
|
5
6
|
module Ruvoy
|
|
@@ -12,11 +13,17 @@ module Ruvoy
|
|
|
12
13
|
STREAM_IDLE_TIMEOUT = "15s"
|
|
13
14
|
REQUEST_TIMEOUT = "30s"
|
|
14
15
|
CONNECTION_BUFFER_LIMIT_BYTES = 4_194_304
|
|
16
|
+
EXTENSION_NAME = "ruvoy"
|
|
17
|
+
STRING_VALUE = "type.googleapis.com/google.protobuf.StringValue"
|
|
15
18
|
|
|
16
19
|
module_function
|
|
17
20
|
|
|
18
21
|
def build(rackup:, address:, port:, admin_port: nil)
|
|
19
|
-
config = {
|
|
22
|
+
config = {
|
|
23
|
+
"bootstrap_extensions" => [ bootstrap_extension(rackup: rackup) ],
|
|
24
|
+
"memory_allocator_manager" => memory_allocator_manager,
|
|
25
|
+
"static_resources" => { "listeners" => [ listener(address, port) ] }
|
|
26
|
+
}
|
|
20
27
|
config["admin"] = admin(address, admin_port) if admin_port
|
|
21
28
|
config
|
|
22
29
|
end
|
|
@@ -25,16 +32,42 @@ module Ruvoy
|
|
|
25
32
|
YAML.dump(build(**options))
|
|
26
33
|
end
|
|
27
34
|
|
|
28
|
-
|
|
35
|
+
# The extension that starts the Ruby runtime and names the application.
|
|
36
|
+
# Every listener's rack filter joins the runtime it started, so there is
|
|
37
|
+
# exactly one per Envoy.
|
|
38
|
+
def bootstrap_extension(rackup:, **settings)
|
|
39
|
+
{
|
|
40
|
+
"name" => EXTENSION_NAME,
|
|
41
|
+
"typed_config" => {
|
|
42
|
+
"@type" => "type.googleapis.com/envoy.extensions.bootstrap.dynamic_modules.v3." \
|
|
43
|
+
"DynamicModuleBootstrapExtension",
|
|
44
|
+
"dynamic_module_config" => module_config,
|
|
45
|
+
"extension_name" => EXTENSION_NAME,
|
|
46
|
+
"extension_config" => {
|
|
47
|
+
"@type" => STRING_VALUE,
|
|
48
|
+
"value" => JSON.generate({ "rackup" => rackup }.merge(settings.transform_keys(&:to_s)))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The same as a top-level YAML section, to head a configuration written
|
|
55
|
+
# by hand.
|
|
56
|
+
def bootstrap_yaml(rackup:, **settings)
|
|
57
|
+
YAML.dump("bootstrap_extensions" => [ bootstrap_extension(rackup: rackup, **settings) ])
|
|
58
|
+
.delete_prefix("---\n")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def listener(address, port)
|
|
29
62
|
{
|
|
30
63
|
"name" => "ruvoy",
|
|
31
64
|
"per_connection_buffer_limit_bytes" => CONNECTION_BUFFER_LIMIT_BYTES,
|
|
32
65
|
"address" => { "socket_address" => { "address" => address, "port_value" => port } },
|
|
33
|
-
"filter_chains" => [ { "filters" => [ connection_manager
|
|
66
|
+
"filter_chains" => [ { "filters" => [ connection_manager ] } ]
|
|
34
67
|
}
|
|
35
68
|
end
|
|
36
69
|
|
|
37
|
-
def connection_manager
|
|
70
|
+
def connection_manager
|
|
38
71
|
{
|
|
39
72
|
"name" => "envoy.filters.network.http_connection_manager",
|
|
40
73
|
"typed_config" => {
|
|
@@ -48,29 +81,41 @@ module Ruvoy
|
|
|
48
81
|
"name" => "local_route",
|
|
49
82
|
"virtual_hosts" => [ { "name" => "local_service", "domains" => [ "*" ] } ]
|
|
50
83
|
},
|
|
51
|
-
"http_filters" => [ rack_filter
|
|
84
|
+
"http_filters" => [ rack_filter ]
|
|
52
85
|
}
|
|
53
86
|
}
|
|
54
87
|
end
|
|
55
88
|
|
|
56
89
|
# Terminal: the Rack application answers the request, so no router follows.
|
|
57
|
-
|
|
90
|
+
# The filter has nothing of its own to configure here; the application is
|
|
91
|
+
# the bootstrap extension's.
|
|
92
|
+
def rack_filter
|
|
58
93
|
{
|
|
59
94
|
"name" => "envoy.extensions.filters.http.dynamic_modules",
|
|
60
95
|
"typed_config" => {
|
|
61
96
|
"@type" => "type.googleapis.com/envoy.extensions.filters.http." \
|
|
62
97
|
"dynamic_modules.v3.DynamicModuleFilter",
|
|
63
|
-
"dynamic_module_config" =>
|
|
98
|
+
"dynamic_module_config" => module_config,
|
|
64
99
|
"filter_name" => "fiber_rack",
|
|
65
100
|
"terminal_filter" => true,
|
|
66
|
-
"filter_config" => {
|
|
67
|
-
"@type" => "type.googleapis.com/google.protobuf.StringValue",
|
|
68
|
-
"value" => rackup
|
|
69
|
-
}
|
|
101
|
+
"filter_config" => { "@type" => STRING_VALUE, "value" => "{}" }
|
|
70
102
|
}
|
|
71
103
|
}
|
|
72
104
|
end
|
|
73
105
|
|
|
106
|
+
# Envoy's allocator keeps what Ruby and Envoy free unless told to return
|
|
107
|
+
# it, so the resident set would only ever grow. Its own background thread
|
|
108
|
+
# returns a megabyte a second, more than a busy process frees.
|
|
109
|
+
def memory_allocator_manager
|
|
110
|
+
{ "bytes_to_release" => 1_048_576, "memory_release_interval" => "1s" }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# A fresh hash each time: one object in two places would come out of the
|
|
114
|
+
# YAML dumper as an anchor and an alias.
|
|
115
|
+
def module_config
|
|
116
|
+
{ "name" => "ruvoy_fiber", "do_not_close" => true }
|
|
117
|
+
end
|
|
118
|
+
|
|
74
119
|
def admin(address, port)
|
|
75
120
|
{
|
|
76
121
|
"address" => { "socket_address" => { "address" => address, "port_value" => port } }
|
data/lib/ruvoy/version.rb
CHANGED