Skip to content

Commit 8e32174

Browse files
linmoskoguydc
andauthored
feat(xds): raise xDS gRPC server default receive limit and make it configurable (#9564)
* feat(xds): add configurable max receive message size for xDS gRPC server Signed-off-by: Lin Moskovitch <lin.moskovitch@sap.com> * update release notes Signed-off-by: Lin Moskovitch <lin.moskovitch@sap.com> * test(xds): add coverage for MaxRecvMsgSize runner option Signed-off-by: Lin Moskovitch <lin.moskovitch@sap.com> * lint Signed-off-by: Lin Moskovitch <lin.moskovitch@sap.com> * refactor: rename MaxRecvMsgSize to MaxReceiveMessageSize for consistency Signed-off-by: Lin Moskovitch <lin.moskovitch@sap.com> * docs: improve MaxReceiveMessageSize comment and release note Signed-off-by: Lin Moskovitch <lin.moskovitch@sap.com> * feat(xds): set default max receive message size to 32MiB Signed-off-by: Lin Moskovitch <lin.moskovitch@sap.com> --------- Signed-off-by: Lin Moskovitch <lin.moskovitch@sap.com> Co-authored-by: Guy Daich <guy.daich@sap.com>
1 parent bb7cdd1 commit 8e32174

8 files changed

Lines changed: 83 additions & 0 deletions

File tree

api/v1alpha1/envoygateway_types.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,23 @@ type XDSServer struct {
219219
//
220220
// +optional
221221
MaxConnectionAgeGrace *gwapiv1.Duration `json:"maxConnectionAgeGrace,omitempty"`
222+
223+
// MaxReceiveMessageSize defines the maximum size of a single xDS message that the xDS gRPC
224+
// server will accept from an Envoy proxy.
225+
//
226+
// Envoy's requests grow with the number of resources it holds: on every stream (re)connect,
227+
// the first delta xDS request for each resource type echoes back the name and version of
228+
// every resource the proxy currently has. At a large enough scale this exceeds the 4MiB
229+
// default, and the stream fails immediately with "received message larger than max", leaving
230+
// the proxy stuck on its last known-good configuration.
231+
//
232+
// Note this limit applies only to what Envoy Gateway receives; the configuration it sends to
233+
// Envoy is not bounded by it.
234+
//
235+
// If unspecified, defaults to 32MiB.
236+
//
237+
// +optional
238+
MaxReceiveMessageSize *resource.Quantity `json:"maxReceiveMessageSize,omitempty"`
222239
}
223240

224241
// LeaderElection defines the desired leader election settings.

api/v1alpha1/validation/envoygateway_validate.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,13 @@ func validateEnvoyGatewayXDSServer(xdsServer *egv1a1.XDSServer) error {
334334
}
335335
}
336336

337+
if xdsServer.MaxReceiveMessageSize != nil {
338+
v, ok := xdsServer.MaxReceiveMessageSize.AsInt64()
339+
if !ok || v <= 0 {
340+
return fmt.Errorf("xdsServer.maxReceiveMessageSize must be greater than zero")
341+
}
342+
}
343+
337344
return nil
338345
}
339346

api/v1alpha1/validation/envoygateway_validate_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/stretchr/testify/assert"
1212
"github.com/stretchr/testify/require"
1313
corev1 "k8s.io/api/core/v1"
14+
"k8s.io/apimachinery/pkg/api/resource"
1415
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1516
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
1617

@@ -1112,6 +1113,18 @@ func TestValidateEnvoyGatewayXDSServer(t *testing.T) {
11121113
x := &egv1a1.XDSServer{MaxConnectionAgeGrace: &age}
11131114
require.Error(t, validateEnvoyGatewayXDSServer(x))
11141115
})
1116+
1117+
t.Run("valid maxReceiveMessageSize", func(t *testing.T) {
1118+
size := resource.MustParse("100Mi")
1119+
x := &egv1a1.XDSServer{MaxReceiveMessageSize: &size}
1120+
require.NoError(t, validateEnvoyGatewayXDSServer(x))
1121+
})
1122+
1123+
t.Run("invalid zero maxReceiveMessageSize", func(t *testing.T) {
1124+
size := resource.MustParse("0")
1125+
x := &egv1a1.XDSServer{MaxReceiveMessageSize: &size}
1126+
require.Error(t, validateEnvoyGatewayXDSServer(x))
1127+
})
11151128
}
11161129

11171130
func TestDefaultEnvoyGatewayLoggingLevel(t *testing.T) {

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/xds/runner/runner.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,17 @@ func (r *Runner) Start(ctx context.Context) error {
172172
PermitWithoutStream: true,
173173
}
174174

175+
// Default to 32MiB
176+
maxReceiveMessageSize := int64(32 * 1024 * 1024)
177+
if r.EnvoyGateway.XDSServer != nil && r.EnvoyGateway.XDSServer.MaxReceiveMessageSize != nil {
178+
maxReceiveMessageSize, _ = r.EnvoyGateway.XDSServer.MaxReceiveMessageSize.AsInt64()
179+
}
175180
baseKeepaliveOptions := []grpc.ServerOption{
176181
grpc.KeepaliveEnforcementPolicy(enforcementPolicy),
177182
grpc.KeepaliveParams(keepaliveParams),
183+
grpc.MaxRecvMsgSize(int(maxReceiveMessageSize)),
178184
}
185+
r.Logger.Info("configured gRPC max receive message size", "maxReceiveMessageSize", maxReceiveMessageSize)
179186

180187
grpcOpts := append([]grpc.ServerOption{}, baseKeepaliveOptions...)
181188
grpcOpts = append(grpcOpts, grpc.Creds(credentials.NewTLS(tlsConfig)))

internal/xds/runner/runner_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"go.opentelemetry.io/otel/trace"
2727
"google.golang.org/grpc"
2828
"google.golang.org/grpc/credentials"
29+
"k8s.io/apimachinery/pkg/api/resource"
2930
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3031

3132
egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
@@ -339,6 +340,36 @@ func TestRunner(t *testing.T) {
339340
}, time.Second*5, time.Millisecond*50)
340341
}
341342

343+
func TestRunner_withMaxReceiveMessageSize(t *testing.T) {
344+
caFile, certFile, keyFile, cleanup := setupTLSCerts(t)
345+
defer cleanup()
346+
347+
xdsIR := new(message.XdsIR)
348+
pResource := new(message.ProviderResources)
349+
cfg, err := config.New(os.Stdout, os.Stderr)
350+
require.NoError(t, err)
351+
352+
size := resource.MustParse("100Mi")
353+
cfg.EnvoyGateway.XDSServer = &egv1a1.XDSServer{
354+
MaxReceiveMessageSize: &size,
355+
}
356+
357+
r := New(&Config{
358+
Server: *cfg,
359+
ProviderResources: pResource,
360+
XdsIR: xdsIR,
361+
TLSCertPath: certFile,
362+
TLSKeyPath: keyFile,
363+
TLSCaPath: caFile,
364+
})
365+
366+
ctx, cancel := context.WithCancel(newTestTraceContext())
367+
defer cancel()
368+
369+
err = r.Start(ctx)
370+
require.NoError(t, err)
371+
}
372+
342373
func TestRunner_withExtensionManager_FailOpen(t *testing.T) {
343374
// Setup TLS certificates
344375
caFile, certFile, keyFile, cleanup := setupTLSCerts(t)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Added `xdsServer.maxReceiveMessageSize` to the EnvoyGateway API and raised the xDS gRPC server's default receive limit from 4MiB to 32MiB. At large resource counts, an Envoy proxy's delta xDS request on stream reconnect can exceed 4MiB, which previously broke the stream with "received message larger than max" errors and left the proxy on its last known-good configuration.
2+

site/content/en/latest/api/extension_types.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6603,6 +6603,7 @@ _Appears in:_
66036603
| --- | --- | --- | --- | --- |
66046604
| `maxConnectionAge` | _[Duration](https://gateway-api.sigs.k8s.io/reference/api-spec/1.5/spec/#duration)_ | false | | MaxConnectionAge is the maximum age of an active connection before Envoy Gateway will initiate a graceful close.<br />If unspecified, Envoy Gateway randomly selects a value between 10h and 12h to stagger reconnects across replicas. |
66056605
| `maxConnectionAgeGrace` | _[Duration](https://gateway-api.sigs.k8s.io/reference/api-spec/1.5/spec/#duration)_ | false | | MaxConnectionAgeGrace is the grace period granted after reaching MaxConnectionAge before the connection is forcibly closed.<br />The default grace period is 2m. |
6606+
| `maxReceiveMessageSize` | _[Quantity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#quantity-resource-api)_ | false | | MaxReceiveMessageSize defines the maximum size of a single xDS message that the xDS gRPC<br />server will accept from an Envoy proxy.<br />Envoy's requests grow with the number of resources it holds: on every stream (re)connect,<br />the first delta xDS request for each resource type echoes back the name and version of<br />every resource the proxy currently has. At a large enough scale this exceeds the 4MiB<br />default, and the stream fails immediately with "received message larger than max", leaving<br />the proxy stuck on its last known-good configuration.<br />Note this limit applies only to what Envoy Gateway receives; the configuration it sends to<br />Envoy is not bounded by it.<br />If unspecified, defaults to 32MiB. |
66066607

66076608

66086609
#### XDSTranslatorHook

0 commit comments

Comments
 (0)