Avoid flapping of multiple Ingress definitions
This commit is contained in:
parent
a9deeb321b
commit
157580c232
3 changed files with 163 additions and 23 deletions
|
@ -742,6 +742,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/)
|
||||
|
||||
## Multiple Ingress Definitions for the Same Host (or Host+Path)
|
||||
|
||||
Træfik will merge multiple Ingress definitions for the same host/path pair into one definition.
|
||||
|
||||
Let's say the number of cheese services is growing.
|
||||
It is now time to move the cheese services to a dedicated cheese namespace to simplify the managements of cheese and non-cheese services.
|
||||
|
||||
Simply deploy a new Ingress Object with the same host an path into the cheese namespace:
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: cheese
|
||||
namespace: cheese
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: traefik
|
||||
traefik.frontend.rule.type: PathPrefixStrip
|
||||
spec:
|
||||
rules:
|
||||
- host: cheese.minikube
|
||||
http:
|
||||
paths:
|
||||
- path: /cheddar
|
||||
backend:
|
||||
serviceName: cheddar
|
||||
servicePort: http
|
||||
```
|
||||
|
||||
Træfik will now look for cheddar service endpoints (ports on healthy pods) in both the cheese and the default namespace.
|
||||
Deploying cheddar into the cheese namespace and afterwards shutting down cheddar in the default namespace is enough to migrate the traffic.
|
||||
|
||||
!!! note
|
||||
The kubernetes documentation does not specify this merging behavior.
|
||||
|
||||
!!! note
|
||||
Merging ingress definitions can cause problems if the annotations differ or if the services handle requests differently.
|
||||
Be careful and extra cautious when running multiple overlapping ingress definitions.
|
||||
|
||||
## Specifying Routing Priorities
|
||||
|
||||
Sometimes you need to specify priority for ingress routes, especially when handling wildcard routes.
|
||||
|
|
|
@ -258,7 +258,10 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
|||
continue
|
||||
}
|
||||
|
||||
if _, exists := templateObjects.Frontends[baseName]; !exists {
|
||||
var frontend *types.Frontend
|
||||
if fe, exists := templateObjects.Frontends[baseName]; exists {
|
||||
frontend = fe
|
||||
} else {
|
||||
auth, err := getAuthConfig(i, k8sClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to retrieve auth configuration for ingress %s/%s: %s", i.Namespace, i.Name, err)
|
||||
|
@ -269,7 +272,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
|||
passTLSCert := getBoolValue(i.Annotations, annotationKubernetesPassTLSCert, p.EnablePassTLSCert)
|
||||
entryPoints := getSliceStringValue(i.Annotations, annotationKubernetesFrontendEntryPoints)
|
||||
|
||||
templateObjects.Frontends[baseName] = &types.Frontend{
|
||||
frontend = &types.Frontend{
|
||||
Backend: baseName,
|
||||
PassHostHeader: passHostHeader,
|
||||
PassTLSCert: passTLSCert,
|
||||
|
@ -285,26 +288,6 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
|||
}
|
||||
}
|
||||
|
||||
if len(r.Host) > 0 {
|
||||
if _, exists := templateObjects.Frontends[baseName].Routes[r.Host]; !exists {
|
||||
templateObjects.Frontends[baseName].Routes[r.Host] = types.Route{
|
||||
Rule: getRuleForHost(r.Host),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rule, err := getRuleForPath(pa, i)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get rule for ingress %s/%s: %s", i.Namespace, i.Name, err)
|
||||
delete(templateObjects.Frontends, baseName)
|
||||
continue
|
||||
}
|
||||
if rule != "" {
|
||||
templateObjects.Frontends[baseName].Routes[pa.Path] = types.Route{
|
||||
Rule: rule,
|
||||
}
|
||||
}
|
||||
|
||||
service, exists, err := k8sClient.GetService(i.Namespace, pa.Backend.ServiceName)
|
||||
if err != nil {
|
||||
log.Errorf("Error while retrieving service information from k8s API %s/%s: %v", i.Namespace, pa.Backend.ServiceName, err)
|
||||
|
@ -313,10 +296,30 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
|||
|
||||
if !exists {
|
||||
log.Errorf("Service not found for %s/%s", i.Namespace, pa.Backend.ServiceName)
|
||||
delete(templateObjects.Frontends, baseName)
|
||||
continue
|
||||
}
|
||||
|
||||
rule, err := getRuleForPath(pa, i)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get rule for ingress %s/%s: %s", i.Namespace, i.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if rule != "" {
|
||||
frontend.Routes[pa.Path] = types.Route{
|
||||
Rule: rule,
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.Host) > 0 {
|
||||
if _, exists := frontend.Routes[r.Host]; !exists {
|
||||
frontend.Routes[r.Host] = types.Route{
|
||||
Rule: getRuleForHost(r.Host),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
templateObjects.Frontends[baseName] = frontend
|
||||
templateObjects.Backends[baseName].CircuitBreaker = getCircuitBreaker(service)
|
||||
templateObjects.Backends[baseName].LoadBalancer = getLoadBalancer(service)
|
||||
templateObjects.Backends[baseName].MaxConn = getMaxConn(service)
|
||||
|
|
|
@ -1571,6 +1571,14 @@ rateset:
|
|||
route("root", "Host:root"),
|
||||
),
|
||||
),
|
||||
frontend("root2/",
|
||||
passHostHeader(),
|
||||
redirectRegex("root2/$", "root2/root2"),
|
||||
routes(
|
||||
route("/", "PathPrefix:/;ReplacePathRegex: ^/(.*) /abc$1"),
|
||||
route("root2", "Host:root2"),
|
||||
),
|
||||
),
|
||||
frontend("root/root1",
|
||||
passHostHeader(),
|
||||
routes(
|
||||
|
@ -3476,3 +3484,93 @@ func TestTemplateBreakingIngresssValues(t *testing.T) {
|
|||
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestDivergingIngressDefinitions(t *testing.T) {
|
||||
ingresses := []*extensionsv1beta1.Ingress{
|
||||
buildIngress(
|
||||
iNamespace("testing"),
|
||||
iRules(
|
||||
iRule(
|
||||
iHost("host-a"),
|
||||
iPaths(
|
||||
onePath(iBackend("service1", intstr.FromString("80"))),
|
||||
)),
|
||||
),
|
||||
),
|
||||
buildIngress(
|
||||
iNamespace("testing"),
|
||||
iRules(
|
||||
iRule(
|
||||
iHost("host-a"),
|
||||
iPaths(
|
||||
onePath(iBackend("missing", intstr.FromString("80"))),
|
||||
)),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
services := []*corev1.Service{
|
||||
buildService(
|
||||
sName("service1"),
|
||||
sNamespace("testing"),
|
||||
sUID("1"),
|
||||
sSpec(
|
||||
clusterIP("10.0.0.1"),
|
||||
sPorts(sPort(80, "http")),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
endpoints := []*corev1.Endpoints{
|
||||
buildEndpoint(
|
||||
eNamespace("testing"),
|
||||
eName("service1"),
|
||||
eUID("1"),
|
||||
subset(
|
||||
eAddresses(
|
||||
eAddress("10.10.0.1"),
|
||||
),
|
||||
ePorts(ePort(80, "http")),
|
||||
),
|
||||
subset(
|
||||
eAddresses(
|
||||
eAddress("10.10.0.2"),
|
||||
),
|
||||
ePorts(ePort(80, "http")),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
watchChan := make(chan interface{})
|
||||
client := clientMock{
|
||||
ingresses: ingresses,
|
||||
services: services,
|
||||
endpoints: endpoints,
|
||||
watchChan: watchChan,
|
||||
}
|
||||
provider := Provider{}
|
||||
|
||||
actual, err := provider.loadIngresses(client)
|
||||
require.NoError(t, err, "error loading ingresses")
|
||||
|
||||
expected := buildConfiguration(
|
||||
backends(
|
||||
backend("host-a",
|
||||
servers(
|
||||
server("http://10.10.0.1:80", weight(1)),
|
||||
server("http://10.10.0.2:80", weight(1)),
|
||||
),
|
||||
lbMethod("wrr"),
|
||||
),
|
||||
),
|
||||
frontends(
|
||||
frontend("host-a",
|
||||
passHostHeader(),
|
||||
routes(
|
||||
route("host-a", "Host:host-a")),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assert.Equal(t, expected, actual, "error merging multiple backends")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue