From 94f922cd288ae5110d76be190e26d49d194fa6f9 Mon Sep 17 00:00:00 2001 From: Kirill Orlov Date: Sat, 29 Jul 2017 19:35:23 +0300 Subject: [PATCH] Added ability to override frontend priority for k8s ingress router --- docs/toml.md | 1 + docs/user-guide/kubernetes.md | 39 ++++++++++ provider/kubernetes/kubernetes.go | 22 +++++- provider/kubernetes/kubernetes_test.go | 102 +++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 1 deletion(-) diff --git a/docs/toml.md b/docs/toml.md index 7b53c4599..2b003aff1 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -1242,6 +1242,7 @@ Træfik can be configured to use Kubernetes Ingress as a backend configuration: Annotations can be used on containers to override default behaviour for the whole Ingress resource: - `traefik.frontend.rule.type: PathPrefixStrip`: override the default frontend rule type (Default: `PathPrefix`). +- `traefik.frontend.priority: 3`: override the default frontend rule priority (Default: `len(Path)`). Annotations can be used on the Kubernetes service to override default behaviour: diff --git a/docs/user-guide/kubernetes.md b/docs/user-guide/kubernetes.md index 57bd75c78..523e94f1c 100644 --- a/docs/user-guide/kubernetes.md +++ b/docs/user-guide/kubernetes.md @@ -604,6 +604,45 @@ You should now be able to visit the websites in your browser. * [cheeses.minikube/cheddar](http://cheeses.minikube/cheddar/) * [cheeses.minikube/wensleydale](http://cheeses.minikube/wensleydale/) +## Specifying priority for routing + +Sometimes you need to specify priority for ingress route, especially when handling wildcard routes. +This can be done by adding annotation `traefik.frontend.priority`, i.e.: + +```yaml +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: wildcard-cheeses + annotations: + traefik.frontend.priority: 1 +spec: + rules: + - host: *.minikube + http: + paths: + - path: / + backend: + serviceName: stilton + servicePort: http + +kind: Ingress +metadata: + name: specific-cheeses + annotations: + traefik.frontend.priority: 2 +spec: + rules: + - host: specific.minikube + http: + paths: + - path: / + backend: + serviceName: stilton + servicePort: http +``` + + ## Forwarding to ExternalNames When specifying an [ExternalName](https://kubernetes.io/docs/concepts/services-networking/service/#services-without-selectors), diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index 1f1bcf8d1..5b8c02f2d 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -192,11 +192,14 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error) log.Errorf("Failed to retrieve basic auth configuration for ingress %s/%s: %s", i.ObjectMeta.Namespace, i.ObjectMeta.Name, err) continue } + + priority := p.getPriority(pa, i) + templateObjects.Frontends[r.Host+pa.Path] = &types.Frontend{ Backend: r.Host + pa.Path, PassHostHeader: PassHostHeader, Routes: make(map[string]types.Route), - Priority: len(pa.Path), + Priority: priority, BasicAuth: basicAuthCreds, WhitelistSourceRange: whitelistSourceRange, } @@ -322,6 +325,23 @@ func getRuleForPath(pa v1beta1.HTTPIngressPath, i *v1beta1.Ingress) string { return rule } +func (p *Provider) getPriority(path v1beta1.HTTPIngressPath, i *v1beta1.Ingress) int { + priority := len(path.Path) + + priorityRaw, ok := i.Annotations[types.LabelFrontendPriority] + if ok { + priorityParsed, err := strconv.Atoi(priorityRaw) + + if err == nil { + priority = priorityParsed + } else { + log.Errorf("Error in ingress: failed to parse %q value %q.", types.LabelFrontendPriority, priorityRaw) + } + } + + return priority +} + func handleBasicAuthConfig(i *v1beta1.Ingress, k8sClient Client) ([]string, error) { authType, exists := i.Annotations[annotationKubernetesAuthType] if !exists { diff --git a/provider/kubernetes/kubernetes_test.go b/provider/kubernetes/kubernetes_test.go index dcf490b07..c92ea16fb 100644 --- a/provider/kubernetes/kubernetes_test.go +++ b/provider/kubernetes/kubernetes_test.go @@ -1778,6 +1778,108 @@ func TestIngressAnnotations(t *testing.T) { assert.Equal(t, expected, actual) } +func TestPriorityHeaderValue(t *testing.T) { + ingresses := []*v1beta1.Ingress{ + { + ObjectMeta: v1.ObjectMeta{ + Namespace: "testing", + Annotations: map[string]string{ + types.LabelFrontendPriority: "1337", + }, + }, + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ + { + Host: "foo", + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ + { + Path: "/bar", + Backend: v1beta1.IngressBackend{ + ServiceName: "service1", + ServicePort: intstr.FromInt(80), + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + services := []*v1.Service{ + { + ObjectMeta: v1.ObjectMeta{ + Name: "service1", + UID: "1", + Namespace: "testing", + }, + Spec: v1.ServiceSpec{ + ClusterIP: "10.0.0.1", + Type: "ExternalName", + ExternalName: "example.com", + Ports: []v1.ServicePort{ + { + Name: "http", + Port: 80, + }, + }, + }, + }, + } + + endpoints := []*v1.Endpoints{} + watchChan := make(chan interface{}) + client := clientMock{ + ingresses: ingresses, + services: services, + endpoints: endpoints, + watchChan: watchChan, + } + provider := Provider{} + actual, err := provider.loadIngresses(client) + if err != nil { + t.Fatalf("error %+v", err) + } + + expected := &types.Configuration{ + Backends: map[string]*types.Backend{ + "foo/bar": { + Servers: map[string]types.Server{ + "http://example.com": { + URL: "http://example.com", + Weight: 1, + }, + }, + CircuitBreaker: nil, + LoadBalancer: &types.LoadBalancer{ + Sticky: false, + Method: "wrr", + }, + }, + }, + Frontends: map[string]*types.Frontend{ + "foo/bar": { + Backend: "foo/bar", + PassHostHeader: true, + Priority: 1337, + Routes: map[string]types.Route{ + "/bar": { + Rule: "PathPrefix:/bar", + }, + "foo": { + Rule: "Host:foo", + }, + }, + }, + }, + } + + assert.Equal(t, expected, actual) +} + func TestInvalidPassHostHeaderValue(t *testing.T) { ingresses := []*v1beta1.Ingress{ {