Merge 'v1.4.2' into master

This commit is contained in:
Fernandez Ludovic 2017-11-03 13:51:24 +01:00
commit 2070aa9443
16 changed files with 304 additions and 87 deletions

View file

@ -16,8 +16,21 @@ HOW TO WRITE A GOOD PULL REQUEST?
--> -->
### Description ### What does this PR do?
<!-- <!-- A brief description of the change being made with this pull request. -->
Briefly describe the pull request in a few paragraphs.
-->
### Motivation
<!-- What inspired you to submit this pull request? -->
### More
- [ ] Added/updated tests
- [ ] Added/updated documentation
### Additional Notes
<!-- Anything else we should know when reviewing? -->

View file

@ -1,5 +1,23 @@
# Change Log # Change Log
## [v1.4.2](https://github.com/containous/traefik/tree/v1.4.2) (2017-11-02)
[All Commits](https://github.com/containous/traefik/compare/v1.4.1...v1.4.2)
**Bug fixes:**
- **[cluster]** Fix datastore corruption on reload due to shrinking config size ([#2340](https://github.com/containous/traefik/pull/2340) by [else](https://github.com/else))
- **[docker,docker/swarm]** Make frontend names differents for similar routes ([#2338](https://github.com/containous/traefik/pull/2338) by [nmengin](https://github.com/nmengin))
- **[docker]** Fix IP address when Docker container network mode is container ([#2331](https://github.com/containous/traefik/pull/2331) by [nmengin](https://github.com/nmengin))
- **[docker]** Make the traefik.port label optional when using service labels in Docker containers. ([#2330](https://github.com/containous/traefik/pull/2330) by [nmengin](https://github.com/nmengin))
- **[docker]** Add unique ID to Docker services replicas ([#2314](https://github.com/containous/traefik/pull/2314) by [nmengin](https://github.com/nmengin))
- **[marathon]** Missing Backend key in configuration when application has no tasks ([#2333](https://github.com/containous/traefik/pull/2333) by [aantono](https://github.com/aantono))
- Remove hardcoded runtime.GOMAXPROCS. ([#2317](https://github.com/containous/traefik/pull/2317) by [ldez](https://github.com/ldez))
**Documentation:**
- **[k8s]** fixed dead link in kubernetes backend config docs ([#2337](https://github.com/containous/traefik/pull/2337) by [perplexa](https://github.com/perplexa))
- **[k8s]** Fix the k8s docs example deployment yaml ([#2308](https://github.com/containous/traefik/pull/2308) by [gnur](https://github.com/gnur))
- Minor grammar change ([#2350](https://github.com/containous/traefik/pull/2350) by [haxorjim](https://github.com/haxorjim))
- Minor typo ([#2343](https://github.com/containous/traefik/pull/2343) by [burningTyger](https://github.com/burningTyger))
## [v1.4.1](https://github.com/containous/traefik/tree/v1.4.1) (2017-10-24) ## [v1.4.1](https://github.com/containous/traefik/tree/v1.4.1) (2017-10-24)
[All Commits](https://github.com/containous/traefik/compare/v1.4.0...v1.4.1) [All Commits](https://github.com/containous/traefik/compare/v1.4.0...v1.4.1)

View file

@ -119,19 +119,8 @@ func (d *Datastore) watchChanges() error {
func (d *Datastore) reload() error { func (d *Datastore) reload() error {
log.Debug("Datastore reload") log.Debug("Datastore reload")
d.localLock.Lock() _, err := d.Load()
err := d.kv.LoadConfig(d.meta)
if err != nil {
d.localLock.Unlock()
return err return err
}
err = d.meta.unmarshall()
if err != nil {
d.localLock.Unlock()
return err
}
d.localLock.Unlock()
return nil
} }
// Begin creates a transaction with the KV store. // Begin creates a transaction with the KV store.

View file

@ -9,7 +9,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime"
"strings" "strings"
"time" "time"
@ -34,8 +33,6 @@ import (
) )
func main() { func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
//traefik config inits //traefik config inits
traefikConfiguration := NewTraefikConfiguration() traefikConfiguration := NewTraefikConfiguration()
traefikPointersConfiguration := NewTraefikDefaultPointersConfiguration() traefikPointersConfiguration := NewTraefikDefaultPointersConfiguration()

View file

@ -189,6 +189,12 @@ Services labels can be used for overriding default behaviour
| `traefik.<service-name>.frontend.priority` | Overrides `traefik.frontend.priority`. | | `traefik.<service-name>.frontend.priority` | Overrides `traefik.frontend.priority`. |
| `traefik.<service-name>.frontend.rule` | Overrides `traefik.frontend.rule`. | | `traefik.<service-name>.frontend.rule` | Overrides `traefik.frontend.rule`. |
!!! note
if a label is defined both as a `container label` and a `service label` (for example `traefik.<service-name>.port=PORT` and `traefik.port=PORT` ), the `service label` is used to defined the `<service-name>` property (`port` in the example).
It's possible to mix `container labels` and `service labels`, in this case `container labels` are used as default value for missing `service labels` but no frontends are going to be created with the `container labels`.
More details in this [example](/user-guide/docker-and-lets-encrypt/#labels).
!!! warning !!! warning
when running inside a container, Træfik will need network access through: when running inside a container, Træfik will need network access through:

View file

@ -2,7 +2,7 @@
Træfik can be configured to use Kubernetes Ingress as a backend configuration. Træfik can be configured to use Kubernetes Ingress as a backend configuration.
See also [Kubernetes user guide](/user-guide/kubernetes). See also [Kubernetes user guide](/docs/user-guide/kubernetes).
## Configuration ## Configuration

View file

@ -123,10 +123,10 @@ Otherwise, the response from the auth server is returned.
```toml ```toml
[entryPoints] [entryPoints]
[entrypoints.http] [entryPoints.http]
# ... # ...
# To enable forward auth on an entrypoint # To enable forward auth on an entrypoint
[entrypoints.http.auth.forward] [entryPoints.http.auth.forward]
address = "https://authserver.com/auth" address = "https://authserver.com/auth"
# Trust existing X-Forwarded-* headers. # Trust existing X-Forwarded-* headers.
@ -141,7 +141,7 @@ Otherwise, the response from the auth server is returned.
# #
# Optional # Optional
# #
[entrypoints.http.auth.forward.tls] [entryPoints.http.auth.forward.tls]
cert = "authserver.crt" cert = "authserver.crt"
key = "authserver.key" key = "authserver.key"
``` ```
@ -226,7 +226,7 @@ Only IPs in `trustedIPs` will lead to remote client address replacement: you sho
## Forwarded Header ## Forwarded Header
Only IPs in `trustedIPs` will be authorize to trust the client forwarded headers (`X-Forwarded-*`). Only IPs in `trustedIPs` will be authorized to trust the client forwarded headers (`X-Forwarded-*`).
```toml ```toml
[entryPoints] [entryPoints]

View file

@ -1,8 +1,8 @@
# Docker & Traefik # Docker & Traefik
In this use case, we want to use Traefik as a _layer-7_ load balancer with SSL termination for a set of micro-services used to run a web application. In this use case, we want to use Træfik as a _layer-7_ load balancer with SSL termination for a set of micro-services used to run a web application.
We also want to automatically _discover any services_ on the Docker host and let Traefik reconfigure itself automatically when containers get created (or shut down) so HTTP traffic can be routed accordingly. We also want to automatically _discover any services_ on the Docker host and let Træfik reconfigure itself automatically when containers get created (or shut down) so HTTP traffic can be routed accordingly.
In addition, we want to use Let's Encrypt to automatically generate and renew SSL certificates per hostname. In addition, we want to use Let's Encrypt to automatically generate and renew SSL certificates per hostname.
@ -19,7 +19,7 @@ In real-life, you'll want to use your own domain and have the DNS configured acc
Docker containers can only communicate with each other over TCP when they share at least one network. Docker containers can only communicate with each other over TCP when they share at least one network.
This makes sense from a topological point of view in the context of networking, since Docker under the hood creates IPTable rules so containers can't reach other containers _unless you'd want to_. This makes sense from a topological point of view in the context of networking, since Docker under the hood creates IPTable rules so containers can't reach other containers _unless you'd want to_.
In this example, we're going to use a single network called `web` where all containers that are handling HTTP traffic (including Traefik) will reside in. In this example, we're going to use a single network called `web` where all containers that are handling HTTP traffic (including Træfik) will reside in.
On the Docker host, run the following command: On the Docker host, run the following command:
@ -27,7 +27,7 @@ On the Docker host, run the following command:
docker network create web docker network create web
``` ```
Now, let's create a directory on the server where we will configure the rest of Traefik: Now, let's create a directory on the server where we will configure the rest of Træfik:
```shell ```shell
mkdir -p /opt/traefik mkdir -p /opt/traefik
@ -41,7 +41,7 @@ touch /opt/traefik/acme.json && chmod 600 /opt/traefik/acme.json
touch /opt/traefik/traefik.toml touch /opt/traefik/traefik.toml
``` ```
The `docker-compose.yml` file will provide us with a simple, consistent and more importantly, a deterministic way to create Traefik. The `docker-compose.yml` file will provide us with a simple, consistent and more importantly, a deterministic way to create Træfik.
The contents of the file is as follows: The contents of the file is as follows:
@ -69,12 +69,12 @@ networks:
``` ```
As you can see, we're mounting the `traefik.toml` file as well as the (empty) `acme.json` file in the container. As you can see, we're mounting the `traefik.toml` file as well as the (empty) `acme.json` file in the container.
Also, we're mounting the `/var/run/docker.sock` Docker socket in the container as well, so Traefik can listen to Docker events and reconfigure it's own internal configuration when containers are created (or shut down). Also, we're mounting the `/var/run/docker.sock` Docker socket in the container as well, so Træfik can listen to Docker events and reconfigure it's own internal configuration when containers are created (or shut down).
Also, we're making sure the container is automatically restarted by the Docker engine in case of problems (or: if the server is rebooted). Also, we're making sure the container is automatically restarted by the Docker engine in case of problems (or: if the server is rebooted).
We're publishing the default HTTP ports `80` and `443` on the host, and making sure the container is placed within the `web` network we've created earlier on. We're publishing the default HTTP ports `80` and `443` on the host, and making sure the container is placed within the `web` network we've created earlier on.
Finally, we're giving this container a static name called `traefik`. Finally, we're giving this container a static name called `traefik`.
Let's take a look at a simple `traefik.toml` configuration as well before we'll create the Traefik container: Let's take a look at a simple `traefik.toml` configuration as well before we'll create the Træfik container:
```toml ```toml
debug = false debug = false
@ -109,17 +109,17 @@ OnHostRule = true
This is the minimum configuration required to do the following: This is the minimum configuration required to do the following:
- Log `ERROR`-level messages (or more severe) to the console, but silence `DEBUG`-level messagse - Log `ERROR`-level messages (or more severe) to the console, but silence `DEBUG`-level messagse
- Check for new versions of Traefik periodically - Check for new versions of Træfik periodically
- Create two entry points, namely an `HTTP` endpoint on port `80`, and an `HTTPS` endpoint on port `443` where all incoming traffic on port `80` will immediately get redirected to `HTTPS`. - Create two entry points, namely an `HTTP` endpoint on port `80`, and an `HTTPS` endpoint on port `443` where all incoming traffic on port `80` will immediately get redirected to `HTTPS`.
- Enable the Docker configuration backend and listen for container events on the Docker unix socket we've mounted earlier. However, **new containers will not be exposed by Traefik by default, we'll get into this in a bit!** - Enable the Docker configuration backend and listen for container events on the Docker unix socket we've mounted earlier. However, **new containers will not be exposed by Træfik by default, we'll get into this in a bit!**
- Enable automatic request and configuration of SSL certificates using Let's Encrypt. - Enable automatic request and configuration of SSL certificates using Let's Encrypt.
These certificates will be stored in the `acme.json` file, which you can back-up yourself and store off-premises. These certificates will be stored in the `acme.json` file, which you can back-up yourself and store off-premises.
Alright, let's boot the container. From the `/opt/traefik` directory, run `docker-compose up -d` which will create and start the Traefik container. Alright, let's boot the container. From the `/opt/traefik` directory, run `docker-compose up -d` which will create and start the Træfik container.
## Exposing Web Services to the Outside World ## Exposing Web Services to the Outside World
Now that we've fully configured and started Traefik, it's time to get our applications running! Now that we've fully configured and started Træfik, it's time to get our applications running!
Let's take a simple example of a micro-service project consisting of various services, where some will be exposed to the outside world and some will not. Let's take a simple example of a micro-service project consisting of various services, where some will be exposed to the outside world and some will not.
@ -148,6 +148,10 @@ services:
- "traefik.frontend.rule=Host:app.my-awesome-app.org" - "traefik.frontend.rule=Host:app.my-awesome-app.org"
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.port=9000" - "traefik.port=9000"
- "traefik.default.protocol=http"
- "traefik.admin.frontend.rule=Host:admin-app.my-awesome-app.org"
- "traefik.admin.protocol=https"
- "traefik.admin.port=9443"
db: db:
image: my-docker-registry.com/back-end/5.7 image: my-docker-registry.com/back-end/5.7
@ -190,10 +194,10 @@ Since the `traefik` container we've created and started earlier is also attached
### Labels ### Labels
As mentioned earlier, we don't want containers exposed automatically by Traefik. As mentioned earlier, we don't want containers exposed automatically by Træfik.
The reason behind this is simple: we want to have control over this process ourselves. The reason behind this is simple: we want to have control over this process ourselves.
Thanks to Docker labels, we can tell Traefik how to create it's internal routing configuration. Thanks to Docker labels, we can tell Træfik how to create it's internal routing configuration.
Let's take a look at the labels themselves for the `app` service, which is a HTTP webservice listing on port 9000: Let's take a look at the labels themselves for the `app` service, which is a HTTP webservice listing on port 9000:
@ -203,31 +207,56 @@ Let's take a look at the labels themselves for the `app` service, which is a HTT
- "traefik.frontend.rule=Host:app.my-awesome-app.org" - "traefik.frontend.rule=Host:app.my-awesome-app.org"
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.port=9000" - "traefik.port=9000"
- "traefik.default.protocol=http"
- "traefik.admin.frontend.rule=Host:admin-app.my-awesome-app.org"
- "traefik.admin.protocol=https"
- "traefik.admin.port=9443"
``` ```
We use both `container labels` and `service labels`.
#### Container labels
First, we specify the `backend` name which corresponds to the actual service we're routing **to**. First, we specify the `backend` name which corresponds to the actual service we're routing **to**.
We also tell Traefik to use the `web` network to route HTTP traffic to this container. We also tell Træfik to use the `web` network to route HTTP traffic to this container.
With the `frontend.rule` label, we tell Traefik that we want to route to this container if the incoming HTTP request contains the `Host` `app.my-awesome-app.org`. With the `traefik.enable` label, we tell Træfik to include this container in it's internal configuration.
Essentially, this is the actual rule used for Layer-7 load balancing.
With the `traefik.enable` label, we tell Traefik to include this container in it's internal configuration.
Finally but not unimportantly, we tell Traefik to route **to** port `9000`, since that is the actual TCP/IP port the container actually listens on. With the `frontend.rule` label, we tell Træfik that we want to route to this container if the incoming HTTP request contains the `Host` `app.my-awesome-app.org`.
Essentially, this is the actual rule used for Layer-7 load balancing.
Finally but not unimportantly, we tell Træfik to route **to** port `9000`, since that is the actual TCP/IP port the container actually listens on.
### Service labels
`Service labels` allow managing many routes for the same container.
When both `container labels` and `service labels` are defined, `container labels` are just used as default values for missing `service labels` but no frontend/backend are going to be defined only with these labels.
Obviously, labels `traefik.frontend.rule` and `traefik.port` described above, will only be used to complete information set in `service labels` during the container frontends/bakends creation.
In the example, two service names are defined : `default` and `admin`.
They allow creating two frontends and two backends.
- `default` has only one `service label` : `traefik.default.protocol`.
Træfik will use values set in `traefik.frontend.rule` and `traefik.port` to create the `default` frontend and backend.
The frontend listens to incoming HTTP requests which contain the `Host` `app.my-awesome-app.org` and redirect them in `HTTP` to the port `9000` of the backend.
- `admin` has all the `services labels` needed to create the `admin` frontend and backend (`traefik.admin.frontend.rule`, `traefik.admin.protocol`, `traefik.admin.port`).
Træfik will create a frontend to listen to incoming HTTP requests which contain the `Host` `admin-app.my-awesome-app.org` and redirect them in `HTTPS` to the port `9443` of the backend.
#### Gotchas and tips #### Gotchas and tips
- Always specify the correct port where the container expects HTTP traffic using `traefik.port` label. - Always specify the correct port where the container expects HTTP traffic using `traefik.port` label.
If a container exposes multiple ports, Traefik may forward traffic to the wrong port. If a container exposes multiple ports, Træfik may forward traffic to the wrong port.
Even if a container only exposes one port, you should always write configuration defensively and explicitly. Even if a container only exposes one port, you should always write configuration defensively and explicitly.
- Should you choose to enable the `exposedbydefault` flag in the `traefik.toml` configuration, be aware that all containers that are placed in the same network as Traefik will automatically be reachable from the outside world, for everyone and everyone to see. - Should you choose to enable the `exposedbydefault` flag in the `traefik.toml` configuration, be aware that all containers that are placed in the same network as Træfik will automatically be reachable from the outside world, for everyone and everyone to see.
Usually, this is a bad idea. Usually, this is a bad idea.
- With the `traefik.frontend.auth.basic` label, it's possible for Traefik to provide a HTTP basic-auth challenge for the endpoints you provide the label for. - With the `traefik.frontend.auth.basic` label, it's possible for Træfik to provide a HTTP basic-auth challenge for the endpoints you provide the label for.
- Traefik has built-in support to automatically export [Prometheus](https://prometheus.io) metrics - Træfik has built-in support to automatically export [Prometheus](https://prometheus.io) metrics
- Traefik supports websockets out of the box. In the example above, the `events`-service could be a NodeJS-based application which allows clients to connect using websocket protocol. - Træfik supports websockets out of the box. In the example above, the `events`-service could be a NodeJS-based application which allows clients to connect using websocket protocol.
Thanks to the fact that HTTPS in our example is enforced, these websockets are automatically secure as well (WSS) Thanks to the fact that HTTPS in our example is enforced, these websockets are automatically secure as well (WSS)
### Final thoughts ### Final thoughts
Using Traefik as a Layer-7 load balancer in combination with both Docker and Let's Encrypt provides you with an extremely flexible, powerful and self-configuring solution for your projects. Using Træfik as a Layer-7 load balancer in combination with both Docker and Let's Encrypt provides you with an extremely flexible, powerful and self-configuring solution for your projects.
With Let's Encrypt, your endpoints are automatically secured with production-ready SSL certificates that are renewed automatically as well. With Let's Encrypt, your endpoints are automatically secured with production-ready SSL certificates that are renewed automatically as well.

View file

@ -121,6 +121,7 @@ kind: Service
apiVersion: v1 apiVersion: v1
metadata: metadata:
name: traefik-ingress-service name: traefik-ingress-service
namespace: kube-system
spec: spec:
selector: selector:
k8s-app: traefik-ingress-lb k8s-app: traefik-ingress-lb
@ -185,6 +186,7 @@ kind: Service
apiVersion: v1 apiVersion: v1
metadata: metadata:
name: traefik-ingress-service name: traefik-ingress-service
namespace: kube-system
spec: spec:
selector: selector:
k8s-app: traefik-ingress-lb k8s-app: traefik-ingress-lb

View file

@ -129,6 +129,11 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
} }
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla") s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
// Start another container by replacing a '.' by a '-'
labels = map[string]string{
types.LabelFrontendRule: "Host:my-super.host",
}
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blablabla")
// Start traefik // Start traefik
cmd, display := s.traefikCmd(withConfigFile(file)) cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c) defer display(c)
@ -138,12 +143,20 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil) req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
req.Host = "my.super.host" req.Host = "my-super.host"
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?) // FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK) resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil)
c.Assert(err, checker.IsNil)
req.Host = "my.super.host"
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
resp, err = try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK)
c.Assert(err, checker.IsNil)
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
@ -179,3 +192,40 @@ func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) {
err = try.Request(req, 1500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) err = try.Request(req, 1500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
} }
// TestDockerContainersWithServiceLabels allows cheking the labels behavior
// Use service label if defined and compete information with container labels.
func (s *DockerSuite) TestDockerContainersWithServiceLabels(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
defer os.Remove(file)
// Start a container with some labels
labels := map[string]string{
types.LabelPrefix + "servicename.frontend.rule": "Host:my.super.host",
types.LabelFrontendRule: "Host:my.wrong.host",
types.LabelPort: "2375",
}
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
// Start traefik
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/version", nil)
c.Assert(err, checker.IsNil)
req.Host = "my.super.host"
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK)
c.Assert(err, checker.IsNil)
body, err := ioutil.ReadAll(resp.Body)
c.Assert(err, checker.IsNil)
var version map[string]interface{}
c.Assert(json.Unmarshal(body, &version), checker.IsNil)
c.Assert(version["Version"], checker.Equals, "swarm/1.0.0")
}

View file

@ -29,11 +29,12 @@ import (
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/docker/go-connections/sockets" "github.com/docker/go-connections/sockets"
"github.com/pkg/errors"
) )
const ( const (
// SwarmAPIVersion is a constant holding the version of the Provider API traefik will use // SwarmAPIVersion is a constant holding the version of the Provider API traefik will use
SwarmAPIVersion string = "1.24" SwarmAPIVersion = "1.24"
// SwarmDefaultWatchTime is the duration of the interval when polling docker // SwarmDefaultWatchTime is the duration of the interval when polling docker
SwarmDefaultWatchTime = 15 * time.Second SwarmDefaultWatchTime = 15 * time.Second
@ -306,8 +307,8 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con
frontends := map[string][]dockerData{} frontends := map[string][]dockerData{}
backends := map[string]dockerData{} backends := map[string]dockerData{}
servers := map[string][]dockerData{} servers := map[string][]dockerData{}
for _, container := range filteredContainers { for idx, container := range filteredContainers {
frontendName := p.getFrontendName(container) frontendName := p.getFrontendName(container, idx)
frontends[frontendName] = append(frontends[frontendName], container) frontends[frontendName] = append(frontends[frontendName], container)
backendName := p.getBackend(container) backendName := p.getBackend(container)
backends[backendName] = container backends[backendName] = container
@ -522,9 +523,16 @@ func (p *Provider) getMaxConnExtractorFunc(container dockerData) string {
} }
func (p *Provider) containerFilter(container dockerData) bool { func (p *Provider) containerFilter(container dockerData) bool {
_, err := strconv.Atoi(container.Labels[types.LabelPort]) var err error
portLabel := "traefik.port label"
if p.hasServices(container) {
portLabel = "traefik.<serviceName>.port or " + portLabel + "s"
err = checkServiceLabelPort(container)
} else {
_, err = strconv.Atoi(container.Labels[types.LabelPort])
}
if len(container.NetworkSettings.Ports) == 0 && err != nil { if len(container.NetworkSettings.Ports) == 0 && err != nil {
log.Debugf("Filtering container without port and no traefik.port label %s", container.Name) log.Debugf("Filtering container without port and no %s %s : %s", portLabel, container.Name, err.Error())
return false return false
} }
@ -554,9 +562,46 @@ func (p *Provider) containerFilter(container dockerData) bool {
return true return true
} }
func (p *Provider) getFrontendName(container dockerData) string { // checkServiceLabelPort checks if all service names have a port service label
// or if port container label exists for default value
func checkServiceLabelPort(container dockerData) error {
// If port container label is present, there is a default values for all ports, use it for the check
_, err := strconv.Atoi(container.Labels[types.LabelPort])
if err != nil {
serviceLabelPorts := make(map[string]struct{})
serviceLabels := make(map[string]struct{})
portRegexp := regexp.MustCompile(`^traefik\.(?P<service_name>.+?)\.port$`)
for label := range container.Labels {
// Get all port service labels
portLabel := portRegexp.FindStringSubmatch(label)
if portLabel != nil && len(portLabel) > 0 {
serviceLabelPorts[portLabel[0]] = struct{}{}
}
// Get only one instance of all service names from service labels
servicesLabelNames := servicesPropertiesRegexp.FindStringSubmatch(label)
if servicesLabelNames != nil && len(servicesLabelNames) > 0 {
serviceLabels[strings.Split(servicesLabelNames[0], ".")[1]] = struct{}{}
}
}
// If the number of service labels is different than the number of port services label
// there is an error
if len(serviceLabels) == len(serviceLabelPorts) {
for labelPort := range serviceLabelPorts {
_, err = strconv.Atoi(container.Labels[labelPort])
if err != nil {
break
}
}
} else {
err = errors.New("Port service labels missing, please use traefik.port as default value or define all port service labels")
}
}
return err
}
func (p *Provider) getFrontendName(container dockerData, idx int) string {
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78 // Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
return provider.Normalize(p.getFrontendRule(container)) return provider.Normalize(p.getFrontendRule(container) + "-" + strconv.Itoa(idx))
} }
// GetFrontendRule returns the frontend rule for the specified container, using // GetFrontendRule returns the frontend rule for the specified container, using
@ -597,7 +642,7 @@ func (p *Provider) getIPAddress(container dockerData) string {
} }
} }
if "host" == container.NetworkSettings.NetworkMode { if container.NetworkSettings.NetworkMode.IsHost() {
if container.Node != nil { if container.Node != nil {
if container.Node.IPAddress != "" { if container.Node.IPAddress != "" {
return container.Node.IPAddress return container.Node.IPAddress
@ -607,6 +652,21 @@ func (p *Provider) getIPAddress(container dockerData) string {
return "127.0.0.1" return "127.0.0.1"
} }
if container.NetworkSettings.NetworkMode.IsContainer() {
dockerClient, err := p.createClient()
if err != nil {
log.Warnf("Unable to get IP address for container %s, error: %s", container.Name, err)
return ""
}
ctx := context.Background()
containerInspected, err := dockerClient.ContainerInspect(ctx, container.NetworkSettings.NetworkMode.ConnectedContainer())
if err != nil {
log.Warnf("Unable to get IP address for container %s : Failed to inspect container ID %s, error: %s", container.Name, container.NetworkSettings.NetworkMode.ConnectedContainer(), err)
return ""
}
return p.getIPAddress(parseContainer(containerInspected))
}
if p.UseBindPortIP { if p.UseBindPortIP {
port := p.getPort(container) port := p.getPort(container)
for netport, portBindings := range container.NetworkSettings.Ports { for netport, portBindings := range container.NetworkSettings.Ports {

View file

@ -20,38 +20,38 @@ func TestDockerGetFrontendName(t *testing.T) {
}{ }{
{ {
container: containerJSON(name("foo")), container: containerJSON(name("foo")),
expected: "Host-foo-docker-localhost", expected: "Host-foo-docker-localhost-0",
}, },
{ {
container: containerJSON(labels(map[string]string{ container: containerJSON(labels(map[string]string{
types.LabelFrontendRule: "Headers:User-Agent,bat/0.1.0", types.LabelFrontendRule: "Headers:User-Agent,bat/0.1.0",
})), })),
expected: "Headers-User-Agent-bat-0-1-0", expected: "Headers-User-Agent-bat-0-1-0-0",
}, },
{ {
container: containerJSON(labels(map[string]string{ container: containerJSON(labels(map[string]string{
"com.docker.compose.project": "foo", "com.docker.compose.project": "foo",
"com.docker.compose.service": "bar", "com.docker.compose.service": "bar",
})), })),
expected: "Host-bar-foo-docker-localhost", expected: "Host-bar-foo-docker-localhost-0",
}, },
{ {
container: containerJSON(labels(map[string]string{ container: containerJSON(labels(map[string]string{
types.LabelFrontendRule: "Host:foo.bar", types.LabelFrontendRule: "Host:foo.bar",
})), })),
expected: "Host-foo-bar", expected: "Host-foo-bar-0",
}, },
{ {
container: containerJSON(labels(map[string]string{ container: containerJSON(labels(map[string]string{
types.LabelFrontendRule: "Path:/test", types.LabelFrontendRule: "Path:/test",
})), })),
expected: "Path-test", expected: "Path-test-0",
}, },
{ {
container: containerJSON(labels(map[string]string{ container: containerJSON(labels(map[string]string{
types.LabelFrontendRule: "PathPrefix:/test2", types.LabelFrontendRule: "PathPrefix:/test2",
})), })),
expected: "PathPrefix-test2", expected: "PathPrefix-test2-0",
}, },
} }
@ -63,7 +63,7 @@ func TestDockerGetFrontendName(t *testing.T) {
provider := &Provider{ provider := &Provider{
Domain: "docker.localhost", Domain: "docker.localhost",
} }
actual := provider.getFrontendName(dockerData) actual := provider.getFrontendName(dockerData, 0)
if actual != e.expected { if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual) t.Errorf("expected %q, got %q", e.expected, actual)
} }
@ -897,13 +897,13 @@ func TestDockerLoadDockerConfig(t *testing.T) {
), ),
}, },
expectedFrontends: map[string]*types.Frontend{ expectedFrontends: map[string]*types.Frontend{
"frontend-Host-test-docker-localhost": { "frontend-Host-test-docker-localhost-0": {
Backend: "backend-test", Backend: "backend-test",
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{}, EntryPoints: []string{},
BasicAuth: []string{}, BasicAuth: []string{},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test-docker-localhost": { "route-frontend-Host-test-docker-localhost-0": {
Rule: "Host:test.docker.localhost", Rule: "Host:test.docker.localhost",
}, },
}, },
@ -947,24 +947,24 @@ func TestDockerLoadDockerConfig(t *testing.T) {
), ),
}, },
expectedFrontends: map[string]*types.Frontend{ expectedFrontends: map[string]*types.Frontend{
"frontend-Host-test1-docker-localhost": { "frontend-Host-test1-docker-localhost-0": {
Backend: "backend-foobar", Backend: "backend-foobar",
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{"http", "https"}, EntryPoints: []string{"http", "https"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test1-docker-localhost": { "route-frontend-Host-test1-docker-localhost-0": {
Rule: "Host:test1.docker.localhost", Rule: "Host:test1.docker.localhost",
}, },
}, },
}, },
"frontend-Host-test2-docker-localhost": { "frontend-Host-test2-docker-localhost-1": {
Backend: "backend-foobar", Backend: "backend-foobar",
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{}, EntryPoints: []string{},
BasicAuth: []string{}, BasicAuth: []string{},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test2-docker-localhost": { "route-frontend-Host-test2-docker-localhost-1": {
Rule: "Host:test2.docker.localhost", Rule: "Host:test2.docker.localhost",
}, },
}, },
@ -1005,13 +1005,13 @@ func TestDockerLoadDockerConfig(t *testing.T) {
), ),
}, },
expectedFrontends: map[string]*types.Frontend{ expectedFrontends: map[string]*types.Frontend{
"frontend-Host-test1-docker-localhost": { "frontend-Host-test1-docker-localhost-0": {
Backend: "backend-foobar", Backend: "backend-foobar",
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{"http", "https"}, EntryPoints: []string{"http", "https"},
BasicAuth: []string{}, BasicAuth: []string{},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test1-docker-localhost": { "route-frontend-Host-test1-docker-localhost-0": {
Rule: "Host:test1.docker.localhost", Rule: "Host:test1.docker.localhost",
}, },
}, },
@ -1104,3 +1104,53 @@ func TestDockerHasStickinessLabel(t *testing.T) {
}) })
} }
} }
func TestDockerCheckPortLabels(t *testing.T) {
testCases := []struct {
container docker.ContainerJSON
expectedError bool
}{
{
container: containerJSON(labels(map[string]string{
types.LabelPort: "80",
})),
expectedError: false,
},
{
container: containerJSON(labels(map[string]string{
types.LabelPrefix + "servicename.protocol": "http",
types.LabelPrefix + "servicename.port": "80",
})),
expectedError: false,
},
{
container: containerJSON(labels(map[string]string{
types.LabelPrefix + "servicename.protocol": "http",
types.LabelPort: "80",
})),
expectedError: false,
},
{
container: containerJSON(labels(map[string]string{
types.LabelPrefix + "servicename.protocol": "http",
})),
expectedError: true,
},
}
for containerID, test := range testCases {
test := test
t.Run(strconv.Itoa(containerID), func(t *testing.T) {
t.Parallel()
dockerData := parseContainer(test.container)
err := checkServiceLabelPort(dockerData)
if test.expectedError && err == nil {
t.Error("expected an error but got nil")
} else if !test.expectedError && err != nil {
t.Errorf("expected no error, got %q", err)
}
})
}
}

View file

@ -23,28 +23,28 @@ func TestSwarmGetFrontendName(t *testing.T) {
}{ }{
{ {
service: swarmService(serviceName("foo")), service: swarmService(serviceName("foo")),
expected: "Host-foo-docker-localhost", expected: "Host-foo-docker-localhost-0",
networks: map[string]*docker.NetworkResource{}, networks: map[string]*docker.NetworkResource{},
}, },
{ {
service: swarmService(serviceLabels(map[string]string{ service: swarmService(serviceLabels(map[string]string{
types.LabelFrontendRule: "Headers:User-Agent,bat/0.1.0", types.LabelFrontendRule: "Headers:User-Agent,bat/0.1.0",
})), })),
expected: "Headers-User-Agent-bat-0-1-0", expected: "Headers-User-Agent-bat-0-1-0-0",
networks: map[string]*docker.NetworkResource{}, networks: map[string]*docker.NetworkResource{},
}, },
{ {
service: swarmService(serviceLabels(map[string]string{ service: swarmService(serviceLabels(map[string]string{
types.LabelFrontendRule: "Host:foo.bar", types.LabelFrontendRule: "Host:foo.bar",
})), })),
expected: "Host-foo-bar", expected: "Host-foo-bar-0",
networks: map[string]*docker.NetworkResource{}, networks: map[string]*docker.NetworkResource{},
}, },
{ {
service: swarmService(serviceLabels(map[string]string{ service: swarmService(serviceLabels(map[string]string{
types.LabelFrontendRule: "Path:/test", types.LabelFrontendRule: "Path:/test",
})), })),
expected: "Path-test", expected: "Path-test-0",
networks: map[string]*docker.NetworkResource{}, networks: map[string]*docker.NetworkResource{},
}, },
{ {
@ -54,7 +54,7 @@ func TestSwarmGetFrontendName(t *testing.T) {
types.LabelFrontendRule: "PathPrefix:/test2", types.LabelFrontendRule: "PathPrefix:/test2",
}), }),
), ),
expected: "PathPrefix-test2", expected: "PathPrefix-test2-0",
networks: map[string]*docker.NetworkResource{}, networks: map[string]*docker.NetworkResource{},
}, },
} }
@ -68,7 +68,7 @@ func TestSwarmGetFrontendName(t *testing.T) {
Domain: "docker.localhost", Domain: "docker.localhost",
SwarmMode: true, SwarmMode: true,
} }
actual := provider.getFrontendName(dockerData) actual := provider.getFrontendName(dockerData, 0)
if actual != e.expected { if actual != e.expected {
t.Errorf("expected %q, got %q", e.expected, actual) t.Errorf("expected %q, got %q", e.expected, actual)
} }
@ -660,13 +660,13 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
), ),
}, },
expectedFrontends: map[string]*types.Frontend{ expectedFrontends: map[string]*types.Frontend{
"frontend-Host-test-docker-localhost": { "frontend-Host-test-docker-localhost-0": {
Backend: "backend-test", Backend: "backend-test",
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{}, EntryPoints: []string{},
BasicAuth: []string{}, BasicAuth: []string{},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test-docker-localhost": { "route-frontend-Host-test-docker-localhost-0": {
Rule: "Host:test.docker.localhost", Rule: "Host:test.docker.localhost",
}, },
}, },
@ -714,24 +714,24 @@ func TestSwarmLoadDockerConfig(t *testing.T) {
), ),
}, },
expectedFrontends: map[string]*types.Frontend{ expectedFrontends: map[string]*types.Frontend{
"frontend-Host-test1-docker-localhost": { "frontend-Host-test1-docker-localhost-0": {
Backend: "backend-foobar", Backend: "backend-foobar",
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{"http", "https"}, EntryPoints: []string{"http", "https"},
BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test1-docker-localhost": { "route-frontend-Host-test1-docker-localhost-0": {
Rule: "Host:test1.docker.localhost", Rule: "Host:test1.docker.localhost",
}, },
}, },
}, },
"frontend-Host-test2-docker-localhost": { "frontend-Host-test2-docker-localhost-1": {
Backend: "backend-foobar", Backend: "backend-foobar",
PassHostHeader: true, PassHostHeader: true,
EntryPoints: []string{}, EntryPoints: []string{},
BasicAuth: []string{}, BasicAuth: []string{},
Routes: map[string]types.Route{ Routes: map[string]types.Route{
"route-frontend-Host-test2-docker-localhost": { "route-frontend-Host-test2-docker-localhost-1": {
Rule: "Host:test2.docker.localhost", Rule: "Host:test2.docker.localhost",
}, },
}, },

View file

@ -93,7 +93,9 @@ func TestMarathonLoadConfigNonAPIErrors(t *testing.T) {
}, },
}, },
}, },
expectedBackends: nil, expectedBackends: map[string]*types.Backend{
"backend-app": {},
},
}, },
{ {
desc: "load balancer / circuit breaker labels", desc: "load balancer / circuit breaker labels",

View file

@ -1,4 +1,4 @@
mkdocs==0.16.3 mkdocs==0.16.3
pymdown-extensions>=1.4 pymdown-extensions>=1.4
mkdocs-bootswatch>=0.4.0 mkdocs-bootswatch>=0.4.0
mkdocs-material>=1.8.1 mkdocs-material==1.12.2

View file

@ -12,6 +12,7 @@
{{range $app := $apps}} {{range $app := $apps}}
{{range $serviceIndex, $serviceName := getServiceNames $app}} {{range $serviceIndex, $serviceName := getServiceNames $app}}
[backends."backend{{getBackend $app $serviceName }}"]
{{ if hasMaxConnLabels $app }} {{ if hasMaxConnLabels $app }}
[backends."backend{{getBackend $app $serviceName }}".maxconn] [backends."backend{{getBackend $app $serviceName }}".maxconn]
amount = {{getMaxConnAmount $app }} amount = {{getMaxConnAmount $app }}