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
|
||||
log
|
||||
*.iml
|
||||
./traefik
|
|
@ -12,6 +12,7 @@ type GlobalConfiguration struct {
|
|||
File *FileProvider
|
||||
Web *WebProvider
|
||||
Marathon *MarathonProvider
|
||||
Consul *ConsulProvider
|
||||
}
|
||||
|
||||
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/cenkalti/backoff"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
"github.com/leekchan/gtf"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
@ -73,8 +72,8 @@ func (provider *DockerProvider) Provide(configurationChan chan<- *Configuration)
|
|||
log.Fatalf("Docker connection error %+v", err)
|
||||
}
|
||||
log.Debug("Docker connection established")
|
||||
dockerEvents := make(chan *docker.APIEvents)
|
||||
if provider.Watch {
|
||||
dockerEvents := make(chan *docker.APIEvents)
|
||||
dockerClient.AddEventListener(dockerEvents)
|
||||
log.Debug("Docker listening")
|
||||
go func() {
|
||||
|
@ -152,7 +151,6 @@ func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *C
|
|||
hosts,
|
||||
provider.Domain,
|
||||
}
|
||||
gtf.Inject(DockerFuncMap)
|
||||
tmpl := template.New(provider.Filename).Funcs(DockerFuncMap)
|
||||
if len(provider.Filename) > 0 {
|
||||
_, err := tmpl.ParseFiles(provider.Filename)
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"github.com/BurntSushi/toml"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
"github.com/gambol99/go-marathon"
|
||||
"github.com/leekchan/gtf"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
@ -148,8 +147,7 @@ func (provider *MarathonProvider) loadMarathonConfig() *Configuration {
|
|||
provider.Domain,
|
||||
}
|
||||
|
||||
gtf.Inject(MarathonFuncMap)
|
||||
tmpl := template.New(provider.Filename).Funcs(DockerFuncMap)
|
||||
tmpl := template.New(provider.Filename).Funcs(MarathonFuncMap)
|
||||
if len(provider.Filename) > 0 {
|
||||
_, err := tmpl.ParseFiles(provider.Filename)
|
||||
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)
|
||||
var srv *graceful.Server
|
||||
var configurationRouter *mux.Router
|
||||
var configurationChan = make(chan *Configuration)
|
||||
var configurationChan = make(chan *Configuration, 10)
|
||||
defer close(configurationChan)
|
||||
var providers = []Provider{}
|
||||
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 {
|
||||
providers = append(providers, gloablConfiguration.Web)
|
||||
}
|
||||
// providers = append(providers, NewConsulProvider())
|
||||
if gloablConfiguration.Consul != nil {
|
||||
providers = append(providers, gloablConfiguration.Consul)
|
||||
}
|
||||
|
||||
// start providers
|
||||
for _, provider := range providers {
|
||||
|
|
|
@ -172,6 +172,41 @@
|
|||
# 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
|
||||
|
|
102
traefik.toml
102
traefik.toml
|
@ -41,7 +41,7 @@ port = ":8081"
|
|||
# Optional
|
||||
# Default: "ERROR"
|
||||
#
|
||||
# logLevel = "ERROR"
|
||||
logLevel = "DEBUG"
|
||||
|
||||
# SSL certificate and key used
|
||||
#
|
||||
|
@ -75,7 +75,7 @@ address = ":8082"
|
|||
#
|
||||
# Optional
|
||||
#
|
||||
# [file]
|
||||
[file]
|
||||
|
||||
# Rules file
|
||||
# If defined, traefik will load rules from this file,
|
||||
|
@ -89,7 +89,7 @@ address = ":8082"
|
|||
#
|
||||
# Optional
|
||||
#
|
||||
# watch = true
|
||||
watch = true
|
||||
|
||||
|
||||
################################################################
|
||||
|
@ -100,26 +100,26 @@ address = ":8082"
|
|||
#
|
||||
# Optional
|
||||
#
|
||||
[docker]
|
||||
# [docker]
|
||||
|
||||
# Docker server endpoint. Can be a tcp or a unix socket endpoint.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
endpoint = "unix:///var/run/docker.sock"
|
||||
# endpoint = "unix:///var/run/docker.sock"
|
||||
|
||||
# Enable watch docker changes
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
watch = true
|
||||
# watch = true
|
||||
|
||||
# Default domain used.
|
||||
# Can be overridden by setting the "traefik.domain" label on a container.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
domain = "docker.localhost"
|
||||
# domain = "docker.localhost"
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
#
|
||||
|
@ -172,34 +172,68 @@ domain = "docker.localhost"
|
|||
# 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
|
||||
################################################################
|
||||
# [backends]
|
||||
# [backends.backend1]
|
||||
# [backends.backend1.servers.server1]
|
||||
# url = "http://172.17.0.2:80"
|
||||
# weight = 10
|
||||
# [backends.backend1.servers.server2]
|
||||
# url = "http://172.17.0.3:80"
|
||||
# weight = 1
|
||||
# [backends.backend2]
|
||||
# [backends.backend2.servers.server1]
|
||||
# url = "http://172.17.0.4:80"
|
||||
# weight = 1
|
||||
# [backends.backend2.servers.server2]
|
||||
# url = "http://172.17.0.5:80"
|
||||
# weight = 2
|
||||
#
|
||||
# [frontends]
|
||||
# [frontends.frontend1]
|
||||
# backend = "backend2"
|
||||
# [frontends.frontend1.routes.test_1]
|
||||
# category = "Host"
|
||||
# value = "test.localhost"
|
||||
# [frontends.frontend2]
|
||||
# backend = "backend1"
|
||||
# [frontends.frontend2.routes.test_2]
|
||||
# category = "Path"
|
||||
# value = "/test"
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://172.17.0.2:80"
|
||||
weight = 10
|
||||
[backends.backend1.servers.server2]
|
||||
url = "http://172.17.0.3:80"
|
||||
weight = 1
|
||||
[backends.backend2]
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://172.17.0.83:80"
|
||||
weight = 3
|
||||
[backends.backend2.servers.server2]
|
||||
url = "http://172.17.0.5:80"
|
||||
weight = 10
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend2"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host"
|
||||
value = "test.localhost"
|
||||
[frontends.frontend2]
|
||||
backend = "backend1"
|
||||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Path"
|
||||
value = "/test"
|
||||
|
|
Loading…
Reference in a new issue