Merge branch 'v1.7' into master

This commit is contained in:
Fernandez Ludovic 2018-10-09 11:19:55 +02:00
commit 94a6f8426b
6 changed files with 242 additions and 44 deletions

View file

@ -187,12 +187,13 @@ func runCmd(globalConfiguration *configuration.GlobalConfiguration, configFile s
providerAggregator := configuration.NewProviderAggregator(globalConfiguration)
acmeprovider := globalConfiguration.InitACMEProvider()
if acmeprovider != nil {
if err := providerAggregator.AddProvider(acmeprovider); err != nil {
log.Errorf("Error initializing provider ACME: %v", err)
acmeprovider = nil
acmeProvider, err := globalConfiguration.InitACMEProvider()
if err != nil {
log.Errorf("Unable to initialize ACME provider: %v", err)
} else if acmeProvider != nil {
if err := providerAggregator.AddProvider(acmeProvider); err != nil {
log.Errorf("Unable to add ACME provider to the providers list: %v", err)
acmeProvider = nil
}
}
@ -204,23 +205,23 @@ func runCmd(globalConfiguration *configuration.GlobalConfiguration, configFile s
}
internalRouter := router.NewInternalRouterAggregator(*globalConfiguration, entryPointName)
if acmeprovider != nil {
if acmeprovider.HTTPChallenge != nil && entryPointName == acmeprovider.HTTPChallenge.EntryPoint {
internalRouter.AddRouter(acmeprovider)
if acmeProvider != nil {
if acmeProvider.HTTPChallenge != nil && entryPointName == acmeProvider.HTTPChallenge.EntryPoint {
internalRouter.AddRouter(acmeProvider)
}
// TLS ALPN 01
if acmeprovider.TLSChallenge != nil && acmeprovider.HTTPChallenge == nil && acmeprovider.DNSChallenge == nil {
entryPoint.TLSALPNGetter = acmeprovider.GetTLSALPNCertificate
if acmeProvider.TLSChallenge != nil && acmeProvider.HTTPChallenge == nil && acmeProvider.DNSChallenge == nil {
entryPoint.TLSALPNGetter = acmeProvider.GetTLSALPNCertificate
}
if acmeprovider.OnDemand && entryPointName == acmeprovider.EntryPoint {
entryPoint.OnDemandListener = acmeprovider.ListenRequest
if acmeProvider.OnDemand && entryPointName == acmeProvider.EntryPoint {
entryPoint.OnDemandListener = acmeProvider.ListenRequest
}
if entryPointName == acmeprovider.EntryPoint {
if entryPointName == acmeProvider.EntryPoint {
entryPoint.CertificateStore = traefiktls.NewCertificateStore()
acmeprovider.SetCertificateStore(entryPoint.CertificateStore)
acmeProvider.SetCertificateStore(entryPoint.CertificateStore)
log.Debugf("Setting Acme Certificate store from Entrypoint: %s", entryPointName)
}
}
@ -230,9 +231,9 @@ func runCmd(globalConfiguration *configuration.GlobalConfiguration, configFile s
}
svr := server.NewServer(*globalConfiguration, providerAggregator, entryPoints)
if acmeprovider != nil && acmeprovider.OnHostRule {
acmeprovider.SetConfigListenerChan(make(chan types.Configuration))
svr.AddListener(acmeprovider.ListenConfiguration)
if acmeProvider != nil && acmeProvider.OnHostRule {
acmeProvider.SetConfigListenerChan(make(chan types.Configuration))
svr.AddListener(acmeProvider.ListenConfiguration)
}
ctx := cmd.ContextWithSignal(context.Background())

View file

@ -33,6 +33,7 @@ import (
"github.com/containous/traefik/provider/zk"
"github.com/containous/traefik/tls"
"github.com/containous/traefik/types"
"github.com/pkg/errors"
)
const (
@ -271,8 +272,13 @@ func (gc *GlobalConfiguration) initACMEProvider() {
}
// InitACMEProvider create an acme provider from the ACME part of globalConfiguration
func (gc *GlobalConfiguration) InitACMEProvider() *acmeprovider.Provider {
func (gc *GlobalConfiguration) InitACMEProvider() (*acmeprovider.Provider, error) {
if gc.ACME != nil {
if len(gc.ACME.Storage) == 0 {
// Delete the ACME configuration to avoid starting ACME in cluster mode
gc.ACME = nil
return nil, errors.New("unable to initialize ACME provider with no storage location for the certificates")
}
// TODO: Remove when Provider ACME will replace totally ACME
// If provider file, use Provider ACME instead of ACME
if gc.Cluster == nil {
@ -296,10 +302,10 @@ func (gc *GlobalConfiguration) InitACMEProvider() *acmeprovider.Provider {
provider.Store = store
acme.ConvertToNewFormat(provider.Storage)
gc.ACME = nil
return provider
return provider, nil
}
}
return nil
return nil, nil
}
func getSafeACMECAServer(caServerSrc string) string {

View file

@ -3,10 +3,12 @@ package configuration
import (
"testing"
"github.com/containous/traefik/acme"
"github.com/containous/traefik/middlewares/tracing"
"github.com/containous/traefik/middlewares/tracing/jaeger"
"github.com/containous/traefik/middlewares/tracing/zipkin"
"github.com/containous/traefik/provider"
acmeprovider "github.com/containous/traefik/provider/acme"
"github.com/containous/traefik/provider/file"
"github.com/stretchr/testify/assert"
)
@ -171,3 +173,52 @@ func TestSetEffectiveConfigurationTracing(t *testing.T) {
})
}
}
func TestInitACMEProvider(t *testing.T) {
testCases := []struct {
desc string
acmeConfiguration *acme.ACME
expectedConfiguration *acmeprovider.Provider
noError bool
}{
{
desc: "No ACME configuration",
acmeConfiguration: nil,
expectedConfiguration: nil,
noError: true,
},
{
desc: "ACME configuration with storage",
acmeConfiguration: &acme.ACME{Storage: "foo/acme.json"},
expectedConfiguration: &acmeprovider.Provider{Configuration: &acmeprovider.Configuration{Storage: "foo/acme.json"}},
noError: true,
},
{
desc: "ACME configuration with no storage",
acmeConfiguration: &acme.ACME{},
expectedConfiguration: nil,
noError: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
gc := &GlobalConfiguration{
ACME: test.acmeConfiguration,
}
configuration, err := gc.InitACMEProvider()
assert.True(t, (err == nil) == test.noError)
if test.expectedConfiguration == nil {
assert.Nil(t, configuration)
} else {
assert.Equal(t, test.expectedConfiguration.Storage, configuration.Storage)
}
})
}
}

View file

@ -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.

View file

@ -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)

View file

@ -1597,6 +1597,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(
@ -3502,3 +3510,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")
}