-
Notifications
You must be signed in to change notification settings - Fork 160
Stop Envoy reusing upstream connections to actors #549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,6 +66,12 @@ const ( | |
| RouteName = "substrate_routes" | ||
| ClusterName = "ate-cluster" | ||
| OtlpClusterName = "otel_collector_cluster" | ||
|
|
||
| // httpProtocolOptionsName is the well-known extension key Envoy looks for in | ||
| // a cluster's typed_extension_protocol_options. It must match the message's | ||
| // full proto type name exactly; a typo is silently ignored rather than | ||
| // rejected, so the options simply never take effect. | ||
| httpProtocolOptionsName = "envoy.extensions.upstreams.http.v3.HttpProtocolOptions" | ||
| ) | ||
|
|
||
| // XdsServer implements an aggregated discovery service server for dynamic Envoy router nodes. | ||
|
|
@@ -334,10 +340,46 @@ func (x *XdsServer) buildDynamicForwardProxyCluster() *clusterv3.Cluster { | |
| ClusterImplementationSpecifier: &dfpclusterv3.ClusterConfig_DnsCacheConfig{ | ||
| DnsCacheConfig: buildDnsCacheConfig(), | ||
| }, | ||
| // Envoy refuses a dynamic_forward_proxy cluster that carries | ||
| // HttpProtocolOptions unless auto_sni and auto_san_validation are on, or | ||
| // this flag is set. That guard exists because a DFP cluster forwards to | ||
| // whatever host the request names, so a TLS upstream must validate the | ||
| // certificate against that host. This cluster has no transport socket at | ||
| // all — it speaks plaintext HTTP to worker pod IPs inside the cluster — | ||
| // and the authority is an IP literal, which cannot produce a meaningful | ||
| // SNI or SAN match. Saying so is more honest than enabling two TLS knobs | ||
| // that would do nothing. | ||
| AllowInsecureClusterOptions: true, | ||
| } | ||
|
|
||
| clusterConfigAny, _ := anypb.New(dfpClusterConfig) | ||
|
|
||
| // Disable upstream connection reuse. | ||
| // | ||
| // Envoy pools HTTP/1.1 connections by destination address, which is safe | ||
| // when an address means one stable server. Here it does not. A worker pod | ||
| // keeps its IP for its whole lifetime, but the actor sandbox behind port 80 | ||
| // is destroyed on every Suspend and a different actor takes the slot. A | ||
| // pooled connection therefore belongs to an actor that may already be gone, | ||
| // and reusing it races the far end's close: the request goes into a dead | ||
| // socket and Envoy synthesizes a 503. Measured at ~40% of pings with 50 | ||
| // actors churning across 50 workers. | ||
| // | ||
| // One request per connection costs a TCP handshake — sub-millisecond | ||
| // in-cluster, against pings that take ~22ms — and removes the race outright | ||
| // rather than recovering from it after the fact. | ||
| httpOpts, _ := anypb.New(&httpv3.HttpProtocolOptions{ | ||
| CommonHttpProtocolOptions: &corev3.HttpProtocolOptions{ | ||
| MaxRequestsPerConnection: wrapperspb.UInt32(1), | ||
| }, | ||
| UpstreamProtocolOptions: &httpv3.HttpProtocolOptions_ExplicitHttpConfig_{ | ||
| ExplicitHttpConfig: &httpv3.HttpProtocolOptions_ExplicitHttpConfig{ | ||
| // HTTP/1.1 upstream, matching what actors serve on port 80. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we know what actors will be serving on port 80? Currently we are assuming HTTP/1.1 as a simplification. That may not always be true. Not familiar enough with envoy to know how limiting this configuration will be on future updates. Are we precluded from supporting streaming connections, proto servers, etc. in the future with this choice? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these are all defaults so nothing is locking us into http 1.1 |
||
| ProtocolConfig: &httpv3.HttpProtocolOptions_ExplicitHttpConfig_HttpProtocolOptions{}, | ||
| }, | ||
| }, | ||
| }) | ||
|
|
||
| return &clusterv3.Cluster{ | ||
| Name: "dynamic_forward_proxy_cluster", | ||
| LbPolicy: clusterv3.Cluster_CLUSTER_PROVIDED, | ||
|
|
@@ -347,6 +389,9 @@ func (x *XdsServer) buildDynamicForwardProxyCluster() *clusterv3.Cluster { | |
| TypedConfig: clusterConfigAny, | ||
| }, | ||
| }, | ||
| TypedExtensionProtocolOptions: map[string]*anypb.Any{ | ||
| httpProtocolOptionsName: httpOpts, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid citing specific latency/measurement references as those are coupled with infrastructure and may quickly go stale as project progresses. Noting the shape of the concern and that it has been observed should be enough to help with Chesterton's Fence in the future.