diff --git a/CHANGELOG.md b/CHANGELOG.md index f735e2954..320724432 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # 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) [Full Changelog](https://github.com/containous/traefik/compare/v1.0.0...v1.1.0-rc1) diff --git a/README.md b/README.md index 15e223166..74a832141 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ 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 @@ -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. +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. 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 - [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): diff --git a/acme/acme.go b/acme/acme.go index 32bebf37a..0c88caae3 100644 --- a/acme/acme.go +++ b/acme/acme.go @@ -4,11 +4,13 @@ import ( "crypto/tls" "errors" "fmt" + "github.com/BurntSushi/ty/fun" "github.com/cenk/backoff" "github.com/containous/staert" "github.com/containous/traefik/cluster" "github.com/containous/traefik/log" "github.com/containous/traefik/safe" + "github.com/containous/traefik/types" "github.com/xenolf/lego/acme" "golang.org/x/net/context" "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) { + domain := types.CanonicalDomain(clientHello.ServerName) account := a.store.Get().(*Account) - if challengeCert, ok := a.challengeProvider.getCertificate(clientHello.ServerName); ok { - log.Debugf("ACME got challenge %s", clientHello.ServerName) + if challengeCert, ok := a.challengeProvider.getCertificate(domain); ok { + log.Debugf("ACME got challenge %s", domain) return challengeCert, nil } - if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok { - log.Debugf("ACME got domain cert %s", clientHello.ServerName) + if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok { + log.Debugf("ACME got domain cert %s", domain) return domainCert.tlsCert, nil } if a.OnDemand { - if a.checkOnDemandDomain != nil && !a.checkOnDemandDomain(clientHello.ServerName) { + if a.checkOnDemandDomain != nil && !a.checkOnDemandDomain(domain) { return nil, nil } return a.loadCertificateOnDemand(clientHello) } - log.Debugf("ACME got nothing %s", clientHello.ServerName) + log.Debugf("ACME got nothing %s", domain) 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) { + domain := types.CanonicalDomain(clientHello.ServerName) 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 } - certificate, err := a.getDomainsCertificates([]string{clientHello.ServerName}) + certificate, err := a.getDomainsCertificates([]string{domain}) if err != nil { 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() if err != nil { return nil, err } 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 { return nil, err } @@ -456,6 +460,7 @@ func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.C // LoadCertificateForDomains loads certificates from ACME for given domains func (a *ACME) LoadCertificateForDomains(domains []string) { + domains = fun.Map(types.CanonicalDomain, domains).([]string) safe.Go(func() { operation := func() error { if a.client == nil { @@ -514,6 +519,7 @@ func (a *ACME) LoadCertificateForDomains(domains []string) { } func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) { + domains = fun.Map(types.CanonicalDomain, domains).([]string) log.Debugf("Loading ACME certificates %s...", domains) bundle := true certificate, failures := a.client.ObtainCertificate(domains, bundle, nil) diff --git a/docs/toml.md b/docs/toml.md index ad2fe3ccd..25245c4bc 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -783,6 +783,13 @@ domain = "marathon.localhost" # # groupsAsSubDomains = true +# Enable compatibility with marathon-lb labels +# +# Optional +# Default: false +# +# marathonLBCompatibility = true + # Enable Marathon basic authentication # # 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`). -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 diff --git a/docs/user-guide/swarm-mode.md b/docs/user-guide/swarm-mode.md new file mode 100644 index 000000000..4bbc1c787 --- /dev/null +++ b/docs/user-guide/swarm-mode.md @@ -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) + + diff --git a/docs/user-guide/swarm.md b/docs/user-guide/swarm.md index 5a720fccf..13a9ab96b 100644 --- a/docs/user-guide/swarm.md +++ b/docs/user-guide/swarm.md @@ -1,7 +1,7 @@ # 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: +The cluster consists of: - 2 servers - 1 swarm master @@ -10,16 +10,16 @@ The cluster will be made of: ## 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) +1. You need to install [docker-machine](https://docs.docker.com/machine/) +2. You 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. +We 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. +This machine is the service registry of our cluster. ```sh docker-machine create -d virtualbox mh-keystore @@ -37,7 +37,7 @@ docker run -d \ ### 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 docker-machine create -d virtualbox \ @@ -50,7 +50,7 @@ docker-machine create -d virtualbox \ ### Create machine `mhs-demo1` -This machine will have a swarm agent on it. +This machine have a swarm agent on it. ```sh docker-machine create -d virtualbox \ @@ -84,14 +84,14 @@ docker $(docker-machine config mhs-demo0) run \ -l DEBUG \ -c /dev/null \ --docker \ - --docker.domain traefik \ - --docker.endpoint tcp://$(docker-machine ip mhs-demo0):3376 \ + --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.ca=/ssl/ca.pem \ + --docker.tls.cert=/ssl/server.pem \ + --docker.tls.key=/ssl/server-key.pem \ --docker.tls.insecureSkipVerify \ - --docker.watch \ + --docker.watch \ --web ``` @@ -102,7 +102,7 @@ Let's explain this command: - `-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.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 diff --git a/examples/k8s.ingress.yaml b/examples/k8s.ingress.yaml deleted file mode 100644 index e340406bf..000000000 --- a/examples/k8s.ingress.yaml +++ /dev/null @@ -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 diff --git a/examples/k8s.namespace.sh b/examples/k8s.namespace.yaml similarity index 51% rename from examples/k8s.namespace.sh rename to examples/k8s.namespace.yaml index 235beee0a..64e53ec0d 100755 --- a/examples/k8s.namespace.sh +++ b/examples/k8s.namespace.yaml @@ -1,10 +1,6 @@ -#!/bin/bash - -kubectl create -f - << EOF kind: Namespace apiVersion: v1 metadata: name: kube-system labels: - name: kube-system -EOF + name: kube-system \ No newline at end of file diff --git a/examples/k8s.rc.yaml b/examples/k8s.rc.yaml deleted file mode 100644 index 43d3029d5..000000000 --- a/examples/k8s.rc.yaml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/examples/k8s/traefik.yaml b/examples/k8s/traefik.yaml index 470cd235f..86e17fcea 100644 --- a/examples/k8s/traefik.yaml +++ b/examples/k8s/traefik.yaml @@ -16,11 +16,12 @@ spec: labels: k8s-app: traefik-ingress-lb name: traefik-ingress-lb - version: v1.0.0 + version: v1.1.0 spec: terminationGracePeriodSeconds: 60 + hostNetwork: true containers: - - image: traefik:v1.0.0 + - image: traefik:v1.1.0 name: traefik-ingress-lb resources: limits: diff --git a/glide.lock b/glide.lock index 0ec63fe12..96ae7f6b0 100644 --- a/glide.lock +++ b/glide.lock @@ -1,10 +1,10 @@ hash: 39ff28cc1d13d5915a870b14491ece1849c4eaf5a56cecd50a7676ecee6c6143 -updated: 2016-09-30T11:27:29.529525636+02:00 +updated: 2016-10-06T14:06:39.455848971+02:00 imports: - name: github.com/abbot/go-http-auth version: cb4372376e1e00e9f6ab9ec142e029302c9e7140 - name: github.com/boltdb/bolt - version: 5cc10bbbc5c141029940133bb33c9e969512a698 + version: f4c032d907f61f08dba2d719c58f108a1abb8e81 - name: github.com/BurntSushi/toml version: 99064174e013895bbd9b025c31100bd1d9b590ca - name: github.com/BurntSushi/ty @@ -18,7 +18,7 @@ imports: - name: github.com/codegangsta/cli version: 1efa31f08b9333f1bd4882d61f9d668a70cd902e - name: github.com/codegangsta/negroni - version: dc6b9d037e8dab60cbfc09c61d6932537829be8b + version: 3f7ce7b928e14ff890b067e5bbbc80af73690a9c - name: github.com/containous/flaeg version: a731c034dda967333efce5f8d276aeff11f8ff87 - name: github.com/containous/mux @@ -32,11 +32,11 @@ imports: - pkg/pathutil - pkg/types - name: github.com/davecgh/go-spew - version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d + version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 subpackages: - spew - name: github.com/docker/distribution - version: 87917f30529e6a7fca8eaff2932424915fb11225 + version: 99cb7c0946d2f5a38015443e515dc916295064d7 subpackages: - context - digest @@ -116,15 +116,15 @@ imports: - types/time - types/versions - name: github.com/docker/go-connections - version: 990a1a1a70b0da4c4cb70e117971a4f0babfbf1a + version: 988efe982fdecb46f01d53465878ff1f2ff411ce subpackages: - nat - sockets - tlsconfig - name: github.com/docker/go-units - version: f2d77a61e3c169b43402a0a1e84f06daf29b8190 + version: f2145db703495b2e525c59662db69a7344b00bb8 - name: github.com/docker/leadership - version: bfc7753dd48af19513b29deec23c364bf0f274eb + version: 0a913e2d71a12fd14a028452435cb71ac8d82cb6 - name: github.com/docker/libcompose version: d1876c1d68527a49c0aac22a0b161acc7296b740 subpackages: @@ -143,7 +143,7 @@ imports: - version - yaml - name: github.com/docker/libkv - version: 35d3e2084c650109e7bcc7282655b1bc8ba924ff + version: 3fce6a0f26e07da3eac45796a8e255547a47a750 subpackages: - store - store/boltdb @@ -153,13 +153,13 @@ imports: - name: github.com/donovanhide/eventsource version: fd1de70867126402be23c306e1ce32828455d85b - name: github.com/elazarl/go-bindata-assetfs - version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2 + version: 9a6736ed45b44bf3835afeebb3034b57ed329f3e - name: github.com/gambol99/go-marathon version: a558128c87724cd7430060ef5aedf39f83937f55 - name: github.com/go-check/check version: 4f90aeace3a26ad7021961c297b22c42160c7b25 - name: github.com/gogo/protobuf - version: e33835a643a970c11ac74f6333f5f6866387a101 + version: 99cb9b23110011cc45571c901ecae6f6f5e65cd3 subpackages: - proto - name: github.com/golang/glog @@ -169,18 +169,17 @@ imports: subpackages: - query - name: github.com/gorilla/context - version: aed02d124ae4a0e94fea4541c8effd05bf0c8296 + version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 - name: github.com/hashicorp/consul - version: fce7d75609a04eeb9d4bf41c8dc592aac18fc97d + version: d8e2fb7dd594163e25a89bc52c1a4613f5c5bfb8 subpackages: - api - name: github.com/hashicorp/go-cleanhttp - version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d + version: ad28ea4487f05916463e2423a55166280e8254b5 - name: github.com/hashicorp/serf - version: 6c4672d66fc6312ddde18399262943e21175d831 + version: b03bf85930b2349eb04b97c8fac437495296e3e7 subpackages: - coordinate - - serf - name: github.com/jarcoal/httpmock version: 145b10d659265440f062c31ea15326166bae56ee - name: github.com/libkermit/compose @@ -190,7 +189,7 @@ imports: - name: github.com/libkermit/docker version: 55e3595409924fcfbb850811e5a7cdbe8960a0b7 - name: github.com/mailgun/manners - version: fada45142db3f93097ca917da107aa3fad0ffcb5 + version: a585afd9d65c0e05f6c003f921e71ebc05074f4f - name: github.com/mailgun/timetools version: fd192d755b00c968d312d23f521eb0cdc6f66bd0 - name: github.com/mattn/go-shellwords @@ -222,7 +221,7 @@ imports: - name: github.com/miekg/dns version: 5d001d020961ae1c184f9f8152fdc73810481677 - name: github.com/mitchellh/mapstructure - version: d2dd0262208475919e1a362f675cfc0e7c10e905 + version: ca63d7c062ee3c9f34db231e352b60012b4fd0c1 - name: github.com/moul/http2curl version: b1479103caacaa39319f75e7f57fc545287fca0d - name: github.com/NYTimes/gziphandler @@ -230,11 +229,11 @@ imports: - name: github.com/ogier/pflag version: 45c278ab3607870051a2ea9040bb85fcb8557481 - name: github.com/opencontainers/runc - version: 1a81e9ab1f138c091fe5c86d0883f87716088527 + version: 02f8fa7863dd3f82909a73e2061897828460d52f subpackages: - libcontainer/user - name: github.com/parnurzeal/gorequest - version: 045012d33ef41ea146c1b675df9296d0dc1a212d + version: e30af16d4e485943aab0b0885ad6bdbb8c0d3dc7 - name: github.com/pmezard/go-difflib version: d8ed2627bdf02c080bf22230dbb337003b7aba2d subpackages: @@ -242,24 +241,24 @@ imports: - name: github.com/ryanuber/go-glob version: 572520ed46dbddaed19ea3d9541bdd0494163693 - name: github.com/samuel/go-zookeeper - version: e64db453f3512cade908163702045e0f31137843 + version: 87e1bca4477a3cc767ca71be023ced183d74e538 subpackages: - zk - name: github.com/satori/go.uuid version: 879c5887cd475cd7864858769793b2ceb0d44feb - name: github.com/Sirupsen/logrus - version: a283a10442df8dc09befd873fab202bf8a253d6a + version: 3ec0642a7fb6488f65b06f9040adc67e3990296a - name: github.com/streamrail/concurrent-map - version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287 + version: 8bf1e9bacbf65b10c81d0f4314cf2b1ebef728b5 - name: github.com/stretchr/objx version: cbeaeb16a013161a98496fad62933b1d21786672 - name: github.com/stretchr/testify - version: d77da356e56a7428ad25149ca77381849a6a5232 + version: 976c720a22c8eb4eb6a0b4348ad85ad12491a506 subpackages: - assert - mock - name: github.com/thoas/stats - version: 79b768ff1780f4e5b0ed132e192bfeefe9f85a9c + version: 152b5d051953fdb6e45f14b6826962aadc032324 - name: github.com/tv42/zbase32 version: 03389da7e0bf9844767f82690f4d68fc097a1306 - name: github.com/ugorji/go @@ -267,7 +266,7 @@ imports: subpackages: - codec - name: github.com/unrolled/render - version: 198ad4d8b8a4612176b804ca10555b222a086b40 + version: 526faf80cd4b305bb8134abea8d20d5ced74faa6 - name: github.com/vdemeester/docker-events version: be74d4929ec1ad118df54349fda4b0cba60f849b - name: github.com/vdemeester/shakers @@ -289,7 +288,7 @@ imports: - name: github.com/vulcand/route version: cb89d787ddbb1c5849a7ac9f79004c1fd12a4a32 - name: github.com/vulcand/vulcand - version: 28a4e5c0892167589737b95ceecbcef00295be50 + version: bed092e10989250b48bdb6aa3b0557b207f05c80 subpackages: - conntracker - plugin @@ -317,13 +316,13 @@ imports: - unix - windows - name: gopkg.in/fsnotify.v1 - version: a8a77c9133d2d6fd8334f3260d06f60e8d80a5fb + version: 944cff21b3baf3ced9a880365682152ba577d348 - name: gopkg.in/mgo.v2 - version: 29cc868a5ca65f401ff318143f9408d02f4799cc + version: 22287bab4379e1fbf6002fb4eb769888f3fb224c subpackages: - bson - name: gopkg.in/square/go-jose.v1 - version: e3f973b66b91445ec816dd7411ad1b6495a5a2fc + version: aa2e30fdd1fe9dd3394119af66451ae790d50e0d subpackages: - cipher - json @@ -341,9 +340,9 @@ testImports: - name: github.com/libkermit/docker-check version: cbe0ef03b3d23070eac4d00ba8828f2cc7f7e5a3 - name: github.com/spf13/pflag - version: 5644820622454e71517561946e3d94b9f9db6842 + version: 08b1a584251b5b62f458943640fc8ebd4d50aaa5 - name: github.com/vbatts/tar-split - version: 6810cedb21b2c3d0b9bb8f9af12ff2dc7a2f14df + version: bd4c5d64c3e9297f410025a3b1bd0c58f659e721 subpackages: - archive/tar - tar/asm diff --git a/mkdocs.yml b/mkdocs.yml index fbb1642d8..3aaf0284c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -49,6 +49,7 @@ pages: - User Guide: - 'Configuration examples': 'user-guide/examples.md' - 'Swarm cluster': 'user-guide/swarm.md' + - 'Swarm mode cluster': 'user-guide/swarm-mode.md' - 'Kubernetes': 'user-guide/kubernetes.md' - 'Key-value store configuration': 'user-guide/kv-config.md' - 'Clustering/HA': 'user-guide/cluster.md' diff --git a/provider/docker.go b/provider/docker.go index 411f18696..cd5ddc847 100644 --- a/provider/docker.go +++ b/provider/docker.go @@ -58,6 +58,7 @@ type dockerData struct { Name string Labels map[string]string // List of labels set to container or service NetworkSettings networkSettings + Health string } // 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("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) if err := <-errChan; err != nil { @@ -378,6 +382,11 @@ func (provider *Docker) containerFilter(container dockerData) bool { return false } + if container.Health != "" && container.Health != "healthy" { + log.Debugf("Filtering unhealthy or starting container %s", container.Name) + return false + } + 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 } @@ -602,7 +615,8 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD return []dockerData{}, err } for _, network := range networkList { - networkMap[network.ID] = &network + networkToAdd := network + networkMap[network.ID] = &networkToAdd } var dockerDataList []dockerData diff --git a/provider/docker_test.go b/provider/docker_test.go index 45f5ccbd8..3d106bd71 100644 --- a/provider/docker_test.go +++ b/provider/docker_test.go @@ -569,12 +569,18 @@ func TestDockerGetLabel(t *testing.T) { }{ { container: docker.ContainerJSON{ + ContainerJSONBase: &docker.ContainerJSONBase{ + Name: "foo", + }, Config: &container.Config{}, }, expected: "Label not found:", }, { container: docker.ContainerJSON{ + ContainerJSONBase: &docker.ContainerJSONBase{ + Name: "foo", + }, Config: &container.Config{ Labels: map[string]string{ "foo": "bar", @@ -608,6 +614,9 @@ func TestDockerGetLabels(t *testing.T) { }{ { container: docker.ContainerJSON{ + ContainerJSONBase: &docker.ContainerJSONBase{ + Name: "foo", + }, Config: &container.Config{}, }, expectedLabels: map[string]string{}, @@ -615,6 +624,9 @@ func TestDockerGetLabels(t *testing.T) { }, { container: docker.ContainerJSON{ + ContainerJSONBase: &docker.ContainerJSONBase{ + Name: "foo", + }, Config: &container.Config{ Labels: map[string]string{ "foo": "fooz", @@ -628,6 +640,9 @@ func TestDockerGetLabels(t *testing.T) { }, { container: docker.ContainerJSON{ + ContainerJSONBase: &docker.ContainerJSONBase{ + Name: "foo", + }, Config: &container.Config{ Labels: map[string]string{ "foo": "fooz", diff --git a/provider/marathon.go b/provider/marathon.go index 39934ec3e..f2ec40918 100644 --- a/provider/marathon.go +++ b/provider/marathon.go @@ -26,14 +26,15 @@ var _ Provider = (*Marathon)(nil) // Marathon holds configuration of the Marathon provider. type Marathon struct { BaseProvider - Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"` - Domain string `description:"Default domain used"` - ExposedByDefault bool `description:"Expose Marathon apps by default"` - GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"` - DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"` - TLS *ClientTLS `description:"Enable Docker TLS support"` - Basic *MarathonBasic - marathonClient marathon.Marathon + Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"` + Domain string `description:"Default domain used"` + ExposedByDefault bool `description:"Expose Marathon apps by default"` + GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"` + DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"` + MarathonLBCompatibility bool `description:"Add compatibility with marathon-lb labels"` + TLS *ClientTLS `description:"Enable Docker TLS support"` + Basic *MarathonBasic + marathonClient marathon.Marathon } // 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") 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 failingConstraint != nil { 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 { label, _ := provider.getLabel(app, "traefik.tags") 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 failingConstraint != nil { 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 { 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 } diff --git a/provider/marathon_test.go b/provider/marathon_test.go index 030cc2fb8..b9e712ea1 100644 --- a/provider/marathon_test.go +++ b/provider/marathon_test.go @@ -114,7 +114,7 @@ func TestMarathonLoadConfig(t *testing.T) { applications: &marathon.Applications{ Apps: []marathon.Application{ { - ID: "/testLoadBalancerAndCircuitBreaker", + ID: "/testLoadBalancerAndCircuitBreaker.dot", Ports: []int{80}, Labels: &map[string]string{ "traefik.backend.loadbalancer.method": "drr", @@ -126,29 +126,29 @@ func TestMarathonLoadConfig(t *testing.T) { tasks: &marathon.Tasks{ Tasks: []marathon.Task{ { - ID: "testLoadBalancerAndCircuitBreaker", - AppID: "/testLoadBalancerAndCircuitBreaker", + ID: "testLoadBalancerAndCircuitBreaker.dot", + AppID: "/testLoadBalancerAndCircuitBreaker.dot", Host: "127.0.0.1", Ports: []int{80}, }, }, }, expectedFrontends: map[string]*types.Frontend{ - `frontend-testLoadBalancerAndCircuitBreaker`: { - Backend: "backend-testLoadBalancerAndCircuitBreaker", + `frontend-testLoadBalancerAndCircuitBreaker.dot`: { + Backend: "backend-testLoadBalancerAndCircuitBreaker.dot", PassHostHeader: true, EntryPoints: []string{}, Routes: map[string]types.Route{ - `route-host-testLoadBalancerAndCircuitBreaker`: { - Rule: "Host:testLoadBalancerAndCircuitBreaker.docker.localhost", + `route-host-testLoadBalancerAndCircuitBreaker.dot`: { + Rule: "Host:testLoadBalancerAndCircuitBreaker.dot.docker.localhost", }, }, }, }, expectedBackends: map[string]*types.Backend{ - "backend-testLoadBalancerAndCircuitBreaker": { + "backend-testLoadBalancerAndCircuitBreaker.dot": { Servers: map[string]types.Server{ - "server-testLoadBalancerAndCircuitBreaker": { + "server-testLoadBalancerAndCircuitBreaker-dot": { URL: "http://127.0.0.1:80", Weight: 0, }, @@ -684,9 +684,10 @@ func TestMarathonTaskFilter(t *testing.T) { func TestMarathonAppConstraints(t *testing.T) { cases := []struct { - application marathon.Application - filteredTasks []marathon.Task - expected bool + application marathon.Application + filteredTasks []marathon.Task + expected bool + marathonLBCompatibility bool }{ { application: marathon.Application{ @@ -698,28 +699,48 @@ func TestMarathonAppConstraints(t *testing.T) { AppID: "foo1", }, }, - expected: false, + marathonLBCompatibility: false, + expected: false, }, { application: marathon.Application{ - ID: "foo", + ID: "foo2", Labels: &map[string]string{ "traefik.tags": "valid", }, }, 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 { + provider := &Marathon{ + MarathonLBCompatibility: c.marathonLBCompatibility, + } + constraint, _ := types.NewConstraint("tag==valid") + provider.Constraints = []types.Constraint{*constraint} actual := provider.applicationFilter(c.application, c.filteredTasks) if actual != c.expected { 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) { cases := []struct { - applications []marathon.Application - filteredTask marathon.Task - expected bool + applications []marathon.Application + filteredTask marathon.Task + expected bool + marathonLBCompatibility bool }{ { applications: []marathon.Application{ @@ -749,7 +771,8 @@ func TestMarathonTaskConstraints(t *testing.T) { AppID: "foo1", Ports: []int{80}, }, - expected: false, + marathonLBCompatibility: false, + expected: false, }, { applications: []marathon.Application{ @@ -764,14 +787,40 @@ func TestMarathonTaskConstraints(t *testing.T) { AppID: "foo2", 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 { + provider := &Marathon{ + MarathonLBCompatibility: c.marathonLBCompatibility, + } + constraint, _ := types.NewConstraint("tag==valid") + provider.Constraints = []types.Constraint{*constraint} apps := new(marathon.Applications) apps.Apps = c.applications actual := provider.taskFilter(c.filteredTask, apps, true) @@ -1152,37 +1201,53 @@ func TestMarathonGetEntryPoints(t *testing.T) { } func TestMarathonGetFrontendRule(t *testing.T) { - provider := &Marathon{ - Domain: "docker.localhost", - } - applications := []struct { - application marathon.Application - expected string + application marathon.Application + expected string + marathonLBCompatibility bool }{ { application: marathon.Application{ Labels: &map[string]string{}}, - expected: "Host:.docker.localhost", + marathonLBCompatibility: true, + expected: "Host:.docker.localhost", }, { application: marathon.Application{ - ID: "test", - Labels: &map[string]string{}, + ID: "test", + Labels: &map[string]string{ + "HAPROXY_0_VHOST": "foo.bar", + }, }, - expected: "Host:test.docker.localhost", + marathonLBCompatibility: false, + expected: "Host:test.docker.localhost", }, { application: marathon.Application{ Labels: &map[string]string{ "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 { + provider := &Marathon{ + Domain: "docker.localhost", + MarathonLBCompatibility: a.marathonLBCompatibility, + } actual := provider.getFrontendRule(a.application) if actual != a.expected { t.Fatalf("expected %q, got %q", a.expected, actual) diff --git a/rules.go b/rules.go index c091e0fa6..96cc73b6e 100644 --- a/rules.go +++ b/rules.go @@ -3,7 +3,9 @@ package main import ( "errors" "fmt" + "github.com/BurntSushi/ty/fun" "github.com/containous/mux" + "github.com/containous/traefik/types" "net" "net/http" "reflect" @@ -24,7 +26,7 @@ func (r *Rules) host(hosts ...string) *mux.Route { reqHost = req.Host } for _, host := range hosts { - if reqHost == strings.TrimSpace(host) { + if types.CanonicalDomain(reqHost) == types.CanonicalDomain(host) { return true } } @@ -35,7 +37,7 @@ func (r *Rules) host(hosts ...string) *mux.Route { func (r *Rules) hostRegexp(hosts ...string) *mux.Route { router := r.route.route.Subrouter() for _, host := range hosts { - router.Host(strings.TrimSpace(host)) + router.Host(types.CanonicalDomain(host)) } return r.route.route } @@ -43,7 +45,7 @@ func (r *Rules) hostRegexp(hosts ...string) *mux.Route { func (r *Rules) path(paths ...string) *mux.Route { router := r.route.route.Subrouter() for _, path := range paths { - router.Path(strings.TrimSpace(path)) + router.Path(types.CanonicalDomain(path)) } return r.route.route } @@ -51,7 +53,7 @@ func (r *Rules) path(paths ...string) *mux.Route { func (r *Rules) pathPrefix(paths ...string) *mux.Route { router := r.route.route.Subrouter() for _, path := range paths { - router.PathPrefix(strings.TrimSpace(path)) + router.PathPrefix(types.CanonicalDomain(path)) } return r.route.route } @@ -67,7 +69,7 @@ func (r *Rules) pathStrip(paths ...string) *mux.Route { r.route.stripPrefixes = paths router := r.route.route.Subrouter() for _, path := range paths { - router.Path(strings.TrimSpace(path)) + router.Path(types.CanonicalDomain(path)) } return r.route.route } @@ -77,7 +79,7 @@ func (r *Rules) pathPrefixStrip(paths ...string) *mux.Route { r.route.stripPrefixes = paths router := r.route.route.Subrouter() for _, path := range paths { - router.PathPrefix(strings.TrimSpace(path)) + router.PathPrefix(types.CanonicalDomain(path)) } return r.route.route } @@ -153,7 +155,6 @@ func (r *Rules) parseRules(expression string, onRule func(functionName string, f } } return nil - } // Parse parses rules expressions @@ -197,5 +198,5 @@ func (r *Rules) ParseDomains(expression string) ([]string, error) { if err != nil { return nil, fmt.Errorf("Error parsing domains: %v", err) } - return domains, nil + return fun.Map(types.CanonicalDomain, domains).([]string), nil } diff --git a/rules_test.go b/rules_test.go index 2bb89a347..694fde089 100644 --- a/rules_test.go +++ b/rules_test.go @@ -36,7 +36,7 @@ func TestParseTwoRules(t *testing.T) { serverRoute := &serverRoute{route: route} rules := &Rules{route: serverRoute} - expression := "Host:foo.bar;Path:/foobar" + expression := "Host: Foo.Bar ; Path:/FOObar" routeResult, err := rules.Parse(expression) if err != nil { @@ -58,11 +58,13 @@ func TestParseDomains(t *testing.T) { "Host:foo.bar,test.bar", "Path:/test", "Host:foo.bar;Path:/test", + "Host: Foo.Bar ;Path:/test", } domainsSlice := [][]string{ {"foo.bar", "test.bar"}, {}, {"foo.bar"}, + {"foo.bar"}, } for i, expression := range expressionsSlice { domains, err := rules.ParseDomains(expression) diff --git a/script/deploy.sh b/script/deploy.sh index fe7277f5a..820cdce19 100755 --- a/script/deploy.sh +++ b/script/deploy.sh @@ -20,8 +20,8 @@ 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 +curl -LOs https://github.com/tcnksm/ghr/releases/download/v0.5.0/ghr_v0.5.0_linux_amd64.zip +unzip -q ghr_v0.5.0_linux_amd64.zip sudo mv ghr /usr/bin/ghr sudo chmod +x /usr/bin/ghr diff --git a/templates/marathon.tmpl b/templates/marathon.tmpl index 04a6912c2..2a617a378 100644 --- a/templates/marathon.tmpl +++ b/templates/marathon.tmpl @@ -1,35 +1,35 @@ {{$apps := .Applications}} [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}}" weight = {{getWeight . $apps}} {{end}} {{range .Applications}} {{ if hasMaxConnLabels . }} - [backends.backend{{getFrontendBackend . }}.maxconn] + [backends."backend{{getFrontendBackend . }}".maxconn] amount = {{getMaxConnAmount . }} extractorfunc = "{{getMaxConnExtractorFunc . }}" {{end}} {{ if hasLoadBalancerLabels . }} - [backends.backend{{getFrontendBackend . }}.loadbalancer] + [backends."backend{{getFrontendBackend . }}".loadbalancer] method = "{{getLoadBalancerMethod . }}" sticky = {{getSticky .}} {{end}} {{ if hasCircuitBreakerLabels . }} - [backends.backend{{getFrontendBackend . }}.circuitbreaker] + [backends."backend{{getFrontendBackend . }}".circuitbreaker] expression = "{{getCircuitBreakerExpression . }}" {{end}} {{end}} [frontends]{{range .Applications}} - [frontends.frontend{{.ID | replace "/" "-"}}] + [frontends."frontend{{.ID | replace "/" "-"}}"] backend = "backend{{getFrontendBackend .}}" passHostHeader = {{getPassHostHeader .}} priority = {{getPriority .}} entryPoints = [{{range getEntryPoints .}} "{{.}}", {{end}}] - [frontends.frontend{{.ID | replace "/" "-"}}.routes.route-host{{.ID | replace "/" "-"}}] + [frontends."frontend{{.ID | replace "/" "-"}}".routes."route-host{{.ID | replace "/" "-"}}"] rule = "{{getFrontendRule .}}" {{end}} diff --git a/types/types.go b/types/types.go index b9d8298f8..76fa96bd4 100644 --- a/types/types.go +++ b/types/types.go @@ -223,3 +223,8 @@ type Basic struct { type Digest struct { Users } + +// CanonicalDomain returns a lower case domain with trim space +func CanonicalDomain(domain string) string { + return strings.ToLower(strings.TrimSpace(domain)) +} diff --git a/web.go b/web.go index 97b31ff17..65a10f392 100644 --- a/web.go +++ b/web.go @@ -15,6 +15,7 @@ import ( "github.com/containous/traefik/middlewares" "github.com/containous/traefik/safe" "github.com/containous/traefik/types" + "github.com/containous/traefik/version" "github.com/elazarl/go-bindata-assetfs" "github.com/thoas/stats" "github.com/unrolled/render" @@ -60,6 +61,7 @@ func (provider *WebProvider) Provide(configurationChan chan<- types.ConfigMessag systemRouter.Methods("GET").Path("/ping").HandlerFunc(provider.getPingHandler) // API routes 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/{provider}").HandlerFunc(provider.getProviderHandler) 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) } +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) { vars := mux.Vars(request) providerID := vars["provider"] diff --git a/webui/src/app/core/version.resource.js b/webui/src/app/core/version.resource.js new file mode 100644 index 000000000..5ec80d56d --- /dev/null +++ b/webui/src/app/core/version.resource.js @@ -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'); + } diff --git a/webui/src/app/version/version.controller.js b/webui/src/app/version/version.controller.js new file mode 100644 index 000000000..373f28b06 --- /dev/null +++ b/webui/src/app/version/version.controller.js @@ -0,0 +1,10 @@ +'use strict'; + +/** @ngInject */ +function VersionController($scope, $interval, $log, Version) { + Version.get(function (version) { + $scope.version = version; + }); +} + +module.exports = VersionController; diff --git a/webui/src/app/version/version.module.js b/webui/src/app/version/version.module.js new file mode 100644 index 000000000..991ed2474 --- /dev/null +++ b/webui/src/app/version/version.module.js @@ -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); diff --git a/webui/src/index.html b/webui/src/index.html index 468431563..bec9ce4d5 100644 --- a/webui/src/index.html +++ b/webui/src/index.html @@ -27,6 +27,11 @@
  • Health