Add named port support to Kubernetes IngressRoute CRDs
This commit is contained in:
parent
b1ddd0e038
commit
bbee63fcf3
10 changed files with 154 additions and 57 deletions
|
@ -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,21 +343,21 @@ 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. |
|
||||
|
@ -366,15 +366,16 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne
|
|||
| [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) |
|
||||
| [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"
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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.
|
||||
|
@ -25,7 +26,7 @@ type TLSOptionUDPRef struct {
|
|||
type ServiceUDP struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
Port int32 `json:"port"`
|
||||
Port intstr.IntOrString `json:"port"`
|
||||
Weight *int `json:"weight,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue