1
0
Fork 0

Add allowEmptyServices for Docker provider

This commit is contained in:
Jérôme 2022-07-06 10:24:08 +02:00 committed by GitHub
parent c51e590591
commit aff334ffb4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 253 additions and 37 deletions

View file

@ -7,6 +7,7 @@ import (
"net"
"strings"
dockertypes "github.com/docker/docker/api/types"
"github.com/docker/go-connections/nat"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/config/label"
@ -100,10 +101,13 @@ func (p *Provider) buildTCPServiceConfiguration(ctx context.Context, container d
}
}
if container.Health != "" && container.Health != dockertypes.Healthy {
return nil
}
for name, service := range configuration.Services {
ctxSvc := log.With(ctx, log.Str(log.ServiceName, name))
err := p.addServerTCP(ctxSvc, container, service.LoadBalancer)
if err != nil {
ctx := log.With(ctx, log.Str(log.ServiceName, name))
if err := p.addServerTCP(ctx, container, service.LoadBalancer); err != nil {
return fmt.Errorf("service %q error: %w", name, err)
}
}
@ -116,16 +120,18 @@ func (p *Provider) buildUDPServiceConfiguration(ctx context.Context, container d
if len(configuration.Services) == 0 {
configuration.Services = make(map[string]*dynamic.UDPService)
lb := &dynamic.UDPServersLoadBalancer{}
configuration.Services[serviceName] = &dynamic.UDPService{
LoadBalancer: lb,
LoadBalancer: &dynamic.UDPServersLoadBalancer{},
}
}
if container.Health != "" && container.Health != dockertypes.Healthy {
return nil
}
for name, service := range configuration.Services {
ctxSvc := log.With(ctx, log.Str(log.ServiceName, name))
err := p.addServerUDP(ctxSvc, container, service.LoadBalancer)
if err != nil {
ctx := log.With(ctx, log.Str(log.ServiceName, name))
if err := p.addServerUDP(ctx, container, service.LoadBalancer); err != nil {
return fmt.Errorf("service %q error: %w", name, err)
}
}
@ -145,10 +151,13 @@ func (p *Provider) buildServiceConfiguration(ctx context.Context, container dock
}
}
if container.Health != "" && container.Health != dockertypes.Healthy {
return nil
}
for name, service := range configuration.Services {
ctxSvc := log.With(ctx, log.Str(log.ServiceName, name))
err := p.addServer(ctxSvc, container, service.LoadBalancer)
if err != nil {
ctx := log.With(ctx, log.Str(log.ServiceName, name))
if err := p.addServer(ctx, container, service.LoadBalancer); err != nil {
return fmt.Errorf("service %q error: %w", name, err)
}
}
@ -174,7 +183,7 @@ func (p *Provider) keepContainer(ctx context.Context, container dockerData) bool
return false
}
if container.Health != "" && container.Health != "healthy" {
if !p.AllowEmptyServices && container.Health != "" && container.Health != dockertypes.Healthy {
logger.Debug("Filtering unhealthy or starting container")
return false
}

View file

@ -13,9 +13,6 @@ import (
"github.com/traefik/traefik/v2/pkg/config/dynamic"
)
func Int(v int) *int { return &v }
func Bool(v bool) *bool { return &v }
func TestDefaultRule(t *testing.T) {
testCases := []struct {
desc string
@ -375,11 +372,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",
@ -2234,24 +2232,12 @@ func Test_buildConfiguration(t *testing.T) {
},
},
{
desc: "one container not healthy",
desc: "one unhealthy HTTP container",
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: docker.Unhealthy,
},
},
expected: &dynamic.Configuration{
@ -2272,6 +2258,186 @@ func Test_buildConfiguration(t *testing.T) {
},
},
},
{
desc: "one unhealthy HTTP container with allowEmptyServices",
allowEmptyServices: true,
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Health: docker.Unhealthy,
},
},
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{
"Test": {
Service: "Test",
Rule: "Host(`Test.traefik.wtf`)",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"Test": {
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
},
},
{
desc: "one unhealthy TCP container",
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Health: docker.Unhealthy,
Labels: map[string]string{
"traefik.tcp.routers.foo.rule": "HostSNI(`foo.bar`)",
},
},
},
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 unhealthy TCP container with allowEmptyServices",
allowEmptyServices: true,
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Health: docker.Unhealthy,
Labels: map[string]string{
"traefik.tcp.routers.foo.rule": "HostSNI(`foo.bar`)",
},
},
},
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 unhealthy UDP container",
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Health: docker.Unhealthy,
Labels: map[string]string{
"traefik.udp.routers.foo": "true",
},
},
},
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 unhealthy UDP container with allowEmptyServices",
allowEmptyServices: true,
containers: []dockerData{
{
ServiceName: "Test",
Name: "Test",
Labels: map[string]string{
"traefik.udp.routers.foo": "true",
},
Health: docker.Unhealthy,
},
},
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{
@ -3058,9 +3224,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
@ -3515,3 +3682,7 @@ func TestSwarmGetPort(t *testing.T) {
})
}
}
func Int(v int) *int { return &v }
func Bool(v bool) *bool { return &v }

View file

@ -59,6 +59,7 @@ type Provider struct {
Network string `description:"Default Docker network used." json:"network,omitempty" toml:"network,omitempty" yaml:"network,omitempty" export:"true"`
SwarmModeRefreshSeconds ptypes.Duration `description:"Polling interval for swarm mode." json:"swarmModeRefreshSeconds,omitempty" toml:"swarmModeRefreshSeconds,omitempty" yaml:"swarmModeRefreshSeconds,omitempty" export:"true"`
HTTPClientTimeout ptypes.Duration `description:"Client timeout for HTTP connections." json:"httpClientTimeout,omitempty" toml:"httpClientTimeout,omitempty" yaml:"httpClientTimeout,omitempty" export:"true"`
AllowEmptyServices bool `description:"Disregards the Docker containers health checks with respect to the creation or removal of the corresponding services." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
defaultRuleTpl *template.Template
}