Skip to content

Commit

Permalink
feat: add support of KongServiceFacade as default Ingress backend
Browse files Browse the repository at this point in the history
  • Loading branch information
czeslavo committed Dec 4, 2023
1 parent 96941c3 commit dc100c7
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 55 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Adding a new version? You'll need three changes:
When installed, it has to be enabled with `ServiceFacade` feature gate.
[#5220](https:/Kong/kubernetes-ingress-controller/pull/5220)
[#5234](https:/Kong/kubernetes-ingress-controller/pull/5234)
[#5282](https:/Kong/kubernetes-ingress-controller/pull/5282)
- Added support for GRPC over HTTP (without TLS) in Gateway API.
[#5128](https:/Kong/kubernetes-ingress-controller/pull/5128)
- Added `-init-cache-sync-duration` CLI flag. This flag configures how long the controller waits for Kubernetes resources to populate at startup before generating the initial Kong configuration. It also fixes a bug that removed the default 5 second wait period.
Expand Down
5 changes: 3 additions & 2 deletions internal/dataplane/translator/subtranslator/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func (i *ingressTranslationIndex) getIngressPathBackend(namespace string, httpIn
}

if resource := httpIngressPath.Backend.Resource; resource != nil {
if !isKongServiceFacade(resource) {
if !IsKongServiceFacade(resource) {
gk := resource.Kind
if resource.APIGroup != nil {
gk = *resource.APIGroup + "/" + gk
Expand All @@ -209,7 +209,8 @@ func (i *ingressTranslationIndex) getIngressPathBackend(namespace string, httpIn
return ingressTranslationMetaBackend{}, fmt.Errorf("no Service or Resource specified for Ingress path")
}

func isKongServiceFacade(resource *corev1.TypedLocalObjectReference) bool {
// IsKongServiceFacade returns true if the given resource reference is a KongServiceFacade.
func IsKongServiceFacade(resource *corev1.TypedLocalObjectReference) bool {
return resource.Kind == incubatorv1alpha1.KongServiceFacadeKind &&
resource.APIGroup != nil && *resource.APIGroup == incubatorv1alpha1.GroupVersion.Group
}
Expand Down
155 changes: 119 additions & 36 deletions internal/dataplane/translator/translate_ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import (
"sort"

"github.com/kong/go-kong/kong"
corev1 "k8s.io/api/core/v1"
netv1 "k8s.io/api/networking/v1"

"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/failures"
"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/kongstate"
"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/translator/atc"
"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/translator/subtranslator"
"github.com/kong/kubernetes-ingress-controller/v3/internal/manager/featuregates"
"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
"github.com/kong/kubernetes-ingress-controller/v3/internal/util"
)
Expand Down Expand Up @@ -66,7 +69,7 @@ func (t *Translator) ingressRulesFromIngressV1() ingressRules {
}

// Add a default backend if it exists.
defaultBackendService, ok := getDefaultBackendService(allDefaultBackends, t.featureFlags.ExpressionRoutes)
defaultBackendService, ok := getDefaultBackendService(t.storer, t.failuresCollector, allDefaultBackends, t.featureFlags)
if ok {
// When such service would overwrite an existing service, merge the routes.
if svc, ok := result.ServiceNameToServices[*defaultBackendService.Name]; ok {
Expand All @@ -81,53 +84,133 @@ func (t *Translator) ingressRulesFromIngressV1() ingressRules {
}

// getDefaultBackendService picks the oldest Ingress with a DefaultBackend defined and returns a Kong Service for it.
func getDefaultBackendService(allDefaultBackends []netv1.Ingress, expressionRoutes bool) (kongstate.Service, bool) {
func getDefaultBackendService(
storer store.Storer,
failuresCollector *failures.ResourceFailuresCollector,
allDefaultBackends []netv1.Ingress,
features FeatureFlags,
) (kongstate.Service, bool) {
// Sort the default backends by creation timestamp, so that the oldest one is picked.
sort.SliceStable(allDefaultBackends, func(i, j int) bool {
return allDefaultBackends[i].CreationTimestamp.Before(&allDefaultBackends[j].CreationTimestamp)
})

if len(allDefaultBackends) > 0 {
ingress := allDefaultBackends[0]
defaultBackend := allDefaultBackends[0].Spec.DefaultBackend
port := subtranslator.PortDefFromServiceBackendPort(&defaultBackend.Service.Port)
serviceName := fmt.Sprintf(
"%s.%s.%s",
allDefaultBackends[0].Namespace,
defaultBackend.Service.Name,
port.CanonicalString(),
)
service := kongstate.Service{
Service: kong.Service{
Name: kong.String(serviceName),
Host: kong.String(fmt.Sprintf(
"%s.%s.%s.svc",
defaultBackend.Service.Name,
ingress.Namespace,
port.CanonicalString(),
)),
Port: kong.Int(DefaultHTTPPort),
Protocol: kong.String("http"),
ConnectTimeout: kong.Int(DefaultServiceTimeout),
ReadTimeout: kong.Int(DefaultServiceTimeout),
WriteTimeout: kong.Int(DefaultServiceTimeout),
Retries: kong.Int(DefaultRetries),
Tags: util.GenerateTagsForObject(&ingress),
},
Namespace: ingress.Namespace,
Backends: []kongstate.ServiceBackend{{
Name: defaultBackend.Service.Name,
PortDef: port,
}},
Parent: &ingress,
defaultBackend := ingress.Spec.DefaultBackend
route := translateIngressDefaultBackendRoute(&ingress, util.GenerateTagsForObject(&ingress), features.ExpressionRoutes)

// If the default backend is defined as an arbitrary resource, then we need handle it differently.
if resource := defaultBackend.Resource; resource != nil {
return translateIngressDefaultBackendResource(
resource,
ingress,
route,
storer,
failuresCollector,
features,
)
}
r := translateIngressDefaultBackendRoute(&ingress, util.GenerateTagsForObject(&ingress), expressionRoutes)
service.Routes = append(service.Routes, *r)
return service, true

// Otherwise, the default backend is defined as a Kubernetes Service.
return translateIngressDefaultBackendService(ingress, route)
}

return kongstate.Service{}, false
}

func translateIngressDefaultBackendResource(
resource *corev1.TypedLocalObjectReference,
ingress netv1.Ingress,
route *kongstate.Route,
storer store.Storer,
failuresCollector *failures.ResourceFailuresCollector,
features FeatureFlags,
) (kongstate.Service, bool) {
if !subtranslator.IsKongServiceFacade(resource) {
gk := resource.Kind
if resource.APIGroup != nil {
gk = *resource.APIGroup + "/" + gk
}
failuresCollector.PushResourceFailure(fmt.Sprintf("default backend: unknown resource type %s", gk), &ingress)
return kongstate.Service{}, false
}
if !features.KongServiceFacade {
failuresCollector.PushResourceFailure(
fmt.Sprintf("default backend: KongServiceFacade is not enabled, please set the %q feature gate to 'true' to enable it", featuregates.KongServiceFacade),
&ingress,
)
return kongstate.Service{}, false
}
facade, err := storer.GetKongServiceFacade(ingress.Namespace, resource.Name)
if err != nil {
failuresCollector.PushResourceFailure(
fmt.Sprintf("default backend: KongServiceFacade %q could not be fetched: %s", resource.Name, err),
&ingress,
)
return kongstate.Service{}, false
}

serviceName := fmt.Sprintf("%s.%s.svc.facade", ingress.Namespace, resource.Name)
return kongstate.Service{
Service: kong.Service{
Name: kong.String(serviceName),
Host: kong.String(serviceName),
Port: kong.Int(DefaultHTTPPort),
Protocol: kong.String("http"),
ConnectTimeout: kong.Int(DefaultServiceTimeout),
ReadTimeout: kong.Int(DefaultServiceTimeout),
WriteTimeout: kong.Int(DefaultServiceTimeout),
Retries: kong.Int(DefaultRetries),
},
Namespace: ingress.Namespace,
Backends: []kongstate.ServiceBackend{{
Type: kongstate.ServiceBackendTypeKongServiceFacade,
Name: resource.Name,
Namespace: ingress.Namespace,
PortDef: subtranslator.PortDefFromPortNumber(facade.Spec.Backend.Port),
}},
Parent: facade,
Routes: []kongstate.Route{*route},
}, true
}

func translateIngressDefaultBackendService(ingress netv1.Ingress, route *kongstate.Route) (kongstate.Service, bool) {
defaultBackend := ingress.Spec.DefaultBackend
port := subtranslator.PortDefFromServiceBackendPort(&defaultBackend.Service.Port)
serviceName := fmt.Sprintf(
"%s.%s.%s",
ingress.Namespace,
defaultBackend.Service.Name,
port.CanonicalString(),
)
return kongstate.Service{
Service: kong.Service{
Name: kong.String(serviceName),
Host: kong.String(fmt.Sprintf(
"%s.%s.%s.svc",
defaultBackend.Service.Name,
ingress.Namespace,
port.CanonicalString(),
)),
Port: kong.Int(DefaultHTTPPort),
Protocol: kong.String("http"),
ConnectTimeout: kong.Int(DefaultServiceTimeout),
ReadTimeout: kong.Int(DefaultServiceTimeout),
WriteTimeout: kong.Int(DefaultServiceTimeout),
Retries: kong.Int(DefaultRetries),
Tags: util.GenerateTagsForObject(&ingress),
},
Namespace: ingress.Namespace,
Backends: []kongstate.ServiceBackend{{
Name: defaultBackend.Service.Name,
PortDef: port,
}},
Parent: &ingress,
Routes: []kongstate.Route{*route},
}, true
}

func translateIngressDefaultBackendRoute(ingress *netv1.Ingress, tags []*string, expressionRoutes bool) *kongstate.Route {
r := &kongstate.Route{
Ingress: util.FromK8sObject(ingress),
Expand Down
Loading

0 comments on commit dc100c7

Please sign in to comment.