Extend marathon port discovery to allow port names as identifier
This commit is contained in:
parent
7900d266b1
commit
ec0075e0d0
4 changed files with 95 additions and 11 deletions
|
@ -14,13 +14,21 @@ Traefik tries to detect the configured mode and route traffic to the right IP ad
|
||||||
|
|
||||||
## Port detection
|
## Port detection
|
||||||
|
|
||||||
Traefik also attempts to determine the right port (which is a [non-trivial matter in Marathon](https://mesosphere.github.io/marathon/docs/ports.html)).
|
Traefik also attempts to determine the right port (which is a [non-trivial matter in Marathon](https://mesosphere.github.io/marathon/docs/ports.html)) from the following sources:
|
||||||
Following is the order by which Traefik tries to identify the port (the first one that yields a positive result will be used):
|
|
||||||
|
|
||||||
1. A arbitrary port specified through the `traefik.http.services.serviceName.loadbalancer.server.port=8080`
|
1. An arbitrary port specified through label `traefik.http.services.serviceName.loadbalancer.server.port=8080`
|
||||||
1. The task port (possibly indexed through the `traefik.http.services.serviceName.loadbalancer.server.port=index:0` label, otherwise the first one).
|
1. The task port.
|
||||||
1. The port from the application's `portDefinitions` field (possibly indexed through the `traefik.http.services.serviceName.loadbalancer.server.port=index:0` label, otherwise the first one).
|
1. The port from the application's `portDefinitions` field.
|
||||||
1. The port from the application's `ipAddressPerTask` field (possibly indexed through the `traefik.http.services.serviceName.loadbalancer.server.port=index:0` label, otherwise the first one).
|
1. The port from the application's `ipAddressPerTask` field.
|
||||||
|
|
||||||
|
### Port label syntax
|
||||||
|
|
||||||
|
To select a port, you can either
|
||||||
|
|
||||||
|
- specify the port directly: `traefik.http.services.serviceName.loadbalancer.server.port=8080`
|
||||||
|
- specify a port index: `traefik.http.services.serviceName.loadbalancer.server.port=index:0`
|
||||||
|
- specify a port name: `traefik.http.services.serviceName.loadbalancer.server.port=name:http`
|
||||||
|
- otherwise the first one is selected.
|
||||||
|
|
||||||
## Achieving high availability
|
## Achieving high availability
|
||||||
|
|
||||||
|
|
|
@ -53,10 +53,11 @@ func constraint(value string) func(*marathon.Application) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func portDefinition(port int) func(*marathon.Application) {
|
func portDefinition(port int, name string) func(*marathon.Application) {
|
||||||
return func(app *marathon.Application) {
|
return func(app *marathon.Application) {
|
||||||
app.AddPortDefinition(marathon.PortDefinition{
|
app.AddPortDefinition(marathon.PortDefinition{
|
||||||
Port: &port,
|
Port: &port,
|
||||||
|
Name: name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -373,7 +373,7 @@ func getPort(task marathon.Task, app marathon.Application, serverPort string) (s
|
||||||
// one of the available port. The first such found port is returned unless an
|
// one of the available port. The first such found port is returned unless an
|
||||||
// optional index is provided.
|
// optional index is provided.
|
||||||
func processPorts(app marathon.Application, task marathon.Task, serverPort string) (int, error) {
|
func processPorts(app marathon.Application, task marathon.Task, serverPort string) (int, error) {
|
||||||
if len(serverPort) > 0 && !strings.HasPrefix(serverPort, "index:") {
|
if len(serverPort) > 0 && !(strings.HasPrefix(serverPort, "index:") || strings.HasPrefix(serverPort, "name:")) {
|
||||||
port, err := strconv.Atoi(serverPort)
|
port, err := strconv.Atoi(serverPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -386,6 +386,17 @@ func processPorts(app marathon.Application, task marathon.Task, serverPort strin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(serverPort, "name:") {
|
||||||
|
name := strings.TrimPrefix(serverPort, "name:")
|
||||||
|
port := retrieveNamedPort(app, name)
|
||||||
|
|
||||||
|
if port == 0 {
|
||||||
|
return 0, fmt.Errorf("no port with name %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return port, nil
|
||||||
|
}
|
||||||
|
|
||||||
ports := retrieveAvailablePorts(app, task)
|
ports := retrieveAvailablePorts(app, task)
|
||||||
if len(ports) == 0 {
|
if len(ports) == 0 {
|
||||||
return 0, errors.New("no port found")
|
return 0, errors.New("no port found")
|
||||||
|
@ -393,8 +404,8 @@ func processPorts(app marathon.Application, task marathon.Task, serverPort strin
|
||||||
|
|
||||||
portIndex := 0
|
portIndex := 0
|
||||||
if strings.HasPrefix(serverPort, "index:") {
|
if strings.HasPrefix(serverPort, "index:") {
|
||||||
split := strings.SplitN(serverPort, ":", 2)
|
indexString := strings.TrimPrefix(serverPort, "index:")
|
||||||
index, err := strconv.Atoi(split[1])
|
index, err := strconv.Atoi(indexString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -402,11 +413,35 @@ func processPorts(app marathon.Application, task marathon.Task, serverPort strin
|
||||||
if index < 0 || index > len(ports)-1 {
|
if index < 0 || index > len(ports)-1 {
|
||||||
return 0, fmt.Errorf("index %d must be within range (0, %d)", index, len(ports)-1)
|
return 0, fmt.Errorf("index %d must be within range (0, %d)", index, len(ports)-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
portIndex = index
|
portIndex = index
|
||||||
}
|
}
|
||||||
|
|
||||||
return ports[portIndex], nil
|
return ports[portIndex], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func retrieveNamedPort(app marathon.Application, name string) int {
|
||||||
|
// Using port definition if available
|
||||||
|
if app.PortDefinitions != nil && len(*app.PortDefinitions) > 0 {
|
||||||
|
for _, def := range *app.PortDefinitions {
|
||||||
|
if def.Port != nil && *def.Port > 0 && def.Name == name {
|
||||||
|
return *def.Port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If using IP-per-task using this port definition
|
||||||
|
if app.IPAddressPerTask != nil && app.IPAddressPerTask.Discovery != nil && len(*(app.IPAddressPerTask.Discovery.Ports)) > 0 {
|
||||||
|
for _, def := range *(app.IPAddressPerTask.Discovery.Ports) {
|
||||||
|
if def.Number > 0 && def.Name == name {
|
||||||
|
return def.Number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func retrieveAvailablePorts(app marathon.Application, task marathon.Task) []int {
|
func retrieveAvailablePorts(app marathon.Application, task marathon.Task) []int {
|
||||||
// Using default port configuration
|
// Using default port configuration
|
||||||
if len(task.Ports) > 0 {
|
if len(task.Ports) > 0 {
|
||||||
|
|
|
@ -1941,13 +1941,53 @@ func TestGetServer(t *testing.T) {
|
||||||
error: `unable to process ports for /app taskID: strconv.Atoi: parsing "aaa": invalid syntax`,
|
error: `unable to process ports for /app taskID: strconv.Atoi: parsing "aaa": invalid syntax`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "with port name",
|
||||||
|
provider: Provider{},
|
||||||
|
app: application(
|
||||||
|
appID("/app"),
|
||||||
|
portDefinition(80, "fist-port"),
|
||||||
|
portDefinition(81, "second-port"),
|
||||||
|
portDefinition(82, "third-port"),
|
||||||
|
withTasks(localhostTask()),
|
||||||
|
),
|
||||||
|
extraConf: configuration{},
|
||||||
|
defaultServer: dynamic.Server{
|
||||||
|
Scheme: "http",
|
||||||
|
Port: "name:third-port",
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
server: dynamic.Server{
|
||||||
|
URL: "http://localhost:82",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "with port name not found",
|
||||||
|
provider: Provider{},
|
||||||
|
app: application(
|
||||||
|
appID("/app"),
|
||||||
|
portDefinition(80, "fist-port"),
|
||||||
|
portDefinition(81, "second-port"),
|
||||||
|
portDefinition(82, "third-port"),
|
||||||
|
withTasks(localhostTask()),
|
||||||
|
),
|
||||||
|
extraConf: configuration{},
|
||||||
|
defaultServer: dynamic.Server{
|
||||||
|
Scheme: "http",
|
||||||
|
Port: "name:other-name",
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
error: `unable to process ports for /app taskID: no port with name other-name`,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "with application port and no task port",
|
desc: "with application port and no task port",
|
||||||
provider: Provider{},
|
provider: Provider{},
|
||||||
app: application(
|
app: application(
|
||||||
appID("/app"),
|
appID("/app"),
|
||||||
appPorts(80),
|
appPorts(80),
|
||||||
portDefinition(80),
|
portDefinition(80, "http"),
|
||||||
withTasks(localhostTask()),
|
withTasks(localhostTask()),
|
||||||
),
|
),
|
||||||
extraConf: configuration{},
|
extraConf: configuration{},
|
||||||
|
|
Loading…
Reference in a new issue