Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge current v1.7 into master #3992

Merged
merged 3 commits into from
Oct 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 19 additions & 18 deletions cmd/traefik/traefik.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand All @@ -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)
}
}
Expand All @@ -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())

Expand Down
12 changes: 9 additions & 3 deletions configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
51 changes: 51 additions & 0 deletions configuration/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
}
})
}
}
39 changes: 39 additions & 0 deletions docs/user-guide/kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
43 changes: 23 additions & 20 deletions provider/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand All @@ -285,38 +288,38 @@ 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),
}
}
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)
return nil, err
}

if !exists {
log.Errorf("Service not found for %s/%s", i.Namespace, pa.Backend.ServiceName)
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)
delete(templateObjects.Frontends, baseName)
continue
}

if rule != "" {
templateObjects.Frontends[baseName].Routes[pa.Path] = types.Route{
frontend.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)
return nil, err
}

if !exists {
log.Errorf("Service not found for %s/%s", i.Namespace, pa.Backend.ServiceName)
delete(templateObjects.Frontends, baseName)
continue
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)
Expand Down