Skip to content

Commit e7195a0

Browse files
committed
fix: make the TLSRoute watch optional too
TLSRoute graduated to the Gateway API standard channel in v1.5, so it is subject to the same problem as ListenerSet and GRPCRoute: a cluster may run a bundle that predates the graduation, or a managed offering may ship only a curated subset of the standard channel, and the unconditional watch then fails the manager's cache sync and crash-loops Envoy Gateway at startup. Guard the TLSRoute watch, its indexers, the reconcile-time processing and the TLSRoute predicate List behind a CRD existence check, matching what the previous commit does for ListenerSet and GRPCRoute. Also correct the rationale comment: TCPRoute and UDPRoute are standard channel as of Gateway API v1.6, they are not experimental-only. Signed-off-by: Huabing (Robin) Zhao <zhaohuabing@gmail.com>
1 parent 2035d02 commit e7195a0

6 files changed

Lines changed: 70 additions & 40 deletions

File tree

internal/provider/kubernetes/controller.go

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ type gatewayAPIReconciler struct {
9191
serviceImportCRDExists bool
9292
spCRDExists bool
9393
tcpRouteCRDExists bool
94+
tlsRouteCRDExists bool
9495
udpRouteCRDExists bool
9596

9697
clusterTrustBundleExits bool
@@ -1900,9 +1901,11 @@ func (r *gatewayAPIReconciler) processGateways(ctx context.Context, managedGC *g
19001901

19011902
// Route Processing
19021903

1903-
// Get TLSRoute objects and check if it exists.
1904-
if err := r.processTLSRoutes(ctx, gtwNamespacedName, resourceMap, resourceTree); err != nil {
1905-
return err
1904+
if r.tlsRouteCRDExists {
1905+
// Get TLSRoute objects and check if it exists.
1906+
if err := r.processTLSRoutes(ctx, gtwNamespacedName, resourceMap, resourceTree); err != nil {
1907+
return err
1908+
}
19061909
}
19071910

19081911
// Get HTTPRoute objects and check if it exists.
@@ -2316,14 +2319,17 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M
23162319
return err
23172320
}
23182321

2319-
// Some managed Kubernetes offerings install a curated subset of the Gateway API standard channel
2320-
// CRDs and don't allow users to add the missing ones: for example, GKE's managed gateway-api-crds
2321-
// addon ships Gateway API v1.5 without ListenerSet and GRPCRoute, because the GKE Gateway
2322-
// controller doesn't implement them. Keep the ListenerSet and GRPCRoute watches optional so that
2323-
// Envoy Gateway starts on those clusters instead of crash-looping: the absent kind can't be used
2324-
// there anyway, so its absence should disable the feature, not take down the controller.
2325-
// TODO: remove these checks once ListenerSet and GRPCRoute are included in the Gateway API CRD
2326-
// bundles shipped by the major managed Kubernetes offerings.
2322+
// The watches for ListenerSet, GRPCRoute, TLSRoute, TCPRoute and UDPRoute are all optional. These
2323+
// kinds are in the Gateway API standard channel today (ListenerSet and TLSRoute graduated in
2324+
// v1.5, TCPRoute and UDPRoute in v1.6), but they are still not guaranteed to be installed:
2325+
// - The cluster may run an older Gateway API bundle, or a standard channel bundle from before
2326+
// the kind graduated, so the kind is simply not there.
2327+
// - Some managed Kubernetes offerings install only a curated subset of the standard channel and
2328+
// don't let users add the missing CRDs. For example, GKE's managed gateway-api-crds addon
2329+
// ships Gateway API v1.5 without ListenerSet and GRPCRoute, because the GKE Gateway
2330+
// controller doesn't implement them.
2331+
// Envoy Gateway must start on those clusters instead of crash-looping: an absent kind can't be
2332+
// used there anyway, so its absence should disable the feature, not take down the controller.
23272333
r.listenerSetCRDExists, err = checkCRD(resource.KindListenerSet, gwapiv1.GroupVersion.String())
23282334
if err != nil {
23292335
return err
@@ -2372,7 +2378,6 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M
23722378
return err
23732379
}
23742380

2375-
// See the comment on the ListenerSet watch above for why this watch is optional.
23762381
r.grpcRouteCRDExists, err = checkCRD(resource.KindGRPCRoute, gwapiv1.GroupVersion.String())
23772382
if err != nil {
23782383
return err
@@ -2399,24 +2404,31 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M
23992404
return err
24002405
}
24012406
}
2402-
2403-
// Watch TLSRoute CRUDs and process affected Gateways.
2404-
tlsrPredicates := commonPredicates[*gwapiv1.TLSRoute]()
2405-
if r.namespaceLabel != nil {
2406-
tlsrPredicates = append(tlsrPredicates, predicate.NewTypedPredicateFuncs(func(route *gwapiv1.TLSRoute) bool {
2407-
return r.hasMatchingNamespaceLabels(route)
2408-
}))
2409-
}
2410-
if err := c.Watch(
2411-
source.Kind(mgr.GetCache(), &gwapiv1.TLSRoute{},
2412-
handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, route *gwapiv1.TLSRoute) []reconcile.Request {
2413-
return r.enqueueClass(ctx, route)
2414-
}),
2415-
tlsrPredicates...)); err != nil {
2407+
r.tlsRouteCRDExists, err = checkCRD(resource.KindTLSRoute, gwapiv1.GroupVersion.String())
2408+
if err != nil {
24162409
return err
24172410
}
2418-
if err := addTLSRouteIndexers(ctx, mgr); err != nil {
2419-
return err
2411+
if !r.tlsRouteCRDExists {
2412+
r.log.Info("TLSRoute CRD not found, skipping TLSRoute watch")
2413+
} else {
2414+
// Watch TLSRoute CRUDs and process affected Gateways.
2415+
tlsrPredicates := commonPredicates[*gwapiv1.TLSRoute]()
2416+
if r.namespaceLabel != nil {
2417+
tlsrPredicates = append(tlsrPredicates, predicate.NewTypedPredicateFuncs(func(route *gwapiv1.TLSRoute) bool {
2418+
return r.hasMatchingNamespaceLabels(route)
2419+
}))
2420+
}
2421+
if err := c.Watch(
2422+
source.Kind(mgr.GetCache(), &gwapiv1.TLSRoute{},
2423+
handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, route *gwapiv1.TLSRoute) []reconcile.Request {
2424+
return r.enqueueClass(ctx, route)
2425+
}),
2426+
tlsrPredicates...)); err != nil {
2427+
return err
2428+
}
2429+
if err := addTLSRouteIndexers(ctx, mgr); err != nil {
2430+
return err
2431+
}
24202432
}
24212433

24222434
r.udpRouteCRDExists, err = checkCRD(resource.KindUDPRoute, gwapiv1.GroupVersion.String())

internal/provider/kubernetes/controller_offline.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ func NewOfflineGatewayAPIController(
113113
serviceImportCRDExists: true,
114114
spCRDExists: true,
115115
tcpRouteCRDExists: true,
116+
tlsRouteCRDExists: true,
116117
udpRouteCRDExists: true,
117118
backendCRDExists: true,
118119
}

internal/provider/kubernetes/predicates.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -575,15 +575,17 @@ func (r *gatewayAPIReconciler) isRouteReferencingBackend(nsName *types.Namespace
575575
}
576576
}
577577

578-
tlsRouteList := &gwapiv1.TLSRouteList{}
579-
if err := r.client.List(ctx, tlsRouteList, &client.ListOptions{
580-
FieldSelector: fields.OneTermEqualSelector(backendTLSRouteIndex, nsName.String()),
581-
}); err != nil && !kerrors.IsNotFound(err) {
582-
r.log.Error(err, "failed to find associated TLSRoutes")
583-
return false
584-
}
585-
if len(tlsRouteList.Items) > 0 {
586-
return true
578+
if r.tlsRouteCRDExists {
579+
tlsRouteList := &gwapiv1.TLSRouteList{}
580+
if err := r.client.List(ctx, tlsRouteList, &client.ListOptions{
581+
FieldSelector: fields.OneTermEqualSelector(backendTLSRouteIndex, nsName.String()),
582+
}); err != nil && !kerrors.IsNotFound(err) {
583+
r.log.Error(err, "failed to find associated TLSRoutes")
584+
return false
585+
}
586+
if len(tlsRouteList.Items) > 0 {
587+
return true
588+
}
587589
}
588590

589591
if r.tcpRouteCRDExists {

internal/provider/kubernetes/predicates_test.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,9 +1155,11 @@ func TestValidateServiceForReconcile(t *testing.T) {
11551155

11561156
testCases := []struct {
11571157
name string
1158-
// grpcRouteCRDAbsent simulates a cluster whose Gateway API CRD bundle doesn't
1159-
// include GRPCRoute, e.g. GKE's managed gateway-api-crds addon.
1158+
// grpcRouteCRDAbsent and tlsRouteCRDAbsent simulate a cluster whose Gateway API
1159+
// CRD bundle doesn't include GRPCRoute or TLSRoute, e.g. GKE's managed
1160+
// gateway-api-crds addon.
11601161
grpcRouteCRDAbsent bool
1162+
tlsRouteCRDAbsent bool
11611163
configs []client.Object
11621164
service client.Object
11631165
expect bool
@@ -1267,6 +1269,18 @@ func TestValidateServiceForReconcile(t *testing.T) {
12671269
service: test.GetService(types.NamespacedName{Name: "service"}, nil, nil),
12681270
expect: true,
12691271
},
1272+
{
1273+
name: "tls route service routes exist but TLSRoute CRD is absent",
1274+
tlsRouteCRDAbsent: true,
1275+
configs: []client.Object{
1276+
test.GetGatewayClass("test-gc", egv1a1.GatewayControllerName, nil),
1277+
sampleGateway,
1278+
test.GetTLSRoute(types.NamespacedName{Name: "tlsroute-test"}, "scheduled-status-test",
1279+
types.NamespacedName{Name: "service"}, 443),
1280+
},
1281+
service: test.GetService(types.NamespacedName{Name: "service"}, nil, nil),
1282+
expect: false,
1283+
},
12701284
{
12711285
name: "udp route service routes exist",
12721286
configs: []client.Object{
@@ -1511,6 +1525,7 @@ func TestValidateServiceForReconcile(t *testing.T) {
15111525

15121526
for _, tc := range testCases {
15131527
r.grpcRouteCRDExists = !tc.grpcRouteCRDAbsent
1528+
r.tlsRouteCRDExists = !tc.tlsRouteCRDAbsent
15141529
r.client = fakeclient.NewClientBuilder().
15151530
WithScheme(envoygateway.GetScheme()).
15161531
WithObjects(tc.configs...).

release-notes/current/bug_fixes/9583-optional-listenerset-grpcroute-crd-watches.md

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed Envoy Gateway crash-looping at startup on clusters whose Gateway API CRD bundle omits ListenerSet, GRPCRoute or TLSRoute, such as GKE's managed gateway-api-crds addon, by making those watches conditional on the CRD being present.

0 commit comments

Comments
 (0)