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
|
||||
```
|
||||
|
||||
### `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
|
||||
|
||||
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`:
|
||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||
|
||||
`--providers.kubernetesingress.allowemptyservices`:
|
||||
Allow creation of services without endpoints. (Default: ```false```)
|
||||
|
||||
`--providers.kubernetesingress.certauthfilepath`:
|
||||
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`:
|
||||
Enable Kubernetes backend with default settings. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_ALLOWEMPTYSERVICES`:
|
||||
Allow creation of services without endpoints. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_CERTAUTHFILEPATH`:
|
||||
Kubernetes certificate authority file path (not needed for in-cluster client).
|
||||
|
||||
|
|
|
@ -106,6 +106,7 @@
|
|||
labelSelector = "foobar"
|
||||
ingressClass = "foobar"
|
||||
throttleDuration = "42s"
|
||||
allowEmptyServices = true
|
||||
[providers.kubernetesIngress.ingressEndpoint]
|
||||
ip = "foobar"
|
||||
hostname = "foobar"
|
||||
|
|
|
@ -114,6 +114,7 @@ providers:
|
|||
labelSelector: foobar
|
||||
ingressClass: foobar
|
||||
throttleDuration: 42s
|
||||
allowEmptyServices: true
|
||||
ingressEndpoint:
|
||||
ip: foobar
|
||||
hostname: foobar
|
||||
|
|
|
@ -13,3 +13,4 @@
|
|||
address = ":8000"
|
||||
|
||||
[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
|
|
@ -37,15 +37,16 @@ const (
|
|||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
|
||||
Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"`
|
||||
LabelSelector string `description:"Kubernetes Ingress label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,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"`
|
||||
ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"`
|
||||
lastConfiguration safe.Safe
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
Token string `description:"Kubernetes bearer token (not needed for in-cluster client)." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty"`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
|
||||
Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"`
|
||||
LabelSelector string `description:"Kubernetes Ingress label selector to use." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,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"`
|
||||
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
|
||||
}
|
||||
|
||||
// EndpointIngress holds the endpoint information for the Kubernetes provider.
|
||||
|
@ -241,6 +242,14 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
|||
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{
|
||||
Rule: "PathPrefix(`/`)",
|
||||
Priority: math.MinInt32,
|
||||
|
@ -278,6 +287,14 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
|||
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
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
if len(endpoints.Subsets) == 0 {
|
||||
return nil, errors.New("subset not found")
|
||||
}
|
||||
|
||||
var port int32
|
||||
for _, subset := range endpoints.Subsets {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -24,10 +24,11 @@ func Bool(v bool) *bool { return &v }
|
|||
|
||||
func TestLoadConfigurationFromIngresses(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
ingressClass string
|
||||
serverVersion string
|
||||
expected *dynamic.Configuration
|
||||
desc string
|
||||
ingressClass string
|
||||
serverVersion string
|
||||
expected *dynamic.Configuration
|
||||
allowEmptyServices bool
|
||||
}{
|
||||
{
|
||||
desc: "Empty ingresses",
|
||||
|
@ -444,13 +445,25 @@ 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{
|
||||
TCP: &dynamic.TCPConfiguration{},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"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...)
|
||||
|
||||
p := Provider{IngressClass: test.ingressClass}
|
||||
p := Provider{IngressClass: test.ingressClass, AllowEmptyServices: test.allowEmptyServices}
|
||||
conf := p.loadConfigurationFromIngresses(context.Background(), clientMock)
|
||||
|
||||
assert.Equal(t, test.expected, conf)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<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>
|
||||
<div class="row items-start no-wrap">
|
||||
<div class="col-3" v-if="showStatus">
|
||||
|
@ -33,6 +33,18 @@
|
|||
<q-separator />
|
||||
</div>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
|
@ -121,6 +133,19 @@ export default {
|
|||
letter-spacing: normal;
|
||||
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>
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
</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">
|
||||
<q-icon name="eva-globe-outline"></q-icon>
|
||||
<div class="app-title-label">Servers</div>
|
||||
|
|
Loading…
Reference in a new issue