Merge pull request #773 from containous/merge-v1.1.0-rc2

Merge v1.1.0 rc2
This commit is contained in:
Emile Vauge 2016-10-25 09:04:07 +02:00 committed by GitHub
commit 14db2343c9
27 changed files with 575 additions and 273 deletions

View file

@ -1,5 +1,38 @@
# Change Log # Change Log
## [v1.1.0-rc2](https://github.com/containous/traefik/tree/v1.1.0-rc2) (2016-10-17)
[Full Changelog](https://github.com/containous/traefik/compare/v1.1.0-rc1...v1.1.0-rc2)
**Implemented enhancements:**
- Support healthcheck if present for docker [\#666](https://github.com/containous/traefik/issues/666)
**Closed issues:**
- Sensible configuration for consulCatalog [\#737](https://github.com/containous/traefik/issues/737)
- Traefik ignoring container listening in more than one TCP port [\#734](https://github.com/containous/traefik/issues/734)
- Error when using HA acme in kubernetes with etcd [\#725](https://github.com/containous/traefik/issues/725)
- \[Docker swarm mode\] No round robin when using service [\#718](https://github.com/containous/traefik/issues/718)
- Dose it support docker swarm mode [\#712](https://github.com/containous/traefik/issues/712)
- Kubernetes - Undefined backend [\#710](https://github.com/containous/traefik/issues/710)
- Constraints on Consul Catalogue not working as expected [\#703](https://github.com/containous/traefik/issues/703)
- docker run syntax in swarm example has changed [\#528](https://github.com/containous/traefik/issues/528)
- Secure WebSockets [\#467](https://github.com/containous/traefik/issues/467)
**Merged pull requests:**
- Fix case sensitive host [\#733](https://github.com/containous/traefik/pull/733) ([emilevauge](https://github.com/emilevauge))
- Update Kubernetes examples [\#731](https://github.com/containous/traefik/pull/731) ([Starefossen](https://github.com/Starefossen))
- fIx marathon template with dots in ID [\#728](https://github.com/containous/traefik/pull/728) ([emilevauge](https://github.com/emilevauge))
- Fix networkMap construction in ListServices [\#724](https://github.com/containous/traefik/pull/724) ([vincentlepot](https://github.com/vincentlepot))
- Add basic compatibility with marathon-lb [\#720](https://github.com/containous/traefik/pull/720) ([guilhem](https://github.com/guilhem))
- Add Ed's video at ContainerCamp [\#717](https://github.com/containous/traefik/pull/717) ([emilevauge](https://github.com/emilevauge))
- Add documentation for Træfik on docker swarm mode [\#715](https://github.com/containous/traefik/pull/715) ([vdemeester](https://github.com/vdemeester))
- Remove duplicated link to Kubernetes.io in README.md [\#713](https://github.com/containous/traefik/pull/713) ([oscerd](https://github.com/oscerd))
- Show current version in web UI [\#709](https://github.com/containous/traefik/pull/709) ([vhf](https://github.com/vhf))
- Add support for docker healthcheck 👼 [\#708](https://github.com/containous/traefik/pull/708) ([vdemeester](https://github.com/vdemeester))
- Fix syntax in Swarm example. Resolves \#528 [\#707](https://github.com/containous/traefik/pull/707) ([billglover](https://github.com/billglover))
## [v1.1.0-rc1](https://github.com/containous/traefik/tree/v1.1.0-rc1) (2016-09-30) ## [v1.1.0-rc1](https://github.com/containous/traefik/tree/v1.1.0-rc1) (2016-09-30)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0...v1.1.0-rc1) [Full Changelog](https://github.com/containous/traefik/compare/v1.0.0...v1.1.0-rc1)

View file

@ -13,7 +13,7 @@
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease. Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Kubernetes](http://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Mesos](https://github.com/apache/mesos), [Kubernetes](http://kubernetes.io/), [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), [Kubernetes](http://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Mesos](https://github.com/apache/mesos), [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 ## Overview
@ -66,6 +66,11 @@ Run it and forget it!
You can have a quick look at Træfɪk in this [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers. You can have a quick look at Træfɪk in this [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers.
Here is a talk given by [Ed Robinson](https://github.com/errm) at the [ContainerCamp UK](https://container.camp) conference.
You will learn fundamental Træfɪk features and see some demos with Kubernetes.
[![Traefik ContainerCamp UK](http://img.youtube.com/vi/aFtpIShV60I/0.jpg)](https://www.youtube.com/watch?v=aFtpIShV60I)
Here is a talk (in French) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference. Here is a talk (in French) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Let's Encrypt. You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Let's Encrypt.
@ -86,7 +91,7 @@ You can access to a simple HTML frontend of Træfik.
- [Manners](https://github.com/mailgun/manners): graceful shutdown of http.Handler servers - [Manners](https://github.com/mailgun/manners): graceful shutdown of http.Handler servers
- [Lego](https://github.com/xenolf/lego): the best [Let's Encrypt](https://letsencrypt.org) library in go - [Lego](https://github.com/xenolf/lego): the best [Let's Encrypt](https://letsencrypt.org) library in go
## Quick start ## Test it
- The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml): - The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):

View file

@ -4,11 +4,13 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"github.com/BurntSushi/ty/fun"
"github.com/cenk/backoff" "github.com/cenk/backoff"
"github.com/containous/staert" "github.com/containous/staert"
"github.com/containous/traefik/cluster" "github.com/containous/traefik/cluster"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/safe" "github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/xenolf/lego/acme" "github.com/xenolf/lego/acme"
"golang.org/x/net/context" "golang.org/x/net/context"
"io/ioutil" "io/ioutil"
@ -311,22 +313,23 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func
} }
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
domain := types.CanonicalDomain(clientHello.ServerName)
account := a.store.Get().(*Account) account := a.store.Get().(*Account)
if challengeCert, ok := a.challengeProvider.getCertificate(clientHello.ServerName); ok { if challengeCert, ok := a.challengeProvider.getCertificate(domain); ok {
log.Debugf("ACME got challenge %s", clientHello.ServerName) log.Debugf("ACME got challenge %s", domain)
return challengeCert, nil return challengeCert, nil
} }
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok { if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok {
log.Debugf("ACME got domain cert %s", clientHello.ServerName) log.Debugf("ACME got domain cert %s", domain)
return domainCert.tlsCert, nil return domainCert.tlsCert, nil
} }
if a.OnDemand { if a.OnDemand {
if a.checkOnDemandDomain != nil && !a.checkOnDemandDomain(clientHello.ServerName) { if a.checkOnDemandDomain != nil && !a.checkOnDemandDomain(domain) {
return nil, nil return nil, nil
} }
return a.loadCertificateOnDemand(clientHello) return a.loadCertificateOnDemand(clientHello)
} }
log.Debugf("ACME got nothing %s", clientHello.ServerName) log.Debugf("ACME got nothing %s", domain)
return nil, nil return nil, nil
} }
@ -429,22 +432,23 @@ func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
} }
func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
domain := types.CanonicalDomain(clientHello.ServerName)
account := a.store.Get().(*Account) account := a.store.Get().(*Account)
if certificateResource, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok { if certificateResource, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok {
return certificateResource.tlsCert, nil return certificateResource.tlsCert, nil
} }
certificate, err := a.getDomainsCertificates([]string{clientHello.ServerName}) certificate, err := a.getDomainsCertificates([]string{domain})
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debugf("Got certificate on demand for domain %s", clientHello.ServerName) log.Debugf("Got certificate on demand for domain %s", domain)
transaction, object, err := a.store.Begin() transaction, object, err := a.store.Begin()
if err != nil { if err != nil {
return nil, err return nil, err
} }
account = object.(*Account) account = object.(*Account)
cert, err := account.DomainsCertificate.addCertificateForDomains(certificate, Domain{Main: clientHello.ServerName}) cert, err := account.DomainsCertificate.addCertificateForDomains(certificate, Domain{Main: domain})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -456,6 +460,7 @@ func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.C
// LoadCertificateForDomains loads certificates from ACME for given domains // LoadCertificateForDomains loads certificates from ACME for given domains
func (a *ACME) LoadCertificateForDomains(domains []string) { func (a *ACME) LoadCertificateForDomains(domains []string) {
domains = fun.Map(types.CanonicalDomain, domains).([]string)
safe.Go(func() { safe.Go(func() {
operation := func() error { operation := func() error {
if a.client == nil { if a.client == nil {
@ -514,6 +519,7 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
} }
func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) { func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
domains = fun.Map(types.CanonicalDomain, domains).([]string)
log.Debugf("Loading ACME certificates %s...", domains) log.Debugf("Loading ACME certificates %s...", domains)
bundle := true bundle := true
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil) certificate, failures := a.client.ObtainCertificate(domains, bundle, nil)

View file

@ -783,6 +783,13 @@ domain = "marathon.localhost"
# #
# groupsAsSubDomains = true # groupsAsSubDomains = true
# Enable compatibility with marathon-lb labels
#
# Optional
# Default: false
#
# marathonLBCompatibility = true
# Enable Marathon basic authentication # Enable Marathon basic authentication
# #
# Optional # Optional
@ -944,7 +951,7 @@ Annotations can be used on containers to override default behaviour for the whol
- `traefik.frontend.rule.type: PathPrefixStrip`: override the default frontend rule type (Default: `PathPrefix`). - `traefik.frontend.rule.type: PathPrefixStrip`: override the default frontend rule type (Default: `PathPrefix`).
You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.rc.yaml). You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik.yaml).
## Consul backend ## Consul backend

View file

@ -0,0 +1,223 @@
# Docker Swarm (mode) cluster
This section explains how to create a multi-host docker cluster with
swarm mode using [docker-machine](https://docs.docker.com/machine) and
how to deploy Træfɪk on it.
The cluster constist of:
- 3 servers
- 1 manager
- 2 workers
- 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
First, let's create all the nodes required. It's a shorter version of
the [swarm tutorial](https://docs.docker.com/engine/swarm/swarm-tutorial/).
```sh
docker-machine create -d virtualbox manager
docker-machine create -d virtualbox worker1
docker-machine create -d virtualbox worker2
```
Then, let's setup the cluster, in order :
1. initialize the cluster
2. get the token for other host to join
3. on both workers, join the cluster with the token
```sh
docker-machine ssh manager "docker swarm init \
--listen-addr $(docker-machine ip manager) \
--advertise-addr $(docker-machine ip manager)"
export worker_token=$(docker-machine ssh manager "docker swarm \
join-token worker -q")
docker-machine ssh worker1 "docker swarm join \
--token=${worker_token} \
--listen-addr $(docker-machine ip worker1) \
--advertise-addr $(docker-machine ip worker1) \
$(docker-machine ip manager)"
docker-machine ssh worker2 "docker swarm join \
--token=${worker_token} \
--listen-addr $(docker-machine ip worker2) \
--advertise-addr $(docker-machine ip worker2) \
$(docker-machine ip manager)"
```
Let's validate the cluster is up and running.
```sh
docker-machine ssh manager docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
2a770ov9vixeadep674265u1n worker1 Ready Active
dbi3or4q8ii8elbws70g4hkdh * manager Ready Active Leader
esbhhy6vnqv90xomjaomdgy46 worker2 Ready Active
```
Finally, let's create a network for Træfik to use.
```sh
docker-machine ssh manager "docker network create --driver=overlay traefik-net"
```
## Deploy Træfik
Let's deploy Træfik as a docker service in our cluster. The only
requirement for Træfik to work with swarm mode is that it needs to run
on a manager node — we are going to use a
[constraint](https://docs.docker.com/engine/reference/commandline/service_create/#/specify-service-constraints-constraint) for
that.
```
docker-machine ssh manager "docker service create \
--name traefik \
--constraint=node.role==manager \
--publish 80:80 --publish 8080:8080 \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--network traefik-net \
traefik \
--docker \
--docker.swarmmode \
--docker.domain=traefik \
--docker.watch \
--web"
```
Let's explain this command:
- `--publish 80:80 --publish 8080:8080`: we publish port `80` and
`8080` on the cluster.
- `--constraint=node.role==manager`: we ask docker to schedule Træfik
on a manager node.
- `--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock`:
we bind mount the docker socket where Træfik is scheduled to be able
to speak to the daemon.
- `--network traefik-net`: we attach the Træfik service (and thus
the underlined container) to the `traefik-net` network.
- `--docker`: enable docker backend, and `--docker.swarmmode` to
enable the swarm mode on Træfik.
- `--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. We start 2 services, on the `traefik-net` network.
```sh
docker-machine ssh manager "docker service create \
--name whoami0 \
--label traefik.port=80 \
--network traefik-net \
emilevauge/whoami"
docker-machine ssh manager "docker service create \
--name whoami1 \
--label traefik.port=80 \
--network traefik-net \
emilevauge/whoami"
```
Check that everything is scheduled and started:
```sh
docker-machine ssh manager "docker service ls"
ID NAME REPLICAS IMAGE COMMAND
ab046gpaqtln whoami0 1/1 emilevauge/whoami
cgfg5ifzrpgm whoami1 1/1 emilevauge/whoami
dtpl249tfghc traefik 1/1 traefik --docker --docker.swarmmode --docker.domain=traefik --docker.watch --web
```
## Access to your apps through Træfɪk
```sh
curl -H Host:whoami0.traefik http://$(docker-machine ip manager)
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 manager)
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
```
Note that as Træfik is published, you can access it from any machine
and not only the manager.
```sh
curl -H Host:whoami0.traefik http://$(docker-machine ip worker1)
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 worker2)
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

@ -1,7 +1,7 @@
# Swarm cluster # 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. 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: The cluster consists of:
- 2 servers - 2 servers
- 1 swarm master - 1 swarm master
@ -10,16 +10,16 @@ The cluster will be made of:
## Prerequisites ## Prerequisites
1. You will need to install [docker-machine](https://docs.docker.com/machine/) 1. You need to install [docker-machine](https://docs.docker.com/machine/)
2. You will need the latest [VirtualBox](https://www.virtualbox.org/wiki/Downloads) 2. You need the latest [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
## Cluster provisioning ## Cluster provisioning
We will first follow [this guide](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) to create the cluster. We first follow [this guide](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) to create the cluster.
### Create machine `mh-keystore` ### Create machine `mh-keystore`
This machine will be the service registry of our cluster. This machine is the service registry of our cluster.
```sh ```sh
docker-machine create -d virtualbox mh-keystore docker-machine create -d virtualbox mh-keystore
@ -37,7 +37,7 @@ docker run -d \
### Create machine `mhs-demo0` ### Create machine `mhs-demo0`
This machine will have a swarm master and a swarm agent on it. This machine is a swarm master and a swarm agent on it.
```sh ```sh
docker-machine create -d virtualbox \ docker-machine create -d virtualbox \
@ -50,7 +50,7 @@ docker-machine create -d virtualbox \
### Create machine `mhs-demo1` ### Create machine `mhs-demo1`
This machine will have a swarm agent on it. This machine have a swarm agent on it.
```sh ```sh
docker-machine create -d virtualbox \ docker-machine create -d virtualbox \
@ -84,14 +84,14 @@ docker $(docker-machine config mhs-demo0) run \
-l DEBUG \ -l DEBUG \
-c /dev/null \ -c /dev/null \
--docker \ --docker \
--docker.domain traefik \ --docker.domain=traefik \
--docker.endpoint tcp://$(docker-machine ip mhs-demo0):3376 \ --docker.endpoint=tcp://$(docker-machine ip mhs-demo0):3376 \
--docker.tls \ --docker.tls \
--docker.tls.ca /ssl/ca.pem \ --docker.tls.ca=/ssl/ca.pem \
--docker.tls.cert /ssl/server.pem \ --docker.tls.cert=/ssl/server.pem \
--docker.tls.key /ssl/server-key.pem \ --docker.tls.key=/ssl/server-key.pem \
--docker.tls.insecureSkipVerify \ --docker.tls.insecureSkipVerify \
--docker.watch \ --docker.watch \
--web --web
``` ```
@ -102,7 +102,7 @@ Let's explain this command:
- `-v /var/lib/boot2docker/:/ssl`: mount the ssl keys generated by docker-machine - `-v /var/lib/boot2docker/:/ssl`: mount the ssl keys generated by docker-machine
- `-c /dev/null`: empty config file - `-c /dev/null`: empty config file
- `--docker`: enable docker backend - `--docker`: enable docker backend
- `--docker.endpoint tcp://172.18.0.1:3376`: connect to the swarm master using the docker_gwbridge network - `--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 - `--docker.tls`: enable TLS using the docker-machine keys
- `--web`: activate the webUI on port 8080 - `--web`: activate the webUI on port 8080

View file

@ -1,111 +0,0 @@
# 3 Services for the 3 endpoints of the Ingress
apiVersion: v1
kind: Service
metadata:
name: service1
labels:
app: whoami
spec:
type: NodePort
ports:
- port: 80
nodePort: 30283
targetPort: 80
protocol: TCP
name: https
selector:
app: whoami
---
apiVersion: v1
kind: Service
metadata:
name: service2
labels:
app: whoami
spec:
type: NodePort
ports:
- port: 80
nodePort: 30284
targetPort: 80
protocol: TCP
name: http
selector:
app: whoami
---
apiVersion: v1
kind: Service
metadata:
name: service3
labels:
app: whoami
spec:
type: NodePort
ports:
- port: 80
nodePort: 30285
targetPort: 80
protocol: TCP
name: http
selector:
app: whoami
---
# A single RC matching all Services
apiVersion: v1
kind: ReplicationController
metadata:
name: whoami
spec:
replicas: 1
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: emilevauge/whoami
ports:
- containerPort: 80
---
# An Ingress with 2 hosts and 3 endpoints
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: whoami-ingress
spec:
rules:
- host: foo.localhost
http:
paths:
- path: /bar
backend:
serviceName: service1
servicePort: 80
- host: bar.localhost
http:
paths:
- backend:
serviceName: service2
servicePort: 80
- backend:
serviceName: service3
servicePort: 80
---
# Another Ingress with PathPrefixStrip
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: whoami-ingress-stripped
annotations:
traefik.frontend.rule.type: "PathPrefixStrip"
spec:
rules:
- host: foo.localhost
http:
paths:
- path: /prefixWillBeStripped
backend:
serviceName: service1
servicePort: 80

View file

@ -1,10 +1,6 @@
#!/bin/bash
kubectl create -f - << EOF
kind: Namespace kind: Namespace
apiVersion: v1 apiVersion: v1
metadata: metadata:
name: kube-system name: kube-system
labels: labels:
name: kube-system name: kube-system
EOF

View file

@ -1,31 +0,0 @@
apiVersion: v1
kind: ReplicationController
metadata:
name: traefik-ingress-controller
labels:
k8s-app: traefik-ingress-lb
spec:
replicas: 1
selector:
k8s-app: traefik-ingress-lb
template:
metadata:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
spec:
terminationGracePeriodSeconds: 60
containers:
- image: traefik
name: traefik-ingress-lb
imagePullPolicy: Always
ports:
- containerPort: 80
hostPort: 80
- containerPort: 443
hostPort: 443
- containerPort: 8080
args:
- --web
- --kubernetes
- --logLevel=DEBUG

View file

@ -16,11 +16,12 @@ spec:
labels: labels:
k8s-app: traefik-ingress-lb k8s-app: traefik-ingress-lb
name: traefik-ingress-lb name: traefik-ingress-lb
version: v1.0.0 version: v1.1.0
spec: spec:
terminationGracePeriodSeconds: 60 terminationGracePeriodSeconds: 60
hostNetwork: true
containers: containers:
- image: traefik:v1.0.0 - image: traefik:v1.1.0
name: traefik-ingress-lb name: traefik-ingress-lb
resources: resources:
limits: limits:

63
glide.lock generated
View file

@ -1,10 +1,10 @@
hash: 39ff28cc1d13d5915a870b14491ece1849c4eaf5a56cecd50a7676ecee6c6143 hash: 39ff28cc1d13d5915a870b14491ece1849c4eaf5a56cecd50a7676ecee6c6143
updated: 2016-09-30T11:27:29.529525636+02:00 updated: 2016-10-06T14:06:39.455848971+02:00
imports: imports:
- name: github.com/abbot/go-http-auth - name: github.com/abbot/go-http-auth
version: cb4372376e1e00e9f6ab9ec142e029302c9e7140 version: cb4372376e1e00e9f6ab9ec142e029302c9e7140
- name: github.com/boltdb/bolt - name: github.com/boltdb/bolt
version: 5cc10bbbc5c141029940133bb33c9e969512a698 version: f4c032d907f61f08dba2d719c58f108a1abb8e81
- name: github.com/BurntSushi/toml - name: github.com/BurntSushi/toml
version: 99064174e013895bbd9b025c31100bd1d9b590ca version: 99064174e013895bbd9b025c31100bd1d9b590ca
- name: github.com/BurntSushi/ty - name: github.com/BurntSushi/ty
@ -18,7 +18,7 @@ imports:
- name: github.com/codegangsta/cli - name: github.com/codegangsta/cli
version: 1efa31f08b9333f1bd4882d61f9d668a70cd902e version: 1efa31f08b9333f1bd4882d61f9d668a70cd902e
- name: github.com/codegangsta/negroni - name: github.com/codegangsta/negroni
version: dc6b9d037e8dab60cbfc09c61d6932537829be8b version: 3f7ce7b928e14ff890b067e5bbbc80af73690a9c
- name: github.com/containous/flaeg - name: github.com/containous/flaeg
version: a731c034dda967333efce5f8d276aeff11f8ff87 version: a731c034dda967333efce5f8d276aeff11f8ff87
- name: github.com/containous/mux - name: github.com/containous/mux
@ -32,11 +32,11 @@ imports:
- pkg/pathutil - pkg/pathutil
- pkg/types - pkg/types
- name: github.com/davecgh/go-spew - name: github.com/davecgh/go-spew
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
subpackages: subpackages:
- spew - spew
- name: github.com/docker/distribution - name: github.com/docker/distribution
version: 87917f30529e6a7fca8eaff2932424915fb11225 version: 99cb7c0946d2f5a38015443e515dc916295064d7
subpackages: subpackages:
- context - context
- digest - digest
@ -116,15 +116,15 @@ imports:
- types/time - types/time
- types/versions - types/versions
- name: github.com/docker/go-connections - name: github.com/docker/go-connections
version: 990a1a1a70b0da4c4cb70e117971a4f0babfbf1a version: 988efe982fdecb46f01d53465878ff1f2ff411ce
subpackages: subpackages:
- nat - nat
- sockets - sockets
- tlsconfig - tlsconfig
- name: github.com/docker/go-units - name: github.com/docker/go-units
version: f2d77a61e3c169b43402a0a1e84f06daf29b8190 version: f2145db703495b2e525c59662db69a7344b00bb8
- name: github.com/docker/leadership - name: github.com/docker/leadership
version: bfc7753dd48af19513b29deec23c364bf0f274eb version: 0a913e2d71a12fd14a028452435cb71ac8d82cb6
- name: github.com/docker/libcompose - name: github.com/docker/libcompose
version: d1876c1d68527a49c0aac22a0b161acc7296b740 version: d1876c1d68527a49c0aac22a0b161acc7296b740
subpackages: subpackages:
@ -143,7 +143,7 @@ imports:
- version - version
- yaml - yaml
- name: github.com/docker/libkv - name: github.com/docker/libkv
version: 35d3e2084c650109e7bcc7282655b1bc8ba924ff version: 3fce6a0f26e07da3eac45796a8e255547a47a750
subpackages: subpackages:
- store - store
- store/boltdb - store/boltdb
@ -153,13 +153,13 @@ imports:
- name: github.com/donovanhide/eventsource - name: github.com/donovanhide/eventsource
version: fd1de70867126402be23c306e1ce32828455d85b version: fd1de70867126402be23c306e1ce32828455d85b
- name: github.com/elazarl/go-bindata-assetfs - name: github.com/elazarl/go-bindata-assetfs
version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2 version: 9a6736ed45b44bf3835afeebb3034b57ed329f3e
- name: github.com/gambol99/go-marathon - name: github.com/gambol99/go-marathon
version: a558128c87724cd7430060ef5aedf39f83937f55 version: a558128c87724cd7430060ef5aedf39f83937f55
- name: github.com/go-check/check - name: github.com/go-check/check
version: 4f90aeace3a26ad7021961c297b22c42160c7b25 version: 4f90aeace3a26ad7021961c297b22c42160c7b25
- name: github.com/gogo/protobuf - name: github.com/gogo/protobuf
version: e33835a643a970c11ac74f6333f5f6866387a101 version: 99cb9b23110011cc45571c901ecae6f6f5e65cd3
subpackages: subpackages:
- proto - proto
- name: github.com/golang/glog - name: github.com/golang/glog
@ -169,18 +169,17 @@ imports:
subpackages: subpackages:
- query - query
- name: github.com/gorilla/context - name: github.com/gorilla/context
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296 version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
- name: github.com/hashicorp/consul - name: github.com/hashicorp/consul
version: fce7d75609a04eeb9d4bf41c8dc592aac18fc97d version: d8e2fb7dd594163e25a89bc52c1a4613f5c5bfb8
subpackages: subpackages:
- api - api
- name: github.com/hashicorp/go-cleanhttp - name: github.com/hashicorp/go-cleanhttp
version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d version: ad28ea4487f05916463e2423a55166280e8254b5
- name: github.com/hashicorp/serf - name: github.com/hashicorp/serf
version: 6c4672d66fc6312ddde18399262943e21175d831 version: b03bf85930b2349eb04b97c8fac437495296e3e7
subpackages: subpackages:
- coordinate - coordinate
- serf
- name: github.com/jarcoal/httpmock - name: github.com/jarcoal/httpmock
version: 145b10d659265440f062c31ea15326166bae56ee version: 145b10d659265440f062c31ea15326166bae56ee
- name: github.com/libkermit/compose - name: github.com/libkermit/compose
@ -190,7 +189,7 @@ imports:
- name: github.com/libkermit/docker - name: github.com/libkermit/docker
version: 55e3595409924fcfbb850811e5a7cdbe8960a0b7 version: 55e3595409924fcfbb850811e5a7cdbe8960a0b7
- name: github.com/mailgun/manners - name: github.com/mailgun/manners
version: fada45142db3f93097ca917da107aa3fad0ffcb5 version: a585afd9d65c0e05f6c003f921e71ebc05074f4f
- name: github.com/mailgun/timetools - name: github.com/mailgun/timetools
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0 version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
- name: github.com/mattn/go-shellwords - name: github.com/mattn/go-shellwords
@ -222,7 +221,7 @@ imports:
- name: github.com/miekg/dns - name: github.com/miekg/dns
version: 5d001d020961ae1c184f9f8152fdc73810481677 version: 5d001d020961ae1c184f9f8152fdc73810481677
- name: github.com/mitchellh/mapstructure - name: github.com/mitchellh/mapstructure
version: d2dd0262208475919e1a362f675cfc0e7c10e905 version: ca63d7c062ee3c9f34db231e352b60012b4fd0c1
- name: github.com/moul/http2curl - name: github.com/moul/http2curl
version: b1479103caacaa39319f75e7f57fc545287fca0d version: b1479103caacaa39319f75e7f57fc545287fca0d
- name: github.com/NYTimes/gziphandler - name: github.com/NYTimes/gziphandler
@ -230,11 +229,11 @@ imports:
- name: github.com/ogier/pflag - name: github.com/ogier/pflag
version: 45c278ab3607870051a2ea9040bb85fcb8557481 version: 45c278ab3607870051a2ea9040bb85fcb8557481
- name: github.com/opencontainers/runc - name: github.com/opencontainers/runc
version: 1a81e9ab1f138c091fe5c86d0883f87716088527 version: 02f8fa7863dd3f82909a73e2061897828460d52f
subpackages: subpackages:
- libcontainer/user - libcontainer/user
- name: github.com/parnurzeal/gorequest - name: github.com/parnurzeal/gorequest
version: 045012d33ef41ea146c1b675df9296d0dc1a212d version: e30af16d4e485943aab0b0885ad6bdbb8c0d3dc7
- name: github.com/pmezard/go-difflib - name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages: subpackages:
@ -242,24 +241,24 @@ imports:
- name: github.com/ryanuber/go-glob - name: github.com/ryanuber/go-glob
version: 572520ed46dbddaed19ea3d9541bdd0494163693 version: 572520ed46dbddaed19ea3d9541bdd0494163693
- name: github.com/samuel/go-zookeeper - name: github.com/samuel/go-zookeeper
version: e64db453f3512cade908163702045e0f31137843 version: 87e1bca4477a3cc767ca71be023ced183d74e538
subpackages: subpackages:
- zk - zk
- name: github.com/satori/go.uuid - name: github.com/satori/go.uuid
version: 879c5887cd475cd7864858769793b2ceb0d44feb version: 879c5887cd475cd7864858769793b2ceb0d44feb
- name: github.com/Sirupsen/logrus - name: github.com/Sirupsen/logrus
version: a283a10442df8dc09befd873fab202bf8a253d6a version: 3ec0642a7fb6488f65b06f9040adc67e3990296a
- name: github.com/streamrail/concurrent-map - name: github.com/streamrail/concurrent-map
version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287 version: 8bf1e9bacbf65b10c81d0f4314cf2b1ebef728b5
- name: github.com/stretchr/objx - name: github.com/stretchr/objx
version: cbeaeb16a013161a98496fad62933b1d21786672 version: cbeaeb16a013161a98496fad62933b1d21786672
- name: github.com/stretchr/testify - name: github.com/stretchr/testify
version: d77da356e56a7428ad25149ca77381849a6a5232 version: 976c720a22c8eb4eb6a0b4348ad85ad12491a506
subpackages: subpackages:
- assert - assert
- mock - mock
- name: github.com/thoas/stats - name: github.com/thoas/stats
version: 79b768ff1780f4e5b0ed132e192bfeefe9f85a9c version: 152b5d051953fdb6e45f14b6826962aadc032324
- name: github.com/tv42/zbase32 - name: github.com/tv42/zbase32
version: 03389da7e0bf9844767f82690f4d68fc097a1306 version: 03389da7e0bf9844767f82690f4d68fc097a1306
- name: github.com/ugorji/go - name: github.com/ugorji/go
@ -267,7 +266,7 @@ imports:
subpackages: subpackages:
- codec - codec
- name: github.com/unrolled/render - name: github.com/unrolled/render
version: 198ad4d8b8a4612176b804ca10555b222a086b40 version: 526faf80cd4b305bb8134abea8d20d5ced74faa6
- name: github.com/vdemeester/docker-events - name: github.com/vdemeester/docker-events
version: be74d4929ec1ad118df54349fda4b0cba60f849b version: be74d4929ec1ad118df54349fda4b0cba60f849b
- name: github.com/vdemeester/shakers - name: github.com/vdemeester/shakers
@ -289,7 +288,7 @@ imports:
- name: github.com/vulcand/route - name: github.com/vulcand/route
version: cb89d787ddbb1c5849a7ac9f79004c1fd12a4a32 version: cb89d787ddbb1c5849a7ac9f79004c1fd12a4a32
- name: github.com/vulcand/vulcand - name: github.com/vulcand/vulcand
version: 28a4e5c0892167589737b95ceecbcef00295be50 version: bed092e10989250b48bdb6aa3b0557b207f05c80
subpackages: subpackages:
- conntracker - conntracker
- plugin - plugin
@ -317,13 +316,13 @@ imports:
- unix - unix
- windows - windows
- name: gopkg.in/fsnotify.v1 - name: gopkg.in/fsnotify.v1
version: a8a77c9133d2d6fd8334f3260d06f60e8d80a5fb version: 944cff21b3baf3ced9a880365682152ba577d348
- name: gopkg.in/mgo.v2 - name: gopkg.in/mgo.v2
version: 29cc868a5ca65f401ff318143f9408d02f4799cc version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
subpackages: subpackages:
- bson - bson
- name: gopkg.in/square/go-jose.v1 - name: gopkg.in/square/go-jose.v1
version: e3f973b66b91445ec816dd7411ad1b6495a5a2fc version: aa2e30fdd1fe9dd3394119af66451ae790d50e0d
subpackages: subpackages:
- cipher - cipher
- json - json
@ -341,9 +340,9 @@ testImports:
- name: github.com/libkermit/docker-check - name: github.com/libkermit/docker-check
version: cbe0ef03b3d23070eac4d00ba8828f2cc7f7e5a3 version: cbe0ef03b3d23070eac4d00ba8828f2cc7f7e5a3
- name: github.com/spf13/pflag - name: github.com/spf13/pflag
version: 5644820622454e71517561946e3d94b9f9db6842 version: 08b1a584251b5b62f458943640fc8ebd4d50aaa5
- name: github.com/vbatts/tar-split - name: github.com/vbatts/tar-split
version: 6810cedb21b2c3d0b9bb8f9af12ff2dc7a2f14df version: bd4c5d64c3e9297f410025a3b1bd0c58f659e721
subpackages: subpackages:
- archive/tar - archive/tar
- tar/asm - tar/asm

View file

@ -49,6 +49,7 @@ pages:
- User Guide: - User Guide:
- 'Configuration examples': 'user-guide/examples.md' - 'Configuration examples': 'user-guide/examples.md'
- 'Swarm cluster': 'user-guide/swarm.md' - 'Swarm cluster': 'user-guide/swarm.md'
- 'Swarm mode cluster': 'user-guide/swarm-mode.md'
- 'Kubernetes': 'user-guide/kubernetes.md' - 'Kubernetes': 'user-guide/kubernetes.md'
- 'Key-value store configuration': 'user-guide/kv-config.md' - 'Key-value store configuration': 'user-guide/kv-config.md'
- 'Clustering/HA': 'user-guide/cluster.md' - 'Clustering/HA': 'user-guide/cluster.md'

View file

@ -58,6 +58,7 @@ type dockerData struct {
Name string Name string
Labels map[string]string // List of labels set to container or service Labels map[string]string // List of labels set to container or service
NetworkSettings networkSettings NetworkSettings networkSettings
Health string
} }
// NetworkSettings holds the networks data to the Docker provider // NetworkSettings holds the networks data to the Docker provider
@ -214,6 +215,9 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
} }
eventHandler.Handle("start", startStopHandle) eventHandler.Handle("start", startStopHandle)
eventHandler.Handle("die", startStopHandle) eventHandler.Handle("die", startStopHandle)
eventHandler.Handle("health_status: healthy", startStopHandle)
eventHandler.Handle("health_status: unhealthy", startStopHandle)
eventHandler.Handle("health_status: starting", startStopHandle)
errChan := events.MonitorWithHandler(ctx, dockerClient, options, eventHandler) errChan := events.MonitorWithHandler(ctx, dockerClient, options, eventHandler)
if err := <-errChan; err != nil { if err := <-errChan; err != nil {
@ -378,6 +382,11 @@ func (provider *Docker) containerFilter(container dockerData) bool {
return false return false
} }
if container.Health != "" && container.Health != "healthy" {
log.Debugf("Filtering unhealthy or starting container %s", container.Name)
return false
}
return true return true
} }
@ -578,6 +587,10 @@ func parseContainer(container dockertypes.ContainerJSON) dockerData {
} }
if container.State != nil && container.State.Health != nil {
dockerData.Health = container.State.Health.Status
}
return dockerData return dockerData
} }
@ -602,7 +615,8 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD
return []dockerData{}, err return []dockerData{}, err
} }
for _, network := range networkList { for _, network := range networkList {
networkMap[network.ID] = &network networkToAdd := network
networkMap[network.ID] = &networkToAdd
} }
var dockerDataList []dockerData var dockerDataList []dockerData

View file

@ -569,12 +569,18 @@ func TestDockerGetLabel(t *testing.T) {
}{ }{
{ {
container: docker.ContainerJSON{ container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "foo",
},
Config: &container.Config{}, Config: &container.Config{},
}, },
expected: "Label not found:", expected: "Label not found:",
}, },
{ {
container: docker.ContainerJSON{ container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "foo",
},
Config: &container.Config{ Config: &container.Config{
Labels: map[string]string{ Labels: map[string]string{
"foo": "bar", "foo": "bar",
@ -608,6 +614,9 @@ func TestDockerGetLabels(t *testing.T) {
}{ }{
{ {
container: docker.ContainerJSON{ container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "foo",
},
Config: &container.Config{}, Config: &container.Config{},
}, },
expectedLabels: map[string]string{}, expectedLabels: map[string]string{},
@ -615,6 +624,9 @@ func TestDockerGetLabels(t *testing.T) {
}, },
{ {
container: docker.ContainerJSON{ container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "foo",
},
Config: &container.Config{ Config: &container.Config{
Labels: map[string]string{ Labels: map[string]string{
"foo": "fooz", "foo": "fooz",
@ -628,6 +640,9 @@ func TestDockerGetLabels(t *testing.T) {
}, },
{ {
container: docker.ContainerJSON{ container: docker.ContainerJSON{
ContainerJSONBase: &docker.ContainerJSONBase{
Name: "foo",
},
Config: &container.Config{ Config: &container.Config{
Labels: map[string]string{ Labels: map[string]string{
"foo": "fooz", "foo": "fooz",

View file

@ -26,14 +26,15 @@ var _ Provider = (*Marathon)(nil)
// Marathon holds configuration of the Marathon provider. // Marathon holds configuration of the Marathon provider.
type Marathon struct { type Marathon struct {
BaseProvider BaseProvider
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"` Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"`
Domain string `description:"Default domain used"` Domain string `description:"Default domain used"`
ExposedByDefault bool `description:"Expose Marathon apps by default"` ExposedByDefault bool `description:"Expose Marathon apps by default"`
GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"` GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"`
DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"` DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"`
TLS *ClientTLS `description:"Enable Docker TLS support"` MarathonLBCompatibility bool `description:"Add compatibility with marathon-lb labels"`
Basic *MarathonBasic TLS *ClientTLS `description:"Enable Docker TLS support"`
marathonClient marathon.Marathon Basic *MarathonBasic
marathonClient marathon.Marathon
} }
// MarathonBasic holds basic authentication specific configurations // MarathonBasic holds basic authentication specific configurations
@ -194,6 +195,11 @@ func (provider *Marathon) taskFilter(task marathon.Task, applications *marathon.
} }
label, _ := provider.getLabel(application, "traefik.tags") label, _ := provider.getLabel(application, "traefik.tags")
constraintTags := strings.Split(label, ",") constraintTags := strings.Split(label, ",")
if provider.MarathonLBCompatibility {
if label, err := provider.getLabel(application, "HAPROXY_GROUP"); err == nil {
constraintTags = append(constraintTags, label)
}
}
if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok { if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok {
if failingConstraint != nil { if failingConstraint != nil {
log.Debugf("Application %v pruned by '%v' constraint", application.ID, failingConstraint.String()) log.Debugf("Application %v pruned by '%v' constraint", application.ID, failingConstraint.String())
@ -263,6 +269,11 @@ func (provider *Marathon) taskFilter(task marathon.Task, applications *marathon.
func (provider *Marathon) applicationFilter(app marathon.Application, filteredTasks []marathon.Task) bool { func (provider *Marathon) applicationFilter(app marathon.Application, filteredTasks []marathon.Task) bool {
label, _ := provider.getLabel(app, "traefik.tags") label, _ := provider.getLabel(app, "traefik.tags")
constraintTags := strings.Split(label, ",") constraintTags := strings.Split(label, ",")
if provider.MarathonLBCompatibility {
if label, err := provider.getLabel(app, "HAPROXY_GROUP"); err == nil {
constraintTags = append(constraintTags, label)
}
}
if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok { if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok {
if failingConstraint != nil { if failingConstraint != nil {
log.Debugf("Application %v pruned by '%v' constraint", app.ID, failingConstraint.String()) log.Debugf("Application %v pruned by '%v' constraint", app.ID, failingConstraint.String())
@ -384,6 +395,11 @@ func (provider *Marathon) getFrontendRule(application marathon.Application) stri
if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil { if label, err := provider.getLabel(application, "traefik.frontend.rule"); err == nil {
return label return label
} }
if provider.MarathonLBCompatibility {
if label, err := provider.getLabel(application, "HAPROXY_0_VHOST"); err == nil {
return "Host:" + label
}
}
return "Host:" + provider.getSubDomain(application.ID) + "." + provider.Domain return "Host:" + provider.getSubDomain(application.ID) + "." + provider.Domain
} }

View file

@ -114,7 +114,7 @@ func TestMarathonLoadConfig(t *testing.T) {
applications: &marathon.Applications{ applications: &marathon.Applications{
Apps: []marathon.Application{ Apps: []marathon.Application{
{ {
ID: "/testLoadBalancerAndCircuitBreaker", ID: "/testLoadBalancerAndCircuitBreaker.dot",
Ports: []int{80}, Ports: []int{80},
Labels: &map[string]string{ Labels: &map[string]string{
"traefik.backend.loadbalancer.method": "drr", "traefik.backend.loadbalancer.method": "drr",
@ -126,29 +126,29 @@ func TestMarathonLoadConfig(t *testing.T) {
tasks: &marathon.Tasks{ tasks: &marathon.Tasks{
Tasks: []marathon.Task{ Tasks: []marathon.Task{
{ {
ID: "testLoadBalancerAndCircuitBreaker", ID: "testLoadBalancerAndCircuitBreaker.dot",
AppID: "/testLoadBalancerAndCircuitBreaker", AppID: "/testLoadBalancerAndCircuitBreaker.dot",
Host: "127.0.0.1", Host: "127.0.0.1",
Ports: []int{80}, Ports: []int{80},
}, },
}, },
}, },
expectedFrontends: map[string]*types.Frontend{ expectedFrontends: map[string]*types.Frontend{
`frontend-testLoadBalancerAndCircuitBreaker`: { `frontend-testLoadBalancerAndCircuitBreaker.dot`: {
Backend: "backend-testLoadBalancerAndCircuitBreaker", Backend: "backend-testLoadBalancerAndCircuitBreaker.dot",
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{}, EntryPoints: []string{},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
`route-host-testLoadBalancerAndCircuitBreaker`: { `route-host-testLoadBalancerAndCircuitBreaker.dot`: {
Rule: "Host:testLoadBalancerAndCircuitBreaker.docker.localhost", Rule: "Host:testLoadBalancerAndCircuitBreaker.dot.docker.localhost",
}, },
}, },
}, },
}, },
expectedBackends: map[string]*types.Backend{ expectedBackends: map[string]*types.Backend{
"backend-testLoadBalancerAndCircuitBreaker": { "backend-testLoadBalancerAndCircuitBreaker.dot": {
Servers: map[string]types.Server{ Servers: map[string]types.Server{
"server-testLoadBalancerAndCircuitBreaker": { "server-testLoadBalancerAndCircuitBreaker-dot": {
URL: "http://127.0.0.1:80", URL: "http://127.0.0.1:80",
Weight: 0, Weight: 0,
}, },
@ -684,9 +684,10 @@ func TestMarathonTaskFilter(t *testing.T) {
func TestMarathonAppConstraints(t *testing.T) { func TestMarathonAppConstraints(t *testing.T) {
cases := []struct { cases := []struct {
application marathon.Application application marathon.Application
filteredTasks []marathon.Task filteredTasks []marathon.Task
expected bool expected bool
marathonLBCompatibility bool
}{ }{
{ {
application: marathon.Application{ application: marathon.Application{
@ -698,28 +699,48 @@ func TestMarathonAppConstraints(t *testing.T) {
AppID: "foo1", AppID: "foo1",
}, },
}, },
expected: false, marathonLBCompatibility: false,
expected: false,
}, },
{ {
application: marathon.Application{ application: marathon.Application{
ID: "foo", ID: "foo2",
Labels: &map[string]string{ Labels: &map[string]string{
"traefik.tags": "valid", "traefik.tags": "valid",
}, },
}, },
filteredTasks: []marathon.Task{ filteredTasks: []marathon.Task{
{ {
AppID: "foo", AppID: "foo2",
}, },
}, },
expected: true, marathonLBCompatibility: false,
expected: true,
},
{
application: marathon.Application{
ID: "foo3",
Labels: &map[string]string{
"HAPROXY_GROUP": "valid",
"traefik.tags": "notvalid",
},
},
filteredTasks: []marathon.Task{
{
AppID: "foo3",
},
},
marathonLBCompatibility: true,
expected: true,
}, },
} }
provider := &Marathon{}
constraint, _ := types.NewConstraint("tag==valid")
provider.Constraints = []types.Constraint{*constraint}
for _, c := range cases { for _, c := range cases {
provider := &Marathon{
MarathonLBCompatibility: c.marathonLBCompatibility,
}
constraint, _ := types.NewConstraint("tag==valid")
provider.Constraints = []types.Constraint{*constraint}
actual := provider.applicationFilter(c.application, c.filteredTasks) actual := provider.applicationFilter(c.application, c.filteredTasks)
if actual != c.expected { if actual != c.expected {
t.Fatalf("expected %v, got %v: %v", c.expected, actual, c.application) t.Fatalf("expected %v, got %v: %v", c.expected, actual, c.application)
@ -729,9 +750,10 @@ func TestMarathonAppConstraints(t *testing.T) {
} }
func TestMarathonTaskConstraints(t *testing.T) { func TestMarathonTaskConstraints(t *testing.T) {
cases := []struct { cases := []struct {
applications []marathon.Application applications []marathon.Application
filteredTask marathon.Task filteredTask marathon.Task
expected bool expected bool
marathonLBCompatibility bool
}{ }{
{ {
applications: []marathon.Application{ applications: []marathon.Application{
@ -749,7 +771,8 @@ func TestMarathonTaskConstraints(t *testing.T) {
AppID: "foo1", AppID: "foo1",
Ports: []int{80}, Ports: []int{80},
}, },
expected: false, marathonLBCompatibility: false,
expected: false,
}, },
{ {
applications: []marathon.Application{ applications: []marathon.Application{
@ -764,14 +787,40 @@ func TestMarathonTaskConstraints(t *testing.T) {
AppID: "foo2", AppID: "foo2",
Ports: []int{80}, Ports: []int{80},
}, },
expected: true, marathonLBCompatibility: false,
expected: true,
},
{
applications: []marathon.Application{
{
ID: "foo3",
Labels: &map[string]string{
"HAPROXY_GROUP": "valid",
"traefik.tags": "notvalid",
},
}, {
ID: "foo4",
Labels: &map[string]string{
"HAPROXY_GROUP": "notvalid",
"traefik.tags": "valid",
},
},
},
filteredTask: marathon.Task{
AppID: "foo3",
Ports: []int{80},
},
marathonLBCompatibility: true,
expected: true,
}, },
} }
provider := &Marathon{}
constraint, _ := types.NewConstraint("tag==valid")
provider.Constraints = []types.Constraint{*constraint}
for _, c := range cases { for _, c := range cases {
provider := &Marathon{
MarathonLBCompatibility: c.marathonLBCompatibility,
}
constraint, _ := types.NewConstraint("tag==valid")
provider.Constraints = []types.Constraint{*constraint}
apps := new(marathon.Applications) apps := new(marathon.Applications)
apps.Apps = c.applications apps.Apps = c.applications
actual := provider.taskFilter(c.filteredTask, apps, true) actual := provider.taskFilter(c.filteredTask, apps, true)
@ -1152,37 +1201,53 @@ func TestMarathonGetEntryPoints(t *testing.T) {
} }
func TestMarathonGetFrontendRule(t *testing.T) { func TestMarathonGetFrontendRule(t *testing.T) {
provider := &Marathon{
Domain: "docker.localhost",
}
applications := []struct { applications := []struct {
application marathon.Application application marathon.Application
expected string expected string
marathonLBCompatibility bool
}{ }{
{ {
application: marathon.Application{ application: marathon.Application{
Labels: &map[string]string{}}, Labels: &map[string]string{}},
expected: "Host:.docker.localhost", marathonLBCompatibility: true,
expected: "Host:.docker.localhost",
}, },
{ {
application: marathon.Application{ application: marathon.Application{
ID: "test", ID: "test",
Labels: &map[string]string{}, Labels: &map[string]string{
"HAPROXY_0_VHOST": "foo.bar",
},
}, },
expected: "Host:test.docker.localhost", marathonLBCompatibility: false,
expected: "Host:test.docker.localhost",
}, },
{ {
application: marathon.Application{ application: marathon.Application{
Labels: &map[string]string{ Labels: &map[string]string{
"traefik.frontend.rule": "Host:foo.bar", "traefik.frontend.rule": "Host:foo.bar",
"HAPROXY_0_VHOST": "notvalid",
}, },
}, },
expected: "Host:foo.bar", marathonLBCompatibility: true,
expected: "Host:foo.bar",
},
{
application: marathon.Application{
Labels: &map[string]string{
"HAPROXY_0_VHOST": "foo.bar",
},
},
marathonLBCompatibility: true,
expected: "Host:foo.bar",
}, },
} }
for _, a := range applications { for _, a := range applications {
provider := &Marathon{
Domain: "docker.localhost",
MarathonLBCompatibility: a.marathonLBCompatibility,
}
actual := provider.getFrontendRule(a.application) actual := provider.getFrontendRule(a.application)
if actual != a.expected { if actual != a.expected {
t.Fatalf("expected %q, got %q", a.expected, actual) t.Fatalf("expected %q, got %q", a.expected, actual)

View file

@ -3,7 +3,9 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/BurntSushi/ty/fun"
"github.com/containous/mux" "github.com/containous/mux"
"github.com/containous/traefik/types"
"net" "net"
"net/http" "net/http"
"reflect" "reflect"
@ -24,7 +26,7 @@ func (r *Rules) host(hosts ...string) *mux.Route {
reqHost = req.Host reqHost = req.Host
} }
for _, host := range hosts { for _, host := range hosts {
if reqHost == strings.TrimSpace(host) { if types.CanonicalDomain(reqHost) == types.CanonicalDomain(host) {
return true return true
} }
} }
@ -35,7 +37,7 @@ func (r *Rules) host(hosts ...string) *mux.Route {
func (r *Rules) hostRegexp(hosts ...string) *mux.Route { func (r *Rules) hostRegexp(hosts ...string) *mux.Route {
router := r.route.route.Subrouter() router := r.route.route.Subrouter()
for _, host := range hosts { for _, host := range hosts {
router.Host(strings.TrimSpace(host)) router.Host(types.CanonicalDomain(host))
} }
return r.route.route return r.route.route
} }
@ -43,7 +45,7 @@ func (r *Rules) hostRegexp(hosts ...string) *mux.Route {
func (r *Rules) path(paths ...string) *mux.Route { func (r *Rules) path(paths ...string) *mux.Route {
router := r.route.route.Subrouter() router := r.route.route.Subrouter()
for _, path := range paths { for _, path := range paths {
router.Path(strings.TrimSpace(path)) router.Path(types.CanonicalDomain(path))
} }
return r.route.route return r.route.route
} }
@ -51,7 +53,7 @@ func (r *Rules) path(paths ...string) *mux.Route {
func (r *Rules) pathPrefix(paths ...string) *mux.Route { func (r *Rules) pathPrefix(paths ...string) *mux.Route {
router := r.route.route.Subrouter() router := r.route.route.Subrouter()
for _, path := range paths { for _, path := range paths {
router.PathPrefix(strings.TrimSpace(path)) router.PathPrefix(types.CanonicalDomain(path))
} }
return r.route.route return r.route.route
} }
@ -67,7 +69,7 @@ func (r *Rules) pathStrip(paths ...string) *mux.Route {
r.route.stripPrefixes = paths r.route.stripPrefixes = paths
router := r.route.route.Subrouter() router := r.route.route.Subrouter()
for _, path := range paths { for _, path := range paths {
router.Path(strings.TrimSpace(path)) router.Path(types.CanonicalDomain(path))
} }
return r.route.route return r.route.route
} }
@ -77,7 +79,7 @@ func (r *Rules) pathPrefixStrip(paths ...string) *mux.Route {
r.route.stripPrefixes = paths r.route.stripPrefixes = paths
router := r.route.route.Subrouter() router := r.route.route.Subrouter()
for _, path := range paths { for _, path := range paths {
router.PathPrefix(strings.TrimSpace(path)) router.PathPrefix(types.CanonicalDomain(path))
} }
return r.route.route return r.route.route
} }
@ -153,7 +155,6 @@ func (r *Rules) parseRules(expression string, onRule func(functionName string, f
} }
} }
return nil return nil
} }
// Parse parses rules expressions // Parse parses rules expressions
@ -197,5 +198,5 @@ func (r *Rules) ParseDomains(expression string) ([]string, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("Error parsing domains: %v", err) return nil, fmt.Errorf("Error parsing domains: %v", err)
} }
return domains, nil return fun.Map(types.CanonicalDomain, domains).([]string), nil
} }

View file

@ -36,7 +36,7 @@ func TestParseTwoRules(t *testing.T) {
serverRoute := &serverRoute{route: route} serverRoute := &serverRoute{route: route}
rules := &Rules{route: serverRoute} rules := &Rules{route: serverRoute}
expression := "Host:foo.bar;Path:/foobar" expression := "Host: Foo.Bar ; Path:/FOObar"
routeResult, err := rules.Parse(expression) routeResult, err := rules.Parse(expression)
if err != nil { if err != nil {
@ -58,11 +58,13 @@ func TestParseDomains(t *testing.T) {
"Host:foo.bar,test.bar", "Host:foo.bar,test.bar",
"Path:/test", "Path:/test",
"Host:foo.bar;Path:/test", "Host:foo.bar;Path:/test",
"Host: Foo.Bar ;Path:/test",
} }
domainsSlice := [][]string{ domainsSlice := [][]string{
{"foo.bar", "test.bar"}, {"foo.bar", "test.bar"},
{}, {},
{"foo.bar"}, {"foo.bar"},
{"foo.bar"},
} }
for i, expression := range expressionsSlice { for i, expression := range expressionsSlice {
domains, err := rules.ParseDomains(expression) domains, err := rules.ParseDomains(expression)

View file

@ -20,8 +20,8 @@ ssh-add ~/.ssh/traefik.id_rsa
# download github release # download github release
echo "Downloading ghr..." echo "Downloading ghr..."
curl -LOs https://github.com/tcnksm/ghr/releases/download/pre-release/linux_amd64.zip curl -LOs https://github.com/tcnksm/ghr/releases/download/v0.5.0/ghr_v0.5.0_linux_amd64.zip
unzip -q linux_amd64.zip unzip -q ghr_v0.5.0_linux_amd64.zip
sudo mv ghr /usr/bin/ghr sudo mv ghr /usr/bin/ghr
sudo chmod +x /usr/bin/ghr sudo chmod +x /usr/bin/ghr

View file

@ -1,35 +1,35 @@
{{$apps := .Applications}} {{$apps := .Applications}}
[backends]{{range .Tasks}} [backends]{{range .Tasks}}
[backends.backend{{getBackend . $apps}}.servers.server-{{.ID | replace "." "-"}}] [backends."backend{{getBackend . $apps}}".servers."server-{{.ID | replace "." "-"}}"]
url = "{{getProtocol . $apps}}://{{.Host}}:{{getPort . $apps}}" url = "{{getProtocol . $apps}}://{{.Host}}:{{getPort . $apps}}"
weight = {{getWeight . $apps}} weight = {{getWeight . $apps}}
{{end}} {{end}}
{{range .Applications}} {{range .Applications}}
{{ if hasMaxConnLabels . }} {{ if hasMaxConnLabels . }}
[backends.backend{{getFrontendBackend . }}.maxconn] [backends."backend{{getFrontendBackend . }}".maxconn]
amount = {{getMaxConnAmount . }} amount = {{getMaxConnAmount . }}
extractorfunc = "{{getMaxConnExtractorFunc . }}" extractorfunc = "{{getMaxConnExtractorFunc . }}"
{{end}} {{end}}
{{ if hasLoadBalancerLabels . }} {{ if hasLoadBalancerLabels . }}
[backends.backend{{getFrontendBackend . }}.loadbalancer] [backends."backend{{getFrontendBackend . }}".loadbalancer]
method = "{{getLoadBalancerMethod . }}" method = "{{getLoadBalancerMethod . }}"
sticky = {{getSticky .}} sticky = {{getSticky .}}
{{end}} {{end}}
{{ if hasCircuitBreakerLabels . }} {{ if hasCircuitBreakerLabels . }}
[backends.backend{{getFrontendBackend . }}.circuitbreaker] [backends."backend{{getFrontendBackend . }}".circuitbreaker]
expression = "{{getCircuitBreakerExpression . }}" expression = "{{getCircuitBreakerExpression . }}"
{{end}} {{end}}
{{end}} {{end}}
[frontends]{{range .Applications}} [frontends]{{range .Applications}}
[frontends.frontend{{.ID | replace "/" "-"}}] [frontends."frontend{{.ID | replace "/" "-"}}"]
backend = "backend{{getFrontendBackend .}}" backend = "backend{{getFrontendBackend .}}"
passHostHeader = {{getPassHostHeader .}} passHostHeader = {{getPassHostHeader .}}
priority = {{getPriority .}} priority = {{getPriority .}}
entryPoints = [{{range getEntryPoints .}} entryPoints = [{{range getEntryPoints .}}
"{{.}}", "{{.}}",
{{end}}] {{end}}]
[frontends.frontend{{.ID | replace "/" "-"}}.routes.route-host{{.ID | replace "/" "-"}}] [frontends."frontend{{.ID | replace "/" "-"}}".routes."route-host{{.ID | replace "/" "-"}}"]
rule = "{{getFrontendRule .}}" rule = "{{getFrontendRule .}}"
{{end}} {{end}}

View file

@ -223,3 +223,8 @@ type Basic struct {
type Digest struct { type Digest struct {
Users Users
} }
// CanonicalDomain returns a lower case domain with trim space
func CanonicalDomain(domain string) string {
return strings.ToLower(strings.TrimSpace(domain))
}

13
web.go
View file

@ -15,6 +15,7 @@ import (
"github.com/containous/traefik/middlewares" "github.com/containous/traefik/middlewares"
"github.com/containous/traefik/safe" "github.com/containous/traefik/safe"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/containous/traefik/version"
"github.com/elazarl/go-bindata-assetfs" "github.com/elazarl/go-bindata-assetfs"
"github.com/thoas/stats" "github.com/thoas/stats"
"github.com/unrolled/render" "github.com/unrolled/render"
@ -60,6 +61,7 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag
systemRouter.Methods("GET").Path("/ping").HandlerFunc(provider.getPingHandler) systemRouter.Methods("GET").Path("/ping").HandlerFunc(provider.getPingHandler)
// API routes // API routes
systemRouter.Methods("GET").Path("/api").HandlerFunc(provider.getConfigHandler) systemRouter.Methods("GET").Path("/api").HandlerFunc(provider.getConfigHandler)
systemRouter.Methods("GET").Path("/api/version").HandlerFunc(provider.getVersionHandler)
systemRouter.Methods("GET").Path("/api/providers").HandlerFunc(provider.getConfigHandler) systemRouter.Methods("GET").Path("/api/providers").HandlerFunc(provider.getConfigHandler)
systemRouter.Methods("GET").Path("/api/providers/{provider}").HandlerFunc(provider.getProviderHandler) systemRouter.Methods("GET").Path("/api/providers/{provider}").HandlerFunc(provider.getProviderHandler)
systemRouter.Methods("PUT").Path("/api/providers/{provider}").HandlerFunc(func(response http.ResponseWriter, request *http.Request) { systemRouter.Methods("PUT").Path("/api/providers/{provider}").HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
@ -144,6 +146,17 @@ func (provider *WebProvider) getConfigHandler(response http.ResponseWriter, requ
templatesRenderer.JSON(response, http.StatusOK, currentConfigurations) templatesRenderer.JSON(response, http.StatusOK, currentConfigurations)
} }
func (provider *WebProvider) getVersionHandler(response http.ResponseWriter, request *http.Request) {
v := struct {
Version string
Codename string
}{
Version: version.Version,
Codename: version.Codename,
}
templatesRenderer.JSON(response, http.StatusOK, v)
}
func (provider *WebProvider) getProviderHandler(response http.ResponseWriter, request *http.Request) { func (provider *WebProvider) getProviderHandler(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request) vars := mux.Vars(request)
providerID := vars["provider"] providerID := vars["provider"]

View file

@ -0,0 +1,14 @@
'use strict';
var angular = require('angular');
var traefikCoreVersion = 'traefik.core.version';
module.exports = traefikCoreVersion;
angular
.module(traefikCoreVersion, ['ngResource'])
.factory('Version', Version);
/** @ngInject */
function Version($resource) {
return $resource('../api/version');
}

View file

@ -0,0 +1,10 @@
'use strict';
/** @ngInject */
function VersionController($scope, $interval, $log, Version) {
Version.get(function (version) {
$scope.version = version;
});
}
module.exports = VersionController;

View file

@ -0,0 +1,11 @@
'use strict';
var angular = require('angular');
var traefikCoreVersion = require('../core/version.resource');
var VersionController = require('./version.controller');
var traefikVersion = 'traefik.version';
module.exports = traefikVersion;
angular
.module(traefikVersion, [traefikCoreVersion])
.controller('VersionController', VersionController);

View file

@ -27,6 +27,11 @@
<li><a ui-sref="health">Health</a></li> <li><a ui-sref="health">Health</a></li>
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li>
<a ng-controller="VersionController" href="https://github.com/containous/traefik/tree/{{version.Version}}" target="_blank">
<small>{{version.Version}} / {{version.Codename}}</small>
</a>
</li>
<li> <li>
<a href="https://docs.traefik.io" target="_blank">Documentation</a> <a href="https://docs.traefik.io" target="_blank">Documentation</a>
</li> </li>

View file

@ -10,6 +10,7 @@ var uiRouter = require('angular-ui-router');
var uiBootstrap = require('angular-ui-bootstrap'); var uiBootstrap = require('angular-ui-bootstrap');
var moment = require('moment'); var moment = require('moment');
var traefikSection = require('./app/sections/sections'); var traefikSection = require('./app/sections/sections');
var traefikVersion = require('./app/version/version.module');
require('./index.scss'); require('./index.scss');
require('animate.css/animate.css'); require('animate.css/animate.css');
require('nvd3/build/nv.d3.css'); require('nvd3/build/nv.d3.css');
@ -28,7 +29,8 @@ angular
ngResource, ngResource,
uiRouter, uiRouter,
uiBootstrap, uiBootstrap,
traefikSection traefikSection,
traefikVersion
]) ])
.run(runBlock) .run(runBlock)
.constant('moment', moment) .constant('moment', moment)