Add named port support to Kubernetes IngressRoute CRDs

This commit is contained in:
Cirrith 2021-01-15 06:54:04 -08:00 committed by GitHub
parent b1ddd0e038
commit bbee63fcf3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 154 additions and 57 deletions

View file

@ -145,7 +145,7 @@ The Kubernetes Ingress Controller, The Custom Resource Way.
spec:
entryPoints:
- fooudp
- udpep
routes:
- kind: Rule
services:
@ -331,7 +331,7 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
name: foo
namespace: default
passHostHeader: true
port: 80
port: 80 # [9]
responseForwarding:
flushInterval: 1ms
scheme: https
@ -343,38 +343,39 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
sameSite: none
strategy: RoundRobin
weight: 10
tls: # [9]
secretName: supersecret # [10]
options: # [11]
name: opt # [12]
namespace: default # [13]
certResolver: foo # [14]
domains: # [15]
- main: example.net # [16]
sans: # [17]
tls: # [10]
secretName: supersecret # [11]
options: # [12]
name: opt # [13]
namespace: default # [14]
certResolver: foo # [15]
domains: # [16]
- main: example.net # [17]
sans: # [18]
- a.example.net
- b.example.net
```
| Ref | Attribute | Purpose |
|------|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [1] | `entryPoints` | List of [entry points](../routers/index.md#entrypoints) names |
| [2] | `routes` | List of routes |
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule) corresponding to an underlying router. |
| [4] | `routes[n].priority` | [Disambiguate](../routers/index.md#priority) rules of the same length, for route matching |
| [5] | `routes[n].middlewares` | List of reference to [Middleware](#kind-middleware) |
| [6] | `middlewares[n].name` | Defines the [Middleware](#kind-middleware) name |
| [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace |
| [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) (See below for `ExternalName Service` setup) |
| [9] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration |
| [10] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
| [11] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
| [12] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
| [13] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
| [14] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) |
| [15] | `tls.domains` | List of [domains](../routers/index.md#domains) |
| [16] | `domains[n].main` | Defines the main domain name |
| [17] | `domains[n].sans` | List of SANs (alternative domains) |
| Ref | Attribute | Purpose |
|------|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [1] | `entryPoints` | List of [entry points](../routers/index.md#entrypoints) names |
| [2] | `routes` | List of routes |
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule) corresponding to an underlying router. |
| [4] | `routes[n].priority` | [Disambiguate](../routers/index.md#priority) rules of the same length, for route matching |
| [5] | `routes[n].middlewares` | List of reference to [Middleware](#kind-middleware) |
| [6] | `middlewares[n].name` | Defines the [Middleware](#kind-middleware) name |
| [7] | `middlewares[n].namespace` | Defines the [Middleware](#kind-middleware) namespace |
| [8] | `routes[n].services` | List of any combination of [TraefikService](#kind-traefikservice) and reference to a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) (See below for `ExternalName Service` setup) |
| [9] | `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. |
| [10] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration |
| [11] | `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace) |
| [12] | `tls.options` | Defines the reference to a [TLSOption](#kind-tlsoption) |
| [13] | `options.name` | Defines the [TLSOption](#kind-tlsoption) name |
| [14] | `options.namespace` | Defines the [TLSOption](#kind-tlsoption) namespace |
| [15] | `tls.certResolver` | Defines the reference to a [CertResolver](../routers/index.md#certresolver) |
| [16] | `tls.domains` | List of [domains](../routers/index.md#domains) |
| [17] | `domains[n].main` | Defines the main domain name |
| [18] | `domains[n].sans` | List of SANs (alternative domains) |
??? example "Declaring an IngressRoute"
@ -1113,7 +1114,7 @@ Register the `IngressRouteTCP` [kind](../../reference/dynamic-configuration/kube
| [3] | `routes[n].match` | Defines the [rule](../routers/index.md#rule_1) corresponding to an underlying router |
| [4] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions (See below for `ExternalName Service` setup) |
| [5] | `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/) |
| [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 |
| [8] | `services[n].terminationDelay` | corresponds to the deadline that the proxy sets, after one of its connected peers indicates it has closed the writing capability of its connection, to close the reading capability as well, hence fully terminating the connection. It is a duration in milliseconds, defaulting to 100. A negative value means an infinite deadline (i.e. the reading capability is never closed). |
| [9] | `proxyProtocol` | Defines the [PROXY protocol](../services/index.md#proxy-protocol) configuration |
@ -1323,7 +1324,7 @@ Register the `IngressRouteUDP` [kind](../../reference/dynamic-configuration/kube
| [2] | `routes` | List of routes |
| [3] | `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions |
| [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/) |
| [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 |
??? example "Declaring an IngressRouteUDP"

View file

@ -24,6 +24,7 @@ import (
"github.com/traefik/traefik/v2/pkg/tls"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/intstr"
)
const (
@ -323,18 +324,18 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
return conf
}
func getServicePort(svc *corev1.Service, port int32) (*corev1.ServicePort, error) {
func getServicePort(svc *corev1.Service, port intstr.IntOrString) (*corev1.ServicePort, error) {
if svc == nil {
return nil, errors.New("service is not defined")
}
if port == 0 {
if (port.Type == intstr.Int && port.IntVal == 0) || (port.Type == intstr.String && port.StrVal == "") {
return nil, errors.New("ingressRoute service port not defined")
}
hasValidPort := false
for _, p := range svc.Spec.Ports {
if p.Port == port {
if (port.Type == intstr.Int && port.IntVal == p.Port) || (port.Type == intstr.String && port.StrVal == p.Name) {
return &p, nil
}
@ -343,8 +344,8 @@ func getServicePort(svc *corev1.Service, port int32) (*corev1.ServicePort, error
}
}
if svc.Spec.Type != corev1.ServiceTypeExternalName {
return nil, fmt.Errorf("service port not found: %d", port)
if svc.Spec.Type != corev1.ServiceTypeExternalName || port.Type == intstr.String {
return nil, fmt.Errorf("service port not found: %s", &port)
}
if hasValidPort {
@ -352,7 +353,7 @@ func getServicePort(svc *corev1.Service, port int32) (*corev1.ServicePort, error
Warning("The port %d from IngressRoute doesn't match with ports defined in the ExternalName service %s/%s.", port, svc.Namespace, svc.Name)
}
return &corev1.ServicePort{Port: port}, nil
return &corev1.ServicePort{Port: port.IntVal}, nil
}
func (p *Provider) createErrorPageMiddleware(client Client, namespace string, errorPage *v1alpha1.ErrorPage) (*dynamic.ErrorPage, *dynamic.Service, error) {

View file

@ -14,6 +14,7 @@ import (
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
"github.com/traefik/traefik/v2/pkg/tls"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
const (
@ -399,7 +400,7 @@ func (c configBuilder) nameAndService(ctx context.Context, parentNamespace strin
return fullName, serversLB, nil
case service.Kind == "TraefikService":
return fullServiceName(svcCtx, namespace, service, 0), nil, nil
return fullServiceName(svcCtx, namespace, service, intstr.FromInt(0)), nil, nil
default:
return "", nil, fmt.Errorf("unsupported service kind %s", service.Kind)
}
@ -414,9 +415,9 @@ func splitSvcNameProvider(name string) (string, string) {
return svc, pvd
}
func fullServiceName(ctx context.Context, namespace string, service v1alpha1.LoadBalancerSpec, port int32) string {
if port != 0 {
return provider.Normalize(fmt.Sprintf("%s-%s-%d", namespace, service.Name, port))
func fullServiceName(ctx context.Context, namespace string, service v1alpha1.LoadBalancerSpec, port intstr.IntOrString) string {
if (port.Type == intstr.Int && port.IntVal != 0) || (port.Type == intstr.String && port.StrVal != "") {
return provider.Normalize(fmt.Sprintf("%s-%s-%s", namespace, service.Name, &port))
}
if !strings.Contains(service.Name, providerNamespaceSeparator) {

View file

@ -71,7 +71,7 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
break
}
serviceKey := fmt.Sprintf("%s-%s-%d", serviceName, service.Name, service.Port)
serviceKey := fmt.Sprintf("%s-%s-%s", serviceName, service.Name, &service.Port)
conf.Services[serviceKey] = balancerServerTCP
srv := dynamic.TCPWRRService{Name: serviceKey}

View file

@ -18,6 +18,7 @@ import (
"github.com/traefik/traefik/v2/pkg/tls"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
kubefake "k8s.io/client-go/kubernetes/fake"
)
@ -3738,7 +3739,7 @@ func TestGetServicePort(t *testing.T) {
testCases := []struct {
desc string
svc *corev1.Service
port int32
port intstr.IntOrString
expected *corev1.ServicePort
expectError bool
}{
@ -3757,7 +3758,7 @@ func TestGetServicePort(t *testing.T) {
},
},
},
port: 80,
port: intstr.FromInt(80),
expected: &corev1.ServicePort{
Port: 80,
},
@ -3785,12 +3786,57 @@ func TestGetServicePort(t *testing.T) {
},
expectError: true,
},
{
desc: "Matching named port",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 80,
},
},
},
},
port: intstr.FromString("http"),
expected: &corev1.ServicePort{
Name: "http",
Port: 80,
},
},
{
desc: "Matching named port (with external name)",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeExternalName,
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 80,
},
},
},
},
port: intstr.FromString("http"),
expected: &corev1.ServicePort{
Name: "http",
Port: 80,
},
},
{
desc: "Mismatching, only port(Ingress) defined",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{},
},
port: 80,
port: intstr.FromInt(80),
expectError: true,
},
{
desc: "Mismatching, only named port(Ingress) defined",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{},
},
port: intstr.FromString("http"),
expectError: true,
},
{
@ -3800,11 +3846,21 @@ func TestGetServicePort(t *testing.T) {
Type: corev1.ServiceTypeExternalName,
},
},
port: 80,
port: intstr.FromInt(80),
expected: &corev1.ServicePort{
Port: 80,
},
},
{
desc: "Mismatching, only named port(Ingress) defined with external name",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeExternalName,
},
},
port: intstr.FromString("http"),
expectError: true,
},
{
desc: "Mismatching, only Service port defined",
svc: &corev1.Service{
@ -3843,7 +3899,22 @@ func TestGetServicePort(t *testing.T) {
},
},
},
port: 443,
port: intstr.FromInt(443),
expectError: true,
},
{
desc: "Two different named ports defined",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Name: "foo",
Port: 80,
},
},
},
},
port: intstr.FromString("bar"),
expectError: true,
},
{
@ -3858,11 +3929,27 @@ func TestGetServicePort(t *testing.T) {
},
},
},
port: 443,
port: intstr.FromInt(443),
expected: &corev1.ServicePort{
Port: 443,
},
},
{
desc: "Two different named ports defined (with external name)",
svc: &corev1.Service{
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeExternalName,
Ports: []corev1.ServicePort{
{
Name: "foo",
Port: 80,
},
},
},
},
port: intstr.FromString("bar"),
expectError: true,
},
}
for _, test := range testCases {
test := test

View file

@ -11,6 +11,7 @@ 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 {
@ -52,7 +53,7 @@ func (p *Provider) loadIngressRouteUDPConfiguration(ctx context.Context, client
break
}
serviceKey := fmt.Sprintf("%s-%s-%d", serviceName, service.Name, service.Port)
serviceKey := fmt.Sprintf("%s-%s-%s", serviceName, service.Name, &service.Port)
conf.Services[serviceKey] = balancerServerUDP
srv := dynamic.UDPWRRService{Name: serviceKey}
@ -114,7 +115,7 @@ func loadUDPServers(client Client, namespace string, svc v1alpha1.ServiceUDP) ([
var portSpec *corev1.ServicePort
for _, p := range service.Spec.Ports {
p := p
if svc.Port == p.Port {
if (svc.Port.Type == intstr.Int && svc.Port.IntVal == p.Port) || (svc.Port.Type == intstr.String && svc.Port.StrVal == p.Name) {
portSpec = &p
break
}

View file

@ -4,6 +4,7 @@ import (
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// IngressRouteSpec is a specification for a IngressRouteSpec resource.
@ -67,7 +68,7 @@ type LoadBalancerSpec struct {
// Port and all the fields below are related to a servers load-balancer,
// and therefore should only be specified when Name references a Kubernetes Service.
Port int32 `json:"port"`
Port intstr.IntOrString `json:"port"`
Scheme string `json:"scheme,omitempty"`
Strategy string `json:"strategy,omitempty"`
PassHostHeader *bool `json:"passHostHeader,omitempty"`

View file

@ -4,6 +4,7 @@ import (
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// IngressRouteTCPSpec is a specification for a IngressRouteTCPSpec resource.
@ -56,7 +57,7 @@ type TLSStoreTCPRef struct {
type ServiceTCP struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Port int32 `json:"port"`
Port intstr.IntOrString `json:"port"`
Weight *int `json:"weight,omitempty"`
TerminationDelay *int `json:"terminationDelay,omitempty"`
ProxyProtocol *dynamic.ProxyProtocol `json:"proxyProtocol,omitempty"`

View file

@ -2,6 +2,7 @@ package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// IngressRouteUDPSpec is a specification for a IngressRouteUDPSpec resource.
@ -23,10 +24,10 @@ type TLSOptionUDPRef struct {
// ServiceUDP defines an upstream to proxy traffic.
type ServiceUDP struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Port int32 `json:"port"`
Weight *int `json:"weight,omitempty"`
Name string `json:"name"`
Namespace string `json:"namespace"`
Port intstr.IntOrString `json:"port"`
Weight *int `json:"weight,omitempty"`
}
// +genclient

View file

@ -507,6 +507,7 @@ func (in *LoadBalancerSpec) DeepCopyInto(out *LoadBalancerSpec) {
*out = new(dynamic.Sticky)
(*in).DeepCopyInto(*out)
}
out.Port = in.Port
if in.PassHostHeader != nil {
in, out := &in.PassHostHeader, &out.PassHostHeader
*out = new(bool)
@ -1001,6 +1002,7 @@ func (in *ServiceSpec) DeepCopy() *ServiceSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceTCP) DeepCopyInto(out *ServiceTCP) {
*out = *in
out.Port = in.Port
if in.Weight != nil {
in, out := &in.Weight, &out.Weight
*out = new(int)
@ -1032,6 +1034,7 @@ func (in *ServiceTCP) DeepCopy() *ServiceTCP {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceUDP) DeepCopyInto(out *ServiceUDP) {
*out = *in
out.Port = in.Port
if in.Weight != nil {
in, out := &in.Weight, &out.Weight
*out = new(int)