Support Kubernetes Ingress pathType

Co-authored-by: jbdoumenjou <jb.doumenjou@gmail.com>
Co-authored-by: kevinpollet <pollet.kevin@gmail.com>
This commit is contained in:
Romain 2020-07-28 17:50:04 +02:00 committed by GitHub
parent 3908ef611a
commit dafb14ff37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 423 additions and 24 deletions

View file

@ -258,16 +258,13 @@ Value of `kubernetes.io/ingress.class` annotation that identifies Ingress object
If the parameter is non-empty, only Ingresses containing an annotation with the same value are processed. If the parameter is non-empty, only Ingresses containing an annotation with the same value are processed.
Otherwise, Ingresses missing the annotation, having an empty value, or with the value `traefik` are processed. Otherwise, Ingresses missing the annotation, having an empty value, or with the value `traefik` are processed.
#### ingressClass on Kubernetes 1.18+ !!! info "Kubernetes 1.18+"
If you cluster is running kubernetes 1.18+, If the Kubernetes cluster version is 1.18+,
you can also leverage the newly Introduced `IngressClass` resource to define which Ingress Objects to handle. the new `IngressClass` resource can be leveraged to identify Ingress objects that should be processed.
In that case, Traefik will look for an `IngressClass` in your cluster with the controller of *traefik.io/ingress-controller* inside the spec. In that case, Traefik will look for an `IngressClass` in the cluster with the controller value equal to *traefik.io/ingress-controller*.
!!! note "" Please see [this article](https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/) for more information.
Please note, the ingressClass configuration on the provider is not used then anymore.
Please see [this article](https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/) for more information.
### `ingressEndpoint` ### `ingressEndpoint`

View file

@ -322,9 +322,23 @@ which in turn will create the resulting routers, services, handlers, etc.
traefik.ingress.kubernetes.io/service.sticky.cookie.httponly: "true" traefik.ingress.kubernetes.io/service.sticky.cookie.httponly: "true"
``` ```
### TLS ## Path Types on Kubernetes 1.18+
If the Kubernetes cluster version is 1.18+,
the new `pathType` property can be leveraged to define the rules matchers:
#### Communication Between Traefik and Pods - `Exact`: This path type forces the rule matcher to `Path`
- `Prefix`: This path type forces the rule matcher to `PathPrefix`
Please see [this documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types) for more information.
!!! warning "Multiple Matches"
In the case of multiple matches, Traefik will not ensure the priority of a Path matcher over a PathPrefix matcher,
as stated in [this documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/#multiple-matches).
## TLS
### Communication Between Traefik and Pods
Traefik automatically requests endpoint information based on the service provided in the ingress spec. Traefik automatically requests endpoint information based on the service provided in the ingress spec.
Although Traefik will connect directly to the endpoints (pods), Although Traefik will connect directly to the endpoints (pods),
@ -346,7 +360,7 @@ and will connect via TLS automatically.
If this is not an option, you may need to skip TLS certificate verification. If this is not an option, you may need to skip TLS certificate verification.
See the [insecureSkipVerify](../../routing/overview.md#insecureskipverify) setting for more details. See the [insecureSkipVerify](../../routing/overview.md#insecureskipverify) setting for more details.
#### Certificates Management ### Certificates Management
??? example "Using a secret" ??? example "Using a secret"

View file

@ -341,7 +341,7 @@ func (c *clientWrapper) GetIngressClass() (*networkingv1beta1.IngressClass, erro
for _, ic := range ingressClasses { for _, ic := range ingressClasses {
if ic.Spec.Controller == traefikDefaultIngressClassController { if ic.Spec.Controller == traefikDefaultIngressClassController {
return ic, err return ic, nil
} }
} }

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -0,0 +1,16 @@
kind: Ingress
apiVersion: networking.k8s.io/v1beta1
metadata:
name: ""
namespace: testing
annotations:
traefik.ingress.kubernetes.io/router.pathmatcher: Path
spec:
rules:
- http:
paths:
- path: /bar
pathType: ""
backend:
serviceName: service1
servicePort: 80

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIp: 10.0.0.1

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -0,0 +1,14 @@
kind: Ingress
apiVersion: networking.k8s.io/v1beta1
metadata:
name: ""
namespace: testing
spec:
rules:
- http:
paths:
- path: /bar
pathType: Exact
backend:
serviceName: service1
servicePort: 80

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIp: 10.0.0.1

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -0,0 +1,16 @@
kind: Ingress
apiVersion: networking.k8s.io/v1beta1
metadata:
name: ""
namespace: testing
annotations:
traefik.ingress.kubernetes.io/router.pathmatcher: Path
spec:
rules:
- http:
paths:
- path: /bar
pathType: ImplementationSpecific
backend:
serviceName: service1
servicePort: 80

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIp: 10.0.0.1

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -0,0 +1,15 @@
kind: Ingress
apiVersion: networking.k8s.io/v1beta1
metadata:
name: ""
namespace: testing
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- http:
paths:
- path: /bar
backend:
serviceName: service1
servicePort: 80

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIp: 10.0.0.1

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -0,0 +1,15 @@
kind: Ingress
apiVersion: networking.k8s.io/v1beta1
metadata:
name: ""
namespace: testing
annotations:
traefik.ingress.kubernetes.io/router.pathmatcher: Path
spec:
rules:
- http:
paths:
- path: /bar
backend:
serviceName: service1
servicePort: 80

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIp: 10.0.0.1

View file

@ -0,0 +1,11 @@
kind: Endpoints
apiVersion: v1
metadata:
name: service1
namespace: testing
subsets:
- addresses:
- ip: 10.10.0.1
ports:
- port: 8080

View file

@ -0,0 +1,14 @@
kind: Ingress
apiVersion: networking.k8s.io/v1beta1
metadata:
name: ""
namespace: testing
spec:
rules:
- http:
paths:
- path: /bar
pathType: Prefix
backend:
serviceName: service1
servicePort: 80

View file

@ -0,0 +1,10 @@
kind: Service
apiVersion: v1
metadata:
name: service1
namespace: testing
spec:
ports:
- port: 80
clusterIp: 10.0.0.1

View file

@ -194,13 +194,7 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
if supportsIngressClass(serverVersion) { if supportsIngressClass(serverVersion) {
ic, err := client.GetIngressClass() ic, err := client.GetIngressClass()
if err != nil { if err != nil {
log.FromContext(ctx).Errorf("Failed to find an ingress class: %v", err) log.FromContext(ctx).Warnf("Failed to find an ingress class: %v", err)
return conf
}
if ic == nil {
log.FromContext(ctx).Errorf("No ingress class for the traefik-controller in the cluster")
return conf
} }
ingressClass = ic ingressClass = ic
@ -337,8 +331,12 @@ func (p *Provider) updateIngressStatus(ing *v1beta1.Ingress, k8sClient Client) e
} }
func (p *Provider) shouldProcessIngress(providerIngressClass string, ingress *networkingv1beta1.Ingress, ingressClass *networkingv1beta1.IngressClass) bool { func (p *Provider) shouldProcessIngress(providerIngressClass string, ingress *networkingv1beta1.Ingress, ingressClass *networkingv1beta1.IngressClass) bool {
return ingressClass != nil && ingress.Spec.IngressClassName != nil && ingressClass.ObjectMeta.Name == *ingress.Spec.IngressClassName || // configuration through the new kubernetes ingressClass
providerIngressClass == ingress.Annotations[annotationKubernetesIngressClass] || if ingress.Spec.IngressClassName != nil {
return ingressClass != nil && ingressClass.ObjectMeta.Name == *ingress.Spec.IngressClassName
}
return providerIngressClass == ingress.Annotations[annotationKubernetesIngressClass] ||
len(providerIngressClass) == 0 && ingress.Annotations[annotationKubernetesIngressClass] == traefikDefaultIngressClass len(providerIngressClass) == 0 && ingress.Annotations[annotationKubernetesIngressClass] == traefikDefaultIngressClass
} }
@ -549,8 +547,13 @@ func loadRouter(rule v1beta1.IngressRule, pa v1beta1.HTTPIngressPath, rtConfig *
if len(pa.Path) > 0 { if len(pa.Path) > 0 {
matcher := defaultPathMatcher matcher := defaultPathMatcher
if rtConfig != nil && rtConfig.Router != nil && rtConfig.Router.PathMatcher != "" {
matcher = rtConfig.Router.PathMatcher if pa.PathType == nil || *pa.PathType == "" || *pa.PathType == v1beta1.PathTypeImplementationSpecific {
if rtConfig != nil && rtConfig.Router != nil && rtConfig.Router.PathMatcher != "" {
matcher = rtConfig.Router.PathMatcher
}
} else if *pa.PathType == v1beta1.PathTypeExact {
matcher = "Path"
} }
rules = append(rules, fmt.Sprintf("%s(`%s`)", matcher, pa.Path)) rules = append(rules, fmt.Sprintf("%s(`%s`)", matcher, pa.Path))

View file

@ -952,6 +952,146 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
}, },
}, },
}, },
{
desc: "v18 Ingress with no pathType",
serverVersion: "v1.18",
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{
"testing-bar": {
Rule: "Path(`/bar`)",
Service: "testing-service1-80",
},
},
Services: map[string]*dynamic.Service{
"testing-service1-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:8080",
},
},
},
},
},
},
},
},
{
desc: "v18 Ingress with empty pathType",
serverVersion: "v1.18",
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{
"testing-bar": {
Rule: "Path(`/bar`)",
Service: "testing-service1-80",
},
},
Services: map[string]*dynamic.Service{
"testing-service1-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:8080",
},
},
},
},
},
},
},
},
{
desc: "v18 Ingress with implementationSpecific pathType",
serverVersion: "v1.18",
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{
"testing-bar": {
Rule: "Path(`/bar`)",
Service: "testing-service1-80",
},
},
Services: map[string]*dynamic.Service{
"testing-service1-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:8080",
},
},
},
},
},
},
},
},
{
desc: "v18 Ingress with prefix pathType",
serverVersion: "v1.18",
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{
"testing-bar": {
Rule: "PathPrefix(`/bar`)",
Service: "testing-service1-80",
},
},
Services: map[string]*dynamic.Service{
"testing-service1-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:8080",
},
},
},
},
},
},
},
},
{
desc: "v18 Ingress with exact pathType",
serverVersion: "v1.18",
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{
"testing-bar": {
Rule: "Path(`/bar`)",
Service: "testing-service1-80",
},
},
Services: map[string]*dynamic.Service{
"testing-service1-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:8080",
},
},
},
},
},
},
},
},
{ {
desc: "v18 Ingress with missing ingressClass", desc: "v18 Ingress with missing ingressClass",
serverVersion: "v1.18", serverVersion: "v1.18",
@ -964,10 +1104,39 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
}, },
}, },
}, },
{
desc: "v18 Ingress with ingress annotation",
serverVersion: "v1.18",
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{},
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{
"testing-bar": {
Rule: "PathPrefix(`/bar`)",
Service: "testing-service1-80",
},
},
Services: map[string]*dynamic.Service{
"testing-service1-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:8080",
},
},
},
},
},
},
},
},
} }
for _, test := range testCases { for _, test := range testCases {
test := test test := test
t.Run(test.desc, func(t *testing.T) { t.Run(test.desc, func(t *testing.T) {
t.Parallel() t.Parallel()