Skip to content

Commit 2931bfc

Browse files
committed
always include the controller namespace in the k8s provider
Signed-off-by: Huabing Zhao <zhaohuabing@gmail.com>
1 parent 0385758 commit 2931bfc

8 files changed

Lines changed: 125 additions & 30 deletions

File tree

internal/provider/kubernetes/controller.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,9 @@ func newGatewayAPIController(ctx context.Context, mgr manager.Manager, cfg *conf
177177

178178
if byNamespaceSelectorEnabled(cfg.EnvoyGateway) {
179179
r.namespaceLabel = cfg.EnvoyGateway.Provider.Kubernetes.Watch.NamespaceSelector
180-
r.client = newNamespaceSelectorClient(r.client, r.namespaceLabel)
180+
// Always include the controller namespace so EG-owned infrastructure
181+
// resources remain visible when user namespaces are selected by labels.
182+
r.client = newNamespaceSelectorClient(r.client, r.namespaceLabel, cfg.ControllerNamespace)
181183
}
182184

183185
// controller-runtime doesn't allow run controller with same name for more than once

internal/provider/kubernetes/kubernetes.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,10 @@ func newProvider(ctx context.Context, restCfg *rest.Config, svrCfg *ec.Server,
272272

273273
if svrCfg.EnvoyGateway.NamespaceMode() {
274274
mgrOpts.Cache.DefaultNamespaces = make(map[string]cache.Config)
275+
// Keep the controller namespace visible even when users restrict watched
276+
// Gateway API namespaces; infra reconciliation reads EG-owned resources
277+
// such as Secrets from this namespace through the shared cached client.
278+
mgrOpts.Cache.DefaultNamespaces[svrCfg.ControllerNamespace] = cache.Config{}
275279
for _, watchNS := range svrCfg.EnvoyGateway.Provider.Kubernetes.Watch.Namespaces {
276280
mgrOpts.Cache.DefaultNamespaces[watchNS] = cache.Config{}
277281
}

internal/provider/kubernetes/namespace_selector_client.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"k8s.io/apimachinery/pkg/api/meta"
1313
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/apimachinery/pkg/util/sets"
1415
"sigs.k8s.io/controller-runtime/pkg/client"
1516
)
1617

@@ -20,18 +21,20 @@ import (
2021
// List operations.
2122
type namespaceSelectorClient struct {
2223
client.Client
23-
namespaceSelector *metav1.LabelSelector
24+
namespaceSelector *metav1.LabelSelector
25+
includedNamespaces sets.Set[string]
2426
}
2527

2628
// newNamespaceSelectorClient creates a new namespace-filtered client wrapper.
2729
// If namespaceSelector is nil, the wrapper passes through all operations unchanged.
28-
func newNamespaceSelectorClient(c client.Client, namespaceSelector *metav1.LabelSelector) client.Client {
30+
func newNamespaceSelectorClient(c client.Client, namespaceSelector *metav1.LabelSelector, includedNamespaces ...string) client.Client {
2931
if namespaceSelector == nil {
3032
return c
3133
}
3234
return &namespaceSelectorClient{
33-
Client: c,
34-
namespaceSelector: namespaceSelector,
35+
Client: c,
36+
namespaceSelector: namespaceSelector,
37+
includedNamespaces: sets.New(includedNamespaces...),
3538
}
3639
}
3740

@@ -86,15 +89,21 @@ func (c *namespaceSelectorClient) filterByNamespaceLabels(ctx context.Context, l
8689
}
8790

8891
ns := obj.GetNamespace()
89-
matches, cached := namespaceMatches[ns]
90-
if !cached {
91-
var err error
92-
matches, err = checkObjectNamespaceLabels(ctx, c.Client, c.namespaceSelector, obj)
93-
if err != nil {
94-
return fmt.Errorf("failed to check namespace labels for object %s/%s: %w",
95-
ns, obj.GetName(), err)
92+
// includedNamespaces are part of EG's own operating surface, e.g. the
93+
// controller namespace. They must bypass user namespace selectors.
94+
matches := c.includedNamespaces.Has(ns)
95+
if !matches {
96+
cachedMatches, cached := namespaceMatches[ns]
97+
matches = cachedMatches
98+
if !cached {
99+
var err error
100+
matches, err = checkObjectNamespaceLabels(ctx, c.Client, c.namespaceSelector, obj)
101+
if err != nil {
102+
return fmt.Errorf("failed to check namespace labels for object %s/%s: %w",
103+
ns, obj.GetName(), err)
104+
}
105+
namespaceMatches[ns] = matches
96106
}
97-
namespaceMatches[ns] = matches
98107
}
99108

100109
if matches {

internal/provider/kubernetes/namespace_selector_client_test.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,12 @@ func TestNamespaceSelectorClient(t *testing.T) {
8080
scheme := envoygateway.GetScheme()
8181

8282
testCases := []struct {
83-
name string
84-
namespaceSelector *metav1.LabelSelector
85-
objects []runtime.Object
86-
expectCTPCount int
87-
expectGWCount int
83+
name string
84+
namespaceSelector *metav1.LabelSelector
85+
includedNamespaces []string
86+
objects []runtime.Object
87+
expectCTPCount int
88+
expectGWCount int
8889
}{
8990
{
9091
name: "nil selector returns all resources",
@@ -127,6 +128,22 @@ func TestNamespaceSelectorClient(t *testing.T) {
127128
expectCTPCount: 0,
128129
expectGWCount: 0,
129130
},
131+
{
132+
name: "included namespace bypasses selector",
133+
namespaceSelector: &metav1.LabelSelector{
134+
MatchLabels: map[string]string{
135+
"env": "development",
136+
},
137+
},
138+
includedNamespaces: []string{"non-matching-ns"},
139+
objects: []runtime.Object{
140+
nsMatching, nsNonMatching,
141+
ctpInMatchingNs, ctpInNonMatchingNs,
142+
gwInMatchingNs, gwInNonMatchingNs,
143+
},
144+
expectCTPCount: 1,
145+
expectGWCount: 1,
146+
},
130147
}
131148

132149
for _, tc := range testCases {
@@ -138,7 +155,7 @@ func TestNamespaceSelectorClient(t *testing.T) {
138155
Build()
139156

140157
// Wrap with namespace selector client
141-
wrappedClient := newNamespaceSelectorClient(fakeClient, tc.namespaceSelector)
158+
wrappedClient := newNamespaceSelectorClient(fakeClient, tc.namespaceSelector, tc.includedNamespaces...)
142159

143160
ctx := context.Background()
144161

internal/provider/kubernetes/predicates.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ func (r *gatewayAPIReconciler) hasMatchingController(gc *gwapiv1.GatewayClass) b
5252
// hasMatchingNamespaceLabels returns true if the namespace of provided object has
5353
// the provided labels or false otherwise.
5454
func (r *gatewayAPIReconciler) hasMatchingNamespaceLabels(obj client.Object) bool {
55+
// Controller-namespace events can affect EG-owned infrastructure and should
56+
// not be dropped by the user's Gateway API namespace selector.
57+
if obj.GetNamespace() == r.namespace {
58+
return true
59+
}
5560
ok, err := checkObjectNamespaceLabels(context.Background(), r.client, r.namespaceLabel, obj)
5661
if err != nil {
5762
r.log.Error(

internal/provider/kubernetes/predicates_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,60 @@ func TestFindOwningGateway(t *testing.T) {
260260
}
261261
}
262262

263+
func TestEnvoyServiceForGatewayIncludesControllerNamespace(t *testing.T) {
264+
ctx := context.Background()
265+
gatewayNamespace := "watched"
266+
controllerNamespace := "envoy-gateway-system"
267+
gatewayClassName := "gc-name"
268+
namespaceSelector := &metav1.LabelSelector{
269+
MatchLabels: map[string]string{"gateway": "enabled"},
270+
}
271+
gtw := test.GetGateway(types.NamespacedName{
272+
Namespace: gatewayNamespace,
273+
Name: "gateway",
274+
}, gatewayClassName, 80)
275+
svc := &corev1.Service{
276+
ObjectMeta: metav1.ObjectMeta{
277+
Namespace: controllerNamespace,
278+
Name: "envoy-default-gateway",
279+
Labels: gatewayapi.OwnerLabels(gtw, false),
280+
},
281+
}
282+
283+
baseClient := fakeclient.NewClientBuilder().
284+
WithScheme(envoygateway.GetScheme()).
285+
WithObjects(
286+
&corev1.Namespace{
287+
ObjectMeta: metav1.ObjectMeta{
288+
Name: gatewayNamespace,
289+
Labels: namespaceSelector.MatchLabels,
290+
},
291+
},
292+
&corev1.Namespace{
293+
ObjectMeta: metav1.ObjectMeta{Name: controllerNamespace},
294+
},
295+
svc,
296+
).
297+
Build()
298+
r := &gatewayAPIReconciler{
299+
namespace: controllerNamespace,
300+
classController: gwapiv1.GatewayController(egv1a1.GatewayControllerName),
301+
mergeGateways: sets.New[string](),
302+
}
303+
304+
r.client = newNamespaceSelectorClient(baseClient, namespaceSelector)
305+
got, err := r.envoyServiceForGateway(ctx, gtw)
306+
require.NoError(t, err)
307+
require.Nil(t, got)
308+
309+
r.client = newNamespaceSelectorClient(baseClient, namespaceSelector, controllerNamespace)
310+
got, err = r.envoyServiceForGateway(ctx, gtw)
311+
require.NoError(t, err)
312+
require.NotNil(t, got)
313+
require.Equal(t, svc.Name, got.Name)
314+
require.Equal(t, controllerNamespace, got.Namespace)
315+
}
316+
263317
// TestValidateConfigMapForReconcile tests the validateConfigMapForReconcile
264318
// predicate function.
265319
func TestValidateConfigMapForReconcile(t *testing.T) {

release-notes/current.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ bug fixes: |
1717
Fixed BackendTLSPolicy selection to prefer section name over wildcard match on the same backend.
1818
Fixed missing deprecated field warning in ClientTrafficPolicy and SecurityPolicy.
1919
Fixed ClientTrafficPolicy TLS cipher validation rejecting supported IANA/RFC cipher suite names.
20+
Fixed Kubernetes provider namespace-scoped watches to always include the controller namespace so Envoy Gateway can read its own infrastructure resources.
2021
2122
# Enhancements that improve performance.
2223
performance improvements: |

test/config/envoy-gateaway-config/watch-namespaces.yaml

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,20 @@ data:
1111
type: Kubernetes
1212
kubernetes:
1313
watch:
14-
type: Namespaces
15-
namespaces:
16-
- envoy-gateway-system
17-
- btp-cross-ns-denied
18-
- btp-cross-ns-granted
19-
- gateway-conformance-infra
20-
- gateway-preserve-case-backend
21-
- gateway-upgrade-infra
22-
- listenerset-tls-termination-secret
23-
- monitoring
24-
- multireferencegrants-ns
14+
type: NamespaceSelector
15+
namespaceSelector:
16+
matchExpressions:
17+
- key: kubernetes.io/metadata.name
18+
operator: In
19+
values:
20+
- btp-cross-ns-denied
21+
- btp-cross-ns-granted
22+
- gateway-conformance-infra
23+
- gateway-preserve-case-backend
24+
- gateway-upgrade-infra
25+
- listenerset-tls-termination-secret
26+
- monitoring
27+
- multireferencegrants-ns
2528
gateway:
2629
controllerName: gateway.envoyproxy.io/gatewayclass-controller
2730
extensionApis:

0 commit comments

Comments
 (0)