Adds traefik.frontend. rule and value labels in Docker and Marathon. Fixes #64. Fixes #73

This commit is contained in:
emile 2015-10-23 09:49:19 +02:00
parent 5dea2e7902
commit d671cc3821
6 changed files with 157 additions and 95 deletions

View file

@ -73,18 +73,14 @@ func (provider *DockerProvider) Provide(configurationChan chan<- configMessage)
func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *Configuration { func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *Configuration {
var DockerFuncMap = template.FuncMap{ var DockerFuncMap = template.FuncMap{
"getBackend": func(container docker.Container) string { "getBackend": func(container docker.Container) string {
for key, value := range container.Config.Labels { if label, err := provider.getLabel(container, "traefik.backend"); err == nil {
if key == "traefik.backend" { return label
return value
}
} }
return getHost(container) return provider.getEscapedName(container.Name)
}, },
"getPort": func(container docker.Container) string { "getPort": func(container docker.Container) string {
for key, value := range container.Config.Labels { if label, err := provider.getLabel(container, "traefik.port"); err == nil {
if key == "traefik.port" { return label
return value
}
} }
for key := range container.NetworkSettings.Ports { for key := range container.NetworkSettings.Ports {
return key.Port() return key.Port()
@ -92,30 +88,33 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C
return "" return ""
}, },
"getWeight": func(container docker.Container) string { "getWeight": func(container docker.Container) string {
for key, value := range container.Config.Labels { if label, err := provider.getLabel(container, "traefik.weight"); err == nil {
if key == "traefik.weight" { return label
return value
}
} }
return "0" return "0"
}, },
"getDomain": func(container docker.Container) string { "getDomain": func(container docker.Container) string {
for key, value := range container.Config.Labels { if label, err := provider.getLabel(container, "traefik.domain"); err == nil {
if key == "traefik.domain" { return label
return value
}
} }
return provider.Domain return provider.Domain
}, },
"getProtocole": func(container docker.Container) string {
if label, err := provider.getLabel(container, "traefik.protocole"); err == nil {
return label
}
return "http"
},
"getFrontendValue": provider.GetFrontendValue,
"getFrontendRule": provider.GetFrontendRule,
"replace": func(s1 string, s2 string, s3 string) string { "replace": func(s1 string, s2 string, s3 string) string {
return strings.Replace(s3, s1, s2, -1) return strings.Replace(s3, s1, s2, -1)
}, },
"getHost": getHost,
} }
configuration := new(Configuration) configuration := new(Configuration)
containerList, _ := dockerClient.ListContainers(docker.ListContainersOptions{}) containerList, _ := dockerClient.ListContainers(docker.ListContainersOptions{})
containersInspected := []docker.Container{} containersInspected := []docker.Container{}
hosts := map[string][]docker.Container{} frontends := map[string][]docker.Container{}
// get inspect containers // get inspect containers
for _, container := range containerList { for _, container := range containerList {
@ -138,20 +137,28 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C
log.Debugf("Filtering disabled container %s", container.Name) log.Debugf("Filtering disabled container %s", container.Name)
return false return false
} }
_, errRule := provider.getLabel(container, "traefik.frontend.rule")
_, errValue := provider.getLabel(container, "traefik.frontend.value")
if errRule != nil && errValue == nil || errRule == nil && errValue != nil {
log.Debugf("Filtering bad labeled container %s", container.Name)
return false
}
return true return true
}, containersInspected).([]docker.Container) }, containersInspected).([]docker.Container)
for _, container := range filteredContainers { for _, container := range filteredContainers {
hosts[getHost(container)] = append(hosts[getHost(container)], container) frontends[provider.getFrontendName(container)] = append(frontends[provider.getFrontendName(container)], container)
} }
templateObjects := struct { templateObjects := struct {
Containers []docker.Container Containers []docker.Container
Hosts map[string][]docker.Container Frontends map[string][]docker.Container
Domain string Domain string
}{ }{
filteredContainers, filteredContainers,
hosts, frontends,
provider.Domain, provider.Domain,
} }
tmpl := template.New(provider.Filename).Funcs(DockerFuncMap) tmpl := template.New(provider.Filename).Funcs(DockerFuncMap)
@ -181,17 +188,40 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C
} }
if _, err := toml.Decode(buffer.String(), configuration); err != nil { if _, err := toml.Decode(buffer.String(), configuration); err != nil {
log.Error("Error creating docker configuration", err) log.Error("Error creating docker configuration ", err)
return nil return nil
} }
return configuration return configuration
} }
func getHost(container docker.Container) string { func (provider *DockerProvider) getFrontendName(container docker.Container) string {
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
return strings.Replace(provider.GetFrontendRule(container)+"-"+provider.GetFrontendValue(container), ".", "-", -1)
}
func (provider *DockerProvider) getEscapedName(name string) string {
return strings.Replace(name, "/", "", -1)
}
func (provider *DockerProvider) getLabel(container docker.Container, label string) (string, error) {
for key, value := range container.Config.Labels { for key, value := range container.Config.Labels {
if key == "traefik.host" { if key == label {
return value return value, nil
} }
} }
return strings.Replace(strings.Replace(container.Name, "/", "", -1), ".", "-", -1) return "", errors.New("Label not found:" + label)
}
func (provider *DockerProvider) GetFrontendValue(container docker.Container) string {
if label, err := provider.getLabel(container, "traefik.frontend.value"); err == nil {
return label
}
return provider.getEscapedName(container.Name) + "." + provider.Domain
}
func (provider *DockerProvider) GetFrontendRule(container docker.Container) string {
if label, err := provider.getLabel(container, "traefik.frontend.rule"); err == nil {
return label
}
return "Host"
} }

View file

@ -6,6 +6,7 @@ import (
"strings" "strings"
"text/template" "text/template"
"errors"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/BurntSushi/ty/fun" "github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
@ -62,41 +63,39 @@ func (provider *MarathonProvider) loadMarathonConfig() *Configuration {
} }
return "" return ""
}, },
"getHost": func(application marathon.Application) string { "getWeight": func(task marathon.Task, applications []marathon.Application) string {
for key, value := range application.Labels { application := getApplication(task, applications)
if key == "traefik.host" { if application == nil {
return value log.Errorf("Unable to get marathon application from task %s", task.AppID)
} return "0"
} }
return strings.Replace(application.ID, "/", "", 1) if label, err := provider.getLabel(*application, "traefik.weight"); err == nil {
}, return label
"getWeight": func(application marathon.Application) string {
for key, value := range application.Labels {
if key == "traefik.weight" {
return value
}
} }
return "0" return "0"
}, },
"getDomain": func(application marathon.Application) string { "getDomain": func(application marathon.Application) string {
for key, value := range application.Labels { if label, err := provider.getLabel(application, "traefik.domain"); err == nil {
if key == "traefik.domain" { return label
return value
}
} }
return provider.Domain return provider.Domain
}, },
"getPrefixes": func(application marathon.Application) ([]string, error) {
for key, value := range application.Labels {
if key == "traefik.prefixes" {
return strings.Split(value, ","), nil
}
}
return []string{}, nil
},
"replace": func(s1 string, s2 string, s3 string) string { "replace": func(s1 string, s2 string, s3 string) string {
return strings.Replace(s3, s1, s2, -1) return strings.Replace(s3, s1, s2, -1)
}, },
"getProtocole": func(task marathon.Task, applications []marathon.Application) string {
application := getApplication(task, applications)
if application == nil {
log.Errorf("Unable to get marathon application from task %s", task.AppID)
return "http"
}
if label, err := provider.getLabel(*application, "traefik.protocole"); err == nil {
return label
}
return "http"
},
"getFrontendValue": provider.GetFrontendValue,
"getFrontendRule": provider.GetFrontendRule,
} }
configuration := new(Configuration) configuration := new(Configuration)
@ -202,3 +201,30 @@ func getApplication(task marathon.Task, apps []marathon.Application) *marathon.A
} }
return nil return nil
} }
func (provider *MarathonProvider) getLabel(application marathon.Application, label string) (string, error) {
for key, value := range application.Labels {
if key == label {
return value, nil
}
}
return "", errors.New("Label not found:" + label)
}
func (provider *MarathonProvider) getEscapedName(name string) string {
return strings.Replace(name, "/", "", -1)
}
func (provider *MarathonProvider) GetFrontendValue(application marathon.Application) string {
if label, err := provider.getLabel(application, "traefik.frontend.value"); err == nil {
return label
}
return provider.getEscapedName(application.ID) + "." + provider.Domain
}
func (provider *MarathonProvider) GetFrontendRule(application marathon.Application) string {
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
return label
}
return "Host"
}

View file

@ -1,14 +1,13 @@
[backends]{{range .Containers}} [backends]{{range .Containers}}
[backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}] [backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}]
url = "http://{{.NetworkSettings.IPAddress}}:{{getPort .}}" url = "{{getProtocole .}}://{{.NetworkSettings.IPAddress}}:{{getPort .}}"
weight = {{getWeight .}} weight = {{getWeight .}}
{{end}} {{end}}
[frontends]{{range $host, $containers := .Hosts}} [frontends]{{range $frontend, $containers := .Frontends}}
[frontends.frontend-{{$host}}] [frontends."frontend-{{$frontend}}"]{{$container := index $containers 0}}
{{$container := index $containers 0}}
backend = "backend-{{getBackend $container}}" backend = "backend-{{getBackend $container}}"
[frontends.frontend-{{$host}}.routes.route-host-{{$host}}] [frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
rule = "Host" rule = "{{getFrontendRule $container}}"
value = "{{$host}}.{{getDomain $container}}" value = "{{getFrontendValue $container}}"
{{end}} {{end}}

View file

@ -1,27 +0,0 @@
{{$apps := .Applications}}
[backends]{{range .Tasks}}
[backends.backend{{.AppID | replace "/" "-"}}.servers.server-{{.ID | replace "." "-"}}]
url = "http://{{.Host}}:{{getPort .}}"
{{$appID := .AppID}}
{{range $apps}}
{{if eq $appID .ID}}
weight = {{getWeight .}}
{{end}}
{{end}}
{{end}}
[frontends]{{ range $app := .Applications}}
{{range $prefix := getPrefixes .}}
[frontends.frontend{{$app.ID | replace "/" "-"}}{{$prefix | replace "/" "-"}}]
backend = "backend{{$app.ID | replace "/" "-"}}"
[frontends.frontend-{{getHost $app | replace "/" "-"}}{{$prefix | replace "/" "-"}}.routes.route-prefix{{$prefix | replace "/" "-"}}]
rule = "PathPrefix"
value = "{{.}}"
{{else}}
[frontends.frontend{{.ID | replace "/" "-"}}]
backend = "backend{{.ID | replace "/" "-"}}"
[frontends.frontend-{{getHost $app | replace "/" "-"}}.routes.route-host-{{getHost $app | replace "/" "-"}}]
rule = "Host"
value = "{{getHost $app | replace "/" "-"}}.{{getDomain .}}"
{{end}}
{{end}}

View file

@ -1,19 +1,14 @@
{{$apps := .Applications}} {{$apps := .Applications}}
[backends]{{range .Tasks}} [backends]{{range .Tasks}}
[backends.backend{{.AppID | replace "/" "-"}}.servers.server-{{.ID | replace "." "-"}}] [backends.backend{{.AppID | replace "/" "-"}}.servers.server-{{.ID | replace "." "-"}}]
url = "http://{{.Host}}:{{getPort .}}" url = "{{getProtocole . $apps}}://{{.Host}}:{{getPort .}}"
{{$appID := .AppID}} weight = {{getWeight . $apps}}
{{range $apps}}
{{if eq $appID .ID}}
weight = {{getWeight .}}
{{end}}
{{end}}
{{end}} {{end}}
[frontends]{{range .Applications}} [frontends]{{range .Applications}}
[frontends.frontend{{.ID | replace "/" "-"}}] [frontends.frontend{{.ID | replace "/" "-"}}]
backend = "backend{{.ID | replace "/" "-"}}" backend = "backend{{.ID | replace "/" "-"}}"
[frontends.frontend-{{getHost . | replace "/" "-"}}.routes.route-host-{{getHost . | replace "/" "-"}}] [frontends.frontend-{{.ID | replace "/" ""}}.routes.route-host-{{.ID | replace "/" ""}}]
rule = "Host" rule = "{{getFrontendRule .}}"
value = "{{getHost . | replace "/" "-"}}.{{getDomain .}}" value = "{{getFrontendValue .}}"
{{end}} {{end}}

39
tests/whoami.json Normal file
View file

@ -0,0 +1,39 @@
{
"id": "whoami",
"cpus": 0.1,
"mem": 64.0,
"instances": 3,
"container": {
"type": "DOCKER",
"docker": {
"image": "emilevauge/whoami",
"network": "BRIDGE",
"portMappings": [
{ "containerPort": 80, "hostPort": 0, "protocol": "tcp" }
],
"parameters": [{
"key": "log-driver",
"value": "gelf"
}, {
"key": "log-opt",
"value": "gelf-address=udp://172.17.42.1:12201"
}]
}
},
"healthChecks": [
{
"protocol": "HTTP",
"portIndex": 0,
"path": "/",
"gracePeriodSeconds": 5,
"intervalSeconds": 20,
"maxConsecutiveFailures": 3
}
],
"labels": {
"traefik.weight": "1",
"traefik.protocole": "https",
"traefik.frontend.rule": "Path",
"traefik.frontend.value": "/test"
}
}