diff --git a/api/v1alpha1/envoygateway_helpers.go b/api/v1alpha1/envoygateway_helpers.go
index f6d4568f74..cd94730228 100644
--- a/api/v1alpha1/envoygateway_helpers.go
+++ b/api/v1alpha1/envoygateway_helpers.go
@@ -123,6 +123,12 @@ func (e *EnvoyGateway) TopologyInjectorDisabled() bool {
return false
}
+// GetEnvoyProxyDefaultSpec returns the default EnvoyProxySpec if specified,
+// otherwise returns nil.
+func (e *EnvoyGateway) GetEnvoyProxyDefaultSpec() *EnvoyProxySpec {
+ return e.EnvoyProxy
+}
+
// defaultRuntimeFlags are the default runtime flags for Envoy Gateway.
var defaultRuntimeFlags = map[RuntimeFlag]bool{
XDSNameSchemeV2: false,
diff --git a/api/v1alpha1/envoygateway_types.go b/api/v1alpha1/envoygateway_types.go
index 7221ded161..75bbeee410 100644
--- a/api/v1alpha1/envoygateway_types.go
+++ b/api/v1alpha1/envoygateway_types.go
@@ -110,6 +110,23 @@ type EnvoyGatewaySpec struct {
// RuntimeFlags defines the runtime flags for Envoy Gateway.
// Unlike ExtensionAPIs, these flags are temporary and will be removed in future releases once the related features are stable.
RuntimeFlags *RuntimeFlags `json:"runtimeFlags,omitempty"`
+
+ // EnvoyProxy defines the default EnvoyProxy configuration that applies
+ // to all managed Envoy Proxy fleet. This is an optional field and when
+ // provided, the settings from this EnvoyProxySpec serve as the base
+ // defaults for all Envoy Proxy instances.
+ //
+ // The hierarchy for EnvoyProxy configuration is (highest to lowest priority):
+ // 1. Gateway-level EnvoyProxy (referenced via Gateway.spec.infrastructure.parametersRef)
+ // 2. GatewayClass-level EnvoyProxy (referenced via GatewayClass.spec.parametersRef)
+ // 3. This EnvoyProxy default spec
+ //
+ // Currently, the most specific EnvoyProxy configuration wins completely (replace semantics).
+ // A future release will introduce merge semantics to allow combining configurations
+ // across multiple levels.
+ //
+ // +optional
+ EnvoyProxy *EnvoyProxySpec `json:"envoyProxy,omitempty"`
}
// GatewayAPI defines an experimental Gateway API resource that can be enabled.
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index a364034980..51f27fc0b5 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -2362,6 +2362,11 @@ func (in *EnvoyGatewaySpec) DeepCopyInto(out *EnvoyGatewaySpec) {
*out = new(RuntimeFlags)
(*in).DeepCopyInto(*out)
}
+ if in.EnvoyProxy != nil {
+ in, out := &in.EnvoyProxy, &out.EnvoyProxy
+ *out = new(EnvoyProxySpec)
+ (*in).DeepCopyInto(*out)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyGatewaySpec.
diff --git a/internal/gatewayapi/contexts.go b/internal/gatewayapi/contexts.go
index 03e9f5c61d..928245603d 100644
--- a/internal/gatewayapi/contexts.go
+++ b/internal/gatewayapi/contexts.go
@@ -51,6 +51,11 @@ func (g *GatewayContext) ResetListeners() {
}
func (g *GatewayContext) attachEnvoyProxy(resources *resource.Resources, epMap map[types.NamespacedName]*egv1a1.EnvoyProxy) {
+ // Priority order (highest to lowest):
+ // 1. Gateway-level EnvoyProxy (via parametersRef)
+ // 2. GatewayClass-level EnvoyProxy
+ // 3. Default EnvoyProxySpec from EnvoyGateway configuration
+
if g.Spec.Infrastructure != nil && g.Spec.Infrastructure.ParametersRef != nil && !IsMergeGatewaysEnabled(resources) {
ref := g.Spec.Infrastructure.ParametersRef
if string(ref.Group) == egv1a1.GroupVersion.Group && ref.Kind == egv1a1.KindEnvoyProxy {
@@ -63,7 +68,19 @@ func (g *GatewayContext) attachEnvoyProxy(resources *resource.Resources, epMap m
// not found, fallthrough to use envoyProxy attached to gatewayclass
}
- g.envoyProxy = resources.EnvoyProxyForGatewayClass
+ // Use GatewayClass-level EnvoyProxy if available
+ if resources.EnvoyProxyForGatewayClass != nil {
+ g.envoyProxy = resources.EnvoyProxyForGatewayClass
+ return
+ }
+
+ // Fall back to default EnvoyProxySpec from EnvoyGateway configuration
+ if resources.EnvoyProxyDefaultSpec != nil {
+ // Create a synthetic EnvoyProxy object from the default spec
+ g.envoyProxy = &egv1a1.EnvoyProxy{
+ Spec: *resources.EnvoyProxyDefaultSpec,
+ }
+ }
}
// ListenerContext wraps a Listener and provides helper methods for
diff --git a/internal/gatewayapi/contexts_test.go b/internal/gatewayapi/contexts_test.go
index e298e0b5b4..8e2a816202 100644
--- a/internal/gatewayapi/contexts_test.go
+++ b/internal/gatewayapi/contexts_test.go
@@ -10,8 +10,12 @@ import (
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/utils/ptr"
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
+ egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+ "github.com/envoyproxy/gateway/internal/gatewayapi/resource"
"github.com/envoyproxy/gateway/internal/gatewayapi/status"
)
@@ -150,3 +154,243 @@ func TestContextsStaleListener(t *testing.T) {
expectedGCtxListeners := []*ListenerContext{httpsListenerCtx}
require.Equal(t, expectedGCtxListeners, gCtx.listeners)
}
+
+func TestAttachEnvoyProxy(t *testing.T) {
+ testCases := []struct {
+ name string
+ gatewayParametersRef *gwapiv1.LocalParametersReference
+ envoyProxyForGateway *egv1a1.EnvoyProxy
+ envoyProxyForGWClass *egv1a1.EnvoyProxy
+ envoyProxyDefaultSpec *egv1a1.EnvoyProxySpec
+ expectedMergeGateways *bool
+ expectedConcurrency *int32
+ expectEnvoyProxyNil bool
+ }{
+ {
+ name: "no envoy proxy at any level",
+ expectEnvoyProxyNil: true,
+ },
+ {
+ name: "only default spec - should use default",
+ envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
+ Concurrency: ptr.To[int32](4),
+ },
+ expectedConcurrency: ptr.To[int32](4),
+ },
+ {
+ name: "gatewayclass envoy proxy overrides default spec",
+ envoyProxyForGWClass: &egv1a1.EnvoyProxy{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: "envoy-gateway-system",
+ Name: "gc-proxy",
+ },
+ Spec: egv1a1.EnvoyProxySpec{
+ Concurrency: ptr.To[int32](8),
+ },
+ },
+ envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
+ Concurrency: ptr.To[int32](4),
+ },
+ expectedConcurrency: ptr.To[int32](8),
+ },
+ {
+ name: "gateway envoy proxy overrides gatewayclass",
+ gatewayParametersRef: &gwapiv1.LocalParametersReference{
+ Group: gwapiv1.Group(egv1a1.GroupVersion.Group),
+ Kind: gwapiv1.Kind(egv1a1.KindEnvoyProxy),
+ Name: "gw-proxy",
+ },
+ envoyProxyForGateway: &egv1a1.EnvoyProxy{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: "default",
+ Name: "gw-proxy",
+ },
+ Spec: egv1a1.EnvoyProxySpec{
+ Concurrency: ptr.To[int32](16),
+ },
+ },
+ envoyProxyForGWClass: &egv1a1.EnvoyProxy{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: "envoy-gateway-system",
+ Name: "gc-proxy",
+ },
+ Spec: egv1a1.EnvoyProxySpec{
+ Concurrency: ptr.To[int32](8),
+ },
+ },
+ envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
+ Concurrency: ptr.To[int32](4),
+ },
+ expectedConcurrency: ptr.To[int32](16),
+ },
+ {
+ name: "default spec with merge gateways enabled",
+ envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
+ MergeGateways: ptr.To(true),
+ Concurrency: ptr.To[int32](4),
+ },
+ expectedMergeGateways: ptr.To(true),
+ expectedConcurrency: ptr.To[int32](4),
+ },
+ {
+ name: "gatewayclass overrides default merge gateways setting",
+ envoyProxyForGWClass: &egv1a1.EnvoyProxy{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: "envoy-gateway-system",
+ Name: "gc-proxy",
+ },
+ Spec: egv1a1.EnvoyProxySpec{
+ MergeGateways: ptr.To(false),
+ },
+ },
+ envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
+ MergeGateways: ptr.To(true),
+ },
+ expectedMergeGateways: ptr.To(false),
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ // Create gateway
+ gateway := &gwapiv1.Gateway{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: "default",
+ Name: "test-gateway",
+ },
+ Spec: gwapiv1.GatewaySpec{
+ GatewayClassName: "test-gc",
+ },
+ }
+ if tc.gatewayParametersRef != nil {
+ gateway.Spec.Infrastructure = &gwapiv1.GatewayInfrastructure{
+ ParametersRef: tc.gatewayParametersRef,
+ }
+ }
+
+ gCtx := &GatewayContext{Gateway: gateway}
+
+ // Build resources
+ resources := &resource.Resources{
+ EnvoyProxyForGatewayClass: tc.envoyProxyForGWClass,
+ EnvoyProxyDefaultSpec: tc.envoyProxyDefaultSpec,
+ }
+
+ // Build envoy proxy map for gateway-level proxies
+ epMap := make(map[types.NamespacedName]*egv1a1.EnvoyProxy)
+ if tc.envoyProxyForGateway != nil {
+ key := types.NamespacedName{
+ Namespace: tc.envoyProxyForGateway.Namespace,
+ Name: tc.envoyProxyForGateway.Name,
+ }
+ epMap[key] = tc.envoyProxyForGateway
+ }
+
+ // Call attachEnvoyProxy
+ gCtx.attachEnvoyProxy(resources, epMap)
+
+ // Verify results
+ if tc.expectEnvoyProxyNil {
+ require.Nil(t, gCtx.envoyProxy)
+ return
+ }
+
+ require.NotNil(t, gCtx.envoyProxy)
+
+ if tc.expectedConcurrency != nil {
+ require.NotNil(t, gCtx.envoyProxy.Spec.Concurrency)
+ require.Equal(t, *tc.expectedConcurrency, *gCtx.envoyProxy.Spec.Concurrency)
+ }
+
+ if tc.expectedMergeGateways != nil {
+ require.NotNil(t, gCtx.envoyProxy.Spec.MergeGateways)
+ require.Equal(t, *tc.expectedMergeGateways, *gCtx.envoyProxy.Spec.MergeGateways)
+ }
+ })
+ }
+}
+
+func TestIsMergeGatewaysEnabled(t *testing.T) {
+ testCases := []struct {
+ name string
+ envoyProxyForGWClass *egv1a1.EnvoyProxy
+ envoyProxyDefaultSpec *egv1a1.EnvoyProxySpec
+ expected bool
+ }{
+ {
+ name: "no envoy proxy configured",
+ expected: false,
+ },
+ {
+ name: "default spec with merge gateways true",
+ envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
+ MergeGateways: ptr.To(true),
+ },
+ expected: true,
+ },
+ {
+ name: "default spec with merge gateways false",
+ envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
+ MergeGateways: ptr.To(false),
+ },
+ expected: false,
+ },
+ {
+ name: "gatewayclass proxy with merge gateways true",
+ envoyProxyForGWClass: &egv1a1.EnvoyProxy{
+ Spec: egv1a1.EnvoyProxySpec{
+ MergeGateways: ptr.To(true),
+ },
+ },
+ expected: true,
+ },
+ {
+ name: "gatewayclass proxy overrides default - gc true, default false",
+ envoyProxyForGWClass: &egv1a1.EnvoyProxy{
+ Spec: egv1a1.EnvoyProxySpec{
+ MergeGateways: ptr.To(true),
+ },
+ },
+ envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
+ MergeGateways: ptr.To(false),
+ },
+ expected: true,
+ },
+ {
+ name: "gatewayclass proxy overrides default - gc false, default true",
+ envoyProxyForGWClass: &egv1a1.EnvoyProxy{
+ Spec: egv1a1.EnvoyProxySpec{
+ MergeGateways: ptr.To(false),
+ },
+ },
+ envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
+ MergeGateways: ptr.To(true),
+ },
+ expected: false,
+ },
+ {
+ name: "gatewayclass proxy nil merge gateways falls back to default",
+ envoyProxyForGWClass: &egv1a1.EnvoyProxy{
+ Spec: egv1a1.EnvoyProxySpec{
+ Concurrency: ptr.To[int32](4), // some other setting
+ },
+ },
+ envoyProxyDefaultSpec: &egv1a1.EnvoyProxySpec{
+ MergeGateways: ptr.To(true),
+ },
+ expected: true,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ resources := &resource.Resources{
+ EnvoyProxyForGatewayClass: tc.envoyProxyForGWClass,
+ EnvoyProxyDefaultSpec: tc.envoyProxyDefaultSpec,
+ }
+
+ result := IsMergeGatewaysEnabled(resources)
+ require.Equal(t, tc.expected, result)
+ })
+ }
+}
diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go
index dc3e73dd28..12da3cf54a 100644
--- a/internal/gatewayapi/helpers.go
+++ b/internal/gatewayapi/helpers.go
@@ -472,7 +472,19 @@ func irTLSCrlName(namespace, name string) string {
}
func IsMergeGatewaysEnabled(resources *resource.Resources) bool {
- return resources.EnvoyProxyForGatewayClass != nil && resources.EnvoyProxyForGatewayClass.Spec.MergeGateways != nil && *resources.EnvoyProxyForGatewayClass.Spec.MergeGateways
+ // Check GatewayClass-level EnvoyProxy first (higher priority)
+ if resources.EnvoyProxyForGatewayClass != nil &&
+ resources.EnvoyProxyForGatewayClass.Spec.MergeGateways != nil {
+ return *resources.EnvoyProxyForGatewayClass.Spec.MergeGateways
+ }
+
+ // Fall back to default EnvoyProxySpec from EnvoyGateway configuration
+ if resources.EnvoyProxyDefaultSpec != nil &&
+ resources.EnvoyProxyDefaultSpec.MergeGateways != nil {
+ return *resources.EnvoyProxyDefaultSpec.MergeGateways
+ }
+
+ return false
}
func protocolSliceToStringSlice(protocols []gwapiv1.ProtocolType) []string {
diff --git a/internal/gatewayapi/resource/resource.go b/internal/gatewayapi/resource/resource.go
index ed3844679c..ddf5d3c5f8 100644
--- a/internal/gatewayapi/resource/resource.go
+++ b/internal/gatewayapi/resource/resource.go
@@ -41,6 +41,9 @@ type Resources struct {
EnvoyProxyForGatewayClass *egv1a1.EnvoyProxy `json:"envoyProxyForGatewayClass,omitempty" yaml:"envoyProxyForGatewayClass,omitempty"`
// EnvoyProxiesForGateways holds EnvoyProxiesForGateways attached to Gateways
EnvoyProxiesForGateways []*egv1a1.EnvoyProxy `json:"envoyProxiesForGateways,omitempty" yaml:"envoyProxiesForGateways,omitempty"`
+ // EnvoyProxyDefaultSpec holds the default EnvoyProxySpec from EnvoyGateway configuration.
+ // This serves as the lowest priority fallback when no GatewayClass or Gateway level EnvoyProxy is specified.
+ EnvoyProxyDefaultSpec *egv1a1.EnvoyProxySpec `json:"envoyProxyDefaultSpec,omitempty" yaml:"envoyProxyDefaultSpec,omitempty"`
GatewayClass *gwapiv1.GatewayClass `json:"gatewayClass,omitempty" yaml:"gatewayClass,omitempty"`
Gateways []*gwapiv1.Gateway `json:"gateways,omitempty" yaml:"gateways,omitempty"`
diff --git a/internal/gatewayapi/resource/zz_generated.deepcopy.go b/internal/gatewayapi/resource/zz_generated.deepcopy.go
index b02f43a074..4002ac1a92 100644
--- a/internal/gatewayapi/resource/zz_generated.deepcopy.go
+++ b/internal/gatewayapi/resource/zz_generated.deepcopy.go
@@ -41,6 +41,11 @@ func (in *Resources) DeepCopyInto(out *Resources) {
}
}
}
+ if in.EnvoyProxyDefaultSpec != nil {
+ in, out := &in.EnvoyProxyDefaultSpec, &out.EnvoyProxyDefaultSpec
+ *out = new(v1alpha1.EnvoyProxySpec)
+ (*in).DeepCopyInto(*out)
+ }
if in.GatewayClass != nil {
in, out := &in.GatewayClass, &out.GatewayClass
*out = new(v1.GatewayClass)
diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go
index d71e3753fe..dfbb860772 100644
--- a/internal/provider/kubernetes/controller.go
+++ b/internal/provider/kubernetes/controller.go
@@ -349,6 +349,12 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, _ reconcile.Reques
gwcResource := resource.NewResources()
gwcResource.GatewayClass = managedGC
+ // Set default EnvoyProxySpec from EnvoyGateway configuration if available.
+ // This serves as the lowest priority fallback when no GatewayClass or Gateway level EnvoyProxy is specified.
+ if r.envoyGateway != nil {
+ gwcResource.EnvoyProxyDefaultSpec = r.envoyGateway.GetEnvoyProxyDefaultSpec()
+ }
+
gwcResourceMapping := newResourceMapping()
gcLogger := logger.WithValues("GatewayClass", managedGC.Name)
// Process the parametersRef of the accepted GatewayClass.
@@ -545,9 +551,8 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, _ reconcile.Reques
gwcResource.Namespaces = append(gwcResource.Namespaces, namespace)
}
- if gwcResource.EnvoyProxyForGatewayClass != nil && gwcResource.EnvoyProxyForGatewayClass.Spec.MergeGateways != nil {
- r.setGatewayClassMerge(managedGC.Name, *gwcResource.EnvoyProxyForGatewayClass.Spec.MergeGateways)
- }
+ // Update merge gateways tracking based on EnvoyProxy configuration
+ r.setGatewayClassMerge(managedGC.Name, gatewayapi.IsMergeGatewaysEnabled(gwcResource))
if len(gwcResource.Gateways) == 0 {
gcLogger.Info("No gateways found for accepted GatewayClass")
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index 6caa28b1c2..f299d7acc6 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -1357,6 +1357,7 @@ EnvoyGateway is the schema for the envoygateways API.
| `extensionManager` | _[ExtensionManager](#extensionmanager)_ | false | | ExtensionManager defines an extension manager to register for the Envoy Gateway Control Plane. |
| `extensionApis` | _[ExtensionAPISettings](#extensionapisettings)_ | false | | ExtensionAPIs defines the settings related to specific Gateway API Extensions
implemented by Envoy Gateway |
| `runtimeFlags` | _[RuntimeFlags](#runtimeflags)_ | true | | RuntimeFlags defines the runtime flags for Envoy Gateway.
Unlike ExtensionAPIs, these flags are temporary and will be removed in future releases once the related features are stable. |
+| `envoyProxy` | _[EnvoyProxySpec](#envoyproxyspec)_ | false | | EnvoyProxy defines the default EnvoyProxy configuration that applies
to all managed Envoy Proxy fleet. This is an optional field and when
provided, the settings from this EnvoyProxySpec serve as the base
defaults for all Envoy Proxy instances.
The hierarchy for EnvoyProxy configuration is (highest to lowest priority):
1. Gateway-level EnvoyProxy (referenced via Gateway.spec.infrastructure.parametersRef)
2. GatewayClass-level EnvoyProxy (referenced via GatewayClass.spec.parametersRef)
3. This EnvoyProxy default spec
Currently, the most specific EnvoyProxy configuration wins completely (replace semantics).
A future release will introduce merge semantics to allow combining configurations
across multiple levels. |
#### EnvoyGatewayAdmin
@@ -1643,6 +1644,7 @@ _Appears in:_
| `extensionManager` | _[ExtensionManager](#extensionmanager)_ | false | | ExtensionManager defines an extension manager to register for the Envoy Gateway Control Plane. |
| `extensionApis` | _[ExtensionAPISettings](#extensionapisettings)_ | false | | ExtensionAPIs defines the settings related to specific Gateway API Extensions
implemented by Envoy Gateway |
| `runtimeFlags` | _[RuntimeFlags](#runtimeflags)_ | true | | RuntimeFlags defines the runtime flags for Envoy Gateway.
Unlike ExtensionAPIs, these flags are temporary and will be removed in future releases once the related features are stable. |
+| `envoyProxy` | _[EnvoyProxySpec](#envoyproxyspec)_ | false | | EnvoyProxy defines the default EnvoyProxy configuration that applies
to all managed Envoy Proxy fleet. This is an optional field and when
provided, the settings from this EnvoyProxySpec serve as the base
defaults for all Envoy Proxy instances.
The hierarchy for EnvoyProxy configuration is (highest to lowest priority):
1. Gateway-level EnvoyProxy (referenced via Gateway.spec.infrastructure.parametersRef)
2. GatewayClass-level EnvoyProxy (referenced via GatewayClass.spec.parametersRef)
3. This EnvoyProxy default spec
Currently, the most specific EnvoyProxy configuration wins completely (replace semantics).
A future release will introduce merge semantics to allow combining configurations
across multiple levels. |
#### EnvoyGatewayTelemetry
@@ -1850,6 +1852,8 @@ _Appears in:_
EnvoyProxySpec defines the desired state of EnvoyProxy.
_Appears in:_
+- [EnvoyGateway](#envoygateway)
+- [EnvoyGatewaySpec](#envoygatewayspec)
- [EnvoyProxy](#envoyproxy)
| Field | Type | Required | Default | Description |
diff --git a/test/config/envoy-gateaway-config/default.yaml b/test/config/envoy-gateaway-config/default.yaml
index cc560d2b4b..8b4d6efdc9 100644
--- a/test/config/envoy-gateaway-config/default.yaml
+++ b/test/config/envoy-gateaway-config/default.yaml
@@ -19,3 +19,11 @@ data:
type: Redis
redis:
url: redis.redis-system.svc.cluster.local:6379
+ envoyProxy:
+ logging:
+ level: debug
+ provider:
+ kubernetes:
+ envoyDeployment:
+ container:
+ image: envoyproxy/envoy:distroless-dev-debug