1
0
mirror of https://github.com/containous/traefik.git synced 2025-10-05 07:33:19 +03:00

Compare commits

...

71 Commits

Author SHA1 Message Date
Vincent Demeester
0332e32293 Merge pull request #322 from containous/fix-marathon-backend
Fix Marathon backend
2016-04-19 12:32:56 +02:00
Emile Vauge
2a3a34a80c Fix Marathon backend
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-19 12:05:28 +02:00
Emile Vauge
68da47b59a Merge pull request #321 from samber/minor-doc-fix
fix(doc)
2016-04-19 12:04:13 +02:00
Samuel BERTHE
b1f0f048cd fix(doc) 2016-04-19 10:00:33 +02:00
Vincent Demeester
ee60adc45a Merge pull request #315 from containous/add-backoff-marathon
Add backoff to marathon provider
2016-04-16 17:32:01 +02:00
Emile Vauge
36338b4928 add backoff to marathon provider
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-16 17:21:24 +02:00
Emile Vauge
23d3c512c2 Merge pull request #316 from vdemeester/docker-provider-stop-support
Support stop chan on docker provider
2016-04-16 17:20:55 +02:00
Vincent Demeester
4144638be4 Support stop chan on docker provider
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-04-16 14:46:35 +02:00
Emile Vauge
f2320ee648 Merge pull request #313 from containous/add-user-guide
Add doc user guide with swarm
2016-04-15 19:13:53 +02:00
Emile Vauge
17afa3e672 Add doc user guide with swarm
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-15 19:00:08 +02:00
Vincent Demeester
5b2c355c38 Merge pull request #305 from containous/fix-races
Fix races
2016-04-15 18:09:50 +02:00
Emile Vauge
61d54903e3 Fix doc
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-14 15:27:42 +02:00
Emile Vauge
c1078c4374 Fix races
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-14 15:27:42 +02:00
Emile Vauge
4e427b5a9e remove error oxy log
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-14 15:27:42 +02:00
Vincent Demeester
227ec71db3 Merge pull request #306 from kevioke/oxy-glide-update
Update glide files to use most recent version of containous/oxy
2016-04-14 15:08:31 +02:00
kevin
d047b8daa1 Update glide files to use most recent version of containous/oxy 2016-04-13 14:51:14 -07:00
Emile Vauge
c2009b71b1 Merge pull request #303 from containous/emilevauge-bump-go1.6.1
Bump to go v1.6.1
2016-04-13 21:28:53 +02:00
Emile Vauge
ba8629e2ac Bump to go v1.6.1 2016-04-13 21:09:39 +02:00
Vincent Demeester
6aba453afb Merge pull request #301 from kevioke/maxconns
Add support for maximum connections for backends.
2016-04-13 18:50:29 +02:00
kevin
a15578a8f6 Add support for maximum connections for backends. 2016-04-13 09:37:11 -07:00
Vincent Demeester
5c8d9f4eb9 Merge pull request #274 from samber/consul-catalog-with-tags-settings
feat(traefik,consul-catalog): Set attributes from consul catalog tags (loadbalancer,circuitbreaker,weight)
2016-04-13 17:17:10 +02:00
Emile Vauge
a9e615b3c7 Fix period in frontend name in KV store 2016-04-13 14:56:51 +02:00
Emile Vauge
94ad21020c Merge pull request #297 from containous/emilevauge-fix-period-frontend-kvstore
Fix period in frontend name in KV store
2016-04-13 13:25:52 +02:00
Emile Vauge
4b76cb4318 Fix period in frontend name in KV store 2016-04-13 13:00:20 +02:00
Vincent Demeester
fad7ec6b7f Merge pull request #299 from containous/add-better-benchmarks
add better benchmarks
2016-04-13 12:56:17 +02:00
Emile Vauge
82a49a8e89 add better benchmarks
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-13 12:17:53 +02:00
Vincent Demeester
2bcc5a2ac7 Merge pull request #294 from samber/TRAEFIK-275-consul-catalog-backend-using-container-internal-ip
feat(consul-catalog-provider): + setting unique backend name + backendd redirecting to internal container ip
2016-04-13 09:38:09 +02:00
Samuel BERTHE
4f044cf2f9 feat(consul-catalog-provider): + setting unique backend name + backend redirecting to internal container ip 2016-04-13 08:05:44 +02:00
Emile Vauge
9a407f79ff Merge pull request #291 from vdemeester/kewl-makefile
Add a make help target
2016-04-12 10:35:48 +02:00
Vincent Demeester
affec30c64 Add a make help target
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-04-12 10:09:16 +02:00
Emile Vauge
d050e60da2 Merge pull request #278 from vdemeester/migrate-to-engine-api
Migrate docker provider traefik to engine-api
2016-04-08 15:21:26 +02:00
Vincent Demeester
866b9835a6 Migrate traefik to engine-api
The docker provider now uses docker/engine-api and
vdemeester/docker-events instead of fsouza-dockerclient.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-04-08 14:21:02 +02:00
Emile Vauge
f6564909aa Merge pull request #279 from vdemeester/update-dockerignore
Add **/*.test to .dockerignore
2016-04-07 17:22:53 +02:00
Vincent Demeester
315e8b64b8 Add **/*.test to .dockerignore
`*.test` files are generated by `go test`, do not include them into the
build context. It will lighter a bit the build context..

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-04-07 16:48:35 +02:00
Emile Vauge
f99f634816 Merge pull request #290 from containous/fix-issues
Fix issues
2016-04-07 16:36:13 +02:00
Emile Vauge
5292a5b9d4 Migrate to official docker image
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-07 16:24:11 +02:00
Emile Vauge
cf22d62a74 Fix mkdoc deploy
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-07 16:24:11 +02:00
Emile Vauge
9363e2ab83 Fix broken table in webUI
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-07 16:24:11 +02:00
Emile Vauge
e5ddd92677 Fix port support in host rule
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-07 16:23:57 +02:00
Vincent Demeester
04628056af Merge pull request #287 from containous/fix-doc-deploy
Fix doc deploy...
2016-04-06 19:17:50 +02:00
Emile Vauge
dada86c0b0 Fix doc deploy...
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-06 18:50:35 +02:00
Vincent Demeester
92c269c972 Merge pull request #286 from containous/fix-CI-env-variable
Fix CI env variable...
2016-04-06 17:45:34 +02:00
Emile Vauge
6991e3c99b Fix CI env variable...
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-06 17:31:43 +02:00
Vincent Demeester
3ee3daee00 Merge pull request #285 from containous/add-multiple-rules
Add multiple rules
2016-04-06 16:24:16 +02:00
Emile Vauge
85fcff4cf7 Multiple rules docs
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-06 16:10:20 +02:00
Emile Vauge
30db47d9b6 Fix SSH key, I hope...
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-06 15:28:25 +02:00
Emile Vauge
4d2c85ffdc Fix multiple response.WriteHeader calls
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-06 14:30:29 +02:00
Emile Vauge
e36433c23a Fix retry attempts
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-06 14:07:51 +02:00
Emile Vauge
8486766a60 Add multiple rules
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-06 13:27:54 +02:00
Vincent Demeester
ef72d355d6 Merge pull request #283 from containous/fix-openssl-travis
Fix SSH key
2016-04-06 00:46:30 +02:00
Emile Vauge
7d013ad5e8 Fix SSH key
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-05 22:40:42 +02:00
Vincent Demeester
5fcce6567e Merge pull request #282 from containous/fix-openssl-travis
Fix openssl travis
2016-04-05 22:24:25 +02:00
Emile Vauge
00af537b0d Fix link in README
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-05 22:07:16 +02:00
Emile Vauge
78449fa62f Fix openssl load key in CI
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-05 20:47:23 +02:00
Vincent Demeester
ab0d648a03 Merge pull request #280 from containous/add-doc-site
Add docs.traefik.io
2016-04-05 17:26:26 +02:00
Emile Vauge
43d2107493 Add mkdoc in CI
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-05 17:13:08 +02:00
Emile Vauge
fd8b4a3305 add documentation website
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-05 17:13:08 +02:00
Emile Vauge
79dc4f9a70 Merge pull request #277 from vdemeester/micro-libkermit-update
Update libkermit again for compose enhancements
2016-04-04 10:34:43 +02:00
Vincent Demeester
b0fa11b8b8 Update libkermit again for compose enhancements
Using `.Container(…)`, it's way easier to get the container from a
project's service.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-04-02 15:33:12 +02:00
Emile Vauge
6e7bb93fd6 Merge pull request #276 from vdemeester/mini-kermit-update
A small update of libkermit
2016-04-02 13:26:16 +02:00
Vincent Demeester
e1448eb238 A small update of libkermit
Using compose/check package (and less code)

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-04-02 12:40:21 +02:00
Emile Vauge
585aeb8f0b Merge pull request #272 from wallies/patch-1
Add Go Report Card badge
2016-04-01 14:41:18 +02:00
Cameron
563823189a Merge branch 'master' into patch-1 2016-04-01 13:28:51 +01:00
Vincent Demeester
e9bf916a74 Merge pull request #270 from containous/fix-acme-renew
Fix acme renew panic
2016-04-01 14:12:18 +02:00
Emile Vauge
bcc5f24c0f Add GoSafe goroutine launch
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-01 14:01:31 +02:00
Cameron
9462c2e476 Add Go Report Card badge 2016-04-01 12:43:55 +01:00
Emile Vauge
af41c79798 Fix acme renew panic
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-31 17:21:05 +02:00
Vincent Demeester
733cbb5304 Merge pull request #266 from containous/refactor-frontend-rules
Refactor frontends rules
2016-03-31 16:29:51 +02:00
Emile Vauge
d5e1d2efd5 Fix documentation
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-31 16:17:59 +02:00
Emile Vauge
bb072a1f8f Add backwards compatibility
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-31 13:11:18 +02:00
Emile Vauge
8737530a7d Refactor frontends rules
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-31 13:11:07 +02:00
80 changed files with 11344 additions and 2111 deletions

View File

@@ -1,3 +1,5 @@
dist/
vendor/
!dist/traefik
site/
**/*.test

3
.gitignore vendored
View File

@@ -9,4 +9,5 @@ traefik.toml
*.test
vendor/
static/
.vscode/
.vscode/
site/

View File

@@ -1,31 +1,28 @@
branches:
except:
- /^v\d\.\d\.\d.*$/
- /^v\d\.\d\.\d.*$/
env:
REPO: $TRAVIS_REPO_SLUG
VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER
global:
- secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg=
- REPO: $TRAVIS_REPO_SLUG
- VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER
sudo: required
services:
- docker
- docker
install:
- sudo service docker stop
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-1.10.1 -o /usr/bin/docker
- sudo chmod +x /usr/bin/docker
- sudo service docker start
- sudo service docker stop
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-1.10.1 -o /usr/bin/docker
- sudo chmod +x /usr/bin/docker
- sudo service docker start
- pip install --user mkdocs
- pip install --user pymdown-extensions
before_script:
- make validate
- make binary
- make validate
- make binary
script:
- make test-unit
- make test-integration
- make crossbinary
- make image
- make test-unit
- make test-integration
- make crossbinary
- make image
after_success:
- make deploy
- make deploy

BIN
.travis/traefik.id_rsa.enc Normal file

Binary file not shown.

View File

@@ -24,36 +24,27 @@ print-%: ; @echo $*=$($*)
default: binary
all: generate-webui build
all: generate-webui build ## validate all checks, build linux binary, run all tests\ncross non-linux binaries
$(DOCKER_RUN_TRAEFIK) ./script/make.sh
binary: generate-webui build
binary: generate-webui build ## build the linux binary
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary
crossbinary: generate-webui build
crossbinary: generate-webui build ## cross build the non-linux binaries
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate crossbinary
test: build
test: build ## run the unit and integration tests
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit binary test-integration
test-unit: build
test-unit: build ## run the unit tests
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit
test-integration: build
test-integration: build ## run the integration tests
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-integration
validate: build
validate: build ## validate gofmt, golint and go vet
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
validate-gofmt: build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt
validate-govet: build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-govet
validate-golint: build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-golint
build: dist
docker build -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
@@ -63,10 +54,10 @@ build-webui:
build-no-cache: dist
docker build --no-cache -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
shell: build
shell: build ## start a shell inside the build env
$(DOCKER_RUN_TRAEFIK) /bin/bash
image: build
image: build ## build a docker traefik image
docker build -t $(TRAEFIK_IMAGE) .
dist:
@@ -92,3 +83,6 @@ fmt:
deploy:
./script/deploy.sh
help: ## this help
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

View File

@@ -1,22 +1,48 @@
<p align="center">
<img src="http://traefik.github.io/traefik.logo.svg" alt="Træfɪk" title="Træfɪk" />
<img src="docs/img/traefik.logo.png" 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)
[![Docs](https://img.shields.io/badge/docs-current-brightgreen.svg)](https://docs.traefik.io)
[![Go Report Card](https://goreportcard.com/badge/kubernetes/helm)](http://goreportcard.com/report/containous/traefik)
[![Image Layer](https://badge.imagelayers.io/traefik:latest.svg)](https://imagelayers.io/?images=traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](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)
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
It supports several backends ([Docker :whale:](https://www.docker.com/), [Swarm :whale: :whale:](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
## Overview
Imagine that you have deployed a bunch of microservices on your infrastructure. You probably used a service registry (like etcd or consul) and/or an orchestrator (swarm, Mesos/Marathon) to manage all these services.
If you want your users to access some of your microservices from the Internet, you will have to use a reverse proxy and configure it using virtual hosts or prefix paths:
- domain `api.domain.com` will point the microservice `api` in your private network
- path `domain.com/web` will point the microservice `web` in your private network
- domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances
But a microservices architecture is dynamic... Services are added, removed, killed or upgraded often, eventually several times a day.
Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily.
Here enters Træfɪk.
![Architecture](docs/img/architecture.png)
Træfɪk can listen to your service registry/orchestrator API, and knows each time a microservice is added, removed, killed or upgraded, and can generate its configuration automatically.
Routes to your services will be created instantly.
Run it and forget it!
## Features
- [It's fast](docs/index.md#benchmarks)
- [It's fast](http://docs.traefik.io/benchmarks)
- No dependency hell, single binary made with go
- Rest API
- Multiple backends supported: Docker, Mesos/Marathon, Consul, Etcd, and more to come
@@ -26,7 +52,7 @@ It supports several backends ([Docker :whale:](https://www.docker.com/), [Swarm
- Circuit breakers on backends
- Round Robin, rebalancer load-balancers
- Rest Metrics
- Tiny docker image included [![Image Layers](https://badge.imagelayers.io/containous/traefik:latest.svg)](https://imagelayers.io/?images=containous/traefik:latest)
- [Tiny](https://imagelayers.io/?images=traefik) [official](https://hub.docker.com/r/_/traefik/) docker image included
- SSL backends support
- SSL frontend support (with SNI)
- Clean AngularJS Web UI
@@ -67,7 +93,7 @@ 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 containous/traefik
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
```
- From sources:
@@ -78,7 +104,7 @@ git clone https://github.com/containous/traefik
## Documentation
You can find the complete documentation [here](docs/index.md).
You can find the complete documentation [here](https://docs.traefik.io).
## Contributing

View File

@@ -10,6 +10,7 @@ import (
"errors"
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/safe"
"github.com/xenolf/lego/acme"
"io/ioutil"
fmtlog "log"
@@ -142,6 +143,22 @@ type DomainsCertificate struct {
tlsCert *tls.Certificate
}
func (dc *DomainsCertificate) needRenew() bool {
for _, c := range dc.tlsCert.Certificate {
crt, err := x509.ParseCertificate(c)
if err != nil {
// If there's an error, we assume the cert is broken, and needs update
return true
}
// <= 7 days left, renew certificate
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 7 * time.Hour))) {
return true
}
}
return false
}
// ACME allows to connect to lets encrypt and retrieve certs
type ACME struct {
Email string
@@ -226,7 +243,9 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
return err
}
go a.retrieveCertificates(client, account)
safe.Go(func() {
a.retrieveCertificates(client, account)
})
tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
if challengeCert, ok := wrapperChallengeProvider.getCertificate(clientHello.ServerName); ok {
@@ -245,7 +264,7 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
}
ticker := time.NewTicker(24 * time.Hour)
go func() {
safe.Go(func() {
for {
select {
case <-ticker.C:
@@ -256,7 +275,7 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
}
}
}()
})
return nil
}
@@ -289,8 +308,7 @@ func (a *ACME) retrieveCertificates(client *acme.Client, account *Account) {
func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
for _, certificateResource := range account.DomainsCertificate.Certs {
// <= 7 days left, renew certificate
if certificateResource.tlsCert.Leaf.NotAfter.Before(time.Now().Add(time.Duration(24 * 7 * time.Hour))) {
if certificateResource.needRenew() {
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
renewedCert, err := client.RenewCertificate(acme.CertificateResource{
Domain: certificateResource.Certificate.Domain,

View File

@@ -23,9 +23,9 @@ func (oxylogger *OxyLogger) Warningf(format string, args ...interface{}) {
log.Warningf(format, args...)
}
// Errorf logs specified string as Error level in logrus.
// Errorf logs specified string as Warningf level in logrus.
func (oxylogger *OxyLogger) Errorf(format string, args ...interface{}) {
log.Errorf(format, args...)
log.Warningf(format, args...)
}
func notFoundHandler(w http.ResponseWriter, r *http.Request) {

View File

@@ -1,4 +1,4 @@
FROM golang:1.6.0-alpine
FROM golang:1.6.1-alpine
RUN apk update && apk add git bash gcc musl-dev \
&& go get github.com/Masterminds/glide \

1
cmd.go
View File

@@ -142,6 +142,7 @@ func init() {
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().StringVar(&arguments.ConsulCatalog.Prefix, "consulCatalog.prefix", "traefik", "Consul catalog tag prefix")
traefikCmd.PersistentFlags().BoolVar(&arguments.zookeeper, "zookeeper", false, "Enable Zookeeper backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Zookeeper.Watch, "zookeeper.watch", true, "Watch provider")

1
docs/CNAME Normal file
View File

@@ -0,0 +1 @@
docs.traefik.io

202
docs/basics.md Normal file
View File

@@ -0,0 +1,202 @@
# Concepts
Let's take our example from the [overview](https://docs.traefik.io/#overview) again:
> Imagine that you have deployed a bunch of microservices on your infrastructure. You probably used a service registry (like etcd or consul) and/or an orchestrator (swarm, Mesos/Marathon) to manage all these services.
> If you want your users to access some of your microservices from the Internet, you will have to use a reverse proxy and configure it using virtual hosts or prefix paths:
> - domain `api.domain.com` will point the microservice `api` in your private network
> - path `domain.com/web` will point the microservice `web` in your private network
> - domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances
> ![Architecture](img/architecture.png)
Let's zoom on Træfɪk and have an overview of its internal architecture:
![Architecture](img/internal.png)
- Incoming requests end on [entrypoints](#entrypoints), as the name suggests, they are the network entry points into Træfɪk (listening port, SSL, traffic redirection...).
- Traffic is then forwared to a matching [frontend](#frontends). A frontend defines routes from [entrypoints](#entrypoints) to [backends](#backends).
Routes are created using requests fields (`Host`, `Path`, `Headers`...) and can match or not a request.
- The [frontend](#frontends) will then send the request to a [backend](#backends). A backend can be composed by one or more [servers](#servers), and by a load-balancing strategy.
- Finally, the [server](#servers) will forward the request to the corresponding microservice in the private network.
## Entrypoints
Entrypoints are the network entry points into Træfɪk.
They can be defined using:
- a port (80, 443...)
- SSL (Certificates. Keys...)
- redirection to another entrypoint (redirect `HTTP` to `HTTPS`)
Here is an example of entrypoints definition:
```toml
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
certFile = "tests/traefik.crt"
keyFile = "tests/traefik.key"
```
- Two entrypoints are defined `http` and `https`.
- `http` listens on port `80` et `https` on port `443`.
- We enable SSL en `https` by giving a certificate and a key.
- We also redirect all the traffic from entrypoint `http` to `https`.
## Frontends
A frontend is a set of rules that forwards the incoming traffic from an entrypoint to a backend.
Frontends can be defined using the following rules:
- `Headers: Content-Type, application/json`: Headers adds a matcher for request header values. It accepts a sequence of key/value pairs to be matched.
- `HeadersRegexp: Content-Type, application/(text|json)`: Regular expressions can be used with headers as well. It accepts a sequence of key/value pairs, where the value has regex support.
- `Host: traefik.io, www.traefik.io`: Match request host with given host list.
- `HostRegexp: traefik.io, {subdomain:[a-z]+}.traefik.io`: Adds a matcher for the URL hosts. It accepts templates with zero or more URL variables enclosed by `{}`. Variables can define an optional regexp pattern to be matched.
- `Method: GET, POST, PUT`: Method adds a matcher for HTTP methods. It accepts a sequence of one or more methods to be matched.
- `Path: /products/, /articles/{category}/{id:[0-9]+}`: Path adds a matcher for the URL paths. It accepts templates with zero or more URL variables enclosed by `{}`.
- `PathStrip`: Same as `Path` but strip the given prefix from the request URL's Path.
- `PathPrefix`: PathPrefix adds a matcher for the URL path prefixes. 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.
You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
Here is an example of frontends definition:
```toml
[frontends]
[frontends.frontend1]
backend = "backend2"
[frontends.frontend1.routes.test_1]
rule = "Host: test.localhost, test2.localhost"
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host: localhost, {subdomain:[a-z]+}.localhost"
[frontends.frontend3]
backend = "backend2"
rule = "Path:/test"
```
- Three frontends are defined: `frontend1`, `frontend2` and `frontend3`
- `frontend1` will forward the traffic to the `backend2` if the rule `Host: test.localhost, test2.localhost` is matched
- `frontend2` will forward the traffic to the `backend1` if the rule `Host: localhost, {subdomain:[a-z]+}.localhost` is matched (forwarding client `Host` header to the backend)
- `frontend3` will forward the traffic to the `backend2` if the rule `Path:/test` is matched
## Backends
A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers.
Various methods of load-balancing is supported:
- `wrr`: Weighted Round Robin
- `drr`: Dynamic Round Robin: increases weights on servers that perform better than others. It also rolls back to original weights if the servers have changed.
A circuit breaker can also be applied to a backend, preventing high loads on failing servers.
Initial state is Standby. CB observes the statistics and does not modify the request.
In case if condition matches, CB enters Tripped state, where it responds with predefines code or redirects to another frontend.
Once Tripped timer expires, CB enters Recovering state and resets all stats.
In case if the condition does not match and recovery timer expires, CB enters Standby state.
It can be configured using:
- Methods: `LatencyAtQuantileMS`, `NetworkErrorRatio`, `ResponseCodeRatio`
- Operators: `AND`, `OR`, `EQ`, `NEQ`, `LT`, `LE`, `GT`, `GE`
For example:
- `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window for a frontend
- `LatencyAtQuantileMS(50.0) > 50`: watch latency at quantile in milliseconds.
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in range [500-600) to [0-600)
To proactively prevent backends from being overwhelmed with high load, a maximum connection limit can
also be applied to each backend.
Maximum connections can be configured by specifying an integer value for `maxconn.amount` and
`maxconn.extractorfunc` which is a strategy used to determine how to categorize requests in order to
evaluate the maximum connections.
For example:
```toml
[backends]
[backends.backend1]
[backends.backend1.maxconn]
amount = 10
extractorfunc = "request.host"
```
- `backend1` will return `HTTP code 429 Too Many Requests` if there are already 10 requests in progress for the same Host header.
- Another possible value for `extractorfunc` is `client.ip` which will categorize requests based on client source ip.
- Lastly `extractorfunc` can take the value of `request.header.ANY_HEADER` which will categorize requests based on `ANY_HEADER` that you provide.
## Servers
Servers are simply defined using a `URL`. You can also apply a custom `weight` to each server (this will be used by load-balacning).
Here is an example of backends and servers definition:
```toml
[backends]
[backends.backend1]
[backends.backend1.circuitbreaker]
expression = "NetworkErrorRatio() > 0.5"
[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.LoadBalancer]
method = "drr"
[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
```
- Two backends are defined: `backend1` and `backend2`
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1` using default `wrr` load-balancing strategy.
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2` using `drr` load-balancing strategy.
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
# Launch
Træfɪk can be configured using a TOML file configuration, arguments, or both.
By default, Træfɪk will try to find a `traefik.toml` in the following places:
- `/etc/traefik/`
- `$HOME/.traefik/`
- `.` *the working directory*
You can override this by setting a `configFile` argument:
```bash
$ traefik --configFile=foo/bar/myconfigfile.toml
```
Træfɪk uses the following precedence order. Each item takes precedence over the item below it:
- arguments
- configuration file
- default
It means that arguments overrides configuration file.
Each argument is described in the help section:
```bash
$ traefik --help
```

213
docs/benchmarks.md Normal file
View File

@@ -0,0 +1,213 @@
# Benchmarks
## Configuration
I would like to thanks [vincentbernat](https://github.com/vincentbernat) from [exoscale.ch](https://www.exoscale.ch) who kindly provided the infrastructure needed for the benchmarks.
I used 4 VMs for the tests with the following configuration:
- 32 GB RAM
- 8 CPU Cores
- 10 GB SSD
- Ubuntu 14.04 LTS 64-bit
## Setup
1. One VM used to launch the benchmarking tool [wrk](https://github.com/wg/wrk)
2. One VM for traefik (v1.0.0-beta.416) / nginx (v1.4.6)
3. Two VMs for 2 backend servers in go [whoami](https://github.com/emilevauge/whoamI/)
Each VM has been tuned using the following limits:
```bash
sysctl -w fs.file-max="9999999"
sysctl -w fs.nr_open="9999999"
sysctl -w net.core.netdev_max_backlog="4096"
sysctl -w net.core.rmem_max="16777216"
sysctl -w net.core.somaxconn="65535"
sysctl -w net.core.wmem_max="16777216"
sysctl -w net.ipv4.ip_local_port_range="1025 65535"
sysctl -w net.ipv4.tcp_fin_timeout="30"
sysctl -w net.ipv4.tcp_keepalive_time="30"
sysctl -w net.ipv4.tcp_max_syn_backlog="20480"
sysctl -w net.ipv4.tcp_max_tw_buckets="400000"
sysctl -w net.ipv4.tcp_no_metrics_save="1"
sysctl -w net.ipv4.tcp_syn_retries="2"
sysctl -w net.ipv4.tcp_synack_retries="2"
sysctl -w net.ipv4.tcp_tw_recycle="1"
sysctl -w net.ipv4.tcp_tw_reuse="1"
sysctl -w vm.min_free_kbytes="65536"
sysctl -w vm.overcommit_memory="1"
ulimit -n 9999999
```
### Nginx
Here is the config Nginx file use `/etc/nginx/nginx.conf`:
```
user www-data;
worker_processes auto;
worker_rlimit_nofile 200000;
pid /var/run/nginx.pid;
events {
worker_connections 10000;
use epoll;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 300;
keepalive_requests 10000;
types_hash_max_size 2048;
open_file_cache max=200000 inactive=300s;
open_file_cache_valid 300s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
server_tokens off;
dav_methods off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log combined;
error_log /var/log/nginx/error.log warn;
gzip off;
gzip_vary off;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*.conf;
}
```
Here is the Nginx vhost file used:
```
upstream whoami {
server IP-whoami1:80;
server IP-whoami2:80;
keepalive 300;
}
server {
listen 8001;
server_name test.traefik;
access_log off;
error_log /dev/null crit;
if ($host != "test.traefik") {
return 404;
}
location / {
proxy_pass http://whoami;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Forwarded-Host $host;
}
}
```
### Traefik
Here is the `traefik.toml` file used:
```
MaxIdleConnsPerHost = 100000
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
[file]
[backends]
[backends.backend1]
[backends.backend1.servers.server1]
url = "http://IP-whoami1:80"
weight = 1
[backends.backend1.servers.server2]
url = "http://IP-whoami2:80"
weight = 1
[frontends]
[frontends.frontend1]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Host: test.traefik"
```
## Results
### whoami:
```
wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench
Running 1m test @ http://IP-whoami:80/bench
20 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 70.28ms 134.72ms 1.91s 89.94%
Req/Sec 2.92k 742.42 8.78k 68.80%
Latency Distribution
50% 10.63ms
75% 75.64ms
90% 205.65ms
99% 668.28ms
3476705 requests in 1.00m, 384.61MB read
Socket errors: connect 0, read 0, write 0, timeout 103
Requests/sec: 57894.35
Transfer/sec: 6.40MB
```
### nginx:
```
wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-nginx:8001/bench
Running 1m test @ http://IP-nginx:8001/bench
20 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 101.25ms 180.09ms 1.99s 89.34%
Req/Sec 1.69k 567.69 9.39k 72.62%
Latency Distribution
50% 15.46ms
75% 129.11ms
90% 302.44ms
99% 846.59ms
2018427 requests in 1.00m, 298.36MB read
Socket errors: connect 0, read 0, write 0, timeout 90
Requests/sec: 33591.67
Transfer/sec: 4.97MB
```
### traefik:
```
wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench
Running 1m test @ http://IP-traefik:8000/bench
20 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 91.72ms 150.43ms 2.00s 90.50%
Req/Sec 1.43k 266.37 2.97k 69.77%
Latency Distribution
50% 19.74ms
75% 121.98ms
90% 237.39ms
99% 687.49ms
1705073 requests in 1.00m, 188.63MB read
Socket errors: connect 0, read 0, write 0, timeout 7
Requests/sec: 28392.44
Transfer/sec: 3.14MB
```
## Conclusion
Traefik is obviously slower than Nginx, but not so much: Traefik can serve 28392 requests/sec and Nginx 33591 requests/sec which gives a ratio of 85%.
Not bad for young project :) !
Some areas of possible improvements:
- Use [GO_REUSEPORT](https://github.com/kavu/go_reuseport) listener
- Run a separate server instance per CPU core with `GOMAXPROCS=1` (it appears during benchmarks that there is a lot more context switches with traefik than with nginx)

61
docs/css/traefik.css Normal file
View File

@@ -0,0 +1,61 @@
a {
color: #37ABC8;
text-decoration: none;
}
a:hover, a:focus {
color: #25606F;
text-decoration: underline;
}
h1, h2, h3, H4 {
color: #37ABC8;
}
.navbar-default {
background-color: #37ABC8;
border-color: #25606F;
}
.navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:hover, .navbar-default .navbar-nav>.active>a:focus {
color: #fff;
background-color: #25606F;
}
.navbar-default .navbar-nav>li>a:hover, .navbar-default .navbar-nav>li>a:focus {
color: #fff;
background-color: #25606F;
}
.navbar-default .navbar-toggle {
border-color: #25606F;
}
.navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus .navbar-toggle {
background-color: #25606F;
}
.navbar-default .navbar-collapse, .navbar-default .navbar-form {
border-color: #25606F;
}
blockquote p {
font-size: 14px;
}
.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:hover, .navbar-default .navbar-nav>.open>a:focus {
color: #fff;
background-color: #25606F;
}
.dropdown-menu>li>a:hover, .dropdown-menu>li>a:focus {
color: #fff;
text-decoration: none;
background-color: #25606F;
}
.dropdown-menu>.active>a, .dropdown-menu>.active>a:hover, .dropdown-menu>.active>a:focus {
color: #fff;
text-decoration: none;
background-color: #25606F;
outline: 0;
}

BIN
docs/img/architecture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

2407
docs/img/architecture.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 100 KiB

BIN
docs/img/internal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="337.37802"
height="107.921"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="letsencrypt-logo-horizontal.svg">
<metadata
id="metadata37">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs35" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="640"
inkscape:window-height="480"
id="namedview33"
showgrid="false"
fit-margin-bottom="30"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
inkscape:zoom="0.72861357"
inkscape:cx="168.57"
inkscape:cy="69.027001"
inkscape:window-x="0"
inkscape:window-y="30"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<g
id="g4"
transform="translate(-0.930001,-1.606)">
<title
id="title6">Layer 1</title>
<g
id="svg_1">
<g
id="svg_2">
<g
id="svg_3">
<path
id="svg_4"
d="m 76.621002,68.878998 0,-31.406998 7.629997,0 0,24.796997 12.153999,0 0,6.609001 -19.783997,0 0,9.99e-4 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_5"
d="m 121.547,58.098999 c 0,0.295998 0,0.592003 0,0.888 0,0.295997 -0.015,0.576004 -0.044,0.843002 l -16.01301,0 c 0.059,0.620995 0.244,1.182999 0.555,1.685997 0.311,0.502998 0.71,0.938004 1.197,1.308998 0.488,0.370003 1.035,0.658005 1.642,0.864006 0.605,0.208 1.234,0.310997 1.885,0.310997 1.153,0 2.13,-0.213997 2.928,-0.642998 0.799,-0.429001 1.449,-0.983002 1.952,-1.664001 l 5.05699,3.194 c -1.03498,1.507996 -2.40199,2.668999 -4.10299,3.482002 -1.701,0.811996 -3.676,1.219994 -5.922,1.219994 -1.657,0 -3.224,-0.259995 -4.702,-0.775993 -1.479,-0.518005 -2.772,-1.271004 -3.882,-2.263 -1.108,-0.990005 -1.981,-2.210007 -2.616996,-3.659004 -0.635994,-1.448997 -0.953003,-3.104996 -0.953003,-4.969002 0,-1.802994 0.309998,-3.437996 0.931,-4.900997 0.620999,-1.463001 1.463999,-2.706001 2.528999,-3.726002 1.064,-1.021 2.32,-1.811996 3.771,-2.373997 1.448,-0.561001 3.016,-0.843002 4.701,-0.843002 1.626,0 3.12,0.274002 4.48,0.820999 1.36,0.546997 2.528,1.338001 3.505,2.373001 0.976,1.035 1.73599,2.292 2.284,3.771 0.546,1.478001 0.819,3.165001 0.819,5.056 z m -6.698,-2.794998 c 0,-1.153 -0.362,-2.144001 -1.087,-2.972 -0.725,-0.827 -1.812,-1.242001 -3.26,-1.242001 -0.71,0 -1.36,0.111 -1.952,0.333 -0.59199,0.222 -1.108,0.525002 -1.553,0.909 -0.443,0.384998 -0.798,0.835999 -1.064,1.354 -0.266,0.517998 -0.414,1.057999 -0.443,1.618 l 9.359,0 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_6"
d="m 133.168,52.200001 0,8.461002 c 0,1.038994 0.2,1.816994 0.60001,2.337997 0.39799,0.519997 1.11499,0.778 2.151,0.778 0.35399,0 0.73098,-0.028 1.13099,-0.089 0.39901,-0.05901 0.73101,-0.147003 0.998,-0.266006 l 0.089,5.323006 c -0.50299,0.176994 -1.13899,0.332001 -1.90699,0.465996 -0.76999,0.133003 -1.538,0.199005 -2.307,0.199005 -1.479,0 -2.722,-0.186005 -3.727,-0.556007 C 129.19,68.484002 128.384,67.949998 127.77901,67.252 127.172,66.556001 126.73599,65.725999 126.47,64.762002 126.203,63.799005 126.071,62.724 126.071,61.538003 l 0,-9.338001 -3.549,0 0,-5.412003 3.504,0 0,-5.810997 7.142,0 0,5.810997 5.19,0 0,5.412003 -5.19,0 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_7"
d="m 161.91299,53.307999 c -0.59201,-0.560997 -1.28601,-1.034 -2.085,-1.418999 -0.79801,-0.383999 -1.64099,-0.577 -2.528,-0.577 -0.681,0 -1.30899,0.133999 -1.885,0.398998 -0.57699,0.267002 -0.865,0.726002 -0.865,1.375 0,0.621002 0.317,1.064003 0.953,1.331001 0.636,0.266998 1.664,0.562 3.08299,0.887001 0.82801,0.177998 1.664,0.43 2.50701,0.754997 0.843,0.324997 1.604,0.754005 2.28399,1.286003 0.68001,0.531998 1.22701,1.182999 1.64202,1.951996 0.41299,0.769005 0.62098,1.686005 0.62098,2.75 0,1.391006 -0.28099,2.565002 -0.84298,3.526001 -0.56201,0.960999 -1.29401,1.737 -2.19602,2.329002 -0.902,0.592002 -1.91499,1.019997 -3.03799,1.286003 -1.12399,0.266998 -2.248,0.398994 -3.371,0.398994 -1.80499,0 -3.571,-0.287994 -5.302,-0.864998 C 149.161,68.146002 147.719,67.294996 146.566,66.170995 l 4.08099,-4.303001 c 0.649,0.710007 1.448,1.302002 2.395,1.774002 0.946,0.473999 1.952,0.709999 3.017,0.709999 0.592,0 1.176,-0.140999 1.752,-0.421997 0.577,-0.279999 0.86501,-0.776001 0.86501,-1.485001 0,-0.681 -0.35401,-1.182999 -1.06401,-1.509003 -0.71,-0.324997 -1.818,-0.664993 -3.327,-1.020996 -0.769,-0.177002 -1.53799,-0.413002 -2.30699,-0.709 -0.77001,-0.295998 -1.457,-0.694 -2.06202,-1.197998 -0.60598,-0.502007 -1.10199,-1.123001 -1.48599,-1.863007 -0.384,-0.737995 -0.576,-1.625996 -0.576,-2.660995 0,-1.331001 0.28,-2.462002 0.843,-3.394001 0.562,-0.931999 1.286,-1.692001 2.174,-2.284 0.88701,-0.591999 1.87001,-1.027 2.949,-1.308998 1.079,-0.281998 2.151,-0.422001 3.217,-0.422001 1.655,0 3.274,0.259998 4.856,0.776001 1.582,0.517998 2.921,1.293999 4.015,2.328999 l -3.995,4.127998 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_8"
d="m 179.56799,68.878998 0,-31.406998 21.114,0 0,6.388 -13.795,0 0,5.944 13.041,0 0,6.077 -13.041,0 0,6.521 14.594,0 0,6.476997 -21.913,0 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_9"
d="m 220.675,68.878998 0,-12.065994 c 0,-0.621002 -0.053,-1.212002 -0.155,-1.774002 -0.104,-0.562 -0.274,-1.057003 -0.511,-1.486 -0.237,-0.428001 -0.569,-0.769001 -0.998,-1.021 -0.429,-0.25 -0.96899,-0.377003 -1.619,-0.377003 -0.65001,0 -1.22,0.127003 -1.70799,0.377003 -0.487,0.251999 -0.89501,0.599998 -1.22001,1.042999 -0.32499,0.443001 -0.569,0.953999 -0.731,1.529999 -0.16299,0.577 -0.244,1.175999 -0.244,1.797001 l 0,11.976997 -7.319,0 0,-22.091 7.05301,0 0,3.061001 0.089,0 c 0.26699,-0.473 0.613,-0.938 1.043,-1.396 0.428,-0.459 0.932,-0.850998 1.50801,-1.175999 0.57699,-0.325001 1.20498,-0.591999 1.88598,-0.799 0.68001,-0.206001 1.40401,-0.311001 2.17301,-0.311001 1.479,0 2.735,0.266998 3.77099,0.799 1.036,0.532002 1.87001,1.220001 2.50701,2.062 0.636,0.842999 1.09401,1.812 1.375,2.904999 0.28,1.095001 0.421,2.189003 0.421,3.283001 l 0,13.661999 -7.321,0 0,9.99e-4 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_10"
d="m 246.71301,53.929001 c -0.41501,-0.532001 -0.977,-0.959999 -1.686,-1.285999 -0.70999,-0.325001 -1.43601,-0.488003 -2.174,-0.488003 -0.77,0 -1.464,0.155003 -2.085,0.466 -0.62101,0.310997 -1.153,0.726002 -1.59701,1.242001 -0.44299,0.518002 -0.79199,1.117001 -1.04299,1.797001 -0.251,0.681004 -0.377,1.404003 -0.377,2.174 0,0.768997 0.11799,1.493004 0.35499,2.173004 0.23601,0.681 0.58301,1.279999 1.04201,1.796997 0.45799,0.517998 1.005,0.924995 1.642,1.220001 0.636,0.295998 1.35299,0.443001 2.151,0.443001 0.73801,0 1.47099,-0.139999 2.19501,-0.421005 0.72401,-0.281006 1.30899,-0.687996 1.75198,-1.220001 l 4.03702,4.924004 c -0.91703,0.887001 -2.10102,1.582001 -3.54901,2.084999 -1.44899,0.501999 -2.987,0.753998 -4.61299,0.753998 -1.74501,0 -3.37401,-0.266998 -4.88701,-0.798996 -1.512,-0.531998 -2.82601,-1.308998 -3.941,-2.329002 -1.11599,-1.019997 -1.99299,-2.253998 -2.63299,-3.702995 -0.64,-1.448997 -0.959,-3.090004 -0.959,-4.924004 0,-1.804001 0.31898,-3.431 0.959,-4.880001 0.64,-1.447998 1.51699,-2.683998 2.63299,-3.703999 1.11499,-1.021 2.43,-1.804001 3.941,-2.351002 1.513,-0.546997 3.127,-0.820999 4.843,-0.820999 0.798,0 1.589,0.074 2.373,0.223 0.783,0.147003 1.53699,0.348 2.26199,0.599003 0.72501,0.251003 1.39002,0.562 1.996,0.931999 0.60599,0.369999 1.13202,0.776001 1.57502,1.219997 l -4.21201,4.877003 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_11"
d="m 268.03201,52.776001 c -0.32599,-0.089 -0.64401,-0.146999 -0.95401,-0.177002 -0.30999,-0.03 -0.61398,-0.045 -0.90899,-0.045 -0.97599,0 -1.797,0.177998 -2.46201,0.530998 -0.66498,0.354 -1.19699,0.781002 -1.59698,1.283001 -0.39902,0.500999 -0.68802,1.047001 -0.86503,1.636997 -0.177,0.589996 -0.26599,1.105003 -0.26599,1.548004 l 0,11.324997 -7.27499,0 0,-22.063999 7.009,0 0,3.194 0.089,0 c 0.56201,-1.132 1.35901,-2.055 2.396,-2.77 1.03402,-0.715 2.23202,-1.071999 3.59302,-1.071999 0.29498,0 0.58398,0.016 0.86499,0.045 0.27999,0.029 0.51001,0.074 0.68801,0.133003 L 268.03201,52.776 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_12"
d="m 285.12201,72.206001 c -0.44299,1.153 -0.939,2.181 -1.48599,3.083 -0.547,0.901001 -1.19702,1.669998 -1.95102,2.306999 -0.754,0.636002 -1.642,1.114998 -2.66199,1.441002 -1.01999,0.324997 -2.22601,0.487999 -3.61499,0.487999 -0.681,0 -1.38299,-0.045 -2.10602,-0.134003 -0.72598,-0.089 -1.354,-0.207001 -1.88598,-0.353996 L 272.215,72.916 c 0.354,0.116997 0.746,0.213997 1.17602,0.288002 0.42798,0.073 0.81998,0.110001 1.17499,0.110001 1.12399,0 1.93701,-0.259003 2.44,-0.776001 0.50199,-0.518005 0.931,-1.249001 1.28601,-2.195 l 0.70999,-1.818001 -9.22699,-21.736 8.073,0 4.92398,14.195 0.133,0 4.392,-14.195 7.71802,0 -9.89301,25.417 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_13"
d="m 321.496,57.745003 c 0,1.537994 -0.237,3.016998 -0.70999,4.435997 -0.474,1.419998 -1.16101,2.668999 -2.06201,3.748001 -0.90201,1.080002 -2.004,1.945 -3.30499,2.596001 -1.30201,0.649002 -2.78,0.975998 -4.43702,0.975998 -1.35998,0 -2.64599,-0.273003 -3.85901,-0.82 -1.21301,-0.546997 -2.15799,-1.293999 -2.83898,-2.239998 l -0.088,0 0,13.085999 -7.27502,0 0,-32.739002 6.92001,0 0,2.706001 0.133,0 c 0.681,-0.887001 1.61899,-1.662998 2.81698,-2.328999 C 307.98801,46.5 309.39999,46.167 311.02701,46.167 c 1.59698,0 3.04498,0.311001 4.34698,0.931999 1.301,0.621002 2.40201,1.464001 3.305,2.528 0.90298,1.063999 1.59701,2.299999 2.08502,3.704002 0.488,1.404999 0.73199,2.876999 0.73199,4.414001 z m -7.05301,0 c 0,-0.709999 -0.11001,-1.403999 -0.332,-2.085003 -0.22201,-0.68 -0.548,-1.278999 -0.97699,-1.797001 -0.42901,-0.516998 -0.96902,-0.938 -1.61902,-1.264 -0.64999,-0.326 -1.40399,-0.487999 -2.26199,-0.487999 -0.828,0 -1.56799,0.162998 -2.21799,0.487999 -0.651,0.325001 -1.20602,0.754002 -1.664,1.285999 -0.45901,0.532001 -0.81302,1.139 -1.06402,1.818001 -0.25199,0.681004 -0.37699,1.375004 -0.37699,2.085003 0,0.709999 0.125,1.404999 0.37699,2.084999 0.251,0.681 0.60501,1.285995 1.06402,1.818001 0.45798,0.531998 1.013,0.961998 1.664,1.286995 0.64899,0.325005 1.38999,0.487 2.21799,0.487 0.85699,0 1.61099,-0.161995 2.26199,-0.487 0.651,-0.325005 1.19001,-0.754997 1.61902,-1.286995 0.42902,-0.531998 0.75498,-1.146004 0.97699,-1.841003 0.22101,-0.693001 0.332,-1.394997 0.332,-2.104996 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_14"
d="m 333.11801,52.200001 0,8.461002 c 0,1.038994 0.20001,1.816994 0.60001,2.337997 0.39798,0.519997 1.11499,0.778 2.151,0.778 0.354,0 0.73099,-0.028 1.13098,-0.089 0.39902,-0.05901 0.73102,-0.147003 0.99802,-0.266006 l 0.089,5.323006 c -0.50299,0.176994 -1.139,0.332001 -1.90698,0.465996 -0.77002,0.133003 -1.53802,0.199005 -2.307,0.199005 -1.47901,0 -2.72202,-0.186005 -3.72702,-0.556007 -1.00599,-0.369995 -1.81199,-0.903999 -2.417,-1.601997 -0.60699,-0.695999 -1.043,-1.526001 -1.30899,-2.489998 C 326.15302,63.799005 326.021,62.724 326.021,61.538003 l 0,-9.338001 -3.54898,0 0,-5.412003 3.50399,0 0,-5.810997 7.142,0 0,5.810997 5.19,0 0,5.412003 -5.19,0 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
</g>
</g>
<path
id="svg_15"
d="m 145.00999,36.869999 c -2.18299,0 -3.89199,1.573002 -3.89199,3.582001 0,2.116001 1.43899,3.536999 3.582,3.536999 0.183,0 0.35599,-0.017 0.51899,-0.05 -0.343,1.566002 -1.852,2.690002 -3.27799,2.915001 l -0.29001,0.046 0,3.376999 0.376,-0.036 c 1.73,-0.165001 3.439,-0.951 4.691,-2.157001 1.632,-1.572998 2.49501,-3.843998 2.49501,-6.568001 0,-2.691998 -1.76799,-4.646 -4.20301,-4.646 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
</g>
<g
id="svg_16">
<path
id="svg_17"
d="m 46.488998,37.568001 -8.039997,0 0,-4.128002 c 0,-3.296997 -2.683002,-5.979 -5.98,-5.979 -3.297001,0 -5.979,2.683002 -5.979,5.979 l 0,4.128002 -8.040001,0 0,-4.128002 c 0,-7.73 6.288998,-14.019999 14.02,-14.019999 7.731002,0 14.02,6.289 14.02,14.019999 l 0,4.128002 -0.001,0 z"
inkscape:connector-curvature="0"
style="fill:#f9a11d" />
</g>
<path
id="svg_18"
d="m 49.731998,37.568001 -34.524998,0 c -1.474001,0 -2.68,1.205997 -2.68,2.68 l 0,25.540001 c 0,1.473999 1.205999,2.68 2.68,2.68 l 34.524998,0 c 1.474003,0 2.68,-1.206001 2.68,-2.68 l 0,-25.540001 c 0,-1.474003 -1.205997,-2.68 -2.68,-2.68 z m -15.512997,16.769001 0,3.460995 c 0,0.966003 -0.784,1.749001 -1.749001,1.749001 -0.965001,0 -1.749001,-0.783997 -1.749001,-1.749001 l 0,-3.459995 c -1.076,-0.611 -1.803001,-1.764 -1.803001,-3.09 0,-1.962002 1.591,-3.552002 3.552002,-3.552002 1.961998,0 3.551998,1.591 3.551998,3.552002 0,1.325001 -0.727001,2.478001 -1.802998,3.089001 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_19"
d="m 11.707001,33.759998 -8.331,0 c -1.351001,0 -2.446,-1.094997 -2.446,-2.445999 0,-1.351002 1.094999,-2.445999 2.446,-2.445999 l 8.331,0 c 1.351,0 2.445999,1.095001 2.445999,2.445999 0,1.350998 -1.096001,2.445999 -2.445999,2.445999 z"
inkscape:connector-curvature="0"
style="fill:#f9a11d" />
<path
id="svg_20"
d="m 17.575001,20.655001 c -0.546001,0 -1.097,-0.182001 -1.552,-0.557001 l -6.59,-5.418999 C 8.39,13.820999 8.239001,12.280001 9.098,11.236 9.956,10.193001 11.497,10.042 12.541001,10.9 l 6.59,5.419001 c 1.042999,0.858 1.194,2.399 0.334999,3.442999 -0.483,0.589001 -1.184,0.893002 -1.890999,0.893002 z"
inkscape:connector-curvature="0"
style="fill:#f9a11d" />
<path
id="svg_21"
d="m 32.469002,14.895 c -1.351002,0 -2.446003,-1.095001 -2.446003,-2.446001 l 0,-8.396999 c 0,-1.351 1.095001,-2.446 2.446003,-2.446 1.351002,0 2.445999,1.095 2.445999,2.446 l 0,8.396999 c 0,1.351 -1.095001,2.446001 -2.445999,2.446001 z"
inkscape:connector-curvature="0"
style="fill:#f9a11d" />
<g
id="svg_22">
<g
id="svg_23">
<path
id="svg_24"
d="M 47.362999,20.655001 C 46.655998,20.655001 45.956001,20.351 45.472,19.761999 44.613998,18.719 44.764,17.177 45.806999,16.319 l 6.59,-5.419001 c 1.044003,-0.858 2.585003,-0.706999 3.442997,0.336 0.858002,1.042999 0.708,2.584999 -0.334999,3.443001 l -6.589996,5.418999 C 48.459999,20.472999 47.91,20.655 47.362999,20.655 z"
inkscape:connector-curvature="0"
style="fill:#f9a11d" />
</g>
</g>
<path
id="svg_25"
d="m 61.563004,33.759998 -8.410004,0 c -1.351002,0 -2.445999,-1.094997 -2.445999,-2.445999 0,-1.351002 1.094997,-2.445999 2.445999,-2.445999 l 8.410004,0 c 1.350998,0 2.445999,1.095001 2.445999,2.445999 0,1.350998 -1.095001,2.445999 -2.445999,2.445999 z"
inkscape:connector-curvature="0"
style="fill:#f9a11d" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

5394
docs/img/overview.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 218 KiB

BIN
docs/img/traefik.icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
docs/img/traefik.logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because it is too large Load Diff

933
docs/toml.md Normal file
View File

@@ -0,0 +1,933 @@
# Global configuration
## Main section
```toml
# traefik.toml
################################################################
# Global configuration
################################################################
# Traefik logs file
# If not defined, logs to stdout
#
# Optional
#
# traefikLogsFile = "log/traefik.log"
# Access logs file
#
# Optional
#
# accessLogsFile = "log/access.log"
# Log level
#
# Optional
# Default: "ERROR"
#
# logLevel = "ERROR"
# 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.
#
# Optional
# Default: "2s"
#
# ProvidersThrottleDuration = "5s"
# If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used.
# If you encounter 'too many open files' errors, you can either change this value, or change `ulimit` value.
#
# Optional
# Default: http.DefaultMaxIdleConnsPerHost
#
# MaxIdleConnsPerHost = 200
# Entrypoints to be used by frontends that do not specify any entrypoint.
# Each frontend can specify its own entrypoints.
#
# Optional
# Default: ["http"]
#
# defaultEntryPoints = ["http", "https"]
```
## Entrypoints definition
```toml
# Entrypoints definition
#
# Optional
# Default:
# [entryPoints]
# [entryPoints.http]
# address = ":80"
#
# To redirect an http entrypoint to an https entrypoint (with SNI support):
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# [entryPoints.http.redirect]
# entryPoint = "https"
# [entryPoints.https]
# address = ":443"
# [entryPoints.https.tls]
# [[entryPoints.https.tls.certificates]]
# CertFile = "integration/fixtures/https/snitest.com.cert"
# KeyFile = "integration/fixtures/https/snitest.com.key"
# [[entryPoints.https.tls.certificates]]
# CertFile = "integration/fixtures/https/snitest.org.cert"
# KeyFile = "integration/fixtures/https/snitest.org.key"
#
# To redirect an entrypoint rewriting the URL:
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# [entryPoints.http.redirect]
# regex = "^http://localhost/(.*)"
# replacement = "http://mydomain/$1"
[entryPoints]
[entryPoints.http]
address = ":80"
```
## Retry configuration
```toml
# Enable retry sending request if network error
#
# Optional
#
[retry]
# Number of attempts
#
# Optional
# Default: (number servers in backend) -1
#
# attempts = 3
# Sets the maximum request body to be stored in memory in Mo
#
# Optional
# Default: 2
#
# maxMem = 3
```
## ACME (Let's Encrypt) configuration
```toml
# Enable ACME (Let's Encrypt): automatic SSL
#
# Optional
#
[acme]
# Email address used for registration
#
# Required
#
email = "test@traefik.io"
# File used for certificates storage.
# WARNING, if you use Traefik in Docker, don't forget to mount this file as a volume.
#
# Required
#
storageFile = "acme.json"
# Entrypoint to proxy acme challenge to.
# WARNING, must point to an entrypoint on port 443
#
# Required
#
entryPoint = "https"
# Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate.
# WARNING, TLS handshakes will be slow when requesting a hostname certificate for the first time, this can leads to DoS attacks.
# WARNING, Take note that Let's Encrypt have rate limiting: https://community.letsencrypt.org/t/quick-start-guide/1631
#
# Optional
#
# onDemand = true
# CA server to use
# Uncomment the line to run on the staging let's encrypt server
# Leave comment to go to prod
#
# Optional
#
# caServer = "https://acme-staging.api.letsencrypt.org/directory"
# Domains list
# You can provide SANs (alternative domains) to each main domain
# WARNING, Take note that Let's Encrypt have rate limiting: https://community.letsencrypt.org/t/quick-start-guide/1631
# Each domain & SANs will lead to a certificate request.
#
# [[acme.domains]]
# main = "local1.com"
# sans = ["test1.local1.com", "test2.local1.com"]
# [[acme.domains]]
# main = "local2.com"
# sans = ["test1.local2.com", "test2x.local2.com"]
# [[acme.domains]]
# main = "local3.com"
# [[acme.domains]]
# main = "local4.com"
[[acme.domains]]
main = "local1.com"
sans = ["test1.local1.com", "test2.local1.com"]
[[acme.domains]]
main = "local3.com"
[[acme.domains]]
main = "local4.com"
```
# Configuration backends
## File backend
Like any other reverse proxy, Træfɪk can be configured with a file. You have two choices:
- simply add your configuration at the end of the global configuration file `traefik.toml` :
```toml
# traefik.toml
logLevel = "DEBUG"
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
CertFile = "integration/fixtures/https/snitest.com.cert"
KeyFile = "integration/fixtures/https/snitest.com.key"
[[entryPoints.https.tls.certificates]]
CertFile = "integration/fixtures/https/snitest.org.cert"
KeyFile = "integration/fixtures/https/snitest.org.key"
[file]
# rules
[backends]
[backends.backend1]
[backends.backend1.circuitbreaker]
expression = "NetworkErrorRatio() > 0.5"
[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.backend1.maxconn]
amount = 10
extractorfunc = "request.host"
[backends.backend2.LoadBalancer]
method = "drr"
[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]
rule = "Host:test.localhost"
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host:{subdomain:[a-z]+}.localhost"
[frontends.frontend3]
entrypoints = ["http", "https"] # overrides defaultEntryPoints
backend = "backend2"
rule = "Path:/test"
```
- or put your rules in a separate file, for example `rules.tml`:
```toml
# traefik.toml
logLevel = "DEBUG"
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
CertFile = "integration/fixtures/https/snitest.com.cert"
KeyFile = "integration/fixtures/https/snitest.com.key"
[[entryPoints.https.tls.certificates]]
CertFile = "integration/fixtures/https/snitest.org.cert"
KeyFile = "integration/fixtures/https/snitest.org.key"
[file]
filename = "rules.toml"
```
```toml
# rules.toml
[backends]
[backends.backend1]
[backends.backend1.circuitbreaker]
expression = "NetworkErrorRatio() > 0.5"
[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.backend1.maxconn]
amount = 10
extractorfunc = "request.host"
[backends.backend2.LoadBalancer]
method = "drr"
[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]
rule = "Host:test.localhost"
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host:{subdomain:[a-z]+}.localhost"
[frontends.frontend3]
entrypoints = ["http", "https"] # overrides defaultEntryPoints
backend = "backend2"
rule = "Path:/test"
```
If you want Træfɪk to watch file changes automatically, just add:
```toml
[file]
watch = true
```
## API backend
Træfik can be configured using a restful api.
To enable it:
```toml
[web]
address = ":8080"
# SSL certificate and key used
#
# Optional
#
# CertFile = "traefik.crt"
# KeyFile = "traefik.key"
#
# Set REST API to read-only mode
#
# Optional
# ReadOnly = false
```
- `/`: provides a simple HTML frontend of Træfik
![Web UI Providers](img/web.frontend.png)
![Web UI Health](img/traefik-health.png)
- `/health`: `GET` json metrics
```sh
$ curl -s "http://localhost:8080/health" | jq .
{
// Træfɪk PID
"pid": 2458,
// Træfɪk server uptime (formated time)
"uptime": "39m6.885931127s",
// Træfɪk server uptime in seconds
"uptime_sec": 2346.885931127,
// current server date
"time": "2015-10-07 18:32:24.362238909 +0200 CEST",
// current server date in seconds
"unixtime": 1444235544,
// count HTTP response status code in realtime
"status_code_count": {
"502": 1
},
// count HTTP response status code since Træfɪk started
"total_status_code_count": {
"200": 7,
"404": 21,
"502": 13
},
// count HTTP response
"count": 1,
// count HTTP response
"total_count": 41,
// sum of all response time (formated time)
"total_response_time": "35.456865605s",
// sum of all response time in seconds
"total_response_time_sec": 35.456865605,
// average response time (formated time)
"average_response_time": "864.8016ms",
// average response time in seconds
"average_response_time_sec": 0.8648016000000001
}
```
- `/api`: `GET` configuration for all providers
```sh
$ curl -s "http://localhost:8080/api" | jq .
{
"file": {
"frontends": {
"frontend2": {
"routes": {
"test_2": {
"rule": "Path:/test"
}
},
"backend": "backend1"
},
"frontend1": {
"routes": {
"test_1": {
"rule": "Host:test.localhost"
}
},
"backend": "backend2"
}
},
"backends": {
"backend2": {
"loadBalancer": {
"method": "drr"
},
"servers": {
"server2": {
"weight": 2,
"URL": "http://172.17.0.5:80"
},
"server1": {
"weight": 1,
"url": "http://172.17.0.4:80"
}
}
},
"backend1": {
"loadBalancer": {
"method": "wrr"
},
"circuitBreaker": {
"expression": "NetworkErrorRatio() > 0.5"
},
"servers": {
"server2": {
"weight": 1,
"url": "http://172.17.0.3:80"
},
"server1": {
"weight": 10,
"url": "http://172.17.0.2:80"
}
}
}
}
}
}
```
- `/api/providers`: `GET` providers
- `/api/providers/{provider}`: `GET` or `PUT` provider
- `/api/providers/{provider}/backends`: `GET` backends
- `/api/providers/{provider}/backends/{backend}`: `GET` a backend
- `/api/providers/{provider}/backends/{backend}/servers`: `GET` servers in a backend
- `/api/providers/{provider}/backends/{backend}/servers/{server}`: `GET` a server in a backend
- `/api/providers/{provider}/frontends`: `GET` frontends
- `/api/providers/{provider}/frontends/{frontend}`: `GET` a frontend
- `/api/providers/{provider}/frontends/{frontend}/routes`: `GET` routes in a frontend
- `/api/providers/{provider}/frontends/{frontend}/routes/{route}`: `GET` a route in a frontend
## Docker backend
Træfɪk can be configured to use Docker as a backend configuration:
```toml
################################################################
# Docker configuration backend
################################################################
# Enable Docker configuration backend
#
# Optional
#
[docker]
# Docker server endpoint. Can be a tcp or a unix socket endpoint.
#
# Required
#
endpoint = "unix:///var/run/docker.sock"
# Default domain used.
# Can be overridden by setting the "traefik.domain" label on a container.
#
# Required
#
domain = "docker.localhost"
# Enable watch docker changes
#
# Optional
#
watch = true
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "docker.tmpl"
# Enable docker TLS connection
#
# [docker.tls]
# ca = "/etc/ssl/ca.crt"
# cert = "/etc/ssl/docker.crt"
# key = "/etc/ssl/docker.key"
# insecureskipverify = true
```
Labels can be used on containers to override default behaviour:
- `traefik.backend=foo`: assign the container to `foo` backend
- `traefik.port=80`: register this port. Useful when the container exposes multiples ports.
- `traefik.protocol=https`: override the default `http` protocol
- `traefik.weight=10`: assign this weight to the container
- `traefik.enable=false`: disable this container in Træfɪk
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
* `traefik.domain=traefik.localhost`: override the default domain
## Marathon backend
Træfɪk can be configured to use Marathon as a backend configuration:
```toml
################################################################
# Mesos/Marathon configuration backend
################################################################
# Enable Marathon configuration backend
#
# Optional
#
[marathon]
# Marathon server endpoint.
# You can also specify multiple endpoint for Marathon:
# endpoint := "http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080"
#
# Required
#
endpoint = "http://127.0.0.1:8080"
# Enable watch Marathon changes
#
# Optional
#
watch = true
# Default domain used.
# Can be overridden by setting the "traefik.domain" label on an application.
#
# Required
#
domain = "marathon.localhost"
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "marathon.tmpl"
# Expose Marathon apps by default in traefik
#
# Optional
# Default: false
#
# ExposedByDefault = true
# Enable Marathon basic authentication
#
# Optional
#
# [marathon.basic]
# httpBasicAuthUser = "foo"
# httpBasicPassword = "bar"
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
#
# Optional
#
# [marathon.TLS]
# InsecureSkipVerify = true
```
Labels can be used on containers to override default behaviour:
- `traefik.backend=foo`: assign the application to `foo` backend
- `traefik.portIndex=1`: register port by index in the application's ports array. Useful when the application exposes multiple ports.
- `traefik.port=80`: register the explicit application port value. Cannot be used alongside `traefik.portIndex`.
- `traefik.protocol=https`: override the default `http` protocol
- `traefik.weight=10`: assign this weight to the application
- `traefik.enable=false`: disable this application in Træfɪk
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
* `traefik.domain=traefik.localhost`: override the default domain
## Consul backend
Træfɪk can be configured to use Consul as a backend configuration:
```toml
################################################################
# Consul KV configuration backend
################################################################
# Enable Consul KV configuration backend
#
# Optional
#
[consul]
# Consul server endpoint
#
# Required
#
endpoint = "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"
# Enable consul TLS connection
#
# Optional
#
# [consul.tls]
# ca = "/etc/ssl/ca.crt"
# cert = "/etc/ssl/consul.crt"
# key = "/etc/ssl/consul.key"
# insecureskipverify = true
```
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
## 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"
# Prefix for Consul catalog tags
#
# Optional
#
prefix = "traefik"
```
This backend will create routes matching on hostname based on the service name
used in consul.
Additional settings can be defined using Consul Catalog tags:
- ```traefik.enable=false```: disable this container in Træfɪk
- ```traefik.protocol=https```: override the default `http` protocol
- ```traefik.backend.weight=10```: assign this weight to the container
- ```traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5```
- ```traefik.backend.loadbalancer=drr```: override the default load balancing mode
- ```traefik.frontend.rule=Host:test.traefik.io```: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
- ```traefik.frontend.passHostHeader=true```: forward client `Host` header to the backend.
- ```traefik.frontend.entryPoints=http,https```: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
## Etcd backend
Træfɪk can be configured to use Etcd as a backend configuration:
```toml
################################################################
# Etcd configuration backend
################################################################
# Enable Etcd configuration backend
#
# Optional
#
[etcd]
# Etcd server endpoint
#
# Required
#
endpoint = "127.0.0.1:4001"
# Enable watch Etcd changes
#
# Optional
#
watch = true
# Prefix used for KV store.
#
# Optional
#
prefix = "/traefik"
# Override default configuration template. For advanced users :)
#
# 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
```
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
## Zookeeper backend
Træfɪk can be configured to use Zookeeper as a backend configuration:
```toml
################################################################
# Zookeeper configuration backend
################################################################
# Enable Zookeeperconfiguration backend
#
# Optional
#
[zookeeper]
# Zookeeper server endpoint
#
# Required
#
endpoint = "127.0.0.1:2181"
# Enable watch Zookeeper changes
#
# Optional
#
watch = true
# Prefix used for KV store.
#
# Optional
#
prefix = "/traefik"
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "zookeeper.tmpl"
```
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
## BoltDB backend
Træfɪk can be configured to use BoltDB as a backend configuration:
```toml
################################################################
# BoltDB configuration backend
################################################################
# Enable BoltDB configuration backend
#
# Optional
#
[boltdb]
# BoltDB file
#
# Required
#
endpoint = "/my.db"
# Enable watch BoltDB changes
#
# Optional
#
watch = true
# Prefix used for KV store.
#
# Optional
#
prefix = "/traefik"
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "boltdb.tmpl"
```
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
## Key-value storage structure
The Keys-Values structure should look (using `prefix = "/traefik"`):
- backend 1
| Key | Value |
|--------------------------------------------------------|-----------------------------|
| `/traefik/backends/backend1/circuitbreaker/expression` | `NetworkErrorRatio() > 0.5` |
| `/traefik/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik/backends/backend1/servers/server1/weight` | `10` |
| `/traefik/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
| `/traefik/backends/backend1/servers/server2/weight` | `1` |
- backend 2
| Key | Value |
|-----------------------------------------------------|------------------------|
| `/traefik/backends/backend2/maxconn/amount` | `10` |
| `/traefik/backends/backend2/maxconn/extractorfunc` | `request.host` |
| `/traefik/backends/backend2/loadbalancer/method` | `drr` |
| `/traefik/backends/backend2/servers/server1/url` | `http://172.17.0.4:80` |
| `/traefik/backends/backend2/servers/server1/weight` | `1` |
| `/traefik/backends/backend2/servers/server2/url` | `http://172.17.0.5:80` |
| `/traefik/backends/backend2/servers/server2/weight` | `2` |
- frontend 1
| Key | Value |
|---------------------------------------------------|-----------------------|
| `/traefik/frontends/frontend1/backend` | `backend2` |
| `/traefik/frontends/frontend1/routes/test_1/rule` | `Host:test.localhost` |
- frontend 2
| Key | Value |
|----------------------------------------------------|--------------|
| `/traefik/frontends/frontend2/backend` | `backend1` |
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path:/test` |
## Atomic configuration changes
The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. As a result, it may be possible for Træfɪk to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. To solve this problem, Træfɪk supports a special key called `/traefik/alias`. If set, Træfɪk use the value as an alternative key prefix.
Given the key structure below, Træfɪk will use the `http://172.17.0.2:80` as its only backend (frontend keys have been omitted for brevity).
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/1` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
When an atomic configuration change is required, you may write a new configuration at an alternative prefix. Here, although the `/traefik_configurations/2/...` keys have been set, the old configuration is still active because the `/traefik/alias` key still points to `/traefik_configurations/1`:
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/1` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` configuration becomes active atomically. Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://172.17.0.4:80` hosts while no traffic is sent to the `172.17.0.2:80` host:
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/2` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored.

View File

@@ -0,0 +1,98 @@
# Examples
You will find here some configuration examples of Træfɪk.
## HTTP only
```
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":80"
```
## HTTP + HTTPS (with SNI)
```
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
CertFile = "integration/fixtures/https/snitest.com.cert"
KeyFile = "integration/fixtures/https/snitest.com.key"
[[entryPoints.https.tls.certificates]]
CertFile = "integration/fixtures/https/snitest.org.cert"
KeyFile = "integration/fixtures/https/snitest.org.key"
```
## HTTP redirect on HTTPS
```
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
certFile = "tests/traefik.crt"
keyFile = "tests/traefik.key"
```
## Let's Encrypt support
```
[entryPoints]
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
# certs used as default certs
[[entryPoints.https.tls.certificates]]
certFile = "tests/traefik.crt"
keyFile = "tests/traefik.key"
[acme]
email = "test@traefik.io"
storageFile = "acme.json"
onDemand = true
caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
[[acme.domains]]
main = "local1.com"
sans = ["test1.local1.com", "test2.local1.com"]
[[acme.domains]]
main = "local2.com"
sans = ["test1.local2.com", "test2x.local2.com"]
[[acme.domains]]
main = "local3.com"
[[acme.domains]]
main = "local4.com"
```
## Override entrypoints in frontends
```
[frontends]
[frontends.frontend1]
backend = "backend2"
[frontends.frontend1.routes.test_1]
rule = "Host:test.localhost"
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host:{subdomain:[a-z]+}.localhost"
[frontends.frontend3]
entrypoints = ["http", "https"] # overrides defaultEntryPoints
backend = "backend2"
rule = "Path:/test"
```

170
docs/user-guide/swarm.md Normal file
View File

@@ -0,0 +1,170 @@
# Swarm cluster
This section explains how to create a multi-host [swarm](https://docs.docker.com/swarm) cluster using [docker-machine](https://docs.docker.com/machine/) and how to deploy Træfɪk on it.
The cluster will be made of:
- 2 servers
- 1 swarm master
- 2 swarm nodes
- 1 [overlay](https://docs.docker.com/engine/userguide/networking/dockernetworks/#an-overlay-network) network (multi-host networking)
## Prerequisites
1. You will need to install [docker-machine](https://docs.docker.com/machine/)
2. You will need the latest [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
## Cluster provisioning
We will first follow [this guide](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) to create the cluster.
### Create machine `mh-keystore`
This machine will be the service registry of our cluster.
```sh
docker-machine create -d virtualbox mh-keystore
```
Then we install the service registry [Consul](https://consul.io) on this machine:
```sh
eval "$(docker-machine env mh-keystore)"
docker run -d \
-p "8500:8500" \
-h "consul" \
progrium/consul -server -bootstrap
```
### Create machine `mhs-demo0`
This machine will have a swarm master and a swarm agent on it.
```sh
docker-machine create -d virtualbox \
--swarm --swarm-master \
--swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip mh-keystore):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
mhs-demo0
```
### Create machine `mhs-demo1`
This machine will have a swarm agent on it.
```sh
docker-machine create -d virtualbox \
--swarm \
--swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip mh-keystore):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
mhs-demo1
```
### Create the overlay Network
Create the overlay network on the swarm master:
```sh
eval $(docker-machine env --swarm mhs-demo0)
docker network create --driver overlay --subnet=10.0.9.0/24 my-net
```
## Deploy Træfɪk
Deploy Træfɪk:
```sh
docker $(docker-machine config mhs-demo0) run \
-d \
-p 80:80 -p 8080:8080 \
--net=my-net \
-v /var/lib/boot2docker/:/ssl \
traefik \
-l DEBUG \
-c /dev/null \
--docker \
--docker.domain traefik \
--docker.endpoint tcp://$(docker-machine ip mhs-demo0):3376 \
--docker.tls \
--docker.tls.ca /ssl/ca.pem \
--docker.tls.cert /ssl/server.pem \
--docker.tls.key /ssl/server-key.pem \
--docker.tls.insecureSkipVerify \
--docker.watch \
--web
```
Let's explain this command:
- `-p 80:80 -p 8080:8080`: we bind ports 80 and 8080
- `--net=my-net`: run the container on the network my-net
- `-v /var/lib/boot2docker/:/ssl`: mount the ssl keys generated by docker-machine
- `-c /dev/null`: empty config file
- `--docker`: enable docker backend
- `--docker.endpoint tcp://172.18.0.1:3376`: connect to the swarm master using the docker_gwbridge network
- `--docker.tls`: enable TLS using the docker-machine keys
- `--web`: activate the webUI on port 8080
## Deploy your apps
We can now deploy our app on the cluster, here [whoami](https://github.com/emilevauge/whoami), a simple web server in GO, on the network `my-net`:
```sh
eval $(docker-machine env --swarm mhs-demo0)
docker run -d --name=whoami0 --net=my-net --env="constraint:node==mhs-demo0" emilevauge/whoami
docker run -d --name=whoami1 --net=my-net --env="constraint:node==mhs-demo1" emilevauge/whoami
```
Check that everything is started:
```sh
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ba2c21488299 emilevauge/whoami "/whoamI" 8 seconds ago Up 9 seconds 80/tcp mhs-demo1/whoami1
8147a7746e7a emilevauge/whoami "/whoamI" 19 seconds ago Up 20 seconds 80/tcp mhs-demo0/whoami0
8fbc39271b4c traefik "/traefik -l DEBUG -c" 36 seconds ago Up 37 seconds 192.168.99.101:80->80/tcp, 192.168.99.101:8080->8080/tcp mhs-demo0/serene_bhabha
```
## Access to your apps through Træfɪk
```sh
curl -H Host:whoami0.traefik http://$(docker-machine ip mhs-demo0)
Hostname: 8147a7746e7a
IP: 127.0.0.1
IP: ::1
IP: 10.0.9.3
IP: fe80::42:aff:fe00:903
IP: 172.18.0.3
IP: fe80::42:acff:fe12:3
GET / HTTP/1.1
Host: 10.0.9.3:80
User-Agent: curl/7.35.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 192.168.99.1
X-Forwarded-Host: 10.0.9.3:80
X-Forwarded-Proto: http
X-Forwarded-Server: 8fbc39271b4c
curl -H Host:whoami1.traefik http://$(docker-machine ip mhs-demo0)
Hostname: ba2c21488299
IP: 127.0.0.1
IP: ::1
IP: 10.0.9.4
IP: fe80::42:aff:fe00:904
IP: 172.18.0.2
IP: fe80::42:acff:fe12:2
GET / HTTP/1.1
Host: 10.0.9.4:80
User-Agent: curl/7.35.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 192.168.99.1
X-Forwarded-Host: 10.0.9.4:80
X-Forwarded-Proto: http
X-Forwarded-Server: 8fbc39271b4c
```
![](http://i.giphy.com/ujUdrdpX7Ok5W.gif)

View File

@@ -6,7 +6,7 @@ zk:
ZK_ID: 1
master:
image: mesosphere/mesos-master:0.26.0-0.2.145.ubuntu1404
image: mesosphere/mesos-master:0.28.1-2.0.20.ubuntu1404
net: host
environment:
MESOS_ZK: zk://127.0.0.1:2181/mesos
@@ -17,7 +17,7 @@ master:
MESOS_WORK_DIR: /var/lib/mesos
slave:
image: mesosphere/mesos-slave:0.26.0-0.2.145.ubuntu1404
image: mesosphere/mesos-slave:0.28.1-2.0.20.ubuntu1404
net: host
pid: host
privileged: true
@@ -34,10 +34,19 @@ slave:
- /lib/x86_64-linux-gnu/libsystemd-journal.so.0:/lib/x86_64-linux-gnu/libsystemd-journal.so.0
marathon:
image: mesosphere/marathon:v0.13.0
image: mesosphere/marathon:v1.1.1
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
traefik:
image: containous/traefik
command: -c /dev/null --web --logLevel=DEBUG --marathon --marathon.domain marathon.localhost --marathon.endpoint http://172.17.0.1:8080 --marathon.watch
ports:
- "8000:80"
- "8081:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock

View File

@@ -0,0 +1,20 @@
traefik:
image: traefik
command: -c /dev/null --web --docker --docker.domain=docker.localhost --logLevel=DEBUG
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
whoami1:
image: emilevauge/whoami
labels:
- "traefik.backend=whoami"
- "traefik.frontend.rule=Host:whoami.docker.localhost"
whoami2:
image: emilevauge/whoami
labels:
- "traefik.backend=whoami"
- "traefik.frontend.rule=Host:whoami.docker.localhost"

View File

@@ -25,6 +25,7 @@
],
"labels": {
"traefik.weight": "1",
"traefik.protocole": "http"
"traefik.protocole": "http",
"traefik.frontend.rule" : "Host:test.marathon.localhost"
}
}

99
glide.lock generated
View File

@@ -1,5 +1,5 @@
hash: 21d4e8dc80c87101568a719ecf01d1af9a1b58f03c5c9dc864a8cb1005ddc160
updated: 2016-03-29T21:50:20.577439177+02:00
hash: fffa87220825895f7e3a6ceed3b13ecbf6bc934ab072fc9be3d00e3eef411ecb
updated: 2016-04-13T14:05:41.300658168+02:00
imports:
- name: github.com/alecthomas/template
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
@@ -22,7 +22,7 @@ imports:
- name: github.com/codegangsta/negroni
version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
- name: github.com/containous/oxy
version: 0b5b371bce661385d35439204298fa6fb5db5463
version: 021f82bd8260ba15f5862a9fe62018437720dff5
subpackages:
- cbreaker
- forward
@@ -39,7 +39,10 @@ imports:
subpackages:
- spew
- name: github.com/docker/distribution
version: 9038e48c3b982f8e82281ea486f078a73731ac4e
version: ff6f38ccb69afa96214c7ee955359465d1fc767a
subpackages:
- reference
- digest
- name: github.com/docker/docker
version: f39987afe8d611407887b3094c03d6ba6a766a67
subpackages:
@@ -79,14 +82,33 @@ imports:
- runconfig
- utils
- volume
- name: github.com/docker/engine-api
version: 8924d6900370b4c7e7984be5adc61f50a80d7537
subpackages:
- client
- types
- types/container
- types/filters
- types/strslice
- types/events
- client/transport
- client/transport/cancellable
- types/network
- types/reference
- types/registry
- types/time
- types/versions
- types/blkiodev
- name: github.com/docker/go-connections
version: f549a9393d05688dff0992ef3efd8bbe6c628aeb
subpackages:
- nat
- sockets
- tlsconfig
- name: github.com/docker/go-units
version: 5d2041e26a699eaca682e2ea41c8f891e1060444
- name: github.com/docker/libcompose
version: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
subpackages:
- docker
- logger
- lookup
- project
- utils
- name: github.com/docker/libkv
version: 3732f7ff1b56057c3158f10bceb1e79133025373
subpackages:
@@ -103,32 +125,14 @@ imports:
version: d5cac425555ca5cf00694df246e04f05e6a55150
- name: github.com/flynn/go-shlex
version: 3f9db97f856818214da2e1057f8ad84803971cff
- name: github.com/fsouza/go-dockerclient
version: a49c8269a6899cae30da1f8a4b82e0ce945f9967
subpackages:
- external/github.com/docker/docker/opts
- external/github.com/docker/docker/pkg/archive
- external/github.com/docker/docker/pkg/fileutils
- external/github.com/docker/docker/pkg/homedir
- external/github.com/docker/docker/pkg/stdcopy
- external/github.com/hashicorp/go-cleanhttp
- external/github.com/Sirupsen/logrus
- external/github.com/docker/docker/pkg/idtools
- external/github.com/docker/docker/pkg/ioutils
- external/github.com/docker/docker/pkg/pools
- external/github.com/docker/docker/pkg/promise
- external/github.com/docker/docker/pkg/system
- external/github.com/docker/docker/pkg/longpath
- external/github.com/opencontainers/runc/libcontainer/user
- external/golang.org/x/sys/unix
- external/golang.org/x/net/context
- external/github.com/docker/go-units
- name: github.com/gambol99/go-marathon
version: ade11d1dc2884ee1f387078fc28509559b6235d1
- name: github.com/go-check/check
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
- name: github.com/golang/glog
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
- name: github.com/google/go-querystring
version: 6bb77fe6f42b85397288d4f6f67ac72f8f400ee7
version: 9235644dd9e52eeae6fa48efd539fdc351a0af53
subpackages:
- query
- name: github.com/gorilla/context
@@ -168,12 +172,12 @@ imports:
version: fada45142db3f93097ca917da107aa3fad0ffcb5
- name: github.com/mailgun/multibuf
version: 565402cd71fbd9c12aa7e295324ea357e970a61e
- name: github.com/mailgun/predicate
version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
- name: github.com/mailgun/timetools
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
- name: github.com/Microsoft/go-winio
version: 862b6557927a5c5c81e411c12aa6de7e566cbb7a
- name: github.com/miekg/dns
version: 7e024ce8ce18b21b475ac6baf8fa3c42536bf2fa
version: dd83d5cbcfd986f334b2747feeb907e281318fdf
- name: github.com/mitchellh/mapstructure
version: d2dd0262208475919e1a362f675cfc0e7c10e905
- name: github.com/opencontainers/runc
@@ -184,8 +188,6 @@ imports:
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/samalba/dockerclient
version: cfb489c624b635251a93e74e1e90eb0959c5367f
- name: github.com/samuel/go-zookeeper
version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
subpackages:
@@ -195,19 +197,21 @@ imports:
- name: github.com/spf13/cast
version: ee7b3e0353166ab1f3a605294ac8cd2b77953778
- name: github.com/spf13/cobra
version: c678ff029ee250b65714e518f4f5c5cb934955de
version: 4c05eb1145f16d0e6bb4a3e1b6d769f4713cb41f
subpackages:
- cobra
- name: github.com/spf13/jwalterweatherman
version: 33c24e77fb80341fe7130ee7c594256ff08ccc46
- name: github.com/spf13/pflag
version: 7f60f83a2c81bc3c3c0d5297f61ddfa68da9d3b7
version: 1f296710f879815ad9e6d39d947c828c3e4b4c3d
- name: github.com/spf13/viper
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
- name: github.com/streamrail/concurrent-map
version: 788b276dc7eabf20890ea3fa280956664d58b329
- name: github.com/stretchr/objx
version: cbeaeb16a013161a98496fad62933b1d21786672
- name: github.com/stretchr/testify
version: 6fe211e493929a8aac0469b93f28b1d0688a9a3a
version: bcd9e3389dd03b0b668d11f4d462a6af6c2dfd60
subpackages:
- mock
- assert
@@ -215,12 +219,14 @@ imports:
version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
- name: github.com/unrolled/render
version: 26b4e3aac686940fe29521545afad9966ddfc80c
- name: github.com/vdemeester/docker-events
version: 6ea3f28df37f29a47498bc8b32b36ad8491dbd37
- name: github.com/vdemeester/libkermit
version: 01a5399bdbd3312916c9fa4848108fbc81fe88d8
version: 7e4e689a6fa9281e0fb9b7b9c297e22d5342a5ec
- name: github.com/vdemeester/shakers
version: 8fe734f75f3a70b651cbfbf8a55a009da09e8dc5
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
- name: github.com/vulcand/oxy
version: 8aaf36279137ac04ace3792a4f86098631b27d5a
version: 11677428db34c4a05354d66d028174d0e3c6e905
subpackages:
- memmetrics
- utils
@@ -237,11 +243,11 @@ imports:
- name: github.com/wendal/errors
version: f66c77a7882b399795a8987ebf87ef64a427417e
- name: github.com/xenolf/lego
version: ca19a90028e242e878585941c2a27c8f3b3efc25
version: 23e88185c255e95a106835d80e76e5a3a66d7c54
subpackages:
- acme
- name: golang.org/x/crypto
version: 9e7f5dc375abeb9619ea3c5c58502c428f457aa2
version: d68c3ecb62c850b645dc072a8d78006286bf81ca
subpackages:
- ocsp
- name: golang.org/x/net
@@ -249,14 +255,13 @@ imports:
subpackages:
- context
- publicsuffix
- proxy
- name: golang.org/x/sys
version: eb2c74142fd19a79b3f237334c7384d5167b1b46
subpackages:
- unix
- name: gopkg.in/alecthomas/kingpin.v2
version: 639879d6110b1b0409410c7b737ef0bb18325038
- name: gopkg.in/check.v1
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
- name: gopkg.in/fsnotify.v1
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
- name: gopkg.in/mgo.v2
@@ -264,7 +269,7 @@ imports:
subpackages:
- bson
- name: gopkg.in/square/go-jose.v1
version: 7d9df93c5ee8a09ed250b3b2360972fa29b4bb3c
version: 40d457b439244b546f023d056628e5184136899b
subpackages:
- cipher
- json

View File

@@ -4,12 +4,10 @@ import:
ref: cc90c7b091275e606ad0ca7102a23fb2072f3f5e
subpackages:
- etcd
- package: github.com/docker/distribution
ref: 9038e48c3b982f8e82281ea486f078a73731ac4e
- package: github.com/mailgun/log
ref: 44874009257d4d47ba9806f1b7f72a32a015e4d8
- package: github.com/containous/oxy
ref: 0b5b371bce661385d35439204298fa6fb5db5463
ref: 021f82bd8260ba15f5862a9fe62018437720dff5
subpackages:
- cbreaker
- forward
@@ -26,7 +24,7 @@ import:
- zk
- package: github.com/docker/libtrust
ref: 9cbd2a1374f46905c68a4eb3694a130610adc62a
- package: gopkg.in/check.v1
- package: github.com/go-check/check
ref: 11d3bc7aa68e238947792f30573146a3231fc0f1
- package: golang.org/x/net
ref: d9558e5c97f85372afee28cf2b6059d7d3818919
@@ -39,25 +37,21 @@ import:
- package: github.com/alecthomas/template
ref: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
- package: github.com/vdemeester/shakers
ref: 8fe734f75f3a70b651cbfbf8a55a009da09e8dc5
ref: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
- package: github.com/alecthomas/units
ref: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- package: github.com/gambol99/go-marathon
ref: ade11d1dc2884ee1f387078fc28509559b6235d1
- package: github.com/mailgun/predicate
- package: github.com/vulcand/predicate
ref: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
- package: github.com/thoas/stats
ref: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
- package: github.com/samalba/dockerclient
ref: cfb489c624b635251a93e74e1e90eb0959c5367f
- package: github.com/Sirupsen/logrus
ref: 418b41d23a1bf978c06faea5313ba194650ac088
- package: github.com/unrolled/render
ref: 26b4e3aac686940fe29521545afad9966ddfc80c
- package: github.com/flynn/go-shlex
ref: 3f9db97f856818214da2e1057f8ad84803971cff
- package: github.com/fsouza/go-dockerclient
ref: a49c8269a6899cae30da1f8a4b82e0ce945f9967
- package: github.com/boltdb/bolt
ref: 51f99c862475898df9773747d3accd05a7ca33c1
- package: gopkg.in/mgo.v2
@@ -123,14 +117,6 @@ import:
ref: bd2bdf7f18f849530ef7a1c29a4290217cab32a1
- package: gopkg.in/alecthomas/kingpin.v2
ref: 639879d6110b1b0409410c7b737ef0bb18325038
- package: github.com/docker/libcompose
ref: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
subpackages:
- docker
- logger
- lookup
- project
- utils
- package: github.com/cenkalti/backoff
ref: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
- package: gopkg.in/fsnotify.v1
@@ -166,5 +152,26 @@ import:
- package: github.com/stretchr/testify/mock
- package: github.com/xenolf/lego
- package: github.com/vdemeester/libkermit
ref: 01a5399bdbd3312916c9fa4848108fbc81fe88d8
ref: 7e4e689a6fa9281e0fb9b7b9c297e22d5342a5ec
- package: github.com/docker/libcompose
version: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
- package: github.com/docker/distribution
version: ff6f38ccb69afa96214c7ee955359465d1fc767a
subpackages:
- reference
- package: github.com/docker/engine-api
subpackages:
- client
- types
- types/container
- types/filters
- types/strslice
- package: github.com/vdemeester/docker-events
- package: github.com/docker/go-connections
subpackages:
- nat
- sockets
- tlsconfig
- package: github.com/docker/go-units
- package: github.com/mailgun/multibuf
- package: github.com/streamrail/concurrent-map

View File

@@ -6,8 +6,9 @@ import (
"time"
"fmt"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// SimpleSuite

View File

@@ -6,11 +6,10 @@ import (
"os/exec"
"time"
"github.com/go-check/check"
"github.com/hashicorp/consul/api"
docker "github.com/vdemeester/libkermit/docker"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// Consul catalog test suites
@@ -18,20 +17,14 @@ type ConsulCatalogSuite struct {
BaseSuite
consulIP string
consulClient *api.Client
project *docker.Project
}
func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
project, err := docker.NewProjectFromEnv()
c.Assert(err, checker.IsNil, check.Commentf("Error while creating docker project"))
s.project = project
s.createComposeProject(c, "consul_catalog")
err = s.composeProject.Start()
c.Assert(err, checker.IsNil, check.Commentf("Error starting project"))
s.composeProject.Start(c)
consul, err := s.project.Inspect("integration-test-consul_catalog_consul_1")
c.Assert(err, checker.IsNil, check.Commentf("Error finding consul container"))
consul := s.composeProject.Container(c, "consul")
s.consulIP = consul.NetworkSettings.IPAddress
config := api.DefaultConfig()
@@ -46,7 +39,7 @@ func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
time.Sleep(2000 * time.Millisecond)
}
func (s *ConsulCatalogSuite) registerService(name string, address string, port int) error {
func (s *ConsulCatalogSuite) registerService(name string, address string, port int, tags []string) error {
catalog := s.consulClient.Catalog()
_, err := catalog.Register(
&api.CatalogRegistration{
@@ -57,6 +50,7 @@ func (s *ConsulCatalogSuite) registerService(name string, address string, port i
Service: name,
Address: address,
Port: port,
Tags: tags,
},
},
&api.WriteOptions{},
@@ -98,10 +92,9 @@ func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
nginx, err := s.project.Inspect("integration-test-consul_catalog_nginx_1")
c.Assert(err, checker.IsNil, check.Commentf("Error finding nginx container"))
nginx := s.composeProject.Container(c, "nginx")
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80)
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)

View File

@@ -5,8 +5,9 @@ import (
"os/exec"
"time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// Consul test suites (using libcompose)

View File

@@ -11,10 +11,11 @@ import (
"time"
"github.com/docker/docker/pkg/namesgenerator"
"github.com/vdemeester/libkermit/docker"
"github.com/go-check/check"
d "github.com/vdemeester/libkermit/docker"
docker "github.com/vdemeester/libkermit/docker/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
var (
@@ -36,46 +37,42 @@ type DockerSuite struct {
}
func (s *DockerSuite) startContainer(c *check.C, image string, args ...string) string {
return s.startContainerWithConfig(c, image, docker.ContainerConfig{
return s.startContainerWithConfig(c, image, d.ContainerConfig{
Cmd: args,
})
}
func (s *DockerSuite) startContainerWithLabels(c *check.C, image string, labels map[string]string, args ...string) string {
return s.startContainerWithConfig(c, image, docker.ContainerConfig{
return s.startContainerWithConfig(c, image, d.ContainerConfig{
Cmd: args,
Labels: labels,
})
}
func (s *DockerSuite) startContainerWithConfig(c *check.C, image string, config docker.ContainerConfig) string {
func (s *DockerSuite) startContainerWithConfig(c *check.C, image string, config d.ContainerConfig) string {
if config.Name == "" {
config.Name = namesgenerator.GetRandomName(10)
}
container, err := s.project.StartWithConfig(image, config)
c.Assert(err, checker.IsNil, check.Commentf("Error starting a container using config %v", config))
container := s.project.StartWithConfig(c, image, config)
// FIXME(vdemeester) this is ugly (it's because of the / in front of the name in docker..)
return strings.SplitAfter(container.Name, "/")[1]
}
func (s *DockerSuite) SetUpSuite(c *check.C) {
project, err := docker.NewProjectFromEnv()
c.Assert(err, checker.IsNil, check.Commentf("Error while creating docker project"))
project := docker.NewProjectFromEnv(c)
s.project = project
// Pull required images
for repository, tag := range RequiredImages {
image := fmt.Sprintf("%s:%s", repository, tag)
s.project.Pull(image)
c.Assert(err, checker.IsNil, check.Commentf("Error while pulling image %s", image))
s.project.Pull(c, image)
}
}
func (s *DockerSuite) TearDownTest(c *check.C) {
err := s.project.Clean(os.Getenv("CIRCLECI") != "")
c.Assert(err, checker.IsNil, check.Commentf("Error while cleaning containers"))
s.project.Clean(c, os.Getenv("CIRCLECI") != "")
}
func (s *DockerSuite) TestSimpleConfiguration(c *check.C) {
@@ -133,8 +130,7 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
defer os.Remove(file)
// Start a container with some labels
labels := map[string]string{
"traefik.frontend.rule": "Host",
"traefik.frontend.value": "my.super.host",
"traefik.frontend.rule": "Host:my.super.host",
}
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")

View File

@@ -5,8 +5,9 @@ import (
"os/exec"
"time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// Etcd test suites (using libcompose)

View File

@@ -5,8 +5,9 @@ import (
"os/exec"
"time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// File test suites
@@ -15,7 +16,7 @@ type FileSuite struct{ BaseSuite }
func (s *FileSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "file")
s.composeProject.Start()
s.composeProject.Start(c)
}
func (s *FileSuite) TestSimpleConfiguration(c *check.C) {

View File

@@ -33,10 +33,8 @@ logLevel = "DEBUG"
[frontends.frontend1]
backend = "backend2"
[frontends.frontend1.routes.test_1]
rule = "Host"
value = "test.localhost"
rule = "Host:test.localhost"
[frontends.frontend2]
backend = "backend1"
[frontends.frontend2.routes.test_2]
rule = "Path"
value = "/test"
rule = "Path:/test"

View File

@@ -27,10 +27,8 @@ defaultEntryPoints = ["https"]
[frontends.frontend1]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Host"
value = "snitest.com"
rule = "Host:snitest.com"
[frontends.frontend2]
backend = "backend2"
[frontends.frontend2.routes.test_2]
rule = "Host"
value = "snitest.org"
rule = "Host:snitest.org"

View File

@@ -8,8 +8,9 @@ import (
"os/exec"
"time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// HTTPSSuite

View File

@@ -11,10 +11,10 @@ import (
"text/template"
"github.com/containous/traefik/integration/utils"
"github.com/vdemeester/libkermit/compose"
"github.com/go-check/check"
compose "github.com/vdemeester/libkermit/compose/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
func Test(t *testing.T) {
@@ -41,17 +41,14 @@ type BaseSuite struct {
func (s *BaseSuite) TearDownSuite(c *check.C) {
// shutdown and delete compose project
if s.composeProject != nil {
err := s.composeProject.Stop()
c.Assert(err, checker.IsNil)
s.composeProject.Stop(c)
}
}
func (s *BaseSuite) createComposeProject(c *check.C, name string) {
projectName := fmt.Sprintf("integration-test-%s", name)
composeFile := fmt.Sprintf("resources/compose/%s.yml", name)
composeProject, err := compose.CreateProject(projectName, composeFile)
c.Assert(err, checker.IsNil)
s.composeProject = composeProject
s.composeProject = compose.CreateProject(c, projectName, composeFile)
}
func (s *BaseSuite) traefikCmd(c *check.C, args ...string) (*exec.Cmd, string) {

View File

@@ -5,8 +5,9 @@ import (
"os/exec"
"time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// Marathon test suites (using libcompose)

View File

@@ -1,22 +0,0 @@
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

@@ -1,40 +1,35 @@
package middlewares
import (
"github.com/containous/traefik/safe"
"github.com/gorilla/mux"
"net/http"
"sync"
)
// HandlerSwitcher allows hot switching of http.ServeMux
type HandlerSwitcher struct {
handler *mux.Router
handlerLock *sync.Mutex
handler *safe.Safe
}
// NewHandlerSwitcher builds a new instance of HandlerSwitcher
func NewHandlerSwitcher(newHandler *mux.Router) (hs *HandlerSwitcher) {
return &HandlerSwitcher{
handler: newHandler,
handlerLock: &sync.Mutex{},
handler: safe.New(newHandler),
}
}
func (hs *HandlerSwitcher) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
hs.handlerLock.Lock()
handlerBackup := hs.handler
hs.handlerLock.Unlock()
handlerBackup := hs.handler.Get().(*mux.Router)
handlerBackup.ServeHTTP(rw, r)
}
// GetHandler returns the current http.ServeMux
func (hs *HandlerSwitcher) GetHandler() (newHandler *mux.Router) {
return hs.handler
handler := hs.handler.Get().(*mux.Router)
return 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()
hs.handler.Set(newHandler)
}

View File

@@ -35,5 +35,7 @@ func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.Ha
// Close closes the logger (i.e. the file).
func (l *Logger) Close() {
l.file.Close()
if l.file != nil {
l.file.Close()
}
}

View File

@@ -0,0 +1,29 @@
package middlewares
import (
"net/http"
"strings"
)
// StripPrefix is a middleware used to strip prefix from an URL request
type StripPrefix struct {
Handler http.Handler
Prefixes []string
}
func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, prefix := range s.Prefixes {
if p := strings.TrimPrefix(r.URL.Path, strings.TrimSpace(prefix)); len(p) < len(r.URL.Path) {
r.URL.Path = p
r.RequestURI = r.URL.RequestURI()
s.Handler.ServeHTTP(w, r)
return
}
}
http.NotFound(w, r)
}
// SetHandler sets handler
func (s *StripPrefix) SetHandler(Handler http.Handler) {
s.Handler = Handler
}

52
mkdocs.yml Normal file
View File

@@ -0,0 +1,52 @@
site_name: Traefik
site_description: Traefik Documentation
site_author: containo.us
site_url: https://docs.traefik.io
repo_name: 'GitHub'
repo_url: 'https://github.com/containous/traefik'
# Documentation and theme
docs_dir: 'docs'
theme: united
# theme: readthedocs
# theme: 'material'
# theme: bootstrap
site_favicon: 'img/traefik.icon.png'
# Copyright
copyright: Copyright (c) 2016 Containous SAS
# Options
extra:
# version: 0.2.2
logo: img/traefik.logo.png
# author:
# twitter: traefikproxy
palette:
primary: 'blue'
accent: 'light blue'
i18n:
prev: 'Previous'
next: 'Next'
markdown_extensions:
# - codehilite(css_class=code)
- admonition
# - toc:
# permalink: '##'
# - fenced_code
extra_css:
- css/traefik.css
# Page tree
pages:
- Getting Started: index.md
- Basics: basics.md
- traefik.toml: toml.md
- User Guide:
- 'Configuration examples': 'user-guide/examples.md'
- 'Swarm cluster': 'user-guide/swarm.md'
- Benchmarks: benchmarks.md

View File

@@ -1,6 +1,7 @@
package provider
import (
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/boltdb"
@@ -13,8 +14,8 @@ type BoltDb struct {
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage) error {
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
provider.storeType = store.BOLTDB
boltdb.Register()
return provider.provide(configurationChan)
return provider.provide(configurationChan, pool)
}

View File

@@ -1,6 +1,7 @@
package provider
import (
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/consul"
@@ -13,8 +14,8 @@ type Consul struct {
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage) error {
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
provider.storeType = store.CONSUL
consul.Register()
return provider.provide(configurationChan)
return provider.provide(configurationChan, pool)
}

View File

@@ -8,6 +8,7 @@ import (
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/hashicorp/consul/api"
)
@@ -15,6 +16,8 @@ import (
const (
// DefaultWatchWaitTime is the duration to wait when polling consul
DefaultWatchWaitTime = 15 * time.Second
// DefaultConsulCatalogTagPrefix is a prefix for additional service/node configurations
DefaultConsulCatalogTagPrefix = "traefik"
)
// ConsulCatalog holds configurations of the Consul catalog provider.
@@ -23,10 +26,16 @@ type ConsulCatalog struct {
Endpoint string
Domain string
client *api.Client
Prefix string
}
type serviceUpdate struct {
ServiceName string
Attributes []string
}
type catalogUpdate struct {
Service string
Service *serviceUpdate
Nodes []*api.ServiceEntry
}
@@ -35,7 +44,7 @@ func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[
catalog := provider.client.Catalog()
go func() {
safe.Go(func() {
defer close(watchCh)
opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
@@ -64,7 +73,7 @@ func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[
watchCh <- data
}
}
}()
})
return watchCh
}
@@ -78,41 +87,76 @@ func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, erro
return catalogUpdate{}, err
}
set := map[string]bool{}
tags := []string{}
for _, node := range data {
for _, tag := range node.Service.Tags {
if _, ok := set[tag]; ok == false {
set[tag] = true
tags = append(tags, tag)
}
}
}
return catalogUpdate{
Service: service,
Nodes: data,
Service: &serviceUpdate{
ServiceName: service,
Attributes: tags,
},
Nodes: data,
}, nil
}
func (provider *ConsulCatalog) getEntryPoints(list string) []string {
return strings.Split(list, ",")
}
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) getFrontendRule(service serviceUpdate) string {
customFrontendRule := provider.getAttribute("frontend.rule", service.Attributes, "")
if customFrontendRule != "" {
return customFrontendRule
}
return "Host:" + service.ServiceName + "." + provider.Domain
}
func (provider *ConsulCatalog) getAttribute(name string, tags []string, defaultValue string) string {
for _, tag := range tags {
if strings.Index(tag, DefaultConsulCatalogTagPrefix+".") == 0 {
if kv := strings.SplitN(tag[len(DefaultConsulCatalogTagPrefix+"."):], "=", 2); len(kv) == 2 && kv[0] == name {
return kv[1]
}
}
}
return defaultValue
}
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
var FuncMap = template.FuncMap{
"getBackend": provider.getBackend,
"getFrontendValue": provider.getFrontendValue,
"replace": replace,
"getBackend": provider.getBackend,
"getFrontendRule": provider.getFrontendRule,
"getAttribute": provider.getAttribute,
"getEntryPoints": provider.getEntryPoints,
"replace": replace,
}
allNodes := []*api.ServiceEntry{}
serviceNames := []string{}
services := []*serviceUpdate{}
for _, info := range catalog {
if len(info.Nodes) > 0 {
serviceNames = append(serviceNames, info.Service)
services = append(services, info.Service)
allNodes = append(allNodes, info.Nodes...)
}
}
templateObjects := struct {
Services []string
Services []*serviceUpdate
Nodes []*api.ServiceEntry
}{
Services: serviceNames,
Services: services,
Nodes: allNodes,
}
@@ -145,7 +189,7 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd
return nodes, nil
}
func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessage) error {
func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessage, stop chan bool) error {
stopCh := make(chan struct{})
serviceCatalog := provider.watchServices(stopCh)
@@ -153,6 +197,8 @@ func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessag
for {
select {
case <-stop:
return nil
case index, ok := <-serviceCatalog:
if !ok {
return errors.New("Consul service list nil")
@@ -173,7 +219,7 @@ func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessag
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage) error {
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
config := api.DefaultConfig()
config.Address = provider.Endpoint
client, err := api.NewClient(config)
@@ -182,18 +228,18 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess
}
provider.client = client
go func() {
pool.Go(func(stop chan bool) {
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)
return provider.watch(configurationChan, stop)
}
err := backoff.RetryNotify(worker, backoff.NewExponentialBackOff(), notify)
if err != nil {
log.Fatalf("Cannot connect to consul server %+v", err)
}
}()
})
return err
}

View File

@@ -8,23 +8,74 @@ import (
"github.com/hashicorp/consul/api"
)
func TestConsulCatalogGetFrontendValue(t *testing.T) {
func TestConsulCatalogGetFrontendRule(t *testing.T) {
provider := &ConsulCatalog{
Domain: "localhost",
}
services := []struct {
service string
service serviceUpdate
expected string
}{
{
service: "foo",
expected: "foo.localhost",
service: serviceUpdate{
ServiceName: "foo",
Attributes: []string{},
},
expected: "Host:foo.localhost",
},
{
service: serviceUpdate{
ServiceName: "foo",
Attributes: []string{
"traefik.frontend.rule=Host:*.example.com",
},
},
expected: "Host:*.example.com",
},
}
for _, e := range services {
actual := provider.getFrontendValue(e.service)
actual := provider.getFrontendRule(e.service)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestConsulCatalogGetAttribute(t *testing.T) {
provider := &ConsulCatalog{
Domain: "localhost",
}
services := []struct {
tags []string
key string
defaultValue string
expected string
}{
{
tags: []string{
"foo.bar=ramdom",
"traefik.backend.weight=42",
},
key: "backend.weight",
defaultValue: "",
expected: "42",
},
{
tags: []string{
"foo.bar=ramdom",
"traefik.backend.wei=42",
},
key: "backend.weight",
defaultValue: "",
expected: "",
},
}
for _, e := range services {
actual := provider.getAttribute(e.key, e.tags, e.defaultValue)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
@@ -49,7 +100,10 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
{
nodes: []catalogUpdate{
{
Service: "test",
Service: &serviceUpdate{
ServiceName: "test",
Attributes: []string{},
},
},
},
expectedFrontends: map[string]*types.Frontend{},
@@ -58,12 +112,26 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
{
nodes: []catalogUpdate{
{
Service: "test",
Service: &serviceUpdate{
ServiceName: "test",
Attributes: []string{
"traefik.backend.loadbalancer=drr",
"traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5",
"random.foo=bar",
},
},
Nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "test",
Address: "127.0.0.1",
Port: 80,
Tags: []string{
"traefik.backend.weight=42",
"random.foo=bar",
"traefik.backend.passHostHeader=true",
"traefik.protocol=https",
},
},
Node: &api.Node{
Node: "localhost",
@@ -78,8 +146,7 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
Backend: "backend-test",
Routes: map[string]types.Route{
"route-host-test": {
Rule: "Host",
Value: "test.localhost",
Rule: "Host:test.localhost",
},
},
},
@@ -87,12 +154,17 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
expectedBackends: map[string]*types.Backend{
"backend-test": {
Servers: map[string]types.Server{
"server-localhost-80": {
URL: "http://127.0.0.1:80",
"test--127-0-0-1--80": {
URL: "https://127.0.0.1:80",
Weight: 42,
},
},
CircuitBreaker: nil,
LoadBalancer: nil,
CircuitBreaker: &types.CircuitBreaker{
Expression: "NetworkErrorRatio() > 0.5",
},
LoadBalancer: &types.LoadBalancer{
Method: "drr",
},
},
},
},

View File

@@ -2,19 +2,31 @@ package provider
import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"text/template"
"time"
"golang.org/x/net/context"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/fsouza/go-dockerclient"
"github.com/docker/engine-api/client"
dockertypes "github.com/docker/engine-api/types"
eventtypes "github.com/docker/engine-api/types/events"
"github.com/docker/engine-api/types/filters"
"github.com/docker/go-connections/sockets"
"github.com/docker/go-connections/tlsconfig"
"github.com/vdemeester/docker-events"
)
// DockerAPIVersion is a constant holding the version of the Docker API traefik will use
const DockerAPIVersion string = "1.21"
// Docker holds configurations of the Docker provider.
type Docker struct {
BaseProvider `mapstructure:",squash"`
@@ -31,59 +43,105 @@ type DockerTLS struct {
InsecureSkipVerify bool
}
func (provider *Docker) createClient() (client.APIClient, error) {
var httpClient *http.Client
httpHeaders := map[string]string{
// FIXME(vdemeester) use version here O:)
"User-Agent": "Traefik",
}
if provider.TLS != nil {
tlsOptions := tlsconfig.Options{
CAFile: provider.TLS.CA,
CertFile: provider.TLS.Cert,
KeyFile: provider.TLS.Key,
InsecureSkipVerify: provider.TLS.InsecureSkipVerify,
}
config, err := tlsconfig.Client(tlsOptions)
if err != nil {
return nil, err
}
tr := &http.Transport{
TLSClientConfig: config,
}
proto, addr, _, err := client.ParseHost(provider.Endpoint)
if err != nil {
return nil, err
}
sockets.ConfigureTransport(tr, proto, addr)
httpClient = &http.Client{
Transport: tr,
}
}
return client.NewClient(provider.Endpoint, DockerAPIVersion, httpClient, httpHeaders)
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage) error {
go func() {
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
// TODO register this routine in pool, and watch for stop channel
safe.Go(func() {
operation := func() error {
var dockerClient *docker.Client
var err error
if provider.TLS != nil {
dockerClient, err = docker.NewTLSClient(provider.Endpoint,
provider.TLS.Cert, provider.TLS.Key, provider.TLS.CA)
if err == nil {
dockerClient.TLSConfig.InsecureSkipVerify = provider.TLS.InsecureSkipVerify
}
} else {
dockerClient, err = docker.NewClient(provider.Endpoint)
}
dockerClient, err := provider.createClient()
if err != nil {
log.Errorf("Failed to create a client for docker, error: %s", err)
return err
}
err = dockerClient.Ping()
version, err := dockerClient.ServerVersion(context.Background())
log.Debugf("Docker connection established with docker %s (API %s)", version.Version, version.APIVersion)
containers, err := listContainers(dockerClient)
if err != nil {
log.Errorf("Docker connection error %+v", err)
log.Errorf("Failed to list containers for docker, error %s", err)
return err
}
log.Debug("Docker connection established")
configuration := provider.loadDockerConfig(listContainers(dockerClient))
configuration := provider.loadDockerConfig(containers)
configurationChan <- types.ConfigMessage{
ProviderName: "docker",
Configuration: configuration,
}
if provider.Watch {
dockerEvents := make(chan *docker.APIEvents)
dockerClient.AddEventListener(dockerEvents)
log.Debug("Docker listening")
for {
event := <-dockerEvents
if event == nil {
return errors.New("Docker event nil")
// log.Fatalf("Docker connection error")
ctx, cancel := context.WithCancel(context.Background())
f := filters.NewArgs()
f.Add("type", "container")
options := dockertypes.EventsOptions{
Filters: f,
}
eventHandler := events.NewHandler(events.ByAction)
startStopHandle := func(m eventtypes.Message) {
log.Debugf("Docker event received %+v", m)
containers, err := listContainers(dockerClient)
if err != nil {
log.Errorf("Failed to list containers for docker, error %s", err)
// Call cancel to get out of the monitor
cancel()
}
if event.Status == "start" || event.Status == "die" {
log.Debugf("Docker event receveived %+v", event)
configuration := provider.loadDockerConfig(listContainers(dockerClient))
if configuration != nil {
configurationChan <- types.ConfigMessage{
ProviderName: "docker",
Configuration: configuration,
}
configuration := provider.loadDockerConfig(containers)
if configuration != nil {
configurationChan <- types.ConfigMessage{
ProviderName: "docker",
Configuration: configuration,
}
}
}
eventHandler.Handle("start", startStopHandle)
eventHandler.Handle("die", startStopHandle)
errChan := events.MonitorWithHandler(ctx, dockerClient, options, eventHandler)
pool.Go(func(stop chan bool) {
for {
select {
case <-stop:
cancel()
return
}
}
})
if err := <-errChan; err != nil {
return err
}
}
return nil
}
@@ -94,12 +152,12 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage) er
if err != nil {
log.Fatalf("Cannot connect to docker server %+v", err)
}
}()
})
return nil
}
func (provider *Docker) loadDockerConfig(containersInspected []docker.Container) *types.Configuration {
func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration {
var DockerFuncMap = template.FuncMap{
"getBackend": provider.getBackend,
"getPort": provider.getPort,
@@ -108,22 +166,21 @@ func (provider *Docker) loadDockerConfig(containersInspected []docker.Container)
"getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader,
"getEntryPoints": provider.getEntryPoints,
"getFrontendValue": provider.getFrontendValue,
"getFrontendRule": provider.getFrontendRule,
"replace": replace,
}
// filter containers
filteredContainers := fun.Filter(containerFilter, containersInspected).([]docker.Container)
filteredContainers := fun.Filter(containerFilter, containersInspected).([]dockertypes.ContainerJSON)
frontends := map[string][]docker.Container{}
frontends := map[string][]dockertypes.ContainerJSON{}
for _, container := range filteredContainers {
frontends[provider.getFrontendName(container)] = append(frontends[provider.getFrontendName(container)], container)
}
templateObjects := struct {
Containers []docker.Container
Frontends map[string][]docker.Container
Containers []dockertypes.ContainerJSON
Frontends map[string][]dockertypes.ContainerJSON
Domain string
}{
filteredContainers,
@@ -138,7 +195,7 @@ func (provider *Docker) loadDockerConfig(containersInspected []docker.Container)
return configuration
}
func containerFilter(container docker.Container) bool {
func containerFilter(container dockertypes.ContainerJSON) bool {
if len(container.NetworkSettings.Ports) == 0 {
log.Debugf("Filtering container without port %s", container.Name)
return false
@@ -154,50 +211,40 @@ func containerFilter(container docker.Container) bool {
return false
}
labels, err := getLabels(container, []string{"traefik.frontend.rule", "traefik.frontend.value"})
if len(labels) != 0 && err != nil {
log.Debugf("Filtering bad labeled container %s", container.Name)
return false
}
return true
}
func (provider *Docker) getFrontendName(container docker.Container) string {
func (provider *Docker) getFrontendName(container dockertypes.ContainerJSON) string {
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
frontendName := fmt.Sprintf("%s-%s", provider.getFrontendRule(container), provider.getFrontendValue(container))
frontendName = strings.Replace(frontendName, "[", "", -1)
frontendName = strings.Replace(frontendName, "]", "", -1)
return strings.Replace(frontendName, ".", "-", -1)
}
// GetFrontendValue returns the frontend value for the specified container, using
// it's label. It returns a default one if the label is not present.
func (provider *Docker) getFrontendValue(container docker.Container) string {
if label, err := getLabel(container, "traefik.frontend.value"); err == nil {
return label
}
return getEscapedName(container.Name) + "." + provider.Domain
return normalize(provider.getFrontendRule(container))
}
// GetFrontendRule returns the frontend rule for the specified container, using
// it's label. It returns a default one (Host) if the label is not present.
func (provider *Docker) getFrontendRule(container docker.Container) string {
func (provider *Docker) getFrontendRule(container dockertypes.ContainerJSON) string {
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
// TODO: backwards compatibility with DEPRECATED rule.Value
if value, ok := container.Config.Labels["traefik.frontend.value"]; ok {
log.Warnf("Label traefik.frontend.value=%s is DEPRECATED (will be removed in v1.0.0), please refer to the rule label: https://github.com/containous/traefik/blob/master/docs/index.md#docker", value)
rule, _ := container.Config.Labels["traefik.frontend.rule"]
return rule + ":" + value
}
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
return label
}
return "Host"
return "Host:" + getEscapedName(container.Name) + "." + provider.Domain
}
func (provider *Docker) getBackend(container docker.Container) string {
func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.backend"); err == nil {
return label
}
return getEscapedName(container.Name)
return normalize(container.Name)
}
func (provider *Docker) getPort(container docker.Container) string {
func (provider *Docker) getPort(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.port"); err == nil {
return label
}
@@ -207,42 +254,42 @@ func (provider *Docker) getPort(container docker.Container) string {
return ""
}
func (provider *Docker) getWeight(container docker.Container) string {
func (provider *Docker) getWeight(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.weight"); err == nil {
return label
}
return "0"
return "1"
}
func (provider *Docker) getDomain(container docker.Container) string {
func (provider *Docker) getDomain(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.domain"); err == nil {
return label
}
return provider.Domain
}
func (provider *Docker) getProtocol(container docker.Container) string {
func (provider *Docker) getProtocol(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.protocol"); err == nil {
return label
}
return "http"
}
func (provider *Docker) getPassHostHeader(container docker.Container) string {
func (provider *Docker) getPassHostHeader(container dockertypes.ContainerJSON) string {
if passHostHeader, err := getLabel(container, "traefik.frontend.passHostHeader"); err == nil {
return passHostHeader
}
return "false"
}
func (provider *Docker) getEntryPoints(container docker.Container) []string {
func (provider *Docker) getEntryPoints(container dockertypes.ContainerJSON) []string {
if entryPoints, err := getLabel(container, "traefik.frontend.entryPoints"); err == nil {
return strings.Split(entryPoints, ",")
}
return []string{}
}
func getLabel(container docker.Container, label string) (string, error) {
func getLabel(container dockertypes.ContainerJSON, label string) (string, error) {
for key, value := range container.Config.Labels {
if key == label {
return value, nil
@@ -251,7 +298,7 @@ func getLabel(container docker.Container, label string) (string, error) {
return "", errors.New("Label not found:" + label)
}
func getLabels(container docker.Container, labels []string) (map[string]string, error) {
func getLabels(container dockertypes.ContainerJSON, labels []string) (map[string]string, error) {
var globalErr error
foundLabels := map[string]string{}
for _, label := range labels {
@@ -267,14 +314,20 @@ func getLabels(container docker.Container, labels []string) (map[string]string,
return foundLabels, globalErr
}
func listContainers(dockerClient *docker.Client) []docker.Container {
containerList, _ := dockerClient.ListContainers(docker.ListContainersOptions{})
containersInspected := []docker.Container{}
func listContainers(dockerClient client.APIClient) ([]dockertypes.ContainerJSON, error) {
containerList, err := dockerClient.ContainerList(context.Background(), dockertypes.ContainerListOptions{})
if err != nil {
return []dockertypes.ContainerJSON{}, err
}
containersInspected := []dockertypes.ContainerJSON{}
// get inspect containers
for _, container := range containerList {
containerInspected, _ := dockerClient.InspectContainer(container.ID)
containersInspected = append(containersInspected, *containerInspected)
containerInspected, err := dockerClient.ContainerInspect(context.Background(), container.ID)
if err != nil {
log.Warnf("Failed to inpsect container %s, error: %s", container.ID, err)
}
containersInspected = append(containersInspected, containerInspected)
}
return containersInspected
return containersInspected, nil
}

View File

@@ -6,7 +6,10 @@ import (
"testing"
"github.com/containous/traefik/types"
"github.com/fsouza/go-dockerclient"
docker "github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
"github.com/docker/engine-api/types/network"
"github.com/docker/go-connections/nat"
)
func TestDockerGetFrontendName(t *testing.T) {
@@ -15,61 +18,69 @@ func TestDockerGetFrontendName(t *testing.T) {
}
containers := []struct {
container docker.Container
container docker.ContainerJSON
expected string
}{
{
container: docker.Container{
Name: "foo",
Config: &docker.Config{},
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "foo",
},
Config: &container.Config{},
},
expected: "Host-foo-docker-localhost",
},
{
container: docker.Container{
Name: "bar",
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "bar",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.frontend.rule": "Header",
"traefik.frontend.rule": "Headers:User-Agent,bat/0.1.0",
},
},
},
expected: "Header-bar-docker-localhost",
expected: "Headers-User-Agent-bat-0-1-0",
},
{
container: docker.Container{
Name: "test",
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.frontend.value": "foo.bar",
"traefik.frontend.rule": "Host:foo.bar",
},
},
},
expected: "Host-foo-bar",
},
{
container: docker.Container{
Name: "test",
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.frontend.value": "foo.bar",
"traefik.frontend.rule": "Header",
"traefik.frontend.rule": "Path:/test",
},
},
},
expected: "Header-foo-bar",
expected: "Path-test",
},
{
container: docker.Container{
Name: "test",
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.frontend.value": "[foo.bar]",
"traefik.frontend.rule": "Header",
"traefik.frontend.rule": "PathPrefix:/test2",
},
},
},
expected: "Header-foo-bar",
expected: "PathPrefix-test2",
},
}
@@ -81,74 +92,58 @@ func TestDockerGetFrontendName(t *testing.T) {
}
}
func TestDockerGetFrontendValue(t *testing.T) {
func TestDockerGetFrontendRule(t *testing.T) {
provider := &Docker{
Domain: "docker.localhost",
}
containers := []struct {
container docker.Container
container docker.ContainerJSON
expected string
}{
{
container: docker.Container{
Name: "foo",
Config: &docker.Config{},
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "foo",
},
Config: &container.Config{},
},
expected: "foo.docker.localhost",
expected: "Host:foo.docker.localhost",
},
{
container: docker.Container{
Name: "bar",
Config: &docker.Config{},
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "bar",
},
Config: &container.Config{},
},
expected: "bar.docker.localhost",
expected: "Host:bar.docker.localhost",
},
{
container: docker.Container{
Name: "test",
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.frontend.value": "foo.bar",
"traefik.frontend.rule": "Host:foo.bar",
},
},
},
expected: "foo.bar",
},
}
for _, e := range containers {
actual := provider.getFrontendValue(e.container)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestDockerGetFrontendRule(t *testing.T) {
provider := &Docker{}
containers := []struct {
container docker.Container
expected string
}{
{
container: docker.Container{
Name: "foo",
Config: &docker.Config{},
},
expected: "Host",
expected: "Host:foo.bar",
},
{
container: docker.Container{
Name: "test",
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.frontend.rule": "foo",
"traefik.frontend.rule": "Path:/test",
},
},
},
expected: "foo",
expected: "Path:/test",
},
}
@@ -164,27 +159,33 @@ func TestDockerGetBackend(t *testing.T) {
provider := &Docker{}
containers := []struct {
container docker.Container
container docker.ContainerJSON
expected string
}{
{
container: docker.Container{
Name: "foo",
Config: &docker.Config{},
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "foo",
},
Config: &container.Config{},
},
expected: "foo",
},
{
container: docker.Container{
Name: "bar",
Config: &docker.Config{},
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "bar",
},
Config: &container.Config{},
},
expected: "bar",
},
{
container: docker.Container{
Name: "test",
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.backend": "foobar",
},
@@ -206,24 +207,30 @@ func TestDockerGetPort(t *testing.T) {
provider := &Docker{}
containers := []struct {
container docker.Container
container docker.ContainerJSON
expected string
}{
{
container: docker.Container{
Name: "foo",
Config: &docker.Config{},
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "foo",
},
Config: &container.Config{},
NetworkSettings: &docker.NetworkSettings{},
},
expected: "",
},
{
container: docker.Container{
Name: "bar",
Config: &docker.Config{},
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "bar",
},
Config: &container.Config{},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
@@ -231,9 +238,9 @@ func TestDockerGetPort(t *testing.T) {
},
// FIXME handle this better..
// {
// container: docker.Container{
// container: docker.ContainerJSON{
// Name: "bar",
// Config: &docker.Config{},
// Config: &container.Config{},
// NetworkSettings: &docker.NetworkSettings{
// Ports: map[docker.Port][]docker.PortBinding{
// "80/tcp": []docker.PortBinding{},
@@ -244,16 +251,20 @@ func TestDockerGetPort(t *testing.T) {
// expected: "80",
// },
{
container: docker.Container{
Name: "test",
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.port": "8080",
},
},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
@@ -273,20 +284,24 @@ func TestDockerGetWeight(t *testing.T) {
provider := &Docker{}
containers := []struct {
container docker.Container
container docker.ContainerJSON
expected string
}{
{
container: docker.Container{
Name: "foo",
Config: &docker.Config{},
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "foo",
},
Config: &container.Config{},
},
expected: "0",
expected: "1",
},
{
container: docker.Container{
Name: "test",
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.weight": "10",
},
@@ -310,20 +325,24 @@ func TestDockerGetDomain(t *testing.T) {
}
containers := []struct {
container docker.Container
container docker.ContainerJSON
expected string
}{
{
container: docker.Container{
Name: "foo",
Config: &docker.Config{},
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "foo",
},
Config: &container.Config{},
},
expected: "docker.localhost",
},
{
container: docker.Container{
Name: "test",
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.domain": "foo.bar",
},
@@ -345,20 +364,24 @@ func TestDockerGetProtocol(t *testing.T) {
provider := &Docker{}
containers := []struct {
container docker.Container
container docker.ContainerJSON
expected string
}{
{
container: docker.Container{
Name: "foo",
Config: &docker.Config{},
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "foo",
},
Config: &container.Config{},
},
expected: "http",
},
{
container: docker.Container{
Name: "test",
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.protocol": "https",
},
@@ -380,20 +403,24 @@ func TestDockerGetPassHostHeader(t *testing.T) {
provider := &Docker{}
containers := []struct {
container docker.Container
container docker.ContainerJSON
expected string
}{
{
container: docker.Container{
Name: "foo",
Config: &docker.Config{},
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "foo",
},
Config: &container.Config{},
},
expected: "false",
},
{
container: docker.Container{
Name: "test",
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.frontend.passHostHeader": "true",
},
@@ -413,18 +440,18 @@ func TestDockerGetPassHostHeader(t *testing.T) {
func TestDockerGetLabel(t *testing.T) {
containers := []struct {
container docker.Container
container docker.ContainerJSON
expected string
}{
{
container: docker.Container{
Config: &docker.Config{},
container: docker.ContainerJSON{
Config: &container.Config{},
},
expected: "Label not found:",
},
{
container: docker.Container{
Config: &docker.Config{
container: docker.ContainerJSON{
Config: &container.Config{
Labels: map[string]string{
"foo": "bar",
},
@@ -450,20 +477,20 @@ func TestDockerGetLabel(t *testing.T) {
func TestDockerGetLabels(t *testing.T) {
containers := []struct {
container docker.Container
container docker.ContainerJSON
expectedLabels map[string]string
expectedError string
}{
{
container: docker.Container{
Config: &docker.Config{},
container: docker.ContainerJSON{
Config: &container.Config{},
},
expectedLabels: map[string]string{},
expectedError: "Label not found:",
},
{
container: docker.Container{
Config: &docker.Config{
container: docker.ContainerJSON{
Config: &container.Config{
Labels: map[string]string{
"foo": "fooz",
},
@@ -475,8 +502,8 @@ func TestDockerGetLabels(t *testing.T) {
expectedError: "Label not found: bar",
},
{
container: docker.Container{
Config: &docker.Config{
container: docker.ContainerJSON{
Config: &container.Config{
Labels: map[string]string{
"foo": "fooz",
"bar": "barz",
@@ -506,141 +533,168 @@ func TestDockerGetLabels(t *testing.T) {
func TestDockerTraefikFilter(t *testing.T) {
containers := []struct {
container docker.Container
container docker.ContainerJSON
expected bool
}{
{
container: docker.Container{
Config: &docker.Config{},
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{},
NetworkSettings: &docker.NetworkSettings{},
},
expected: false,
},
{
container: docker.Container{
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.enable": "false",
},
},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
expected: false,
},
{
container: docker.Container{
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.frontend.rule": "Host",
"traefik.frontend.rule": "Host:foo.bar",
},
},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
},
},
},
expected: false,
},
{
container: docker.Container{
Config: &docker.Config{
Labels: map[string]string{
"traefik.frontend.value": "foo.bar",
},
},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
},
},
},
expected: false,
},
{
container: docker.Container{
Config: &docker.Config{},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
"443/tcp": {},
},
},
},
expected: false,
},
{
container: docker.Container{
Config: &docker.Config{},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
expected: true,
},
{
container: docker.Container{
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
"443/tcp": {},
},
},
},
},
expected: false,
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{},
NetworkSettings: &docker.NetworkSettings{
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
expected: true,
},
{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.port": "80",
},
},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
"443/tcp": {},
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
"443/tcp": {},
},
},
},
},
expected: true,
},
{
container: docker.Container{
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.enable": "true",
},
},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
expected: true,
},
{
container: docker.Container{
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.enable": "anything",
},
},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
expected: true,
},
{
container: docker.Container{
Config: &docker.Config{
container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "container",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.frontend.rule": "Host",
"traefik.frontend.value": "foo.bar",
"traefik.frontend.rule": "Host:foo.bar",
},
},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
},
},
@@ -651,33 +705,37 @@ func TestDockerTraefikFilter(t *testing.T) {
for _, e := range containers {
actual := containerFilter(e.container)
if actual != e.expected {
t.Fatalf("expected %v, got %v", e.expected, actual)
t.Fatalf("expected %v for %+v, got %+v", e.expected, e, actual)
}
}
}
func TestDockerLoadDockerConfig(t *testing.T) {
cases := []struct {
containers []docker.Container
containers []docker.ContainerJSON
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
containers: []docker.Container{},
containers: []docker.ContainerJSON{},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
containers: []docker.Container{
containers: []docker.ContainerJSON{
{
Name: "test",
Config: &docker.Config{},
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test",
},
Config: &container.Config{},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
Networks: map[string]docker.ContainerNetwork{
"bridgde": {
Networks: map[string]*network.EndpointSettings{
"bridge": {
IPAddress: "127.0.0.1",
},
},
@@ -690,8 +748,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
EntryPoints: []string{},
Routes: map[string]types.Route{
`"route-frontend-Host-test-docker-localhost"`: {
Rule: "Host",
Value: "test.docker.localhost",
Rule: "Host:test.docker.localhost",
},
},
},
@@ -700,7 +757,8 @@ func TestDockerLoadDockerConfig(t *testing.T) {
"backend-test": {
Servers: map[string]types.Server{
"server-test": {
URL: "http://127.0.0.1:80",
URL: "http://127.0.0.1:80",
Weight: 1,
},
},
CircuitBreaker: nil,
@@ -709,39 +767,47 @@ func TestDockerLoadDockerConfig(t *testing.T) {
},
},
{
containers: []docker.Container{
containers: []docker.ContainerJSON{
{
Name: "test1",
Config: &docker.Config{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test1",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.backend": "foobar",
"traefik.frontend.entryPoints": "http,https",
},
},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
Networks: map[string]docker.ContainerNetwork{
"bridgde": {
Networks: map[string]*network.EndpointSettings{
"bridge": {
IPAddress: "127.0.0.1",
},
},
},
},
{
Name: "test2",
Config: &docker.Config{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "test2",
},
Config: &container.Config{
Labels: map[string]string{
"traefik.backend": "foobar",
},
},
NetworkSettings: &docker.NetworkSettings{
Ports: map[docker.Port][]docker.PortBinding{
"80/tcp": {},
NetworkSettingsBase: docker.NetworkSettingsBase{
Ports: nat.PortMap{
"80/tcp": {},
},
},
Networks: map[string]docker.ContainerNetwork{
"bridgde": {
Networks: map[string]*network.EndpointSettings{
"bridge": {
IPAddress: "127.0.0.1",
},
},
@@ -754,8 +820,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
EntryPoints: []string{"http", "https"},
Routes: map[string]types.Route{
`"route-frontend-Host-test1-docker-localhost"`: {
Rule: "Host",
Value: "test1.docker.localhost",
Rule: "Host:test1.docker.localhost",
},
},
},
@@ -764,8 +829,7 @@ func TestDockerLoadDockerConfig(t *testing.T) {
EntryPoints: []string{},
Routes: map[string]types.Route{
`"route-frontend-Host-test2-docker-localhost"`: {
Rule: "Host",
Value: "test2.docker.localhost",
Rule: "Host:test2.docker.localhost",
},
},
},
@@ -774,10 +838,12 @@ func TestDockerLoadDockerConfig(t *testing.T) {
"backend-foobar": {
Servers: map[string]types.Server{
"server-test1": {
URL: "http://127.0.0.1:80",
URL: "http://127.0.0.1:80",
Weight: 1,
},
"server-test2": {
URL: "http://127.0.0.1:80",
URL: "http://127.0.0.1:80",
Weight: 1,
},
},
CircuitBreaker: nil,

View File

@@ -1,6 +1,7 @@
package provider
import (
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/etcd"
@@ -13,8 +14,8 @@ type Etcd struct {
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage) error {
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
provider.storeType = store.ETCD
etcd.Register()
return provider.provide(configurationChan)
return provider.provide(configurationChan, pool)
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/BurntSushi/toml"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"gopkg.in/fsnotify.v1"
)
@@ -18,7 +19,7 @@ type File struct {
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage) error {
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Error("Error creating file watcher", err)
@@ -34,10 +35,12 @@ func (provider *File) Provide(configurationChan chan<- types.ConfigMessage) erro
if provider.Watch {
// Process events
go func() {
pool.Go(func(stop chan bool) {
defer watcher.Close()
for {
select {
case <-stop:
return
case event := <-watcher.Events:
if strings.Contains(event.Name, file.Name()) {
log.Debug("File event:", event)
@@ -53,7 +56,7 @@ func (provider *File) Provide(configurationChan chan<- types.ConfigMessage) erro
log.Error("Watcher event error", error)
}
}
}()
})
err = watcher.Add(filepath.Dir(file.Name()))
if err != nil {
log.Error("Error adding file watcher", err)

View File

@@ -12,6 +12,7 @@ import (
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv"
"github.com/docker/libkv/store"
@@ -35,15 +36,17 @@ type KvTLS struct {
InsecureSkipVerify bool
}
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string) {
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) {
for {
chanKeys, err := provider.kvclient.WatchTree(provider.Prefix, make(chan struct{}) /* stop chan */)
events, 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 {
select {
case <-stop:
return
case <-events:
configuration := provider.loadConfig()
if configuration != nil {
configurationChan <- types.ConfigMessage{
@@ -52,11 +55,10 @@ func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix
}
}
}
log.Warnf("Intermittent failure to WatchTree KV. Retrying.")
}
}
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage) error {
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
storeConfig := &store.Config{
ConnectionTimeout: 30 * time.Second,
Bucket: "traefik",
@@ -101,7 +103,9 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage) error
}
provider.kvclient = kv
if provider.Watch {
go provider.watchKv(configurationChan, provider.Prefix)
pool.Go(func(stop chan bool) {
provider.watchKv(configurationChan, provider.Prefix, stop)
})
}
configuration := provider.loadConfig()
configurationChan <- types.ConfigMessage{

View File

@@ -7,6 +7,7 @@ import (
"testing"
"time"
"github.com/containous/traefik/safe"
"github.com/docker/libkv/store"
"reflect"
"sort"
@@ -256,7 +257,9 @@ func TestKvWatchTree(t *testing.T) {
}
configChan := make(chan types.ConfigMessage)
go provider.watchKv(configChan, "prefix")
safe.Go(func() {
provider.watchKv(configChan, "prefix", make(chan bool, 1))
})
select {
case c1 := <-returnedChans:

View File

@@ -10,9 +10,12 @@ import (
"crypto/tls"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/gambol99/go-marathon"
"net/http"
"time"
)
// Marathon holds configuration of the Marathon provider.
@@ -39,50 +42,65 @@ type lightMarathonClient interface {
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage) error {
config := marathon.NewDefaultConfig()
config.URL = provider.Endpoint
config.EventsTransport = marathon.EventsTransportSSE
if provider.Basic != nil {
config.HTTPBasicAuthUser = provider.Basic.HTTPBasicAuthUser
config.HTTPBasicPassword = provider.Basic.HTTPBasicPassword
}
config.HTTPClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: provider.TLS,
},
}
client, err := marathon.NewClient(config)
if err != nil {
log.Errorf("Failed to create a client for marathon, error: %s", err)
return err
}
provider.marathonClient = client
update := make(marathon.EventsChannel, 5)
if provider.Watch {
if err := client.AddEventsListener(update, marathon.EVENTS_APPLICATIONS); err != nil {
log.Errorf("Failed to register for events, %s", err)
} else {
go func() {
func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
operation := func() error {
config := marathon.NewDefaultConfig()
config.URL = provider.Endpoint
config.EventsTransport = marathon.EventsTransportSSE
if provider.Basic != nil {
config.HTTPBasicAuthUser = provider.Basic.HTTPBasicAuthUser
config.HTTPBasicPassword = provider.Basic.HTTPBasicPassword
}
config.HTTPClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: provider.TLS,
},
}
client, err := marathon.NewClient(config)
if err != nil {
log.Errorf("Failed to create a client for marathon, error: %s", err)
return err
}
provider.marathonClient = client
update := make(marathon.EventsChannel, 5)
if provider.Watch {
if err := client.AddEventsListener(update, marathon.EVENTS_APPLICATIONS); err != nil {
log.Errorf("Failed to register for events, %s", err)
return err
}
pool.Go(func(stop chan bool) {
defer close(update)
for {
event := <-update
log.Debug("Marathon event receveived", event)
configuration := provider.loadMarathonConfig()
if configuration != nil {
configurationChan <- types.ConfigMessage{
ProviderName: "marathon",
Configuration: configuration,
select {
case <-stop:
return
case event := <-update:
log.Debug("Marathon event receveived", event)
configuration := provider.loadMarathonConfig()
if configuration != nil {
configurationChan <- types.ConfigMessage{
ProviderName: "marathon",
Configuration: configuration,
}
}
}
}
}()
})
}
configuration := provider.loadMarathonConfig()
configurationChan <- types.ConfigMessage{
ProviderName: "marathon",
Configuration: configuration,
}
return nil
}
configuration := provider.loadMarathonConfig()
configurationChan <- types.ConfigMessage{
ProviderName: "marathon",
Configuration: configuration,
notify := func(err error, time time.Duration) {
log.Errorf("Marathon connection error %+v, retrying in %s", err, time)
}
err := backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)
if err != nil {
log.Fatalf("Cannot connect to Marathon server %+v", err)
}
return nil
}
@@ -96,7 +114,6 @@ func (provider *Marathon) loadMarathonConfig() *types.Configuration {
"getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader,
"getEntryPoints": provider.getEntryPoints,
"getFrontendValue": provider.getFrontendValue,
"getFrontendRule": provider.getFrontendRule,
"getFrontendBackend": provider.getFrontendBackend,
"replace": replace,
@@ -309,22 +326,21 @@ func (provider *Marathon) getEntryPoints(application marathon.Application) []str
return []string{}
}
// getFrontendValue returns the frontend value for the specified application, using
// it's label. It returns a default one if the label is not present.
func (provider *Marathon) getFrontendValue(application marathon.Application) string {
if label, err := provider.getLabel(application, "traefik.frontend.value"); err == nil {
return label
}
return getEscapedName(application.ID) + "." + provider.Domain
}
// getFrontendRule returns the frontend rule for the specified application, using
// it's label. It returns a default one (Host) if the label is not present.
func (provider *Marathon) getFrontendRule(application marathon.Application) string {
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
// TODO: backwards compatibility with DEPRECATED rule.Value
if value, err := provider.getLabel(application, "traefik.frontend.value"); err == nil {
log.Warnf("Label traefik.frontend.value=%s is DEPRECATED, please refer to the rule label: https://github.com/containous/traefik/blob/master/docs/index.md#marathon", value)
rule, _ := provider.getLabel(application, "traefik.frontend.rule")
return rule + ":" + value
}
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
return label
}
return "Host"
return "Host:" + getEscapedName(application.ID) + "." + provider.Domain
}
func (provider *Marathon) getBackend(task marathon.Task, applications []marathon.Application) string {

View File

@@ -86,8 +86,7 @@ func TestMarathonLoadConfig(t *testing.T) {
EntryPoints: []string{},
Routes: map[string]types.Route{
`route-host-test`: {
Rule: "Host",
Value: "test.docker.localhost",
Rule: "Host:test.docker.localhost",
},
},
},
@@ -831,7 +830,7 @@ func TestMarathonGetEntryPoints(t *testing.T) {
}
}
func TestMarathonGetFrontendValue(t *testing.T) {
func TestMarathonGetFrontendRule(t *testing.T) {
provider := &Marathon{
Domain: "docker.localhost",
}
@@ -842,50 +841,21 @@ func TestMarathonGetFrontendValue(t *testing.T) {
}{
{
application: marathon.Application{},
expected: ".docker.localhost",
expected: "Host:.docker.localhost",
},
{
application: marathon.Application{
ID: "test",
},
expected: "test.docker.localhost",
expected: "Host:test.docker.localhost",
},
{
application: marathon.Application{
Labels: map[string]string{
"traefik.frontend.value": "foo.bar",
"traefik.frontend.rule": "Host:foo.bar",
},
},
expected: "foo.bar",
},
}
for _, a := range applications {
actual := provider.getFrontendValue(a.application)
if actual != a.expected {
t.Fatalf("expected %q, got %q", a.expected, actual)
}
}
}
func TestMarathonGetFrontendRule(t *testing.T) {
provider := &Marathon{}
applications := []struct {
application marathon.Application
expected string
}{
{
application: marathon.Application{},
expected: "Host",
},
{
application: marathon.Application{
Labels: map[string]string{
"traefik.frontend.rule": "Header",
},
},
expected: "Header",
expected: "Host:foo.bar",
},
}

View File

@@ -8,14 +8,16 @@ import (
"github.com/BurntSushi/toml"
"github.com/containous/traefik/autogen"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"unicode"
)
// Provider defines methods of a provider.
type Provider interface {
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
Provide(configurationChan chan<- types.ConfigMessage) error
Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error
}
// BaseProvider should be inherited by providers
@@ -67,3 +69,11 @@ func replace(s1 string, s2 string, s3 string) string {
func getEscapedName(name string) string {
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
}
func normalize(name string) string {
fargs := func(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
}
// get function
return strings.Join(strings.FieldsFunc(name, fargs), "-")
}

View File

@@ -168,3 +168,41 @@ func TestReplace(t *testing.T) {
}
}
}
func TestGetConfigurationReturnsCorrectMaxConnConfiguration(t *testing.T) {
templateFile, err := ioutil.TempFile("", "provider-configuration")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(templateFile.Name())
data := []byte(`[backends]
[backends.backend1]
[backends.backend1.maxconn]
amount = 10
extractorFunc = "request.host"`)
err = ioutil.WriteFile(templateFile.Name(), data, 0700)
if err != nil {
t.Fatal(err)
}
provider := &myProvider{
BaseProvider{
Filename: templateFile.Name(),
},
}
configuration, err := provider.getConfiguration(templateFile.Name(), nil, nil)
if err != nil {
t.Fatalf("Shouldn't have error out, got %v", err)
}
if configuration == nil {
t.Fatalf("Configuration should not be nil, but was")
}
if configuration.Backends["backend1"].MaxConn.Amount != 10 {
t.Fatalf("Configuration did not parse MaxConn.Amount properly")
}
if configuration.Backends["backend1"].MaxConn.ExtractorFunc != "request.host" {
t.Fatalf("Configuration did not parse MaxConn.ExtractorFunc properly")
}
}

View File

@@ -1,6 +1,7 @@
package provider
import (
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/zookeeper"
@@ -13,8 +14,8 @@ type Zookepper struct {
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage) error {
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
provider.storeType = store.ZK
zookeeper.Register()
return provider.provide(configurationChan)
return provider.provide(configurationChan, pool)
}

147
rules.go Normal file
View File

@@ -0,0 +1,147 @@
package main
import (
"errors"
"github.com/gorilla/mux"
"net"
"net/http"
"reflect"
"sort"
"strings"
)
// Rules holds rule parsing and configuration
type Rules struct {
route *serverRoute
err error
}
func (r *Rules) host(hosts ...string) *mux.Route {
return r.route.route.MatcherFunc(func(req *http.Request, route *mux.RouteMatch) bool {
reqHost, _, err := net.SplitHostPort(req.Host)
if err != nil {
reqHost = req.Host
}
for _, host := range hosts {
if reqHost == strings.TrimSpace(host) {
return true
}
}
return false
})
}
func (r *Rules) hostRegexp(hosts ...string) *mux.Route {
router := r.route.route.Subrouter()
for _, host := range hosts {
router.Host(strings.TrimSpace(host))
}
return r.route.route
}
func (r *Rules) path(paths ...string) *mux.Route {
router := r.route.route.Subrouter()
for _, path := range paths {
router.Path(strings.TrimSpace(path))
}
return r.route.route
}
func (r *Rules) pathPrefix(paths ...string) *mux.Route {
router := r.route.route.Subrouter()
for _, path := range paths {
router.PathPrefix(strings.TrimSpace(path))
}
return r.route.route
}
type bySize []string
func (a bySize) Len() int { return len(a) }
func (a bySize) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a bySize) Less(i, j int) bool { return len(a[i]) > len(a[j]) }
func (r *Rules) pathStrip(paths ...string) *mux.Route {
sort.Sort(bySize(paths))
r.route.stripPrefixes = paths
router := r.route.route.Subrouter()
for _, path := range paths {
router.Path(strings.TrimSpace(path))
}
return r.route.route
}
func (r *Rules) pathPrefixStrip(paths ...string) *mux.Route {
sort.Sort(bySize(paths))
r.route.stripPrefixes = paths
router := r.route.route.Subrouter()
for _, path := range paths {
router.PathPrefix(strings.TrimSpace(path))
}
return r.route.route
}
func (r *Rules) methods(methods ...string) *mux.Route {
return r.route.route.Methods(methods...)
}
func (r *Rules) headers(headers ...string) *mux.Route {
return r.route.route.Headers(headers...)
}
func (r *Rules) headersRegexp(headers ...string) *mux.Route {
return r.route.route.HeadersRegexp(headers...)
}
// Parse parses rules expressions
func (r *Rules) Parse(expression string) (*mux.Route, error) {
functions := map[string]interface{}{
"Host": r.host,
"HostRegexp": r.hostRegexp,
"Path": r.path,
"PathStrip": r.pathStrip,
"PathPrefix": r.pathPrefix,
"PathPrefixStrip": r.pathPrefixStrip,
"Method": r.methods,
"Headers": r.headers,
"HeadersRegexp": r.headersRegexp,
}
f := func(c rune) bool {
return c == ':'
}
// get function
parsedFunctions := strings.FieldsFunc(expression, f)
if len(parsedFunctions) == 0 {
return nil, errors.New("Error parsing rule: " + expression)
}
parsedFunction, ok := functions[parsedFunctions[0]]
if !ok {
return nil, errors.New("Error parsing rule: " + expression + ". Unknow function: " + parsedFunctions[0])
}
parsedFunctions = append(parsedFunctions[:0], parsedFunctions[1:]...)
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
parsedArgs := strings.FieldsFunc(strings.Join(parsedFunctions, ":"), fargs)
if len(parsedArgs) == 0 {
return nil, errors.New("Error parsing args from rule: " + expression)
}
inputs := make([]reflect.Value, len(parsedArgs))
for i := range parsedArgs {
inputs[i] = reflect.ValueOf(parsedArgs[i])
}
method := reflect.ValueOf(parsedFunction)
if method.IsValid() {
resultRoute := method.Call(inputs)[0].Interface().(*mux.Route)
if r.err != nil {
return nil, r.err
}
if resultRoute.GetError() != nil {
return nil, resultRoute.GetError()
}
return resultRoute, nil
}
return nil, errors.New("Method not found: " + parsedFunctions[0])
}

70
safe/routine.go Normal file
View File

@@ -0,0 +1,70 @@
package safe
import (
"log"
"runtime/debug"
"sync"
)
type routine struct {
goroutine func(chan bool)
stop chan bool
}
// Pool creates a pool of go routines
type Pool struct {
routines []routine
waitGroup sync.WaitGroup
lock sync.Mutex
}
// Go starts a recoverable goroutine, and can be stopped with stop chan
func (p *Pool) Go(goroutine func(stop chan bool)) {
p.lock.Lock()
newRoutine := routine{
goroutine: goroutine,
stop: make(chan bool, 1),
}
p.routines = append(p.routines, newRoutine)
p.waitGroup.Add(1)
Go(func() {
goroutine(newRoutine.stop)
p.waitGroup.Done()
})
p.lock.Unlock()
}
// Stop stops all started routines, waiting for their termination
func (p *Pool) Stop() {
p.lock.Lock()
for _, routine := range p.routines {
routine.stop <- true
}
p.waitGroup.Wait()
for _, routine := range p.routines {
close(routine.stop)
}
p.lock.Unlock()
}
// Go starts a recoverable goroutine
func Go(goroutine func()) {
GoWithRecover(goroutine, defaultRecoverGoroutine)
}
// GoWithRecover starts a recoverable goroutine using given customRecover() function
func GoWithRecover(goroutine func(), customRecover func(err interface{})) {
go func() {
defer func() {
if err := recover(); err != nil {
customRecover(err)
}
}()
goroutine()
}()
}
func defaultRecoverGoroutine(err interface{}) {
log.Println(err)
debug.PrintStack()
}

30
safe/safe.go Normal file
View File

@@ -0,0 +1,30 @@
package safe
import (
"sync"
)
// Safe contains a thread-safe value
type Safe struct {
value interface{}
lock sync.RWMutex
}
// New create a new Safe instance given a value
func New(value interface{}) *Safe {
return &Safe{value: value, lock: sync.RWMutex{}}
}
// Get returns the value
func (s *Safe) Get() interface{} {
s.lock.RLock()
defer s.lock.RUnlock()
return s.value
}
// Set sets a new value
func (s *Safe) Set(value interface{}) {
s.lock.Lock()
defer s.lock.Unlock()
s.value = value
}

View File

@@ -2,34 +2,64 @@
set -e
if ([ "$TRAVIS_BRANCH" = "master" ] || [ ! -z "$TRAVIS_TAG" ]) && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
echo "Deploying"
echo "Deploying..."
else
echo "Skipping deploy"
exit 0
fi
curl -LO https://github.com/tcnksm/ghr/releases/download/pre-release/linux_amd64.zip
git config --global user.email "emile@vauge.com"
git config --global user.name "Emile Vauge"
# load ssh key
echo "Loading key..."
openssl aes-256-cbc -d -k "$pass" -in .travis/traefik.id_rsa.enc -out ~/.ssh/traefik.id_rsa
eval "$(ssh-agent -s)"
chmod 600 ~/.ssh/traefik.id_rsa
ssh-add ~/.ssh/traefik.id_rsa
# download github release
echo "Downloading ghr..."
curl -LOs https://github.com/tcnksm/ghr/releases/download/pre-release/linux_amd64.zip
unzip -q linux_amd64.zip
sudo mv ghr /usr/bin/ghr
sudo chmod +x /usr/bin/ghr
# github release and tag
echo "Github release..."
ghr -t $GITHUB_TOKEN -u containous -r traefik --prerelease ${VERSION} dist/
# update docs.traefik.io
echo "Generating and updating documentation..."
# DOESN'T WORK :'(
# git remote add ssh git@github.com:containous/traefik.git
# mkdocs gh-deploy -m $VERSION -c -r ssh
mkdir site
cd site
git init
git remote add origin git@github.com:containous/traefik.git
git fetch origin
git checkout gh-pages
cd ..
mkdocs build --clean
cd site
git add .
echo $VERSION | git commit --file -
git push -q -f origin gh-pages > /dev/null 2>&1
# 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
echo "Updating traefik-library-imag repo..."
git clone git@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
git push -q --follow-tags -u origin master > /dev/null 2>&1
# create docker image emilevauge/traefik (compatibility)
echo "Updating docker emilevauge/traefik image..."
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
docker tag containous/traefik emilevauge/traefik:latest
docker push emilevauge/traefik:latest

233
server.go
View File

@@ -15,21 +15,24 @@ import (
"regexp"
"sort"
"strconv"
"sync"
"syscall"
"time"
log "github.com/Sirupsen/logrus"
"github.com/codegangsta/negroni"
"github.com/containous/oxy/cbreaker"
"github.com/containous/oxy/connlimit"
"github.com/containous/oxy/forward"
"github.com/containous/oxy/roundrobin"
"github.com/containous/oxy/stream"
"github.com/containous/oxy/utils"
"github.com/containous/traefik/middlewares"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/gorilla/mux"
"github.com/mailgun/manners"
"github.com/streamrail/concurrent-map"
)
var oxyLogger = &OxyLogger{}
@@ -42,10 +45,10 @@ type Server struct {
signals chan os.Signal
stopChan chan bool
providers []provider.Provider
serverLock sync.Mutex
currentConfigurations configs
currentConfigurations safe.Safe
globalConfiguration GlobalConfiguration
loggerMiddleware *middlewares.Logger
routinesPool safe.Pool
}
type serverEntryPoints map[string]*serverEntryPoint
@@ -55,6 +58,11 @@ type serverEntryPoint struct {
httpRouter *middlewares.HandlerSwitcher
}
type serverRoute struct {
route *mux.Route
stripPrefixes []string
}
// NewServer returns an initialized Server.
func NewServer(globalConfiguration GlobalConfiguration) *Server {
server := new(Server)
@@ -63,10 +71,11 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
server.configurationChan = make(chan types.ConfigMessage, 10)
server.configurationValidatedChan = make(chan types.ConfigMessage, 10)
server.signals = make(chan os.Signal, 1)
server.stopChan = make(chan bool)
server.stopChan = make(chan bool, 1)
server.providers = []provider.Provider{}
signal.Notify(server.signals, syscall.SIGINT, syscall.SIGTERM)
server.currentConfigurations = make(configs)
currentConfigurations := make(configs)
server.currentConfigurations.Set(currentConfigurations)
server.globalConfiguration = globalConfiguration
server.loggerMiddleware = middlewares.NewLogger(globalConfiguration.AccessLogsFile)
@@ -76,8 +85,12 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
// Start starts the server and blocks until server is shutted down.
func (server *Server) Start() {
server.startHTTPServers()
go server.listenProviders()
go server.listenConfigurations()
server.routinesPool.Go(func(stop chan bool) {
server.listenProviders(stop)
})
server.routinesPool.Go(func(stop chan bool) {
server.listenConfigurations(stop)
})
server.configureProviders()
server.startProviders()
go server.listenSignals()
@@ -94,6 +107,7 @@ func (server *Server) Stop() {
// Close destroys the server
func (server *Server) Close() {
server.routinesPool.Stop()
close(server.configurationChan)
close(server.configurationValidatedChan)
close(server.signals)
@@ -114,58 +128,79 @@ func (server *Server) startHTTPServers() {
}
}
func (server *Server) listenProviders() {
lastReceivedConfiguration := time.Unix(0, 0)
lastConfigs := make(map[string]*types.ConfigMessage)
func (server *Server) listenProviders(stop chan bool) {
lastReceivedConfiguration := safe.New(time.Unix(0, 0))
lastConfigs := cmap.New()
for {
configMsg := <-server.configurationChan
jsonConf, _ := json.Marshal(configMsg.Configuration)
log.Debugf("Configuration received from provider %s: %s", configMsg.ProviderName, string(jsonConf))
lastConfigs[configMsg.ProviderName] = &configMsg
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
// last config received more than n s ago
server.configurationValidatedChan <- configMsg
} else {
log.Debugf("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
go func() {
<-time.After(server.globalConfiguration.ProvidersThrottleDuration)
if time.Now().After(lastReceivedConfiguration.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
log.Debugf("Waited for %s config, OK", configMsg.ProviderName)
server.configurationValidatedChan <- *lastConfigs[configMsg.ProviderName]
}
}()
select {
case <-stop:
return
case configMsg, ok := <-server.configurationChan:
if !ok {
return
}
jsonConf, _ := json.Marshal(configMsg.Configuration)
log.Debugf("Configuration received from provider %s: %s", configMsg.ProviderName, string(jsonConf))
lastConfigs.Set(configMsg.ProviderName, &configMsg)
lastReceivedConfigurationValue := lastReceivedConfiguration.Get().(time.Time)
if time.Now().After(lastReceivedConfigurationValue.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
log.Debugf("Last %s config received more than %s, OK", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
// last config received more than n s ago
server.configurationValidatedChan <- configMsg
} else {
log.Debugf("Last %s config received less than %s, waiting...", configMsg.ProviderName, server.globalConfiguration.ProvidersThrottleDuration)
server.routinesPool.Go(func(stop chan bool) {
select {
case <-stop:
return
case <-time.After(server.globalConfiguration.ProvidersThrottleDuration):
lastReceivedConfigurationValue := lastReceivedConfiguration.Get().(time.Time)
if time.Now().After(lastReceivedConfigurationValue.Add(time.Duration(server.globalConfiguration.ProvidersThrottleDuration))) {
log.Debugf("Waited for %s config, OK", configMsg.ProviderName)
if lastConfig, ok := lastConfigs.Get(configMsg.ProviderName); ok {
server.configurationValidatedChan <- *lastConfig.(*types.ConfigMessage)
}
}
}
})
}
lastReceivedConfiguration.Set(time.Now())
}
lastReceivedConfiguration = time.Now()
}
}
func (server *Server) listenConfigurations() {
func (server *Server) listenConfigurations(stop chan bool) {
for {
configMsg := <-server.configurationValidatedChan
if configMsg.Configuration == nil {
log.Info("Skipping empty Configuration")
} else if reflect.DeepEqual(server.currentConfigurations[configMsg.ProviderName], configMsg.Configuration) {
log.Info("Skipping same configuration")
} else {
// Copy configurations to new map so we don't change current if LoadConfig fails
newConfigurations := make(configs)
for k, v := range server.currentConfigurations {
newConfigurations[k] = v
select {
case <-stop:
return
case configMsg, ok := <-server.configurationValidatedChan:
if !ok {
return
}
newConfigurations[configMsg.ProviderName] = configMsg.Configuration
newServerEntryPoints, err := server.loadConfig(newConfigurations, server.globalConfiguration)
if err == nil {
server.serverLock.Lock()
for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints {
server.serverEntryPoints[newServerEntryPointName].httpRouter.UpdateHandler(newServerEntryPoint.httpRouter.GetHandler())
log.Infof("Server configuration reloaded on %s", server.serverEntryPoints[newServerEntryPointName].httpServer.Addr)
}
server.currentConfigurations = newConfigurations
server.serverLock.Unlock()
currentConfigurations := server.currentConfigurations.Get().(configs)
if configMsg.Configuration == nil {
log.Info("Skipping empty Configuration")
} else if reflect.DeepEqual(currentConfigurations[configMsg.ProviderName], configMsg.Configuration) {
log.Info("Skipping same configuration")
} else {
log.Error("Error loading new configuration, aborted ", err)
// Copy configurations to new map so we don't change current if LoadConfig fails
newConfigurations := make(configs)
for k, v := range currentConfigurations {
newConfigurations[k] = v
}
newConfigurations[configMsg.ProviderName] = configMsg.Configuration
newServerEntryPoints, err := server.loadConfig(newConfigurations, server.globalConfiguration)
if err == nil {
for newServerEntryPointName, newServerEntryPoint := range newServerEntryPoints {
server.serverEntryPoints[newServerEntryPointName].httpRouter.UpdateHandler(newServerEntryPoint.httpRouter.GetHandler())
log.Infof("Server configuration reloaded on %s", server.serverEntryPoints[newServerEntryPointName].httpServer.Addr)
}
server.currentConfigurations.Set(newConfigurations)
} else {
log.Error("Error loading new configuration, aborted ", err)
}
}
}
}
@@ -209,12 +244,12 @@ func (server *Server) startProviders() {
jsonConf, _ := json.Marshal(provider)
log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf)
currentProvider := provider
go func() {
err := currentProvider.Provide(server.configurationChan)
safe.Go(func() {
err := currentProvider.Provide(server.configurationChan, &server.routinesPool)
if err != nil {
log.Errorf("Error starting provider %s", err)
}
}()
})
}
}
@@ -354,23 +389,22 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
if _, ok := serverEntryPoints[entryPointName]; !ok {
return nil, errors.New("Undefined entrypoint: " + entryPointName)
}
newRoute := serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName)
newServerRoute := &serverRoute{route: 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)
route, err := getRoute(newRoute, route.Rule, route.Value)
err := getRoute(newServerRoute, &route)
if err != nil {
return nil, err
}
newRoute = route
log.Debugf("Creating route %s %s", routeName, route.Rule)
}
entryPoint := globalConfiguration.EntryPoints[entryPointName]
if entryPoint.Redirect != nil {
if redirectHandlers[entryPointName] != nil {
newRoute.Handler(redirectHandlers[entryPointName])
newServerRoute.route.Handler(redirectHandlers[entryPointName])
} else if handler, err := server.loadEntryPointConfig(entryPointName, entryPoint); err != nil {
return nil, err
} else {
newRoute.Handler(handler)
newServerRoute.route.Handler(handler)
redirectHandlers[entryPointName] = handler
}
} else {
@@ -414,9 +448,21 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
}
}
}
maxConns := configuration.Backends[frontend.Backend].MaxConn
if maxConns != nil && maxConns.Amount != 0 {
extractFunc, err := utils.NewExtractor(maxConns.ExtractorFunc)
if err != nil {
return nil, err
}
log.Debugf("Creating loadd-balancer connlimit")
lb, err = connlimit.New(lb, extractFunc, maxConns.Amount, connlimit.Logger(oxyLogger))
if err != nil {
return nil, err
}
}
// retry ?
if globalConfiguration.Retry != nil {
retries := len(configuration.Backends[frontend.Backend].Servers) - 1
retries := len(configuration.Backends[frontend.Backend].Servers)
if globalConfiguration.Retry.Attempts > 0 {
retries = globalConfiguration.Retry.Attempts
}
@@ -448,9 +494,9 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
} else {
log.Debugf("Reusing backend %s", frontend.Backend)
}
server.wireFrontendBackend(frontend.Routes, newRoute, backends[frontend.Backend])
server.wireFrontendBackend(newServerRoute, backends[frontend.Backend])
}
err := newRoute.GetError()
err := newServerRoute.route.GetError()
if err != nil {
log.Errorf("Error building route: %s", err)
}
@@ -460,29 +506,15 @@ 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) {
func (server *Server) wireFrontendBackend(serverRoute *serverRoute, 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)
if len(serverRoute.stripPrefixes) > 0 {
serverRoute.route.Handler(&middlewares.StripPrefix{
Prefixes: serverRoute.stripPrefixes,
Handler: handler,
})
} else {
serverRoute.route.Handler(handler)
}
}
@@ -522,32 +554,29 @@ func (server *Server) buildDefaultHTTPRouter() *mux.Router {
return router
}
func getRoute(any interface{}, rule string, value ...interface{}) (*mux.Route, error) {
switch rule {
case "PathStrip":
rule = "Path"
case "PathPrefixStrip":
rule = "PathPrefix"
func getRoute(serverRoute *serverRoute, route *types.Route) error {
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
// TODO: backwards compatibility with DEPRECATED rule.Value
if len(route.Value) > 0 {
route.Rule += ":" + route.Value
log.Warnf("Value %s is DEPRECATED (will be removed in v1.0.0), please refer to the new frontend notation: https://github.com/containous/traefik/blob/master/docs/index.md#-frontends", route.Value)
}
inputs := make([]reflect.Value, len(value))
for i := range value {
inputs[i] = reflect.ValueOf(value[i])
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
rules := Rules{route: serverRoute}
newRoute, err := rules.Parse(route.Rule)
if err != nil {
return err
}
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)
serverRoute.route = newRoute
return nil
}
func sortedFrontendNamesForConfig(configuration *types.Configuration) []string {
keys := []string{}
for key := range configuration.Frontends {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}

View File

@@ -1,13 +1,40 @@
[backends]{{range .Nodes}}
[backends.backend-{{getBackend .}}.servers.server-{{.Node.Node | replace "." "-"}}-{{.Service.Port}}]
url = "http://{{.Node.Address}}:{{.Service.Port}}"
[backends]
{{range .Nodes}}
{{if ne (getAttribute "enable" .Service.Tags "true") "false"}}
[backends.backend-{{getBackend .}}.servers.{{.Service.Service | replace "." "-"}}--{{.Service.Address | replace "." "-"}}--{{.Service.Port}}]
url = "{{getAttribute "protocol" .Service.Tags "http"}}://{{.Service.Address}}:{{.Service.Port}}"
{{$weight := getAttribute "backend.weight" .Service.Tags ""}}
{{with $weight}}
weight = {{$weight}}
{{end}}
{{end}}
{{end}}
{{range .Services}}
{{$service := .ServiceName}}
{{$circuitBreaker := getAttribute "backend.circuitbreaker" .Attributes ""}}
{{with $circuitBreaker}}
[backends.backend-{{$service}}.circuitbreaker]
expression = "{{$circuitBreaker}}"
{{end}}
{{$loadBalancer := getAttribute "backend.loadbalancer" .Attributes ""}}
{{with $loadBalancer}}
[backends.backend-{{$service}}.loadbalancer]
method = "{{$loadBalancer}}"
{{end}}
{{end}}
[frontends]{{range .Services}}
[frontends.frontend-{{.}}]
backend = "backend-{{.}}"
passHostHeader = false
[frontends.frontend-{{.}}.routes.route-host-{{.}}]
rule = "Host"
value = "{{getFrontendValue .}}"
[frontends.frontend-{{.ServiceName}}]
backend = "backend-{{.ServiceName}}"
passHostHeader = {{getAttribute "frontend.passHostHeader" .Attributes "false"}}
{{$entryPoints := getAttribute "frontend.entrypoints" .Attributes ""}}
{{with $entryPoints}}
entrypoints = [{{range getEntryPoints $entryPoints}}
"{{.}}",
{{end}}]
{{end}}
[frontends.frontend-{{.ServiceName}}.routes.route-host-{{.ServiceName}}]
rule = "{{getFrontendRule .}}"
{{end}}

View File

@@ -13,5 +13,4 @@
{{end}}]
[frontends."frontend-{{$frontend}}".routes."route-frontend-{{$frontend}}"]
rule = "{{getFrontendRule $container}}"
value = "{{getFrontendValue $container}}"
{{end}}

View File

@@ -17,6 +17,16 @@
method = "{{$loadBalancer}}"
{{end}}
{{$maxConnAmt := Get "" . "/maxconn/" "amount"}}
{{$maxConnExtractorFunc := Get "" . "/maxconn/" "extractorfunc"}}
{{with $maxConnAmt}}
{{with $maxConnExtractorFunc}}
[backends.{{Last $backend}}.maxConn]
amount = {{$maxConnAmt}}
extractorFunc = "{{$maxConnExtractorFunc}}"
{{end}}
{{end}}
{{range $servers}}
[backends.{{Last $backend}}.servers.{{Last .}}]
url = "{{Get "" . "/url"}}"
@@ -27,7 +37,7 @@
[frontends]{{range $frontends}}
{{$frontend := Last .}}
{{$entryPoints := SplitGet . "/entrypoints"}}
[frontends.{{$frontend}}]
[frontends."{{$frontend}}"]
backend = "{{Get "" . "/backend"}}"
passHostHeader = {{Get "false" . "/passHostHeader"}}
entryPoints = [{{range $entryPoints}}
@@ -35,8 +45,7 @@
{{end}}]
{{$routes := List . "/routes/"}}
{{range $routes}}
[frontends.{{$frontend}}.routes.{{Last .}}]
[frontends."{{$frontend}}".routes."{{Last .}}"]
rule = "{{Get "" . "/rule"}}"
value = "{{Get "" . "/value"}}"
{{end}}
{{end}}

View File

@@ -14,5 +14,4 @@
{{end}}]
[frontends.frontend{{.ID | replace "/" "-"}}.routes.route-host{{.ID | replace "/" "-"}}]
rule = "{{getFrontendRule .}}"
value = "{{getFrontendValue .}}"
{{end}}

View File

@@ -513,17 +513,14 @@
# [frontends.frontend1]
# backend = "backend2"
# [frontends.frontend1.routes.test_1]
# rule = "Host"
# value = "test.localhost"
# rule = "Host: test.localhost, other.localhost"
# [frontends.frontend2]
# backend = "backend1"
# passHostHeader = true
# entrypoints = ["https"] # overrides defaultEntryPoints
# [frontends.frontend2.routes.test_1]
# rule = "Host"
# value = "{subdomain:[a-z]+}.localhost"
# rule = "Host:{subdomain:[a-z]+}.localhost"
# [frontends.frontend3]
# entrypoints = ["http", "https"] # overrides defaultEntryPoints
# backend = "backend2"
# rule = "Path"
# value = "/test"
# rule = "Path: /test, /other"

View File

@@ -10,6 +10,13 @@ type Backend struct {
Servers map[string]Server `json:"servers,omitempty"`
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty"`
LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"`
MaxConn *MaxConn `json:"maxConn,omitempty"`
}
// MaxConn holds maximum connection configuraiton
type MaxConn struct {
Amount int64 `json:"amount,omitempty"`
ExtractorFunc string `json:"extractorFunc,omitempty"`
}
// LoadBalancer holds load balancing configuration.
@@ -30,8 +37,11 @@ type Server struct {
// Route holds route configuration.
type Route struct {
Rule string `json:"rule,omitempty"`
Rule string `json:"rule,omitempty"`
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
// TODO: backwards compatibility with DEPRECATED rule.Value
Value string `json:"value,omitempty"`
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
}
// Frontend holds frontend configuration.

33
web.go
View File

@@ -8,6 +8,7 @@ import (
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/autogen"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/elazarl/go-bindata-assetfs"
"github.com/gorilla/mux"
@@ -34,7 +35,7 @@ var (
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage) error {
func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
systemRouter := mux.NewRouter()
// health route
@@ -104,13 +105,15 @@ func (provider *WebProvider) getHealthHandler(response http.ResponseWriter, requ
}
func (provider *WebProvider) getConfigHandler(response http.ResponseWriter, request *http.Request) {
templatesRenderer.JSON(response, http.StatusOK, provider.server.currentConfigurations)
currentConfigurations := provider.server.currentConfigurations.Get().(configs)
templatesRenderer.JSON(response, http.StatusOK, currentConfigurations)
}
func (provider *WebProvider) getProviderHandler(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
providerID := vars["provider"]
if provider, ok := provider.server.currentConfigurations[providerID]; ok {
currentConfigurations := provider.server.currentConfigurations.Get().(configs)
if provider, ok := currentConfigurations[providerID]; ok {
templatesRenderer.JSON(response, http.StatusOK, provider)
} else {
http.NotFound(response, request)
@@ -120,7 +123,8 @@ func (provider *WebProvider) getProviderHandler(response http.ResponseWriter, re
func (provider *WebProvider) getBackendsHandler(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
providerID := vars["provider"]
if provider, ok := provider.server.currentConfigurations[providerID]; ok {
currentConfigurations := provider.server.currentConfigurations.Get().(configs)
if provider, ok := currentConfigurations[providerID]; ok {
templatesRenderer.JSON(response, http.StatusOK, provider.Backends)
} else {
http.NotFound(response, request)
@@ -131,7 +135,8 @@ func (provider *WebProvider) getBackendHandler(response http.ResponseWriter, req
vars := mux.Vars(request)
providerID := vars["provider"]
backendID := vars["backend"]
if provider, ok := provider.server.currentConfigurations[providerID]; ok {
currentConfigurations := provider.server.currentConfigurations.Get().(configs)
if provider, ok := currentConfigurations[providerID]; ok {
if backend, ok := provider.Backends[backendID]; ok {
templatesRenderer.JSON(response, http.StatusOK, backend)
return
@@ -144,7 +149,8 @@ func (provider *WebProvider) getServersHandler(response http.ResponseWriter, req
vars := mux.Vars(request)
providerID := vars["provider"]
backendID := vars["backend"]
if provider, ok := provider.server.currentConfigurations[providerID]; ok {
currentConfigurations := provider.server.currentConfigurations.Get().(configs)
if provider, ok := currentConfigurations[providerID]; ok {
if backend, ok := provider.Backends[backendID]; ok {
templatesRenderer.JSON(response, http.StatusOK, backend.Servers)
return
@@ -158,7 +164,8 @@ func (provider *WebProvider) getServerHandler(response http.ResponseWriter, requ
providerID := vars["provider"]
backendID := vars["backend"]
serverID := vars["server"]
if provider, ok := provider.server.currentConfigurations[providerID]; ok {
currentConfigurations := provider.server.currentConfigurations.Get().(configs)
if provider, ok := currentConfigurations[providerID]; ok {
if backend, ok := provider.Backends[backendID]; ok {
if server, ok := backend.Servers[serverID]; ok {
templatesRenderer.JSON(response, http.StatusOK, server)
@@ -172,7 +179,8 @@ func (provider *WebProvider) getServerHandler(response http.ResponseWriter, requ
func (provider *WebProvider) getFrontendsHandler(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
providerID := vars["provider"]
if provider, ok := provider.server.currentConfigurations[providerID]; ok {
currentConfigurations := provider.server.currentConfigurations.Get().(configs)
if provider, ok := currentConfigurations[providerID]; ok {
templatesRenderer.JSON(response, http.StatusOK, provider.Frontends)
} else {
http.NotFound(response, request)
@@ -183,7 +191,8 @@ func (provider *WebProvider) getFrontendHandler(response http.ResponseWriter, re
vars := mux.Vars(request)
providerID := vars["provider"]
frontendID := vars["frontend"]
if provider, ok := provider.server.currentConfigurations[providerID]; ok {
currentConfigurations := provider.server.currentConfigurations.Get().(configs)
if provider, ok := currentConfigurations[providerID]; ok {
if frontend, ok := provider.Frontends[frontendID]; ok {
templatesRenderer.JSON(response, http.StatusOK, frontend)
return
@@ -196,7 +205,8 @@ func (provider *WebProvider) getRoutesHandler(response http.ResponseWriter, requ
vars := mux.Vars(request)
providerID := vars["provider"]
frontendID := vars["frontend"]
if provider, ok := provider.server.currentConfigurations[providerID]; ok {
currentConfigurations := provider.server.currentConfigurations.Get().(configs)
if provider, ok := currentConfigurations[providerID]; ok {
if frontend, ok := provider.Frontends[frontendID]; ok {
templatesRenderer.JSON(response, http.StatusOK, frontend.Routes)
return
@@ -210,7 +220,8 @@ func (provider *WebProvider) getRouteHandler(response http.ResponseWriter, reque
providerID := vars["provider"]
frontendID := vars["frontend"]
routeID := vars["route"]
if provider, ok := provider.server.currentConfigurations[providerID]; ok {
currentConfigurations := provider.server.currentConfigurations.Get().(configs)
if provider, ok := currentConfigurations[providerID]; ok {
if frontend, ok := provider.Frontends[frontendID]; ok {
if route, ok := frontend.Routes[routeID]; ok {
templatesRenderer.JSON(response, http.StatusOK, route)

View File

@@ -7,12 +7,10 @@
<tr>
<td><em>Route</em></td>
<td><em>Rule</em></td>
<td><em>Value</em></td>
</tr>
<tr data-ng-repeat="(routeId, route) in frontendCtrl.frontend.routes">
<td>{{routeId}}</td>
<td>{{route.rule}}</td>
<td><code>{{route.value}}</code></td>
<td><code>{{route.rule}}</code></td>
</tr>
</table>
</div>

View File

@@ -24,3 +24,11 @@
.tabset-row__providers {
margin-top: 3rem;
}
table {
table-layout: fixed;
}
td, th {
word-wrap: break-word;
}

View File

@@ -30,7 +30,7 @@
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand traefik-text" ui-sref="provider"><img src="traefik.icon.png"/></a>
<a class="navbar-brand traefik-text" ui-sref="provider"><img height="16" src="traefik.icon.png"/></a>
</div>
<div class="collapse navbar-collapse">