diff --git a/provider/docker/config.go b/provider/docker/config.go index e1fbae873..fb91f4749 100644 --- a/provider/docker/config.go +++ b/provider/docker/config.go @@ -2,6 +2,8 @@ package docker import ( "context" + "crypto/md5" + "encoding/hex" "fmt" "net" "strconv" @@ -107,13 +109,11 @@ func (p *Provider) buildConfigurationV2(containersInspected []dockerData) *types } func getServiceNameKey(container dockerData, swarmMode bool, segmentName string) string { - serviceNameKey := container.ServiceName - - if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); !swarmMode && err == nil { - serviceNameKey = values[labelDockerComposeService] + values[labelDockerComposeProject] + if swarmMode { + return container.ServiceName + segmentName } - return serviceNameKey + segmentName + return getServiceName(container) + segmentName } func (p *Provider) containerFilter(container dockerData) bool { @@ -170,7 +170,7 @@ func checkSegmentPort(labels map[string]string, segmentName string) error { func (p *Provider) getFrontendName(container dockerData, idx int) string { var name string if len(container.SegmentName) > 0 { - name = getBackendName(container) + name = container.SegmentName + "-" + getBackendName(container) } else { name = p.getFrontendRule(container, container.SegmentLabels) + "-" + strconv.Itoa(idx) } @@ -262,17 +262,21 @@ func isBackendLBSwarm(container dockerData) bool { return label.GetBoolValue(container.Labels, labelBackendLoadBalancerSwarm, false) } -func getSegmentBackendName(container dockerData) string { - serviceName := container.ServiceName - if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil { - serviceName = provider.Normalize(values[labelDockerComposeService] + "_" + values[labelDockerComposeProject]) +func getBackendName(container dockerData) string { + if len(container.SegmentName) > 0 { + return getSegmentBackendName(container) } + return getDefaultBackendName(container) +} + +func getSegmentBackendName(container dockerData) string { + serviceName := getServiceName(container) if value := label.GetStringValue(container.SegmentLabels, label.TraefikBackend, ""); len(value) > 0 { return provider.Normalize(serviceName + "-" + value) } - return provider.Normalize(serviceName + "-" + getDefaultBackendName(container) + "-" + container.SegmentName) + return provider.Normalize(serviceName + "-" + container.SegmentName) } func getDefaultBackendName(container dockerData) string { @@ -280,19 +284,17 @@ func getDefaultBackendName(container dockerData) string { return provider.Normalize(value) } - if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil { - return provider.Normalize(values[labelDockerComposeService] + "_" + values[labelDockerComposeProject]) - } - - return provider.Normalize(container.ServiceName) + return provider.Normalize(getServiceName(container)) } -func getBackendName(container dockerData) string { - if len(container.SegmentName) > 0 { - return getSegmentBackendName(container) +func getServiceName(container dockerData) string { + serviceName := container.ServiceName + + if values, err := label.GetStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil { + serviceName = values[labelDockerComposeService] + "_" + values[labelDockerComposeProject] } - return getDefaultBackendName(container) + return serviceName } func getPort(container dockerData) string { @@ -322,7 +324,7 @@ func getPort(container dockerData) string { func (p *Provider) getServers(containers []dockerData) map[string]types.Server { var servers map[string]types.Server - for i, container := range containers { + for _, container := range containers { ip := p.getIPAddress(container) if len(ip) == 0 { log.Warnf("Unable to find the IP address for the container %q: the server is ignored.", container.Name) @@ -336,16 +338,30 @@ func (p *Provider) getServers(containers []dockerData) map[string]types.Server { protocol := label.GetStringValue(container.SegmentLabels, label.TraefikProtocol, label.DefaultProtocol) port := getPort(container) - serverName := "server-" + container.SegmentName + "-" + container.Name - if len(container.SegmentName) > 0 { - serverName += "-" + strconv.Itoa(i) + serverURL := fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(ip, port)) + + serverName := getServerName(container.Name, serverURL) + if _, exist := servers[serverName]; exist { + log.Debugf("Skipping server %q with the same URL.", serverName) + continue } - servers[provider.Normalize(serverName)] = types.Server{ - URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(ip, port)), + servers[serverName] = types.Server{ + URL: serverURL, Weight: label.GetIntValue(container.SegmentLabels, label.TraefikWeight, label.DefaultWeight), } } return servers } + +func getServerName(containerName, url string) string { + hash := md5.New() + _, err := hash.Write([]byte(url)) + if err != nil { + // Impossible case + log.Errorf("Fail to hash server URL %q", url) + } + + return provider.Normalize("server-" + containerName + "-" + hex.EncodeToString(hash.Sum(nil))) +} diff --git a/provider/docker/config_container_docker_test.go b/provider/docker/config_container_docker_test.go index 96f0b09fe..87d41fafb 100644 --- a/provider/docker/config_container_docker_test.go +++ b/provider/docker/config_container_docker_test.go @@ -55,7 +55,7 @@ func TestDockerBuildConfiguration(t *testing.T) { expectedBackends: map[string]*types.Backend{ "backend-test": { Servers: map[string]types.Server{ - "server-test": { + "server-test-842895ca2aca17f6ee36ddb2f621194d": { URL: "http://127.0.0.1:80", Weight: label.DefaultWeight, }, @@ -270,7 +270,7 @@ func TestDockerBuildConfiguration(t *testing.T) { expectedBackends: map[string]*types.Backend{ "backend-foobar": { Servers: map[string]types.Server{ - "server-test1": { + "server-test1-7f6444e0dff3330c8b0ad2bbbd383b0f": { URL: "https://127.0.0.1:666", Weight: 12, }, @@ -372,10 +372,11 @@ func TestDockerBuildConfiguration(t *testing.T) { expectedBackends: map[string]*types.Backend{ "backend-myService-myProject": { Servers: map[string]types.Server{ - "server-test-0": { + "server-test-0-842895ca2aca17f6ee36ddb2f621194d": { URL: "http://127.0.0.1:80", Weight: label.DefaultWeight, - }, "server-test-1": { + }, + "server-test-1-48093b9fc43454203aacd2bc4057a08c": { URL: "http://127.0.0.2:80", Weight: label.DefaultWeight, }, @@ -384,7 +385,7 @@ func TestDockerBuildConfiguration(t *testing.T) { }, "backend-myService2-myProject": { Servers: map[string]types.Server{ - "server-test-2": { + "server-test-2-405767e9733427148cd8dae6c4d331b0": { URL: "http://127.0.0.3:80", Weight: label.DefaultWeight, }, @@ -1055,7 +1056,7 @@ func TestDockerGetServers(t *testing.T) { })), }, expected: map[string]types.Server{ - "server-test1": { + "server-test1-fb00f762970935200c76ccdaf91458f6": { URL: "http://10.10.10.10:80", Weight: 1, }, @@ -1084,15 +1085,15 @@ func TestDockerGetServers(t *testing.T) { })), }, expected: map[string]types.Server{ - "server-test1": { + "server-test1-743440b6f4a8ffd8737626215f2c5a33": { URL: "http://10.10.10.11:80", Weight: 1, }, - "server-test2": { + "server-test2-547f74bbb5da02b6c8141ce9aa96c13b": { URL: "http://10.10.10.12:81", Weight: 1, }, - "server-test3": { + "server-test3-c57fd8b848c814a3f2a4a4c12e13c179": { URL: "http://10.10.10.13:82", Weight: 1, }, @@ -1121,11 +1122,11 @@ func TestDockerGetServers(t *testing.T) { })), }, expected: map[string]types.Server{ - "server-test2": { + "server-test2-547f74bbb5da02b6c8141ce9aa96c13b": { URL: "http://10.10.10.12:81", Weight: 1, }, - "server-test3": { + "server-test3-c57fd8b848c814a3f2a4a4c12e13c179": { URL: "http://10.10.10.13:82", Weight: 1, }, diff --git a/provider/docker/config_container_swarm_test.go b/provider/docker/config_container_swarm_test.go index ba2fed436..cc689d561 100644 --- a/provider/docker/config_container_swarm_test.go +++ b/provider/docker/config_container_swarm_test.go @@ -57,7 +57,7 @@ func TestSwarmBuildConfiguration(t *testing.T) { expectedBackends: map[string]*types.Backend{ "backend-test": { Servers: map[string]types.Server{ - "server-test": { + "server-test-842895ca2aca17f6ee36ddb2f621194d": { URL: "http://127.0.0.1:80", Weight: label.DefaultWeight, }, @@ -238,7 +238,6 @@ func TestSwarmBuildConfiguration(t *testing.T) { ReferrerPolicy: "foo", IsDevelopment: true, }, - Errors: map[string]*types.ErrorPage{ "foo": { Status: []string{"404"}, @@ -276,7 +275,7 @@ func TestSwarmBuildConfiguration(t *testing.T) { expectedBackends: map[string]*types.Backend{ "backend-foobar": { Servers: map[string]types.Server{ - "server-test1": { + "server-test1-7f6444e0dff3330c8b0ad2bbbd383b0f": { URL: "https://127.0.0.1:666", Weight: 12, }, diff --git a/provider/docker/config_segment_test.go b/provider/docker/config_segment_test.go index caeeaee82..4d7865a49 100644 --- a/provider/docker/config_segment_test.go +++ b/provider/docker/config_segment_test.go @@ -42,22 +42,22 @@ func TestSegmentBuildConfiguration(t *testing.T) { ), }, expectedFrontends: map[string]*types.Frontend{ - "frontend-foo-foo-sauternes": { - Backend: "backend-foo-foo-sauternes", + "frontend-sauternes-foo-sauternes": { + Backend: "backend-foo-sauternes", PassHostHeader: true, EntryPoints: []string{"http", "https"}, BasicAuth: []string{}, Routes: map[string]types.Route{ - "route-frontend-foo-foo-sauternes": { + "route-frontend-sauternes-foo-sauternes": { Rule: "Host:foo.docker.localhost", }, }, }, }, expectedBackends: map[string]*types.Backend{ - "backend-foo-foo-sauternes": { + "backend-foo-sauternes": { Servers: map[string]types.Server{ - "server-sauternes-foo-0": { + "server-foo-863563a2e23c95502862016417ee95ea": { URL: "http://127.0.0.1:2503", Weight: label.DefaultWeight, }, @@ -132,8 +132,8 @@ func TestSegmentBuildConfiguration(t *testing.T) { ), }, expectedFrontends: map[string]*types.Frontend{ - "frontend-foo-foo-sauternes": { - Backend: "backend-foo-foo-sauternes", + "frontend-sauternes-foo-sauternes": { + Backend: "backend-foo-sauternes", EntryPoints: []string{ "http", "https", @@ -224,16 +224,16 @@ func TestSegmentBuildConfiguration(t *testing.T) { }, Routes: map[string]types.Route{ - "route-frontend-foo-foo-sauternes": { + "route-frontend-sauternes-foo-sauternes": { Rule: "Host:foo.docker.localhost", }, }, }, }, expectedBackends: map[string]*types.Backend{ - "backend-foo-foo-sauternes": { + "backend-foo-sauternes": { Servers: map[string]types.Server{ - "server-sauternes-foo-0": { + "server-foo-7f6444e0dff3330c8b0ad2bbbd383b0f": { URL: "https://127.0.0.1:666", Weight: 12, }, @@ -278,7 +278,7 @@ func TestSegmentBuildConfiguration(t *testing.T) { ), }, expectedFrontends: map[string]*types.Frontend{ - "frontend-test1-foobar": { + "frontend-sauternes-test1-foobar": { Backend: "backend-test1-foobar", PassHostHeader: false, Priority: 5000, @@ -288,18 +288,18 @@ func TestSegmentBuildConfiguration(t *testing.T) { EntryPoint: "https", }, Routes: map[string]types.Route{ - "route-frontend-test1-foobar": { + "route-frontend-sauternes-test1-foobar": { Rule: "Path:/mypath", }, }, }, - "frontend-test2-test2-anothersauternes": { - Backend: "backend-test2-test2-anothersauternes", + "frontend-anothersauternes-test2-anothersauternes": { + Backend: "backend-test2-anothersauternes", PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, Routes: map[string]types.Route{ - "route-frontend-test2-test2-anothersauternes": { + "route-frontend-anothersauternes-test2-anothersauternes": { Rule: "Path:/anotherpath", }, }, @@ -308,16 +308,16 @@ func TestSegmentBuildConfiguration(t *testing.T) { expectedBackends: map[string]*types.Backend{ "backend-test1-foobar": { Servers: map[string]types.Server{ - "server-sauternes-test1-0": { + "server-test1-79533a101142718f0fdf84c42593c41e": { URL: "https://127.0.0.1:2503", Weight: 80, }, }, CircuitBreaker: nil, }, - "backend-test2-test2-anothersauternes": { + "backend-test2-anothersauternes": { Servers: map[string]types.Server{ - "server-anothersauternes-test2-0": { + "server-test2-e9c1b66f9af919aa46053fbc2391bb4a": { URL: "http://127.0.0.1:8079", Weight: 33, }, @@ -326,6 +326,152 @@ func TestSegmentBuildConfiguration(t *testing.T) { }, }, }, + { + desc: "several segments with the same backend name and same port", + containers: []docker.ContainerJSON{ + containerJSON( + name("test1"), + labels(map[string]string{ + "traefik.port": "2503", + "traefik.protocol": "https", + "traefik.weight": "80", + "traefik.frontend.entryPoints": "http,https", + "traefik.frontend.redirect.entryPoint": "https", + + "traefik.sauternes.backend": "foobar", + "traefik.sauternes.frontend.rule": "Path:/sauternes", + "traefik.sauternes.frontend.priority": "5000", + + "traefik.arbois.backend": "foobar", + "traefik.arbois.frontend.rule": "Path:/arbois", + "traefik.arbois.frontend.priority": "3000", + }), + ports(nat.PortMap{ + "80/tcp": {}, + }), + withNetwork("bridge", ipv4("127.0.0.1")), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-sauternes-test1-foobar": { + Backend: "backend-test1-foobar", + PassHostHeader: true, + Priority: 5000, + EntryPoints: []string{"http", "https"}, + BasicAuth: []string{}, + Redirect: &types.Redirect{ + EntryPoint: "https", + }, + Routes: map[string]types.Route{ + "route-frontend-sauternes-test1-foobar": { + Rule: "Path:/sauternes", + }, + }, + }, + "frontend-arbois-test1-foobar": { + Backend: "backend-test1-foobar", + PassHostHeader: true, + Priority: 3000, + EntryPoints: []string{"http", "https"}, + BasicAuth: []string{}, + Redirect: &types.Redirect{ + EntryPoint: "https", + }, + Routes: map[string]types.Route{ + "route-frontend-arbois-test1-foobar": { + Rule: "Path:/arbois", + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-test1-foobar": { + Servers: map[string]types.Server{ + "server-test1-79533a101142718f0fdf84c42593c41e": { + URL: "https://127.0.0.1:2503", + Weight: 80, + }, + }, + CircuitBreaker: nil, + }, + }, + }, + { + desc: "several segments with the same backend name and different port (wrong behavior)", + containers: []docker.ContainerJSON{ + containerJSON( + name("test1"), + labels(map[string]string{ + "traefik.protocol": "https", + "traefik.frontend.entryPoints": "http,https", + "traefik.frontend.redirect.entryPoint": "https", + + "traefik.sauternes.port": "2503", + "traefik.sauternes.weight": "80", + "traefik.sauternes.backend": "foobar", + "traefik.sauternes.frontend.rule": "Path:/sauternes", + "traefik.sauternes.frontend.priority": "5000", + + "traefik.arbois.port": "2504", + "traefik.arbois.weight": "90", + "traefik.arbois.backend": "foobar", + "traefik.arbois.frontend.rule": "Path:/arbois", + "traefik.arbois.frontend.priority": "3000", + }), + ports(nat.PortMap{ + "80/tcp": {}, + }), + withNetwork("bridge", ipv4("127.0.0.1")), + ), + }, + expectedFrontends: map[string]*types.Frontend{ + "frontend-sauternes-test1-foobar": { + Backend: "backend-test1-foobar", + PassHostHeader: true, + Priority: 5000, + EntryPoints: []string{"http", "https"}, + BasicAuth: []string{}, + Redirect: &types.Redirect{ + EntryPoint: "https", + }, + Routes: map[string]types.Route{ + "route-frontend-sauternes-test1-foobar": { + Rule: "Path:/sauternes", + }, + }, + }, + "frontend-arbois-test1-foobar": { + Backend: "backend-test1-foobar", + PassHostHeader: true, + Priority: 3000, + EntryPoints: []string{"http", "https"}, + BasicAuth: []string{}, + Redirect: &types.Redirect{ + EntryPoint: "https", + }, + Routes: map[string]types.Route{ + "route-frontend-arbois-test1-foobar": { + Rule: "Path:/arbois", + }, + }, + }, + }, + expectedBackends: map[string]*types.Backend{ + "backend-test1-foobar": { + Servers: map[string]types.Server{ + "server-test1-79533a101142718f0fdf84c42593c41e": { + URL: "https://127.0.0.1:2503", + Weight: 80, + }, + "server-test1-315a41140f1bd825b066e39686c18482": { + URL: "https://127.0.0.1:2504", + Weight: 90, + }, + }, + CircuitBreaker: nil, + }, + }, + }, } provider := &Provider{