Skip to content

Commit

Permalink
Add allowEmptyServices for Docker provider
Browse files Browse the repository at this point in the history
  • Loading branch information
tomMoulard authored and kevinpollet committed Jun 30, 2022
1 parent 9215972 commit 221d8c0
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 56 deletions.
25 changes: 25 additions & 0 deletions docs/content/providers/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -714,3 +714,28 @@ providers:
```bash tab="CLI"
--providers.docker.tls.insecureSkipVerify=true
```

### `allowEmptyServices`

_Optional, Default=false_

If the parameter is set to `true`,
it allows the creation of an empty [servers load balancer](../routing/services/index.md#servers-load-balancer)
if the targeted docker service is not in a [healthy state](https://docs.docker.com/engine/reference/builder/#healthcheck).
With HTTP services,
this results in `503` HTTP responses instead of `404` ones.

```yaml tab="File (YAML)"
providers:
docker:
allowEmptyServices: true
```

```toml tab="File (TOML)"
[providers.docker]
allowEmptyServices = true
```

```bash tab="CLI"
--providers.docker.allowEmptyServices=true
```
3 changes: 3 additions & 0 deletions docs/content/reference/static-configuration/cli-ref.md
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,9 @@ Watch Consul API events. (Default: ```false```)
`--providers.docker`:
Enable Docker backend with default settings. (Default: ```false```)

`--providers.docker.allowemptyservices`:
Allow the creation of services without endpoints. (Default: ```false```)

`--providers.docker.constraints`:
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.

Expand Down
3 changes: 3 additions & 0 deletions docs/content/reference/static-configuration/env-ref.md
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,9 @@ KV Username
`TRAEFIK_PROVIDERS_DOCKER`:
Enable Docker backend with default settings. (Default: ```false```)

`TRAEFIK_PROVIDERS_DOCKER_ALLOWEMPTYSERVICES`:
Allow the creation of services without endpoints. (Default: ```false```)

`TRAEFIK_PROVIDERS_DOCKER_CONSTRAINTS`:
Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container.

Expand Down
11 changes: 6 additions & 5 deletions docs/content/reference/static-configuration/file.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,23 @@
[providers]
providersThrottleDuration = "42s"
[providers.docker]
allowEmptyServices = true
constraints = "foobar"
watch = true
endpoint = "foobar"
defaultRule = "foobar"
endpoint = "foobar"
exposedByDefault = true
useBindPortIP = true
swarmMode = true
httpClientTimeout = "42s"
network = "foobar"
swarmMode = true
swarmModeRefreshSeconds = "42s"
httpClientTimeout = "42s"
[providers.docker.tls]
ca = "foobar"
caOptional = true
cert = "foobar"
key = "foobar"
insecureSkipVerify = true
useBindPortIP = true
watch = true
[providers.file]
directory = "foobar"
watch = true
Expand Down
17 changes: 9 additions & 8 deletions docs/content/reference/static-configuration/file.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,23 @@ entryPoints:
providers:
providersThrottleDuration: 42s
docker:
allowEmptyServices: true
constraints: foobar
watch: true
endpoint: foobar
defaultRule: foobar
endpoint: foobar
exposedByDefault: true
httpClientTimeout: 42s
network: foobar
swarmMode: true
swarmModeRefreshSeconds: 42s
tls:
ca: foobar
caOptional: true
cert: foobar
key: foobar
insecureSkipVerify: true
exposedByDefault: true
key: foobar
useBindPortIP: true
swarmMode: true
network: foobar
swarmModeRefreshSeconds: 42s
httpClientTimeout: 42s
watch: true
file:
directory: foobar
watch: true
Expand Down
38 changes: 17 additions & 21 deletions pkg/provider/docker/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@ func (p *Provider) buildConfiguration(ctx context.Context, containersInspected [
}

func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.TCPConfiguration) error {
logger := log.FromContext(ctx)

serviceName := getServiceName(container)

if len(configuration.Services) == 0 {
Expand All @@ -102,11 +100,6 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, container d
}
}

if container.Health != "" && container.Health != "healthy" {
logger.Debug("Filtering unhealthy or starting container")
return nil
}

for name, service := range configuration.Services {
ctxSvc := log.With(ctx, log.Str(log.ServiceName, name))
err := p.addServerTCP(ctxSvc, container, service.LoadBalancer)
Expand All @@ -119,8 +112,6 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, container d
}

func (p *Provider) buildUDPServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.UDPConfiguration) error {
logger := log.FromContext(ctx)

serviceName := getServiceName(container)

if len(configuration.Services) == 0 {
Expand All @@ -131,11 +122,6 @@ func (p *Provider) buildUDPServiceConfiguration(ctx context.Context, container d
}
}

if container.Health != "" && container.Health != "healthy" {
logger.Debug("Filtering unhealthy or starting container")
return nil
}

for name, service := range configuration.Services {
ctxSvc := log.With(ctx, log.Str(log.ServiceName, name))
err := p.addServerUDP(ctxSvc, container, service.LoadBalancer)
Expand All @@ -148,8 +134,6 @@ func (p *Provider) buildUDPServiceConfiguration(ctx context.Context, container d
}

func (p *Provider) buildServiceConfiguration(ctx context.Context, container dockerData, configuration *dynamic.HTTPConfiguration) error {
logger := log.FromContext(ctx)

serviceName := getServiceName(container)

if len(configuration.Services) == 0 {
Expand All @@ -161,11 +145,6 @@ func (p *Provider) buildServiceConfiguration(ctx context.Context, container dock
}
}

if container.Health != "" && container.Health != "healthy" {
logger.Debug("Filtering unhealthy or starting container")
return nil
}

for name, service := range configuration.Services {
ctxSvc := log.With(ctx, log.Str(log.ServiceName, name))
err := p.addServer(ctxSvc, container, service.LoadBalancer)
Expand Down Expand Up @@ -195,6 +174,11 @@ func (p *Provider) keepContainer(ctx context.Context, container dockerData) bool
return false
}

if !p.AllowEmptyServices && container.Health != "" && container.Health != "healthy" {
logger.Debug("Filtering unhealthy or starting container")
return false
}

return true
}

Expand All @@ -203,6 +187,10 @@ func (p *Provider) addServerTCP(ctx context.Context, container dockerData, loadB
return errors.New("load-balancer is not defined")
}

if container.Health != "" && container.Health != "healthy" {
return nil
}

var serverPort string
if len(loadBalancer.Servers) > 0 {
serverPort = loadBalancer.Servers[0].Port
Expand Down Expand Up @@ -233,6 +221,10 @@ func (p *Provider) addServerUDP(ctx context.Context, container dockerData, loadB
return errors.New("load-balancer is not defined")
}

if container.Health != "" && container.Health != "healthy" {
return nil
}

var serverPort string
if len(loadBalancer.Servers) > 0 {
serverPort = loadBalancer.Servers[0].Port
Expand Down Expand Up @@ -263,6 +255,10 @@ func (p *Provider) addServer(ctx context.Context, container dockerData, loadBala
return errors.New("load-balancer is not defined")
}

if container.Health != "" && container.Health != "healthy" {
return nil
}

var serverPort string
if len(loadBalancer.Servers) > 0 {
serverPort = loadBalancer.Servers[0].Port
Expand Down
144 changes: 122 additions & 22 deletions pkg/provider/docker/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,11 +375,12 @@ func TestDefaultRule(t *testing.T) {

func Test_buildConfiguration(t *testing.T) {
testCases := []struct {
desc string
containers []dockerData
useBindPortIP bool
constraints string
expected *dynamic.Configuration
desc string
containers []dockerData
useBindPortIP bool
constraints string
expected *dynamic.Configuration
allowEmptyServices bool
}{
{
desc: "invalid HTTP service definition",
Expand Down Expand Up @@ -2234,24 +2235,41 @@ func Test_buildConfiguration(t *testing.T) {
},
},
{
desc: "one container not healthy",
desc: "one container not healthy without allowEmpty",
allowEmptyServices: false,
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Labels: map[string]string{},
NetworkSettings: networkSettings{
Ports: nat.PortMap{
nat.Port("80/tcp"): []nat.PortBinding{},
},
Networks: map[string]*networkData{
"bridge": {
Name: "bridge",
Addr: "127.0.0.1",
},
},
},
Health: "not_healthy",
Health: "not_healthy",
},
},
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
},
},
{
desc: "one HTTP container not healthy",
allowEmptyServices: true,
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Health: "not_healthy",
},
},
expected: &dynamic.Configuration{
Expand Down Expand Up @@ -2283,6 +2301,87 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
{
desc: "one TCP container not healthy",
allowEmptyServices: true,
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Labels: map[string]string{
"traefik.tcp.routers.foo.rule": "HostSNI(`foo.bar`)",
},
Health: "not_healthy",
},
},
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"foo": {
Service: "Test",
Rule: "HostSNI(`foo.bar`)",
},
},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{
"Test": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
TerminationDelay: Int(100),
},
},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
},
},
{
desc: "one UDP container not healthy",
allowEmptyServices: true,
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Labels: map[string]string{
"traefik.udp.routers.foo": "true",
},
Health: "not_healthy",
},
},
expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{
"foo": {
Service: "Test",
},
},
Services: map[string]*dynamic.UDPService{
"Test": {
LoadBalancer: &dynamic.UDPServersLoadBalancer{},
},
},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
},
},
{
desc: "one container with non matching constraints",
containers: []dockerData{
Expand Down Expand Up @@ -3069,9 +3168,10 @@ func Test_buildConfiguration(t *testing.T) {
t.Parallel()

p := Provider{
ExposedByDefault: true,
DefaultRule: "Host(`{{ normalize .Name }}.traefik.wtf`)",
UseBindPortIP: test.useBindPortIP,
AllowEmptyServices: test.allowEmptyServices,
DefaultRule: "Host(`{{ normalize .Name }}.traefik.wtf`)",
ExposedByDefault: true,
UseBindPortIP: test.useBindPortIP,
}
p.Constraints = test.constraints

Expand Down

0 comments on commit 221d8c0

Please sign in to comment.