From 3158e51c62cfbc2e1082b9f855b1919e6107b834 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 25 Oct 2017 16:16:02 +0200 Subject: [PATCH 01/13] Remove hardcoded runtime.GOMAXPROCS. --- cmd/traefik/traefik.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index f8cc39df1..c7fc5288f 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -9,7 +9,6 @@ import ( "os" "path/filepath" "reflect" - "runtime" "strings" "time" @@ -34,8 +33,6 @@ import ( ) func main() { - runtime.GOMAXPROCS(runtime.NumCPU()) - //traefik config inits traefikConfiguration := NewTraefikConfiguration() traefikPointersConfiguration := NewTraefikDefaultPointersConfiguration() From 1b2cb53d4f38e49a1adcf1c3a9612ff932737d06 Mon Sep 17 00:00:00 2001 From: Erwin de Keijzer Date: Wed, 25 Oct 2017 16:58:04 +0200 Subject: [PATCH 02/13] Fix the k8s docs example deployment yaml --- docs/user-guide/kubernetes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/user-guide/kubernetes.md b/docs/user-guide/kubernetes.md index fa72b20ac..6ca3d1a72 100644 --- a/docs/user-guide/kubernetes.md +++ b/docs/user-guide/kubernetes.md @@ -118,6 +118,7 @@ kind: Service apiVersion: v1 metadata: name: traefik-ingress-service + namespace: kube-system spec: selector: k8s-app: traefik-ingress-lb @@ -182,6 +183,7 @@ kind: Service apiVersion: v1 metadata: name: traefik-ingress-service + namespace: kube-system spec: selector: k8s-app: traefik-ingress-lb From 9b5845f1cb82623198b94fd63f798648889efa95 Mon Sep 17 00:00:00 2001 From: Simon Elsbrock Date: Mon, 30 Oct 2017 11:22:04 +0100 Subject: [PATCH 03/13] Fix datastore corruption on reload due to shrinking config size --- cluster/datastore.go | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/cluster/datastore.go b/cluster/datastore.go index 47bfd89ba..cae6b8820 100644 --- a/cluster/datastore.go +++ b/cluster/datastore.go @@ -119,19 +119,8 @@ func (d *Datastore) watchChanges() error { func (d *Datastore) reload() error { log.Debug("Datastore reload") - d.localLock.Lock() - err := d.kv.LoadConfig(d.meta) - if err != nil { - d.localLock.Unlock() - return err - } - err = d.meta.unmarshall() - if err != nil { - d.localLock.Unlock() - return err - } - d.localLock.Unlock() - return nil + _, err := d.Load() + return err } // Begin creates a transaction with the KV store. From da7b6f0bafbf53aa8d50b6ed0de87ace81861a98 Mon Sep 17 00:00:00 2001 From: NicoMen Date: Mon, 30 Oct 2017 12:06:03 +0100 Subject: [PATCH 04/13] Make frontend names differents for similar routes --- integration/docker_test.go | 15 ++++++++++++++- provider/docker/docker.go | 8 ++++---- provider/docker/docker_test.go | 30 +++++++++++++++--------------- provider/docker/swarm_test.go | 24 ++++++++++++------------ 4 files changed, 45 insertions(+), 32 deletions(-) diff --git a/integration/docker_test.go b/integration/docker_test.go index e292c247a..665d6a9f8 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -129,6 +129,11 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) { } 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 cmd, display := s.traefikCmd(withConfigFile(file)) 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) 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 ?) resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK) 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) c.Assert(err, checker.IsNil) diff --git a/provider/docker/docker.go b/provider/docker/docker.go index 204d7eb16..4d4938790 100644 --- a/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -301,8 +301,8 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con frontends := map[string][]dockerData{} backends := map[string]dockerData{} servers := map[string][]dockerData{} - for _, container := range filteredContainers { - frontendName := p.getFrontendName(container) + for idx, container := range filteredContainers { + frontendName := p.getFrontendName(container, idx) frontends[frontendName] = append(frontends[frontendName], container) backendName := p.getBackend(container) backends[backendName] = container @@ -549,9 +549,9 @@ func (p *Provider) containerFilter(container dockerData) bool { return true } -func (p *Provider) getFrontendName(container dockerData) string { +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 - 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 diff --git a/provider/docker/docker_test.go b/provider/docker/docker_test.go index 718890e3e..8818f3235 100644 --- a/provider/docker/docker_test.go +++ b/provider/docker/docker_test.go @@ -20,38 +20,38 @@ func TestDockerGetFrontendName(t *testing.T) { }{ { container: containerJSON(name("foo")), - expected: "Host-foo-docker-localhost", + expected: "Host-foo-docker-localhost-0", }, { container: containerJSON(labels(map[string]string{ 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{ "com.docker.compose.project": "foo", "com.docker.compose.service": "bar", })), - expected: "Host-bar-foo-docker-localhost", + expected: "Host-bar-foo-docker-localhost-0", }, { container: containerJSON(labels(map[string]string{ types.LabelFrontendRule: "Host:foo.bar", })), - expected: "Host-foo-bar", + expected: "Host-foo-bar-0", }, { container: containerJSON(labels(map[string]string{ types.LabelFrontendRule: "Path:/test", })), - expected: "Path-test", + expected: "Path-test-0", }, { container: containerJSON(labels(map[string]string{ types.LabelFrontendRule: "PathPrefix:/test2", })), - expected: "PathPrefix-test2", + expected: "PathPrefix-test2-0", }, } @@ -63,7 +63,7 @@ func TestDockerGetFrontendName(t *testing.T) { provider := &Provider{ Domain: "docker.localhost", } - actual := provider.getFrontendName(dockerData) + actual := provider.getFrontendName(dockerData, 0) if actual != e.expected { t.Errorf("expected %q, got %q", e.expected, actual) } @@ -884,13 +884,13 @@ func TestDockerLoadDockerConfig(t *testing.T) { ), }, expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-test-docker-localhost": { + "frontend-Host-test-docker-localhost-0": { Backend: "backend-test", PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, Routes: map[string]types.Route{ - "route-frontend-Host-test-docker-localhost": { + "route-frontend-Host-test-docker-localhost-0": { Rule: "Host:test.docker.localhost", }, }, @@ -934,24 +934,24 @@ func TestDockerLoadDockerConfig(t *testing.T) { ), }, expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-test1-docker-localhost": { + "frontend-Host-test1-docker-localhost-0": { Backend: "backend-foobar", PassHostHeader: true, EntryPoints: []string{"http", "https"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, Routes: map[string]types.Route{ - "route-frontend-Host-test1-docker-localhost": { + "route-frontend-Host-test1-docker-localhost-0": { Rule: "Host:test1.docker.localhost", }, }, }, - "frontend-Host-test2-docker-localhost": { + "frontend-Host-test2-docker-localhost-1": { Backend: "backend-foobar", PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, Routes: map[string]types.Route{ - "route-frontend-Host-test2-docker-localhost": { + "route-frontend-Host-test2-docker-localhost-1": { Rule: "Host:test2.docker.localhost", }, }, @@ -992,13 +992,13 @@ func TestDockerLoadDockerConfig(t *testing.T) { ), }, expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-test1-docker-localhost": { + "frontend-Host-test1-docker-localhost-0": { Backend: "backend-foobar", PassHostHeader: true, EntryPoints: []string{"http", "https"}, BasicAuth: []string{}, Routes: map[string]types.Route{ - "route-frontend-Host-test1-docker-localhost": { + "route-frontend-Host-test1-docker-localhost-0": { Rule: "Host:test1.docker.localhost", }, }, diff --git a/provider/docker/swarm_test.go b/provider/docker/swarm_test.go index c16393c7d..1ece57a21 100644 --- a/provider/docker/swarm_test.go +++ b/provider/docker/swarm_test.go @@ -23,28 +23,28 @@ func TestSwarmGetFrontendName(t *testing.T) { }{ { service: swarmService(serviceName("foo")), - expected: "Host-foo-docker-localhost", + expected: "Host-foo-docker-localhost-0", networks: map[string]*docker.NetworkResource{}, }, { service: swarmService(serviceLabels(map[string]string{ 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{}, }, { service: swarmService(serviceLabels(map[string]string{ types.LabelFrontendRule: "Host:foo.bar", })), - expected: "Host-foo-bar", + expected: "Host-foo-bar-0", networks: map[string]*docker.NetworkResource{}, }, { service: swarmService(serviceLabels(map[string]string{ types.LabelFrontendRule: "Path:/test", })), - expected: "Path-test", + expected: "Path-test-0", networks: map[string]*docker.NetworkResource{}, }, { @@ -54,7 +54,7 @@ func TestSwarmGetFrontendName(t *testing.T) { types.LabelFrontendRule: "PathPrefix:/test2", }), ), - expected: "PathPrefix-test2", + expected: "PathPrefix-test2-0", networks: map[string]*docker.NetworkResource{}, }, } @@ -68,7 +68,7 @@ func TestSwarmGetFrontendName(t *testing.T) { Domain: "docker.localhost", SwarmMode: true, } - actual := provider.getFrontendName(dockerData) + actual := provider.getFrontendName(dockerData, 0) if actual != e.expected { t.Errorf("expected %q, got %q", e.expected, actual) } @@ -660,13 +660,13 @@ func TestSwarmLoadDockerConfig(t *testing.T) { ), }, expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-test-docker-localhost": { + "frontend-Host-test-docker-localhost-0": { Backend: "backend-test", PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, Routes: map[string]types.Route{ - "route-frontend-Host-test-docker-localhost": { + "route-frontend-Host-test-docker-localhost-0": { Rule: "Host:test.docker.localhost", }, }, @@ -714,24 +714,24 @@ func TestSwarmLoadDockerConfig(t *testing.T) { ), }, expectedFrontends: map[string]*types.Frontend{ - "frontend-Host-test1-docker-localhost": { + "frontend-Host-test1-docker-localhost-0": { Backend: "backend-foobar", PassHostHeader: true, EntryPoints: []string{"http", "https"}, BasicAuth: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"}, Routes: map[string]types.Route{ - "route-frontend-Host-test1-docker-localhost": { + "route-frontend-Host-test1-docker-localhost-0": { Rule: "Host:test1.docker.localhost", }, }, }, - "frontend-Host-test2-docker-localhost": { + "frontend-Host-test2-docker-localhost-1": { Backend: "backend-foobar", PassHostHeader: true, EntryPoints: []string{}, BasicAuth: []string{}, Routes: map[string]types.Route{ - "route-frontend-Host-test2-docker-localhost": { + "route-frontend-Host-test2-docker-localhost-1": { Rule: "Host:test2.docker.localhost", }, }, From b27455a36fe1ac568b3f59be6c40e5eb29eb25b9 Mon Sep 17 00:00:00 2001 From: burningTyger Date: Mon, 30 Oct 2017 13:20:03 +0100 Subject: [PATCH 05/13] entrypoints -> entryPoints --- docs/configuration/entrypoints.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/configuration/entrypoints.md b/docs/configuration/entrypoints.md index 7bb55c8f3..6127077e7 100644 --- a/docs/configuration/entrypoints.md +++ b/docs/configuration/entrypoints.md @@ -118,10 +118,10 @@ Otherwise, the response from the auth server is returned. ```toml [entryPoints] - [entrypoints.http] + [entryPoints.http] # ... # To enable forward auth on an entrypoint - [entrypoints.http.auth.forward] + [entryPoints.http.auth.forward] address = "https://authserver.com/auth" # Trust existing X-Forwarded-* headers. @@ -136,7 +136,7 @@ Otherwise, the response from the auth server is returned. # # Optional # - [entrypoints.http.auth.forward.tls] + [entryPoints.http.auth.forward.tls] cert = "authserver.crt" key = "authserver.key" ``` From 5292b84f4fc5f964b24788016382e137e81a4961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20K=C3=B6nig?= Date: Mon, 30 Oct 2017 14:04:03 +0100 Subject: [PATCH 06/13] fixed dead link in kubernetes backend config docs --- docs/configuration/backends/kubernetes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/backends/kubernetes.md b/docs/configuration/backends/kubernetes.md index 84da91e95..2b95025e2 100644 --- a/docs/configuration/backends/kubernetes.md +++ b/docs/configuration/backends/kubernetes.md @@ -2,7 +2,7 @@ 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 From a0e1cf8376ca0d96e4a63d4893e274333ae31000 Mon Sep 17 00:00:00 2001 From: NicoMen Date: Mon, 30 Oct 2017 14:36:04 +0100 Subject: [PATCH 07/13] Fix IP address when Docker container network mode is container --- provider/docker/docker.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/provider/docker/docker.go b/provider/docker/docker.go index 4d4938790..1e6f24c60 100644 --- a/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -594,10 +594,25 @@ func (p *Provider) getIPAddress(container dockerData) string { // If net==host, quick n' dirty, we return 127.0.0.1 // This will work locally, but will fail with swarm. - if "host" == container.NetworkSettings.NetworkMode { + if container.NetworkSettings.NetworkMode.IsHost() { 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 { port := p.getPort(container) for netport, portBindings := range container.NetworkSettings.Ports { From dc66db4abefa183e7782b811828f2fa0d2b4521a Mon Sep 17 00:00:00 2001 From: NicoMen Date: Mon, 30 Oct 2017 15:10:05 +0100 Subject: [PATCH 08/13] Make the traefik.port label optional when using service labels in Docker containers. --- docs/configuration/backends/docker.md | 6 ++ docs/user-guide/docker-and-lets-encrypt.md | 77 +++++++++++++++------- integration/docker_test.go | 37 +++++++++++ provider/docker/docker.go | 49 +++++++++++++- provider/docker/docker_test.go | 50 ++++++++++++++ 5 files changed, 193 insertions(+), 26 deletions(-) diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md index 616dac299..b18afadd1 100644 --- a/docs/configuration/backends/docker.md +++ b/docs/configuration/backends/docker.md @@ -187,6 +187,12 @@ Services labels can be used for overriding default behaviour | `traefik..frontend.priority` | Overrides `traefik.frontend.priority`. | | `traefik..frontend.rule` | Overrides `traefik.frontend.rule`. | + +!!! note + if a label is defined both as a `container label` and a `service label` (for example `traefik..port=PORT` and `traefik.port=PORT` ), the `service label` is used to defined the `` 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 when running inside a container, Træfik will need network access through: diff --git a/docs/user-guide/docker-and-lets-encrypt.md b/docs/user-guide/docker-and-lets-encrypt.md index 1ee4f0e33..baec79b72 100644 --- a/docs/user-guide/docker-and-lets-encrypt.md +++ b/docs/user-guide/docker-and-lets-encrypt.md @@ -1,8 +1,8 @@ # 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. @@ -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. 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: @@ -27,7 +27,7 @@ On the Docker host, run the following command: 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 mkdir -p /opt/traefik @@ -41,7 +41,7 @@ touch /opt/traefik/acme.json && chmod 600 /opt/traefik/acme.json 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: @@ -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. -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). 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`. -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 debug = false @@ -109,17 +109,17 @@ OnHostRule = true 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 -- 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`. -- 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. 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 -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. @@ -148,6 +148,10 @@ services: - "traefik.frontend.rule=Host:app.my-awesome-app.org" - "traefik.enable=true" - "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: 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 -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. -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: @@ -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.enable=true" - "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**. -We also tell Traefik 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`. -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. +We also tell Træfik to use the `web` network to route HTTP traffic to this container. +With the `traefik.enable` label, we tell Træfik 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 - 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. -- 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. -- 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. -- Traefik 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. +- 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. +- Træfik has built-in support to automatically export [Prometheus](https://prometheus.io) metrics +- 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) ### 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. diff --git a/integration/docker_test.go b/integration/docker_test.go index 665d6a9f8..589405502 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -192,3 +192,40 @@ func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) { err = try.Request(req, 1500*time.Millisecond, try.StatusCodeIs(http.StatusNotFound)) 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") +} diff --git a/provider/docker/docker.go b/provider/docker/docker.go index 1e6f24c60..cc5598c37 100644 --- a/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -29,6 +29,7 @@ import ( "github.com/docker/docker/client" "github.com/docker/go-connections/nat" "github.com/docker/go-connections/sockets" + "github.com/pkg/errors" ) const ( @@ -517,9 +518,16 @@ func (p *Provider) getMaxConnExtractorFunc(container dockerData) string { } 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..port or " + portLabel + "s" + err = checkServiceLabelPort(container) + } else { + _, err = strconv.Atoi(container.Labels[types.LabelPort]) + } 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 } @@ -549,6 +557,43 @@ func (p *Provider) containerFilter(container dockerData) bool { return true } +// 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.+?)\.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 return provider.Normalize(p.getFrontendRule(container) + "-" + strconv.Itoa(idx)) diff --git a/provider/docker/docker_test.go b/provider/docker/docker_test.go index 8818f3235..514970a9b 100644 --- a/provider/docker/docker_test.go +++ b/provider/docker/docker_test.go @@ -1091,3 +1091,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) + } + }) + } +} From d3c7681bc5bdde69fa7e5bf65dd07783339d9ac2 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 30 Oct 2017 16:38:03 +0100 Subject: [PATCH 09/13] New PR template --- .github/PULL_REQUEST_TEMPLATE.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index da87008a9..e9c809a33 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -16,8 +16,21 @@ HOW TO WRITE A GOOD PULL REQUEST? --> -### Description +### What does this PR do? - \ No newline at end of file + + + +### Motivation + + + + +### More + +- [ ] Added/updated tests +- [ ] Added/updated documentation + +### Additional Notes + + From 02035d4942c9da9db393739879e66c28d872ce44 Mon Sep 17 00:00:00 2001 From: Alex Antonov Date: Wed, 1 Nov 2017 05:26:03 -0500 Subject: [PATCH 10/13] Missing Backend key in configuration when application has no tasks --- provider/marathon/marathon_test.go | 4 +++- templates/marathon.tmpl | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/provider/marathon/marathon_test.go b/provider/marathon/marathon_test.go index 4c58ab62e..f7ea244af 100644 --- a/provider/marathon/marathon_test.go +++ b/provider/marathon/marathon_test.go @@ -93,7 +93,9 @@ func TestMarathonLoadConfigNonAPIErrors(t *testing.T) { }, }, }, - expectedBackends: nil, + expectedBackends: map[string]*types.Backend{ + "backend-app": {}, + }, }, { desc: "load balancer / circuit breaker labels", diff --git a/templates/marathon.tmpl b/templates/marathon.tmpl index e03fd601c..4f342f02c 100644 --- a/templates/marathon.tmpl +++ b/templates/marathon.tmpl @@ -12,6 +12,7 @@ {{range $app := $apps}} {{range $serviceIndex, $serviceName := getServiceNames $app}} +[backends."backend{{getBackend $app $serviceName }}"] {{ if hasMaxConnLabels $app }} [backends."backend{{getBackend $app $serviceName }}".maxconn] amount = {{getMaxConnAmount $app }} From fc4d670c8827c9528b5bdd62cfa1098b4f8d84ba Mon Sep 17 00:00:00 2001 From: Jim Hribar Date: Thu, 2 Nov 2017 05:38:03 -0400 Subject: [PATCH 11/13] Minor grammar change --- docs/configuration/entrypoints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/entrypoints.md b/docs/configuration/entrypoints.md index 6127077e7..12e8e11b2 100644 --- a/docs/configuration/entrypoints.md +++ b/docs/configuration/entrypoints.md @@ -221,7 +221,7 @@ Only IPs in `trustedIPs` will lead to remote client address replacement: you sho ## 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 [entryPoints] From db9b18f12117b1dc50de4e7f32e8dc89f40496ce Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 2 Nov 2017 12:28:03 +0100 Subject: [PATCH 12/13] Prepare release v1.4.2 --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 222540a76..e39f80c0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # 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) [All Commits](https://github.com/containous/traefik/compare/v1.4.0...v1.4.1) From 0347537f430be426d69a9a18efb32899895a3ddc Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 2 Nov 2017 14:38:03 +0100 Subject: [PATCH 13/13] Freeze version of mkdocs-material. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0e78ab245..1fdf12311 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ mkdocs==0.16.3 pymdown-extensions>=1.4 mkdocs-bootswatch>=0.4.0 -mkdocs-material>=1.8.1 +mkdocs-material==1.12.2