Consul Backend

This commit is contained in:
emile 2015-09-21 18:05:56 +02:00
parent 32c0ffe87a
commit d8e8815ad1
11 changed files with 354 additions and 43 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ gen.go
.idea .idea
log log
*.iml *.iml
./traefik

View file

@ -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
View 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
}

View file

@ -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)

View file

@ -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 {

View 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
View 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

View 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

View file

@ -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 {

View file

@ -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

View file

@ -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"