diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index 0e4ec863d..5afac6097 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -1322,7 +1322,7 @@ Register the `IngressRouteUDP` [kind](../../reference/dynamic-configuration/kube |------|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [1] | `entryPoints` | List of [entrypoints](../routers/index.md#entrypoints_1) names | | [2] | `routes` | List of routes | -| [3] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions | +| [3] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions (See below for `ExternalName Service` setup) | | [4] | `services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) | | [6] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. | | [7] | `services[n].weight` | Defines the weight to apply to the server load balancing | @@ -1348,6 +1348,105 @@ Register the `IngressRouteUDP` [kind](../../reference/dynamic-configuration/kube weight: 10 ``` +!!! important "Using Kubernetes ExternalName Service" + + Traefik backends creation needs a port to be set, however Kubernetes [ExternalName Service](https://kubernetes.io/fr/docs/concepts/services-networking/service/#externalname) could be defined without any port. + Accordingly, Traefik supports defining a port in two ways: + + - only on `IngressRouteUDP` service + - on both sides, you'll be warned if the ports don't match, and the `IngressRouteUDP` service port is used + + Thus, in case of two sides port definition, Traefik expects a match between ports. + + ??? example "Examples" + + ```yaml tab="IngressRouteUDP" + --- + apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRouteUDP + metadata: + name: test.route + namespace: default + + spec: + entryPoints: + - foo + + routes: + - services: + - name: external-svc + port: 80 + + --- + apiVersion: v1 + kind: Service + metadata: + name: external-svc + namespace: default + spec: + externalName: external.domain + type: ExternalName + ``` + + ```yaml tab="ExternalName Service" + --- + apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRouteUDP + metadata: + name: test.route + namespace: default + + spec: + entryPoints: + - foo + + routes: + - services: + - name: external-svc + + --- + apiVersion: v1 + kind: Service + metadata: + name: external-svc + namespace: default + spec: + externalName: external.domain + type: ExternalName + ports: + - port: 80 + ``` + + ```yaml tab="Both sides" + --- + apiVersion: traefik.containo.us/v1alpha1 + kind: IngressRouteUDP + metadata: + name: test.route + namespace: default + + spec: + entryPoints: + - foo + + routes: + - services: + - name: external-svc + port: 80 + + --- + apiVersion: v1 + kind: Service + metadata: + name: external-svc + namespace: default + spec: + externalName: external.domain + type: ExternalName + ports: + - port: 80 + ``` + ### Kind: `TLSOption` `TLSOption` is the CRD implementation of a [Traefik "TLS Option"](../../https/tls.md#tls-options). diff --git a/integration/fixtures/k8s/05-ingressrouteudp.yml b/integration/fixtures/k8s/05-ingressrouteudp.yml index 535a9e4d4..358559c23 100644 --- a/integration/fixtures/k8s/05-ingressrouteudp.yml +++ b/integration/fixtures/k8s/05-ingressrouteudp.yml @@ -12,3 +12,5 @@ spec: - name: whoamiudp namespace: default port: 8090 + - name: externalname-svc + port: 9090 diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json index ea43e5b5d..8b7eaad6e 100644 --- a/integration/testdata/rawdata-crd.json +++ b/integration/testdata/rawdata-crd.json @@ -317,7 +317,17 @@ } }, "udpServices": { - "default-test3.route-0@kubernetescrd": { + "default-test3.route-0-externalname-svc-9090@kubernetescrd": { + "loadBalancer": { + "servers": [ + { + "address": "domain.com:9090" + } + ] + }, + "status": "enabled" + }, + "default-test3.route-0-whoamiudp-8090@kubernetescrd": { "loadBalancer": { "servers": [ { @@ -328,6 +338,21 @@ } ] }, + "status": "enabled" + }, + "default-test3.route-0@kubernetescrd": { + "weighted": { + "services": [ + { + "name": "default-test3.route-0-whoamiudp-8090", + "weight": 1 + }, + { + "name": "default-test3.route-0-externalname-svc-9090", + "weight": 1 + } + ] + }, "status": "enabled", "usedBy": [ "default-test3.route-0@kubernetescrd" diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml index 887ddc385..daf6dd2ad 100644 --- a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml @@ -146,6 +146,43 @@ spec: app: traefiklabs task: whoamiudp +--- +apiVersion: v1 +kind: Service +metadata: + name: external-svc + namespace: default +spec: + externalName: external.domain + type: ExternalName + +--- +apiVersion: v1 +kind: Service +metadata: + name: external.service.with.port + namespace: default +spec: + externalName: external.domain + type: ExternalName + ports: + - name: http + protocol: TCP + port: 80 + +--- +apiVersion: v1 +kind: Service +metadata: + name: external.service.without.port + namespace: default +spec: + externalName: external.domain + type: ExternalName + ports: + - name: http + protocol: TCP + --- kind: Endpoints apiVersion: v1 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname.yml new file mode 100644 index 000000000..14c3ab658 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname.yml @@ -0,0 +1,14 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteUDP +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - foo + + routes: + - services: + - name: external-svc + port: 8000 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_with_port.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_with_port.yml new file mode 100644 index 000000000..962178ed5 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_with_port.yml @@ -0,0 +1,14 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteUDP +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - foo + + routes: + - services: + - name: external.service.with.port + port: 80 diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_without_ports.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_without_ports.yml new file mode 100644 index 000000000..6478e6aa4 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_externalname_without_ports.yml @@ -0,0 +1,13 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteUDP +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - foo + + routes: + - services: + - name: external-svc diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 2c2e84e6d..fdffa31b3 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -82,11 +82,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -123,11 +121,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -137,11 +133,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -179,11 +173,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -423,11 +415,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -467,11 +457,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -532,11 +520,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -595,11 +581,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -657,11 +641,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -708,11 +690,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -759,11 +739,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -801,11 +779,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -844,11 +820,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, TerminationDelay: Int(500), @@ -893,11 +867,9 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -938,7 +910,6 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "external.domain:8000", - Port: "", }, }, }, @@ -976,7 +947,6 @@ func TestLoadIngressRouteTCPs(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "external.domain:80", - Port: "", }, }, }, @@ -3036,11 +3006,9 @@ func TestLoadIngressRoutes(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, ProxyProtocol: &dynamic.ProxyProtocol{Version: 2}, @@ -3393,11 +3361,9 @@ func TestLoadIngressRouteUDPs(t *testing.T) { Servers: []dynamic.UDPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -3438,11 +3404,9 @@ func TestLoadIngressRouteUDPs(t *testing.T) { Servers: []dynamic.UDPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -3452,11 +3416,9 @@ func TestLoadIngressRouteUDPs(t *testing.T) { Servers: []dynamic.UDPServer{ { Address: "10.10.0.3:8080", - Port: "", }, { Address: "10.10.0.4:8080", - Port: "", }, }, }, @@ -3622,6 +3584,104 @@ func TestLoadIngressRouteUDPs(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Simple Ingress Route, with externalName service", + paths: []string{"udp/services.yml", "udp/with_externalname.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "default-test.route-0": { + EntryPoints: []string{"foo"}, + Service: "default-test.route-0", + }, + }, + Services: map[string]*dynamic.UDPService{ + "default-test.route-0": { + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ + { + Address: "external.domain:8000", + }, + }, + }, + }, + }, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Ingress Route, externalName service with port", + paths: []string{"udp/services.yml", "udp/with_externalname_with_port.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "default-test.route-0": { + EntryPoints: []string{"foo"}, + Service: "default-test.route-0", + }, + }, + Services: map[string]*dynamic.UDPService{ + "default-test.route-0": { + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ + { + Address: "external.domain:80", + }, + }, + }, + }, + }, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Ingress Route, externalName service without port", + paths: []string{"udp/services.yml", "udp/with_externalname_without_ports.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "default-test.route-0": { + EntryPoints: []string{"foo"}, + Service: "default-test.route-0", + }, + }, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, { desc: "Ingress class does not match", paths: []string{"udp/services.yml", "udp/simple.yml"}, @@ -4359,11 +4419,9 @@ func TestCrossNamespace(t *testing.T) { Servers: []dynamic.TCPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, @@ -4419,11 +4477,9 @@ func TestCrossNamespace(t *testing.T) { Servers: []dynamic.UDPServer{ { Address: "10.10.0.1:8000", - Port: "", }, { Address: "10.10.0.2:8000", - Port: "", }, }, }, diff --git a/pkg/provider/kubernetes/crd/kubernetes_udp.go b/pkg/provider/kubernetes/crd/kubernetes_udp.go index 35825f741..808b6bff0 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_udp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_udp.go @@ -11,7 +11,6 @@ import ( "github.com/traefik/traefik/v2/pkg/log" "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/util/intstr" ) func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client Client) *dynamic.UDPConfiguration { @@ -112,23 +111,15 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([ return nil, errors.New("service not found") } - var portSpec *corev1.ServicePort - for _, p := range service.Spec.Ports { - p := p - if (svc.Port.Type == intstr.Int && svc.Port.IntVal == p.Port) || (svc.Port.Type == intstr.String && svc.Port.StrVal == p.Name) { - portSpec = &p - break - } - } - - if portSpec == nil { - return nil, errors.New("service port not found") + svcPort, err := getServicePort(service, svc.Port) + if err != nil { + return nil, err } var servers []dynamic.UDPServer if service.Spec.Type == corev1.ServiceTypeExternalName { servers = append(servers, dynamic.UDPServer{ - Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(portSpec.Port))), + Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(svcPort.Port))), }) } else { endpoints, endpointsExists, endpointsErr := client.GetEndpoints(namespace, svc.Name) @@ -147,7 +138,7 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([ var port int32 for _, subset := range endpoints.Subsets { for _, p := range subset.Ports { - if portSpec.Name == p.Name { + if svcPort.Name == p.Name { port = p.Port break }