Fix multiple frontends with docker-compose --scale
This commit is contained in:
parent
8519b0d353
commit
f0589b310f
7 changed files with 194 additions and 8 deletions
78
integration/docker_compose_test.go
Normal file
78
integration/docker_compose_test.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/integration/try"
|
||||||
|
"github.com/containous/traefik/testhelpers"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
"github.com/go-check/check"
|
||||||
|
checker "github.com/vdemeester/shakers"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
composeProject = "minimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Docker test suites
|
||||||
|
type DockerComposeSuite struct {
|
||||||
|
BaseSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerComposeSuite) SetUpSuite(c *check.C) {
|
||||||
|
s.createComposeProject(c, composeProject)
|
||||||
|
s.composeProject.Start(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerComposeSuite) TearDownSuite(c *check.C) {
|
||||||
|
// shutdown and delete compose project
|
||||||
|
if s.composeProject != nil {
|
||||||
|
s.composeProject.Stop(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerComposeSuite) TestComposeScale(c *check.C) {
|
||||||
|
var serviceCount = 2
|
||||||
|
var composeService = "whoami1"
|
||||||
|
|
||||||
|
s.composeProject.Scale(c, composeService, serviceCount)
|
||||||
|
|
||||||
|
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
|
||||||
|
defer os.Remove(file)
|
||||||
|
|
||||||
|
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||||
|
defer display(c)
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
req := testhelpers.MustNewRequest(http.MethodGet, "http://127.0.0.1:8000/whoami", nil)
|
||||||
|
req.Host = "my.super.host"
|
||||||
|
|
||||||
|
_, err = try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
resp, err := http.Get("http://127.0.0.1:8080/api/providers/docker")
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
var provider types.Configuration
|
||||||
|
c.Assert(json.Unmarshal(body, &provider), checker.IsNil)
|
||||||
|
|
||||||
|
// check that we have only one backend with n servers
|
||||||
|
c.Assert(provider.Backends, checker.HasLen, 1)
|
||||||
|
|
||||||
|
myBackend := provider.Backends["backend-"+composeService+"-integrationtest"+composeProject]
|
||||||
|
c.Assert(myBackend, checker.NotNil)
|
||||||
|
c.Assert(myBackend.Servers, checker.HasLen, serviceCount)
|
||||||
|
|
||||||
|
// check that we have only one frontend
|
||||||
|
c.Assert(provider.Frontends, checker.HasLen, 1)
|
||||||
|
}
|
|
@ -99,7 +99,7 @@ func (s *DockerSuite) TestSimpleConfiguration(c *check.C) {
|
||||||
defer cmd.Process.Kill()
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
// TODO validate : run on 80
|
// TODO validate : run on 80
|
||||||
// Expected a 404 as we did not comfigure anything
|
// Expected a 404 as we did not configure anything
|
||||||
err = try.GetRequest("http://127.0.0.1:8000/", 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
|
err = try.GetRequest("http://127.0.0.1:8000/", 500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ func init() {
|
||||||
check.Suite(&ConstraintSuite{})
|
check.Suite(&ConstraintSuite{})
|
||||||
check.Suite(&ConsulCatalogSuite{})
|
check.Suite(&ConsulCatalogSuite{})
|
||||||
check.Suite(&ConsulSuite{})
|
check.Suite(&ConsulSuite{})
|
||||||
|
check.Suite(&DockerComposeSuite{})
|
||||||
check.Suite(&DockerSuite{})
|
check.Suite(&DockerSuite{})
|
||||||
check.Suite(&DynamoDBSuite{})
|
check.Suite(&DynamoDBSuite{})
|
||||||
check.Suite(&EtcdSuite{})
|
check.Suite(&EtcdSuite{})
|
||||||
|
|
4
integration/resources/compose/minimal.yml
Normal file
4
integration/resources/compose/minimal.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
whoami1:
|
||||||
|
image: emilevauge/whoami
|
||||||
|
labels:
|
||||||
|
- traefik.frontend.rule=PathPrefix:/whoami
|
|
@ -67,12 +67,13 @@ func (p *Provider) buildConfigurationV2(containersInspected []dockerData) *types
|
||||||
container.SegmentLabels = labels
|
container.SegmentLabels = labels
|
||||||
container.SegmentName = segmentName
|
container.SegmentName = segmentName
|
||||||
|
|
||||||
// Frontends
|
serviceNamesKey := getServiceNameKey(container, p.SwarmMode, segmentName)
|
||||||
if _, exists := serviceNames[container.ServiceName+segmentName]; !exists {
|
|
||||||
|
if _, exists := serviceNames[serviceNamesKey]; !exists {
|
||||||
frontendName := p.getFrontendName(container, idx)
|
frontendName := p.getFrontendName(container, idx)
|
||||||
frontends[frontendName] = append(frontends[frontendName], container)
|
frontends[frontendName] = append(frontends[frontendName], container)
|
||||||
if len(container.ServiceName+segmentName) > 0 {
|
if len(serviceNamesKey) > 0 {
|
||||||
serviceNames[container.ServiceName+segmentName] = struct{}{}
|
serviceNames[serviceNamesKey] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +105,16 @@ func (p *Provider) buildConfigurationV2(containersInspected []dockerData) *types
|
||||||
return configuration
|
return configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceNameKey + segmentName
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) containerFilter(container dockerData) bool {
|
func (p *Provider) containerFilter(container dockerData) bool {
|
||||||
if !label.IsEnabled(container.Labels, p.ExposedByDefault) {
|
if !label.IsEnabled(container.Labels, p.ExposedByDefault) {
|
||||||
log.Debugf("Filtering disabled container %s", container.Name)
|
log.Debugf("Filtering disabled container %s", container.Name)
|
||||||
|
|
|
@ -304,6 +304,95 @@ func TestDockerBuildConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "when docker compose scale with different compose service names",
|
||||||
|
containers: []docker.ContainerJSON{
|
||||||
|
containerJSON(
|
||||||
|
name("test_0"),
|
||||||
|
labels(map[string]string{
|
||||||
|
labelDockerComposeProject: "myProject",
|
||||||
|
labelDockerComposeService: "myService",
|
||||||
|
}),
|
||||||
|
ports(nat.PortMap{
|
||||||
|
"80/tcp": {},
|
||||||
|
}),
|
||||||
|
withNetwork("bridge", ipv4("127.0.0.1")),
|
||||||
|
),
|
||||||
|
containerJSON(
|
||||||
|
name("test_1"),
|
||||||
|
labels(map[string]string{
|
||||||
|
labelDockerComposeProject: "myProject",
|
||||||
|
labelDockerComposeService: "myService",
|
||||||
|
}),
|
||||||
|
|
||||||
|
ports(nat.PortMap{
|
||||||
|
"80/tcp": {},
|
||||||
|
}),
|
||||||
|
|
||||||
|
withNetwork("bridge", ipv4("127.0.0.2")),
|
||||||
|
),
|
||||||
|
containerJSON(
|
||||||
|
name("test_2"),
|
||||||
|
labels(map[string]string{
|
||||||
|
labelDockerComposeProject: "myProject",
|
||||||
|
labelDockerComposeService: "myService2",
|
||||||
|
}),
|
||||||
|
|
||||||
|
ports(nat.PortMap{
|
||||||
|
"80/tcp": {},
|
||||||
|
}),
|
||||||
|
|
||||||
|
withNetwork("bridge", ipv4("127.0.0.3")),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
|
"frontend-Host-myService-myProject-docker-localhost-0": {
|
||||||
|
Backend: "backend-myService-myProject",
|
||||||
|
PassHostHeader: true,
|
||||||
|
EntryPoints: []string{},
|
||||||
|
BasicAuth: []string{},
|
||||||
|
Routes: map[string]types.Route{
|
||||||
|
"route-frontend-Host-myService-myProject-docker-localhost-0": {
|
||||||
|
Rule: "Host:myService.myProject.docker.localhost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"frontend-Host-myService2-myProject-docker-localhost-2": {
|
||||||
|
Backend: "backend-myService2-myProject",
|
||||||
|
PassHostHeader: true,
|
||||||
|
EntryPoints: []string{},
|
||||||
|
BasicAuth: []string{},
|
||||||
|
Routes: map[string]types.Route{
|
||||||
|
"route-frontend-Host-myService2-myProject-docker-localhost-2": {
|
||||||
|
Rule: "Host:myService2.myProject.docker.localhost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedBackends: map[string]*types.Backend{
|
||||||
|
"backend-myService-myProject": {
|
||||||
|
Servers: map[string]types.Server{
|
||||||
|
"server-test-0": {
|
||||||
|
URL: "http://127.0.0.1:80",
|
||||||
|
Weight: label.DefaultWeight,
|
||||||
|
}, "server-test-1": {
|
||||||
|
URL: "http://127.0.0.2:80",
|
||||||
|
Weight: label.DefaultWeight,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CircuitBreaker: nil,
|
||||||
|
},
|
||||||
|
"backend-myService2-myProject": {
|
||||||
|
Servers: map[string]types.Server{
|
||||||
|
"server-test-2": {
|
||||||
|
URL: "http://127.0.0.3:80",
|
||||||
|
Weight: label.DefaultWeight,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CircuitBreaker: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
|
|
@ -125,11 +125,14 @@ func (p *Provider) buildConfigurationV1(containersInspected []dockerData) *types
|
||||||
servers := map[string][]dockerData{}
|
servers := map[string][]dockerData{}
|
||||||
serviceNames := make(map[string]struct{})
|
serviceNames := make(map[string]struct{})
|
||||||
for idx, container := range filteredContainers {
|
for idx, container := range filteredContainers {
|
||||||
if _, exists := serviceNames[container.ServiceName]; !exists {
|
|
||||||
|
serviceNamesKey := getServiceNameKey(container, p.SwarmMode, "")
|
||||||
|
|
||||||
|
if _, exists := serviceNames[serviceNamesKey]; !exists {
|
||||||
frontendName := p.getFrontendNameV1(container, idx)
|
frontendName := p.getFrontendNameV1(container, idx)
|
||||||
frontends[frontendName] = append(frontends[frontendName], container)
|
frontends[frontendName] = append(frontends[frontendName], container)
|
||||||
if len(container.ServiceName) > 0 {
|
if len(serviceNamesKey) > 0 {
|
||||||
serviceNames[container.ServiceName] = struct{}{}
|
serviceNames[serviceNamesKey] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
backendName := getBackendNameV1(container)
|
backendName := getBackendNameV1(container)
|
||||||
|
|
Loading…
Reference in a new issue