Skip to content
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

feat: combine services across different httproutes for traditional routes #6711

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
_format_version: "3.0"
services:
- connect_timeout: 60000
host: httproute.default.httpbin.0
id: de4fffcc-97bc-59c6-9ccd-f1fb29b93beb
name: httproute.default.httpbin.0
host: httproute.default.svc.default.httpbin.80
id: 04198a15-6d25-58c6-b674-af66a0ef7341
name: httproute.default.svc.default.httpbin.80
port: 80
protocol: http
read_timeout: 60000
Expand Down Expand Up @@ -36,7 +36,7 @@ services:
upstreams:
- algorithm: round-robin
host_header: httpbin.org
name: httproute.default.httpbin.0
name: httproute.default.svc.default.httpbin.80
tags:
- k8s-name:httpbin
- k8s-namespace:default
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
_format_version: "3.0"
services:
- connect_timeout: 60000
host: httproute.default.httproute-testing.0
id: 4e3cb785-a8d0-5866-aa05-117f7c64f24d
name: httproute.default.httproute-testing.0
host: httproute.default.svc.default.httpbin.80.75_default.nginx.8080.25
id: 112d14cb-01c4-549a-aa72-1a4a234c0372
name: httproute.default.svc.default.httpbin.80.75_default.nginx.8080.25
port: 8080
protocol: http
read_timeout: 60000
Expand Down Expand Up @@ -36,7 +36,7 @@ services:
write_timeout: 60000
upstreams:
- algorithm: round-robin
name: httproute.default.httproute-testing.0
name: httproute.default.svc.default.httpbin.80.75_default.nginx.8080.25
tags:
- k8s-name:httproute-testing
- k8s-namespace:default
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
_format_version: "3.0"
services:
- connect_timeout: 60000
host: httproute.default.httproute-testing.0
id: 4e3cb785-a8d0-5866-aa05-117f7c64f24d
name: httproute.default.httproute-testing.0
host: httproute.default.svc.default.httpbin.80.75_default.nginx.8080.25
id: 112d14cb-01c4-549a-aa72-1a4a234c0372
name: httproute.default.svc.default.httpbin.80.75_default.nginx.8080.25
port: 8080
protocol: http
read_timeout: 60000
Expand Down Expand Up @@ -47,7 +47,7 @@ services:
write_timeout: 60000
upstreams:
- algorithm: round-robin
name: httproute.default.httproute-testing.0
name: httproute.default.svc.default.httpbin.80.75_default.nginx.8080.25
tags:
- k8s-name:httproute-testing
- k8s-namespace:default
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
_format_version: "3.0"
services:
- connect_timeout: 60000
host: httproute.default.httpbin.1
id: bbf738ea-19e7-5277-8a48-36d555e5ae94
name: httproute.default.httpbin.1
host: httproute.default.svc.default.httpbin-prod.80.75_default.httpbin-test.80.25
id: b6fa5f0e-5164-504b-a000-3b568d82c8a1
name: httproute.default.svc.default.httpbin-prod.80.75_default.httpbin-test.80.25
port: 80
protocol: http
read_timeout: 60000
retries: 5
routes:
- https_redirect_status_code: 426
id: e148326c-f112-5b0d-9644-4219ccb43cb6
name: httproute.default.httpbin.1.0
id: 668cce91-3da3-590f-82c9-8296c5bb28e5
name: httproute.default.httpbin.0.0
path_handling: v0
paths:
- ~/httpbin-prod-only$
- /httpbin-prod-only/
- ~/httpbin-with-test$
- /httpbin-with-test/
preserve_host: true
protocols:
- http
Expand All @@ -28,27 +28,28 @@ services:
- k8s-group:gateway.networking.k8s.io
- k8s-version:v1
tags:
- k8s-name:httpbin-prod
- k8s-name:httpbin
- k8s-namespace:default
- k8s-kind:Service
- k8s-kind:HTTPRoute
- k8s-group:gateway.networking.k8s.io
- k8s-version:v1
write_timeout: 60000
- connect_timeout: 60000
host: httproute.default.httpbin.0
id: de4fffcc-97bc-59c6-9ccd-f1fb29b93beb
name: httproute.default.httpbin.0
host: httproute.default.svc.default.httpbin-prod.80
id: a1a4f16c-ce16-57c7-8a4b-79789bdc9603
name: httproute.default.svc.default.httpbin-prod.80
port: 80
protocol: http
read_timeout: 60000
retries: 5
routes:
- https_redirect_status_code: 426
id: 668cce91-3da3-590f-82c9-8296c5bb28e5
name: httproute.default.httpbin.0.0
id: e148326c-f112-5b0d-9644-4219ccb43cb6
name: httproute.default.httpbin.1.0
path_handling: v0
paths:
- ~/httpbin-with-test$
- /httpbin-with-test/
- ~/httpbin-prod-only$
- /httpbin-prod-only/
preserve_host: true
protocols:
- http
Expand All @@ -61,10 +62,9 @@ services:
- k8s-group:gateway.networking.k8s.io
- k8s-version:v1
tags:
- k8s-name:httpbin
- k8s-name:httpbin-prod
- k8s-namespace:default
- k8s-kind:HTTPRoute
- k8s-group:gateway.networking.k8s.io
- k8s-kind:Service
- k8s-version:v1
write_timeout: 60000
upstreams:
Expand Down Expand Up @@ -104,12 +104,13 @@ upstreams:
unhealthy:
tcp_failures: 5
timeouts: 10
name: httproute.default.httpbin.1
name: httproute.default.svc.default.httpbin-prod.80.75_default.httpbin-test.80.25
slots: 100
tags:
- k8s-name:httpbin-prod
- k8s-name:httpbin
- k8s-namespace:default
- k8s-kind:Service
- k8s-kind:HTTPRoute
- k8s-group:gateway.networking.k8s.io
- k8s-version:v1
- algorithm: consistent-hashing
hash_fallback: consumer
Expand Down Expand Up @@ -147,11 +148,10 @@ upstreams:
unhealthy:
tcp_failures: 5
timeouts: 10
name: httproute.default.httpbin.0
name: httproute.default.svc.default.httpbin-prod.80
slots: 100
tags:
- k8s-name:httpbin
- k8s-name:httpbin-prod
- k8s-namespace:default
- k8s-kind:HTTPRoute
- k8s-group:gateway.networking.k8s.io
- k8s-kind:Service
- k8s-version:v1
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
_format_version: "3.0"
services:
- connect_timeout: 60000
host: httproute.default.test.0
id: 5879d21c-33ed-5355-be10-f0911e04d397
name: httproute.default.test.0
host: httproute.default.svc.default.one.80.50_other.two.80.50
id: cb8be55b-3c05-50ab-9189-964d026b1312
name: httproute.default.svc.default.one.80.50_other.two.80.50
port: 80
protocol: http
read_timeout: 60000
Expand Down Expand Up @@ -36,7 +36,7 @@ services:
write_timeout: 60000
upstreams:
- algorithm: round-robin
name: httproute.default.test.0
name: httproute.default.svc.default.one.80.50_other.two.80.50
tags:
- k8s-name:test
- k8s-namespace:default
Expand Down
123 changes: 123 additions & 0 deletions internal/dataplane/translator/subtranslator/backendref.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package subtranslator

import (
"errors"
"fmt"

"github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/kongstate"
"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
)

// backendRefsToKongStateBackends takes a list of BackendRefs and returns a list of ServiceBackends.
// The backendRefs are checked for the following conditions. If any of these conditions are met, the BackendRef is
// not included in the returned list:
// - If a BackendRef is not permitted by the provided ReferenceGrantTo set,
// - If a BackendRef is not found,
// - If a BackendRef Group & Kind pair is not supported (currently only Service is supported),
// - If a BackendRef is missing a port.
// The provided client is used to retrieve the Backend referenced by the BackendRef
// to check if it exists.
func backendRefsToKongStateBackends(
logger logr.Logger,
storer store.Storer,
route client.Object,
backendRefs []gatewayapi.BackendRef,
allowed map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo,
) kongstate.ServiceBackends {
backends := kongstate.ServiceBackends{}

for _, backendRef := range backendRefs {
logger := loggerForBackendRef(logger, route, backendRef)

nn := client.ObjectKey{
Name: string(backendRef.Name),
Namespace: route.GetNamespace(),
}
if backendRef.Namespace != nil {
nn.Namespace = string(*backendRef.Namespace)
}

if backendRef.Kind == nil {
// This should never happen as the default value defined by Gateway API is 'Service'. Checking just in case.
logger.Error(nil, "Object requested backendRef to target, but no Kind was specified, skipping...")
continue
}

var err error
switch *backendRef.Kind {
case "Service":
_, err = storer.GetService(nn.Namespace, nn.Name)
default:
err = fmt.Errorf("unsupported kind %q, only 'Service' is supported", *backendRef.Kind)
}
if err != nil {
if errors.As(err, &store.NotFoundError{}) {
logger.Error(err, "Object requested backendRef to target, but it does not exist, skipping...")
} else {
logger.Error(err, "Object requested backendRef to target, but an error occurred, skipping...")
}
continue
}

if !util.IsBackendRefGroupKindSupported(backendRef.Group, backendRef.Kind) ||
!gatewayapi.NewRefCheckerForRoute(logger, route, backendRef).IsRefAllowedByGrant(allowed) {
// we log impermissible refs rather than failing the entire rule. while we cannot actually route to
// these, we do not want a single impermissible ref to take the entire rule offline. in the case of edits,
// failing the entire rule could potentially delete routes that were previously online and in use, and
// that remain viable (because they still have some permissible backendRefs)
logger.Error(nil, "Object requested backendRef to target, but no ReferenceGrant permits it, skipping...")
continue
}

port := int32(-1)
if backendRef.Port != nil {
port = int32(*backendRef.Port)
}
backend, err := kongstate.NewServiceBackendForService(
nn,
kongstate.PortDef{
Mode: kongstate.PortModeByNumber,
Number: port,
},
)
if err != nil {
logger.Error(err, "failed to create ServiceBackend for backendRef")
continue
}
if backendRef.Weight != nil {
backend.SetWeight(*backendRef.Weight)
}
backends = append(backends, backend)
}

return backends
}

func loggerForBackendRef(logger logr.Logger, route client.Object, backendRef gatewayapi.BackendRef) logr.Logger {
var (
namespace = route.GetNamespace()
kind = "unknown"
)
if backendRef.Namespace != nil {
namespace = string(*backendRef.Namespace)
}
if backendRef.Kind != nil {
kind = string(*backendRef.Kind)
}

objName := fmt.Sprintf("%s %s/%s",
route.GetObjectKind().GroupVersionKind().String(),
route.GetNamespace(),
route.GetName())
return logger.WithValues(
"object_name", objName,
"target_kind", kind,
"target_namespace", namespace,
"target_name", backendRef.Name,
)
}
Loading