Consul Backend
This commit is contained in:
parent
32c0ffe87a
commit
d8e8815ad1
11 changed files with 354 additions and 43 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,3 +3,4 @@ gen.go
|
||||||
.idea
|
.idea
|
||||||
log
|
log
|
||||||
*.iml
|
*.iml
|
||||||
|
./traefik
|
|
@ -12,6 +12,7 @@ type GlobalConfiguration struct {
|
||||||
File *FileProvider
|
File *FileProvider
|
||||||
Web *WebProvider
|
Web *WebProvider
|
||||||
Marathon *MarathonProvider
|
Marathon *MarathonProvider
|
||||||
|
Consul *ConsulProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGlobalConfiguration() *GlobalConfiguration {
|
func NewGlobalConfiguration() *GlobalConfiguration {
|
||||||
|
|
155
consul.go
Normal file
155
consul.go
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/consul/api"
|
||||||
|
"text/template"
|
||||||
|
"bytes"
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
"strings"
|
||||||
|
"github.com/BurntSushi/ty/fun"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type Key struct {
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConsulProvider struct {
|
||||||
|
Watch bool
|
||||||
|
Endpoint string
|
||||||
|
Prefix string
|
||||||
|
Filename string
|
||||||
|
consulClient *api.Client
|
||||||
|
}
|
||||||
|
var kvClient *api.KV
|
||||||
|
|
||||||
|
var ConsulFuncMap = template.FuncMap{
|
||||||
|
"List": func(keys ...string) []string {
|
||||||
|
joinedKeys := strings.Join(keys, "")
|
||||||
|
keysPairs, _, err := kvClient.Keys(joinedKeys, "/", nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error getting keys ", joinedKeys, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
keysPairs = fun.Filter(func(key string) bool {
|
||||||
|
if (key == joinedKeys) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, keysPairs).([]string)
|
||||||
|
return keysPairs
|
||||||
|
},
|
||||||
|
"Get": func(keys ...string) string {
|
||||||
|
joinedKeys := strings.Join(keys, "")
|
||||||
|
keyPair, _, err := kvClient.Get(joinedKeys, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error getting key ", joinedKeys, err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(keyPair.Value)
|
||||||
|
},
|
||||||
|
"Last": func(key string) string {
|
||||||
|
splittedKey := strings.Split(key, "/")
|
||||||
|
return splittedKey[len(splittedKey) -2]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConsulProvider() *ConsulProvider {
|
||||||
|
consulProvider := new(ConsulProvider)
|
||||||
|
// default values
|
||||||
|
consulProvider.Watch = true
|
||||||
|
consulProvider.Prefix = "traefik"
|
||||||
|
|
||||||
|
return consulProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *ConsulProvider) Provide(configurationChan chan <- *Configuration) {
|
||||||
|
config := &api.Config{
|
||||||
|
Address: provider.Endpoint,
|
||||||
|
Scheme: "http",
|
||||||
|
HttpClient: http.DefaultClient,
|
||||||
|
}
|
||||||
|
consulClient, _ := api.NewClient(config)
|
||||||
|
provider.consulClient = consulClient
|
||||||
|
if provider.Watch {
|
||||||
|
var waitIndex uint64
|
||||||
|
keypairs, meta, err := consulClient.KV().Keys("", "", nil)
|
||||||
|
if keypairs == nil && err == nil {
|
||||||
|
log.Error("Key was not found.")
|
||||||
|
}
|
||||||
|
waitIndex = meta.LastIndex
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
opts := api.QueryOptions{
|
||||||
|
WaitIndex: waitIndex,
|
||||||
|
}
|
||||||
|
keypairs, meta, err := consulClient.KV().Keys("", "", &opts)
|
||||||
|
if keypairs == nil && err == nil {
|
||||||
|
log.Error("Key was not found.")
|
||||||
|
}
|
||||||
|
waitIndex = meta.LastIndex
|
||||||
|
configuration := provider.loadConsulConfig()
|
||||||
|
if configuration != nil {
|
||||||
|
configurationChan <- configuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
configuration := provider.loadConsulConfig()
|
||||||
|
configurationChan <- configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *ConsulProvider) loadConsulConfig() *Configuration {
|
||||||
|
configuration := new(Configuration)
|
||||||
|
services := []*api.CatalogService{}
|
||||||
|
kvClient = provider.consulClient.KV()
|
||||||
|
|
||||||
|
servicesName, _, _ := provider.consulClient.Catalog().Services(nil)
|
||||||
|
for serviceName, _ := range servicesName {
|
||||||
|
catalogServices, _, _ := provider.consulClient.Catalog().Service(serviceName, "", nil)
|
||||||
|
for _, catalogService := range catalogServices {
|
||||||
|
services= append(services, catalogService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templateObjects := struct {
|
||||||
|
Services []*api.CatalogService
|
||||||
|
}{
|
||||||
|
services,
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl := template.New(provider.Filename).Funcs(ConsulFuncMap)
|
||||||
|
if len(provider.Filename) > 0 {
|
||||||
|
_, err := tmpl.ParseFiles(provider.Filename)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error reading file", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf, err := Asset("providerTemplates/consul.tmpl")
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error reading file", err)
|
||||||
|
}
|
||||||
|
_, err = tmpl.Parse(string(buf))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error reading file", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
err := tmpl.Execute(&buffer, templateObjects)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error with consul template:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
|
||||||
|
log.Error("Error creating consul configuration:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return configuration
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
"github.com/cenkalti/backoff"
|
"github.com/cenkalti/backoff"
|
||||||
"github.com/fsouza/go-dockerclient"
|
"github.com/fsouza/go-dockerclient"
|
||||||
"github.com/leekchan/gtf"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
@ -73,8 +72,8 @@ func (provider *DockerProvider) Provide(configurationChan chan<- *Configuration)
|
||||||
log.Fatalf("Docker connection error %+v", err)
|
log.Fatalf("Docker connection error %+v", err)
|
||||||
}
|
}
|
||||||
log.Debug("Docker connection established")
|
log.Debug("Docker connection established")
|
||||||
dockerEvents := make(chan *docker.APIEvents)
|
|
||||||
if provider.Watch {
|
if provider.Watch {
|
||||||
|
dockerEvents := make(chan *docker.APIEvents)
|
||||||
dockerClient.AddEventListener(dockerEvents)
|
dockerClient.AddEventListener(dockerEvents)
|
||||||
log.Debug("Docker listening")
|
log.Debug("Docker listening")
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -152,7 +151,6 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C
|
||||||
hosts,
|
hosts,
|
||||||
provider.Domain,
|
provider.Domain,
|
||||||
}
|
}
|
||||||
gtf.Inject(DockerFuncMap)
|
|
||||||
tmpl := template.New(provider.Filename).Funcs(DockerFuncMap)
|
tmpl := template.New(provider.Filename).Funcs(DockerFuncMap)
|
||||||
if len(provider.Filename) > 0 {
|
if len(provider.Filename) > 0 {
|
||||||
_, err := tmpl.ParseFiles(provider.Filename)
|
_, err := tmpl.ParseFiles(provider.Filename)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
"github.com/gambol99/go-marathon"
|
"github.com/gambol99/go-marathon"
|
||||||
"github.com/leekchan/gtf"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
@ -148,8 +147,7 @@ func (provider *MarathonProvider) loadMarathonConfig() *Configuration {
|
||||||
provider.Domain,
|
provider.Domain,
|
||||||
}
|
}
|
||||||
|
|
||||||
gtf.Inject(MarathonFuncMap)
|
tmpl := template.New(provider.Filename).Funcs(MarathonFuncMap)
|
||||||
tmpl := template.New(provider.Filename).Funcs(DockerFuncMap)
|
|
||||||
if len(provider.Filename) > 0 {
|
if len(provider.Filename) > 0 {
|
||||||
_, err := tmpl.ParseFiles(provider.Filename)
|
_, err := tmpl.ParseFiles(provider.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
24
providerTemplates/consul.tmpl
Normal file
24
providerTemplates/consul.tmpl
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{{$frontends := "frontends/" | List }}
|
||||||
|
{{$backends := "backends/" | List }}
|
||||||
|
|
||||||
|
{{range $backends}}
|
||||||
|
{{$backend := .}}
|
||||||
|
{{$servers := "servers/" | List $backend }}
|
||||||
|
{{range $servers}}
|
||||||
|
[backends.{{Last $backend}}.servers.{{Last .}}]
|
||||||
|
url = "{{Get . "/url"}}"
|
||||||
|
weight = {{Get . "/weight"}}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
[frontends]{{range $frontends}}
|
||||||
|
{{$frontend := Last .}}
|
||||||
|
[frontends.{{$frontend}}]
|
||||||
|
backend = "{{Get . "/backend"}}"
|
||||||
|
{{$routes := "routes/" | List .}}
|
||||||
|
{{range $routes}}
|
||||||
|
[frontends.{{$frontend}}.routes.{{Last .}}]
|
||||||
|
rule = "{{Get . "/rule"}}"
|
||||||
|
value = "{{Get . "/value"}}"
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
21
tests/compose-consul.yml
Normal file
21
tests/compose-consul.yml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
consul:
|
||||||
|
image: progrium/consul
|
||||||
|
command: -server -bootstrap -advertise 12.0.0.254 -log-level debug -ui-dir /ui
|
||||||
|
ports:
|
||||||
|
- "8400:8400"
|
||||||
|
- "8500:8500"
|
||||||
|
- "8600:53/udp"
|
||||||
|
expose:
|
||||||
|
- "8300"
|
||||||
|
- "8301"
|
||||||
|
- "8301/udp"
|
||||||
|
- "8302"
|
||||||
|
- "8302/udp"
|
||||||
|
|
||||||
|
registrator:
|
||||||
|
image: gliderlabs/registrator:master
|
||||||
|
command: -internal consulkv://consul:8500/traefik
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/tmp/docker.sock
|
||||||
|
links:
|
||||||
|
- consul
|
42
tests/compose-marathon.yml
Normal file
42
tests/compose-marathon.yml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
zk:
|
||||||
|
image: bobrik/zookeeper
|
||||||
|
net: host
|
||||||
|
environment:
|
||||||
|
ZK_CONFIG: tickTime=2000,initLimit=10,syncLimit=5,maxClientCnxns=128,forceSync=no,clientPort=2181
|
||||||
|
ZK_ID: 1
|
||||||
|
|
||||||
|
master:
|
||||||
|
image: mesosphere/mesos-master:0.23.0-1.0.ubuntu1404
|
||||||
|
net: host
|
||||||
|
environment:
|
||||||
|
MESOS_ZK: zk://127.0.0.1:2181/mesos
|
||||||
|
MESOS_HOSTNAME: 127.0.0.1
|
||||||
|
MESOS_IP: 127.0.0.1
|
||||||
|
MESOS_QUORUM: 1
|
||||||
|
MESOS_CLUSTER: docker-compose
|
||||||
|
MESOS_WORK_DIR: /var/lib/mesos
|
||||||
|
|
||||||
|
slave:
|
||||||
|
image: mesosphere/mesos-slave:0.23.0-1.0.ubuntu1404
|
||||||
|
net: host
|
||||||
|
pid: host
|
||||||
|
privileged: true
|
||||||
|
environment:
|
||||||
|
MESOS_MASTER: zk://127.0.0.1:2181/mesos
|
||||||
|
MESOS_HOSTNAME: 127.0.0.1
|
||||||
|
MESOS_IP: 127.0.0.1
|
||||||
|
MESOS_CONTAINERIZERS: docker,mesos
|
||||||
|
volumes:
|
||||||
|
- /sys/fs/cgroup:/sys/fs/cgroup
|
||||||
|
- /usr/bin/docker:/usr/bin/docker:ro
|
||||||
|
- /usr/lib/x86_64-linux-gnu/libapparmor.so.1:/usr/lib/x86_64-linux-gnu/libapparmor.so.1:ro
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
|
||||||
|
marathon:
|
||||||
|
image: mesosphere/marathon:v0.9.2
|
||||||
|
net: host
|
||||||
|
environment:
|
||||||
|
MARATHON_MASTER: zk://127.0.0.1:2181/mesos
|
||||||
|
MARATHON_ZK: zk://127.0.0.1:2181/marathon
|
||||||
|
MARATHON_HOSTNAME: 127.0.0.1
|
||||||
|
command: --event_subscriber http_callback
|
|
@ -41,7 +41,7 @@ func main() {
|
||||||
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
|
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
|
||||||
var srv *graceful.Server
|
var srv *graceful.Server
|
||||||
var configurationRouter *mux.Router
|
var configurationRouter *mux.Router
|
||||||
var configurationChan = make(chan *Configuration)
|
var configurationChan = make(chan *Configuration, 10)
|
||||||
defer close(configurationChan)
|
defer close(configurationChan)
|
||||||
var providers = []Provider{}
|
var providers = []Provider{}
|
||||||
var format = logging.MustStringFormatter("%{color}%{time:15:04:05.000} %{shortfile:20.20s} %{level:8.8s} %{id:03x} ▶%{color:reset} %{message}")
|
var format = logging.MustStringFormatter("%{color}%{time:15:04:05.000} %{shortfile:20.20s} %{level:8.8s} %{id:03x} ▶%{color:reset} %{message}")
|
||||||
|
@ -122,7 +122,9 @@ func main() {
|
||||||
if gloablConfiguration.Web != nil {
|
if gloablConfiguration.Web != nil {
|
||||||
providers = append(providers, gloablConfiguration.Web)
|
providers = append(providers, gloablConfiguration.Web)
|
||||||
}
|
}
|
||||||
// providers = append(providers, NewConsulProvider())
|
if gloablConfiguration.Consul != nil {
|
||||||
|
providers = append(providers, gloablConfiguration.Consul)
|
||||||
|
}
|
||||||
|
|
||||||
// start providers
|
// start providers
|
||||||
for _, provider := range providers {
|
for _, provider := range providers {
|
||||||
|
|
|
@ -172,6 +172,41 @@
|
||||||
# filename = "marathon.tmpl"
|
# filename = "marathon.tmpl"
|
||||||
|
|
||||||
|
|
||||||
|
################################################################
|
||||||
|
# Consul KV configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable Consul KV configuration backend
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# [consul]
|
||||||
|
|
||||||
|
# Consul server endpoint
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
#
|
||||||
|
# endpoint = "http://127.0.0.1:8500"
|
||||||
|
|
||||||
|
# Enable watch Consul changes
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# watch = true
|
||||||
|
|
||||||
|
# Prefix used for KV store.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# prefix = "traefik"
|
||||||
|
|
||||||
|
# Override default configuration template. For advanced users :)
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# filename = "consul.tmpl"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
# Sample rules
|
# Sample rules
|
||||||
|
|
102
traefik.toml
102
traefik.toml
|
@ -41,7 +41,7 @@ port = ":8081"
|
||||||
# Optional
|
# Optional
|
||||||
# Default: "ERROR"
|
# Default: "ERROR"
|
||||||
#
|
#
|
||||||
# logLevel = "ERROR"
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
# SSL certificate and key used
|
# SSL certificate and key used
|
||||||
#
|
#
|
||||||
|
@ -75,7 +75,7 @@ address = ":8082"
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
#
|
#
|
||||||
# [file]
|
[file]
|
||||||
|
|
||||||
# Rules file
|
# Rules file
|
||||||
# If defined, traefik will load rules from this file,
|
# If defined, traefik will load rules from this file,
|
||||||
|
@ -89,7 +89,7 @@ address = ":8082"
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
#
|
#
|
||||||
# watch = true
|
watch = true
|
||||||
|
|
||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
|
@ -100,26 +100,26 @@ address = ":8082"
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
#
|
#
|
||||||
[docker]
|
# [docker]
|
||||||
|
|
||||||
# Docker server endpoint. Can be a tcp or a unix socket endpoint.
|
# Docker server endpoint. Can be a tcp or a unix socket endpoint.
|
||||||
#
|
#
|
||||||
# Required
|
# Required
|
||||||
#
|
#
|
||||||
endpoint = "unix:///var/run/docker.sock"
|
# endpoint = "unix:///var/run/docker.sock"
|
||||||
|
|
||||||
# Enable watch docker changes
|
# Enable watch docker changes
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
#
|
#
|
||||||
watch = true
|
# watch = true
|
||||||
|
|
||||||
# Default domain used.
|
# Default domain used.
|
||||||
# Can be overridden by setting the "traefik.domain" label on a container.
|
# Can be overridden by setting the "traefik.domain" label on a container.
|
||||||
#
|
#
|
||||||
# Required
|
# Required
|
||||||
#
|
#
|
||||||
domain = "docker.localhost"
|
# domain = "docker.localhost"
|
||||||
|
|
||||||
# Override default configuration template. For advanced users :)
|
# Override default configuration template. For advanced users :)
|
||||||
#
|
#
|
||||||
|
@ -172,34 +172,68 @@ domain = "docker.localhost"
|
||||||
# filename = "marathon.tmpl"
|
# filename = "marathon.tmpl"
|
||||||
|
|
||||||
|
|
||||||
|
################################################################
|
||||||
|
# Consul KV configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable Consul KV configuration backend
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# [consul]
|
||||||
|
|
||||||
|
# Consul server endpoint
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
#
|
||||||
|
# endpoint = "http://127.0.0.1:8500"
|
||||||
|
|
||||||
|
# Enable watch Consul changes
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# watch = true
|
||||||
|
|
||||||
|
# Prefix used for KV store.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# prefix = "traefik"
|
||||||
|
|
||||||
|
# Override default configuration template. For advanced users :)
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# filename = "consul.tmpl"
|
||||||
|
|
||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
# Sample rules
|
# Sample rules
|
||||||
################################################################
|
################################################################
|
||||||
# [backends]
|
[backends]
|
||||||
# [backends.backend1]
|
[backends.backend1]
|
||||||
# [backends.backend1.servers.server1]
|
[backends.backend1.servers.server1]
|
||||||
# url = "http://172.17.0.2:80"
|
url = "http://172.17.0.2:80"
|
||||||
# weight = 10
|
weight = 10
|
||||||
# [backends.backend1.servers.server2]
|
[backends.backend1.servers.server2]
|
||||||
# url = "http://172.17.0.3:80"
|
url = "http://172.17.0.3:80"
|
||||||
# weight = 1
|
weight = 1
|
||||||
# [backends.backend2]
|
[backends.backend2]
|
||||||
# [backends.backend2.servers.server1]
|
[backends.backend2.servers.server1]
|
||||||
# url = "http://172.17.0.4:80"
|
url = "http://172.17.0.83:80"
|
||||||
# weight = 1
|
weight = 3
|
||||||
# [backends.backend2.servers.server2]
|
[backends.backend2.servers.server2]
|
||||||
# url = "http://172.17.0.5:80"
|
url = "http://172.17.0.5:80"
|
||||||
# weight = 2
|
weight = 10
|
||||||
#
|
|
||||||
# [frontends]
|
[frontends]
|
||||||
# [frontends.frontend1]
|
[frontends.frontend1]
|
||||||
# backend = "backend2"
|
backend = "backend2"
|
||||||
# [frontends.frontend1.routes.test_1]
|
[frontends.frontend1.routes.test_1]
|
||||||
# category = "Host"
|
rule = "Host"
|
||||||
# value = "test.localhost"
|
value = "test.localhost"
|
||||||
# [frontends.frontend2]
|
[frontends.frontend2]
|
||||||
# backend = "backend1"
|
backend = "backend1"
|
||||||
# [frontends.frontend2.routes.test_2]
|
[frontends.frontend2.routes.test_2]
|
||||||
# category = "Path"
|
rule = "Path"
|
||||||
# value = "/test"
|
value = "/test"
|
||||||
|
|
Loading…
Reference in a new issue