Compare commits

...

35 Commits

Author SHA1 Message Date
Emile Vauge
9830086790 Merge pull request #242 from containous/fix-old-docker-repo
Fix push to old docker repo emilevauge/traefik
2016-03-08 17:24:26 +01:00
Emile Vauge
8393746e02 Fix benchmarks 2016-03-08 16:27:12 +01:00
Emile Vauge
2314ad9bf9 Fix push to old docker repo emilevauge/traefik 2016-03-08 13:16:58 +01:00
Vincent Demeester
3af21612b6 Merge pull request #239 from goguardian/kv-watch-tree
Support libkv.WatchTree chan errors:
2016-03-05 21:58:48 +01:00
Advait Shinde
7674a82801 Fatalf for timeout cases. 2016-03-05 20:43:44 +00:00
Advait Shinde
d63d2a8a26 Support libkv.WatchTree chan errors:
- libkv.WatchTree returns a channel whose messages represent changes
    to the watched tree. In situations where libkv cannot read from the
    underlying store, libkv will close the provided channel.
  - This PR handles this edge case and fixes #238.
2016-03-05 20:38:33 +00:00
Vincent Demeester
a458018aa2 Merge pull request #240 from containous/update-benchmarks
update benchmarks with haproxy and latest results
2016-03-05 18:39:00 +01:00
Emile Vauge
33cde6aacd update benchmarks with haproxy and latest results
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-05 13:34:18 +01:00
Vincent Demeester
4ded2682d2 Merge pull request #235 from containous/refactor-hot-reload
Refactor hot reload
2016-03-04 16:41:45 +01:00
Emile Vauge
4042938556 add handler switcher instead of Manners
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-04 15:55:28 +01:00
Vincent Demeester
0e683cc535 Merge pull request #234 from thermeon/feature/quick_typo
Fix typo Unkown -> unknown in error
2016-03-04 09:01:49 +01:00
Gareth Kirwan
4923da7f4d Fix typo Unkown -> unknown in error 2016-03-03 20:29:52 +00:00
Vincent Demeester
11781087ca Merge pull request #230 from tayzlor/marathon-event-stream
Use event stream API instead of event subscriptions
2016-03-02 15:24:43 +01:00
Graham Taylor
3063251d43 Use event stream API instead of event subscriptions 2016-03-02 09:22:14 +00:00
Vincent Demeester
b42b170ad2 Merge pull request #227 from containous/fix-docker-network
Fix docker network
2016-03-01 13:12:08 +01:00
Emile Vauge
defbb44b35 Fix docker network
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-01 11:52:30 +00:00
Emile Vauge
a00eb81f03 Merge pull request #228 from ProPheT777/patch-1
My $0.02 - Center gopher
2016-02-29 19:35:47 +01:00
Johann Saunier
a63d989a35 My $0.02 - Center gopher 2016-02-29 18:27:09 +01:00
Vincent Demeester
6c3c5578c6 Merge pull request #225 from containous/add-path-prefix
Add PathPrefixStrip and PathStrip rules
2016-02-26 16:52:12 +01:00
Emile Vauge
122783e36b Add PathPrefixStrip and PathStrip rules
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-02-26 15:58:55 +01:00
Emile Vauge
b84b95fe97 Merge pull request #223 from goguardian/kv-multi
Support multiple endpoints for KV stores:
2016-02-26 11:41:18 +01:00
Advait Shinde
a99010b8c2 Create an integration test for Etcd:
- Integration test specifically spins up an Etcd cluster with three
    nodes.
2016-02-25 23:34:51 +00:00
Advait Shinde
8954aa7118 Update docs to mention commas. 2016-02-25 23:34:51 +00:00
Advait Shinde
3cf848958f Support multiple endpoints for KV stores:
- Fixes #222
2016-02-25 23:34:51 +00:00
Vincent Demeester
1a5668377c Merge pull request #209 from tboerger/feature/tls-auth
Integrated TLS auth for etcd and consul
2016-02-25 10:58:23 +01:00
Thomas Boerger
dc10c56b35 Integrated TLS auth for etcd and consul 2016-02-24 23:32:34 +01:00
Vincent Demeester
331cd173ce Merge pull request #220 from containous/transfer-repo-to-containous-org
Transfer emilevauge/traefik to containous/traefik
2016-02-24 22:22:57 +01:00
Emile Vauge
1881d5eeed Transfer emilevauge/traefik to containous/traefik
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-02-24 17:38:36 +01:00
Vincent Demeester
e0872b6157 Merge pull request #219 from emilevauge/add-traefik-library-image
Add publish to traefik-library-image
2016-02-24 16:11:15 +01:00
Emile Vauge
63fb9c7135 publish binary to traefik-library-image repo
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-02-24 15:48:03 +01:00
Vincent Demeester
9964654495 Merge pull request #183 from keis/consul-catalog
WIP consul catalog provider
2016-02-24 09:35:26 +01:00
David Keijser
ae275c9e60 Consul catalog provider
Fixes #176
2016-02-24 09:23:27 +01:00
David Keijser
4277fe2fdb Bump libcompose 2016-02-24 09:23:27 +01:00
Vincent Demeester
7acc2beae0 Merge pull request #214 from octoblu/add-frontend-determinism
Deterministic frontend registration
2016-02-24 00:05:03 +01:00
Roy van de Water
847deeac79 Deterministic frontend registration
Conflicts:
	server.go
2016-02-22 13:37:54 -07:00
44 changed files with 1146 additions and 295 deletions

View File

@@ -23,7 +23,7 @@ Step 0 : FROM golang:1.5
---> 8c6473912976
Step 1 : RUN go get github.com/Masterminds/glide
[...]
docker run --rm -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/emile/dev/go/src/github.com/emilevauge/traefik/"dist":/go/src/github.com/emilevauge/traefik/"dist"" "traefik-dev:no-more-godep-ever" ./script/make.sh generate binary
docker run --rm -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/emile/dev/go/src/github.com/containous/traefik/"dist":/go/src/github.com/containous/traefik/"dist"" "traefik-dev:no-more-godep-ever" ./script/make.sh generate binary
---> Making bundle: generate (in .)
removed 'gen.go'
@@ -66,13 +66,13 @@ integration test using the `test-integration` target.
$ make test-unit
docker build -t "traefik-dev:your-feature-branch" -f build.Dockerfile .
# […]
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/vincent/src/github/vdemeester/traefik/dist:/go/src/github.com/emilevauge/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate test-unit
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/vincent/src/github/vdemeester/traefik/dist:/go/src/github.com/containous/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate test-unit
---> Making bundle: generate (in .)
removed 'gen.go'
---> Making bundle: test-unit (in .)
+ go test -cover -coverprofile=cover.out .
ok github.com/emilevauge/traefik 0.005s coverage: 4.1% of statements
ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
Test success
```

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ traefik.toml
*.test
vendor/
static/
glide.lock

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015 Emile Vauge, emile@vauge.com
Copyright (c) 2016 Containous SAS, Emile Vauge, emile@vauge.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
THE SOFTWARE.

View File

@@ -9,12 +9,12 @@ TRAEFIK_ENVS := \
SRCS = $(shell git ls-files '*.go' | grep -v '^external/')
BIND_DIR := "dist"
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/emilevauge/traefik/$(BIND_DIR)"
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/containous/traefik/$(BIND_DIR)"
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"emilevauge/traefik")
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock")
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"

View File

@@ -1,8 +1,10 @@
![Træfɪk](http://traefik.github.io/traefik.logo.svg "Træfɪk")
___
[![Build Status](https://travis-ci.org/emilevauge/traefik.svg?branch=master)](https://travis-ci.org/emilevauge/traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/EmileVauge/traefik/blob/master/LICENSE.md)
<p align="center">
<img src="http://traefik.github.io/traefik.logo.svg" alt="Træfɪk" title="Træfɪk" />
</p>
[![Build Status](https://travis-ci.org/containous/traefik.svg?branch=master)](https://travis-ci.org/containous/traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/containous/traefik/blob/master/LICENSE.md)
[![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com)
[![Twitter](https://img.shields.io/twitter/follow/traefikproxy.svg?style=social)](https://twitter.com/intent/follow?screen_name=traefikproxy)
@@ -14,17 +16,18 @@ It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/
## Features
- [It's fast](docs/index.md#benchmarks)
- No dependency hell, single binary made with go
- Simple json Rest API
- Simple TOML file configuration
- Multiple backends supported: Docker, Mesos/Marathon, Consul, Etcd, and more to come
- Watchers for backends, can listen change in backends to apply a new configuration automatically
- Hot-reloading of configuration. No need to restart the process
- Graceful shutdown http connections during hot-reloads
- Graceful shutdown http connections
- Circuit breakers on backends
- Round Robin, rebalancer load-balancers
- Rest Metrics
- Tiny docker image included [![Image Layers](https://badge.imagelayers.io/emilevauge/traefik:latest.svg)](https://imagelayers.io/?images=emilevauge/traefik:latest 'Image Layers')
- Tiny docker image included [![Image Layers](https://badge.imagelayers.io/containous/traefik:latest.svg)](https://imagelayers.io/?images=containous/traefik:latest)
- SSL backends support
- SSL frontend support
- Clean AngularJS Web UI
@@ -53,7 +56,7 @@ You can access to a simple HTML frontend of Træfik.
## Quick start
- The simple way: grab the latest binary from the [releases](https://github.com/emilevauge/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/EmileVauge/traefik/master/traefik.sample.toml):
- The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
```shell
./traefik -c traefik.toml
@@ -62,13 +65,13 @@ You can access to a simple HTML frontend of Træfik.
- Use the tiny Docker image:
```shell
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml emilevauge/traefik
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml containous/traefik
```
- From sources:
```shell
git clone https://github.com/emilevauge/traefik
git clone https://github.com/containous/traefik
```
## Documentation
@@ -106,3 +109,4 @@ software products.
[![Asteris](docs/img/asteris.logo.png)](https://aster.is)
Founded in 2014, Asteris creates next-generation infrastructure software for the modern datacenter. Asteris writes software that makes it easy for companies to implement continuous delivery and realtime data pipelines. We support the HashiCorp stack, along with Kubernetes, Apache Mesos, Spark and Kafka. We're core committers on mantl.io, consul-cli and mesos-consul.
.

View File

@@ -21,9 +21,9 @@ RUN set -ex; \
# Set the default Docker to be run
RUN ln -s /usr/local/bin/docker-${DOCKER_VERSION} /usr/local/bin/docker
WORKDIR /go/src/github.com/emilevauge/traefik
WORKDIR /go/src/github.com/containous/traefik
COPY glide.yaml glide.yaml
RUN glide up --quick
COPY . /go/src/github.com/emilevauge/traefik
COPY . /go/src/github.com/containous/traefik

72
cmd.go
View File

@@ -11,8 +11,8 @@ import (
"time"
log "github.com/Sirupsen/logrus"
"github.com/emilevauge/traefik/middlewares"
"github.com/emilevauge/traefik/provider"
"github.com/containous/traefik/middlewares"
"github.com/containous/traefik/provider"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"net/http"
@@ -39,28 +39,40 @@ var versionCmd = &cobra.Command{
var arguments = struct {
GlobalConfiguration
web bool
file bool
docker bool
dockerTLS bool
marathon bool
consul bool
zookeeper bool
etcd bool
boltdb bool
web bool
file bool
docker bool
dockerTLS bool
marathon bool
consul bool
consulTLS bool
consulCatalog bool
zookeeper bool
etcd bool
etcdTLS bool
boltdb bool
}{
GlobalConfiguration{
EntryPoints: make(EntryPoints),
Docker: &provider.Docker{
TLS: &provider.DockerTLS{},
},
File: &provider.File{},
Web: &WebProvider{},
Marathon: &provider.Marathon{},
Consul: &provider.Consul{},
Zookeeper: &provider.Zookepper{},
Etcd: &provider.Etcd{},
Boltdb: &provider.BoltDb{},
File: &provider.File{},
Web: &WebProvider{},
Marathon: &provider.Marathon{},
Consul: &provider.Consul{
Kv: provider.Kv{
TLS: &provider.KvTLS{},
},
},
ConsulCatalog: &provider.ConsulCatalog{},
Zookeeper: &provider.Zookepper{},
Etcd: &provider.Etcd{
Kv: provider.Kv{
TLS: &provider.KvTLS{},
},
},
Boltdb: &provider.BoltDb{},
},
false,
false,
@@ -71,6 +83,9 @@ var arguments = struct {
false,
false,
false,
false,
false,
false,
}
func init() {
@@ -111,25 +126,38 @@ func init() {
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Filename, "marathon.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Endpoint, "marathon.endpoint", "http://127.0.0.1:8080", "Marathon server endpoint. You can also specify multiple endpoint for Marathon")
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Domain, "marathon.domain", "", "Default domain used")
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.NetworkInterface, "marathon.networkInterface", "eth0", "Network interface used to call Marathon web services. Needed in case of multiple network interfaces")
traefikCmd.PersistentFlags().BoolVar(&arguments.consul, "consul", false, "Enable Consul backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.Watch, "consul.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Filename, "consul.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Endpoint, "consul.endpoint", "127.0.0.1:8500", "Consul server endpoint")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Endpoint, "consul.endpoint", "127.0.0.1:8500", "Comma sepparated Consul server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Prefix, "consul.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.consulTLS, "consul.tls", false, "Enable Consul TLS support")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.CA, "consul.tls.ca", "", "TLS CA")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.Cert, "consul.tls.cert", "", "TLS cert")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.Key, "consul.tls.key", "", "TLS key")
traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.TLS.InsecureSkipVerify, "consul.tls.insecureSkipVerify", false, "TLS insecure skip verify")
traefikCmd.PersistentFlags().BoolVar(&arguments.consulCatalog, "consulCatalog", false, "Enable Consul catalog backend")
traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Domain, "consulCatalog.domain", "", "Default domain used")
traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Endpoint, "consulCatalog.endpoint", "127.0.0.1:8500", "Consul server endpoint")
traefikCmd.PersistentFlags().BoolVar(&arguments.zookeeper, "zookeeper", false, "Enable Zookeeper backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Zookeeper.Watch, "zookeeper.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Filename, "zookeeper.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Endpoint, "zookeeper.endpoint", "127.0.0.1:2181", "Zookeeper server endpoint")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Endpoint, "zookeeper.endpoint", "127.0.0.1:2181", "Comma sepparated Zookeeper server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Prefix, "zookeeper.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.etcd, "etcd", false, "Enable Etcd backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Etcd.Watch, "etcd.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Filename, "etcd.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Endpoint, "etcd.endpoint", "127.0.0.1:4001", "Etcd server endpoint")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Endpoint, "etcd.endpoint", "127.0.0.1:4001", "Comma sepparated Etcd server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Prefix, "etcd.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.etcdTLS, "etcd.tls", false, "Enable Etcd TLS support")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.CA, "etcd.tls.ca", "", "TLS CA")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.Cert, "etcd.tls.cert", "", "TLS cert")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.Key, "etcd.tls.key", "", "TLS key")
traefikCmd.PersistentFlags().BoolVar(&arguments.Etcd.TLS.InsecureSkipVerify, "etcd.tls.insecureSkipVerify", false, "TLS insecure skip verify")
traefikCmd.PersistentFlags().BoolVar(&arguments.boltdb, "boltdb", false, "Enable Boltdb backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Boltdb.Watch, "boltdb.watch", true, "Watch provider")

View File

@@ -8,8 +8,8 @@ import (
"strings"
"time"
"github.com/emilevauge/traefik/provider"
"github.com/emilevauge/traefik/types"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/types"
"github.com/mitchellh/mapstructure"
"github.com/spf13/viper"
)
@@ -30,6 +30,7 @@ type GlobalConfiguration struct {
Web *WebProvider
Marathon *provider.Marathon
Consul *provider.Consul
ConsulCatalog *provider.ConsulCatalog
Etcd *provider.Etcd
Zookeeper *provider.Zookepper
Boltdb *provider.BoltDb
@@ -221,12 +222,21 @@ func LoadConfiguration() *GlobalConfiguration {
if arguments.marathon {
viper.Set("marathon", arguments.Marathon)
}
if !arguments.consulTLS {
arguments.Consul.TLS = nil
}
if arguments.consul {
viper.Set("consul", arguments.Consul)
}
if arguments.consulCatalog {
viper.Set("consulCatalog", arguments.ConsulCatalog)
}
if arguments.zookeeper {
viper.Set("zookeeper", arguments.Zookeeper)
}
if !arguments.etcdTLS {
arguments.Etcd.TLS = nil
}
if arguments.etcd {
viper.Set("etcd", arguments.Etcd)
}

View File

@@ -12,6 +12,7 @@ ___
- [Docker backend](#docker)
- [Mesos/Marathon backend](#marathon)
- [Consul backend](#consul)
- [Consul catalog backend](#consulcatalog)
- [Etcd backend](#etcd)
- [Zookeeper backend](#zk)
- [Boltdb backend](#boltdb)
@@ -36,7 +37,9 @@ Frontends can be defined using the following rules:
- `Host`: Host adds a matcher for the URL host. It accepts a template with zero or more URL variables enclosed by `{}`. Variables can define an optional regexp pattern to be matched: `www.traefik.io`, `{subdomain:[a-z]+}.traefik.io`
- `Methods`: Methods adds a matcher for HTTP methods. It accepts a sequence of one or more methods to be matched, e.g.: `GET`, `POST`, `PUT`
- `Path`: Path adds a matcher for the URL path. It accepts a template with zero or more URL variables enclosed by `{}`. The template must start with a `/`. For exemple `/products/` `/articles/{category}/{id:[0-9]+}`
- `PathStrip`: Same as `Path` but strip the given prefix from the request URL's Path.
- `PathPrefix`: PathPrefix adds a matcher for the URL path prefix. This matches if the given template is a prefix of the full URL path.
- `PathPrefixStrip`: Same as `PathPrefix` but strip the given prefix from the request URL's Path.
A frontend is a set of rules that forwards the incoming http traffic to a backend.
@@ -105,10 +108,18 @@ Flags:
--boltdb.watch Watch provider (default true)
-c, --configFile string Configuration file to use (TOML, JSON, YAML, HCL).
--consul Enable Consul backend
--consul.endpoint string Consul server endpoint (default "127.0.0.1:8500")
--consul.endpoint string Comma sepparated Consul server endpoints (default "127.0.0.1:8500")
--consul.filename string Override default configuration template. For advanced users :)
--consul.prefix string Prefix used for KV store (default "/traefik")
--consul.tls Enable Consul TLS support
--consul.tls.ca string TLS CA
--consul.tls.cert string TLS cert
--consul.tls.insecureSkipVerify TLS insecure skip verify
--consul.tls.key string TLS key
--consul.watch Watch provider (default true)
--consulCatalog Enable Consul catalog backend
--consulCatalog.domain string Default domain used
--consulCatalog.endpoint string Consul server endpoint (default "127.0.0.1:8500")
--defaultEntryPoints value Entrypoints to be used by frontends that do not specify any entrypoint (default &main.DefaultEntryPoints(nil))
--docker Enable Docker backend
--docker.domain string Default domain used
@@ -122,9 +133,14 @@ Flags:
--docker.watch Watch provider (default true)
--entryPoints value Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'
--etcd Enable Etcd backend
--etcd.endpoint string Etcd server endpoint (default "127.0.0.1:4001")
--etcd.endpoint string Comma sepparated Etcd server endpoints (default "127.0.0.1:4001")
--etcd.filename string Override default configuration template. For advanced users :)
--etcd.prefix string Prefix used for KV store (default "/traefik")
--etcd.tls Enable Etcd TLS support
--etcd.tls.ca string TLS CA
--etcd.tls.cert string TLS cert
--etcd.tls.insecureSkipVerify TLS insecure skip verify
--etcd.tls.key string TLS key
--etcd.watch Watch provider (default true)
--file Enable File backend
--file.filename string Override default configuration template. For advanced users :)
@@ -135,10 +151,8 @@ Flags:
--marathon.domain string Default domain used
--marathon.endpoint string Marathon server endpoint. You can also specify multiple endpoint for Marathon (default "http://127.0.0.1:8080")
--marathon.filename string Override default configuration template. For advanced users :)
--marathon.networkInterface string Network interface used to call Marathon web services. Needed in case of multiple network interfaces (default "eth0")
--marathon.watch Watch provider (default true)
--maxIdleConnsPerHost int If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used
-p, --port string Reverse proxy port (default ":80")
--providersThrottleDuration duration Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time. (default 2s)
--traefikLogsFile string Traefik logs file (default "log/traefik.log")
--web Enable Web backend
@@ -147,7 +161,7 @@ Flags:
--web.keyFile string SSL certificate
--web.readOnly Enable read only API
--zookeeper Enable Zookeeper backend
--zookeeper.endpoint string Zookeeper server endpoint (default "127.0.0.1:2181")
--zookeeper.endpoint string Comma sepparated Zookeeper server endpoints (default "127.0.0.1:2181")
--zookeeper.filename string Override default configuration template. For advanced users :)
--zookeeper.prefix string Prefix used for KV store (default "/traefik")
--zookeeper.watch Watch provider (default true)
@@ -625,12 +639,6 @@ Træfɪk can be configured to use Marathon as a backend configuration:
#
endpoint = "http://127.0.0.1:8080"
# Network interface used to call Marathon web services. Needed in case of multiple network interfaces.
# Optional
# Default: "eth0"
#
networkInterface = "eth0"
# Enable watch Marathon changes
#
# Optional
@@ -718,6 +726,16 @@ prefix = "traefik"
# Optional
#
# filename = "consul.tmpl"
# Enable consul TLS connection
#
# Optional
#
# [consul.tls]
# ca = "/etc/ssl/ca.crt"
# cert = "/etc/ssl/consul.crt"
# key = "/etc/ssl/consul.key"
# insecureskipverify = true
```
The Keys-Values structure should look (using `prefix = "/traefik"`):
@@ -799,6 +817,16 @@ Træfɪk can be configured to use Etcd as a backend configuration:
# Optional
#
# filename = "etcd.tmpl"
# Enable etcd TLS connection
#
# Optional
#
# [etcd.tls]
# ca = "/etc/ssl/ca.crt"
# cert = "/etc/ssl/etcd.crt"
# key = "/etc/ssl/etcd.key"
# insecureskipverify = true
```
The Keys-Values structure should look (using `prefix = "/traefik"`):
@@ -842,6 +870,37 @@ The Keys-Values structure should look (using `prefix = "/traefik"`):
| `/traefik/frontends/frontend2/routes/test_2/value` | `/test` |
## <a id="consulcatalog"></a> Consul catalog backend
Træfɪk can be configured to use service discovery catalog of Consul as a backend configuration:
```toml
################################################################
# Consul Catalog configuration backend
################################################################
# Enable Consul Catalog configuration backend
#
# Optional
#
[consulCatalog]
# Consul server endpoint
#
# Required
#
endpoint = "127.0.0.1:8500"
# Default domain used.
#
# Optional
#
domain = "consul.localhost"
```
This backend will create routes matching on hostname based on the service name
used in consul.
## <a id="zk"></a> Zookeeper backend
Træfɪk can be configured to use Zookeeper as a backend configuration:
@@ -1003,128 +1062,71 @@ Note that Træfɪk *will not watch for key changes in the `/traefik_configuratio
## <a id="benchmarks"></a> Benchmarks
Here are some early Benchmarks between Nginx and Træfɪk acting as simple load balancers between two servers.
Here are some early Benchmarks between Nginx, HA-Proxy and Træfɪk acting as simple load balancers between two servers.
- Nginx:
```sh
$ docker run -d -e VIRTUAL_HOST=test1.localhost emilevauge/whoami
$ docker run -d -e VIRTUAL_HOST=test1.localhost emilevauge/whoami
$ docker run -d -e VIRTUAL_HOST=test.nginx.localhost emilevauge/whoami
$ docker run -d -e VIRTUAL_HOST=test.nginx.localhost emilevauge/whoami
$ docker run --log-driver=none -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock:ro jwilder/nginx-proxy
$ ab -n 20000 -c 20 -r http://test1.localhost/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
$ wrk -t12 -c400 -d60s -H "Host: test.nginx.localhost" --latency http://127.0.0.1:80
Running 1m test @ http://127.0.0.1:80
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 162.61ms 203.34ms 1.72s 91.07%
Req/Sec 277.57 107.67 790.00 67.53%
Latency Distribution
50% 128.19ms
75% 218.22ms
90% 342.12ms
99% 1.08s
197991 requests in 1.00m, 82.32MB read
Socket errors: connect 0, read 0, write 0, timeout 18
Requests/sec: 3296.04
Transfer/sec: 1.37MB
```
Benchmarking test1.localhost (be patient)
Completed 2000 requests
Completed 4000 requests
Completed 6000 requests
Completed 8000 requests
Completed 10000 requests
Completed 12000 requests
Completed 14000 requests
Completed 16000 requests
Completed 18000 requests
Completed 20000 requests
Finished 20000 requests
- HA-Proxy:
Server Software: nginx/1.9.2
Server Hostname: test1.localhost
Server Port: 80
Document Path: /
Document Length: 287 bytes
Concurrency Level: 20
Time taken for tests: 5.874 seconds
Complete requests: 20000
Failed requests: 0
Total transferred: 8900000 bytes
HTML transferred: 5740000 bytes
Requests per second: 3404.97 [#/sec] (mean)
Time per request: 5.874 [ms] (mean)
Time per request: 0.294 [ms] (mean, across all concurrent requests)
Transfer rate: 1479.70 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 2
Processing: 0 6 2.4 6 35
Waiting: 0 5 2.3 5 33
Total: 0 6 2.4 6 36
Percentage of the requests served within a certain time (ms)
50% 6
66% 6
75% 7
80% 7
90% 9
95% 10
98% 12
99% 13
100% 36 (longest request)
```
$ docker run -d --name web1 -e VIRTUAL_HOST=test.haproxy.localhost emilevauge/whoami
$ docker run -d --name web2 -e VIRTUAL_HOST=test.haproxy.localhost emilevauge/whoami
$ docker run -d -p 80:80 --link web1:web1 --link web2:web2 dockercloud/haproxy
$ wrk -t12 -c400 -d60s -H "Host: test.haproxy.localhost" --latency http://127.0.0.1:80
Running 1m test @ http://127.0.0.1:80
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 158.08ms 187.88ms 1.75s 89.61%
Req/Sec 281.33 120.47 0.98k 65.88%
Latency Distribution
50% 121.77ms
75% 227.10ms
90% 351.98ms
99% 1.01s
200462 requests in 1.00m, 59.65MB read
Requests/sec: 3337.66
Transfer/sec: 0.99MB
```
- Træfɪk:
```sh
docker run -d -l traefik.backend=test1 -l traefik.frontend.rule=Host -l traefik.frontend.value=test1.docker.localhost emilevauge/whoami
docker run -d -l traefik.backend=test1 -l traefik.frontend.rule=Host -l traefik.frontend.value=test1.docker.localhost emilevauge/whoami
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/traefik.toml -v /var/run/docker.sock:/var/run/docker.sock emilevauge/traefik
$ ab -n 20000 -c 20 -r http://test1.docker.localhost/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking test1.docker.localhost (be patient)
Completed 2000 requests
Completed 4000 requests
Completed 6000 requests
Completed 8000 requests
Completed 10000 requests
Completed 12000 requests
Completed 14000 requests
Completed 16000 requests
Completed 18000 requests
Completed 20000 requests
Finished 20000 requests
Server Software: .
Server Hostname: test1.docker.localhost
Server Port: 80
Document Path: /
Document Length: 312 bytes
Concurrency Level: 20
Time taken for tests: 6.545 seconds
Complete requests: 20000
Failed requests: 0
Total transferred: 8600000 bytes
HTML transferred: 6240000 bytes
Requests per second: 3055.60 [#/sec] (mean)
Time per request: 6.545 [ms] (mean)
Time per request: 0.327 [ms] (mean, across all concurrent requests)
Transfer rate: 1283.11 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.2 0 7
Processing: 1 6 2.2 6 22
Waiting: 1 6 2.1 6 21
Total: 1 7 2.2 6 22
Percentage of the requests served within a certain time (ms)
50% 6
66% 7
75% 8
80% 8
90% 9
95% 10
98% 11
99% 13
100% 22 (longest request)
$ docker run -d -l traefik.backend=test1 -l traefik.frontend.rule=Host -l traefik.frontend.value=test.traefik.localhost emilevauge/whoami
$ docker run -d -l traefik.backend=test1 -l traefik.frontend.rule=Host -l traefik.frontend.value=test.traefik.localhost emilevauge/whoami
$ docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/traefik.toml -v /var/run/docker.sock:/var/run/docker.sock containous/traefik
$ wrk -t12 -c400 -d60s -H "Host: test.traefik.localhost" --latency http://127.0.0.1:80
Running 1m test @ http://127.0.0.1:80
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 132.93ms 121.89ms 1.20s 66.62%
Req/Sec 280.95 104.88 740.00 68.26%
Latency Distribution
50% 128.71ms
75% 214.15ms
90% 281.45ms
99% 498.44ms
200734 requests in 1.00m, 80.02MB read
Requests/sec: 3340.13
Transfer/sec: 1.33MB
```

View File

@@ -57,7 +57,7 @@ import:
- package: github.com/flynn/go-shlex
ref: 3f9db97f856818214da2e1057f8ad84803971cff
- package: github.com/fsouza/go-dockerclient
ref: 0239034d42f665efa17fd77c39f891c2f9f32922
ref: a49c8269a6899cae30da1f8a4b82e0ce945f9967
- package: github.com/boltdb/bolt
ref: 51f99c862475898df9773747d3accd05a7ca33c1
- package: gopkg.in/mgo.v2
@@ -124,7 +124,7 @@ import:
- package: gopkg.in/alecthomas/kingpin.v2
ref: 639879d6110b1b0409410c7b737ef0bb18325038
- package: github.com/docker/libcompose
ref: 79ef5d150f053a5b12f16b02d8844ed7cf33611a
ref: d3089811c119a211469a9cc93caea684d937e5d3
subpackages:
- docker
- logger

View File

@@ -0,0 +1,132 @@
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"time"
"github.com/docker/docker/opts"
"github.com/fsouza/go-dockerclient"
"github.com/hashicorp/consul/api"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// Consul catalog test suites
type ConsulCatalogSuite struct {
BaseSuite
consulIP string
consulClient *api.Client
dockerClient *docker.Client
}
func (s *ConsulCatalogSuite) GetContainer(name string) (*docker.Container, error) {
return s.dockerClient.InspectContainer(name)
}
func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
dockerHost := os.Getenv("DOCKER_HOST")
if dockerHost == "" {
// FIXME Handle windows -- see if dockerClient already handle that or not
dockerHost = fmt.Sprintf("unix://%s", opts.DefaultUnixSocket)
}
// Make sure we can speak to docker
dockerClient, err := docker.NewClient(dockerHost)
c.Assert(err, checker.IsNil, check.Commentf("Error connecting to docker daemon"))
s.dockerClient = dockerClient
s.createComposeProject(c, "consul_catalog")
err = s.composeProject.Up()
c.Assert(err, checker.IsNil, check.Commentf("Error starting project"))
consul, err := s.GetContainer("integration-test-consul_catalog_consul_1")
c.Assert(err, checker.IsNil, check.Commentf("Error finding consul container"))
s.consulIP = consul.NetworkSettings.IPAddress
config := api.DefaultConfig()
config.Address = s.consulIP + ":8500"
consulClient, err := api.NewClient(config)
if err != nil {
c.Fatalf("Error creating consul client")
}
s.consulClient = consulClient
// Wait for consul to elect itself leader
time.Sleep(2000 * time.Millisecond)
}
func (s *ConsulCatalogSuite) registerService(name string, address string, port int) error {
catalog := s.consulClient.Catalog()
_, err := catalog.Register(
&api.CatalogRegistration{
Node: address,
Address: address,
Service: &api.AgentService{
ID: name,
Service: name,
Address: address,
Port: port,
},
},
&api.WriteOptions{},
)
return err
}
func (s *ConsulCatalogSuite) deregisterService(name string, address string) error {
catalog := s.consulClient.Catalog()
_, err := catalog.Deregister(
&api.CatalogDeregistration{
Node: address,
Address: address,
ServiceID: name,
},
&api.WriteOptions{},
)
return err
}
func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) {
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--configFile=fixtures/consul_catalog/simple.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(500 * time.Millisecond)
// TODO validate : run on 80
resp, err := http.Get("http://127.0.0.1:8000/")
// Expected a 404 as we did not configure anything
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
}
func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
nginx, err := s.GetContainer("integration-test-consul_catalog_nginx_1")
c.Assert(err, checker.IsNil, check.Commentf("Error finding nginx container"))
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80)
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
time.Sleep(5000 * time.Millisecond)
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.consul.localhost"
resp, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200)
_, err = ioutil.ReadAll(resp.Body)
c.Assert(err, checker.IsNil)
}

27
integration/etcd_test.go Normal file
View File

@@ -0,0 +1,27 @@
package main
import (
"net/http"
"os/exec"
"time"
"fmt"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/etcd/simple.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(1000 * time.Millisecond)
// TODO validate : run on 80
resp, err := http.Get("http://127.0.0.1:8000/")
// Expected no response as we did not configure anything
c.Assert(resp, checker.IsNil)
c.Assert(err, checker.NotNil)
c.Assert(err.Error(), checker.Contains, fmt.Sprintf("getsockopt: connection refused"))
}

View File

@@ -0,0 +1,9 @@
defaultEntryPoints = ["http"]
logLevel = "DEBUG"
[entryPoints]
[entryPoints.http]
address = ":8000"
[consulCatalog]
domain = "consul.localhost"

View File

@@ -0,0 +1,10 @@
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[etcd]
endpoint = "127.0.0.1:4003,127.0.0.1:4002,127.0.0.1:4001"

View File

@@ -10,9 +10,9 @@ import (
"testing"
"text/template"
"github.com/containous/traefik/integration/utils"
"github.com/docker/libcompose/docker"
"github.com/docker/libcompose/project"
"github.com/emilevauge/traefik/integration/utils"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
@@ -28,6 +28,8 @@ func init() {
check.Suite(&FileSuite{})
check.Suite(&DockerSuite{})
check.Suite(&ConsulSuite{})
check.Suite(&ConsulCatalogSuite{})
check.Suite(&EtcdSuite{})
check.Suite(&MarathonSuite{})
}
@@ -49,6 +51,13 @@ func (s *ConsulSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "consul")
}
// Etcd test suites (using libcompose)
type EtcdSuite struct{ BaseSuite }
func (s *EtcdSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "etcd")
}
// Marathon test suites (using libcompose)
type MarathonSuite struct{ BaseSuite }
@@ -80,7 +89,9 @@ func (s *BaseSuite) TearDownSuite(c *check.C) {
func (s *BaseSuite) createComposeProject(c *check.C, name string) {
composeProject, err := docker.NewProject(&docker.Context{
Context: project.Context{
ComposeFile: fmt.Sprintf("resources/compose/%s.yml", name),
ComposeFiles: []string{
fmt.Sprintf("resources/compose/%s.yml", name),
},
ProjectName: fmt.Sprintf("integration-test-%s", name),
},
})

View File

@@ -0,0 +1,17 @@
consul:
image: progrium/consul
command: -server -bootstrap -log-level debug -ui-dir /ui
ports:
- "8400:8400"
- "8500:8500"
- "8600:53/udp"
expose:
- "8300"
- "8301"
- "8301/udp"
- "8302"
- "8302/udp"
nginx:
image: nginx
ports:
- "8881:80"

View File

@@ -0,0 +1,30 @@
etcd1:
image: quay.io/coreos/etcd:v2.2.0
net: "host"
command: >
--name etcd1
--listen-peer-urls http://localhost:7001
--listen-client-urls http://localhost:4001
--initial-advertise-peer-urls http://localhost:7001
--advertise-client-urls http://localhost:4001
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
etcd2:
image: quay.io/coreos/etcd:v2.2.0
net: "host"
command: >
--name etcd2
--listen-peer-urls http://localhost:7002
--listen-client-urls http://localhost:4002
--initial-advertise-peer-urls http://localhost:7002
--advertise-client-urls http://localhost:4002
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
etcd3:
image: quay.io/coreos/etcd:v2.2.0
net: "host"
command: >
--name etcd3
--listen-peer-urls http://localhost:7003
--listen-client-urls http://localhost:4003
--initial-advertise-peer-urls http://localhost:7003
--advertise-client-urls http://localhost:4003
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003

View File

@@ -3,7 +3,7 @@ zk:
net: host
environment:
ZK_CONFIG: tickTime=2000,initLimit=10,syncLimit=5,maxClientCnxns=128,forceSync=no,clientPort=2181
ZK_ID: 1
ZK_ID: " 1"
master:
image: mesosphere/mesos-master:0.23.0-1.0.ubuntu1404
@@ -12,7 +12,7 @@ master:
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_QUORUM: " 1"
MESOS_CLUSTER: docker-compose
MESOS_WORK_DIR: /var/lib/mesos

View File

@@ -0,0 +1,22 @@
package middlewares
import (
"net/http"
"strings"
)
// StripPrefix is a middleware used to strip prefix from an URL request
type StripPrefix struct {
Handler http.Handler
Prefix string
}
func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if p := strings.TrimPrefix(r.URL.Path, s.Prefix); len(p) < len(r.URL.Path) {
r.URL.Path = p
r.RequestURI = r.URL.RequestURI()
s.Handler.ServeHTTP(w, r)
} else {
http.NotFound(w, r)
}
}

View File

@@ -0,0 +1,40 @@
package middlewares
import (
"github.com/gorilla/mux"
"net/http"
"sync"
)
// HandlerSwitcher allows hot switching of http.ServeMux
type HandlerSwitcher struct {
handler *mux.Router
handlerLock *sync.Mutex
}
// NewHandlerSwitcher builds a new instance of HandlerSwitcher
func NewHandlerSwitcher(newHandler *mux.Router) (hs *HandlerSwitcher) {
return &HandlerSwitcher{
handler: newHandler,
handlerLock: &sync.Mutex{},
}
}
func (hs *HandlerSwitcher) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
hs.handlerLock.Lock()
handlerBackup := hs.handler
hs.handlerLock.Unlock()
handlerBackup.ServeHTTP(rw, r)
}
// GetHandler returns the current http.ServeMux
func (hs *HandlerSwitcher) GetHandler() (newHandler *mux.Router) {
return hs.handler
}
// UpdateHandler safely updates the current http.ServeMux with a new one
func (hs *HandlerSwitcher) UpdateHandler(newHandler *mux.Router) {
hs.handlerLock.Lock()
hs.handler = newHandler
defer hs.handlerLock.Unlock()
}

View File

@@ -1,9 +1,9 @@
package provider
import (
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/boltdb"
"github.com/emilevauge/traefik/types"
)
// BoltDb holds configurations of the BoltDb provider.

View File

@@ -1,9 +1,9 @@
package provider
import (
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/consul"
"github.com/emilevauge/traefik/types"
)
// Consul holds configurations of the Consul provider.

199
provider/consul_catalog.go Normal file
View File

@@ -0,0 +1,199 @@
package provider
import (
"errors"
"strings"
"text/template"
"time"
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/containous/traefik/types"
"github.com/hashicorp/consul/api"
)
const (
// DefaultWatchWaitTime is the duration to wait when polling consul
DefaultWatchWaitTime = 15 * time.Second
)
// ConsulCatalog holds configurations of the Consul catalog provider.
type ConsulCatalog struct {
BaseProvider `mapstructure:",squash"`
Endpoint string
Domain string
client *api.Client
}
type catalogUpdate struct {
Service string
Nodes []*api.ServiceEntry
}
func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[string][]string {
watchCh := make(chan map[string][]string)
catalog := provider.client.Catalog()
go func() {
defer close(watchCh)
opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
for {
select {
case <-stopCh:
return
default:
}
data, meta, err := catalog.Services(opts)
if err != nil {
log.WithError(err).Errorf("Failed to list services")
return
}
// If LastIndex didn't change then it means `Get` returned
// because of the WaitTime and the key didn't changed.
if opts.WaitIndex == meta.LastIndex {
continue
}
opts.WaitIndex = meta.LastIndex
if data != nil {
watchCh <- data
}
}
}()
return watchCh
}
func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, error) {
health := provider.client.Health()
opts := &api.QueryOptions{}
data, _, err := health.Service(service, "", true, opts)
if err != nil {
log.WithError(err).Errorf("Failed to fetch details of " + service)
return catalogUpdate{}, err
}
return catalogUpdate{
Service: service,
Nodes: data,
}, nil
}
func (provider *ConsulCatalog) getBackend(node *api.ServiceEntry) string {
return strings.ToLower(node.Service.Service)
}
func (provider *ConsulCatalog) getFrontendValue(service string) string {
return service + "." + provider.Domain
}
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
var FuncMap = template.FuncMap{
"getBackend": provider.getBackend,
"getFrontendValue": provider.getFrontendValue,
"replace": replace,
}
allNodes := []*api.ServiceEntry{}
serviceNames := []string{}
for _, info := range catalog {
if len(info.Nodes) > 0 {
serviceNames = append(serviceNames, info.Service)
allNodes = append(allNodes, info.Nodes...)
}
}
templateObjects := struct {
Services []string
Nodes []*api.ServiceEntry
}{
Services: serviceNames,
Nodes: allNodes,
}
configuration, err := provider.getConfiguration("templates/consul_catalog.tmpl", FuncMap, templateObjects)
if err != nil {
log.WithError(err).Error("Failed to create config")
}
return configuration
}
func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpdate, error) {
visited := make(map[string]bool)
nodes := []catalogUpdate{}
for service := range index {
name := strings.ToLower(service)
if !strings.Contains(name, " ") && !visited[name] {
visited[name] = true
log.WithFields(log.Fields{
"service": name,
}).Debug("Fetching service")
healthy, err := provider.healthyNodes(name)
if err != nil {
return nil, err
}
nodes = append(nodes, healthy)
}
}
return nodes, nil
}
func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessage) error {
stopCh := make(chan struct{})
serviceCatalog := provider.watchServices(stopCh)
defer close(stopCh)
for {
select {
case index, ok := <-serviceCatalog:
if !ok {
return errors.New("Consul service list nil")
}
log.Debug("List of services changed")
nodes, err := provider.getNodes(index)
if err != nil {
return err
}
configuration := provider.buildConfig(nodes)
configurationChan <- types.ConfigMessage{
ProviderName: "consul_catalog",
Configuration: configuration,
}
}
}
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage) error {
config := api.DefaultConfig()
config.Address = provider.Endpoint
client, err := api.NewClient(config)
if err != nil {
return err
}
provider.client = client
go func() {
notify := func(err error, time time.Duration) {
log.Errorf("Consul connection error %+v, retrying in %s", err, time)
}
worker := func() error {
return provider.watch(configurationChan)
}
err := backoff.RetryNotify(worker, backoff.NewExponentialBackOff(), notify)
if err != nil {
log.Fatalf("Cannot connect to consul server %+v", err)
}
}()
return err
}

View File

@@ -0,0 +1,110 @@
package provider
import (
"reflect"
"testing"
"github.com/containous/traefik/types"
"github.com/hashicorp/consul/api"
)
func TestConsulCatalogGetFrontendValue(t *testing.T) {
provider := &ConsulCatalog{
Domain: "localhost",
}
services := []struct {
service string
expected string
}{
{
service: "foo",
expected: "foo.localhost",
},
}
for _, e := range services {
actual := provider.getFrontendValue(e.service)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestConsulCatalogBuildConfig(t *testing.T) {
provider := &ConsulCatalog{
Domain: "localhost",
}
cases := []struct {
nodes []catalogUpdate
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
nodes: []catalogUpdate{},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
nodes: []catalogUpdate{
{
Service: "test",
},
},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
nodes: []catalogUpdate{
{
Service: "test",
Nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "test",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
},
},
},
expectedFrontends: map[string]*types.Frontend{
"frontend-test": {
Backend: "backend-test",
Routes: map[string]types.Route{
"route-host-test": {
Rule: "Host",
Value: "test.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-test": {
Servers: map[string]types.Server{
"server-localhost-80": {
URL: "http://127.0.0.1:80",
},
},
CircuitBreaker: nil,
LoadBalancer: nil,
},
},
},
}
for _, c := range cases {
actualConfig := provider.buildConfig(c.nodes)
if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) {
t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
}
if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) {
t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends)
}
}
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/emilevauge/traefik/types"
"github.com/containous/traefik/types"
"github.com/fsouza/go-dockerclient"
)

View File

@@ -5,7 +5,7 @@ import (
"strings"
"testing"
"github.com/emilevauge/traefik/types"
"github.com/containous/traefik/types"
"github.com/fsouza/go-dockerclient"
)
@@ -676,7 +676,11 @@ func TestDockerLoadDockerConfig(t *testing.T) {
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
},
IPAddress: "127.0.0.1",
Networks: map[string]docker.ContainerNetwork{
"bridgde": {
IPAddress: "127.0.0.1",
},
},
},
},
},
@@ -718,7 +722,11 @@ func TestDockerLoadDockerConfig(t *testing.T) {
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
},
IPAddress: "127.0.0.1",
Networks: map[string]docker.ContainerNetwork{
"bridgde": {
IPAddress: "127.0.0.1",
},
},
},
},
{
@@ -732,7 +740,11 @@ func TestDockerLoadDockerConfig(t *testing.T) {
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
},
IPAddress: "127.0.0.1",
Networks: map[string]docker.ContainerNetwork{
"bridgde": {
IPAddress: "127.0.0.1",
},
},
},
},
},

View File

@@ -1,9 +1,9 @@
package provider
import (
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/etcd"
"github.com/emilevauge/traefik/types"
)
// Etcd holds configurations of the Etcd provider.

View File

@@ -7,7 +7,7 @@ import (
"github.com/BurntSushi/toml"
log "github.com/Sirupsen/logrus"
"github.com/emilevauge/traefik/types"
"github.com/containous/traefik/types"
"gopkg.in/fsnotify.v1"
)

View File

@@ -2,15 +2,19 @@
package provider
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"strings"
"text/template"
"time"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/types"
"github.com/docker/libkv"
"github.com/docker/libkv/store"
"github.com/emilevauge/traefik/types"
)
// Kv holds common configurations of key-value providers.
@@ -18,18 +22,76 @@ type Kv struct {
BaseProvider `mapstructure:",squash"`
Endpoint string
Prefix string
TLS *KvTLS
storeType store.Backend
kvclient store.Store
}
// KvTLS holds TLS specific configurations
type KvTLS struct {
CA string
Cert string
Key string
InsecureSkipVerify bool
}
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string) {
for {
chanKeys, err := provider.kvclient.WatchTree(provider.Prefix, make(chan struct{}) /* stop chan */)
if err != nil {
log.Errorf("Failed to WatchTree %s", err)
continue
}
for range chanKeys {
configuration := provider.loadConfig()
if configuration != nil {
configurationChan <- types.ConfigMessage{
ProviderName: string(provider.storeType),
Configuration: configuration,
}
}
}
log.Warnf("Intermittent failure to WatchTree KV. Retrying.")
}
}
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage) error {
storeConfig := &store.Config{
ConnectionTimeout: 30 * time.Second,
Bucket: "traefik",
}
if provider.TLS != nil {
caPool := x509.NewCertPool()
if provider.TLS.CA != "" {
ca, err := ioutil.ReadFile(provider.TLS.CA)
if err != nil {
return fmt.Errorf("Failed to read CA. %s", err)
}
caPool.AppendCertsFromPEM(ca)
}
cert, err := tls.LoadX509KeyPair(provider.TLS.Cert, provider.TLS.Key)
if err != nil {
return fmt.Errorf("Failed to load keypair. %s", err)
}
storeConfig.TLS = &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caPool,
InsecureSkipVerify: provider.TLS.InsecureSkipVerify,
}
}
kv, err := libkv.NewStore(
provider.storeType,
[]string{provider.Endpoint},
&store.Config{
ConnectionTimeout: 30 * time.Second,
Bucket: "traefik",
},
strings.Split(provider.Endpoint, ","),
storeConfig,
)
if err != nil {
return err
@@ -39,24 +101,7 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage) error
}
provider.kvclient = kv
if provider.Watch {
stopCh := make(chan struct{})
chanKeys, err := kv.WatchTree(provider.Prefix, stopCh)
if err != nil {
return err
}
go func() {
for {
<-chanKeys
configuration := provider.loadConfig()
if configuration != nil {
configurationChan <- types.ConfigMessage{
ProviderName: string(provider.storeType),
Configuration: configuration,
}
}
defer close(stopCh)
}
}()
go provider.watchKv(configurationChan, provider.Prefix)
}
configuration := provider.loadConfig()
configurationChan <- types.ConfigMessage{

View File

@@ -2,8 +2,10 @@ package provider
import (
"errors"
"github.com/containous/traefik/types"
"strings"
"testing"
"time"
"github.com/docker/libkv/store"
"reflect"
@@ -231,10 +233,60 @@ func TestKvLast(t *testing.T) {
}
}
type KvMock struct {
Kv
}
func (provider *KvMock) loadConfig() *types.Configuration {
return nil
}
func TestKvWatchTree(t *testing.T) {
returnedChans := make(chan chan []*store.KVPair)
provider := &KvMock{
Kv{
kvclient: &Mock{
WatchTreeMethod: func() <-chan []*store.KVPair {
c := make(chan []*store.KVPair, 10)
returnedChans <- c
return c
},
},
},
}
configChan := make(chan types.ConfigMessage)
go provider.watchKv(configChan, "prefix")
select {
case c1 := <-returnedChans:
c1 <- []*store.KVPair{}
<-configChan
close(c1) // WatchTree chans can close due to error
case <-time.After(1 * time.Second):
t.Fatalf("Failed to create a new WatchTree chan")
}
select {
case c2 := <-returnedChans:
c2 <- []*store.KVPair{}
<-configChan
case <-time.After(1 * time.Second):
t.Fatalf("Failed to create a new WatchTree chan")
}
select {
case _ = <-configChan:
t.Fatalf("configChan should be empty")
default:
}
}
// Extremely limited mock store so we can test initialization
type Mock struct {
Error bool
KVPairs []*store.KVPair
Error bool
KVPairs []*store.KVPair
WatchTreeMethod func() <-chan []*store.KVPair
}
func (s *Mock) Put(key string, value []byte, opts *store.WriteOptions) error {
@@ -269,7 +321,7 @@ func (s *Mock) Watch(key string, stopCh <-chan struct{}) (<-chan *store.KVPair,
// WatchTree mock
func (s *Mock) WatchTree(prefix string, stopCh <-chan struct{}) (<-chan []*store.KVPair, error) {
return nil, errors.New("WatchTree not supported")
return s.WatchTreeMethod(), nil
}
// NewLock mock

View File

@@ -10,20 +10,19 @@ import (
"crypto/tls"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/emilevauge/traefik/types"
"github.com/containous/traefik/types"
"github.com/gambol99/go-marathon"
"net/http"
)
// Marathon holds configuration of the Marathon provider.
type Marathon struct {
BaseProvider `mapstructure:",squash"`
Endpoint string
Domain string
NetworkInterface string
Basic *MarathonBasic
TLS *tls.Config
marathonClient marathon.Marathon
BaseProvider `mapstructure:",squash"`
Endpoint string
Domain string
Basic *MarathonBasic
TLS *tls.Config
marathonClient marathon.Marathon
}
// MarathonBasic holds basic authentication specific configurations
@@ -42,7 +41,7 @@ type lightMarathonClient interface {
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage) error {
config := marathon.NewDefaultConfig()
config.URL = provider.Endpoint
config.EventsInterface = provider.NetworkInterface
config.EventsTransport = marathon.EventsTransportSSE
if provider.Basic != nil {
config.HTTPBasicAuthUser = provider.Basic.HTTPBasicAuthUser
config.HTTPBasicPassword = provider.Basic.HTTPBasicPassword
@@ -61,7 +60,7 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage)
update := make(marathon.EventsChannel, 5)
if provider.Watch {
if err := client.AddEventsListener(update, marathon.EVENTS_APPLICATIONS); err != nil {
log.Errorf("Failed to register for subscriptions, %s", err)
log.Errorf("Failed to register for events, %s", err)
} else {
go func() {
for {

View File

@@ -5,8 +5,8 @@ import (
"testing"
"errors"
"github.com/emilevauge/traefik/mocks"
"github.com/emilevauge/traefik/types"
"github.com/containous/traefik/mocks"
"github.com/containous/traefik/types"
"github.com/gambol99/go-marathon"
"github.com/stretchr/testify/mock"
)

View File

@@ -7,8 +7,8 @@ import (
"text/template"
"github.com/BurntSushi/toml"
"github.com/emilevauge/traefik/autogen"
"github.com/emilevauge/traefik/types"
"github.com/containous/traefik/autogen"
"github.com/containous/traefik/types"
)
// Provider defines methods of a provider.

View File

@@ -1,9 +1,9 @@
package provider
import (
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/zookeeper"
"github.com/emilevauge/traefik/types"
)
// Zookepper holds configurations of the Zookepper provider.

View File

@@ -4,7 +4,7 @@ if [ -z "$VALIDATE_UPSTREAM" ]; then
# this is kind of an expensive check, so let's not do this twice if we
# are running more than one validate bundlescript
VALIDATE_REPO='https://github.com/emilevauge/traefik.git'
VALIDATE_REPO='https://github.com/containous/traefik.git'
VALIDATE_BRANCH='master'
# Should not be needed for now O:)

View File

@@ -13,8 +13,30 @@ unzip -q linux_amd64.zip
sudo mv ghr /usr/bin/ghr
sudo chmod +x /usr/bin/ghr
ghr -t $GITHUB_TOKEN -u emilevauge -r traefik --prerelease ${VERSION} dist/
# github release and tag
ghr -t $GITHUB_TOKEN -u containous -r traefik --prerelease ${VERSION} dist/
# update traefik-library-image repo (official Docker image)
git config --global user.email "emile@vauge.com"
git config --global user.name "Emile Vauge"
git clone https://github.com/containous/traefik-library-image.git
cd traefik-library-image
git remote rm origin
git remote add origin https://emilevauge:${GITHUB_TOKEN}@github.com/containous/traefik-library-image.git
./update.sh $VERSION
git add -A
echo $VERSION | git commit --file -
echo $VERSION | git tag -a $VERSION --file -
git push --follow-tags -u origin master
# create docker image emilevauge/traefik (compatibility)
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
docker push ${REPO,,}:latest
docker tag ${REPO,,}:latest ${REPO,,}:${VERSION}
docker push ${REPO,,}:${VERSION}
docker tag containous/traefik emilevauge/traefik:latest
docker push emilevauge/traefik:latest
docker tag emilevauge/traefik:latest emilevauge/traefik:${VERSION}
docker push emilevauge/traefik:${VERSION}
cd ..
rm -Rf traefik-library-image/
echo "Deployed"

134
server.go
View File

@@ -7,25 +7,27 @@ import (
"crypto/tls"
"encoding/json"
"errors"
log "github.com/Sirupsen/logrus"
"github.com/codegangsta/negroni"
"github.com/emilevauge/traefik/middlewares"
"github.com/emilevauge/traefik/provider"
"github.com/emilevauge/traefik/types"
"github.com/gorilla/mux"
"github.com/mailgun/manners"
"github.com/mailgun/oxy/cbreaker"
"github.com/mailgun/oxy/forward"
"github.com/mailgun/oxy/roundrobin"
"net/http"
"net/url"
"os"
"os/signal"
"reflect"
"regexp"
"sort"
"sync"
"syscall"
"time"
log "github.com/Sirupsen/logrus"
"github.com/codegangsta/negroni"
"github.com/containous/traefik/middlewares"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/types"
"github.com/gorilla/mux"
"github.com/mailgun/manners"
"github.com/mailgun/oxy/cbreaker"
"github.com/mailgun/oxy/forward"
"github.com/mailgun/oxy/roundrobin"
)
var oxyLogger = &OxyLogger{}
@@ -46,7 +48,7 @@ type Server struct {
type serverEntryPoint struct {
httpServer *manners.GracefulServer
httpRouter *mux.Router
httpRouter *middlewares.HandlerSwitcher
}
// NewServer returns an initialized Server.
@@ -80,7 +82,7 @@ func (server *Server) Start() {
// Stop stops the server
func (server *Server) Stop() {
for _, serverEntryPoint := range server.serverEntryPoints {
serverEntryPoint.httpServer.Close()
serverEntryPoint.httpServer.BlockingClose()
}
server.stopChan <- true
}
@@ -140,22 +142,23 @@ func (server *Server) listenConfigurations() {
server.serverLock.Lock()
for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints {
currentServerEntryPoint := server.serverEntryPoints[newServerEntryPointName]
server.currentConfigurations = newConfigurations
currentServerEntryPoint.httpRouter = newServerEntryPoint.httpRouter
oldServer := currentServerEntryPoint.httpServer
newsrv, err := server.prepareServer(currentServerEntryPoint.httpRouter, server.globalConfiguration.EntryPoints[newServerEntryPointName], oldServer, server.loggerMiddleware, metrics)
if err != nil {
log.Fatal("Error preparing server: ", err)
}
go server.startServer(newsrv, server.globalConfiguration)
currentServerEntryPoint.httpServer = newsrv
server.serverEntryPoints[newServerEntryPointName] = currentServerEntryPoint
time.Sleep(1 * time.Second)
if oldServer != nil {
log.Info("Stopping old server")
oldServer.Close()
if currentServerEntryPoint.httpServer == nil {
newsrv, err := server.prepareServer(newServerEntryPoint.httpRouter, server.globalConfiguration.EntryPoints[newServerEntryPointName], nil, server.loggerMiddleware, metrics)
if err != nil {
log.Fatal("Error preparing server: ", err)
}
go server.startServer(newsrv, server.globalConfiguration)
currentServerEntryPoint.httpServer = newsrv
currentServerEntryPoint.httpRouter = newServerEntryPoint.httpRouter
server.serverEntryPoints[newServerEntryPointName] = currentServerEntryPoint
log.Infof("Created new Handler: %p", newServerEntryPoint.httpRouter.GetHandler())
} else {
handlerSwitcher := currentServerEntryPoint.httpRouter
handlerSwitcher.UpdateHandler(newServerEntryPoint.httpRouter.GetHandler())
log.Infof("Created new Handler: %p", newServerEntryPoint.httpRouter.GetHandler())
}
}
server.currentConfigurations = newConfigurations
server.serverLock.Unlock()
} else {
log.Error("Error loading new configuration, aborted ", err)
@@ -182,6 +185,9 @@ func (server *Server) configureProviders() {
if server.globalConfiguration.Consul != nil {
server.providers = append(server.providers, server.globalConfiguration.Consul)
}
if server.globalConfiguration.ConsulCatalog != nil {
server.providers = append(server.providers, server.globalConfiguration.ConsulCatalog)
}
if server.globalConfiguration.Etcd != nil {
server.providers = append(server.providers, server.globalConfiguration.Etcd)
}
@@ -259,7 +265,7 @@ func (server *Server) startServer(srv *manners.GracefulServer, globalConfigurati
log.Info("Server stopped")
}
func (server *Server) prepareServer(router *mux.Router, entryPoint *EntryPoint, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) (*manners.GracefulServer, error) {
func (server *Server) prepareServer(router http.Handler, entryPoint *EntryPoint, oldServer *manners.GracefulServer, middlewares ...negroni.Handler) (*manners.GracefulServer, error) {
log.Info("Preparing server")
// middlewares
var negroni = negroni.New()
@@ -298,7 +304,7 @@ func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration)
for entryPointName := range globalConfiguration.EntryPoints {
router := server.buildDefaultHTTPRouter()
serverEntryPoints[entryPointName] = serverEntryPoint{
httpRouter: router,
httpRouter: middlewares.NewHandlerSwitcher(router),
}
}
return serverEntryPoints
@@ -312,7 +318,10 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
backends := map[string]http.Handler{}
for _, configuration := range configurations {
for frontendName, frontend := range configuration.Frontends {
frontendNames := sortedFrontendNamesForConfig(configuration)
for _, frontendName := range frontendNames {
frontend := configuration.Frontends[frontendName]
log.Debugf("Creating frontend %s", frontendName)
fwd, _ := forward.New(forward.Logger(oxyLogger), forward.PassHostHeader(frontend.PassHostHeader))
// default endpoints if not defined in frontends
@@ -324,14 +333,14 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
if _, ok := serverEntryPoints[entryPointName]; !ok {
return nil, errors.New("Undefined entrypoint: " + entryPointName)
}
newRoute := serverEntryPoints[entryPointName].httpRouter.NewRoute().Name(frontendName)
newRoute := serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName)
for routeName, route := range frontend.Routes {
log.Debugf("Creating route %s %s:%s", routeName, route.Rule, route.Value)
newRouteReflect, err := invoke(newRoute, route.Rule, route.Value)
route, err := getRoute(newRoute, route.Rule, route.Value)
if err != nil {
return nil, err
}
newRoute = newRouteReflect[0].Interface().(*mux.Route)
newRoute = route
}
entryPoint := globalConfiguration.EntryPoints[entryPointName]
if entryPoint.Redirect != nil {
@@ -391,7 +400,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
} else {
log.Debugf("Reusing backend %s", frontend.Backend)
}
newRoute.Handler(backends[frontend.Backend])
server.wireFrontendBackend(frontend.Routes, newRoute, backends[frontend.Backend])
}
err := newRoute.GetError()
if err != nil {
@@ -403,13 +412,39 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
return serverEntryPoints, nil
}
func (server *Server) wireFrontendBackend(routes map[string]types.Route, newRoute *mux.Route, handler http.Handler) {
// strip prefix
var strip bool
for _, route := range routes {
switch route.Rule {
case "PathStrip":
newRoute.Handler(&middlewares.StripPrefix{
Prefix: route.Value,
Handler: handler,
})
strip = true
break
case "PathPrefixStrip":
newRoute.Handler(&middlewares.StripPrefix{
Prefix: route.Value,
Handler: handler,
})
strip = true
break
}
}
if !strip {
newRoute.Handler(handler)
}
}
func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *EntryPoint) (http.Handler, error) {
regex := entryPoint.Redirect.Regex
replacement := entryPoint.Redirect.Replacement
if len(entryPoint.Redirect.EntryPoint) > 0 {
regex = "^(?:https?:\\/\\/)?([\\da-z\\.-]+)(?::\\d+)(.*)$"
if server.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint] == nil {
return nil, errors.New("Unkown entrypoint " + entryPoint.Redirect.EntryPoint)
return nil, errors.New("Unknown entrypoint " + entryPoint.Redirect.EntryPoint)
}
protocol := "http"
if server.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint].TLS != nil {
@@ -435,5 +470,36 @@ func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *En
func (server *Server) buildDefaultHTTPRouter() *mux.Router {
router := mux.NewRouter()
router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
router.StrictSlash(true)
return router
}
func getRoute(any interface{}, rule string, value ...interface{}) (*mux.Route, error) {
switch rule {
case "PathStrip":
rule = "Path"
case "PathPrefixStrip":
rule = "PathPrefix"
}
inputs := make([]reflect.Value, len(value))
for i := range value {
inputs[i] = reflect.ValueOf(value[i])
}
method := reflect.ValueOf(any).MethodByName(rule)
if method.IsValid() {
return method.Call(inputs)[0].Interface().(*mux.Route), nil
}
return nil, errors.New("Method not found: " + rule)
}
func sortedFrontendNamesForConfig(configuration *types.Configuration) []string {
keys := []string{}
for key := range configuration.Frontends {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}

View File

@@ -0,0 +1,13 @@
[backends]{{range .Nodes}}
[backends.backend-{{getBackend .}}.servers.server-{{.Node.Node | replace "." "-"}}-{{.Service.Port}}]
url = "http://{{.Node.Address}}:{{.Service.Port}}"
{{end}}
[frontends]{{range .Services}}
[frontends.frontend-{{.}}]
backend = "backend-{{.}}"
passHostHeader = false
[frontends.frontend-{{.}}.routes.route-host-{{.}}]
rule = "Host"
value = "{{getFrontendValue .}}"
{{end}}

View File

@@ -1,6 +1,6 @@
[backends]{{range .Containers}}
[backends.backend-{{getBackend .}}.servers.server-{{.Name | replace "/" "" | replace "." "-"}}]
url = "{{getProtocol .}}://{{.NetworkSettings.IPAddress}}:{{getPort .}}"
url = "{{getProtocol .}}://{{range $i := .NetworkSettings.Networks}}{{if $i}}{{.IPAddress}}{{end}}{{end}}:{{getPort .}}"
weight = {{getWeight .}}
{{end}}

View File

@@ -8,7 +8,6 @@ import (
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
if err := traefikCmd.Execute(); err != nil {
fmtlog.Println(err)
os.Exit(-1)

View File

@@ -206,12 +206,6 @@
#
# endpoint = "http://127.0.0.1:8080"
# Network interface used to call Marathon web services. Needed in case of multiple network interfaces.
# Optional
# Default: "eth0"
#
# networkInterface = "eth0"
# Enable watch Marathon changes
#
# Optional
@@ -281,6 +275,16 @@
#
# filename = "consul.tmpl"
# Enable consul TLS connection
#
# Optional
#
# [consul.tls]
# ca = "/etc/ssl/ca.crt"
# cert = "/etc/ssl/consul.crt"
# key = "/etc/ssl/consul.key"
# insecureskipverify = true
################################################################
# Etcd configuration backend
@@ -316,6 +320,16 @@
#
# filename = "etcd.tmpl"
# Enable etcd TLS connection
#
# Optional
#
# [etcd.tls]
# ca = "/etc/ssl/ca.crt"
# cert = "/etc/ssl/etcd.crt"
# key = "/etc/ssl/etcd.key"
# insecureskipverify = true
################################################################
# Zookeeper configuration backend

View File

@@ -1,23 +0,0 @@
/*
Copyright
*/
package main
import (
"errors"
"reflect"
)
// Invoke calls the specified method with the specified arguments on the specified interface.
// It uses the go(lang) reflect package.
func invoke(any interface{}, name string, args ...interface{}) ([]reflect.Value, error) {
inputs := make([]reflect.Value, len(args))
for i := range args {
inputs[i] = reflect.ValueOf(args[i])
}
method := reflect.ValueOf(any).MethodByName(name)
if method.IsValid() {
return method.Call(inputs), nil
}
return nil, errors.New("Method not found: " + name)
}

4
web.go
View File

@@ -7,9 +7,9 @@ import (
"net/http"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/autogen"
"github.com/containous/traefik/types"
"github.com/elazarl/go-bindata-assetfs"
"github.com/emilevauge/traefik/autogen"
"github.com/emilevauge/traefik/types"
"github.com/gorilla/mux"
"github.com/thoas/stats"
"github.com/unrolled/render"

View File

@@ -39,7 +39,7 @@
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="https://github.com/EmileVauge/traefik/blob/master/docs/index.md" target="_blank">Documentation</a>
<a href="https://github.com/containous/traefik/blob/master/docs/index.md" target="_blank">Documentation</a>
</li>
<li>
<a href="http://traefik.io" target="_blank"><span class="traefik-blue">traefik.io</span></a>