Merge branch 'v1.7' into master
This commit is contained in:
commit
94a6f8426b
6 changed files with 242 additions and 44 deletions
|
@ -187,12 +187,13 @@ func runCmd(globalConfiguration *configuration.GlobalConfiguration, configFile s
|
||||||
|
|
||||||
providerAggregator := configuration.NewProviderAggregator(globalConfiguration)
|
providerAggregator := configuration.NewProviderAggregator(globalConfiguration)
|
||||||
|
|
||||||
acmeprovider := globalConfiguration.InitACMEProvider()
|
acmeProvider, err := globalConfiguration.InitACMEProvider()
|
||||||
if acmeprovider != nil {
|
if err != nil {
|
||||||
|
log.Errorf("Unable to initialize ACME provider: %v", err)
|
||||||
if err := providerAggregator.AddProvider(acmeprovider); err != nil {
|
} else if acmeProvider != nil {
|
||||||
log.Errorf("Error initializing provider ACME: %v", err)
|
if err := providerAggregator.AddProvider(acmeProvider); err != nil {
|
||||||
acmeprovider = 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)
|
internalRouter := router.NewInternalRouterAggregator(*globalConfiguration, entryPointName)
|
||||||
if acmeprovider != nil {
|
if acmeProvider != nil {
|
||||||
if acmeprovider.HTTPChallenge != nil && entryPointName == acmeprovider.HTTPChallenge.EntryPoint {
|
if acmeProvider.HTTPChallenge != nil && entryPointName == acmeProvider.HTTPChallenge.EntryPoint {
|
||||||
internalRouter.AddRouter(acmeprovider)
|
internalRouter.AddRouter(acmeProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLS ALPN 01
|
// TLS ALPN 01
|
||||||
if acmeprovider.TLSChallenge != nil && acmeprovider.HTTPChallenge == nil && acmeprovider.DNSChallenge == nil {
|
if acmeProvider.TLSChallenge != nil && acmeProvider.HTTPChallenge == nil && acmeProvider.DNSChallenge == nil {
|
||||||
entryPoint.TLSALPNGetter = acmeprovider.GetTLSALPNCertificate
|
entryPoint.TLSALPNGetter = acmeProvider.GetTLSALPNCertificate
|
||||||
}
|
}
|
||||||
|
|
||||||
if acmeprovider.OnDemand && entryPointName == acmeprovider.EntryPoint {
|
if acmeProvider.OnDemand && entryPointName == acmeProvider.EntryPoint {
|
||||||
entryPoint.OnDemandListener = acmeprovider.ListenRequest
|
entryPoint.OnDemandListener = acmeProvider.ListenRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
if entryPointName == acmeprovider.EntryPoint {
|
if entryPointName == acmeProvider.EntryPoint {
|
||||||
entryPoint.CertificateStore = traefiktls.NewCertificateStore()
|
entryPoint.CertificateStore = traefiktls.NewCertificateStore()
|
||||||
acmeprovider.SetCertificateStore(entryPoint.CertificateStore)
|
acmeProvider.SetCertificateStore(entryPoint.CertificateStore)
|
||||||
log.Debugf("Setting Acme Certificate store from Entrypoint: %s", entryPointName)
|
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)
|
svr := server.NewServer(*globalConfiguration, providerAggregator, entryPoints)
|
||||||
if acmeprovider != nil && acmeprovider.OnHostRule {
|
if acmeProvider != nil && acmeProvider.OnHostRule {
|
||||||
acmeprovider.SetConfigListenerChan(make(chan types.Configuration))
|
acmeProvider.SetConfigListenerChan(make(chan types.Configuration))
|
||||||
svr.AddListener(acmeprovider.ListenConfiguration)
|
svr.AddListener(acmeProvider.ListenConfiguration)
|
||||||
}
|
}
|
||||||
ctx := cmd.ContextWithSignal(context.Background())
|
ctx := cmd.ContextWithSignal(context.Background())
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ import (
|
||||||
"github.com/containous/traefik/provider/zk"
|
"github.com/containous/traefik/provider/zk"
|
||||||
"github.com/containous/traefik/tls"
|
"github.com/containous/traefik/tls"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -271,8 +272,13 @@ func (gc *GlobalConfiguration) initACMEProvider() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitACMEProvider create an acme provider from the ACME part of globalConfiguration
|
// 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 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
|
// TODO: Remove when Provider ACME will replace totally ACME
|
||||||
// If provider file, use Provider ACME instead of ACME
|
// If provider file, use Provider ACME instead of ACME
|
||||||
if gc.Cluster == nil {
|
if gc.Cluster == nil {
|
||||||
|
@ -296,10 +302,10 @@ func (gc *GlobalConfiguration) InitACMEProvider() *acmeprovider.Provider {
|
||||||
provider.Store = store
|
provider.Store = store
|
||||||
acme.ConvertToNewFormat(provider.Storage)
|
acme.ConvertToNewFormat(provider.Storage)
|
||||||
gc.ACME = nil
|
gc.ACME = nil
|
||||||
return provider
|
return provider, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSafeACMECAServer(caServerSrc string) string {
|
func getSafeACMECAServer(caServerSrc string) string {
|
||||||
|
|
|
@ -3,10 +3,12 @@ package configuration
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/acme"
|
||||||
"github.com/containous/traefik/middlewares/tracing"
|
"github.com/containous/traefik/middlewares/tracing"
|
||||||
"github.com/containous/traefik/middlewares/tracing/jaeger"
|
"github.com/containous/traefik/middlewares/tracing/jaeger"
|
||||||
"github.com/containous/traefik/middlewares/tracing/zipkin"
|
"github.com/containous/traefik/middlewares/tracing/zipkin"
|
||||||
"github.com/containous/traefik/provider"
|
"github.com/containous/traefik/provider"
|
||||||
|
acmeprovider "github.com/containous/traefik/provider/acme"
|
||||||
"github.com/containous/traefik/provider/file"
|
"github.com/containous/traefik/provider/file"
|
||||||
"github.com/stretchr/testify/assert"
|
"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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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/cheddar](http://cheeses.minikube/cheddar/)
|
||||||
- [cheeses.minikube/wensleydale](http://cheeses.minikube/wensleydale/)
|
- [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
|
## Specifying Routing Priorities
|
||||||
|
|
||||||
Sometimes you need to specify priority for ingress routes, especially when handling wildcard routes.
|
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
|
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)
|
auth, err := getAuthConfig(i, k8sClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to retrieve auth configuration for ingress %s/%s: %s", i.Namespace, i.Name, err)
|
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)
|
passTLSCert := getBoolValue(i.Annotations, annotationKubernetesPassTLSCert, p.EnablePassTLSCert)
|
||||||
entryPoints := getSliceStringValue(i.Annotations, annotationKubernetesFrontendEntryPoints)
|
entryPoints := getSliceStringValue(i.Annotations, annotationKubernetesFrontendEntryPoints)
|
||||||
|
|
||||||
templateObjects.Frontends[baseName] = &types.Frontend{
|
frontend = &types.Frontend{
|
||||||
Backend: baseName,
|
Backend: baseName,
|
||||||
PassHostHeader: passHostHeader,
|
PassHostHeader: passHostHeader,
|
||||||
PassTLSCert: passTLSCert,
|
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)
|
service, exists, err := k8sClient.GetService(i.Namespace, pa.Backend.ServiceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error while retrieving service information from k8s API %s/%s: %v", i.Namespace, pa.Backend.ServiceName, err)
|
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 {
|
if !exists {
|
||||||
log.Errorf("Service not found for %s/%s", i.Namespace, pa.Backend.ServiceName)
|
log.Errorf("Service not found for %s/%s", i.Namespace, pa.Backend.ServiceName)
|
||||||
delete(templateObjects.Frontends, baseName)
|
|
||||||
continue
|
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].CircuitBreaker = getCircuitBreaker(service)
|
||||||
templateObjects.Backends[baseName].LoadBalancer = getLoadBalancer(service)
|
templateObjects.Backends[baseName].LoadBalancer = getLoadBalancer(service)
|
||||||
templateObjects.Backends[baseName].MaxConn = getMaxConn(service)
|
templateObjects.Backends[baseName].MaxConn = getMaxConn(service)
|
||||||
|
|
|
@ -1597,6 +1597,14 @@ rateset:
|
||||||
route("root", "Host:root"),
|
route("root", "Host:root"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
frontend("root2/",
|
||||||
|
passHostHeader(),
|
||||||
|
redirectRegex("root2/$", "root2/root2"),
|
||||||
|
routes(
|
||||||
|
route("/", "PathPrefix:/;ReplacePathRegex: ^/(.*) /abc$1"),
|
||||||
|
route("root2", "Host:root2"),
|
||||||
|
),
|
||||||
|
),
|
||||||
frontend("root/root1",
|
frontend("root/root1",
|
||||||
passHostHeader(),
|
passHostHeader(),
|
||||||
routes(
|
routes(
|
||||||
|
@ -3502,3 +3510,93 @@ func TestTemplateBreakingIngresssValues(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, expected, actual)
|
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