Add k8s provider option to create services without endpoints
This commit is contained in:
parent
0b48d5d0d2
commit
32e08f3510
11 changed files with 107 additions and 42 deletions
|
@ -440,6 +440,30 @@ providers:
|
||||||
--providers.kubernetesingress.throttleDuration=10s
|
--providers.kubernetesingress.throttleDuration=10s
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `allowEmptyServices`
|
||||||
|
|
||||||
|
_Optional, Default: false
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[providers.kubernetesIngress]
|
||||||
|
allowEmptyServices = true
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
providers:
|
||||||
|
kubernetesIngress:
|
||||||
|
allowEmptyServices: true
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash tab="CLI"
|
||||||
|
--providers.kubernetesingress.allowEmptyServices=true
|
||||||
|
```
|
||||||
|
|
||||||
|
Allow the creation of services if there are no endpoints available.
|
||||||
|
This results in `503` http responses instead of `404`.
|
||||||
|
|
||||||
### Further
|
### Further
|
||||||
|
|
||||||
To learn more about the various aspects of the Ingress specification that Traefik supports,
|
To learn more about the various aspects of the Ingress specification that Traefik supports,
|
||||||
|
|
|
@ -624,6 +624,9 @@ Kubernetes bearer token (not needed for in-cluster client).
|
||||||
`--providers.kubernetesingress`:
|
`--providers.kubernetesingress`:
|
||||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||||
|
|
||||||
|
`--providers.kubernetesingress.allowemptyservices`:
|
||||||
|
Allow creation of services without endpoints. (Default: ```false```)
|
||||||
|
|
||||||
`--providers.kubernetesingress.certauthfilepath`:
|
`--providers.kubernetesingress.certauthfilepath`:
|
||||||
Kubernetes certificate authority file path (not needed for in-cluster client).
|
Kubernetes certificate authority file path (not needed for in-cluster client).
|
||||||
|
|
||||||
|
|
|
@ -624,6 +624,9 @@ Kubernetes bearer token (not needed for in-cluster client).
|
||||||
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS`:
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS`:
|
||||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||||
|
|
||||||
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_ALLOWEMPTYSERVICES`:
|
||||||
|
Allow creation of services without endpoints. (Default: ```false```)
|
||||||
|
|
||||||
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_CERTAUTHFILEPATH`:
|
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_CERTAUTHFILEPATH`:
|
||||||
Kubernetes certificate authority file path (not needed for in-cluster client).
|
Kubernetes certificate authority file path (not needed for in-cluster client).
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,7 @@
|
||||||
labelSelector = "foobar"
|
labelSelector = "foobar"
|
||||||
ingressClass = "foobar"
|
ingressClass = "foobar"
|
||||||
throttleDuration = "42s"
|
throttleDuration = "42s"
|
||||||
|
allowEmptyServices = true
|
||||||
[providers.kubernetesIngress.ingressEndpoint]
|
[providers.kubernetesIngress.ingressEndpoint]
|
||||||
ip = "foobar"
|
ip = "foobar"
|
||||||
hostname = "foobar"
|
hostname = "foobar"
|
||||||
|
|
|
@ -114,6 +114,7 @@ providers:
|
||||||
labelSelector: foobar
|
labelSelector: foobar
|
||||||
ingressClass: foobar
|
ingressClass: foobar
|
||||||
throttleDuration: 42s
|
throttleDuration: 42s
|
||||||
|
allowEmptyServices: true
|
||||||
ingressEndpoint:
|
ingressEndpoint:
|
||||||
ip: foobar
|
ip: foobar
|
||||||
hostname: foobar
|
hostname: foobar
|
||||||
|
|
|
@ -13,3 +13,4 @@
|
||||||
address = ":8000"
|
address = ":8000"
|
||||||
|
|
||||||
[providers.kubernetesIngress]
|
[providers.kubernetesIngress]
|
||||||
|
allowEmptyServices = true
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
kind: Ingress
|
|
||||||
apiVersion: networking.k8s.io/v1beta1
|
|
||||||
metadata:
|
|
||||||
name: ""
|
|
||||||
namespace: testing
|
|
||||||
|
|
||||||
spec:
|
|
||||||
rules:
|
|
||||||
- host: traefik.tchouk
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /bar
|
|
||||||
backend:
|
|
||||||
serviceName: service1
|
|
||||||
servicePort: 80
|
|
|
@ -45,6 +45,7 @@ type Provider struct {
|
||||||
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation or IngressClass name to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
|
IngressClass string `description:"Value of kubernetes.io/ingress.class annotation or IngressClass name to watch for." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"`
|
||||||
IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint." json:"ingressEndpoint,omitempty" toml:"ingressEndpoint,omitempty" yaml:"ingressEndpoint,omitempty" export:"true"`
|
IngressEndpoint *EndpointIngress `description:"Kubernetes Ingress Endpoint." json:"ingressEndpoint,omitempty" toml:"ingressEndpoint,omitempty" yaml:"ingressEndpoint,omitempty" export:"true"`
|
||||||
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
||||||
|
AllowEmptyServices bool `description:"Allow creation of services without endpoints." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
|
||||||
lastConfiguration safe.Safe
|
lastConfiguration safe.Safe
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,6 +242,14 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(service.LoadBalancer.Servers) == 0 && !p.AllowEmptyServices {
|
||||||
|
log.FromContext(ctx).
|
||||||
|
WithField("serviceName", ingress.Spec.DefaultBackend.Service.Name).
|
||||||
|
WithField("servicePort", ingress.Spec.DefaultBackend.Service.Port.String()).
|
||||||
|
Errorf("Skipping service: no endpoints found")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
rt := &dynamic.Router{
|
rt := &dynamic.Router{
|
||||||
Rule: "PathPrefix(`/`)",
|
Rule: "PathPrefix(`/`)",
|
||||||
Priority: math.MinInt32,
|
Priority: math.MinInt32,
|
||||||
|
@ -278,6 +287,14 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(service.LoadBalancer.Servers) == 0 && !p.AllowEmptyServices {
|
||||||
|
log.FromContext(ctx).
|
||||||
|
WithField("serviceName", pa.Backend.Service.Name).
|
||||||
|
WithField("servicePort", pa.Backend.Service.Port.String()).
|
||||||
|
Errorf("Skipping service: no endpoints found")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
portString := pa.Backend.Service.Port.Name
|
portString := pa.Backend.Service.Port.Name
|
||||||
|
|
||||||
if len(pa.Backend.Service.Port.Name) == 0 {
|
if len(pa.Backend.Service.Port.Name) == 0 {
|
||||||
|
@ -534,10 +551,6 @@ func loadService(client Client, namespace string, backend networkingv1.IngressBa
|
||||||
return nil, errors.New("endpoints not found")
|
return nil, errors.New("endpoints not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(endpoints.Subsets) == 0 {
|
|
||||||
return nil, errors.New("subset not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
var port int32
|
var port int32
|
||||||
for _, subset := range endpoints.Subsets {
|
for _, subset := range endpoints.Subsets {
|
||||||
for _, p := range subset.Ports {
|
for _, p := range subset.Ports {
|
||||||
|
@ -562,10 +575,6 @@ func loadService(client Client, namespace string, backend networkingv1.IngressBa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(svc.LoadBalancer.Servers) == 0 {
|
|
||||||
return nil, errors.New("no valid subset found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return svc, nil
|
return svc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
ingressClass string
|
ingressClass string
|
||||||
serverVersion string
|
serverVersion string
|
||||||
expected *dynamic.Configuration
|
expected *dynamic.Configuration
|
||||||
|
allowEmptyServices bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "Empty ingresses",
|
desc: "Empty ingresses",
|
||||||
|
@ -445,12 +446,24 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Ingress with one service without endpoints subset",
|
desc: "Ingress with one service without endpoints subset",
|
||||||
|
allowEmptyServices: true,
|
||||||
expected: &dynamic.Configuration{
|
expected: &dynamic.Configuration{
|
||||||
TCP: &dynamic.TCPConfiguration{},
|
TCP: &dynamic.TCPConfiguration{},
|
||||||
HTTP: &dynamic.HTTPConfiguration{
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
Middlewares: map[string]*dynamic.Middleware{},
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
Routers: map[string]*dynamic.Router{},
|
Routers: map[string]*dynamic.Router{
|
||||||
Services: map[string]*dynamic.Service{},
|
"testing-traefik-tchouk-bar": {
|
||||||
|
Rule: "Host(`traefik.tchouk`) && PathPrefix(`/bar`)",
|
||||||
|
Service: "testing-service1-80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"testing-service1-80": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
PassHostHeader: Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1631,7 +1644,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||||
|
|
||||||
clientMock := newClientMock(serverVersion, paths...)
|
clientMock := newClientMock(serverVersion, paths...)
|
||||||
|
|
||||||
p := Provider{IngressClass: test.ingressClass}
|
p := Provider{IngressClass: test.ingressClass, AllowEmptyServices: test.allowEmptyServices}
|
||||||
conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)
|
conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)
|
||||||
|
|
||||||
assert.Equal(t, test.expected, conf)
|
assert.Equal(t, test.expected, conf)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<q-card flat bordered v-bind:class="['panel-servers', {'panel-servers-dense':isDense}]">
|
<q-card flat bordered v-bind:class="['panel-servers', {'panel-servers-dense':isDense}]">
|
||||||
<q-scroll-area :thumb-style="appThumbStyle" style="height:100%;">
|
<q-scroll-area v-if="data.loadBalancer.servers" :thumb-style="appThumbStyle" style="height:100%;">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div class="row items-start no-wrap">
|
<div class="row items-start no-wrap">
|
||||||
<div class="col-3" v-if="showStatus">
|
<div class="col-3" v-if="showStatus">
|
||||||
|
@ -33,6 +33,18 @@
|
||||||
<q-separator />
|
<q-separator />
|
||||||
</div>
|
</div>
|
||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
|
<q-card-section v-else style="height: 100%">
|
||||||
|
<div class="row items-center" style="height: 100%">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="block-empty"></div>
|
||||||
|
<div class="q-pb-lg block-empty-logo">
|
||||||
|
<img v-if="$q.dark.isActive" alt="empty" src="~assets/middlewares-empty-dark.svg">
|
||||||
|
<img v-else alt="empty" src="~assets/middlewares-empty.svg">
|
||||||
|
</div>
|
||||||
|
<div class="block-empty-label">There is no<br>Server available</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -121,6 +133,19 @@ export default {
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.block-empty {
|
||||||
|
&-logo {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
&-label {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #b8b8b8;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="serviceByName.item.loadBalancer && serviceByName.item.loadBalancer.servers" class="col-12 col-md-4 q-mb-lg path-block">
|
<div v-if="serviceByName.item.loadBalancer" class="col-12 col-md-4 q-mb-lg path-block">
|
||||||
<div class="row no-wrap items-center q-mb-lg app-title">
|
<div class="row no-wrap items-center q-mb-lg app-title">
|
||||||
<q-icon name="eva-globe-outline"></q-icon>
|
<q-icon name="eva-globe-outline"></q-icon>
|
||||||
<div class="app-title-label">Servers</div>
|
<div class="app-title-label">Servers</div>
|
||||||
|
|
Loading…
Reference in a new issue