Merge pull request #481 from cocap10/global-config-kv
Use KvStores as global config sources
This commit is contained in:
commit
686c23d25b
28 changed files with 1150 additions and 214 deletions
|
@ -1,11 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/containous/traefik/acme"
|
"github.com/containous/traefik/acme"
|
||||||
"github.com/containous/traefik/provider"
|
"github.com/containous/traefik/provider"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -13,8 +15,8 @@ import (
|
||||||
|
|
||||||
// TraefikConfiguration holds GlobalConfiguration and other stuff
|
// TraefikConfiguration holds GlobalConfiguration and other stuff
|
||||||
type TraefikConfiguration struct {
|
type TraefikConfiguration struct {
|
||||||
GlobalConfiguration
|
GlobalConfiguration `mapstructure:",squash"`
|
||||||
ConfigFile string `short:"c" description:"Configuration file to use (TOML)."`
|
ConfigFile string `short:"c" description:"Configuration file to use (TOML)."`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GlobalConfiguration holds global configuration (with providers, etc.).
|
// GlobalConfiguration holds global configuration (with providers, etc.).
|
||||||
|
@ -177,8 +179,46 @@ type TLS struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Certificates defines traefik certificates type
|
// Certificates defines traefik certificates type
|
||||||
|
// Certs and Keys could be either a file path, or the file content itself
|
||||||
type Certificates []Certificate
|
type Certificates []Certificate
|
||||||
|
|
||||||
|
//CreateTLSConfig creates a TLS config from Certificate structures
|
||||||
|
func (certs *Certificates) CreateTLSConfig() (*tls.Config, error) {
|
||||||
|
config := &tls.Config{}
|
||||||
|
config.Certificates = []tls.Certificate{}
|
||||||
|
certsSlice := []Certificate(*certs)
|
||||||
|
for _, v := range certsSlice {
|
||||||
|
isAPath := false
|
||||||
|
_, errCert := os.Stat(v.CertFile)
|
||||||
|
_, errKey := os.Stat(v.KeyFile)
|
||||||
|
if errCert == nil {
|
||||||
|
if errKey == nil {
|
||||||
|
isAPath = true
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("Bad TLS Certificate KeyFile format. Expected a path.")
|
||||||
|
}
|
||||||
|
} else if errKey == nil {
|
||||||
|
return nil, fmt.Errorf("Bad TLS Certificate KeyFile format. Expected a path.")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert := tls.Certificate{}
|
||||||
|
var err error
|
||||||
|
if isAPath {
|
||||||
|
cert, err = tls.LoadX509KeyPair(v.CertFile, v.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cert, err = tls.X509KeyPair([]byte(v.CertFile), []byte(v.KeyFile))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.Certificates = append(config.Certificates, cert)
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||||
// The String method's output will be used in diagnostics.
|
// The String method's output will be used in diagnostics.
|
||||||
func (certs *Certificates) String() string {
|
func (certs *Certificates) String() string {
|
||||||
|
@ -209,6 +249,7 @@ func (certs *Certificates) Type() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Certificate holds a SSL cert/key pair
|
// Certificate holds a SSL cert/key pair
|
||||||
|
// Certs and Key could be either a file path, or the file content itself
|
||||||
type Certificate struct {
|
type Certificate struct {
|
||||||
CertFile string
|
CertFile string
|
||||||
KeyFile string
|
KeyFile string
|
||||||
|
|
|
@ -253,9 +253,30 @@ Here is an example of backends and servers definition:
|
||||||
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2` using `drr` load-balancing strategy.
|
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2` using `drr` load-balancing strategy.
|
||||||
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
|
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
|
||||||
|
|
||||||
# Launch
|
# Configuration
|
||||||
|
|
||||||
|
Træfɪk's configuration has two parts:
|
||||||
|
|
||||||
|
- The [static Træfɪk configuration](/basics#static-trfk-configuration) which is loaded only at the begining.
|
||||||
|
- The [dynamic Træfɪk configuration](/basics#dynamic-trfk-configuration) which can be hot-reloaded (no need to restart the process).
|
||||||
|
|
||||||
|
|
||||||
|
## Static Træfɪk configuration
|
||||||
|
|
||||||
|
The static configuration is the global configuration which setting up connections to configuration backends and entrypoints.
|
||||||
|
|
||||||
|
Træfɪk can be configured using many configuration sources with the following precedence order.
|
||||||
|
Each item takes precedence over the item below it:
|
||||||
|
|
||||||
|
- [Key-value Store](/basics/#key-value-stores)
|
||||||
|
- [Arguments](/basics/#arguments)
|
||||||
|
- [Configuration file](/basics/#configuration-file)
|
||||||
|
- Default
|
||||||
|
|
||||||
|
It means that arguments overrides configuration file, and Key-value Store overrides arguments.
|
||||||
|
|
||||||
|
### Configuration file
|
||||||
|
|
||||||
Træfɪk can be configured using a TOML file configuration, arguments, or both.
|
|
||||||
By default, Træfɪk will try to find a `traefik.toml` in the following places:
|
By default, Træfɪk will try to find a `traefik.toml` in the following places:
|
||||||
|
|
||||||
- `/etc/traefik/`
|
- `/etc/traefik/`
|
||||||
|
@ -268,15 +289,40 @@ You can override this by setting a `configFile` argument:
|
||||||
$ traefik --configFile=foo/bar/myconfigfile.toml
|
$ traefik --configFile=foo/bar/myconfigfile.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
Træfɪk uses the following precedence order. Each item takes precedence over the item below it:
|
Please refer to the [global configuration](/toml/#global-configuration) section to get documentation on it.
|
||||||
|
|
||||||
- arguments
|
### Arguments
|
||||||
- configuration file
|
|
||||||
- default
|
|
||||||
|
|
||||||
It means that arguments overrides configuration file.
|
|
||||||
Each argument is described in the help section:
|
Each argument is described in the help section:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ traefik --help
|
$ traefik --help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that all default values will be displayed as well.
|
||||||
|
|
||||||
|
### Key-value stores
|
||||||
|
|
||||||
|
Træfɪk supports several Key-value stores:
|
||||||
|
|
||||||
|
- [Consul](https://consul.io)
|
||||||
|
- [etcd](https://coreos.com/etcd/)
|
||||||
|
- [ZooKeeper](https://zookeeper.apache.org/)
|
||||||
|
- [boltdb](https://github.com/boltdb/bolt)
|
||||||
|
|
||||||
|
Please refer to the [User Guide Key-value store configuration](/user-guide/kv-config/) section to get documentation on it.
|
||||||
|
|
||||||
|
## Dynamic Træfɪk configuration
|
||||||
|
|
||||||
|
The dynamic configuration concerns :
|
||||||
|
|
||||||
|
- [Frontends](/basics/#frontends)
|
||||||
|
- [Backends](/basics/#backends)
|
||||||
|
- [Servers](/basics/#servers)
|
||||||
|
|
||||||
|
Træfɪk can hot-reload those rules which could be provided by [multiple configuration backends](/toml/#configuration-backends).
|
||||||
|
|
||||||
|
We only need to enable `watch` option to make Træfɪk watch configuration backend changes and generate its configuration automatically.
|
||||||
|
Routes to services will be created and updated instantly at any changes.
|
||||||
|
|
||||||
|
Please refer to the [configuration backends](/toml/#configuration-backends) section to get documentation on it.
|
||||||
|
|
89
docs/toml.md
89
docs/toml.md
|
@ -756,7 +756,7 @@ prefix = "traefik"
|
||||||
# insecureskipverify = true
|
# insecureskipverify = true
|
||||||
```
|
```
|
||||||
|
|
||||||
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
|
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure.
|
||||||
|
|
||||||
## Consul catalog backend
|
## Consul catalog backend
|
||||||
|
|
||||||
|
@ -857,7 +857,7 @@ prefix = "/traefik"
|
||||||
# insecureskipverify = true
|
# insecureskipverify = true
|
||||||
```
|
```
|
||||||
|
|
||||||
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
|
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure.
|
||||||
|
|
||||||
|
|
||||||
## Zookeeper backend
|
## Zookeeper backend
|
||||||
|
@ -900,7 +900,7 @@ prefix = "/traefik"
|
||||||
# filename = "zookeeper.tmpl"
|
# filename = "zookeeper.tmpl"
|
||||||
```
|
```
|
||||||
|
|
||||||
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
|
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure.
|
||||||
|
|
||||||
## BoltDB backend
|
## BoltDB backend
|
||||||
|
|
||||||
|
@ -942,85 +942,4 @@ prefix = "/traefik"
|
||||||
# filename = "boltdb.tmpl"
|
# filename = "boltdb.tmpl"
|
||||||
```
|
```
|
||||||
|
|
||||||
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
|
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure.
|
||||||
|
|
||||||
## Key-value storage structure
|
|
||||||
|
|
||||||
The Keys-Values structure should look (using `prefix = "/traefik"`):
|
|
||||||
|
|
||||||
- backend 1
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|--------------------------------------------------------|-----------------------------|
|
|
||||||
| `/traefik/backends/backend1/circuitbreaker/expression` | `NetworkErrorRatio() > 0.5` |
|
|
||||||
| `/traefik/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
|
||||||
| `/traefik/backends/backend1/servers/server1/weight` | `10` |
|
|
||||||
| `/traefik/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
|
|
||||||
| `/traefik/backends/backend1/servers/server2/weight` | `1` |
|
|
||||||
|
|
||||||
- backend 2
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|-----------------------------------------------------|------------------------|
|
|
||||||
| `/traefik/backends/backend2/maxconn/amount` | `10` |
|
|
||||||
| `/traefik/backends/backend2/maxconn/extractorfunc` | `request.host` |
|
|
||||||
| `/traefik/backends/backend2/loadbalancer/method` | `drr` |
|
|
||||||
| `/traefik/backends/backend2/servers/server1/url` | `http://172.17.0.4:80` |
|
|
||||||
| `/traefik/backends/backend2/servers/server1/weight` | `1` |
|
|
||||||
| `/traefik/backends/backend2/servers/server2/url` | `http://172.17.0.5:80` |
|
|
||||||
| `/traefik/backends/backend2/servers/server2/weight` | `2` |
|
|
||||||
|
|
||||||
- frontend 1
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|---------------------------------------------------|-----------------------|
|
|
||||||
| `/traefik/frontends/frontend1/backend` | `backend2` |
|
|
||||||
| `/traefik/frontends/frontend1/routes/test_1/rule` | `Host:test.localhost` |
|
|
||||||
|
|
||||||
- frontend 2
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|----------------------------------------------------|--------------------|
|
|
||||||
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
|
||||||
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
|
||||||
| `/traefik/frontends/frontend2/priority` | `10` |
|
|
||||||
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
|
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
|
|
||||||
|
|
||||||
## Atomic configuration changes
|
|
||||||
|
|
||||||
The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. As a result, it may be possible for Træfɪk to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. To solve this problem, Træfɪk supports a special key called `/traefik/alias`. If set, Træfɪk use the value as an alternative key prefix.
|
|
||||||
|
|
||||||
Given the key structure below, Træfɪk will use the `http://172.17.0.2:80` as its only backend (frontend keys have been omitted for brevity).
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|-------------------------------------------------------------------------|-----------------------------|
|
|
||||||
| `/traefik/alias` | `/traefik_configurations/1` |
|
|
||||||
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
|
||||||
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
|
|
||||||
|
|
||||||
When an atomic configuration change is required, you may write a new configuration at an alternative prefix. Here, although the `/traefik_configurations/2/...` keys have been set, the old configuration is still active because the `/traefik/alias` key still points to `/traefik_configurations/1`:
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|-------------------------------------------------------------------------|-----------------------------|
|
|
||||||
| `/traefik/alias` | `/traefik_configurations/1` |
|
|
||||||
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
|
||||||
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
|
|
||||||
|
|
||||||
Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` configuration becomes active atomically. Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://172.17.0.4:80` hosts while no traffic is sent to the `172.17.0.2:80` host:
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|-------------------------------------------------------------------------|-----------------------------|
|
|
||||||
| `/traefik/alias` | `/traefik_configurations/2` |
|
|
||||||
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
|
||||||
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.3:80` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
|
|
||||||
|
|
||||||
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored.
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ defaultEntryPoints = ["http", "https"]
|
||||||
CertFile = "integration/fixtures/https/snitest.org.cert"
|
CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||||
KeyFile = "integration/fixtures/https/snitest.org.key"
|
KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||||
```
|
```
|
||||||
|
Note that we can either give path to certificate file or directly the file content itself ([like in this TOML example](/user-guide/kv-config/#upload-the-configuration-in-the-key-value-store)).
|
||||||
|
|
||||||
## HTTP redirect on HTTPS
|
## HTTP redirect on HTTPS
|
||||||
|
|
||||||
|
|
298
docs/user-guide/kv-config.md
Normal file
298
docs/user-guide/kv-config.md
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
|
||||||
|
# Key-value store configuration
|
||||||
|
|
||||||
|
Both [static global configuration](/user-guide/kv-config/#static-configuration-in-key-value-store) and [dynamic](/user-guide/kv-config/#dynamic-configuration-in-key-value-store) configuration can be sorted in a Key-value store.
|
||||||
|
|
||||||
|
This section explains how to launch Træfɪk using a configuration loaded from a Key-value store.
|
||||||
|
|
||||||
|
Træfɪk supports several Key-value stores:
|
||||||
|
|
||||||
|
- [Consul](https://consul.io)
|
||||||
|
- [etcd](https://coreos.com/etcd/)
|
||||||
|
- [ZooKeeper](https://zookeeper.apache.org/)
|
||||||
|
- [boltdb](https://github.com/boltdb/bolt)
|
||||||
|
|
||||||
|
# Static configuration in Key-value store
|
||||||
|
|
||||||
|
We will see the steps to set it up with an easy example.
|
||||||
|
Note that we could do the same with any other Key-value Store.
|
||||||
|
|
||||||
|
## docker-compose file for Consul
|
||||||
|
|
||||||
|
The Træfɪk global configuration will be getted from a [Consul](https://consul.io) store.
|
||||||
|
|
||||||
|
First we have to launch Consul in a container.
|
||||||
|
The [docker-compose file](https://docs.docker.com/compose/compose-file/) allows us to launch Consul and four instances of the trivial app [emilevauge/whoamI](https://github.com/emilevauge/whoamI) :
|
||||||
|
|
||||||
|
```yml
|
||||||
|
consul:
|
||||||
|
image: progrium/consul
|
||||||
|
command: -server -bootstrap -log-level debug -ui-dir /ui
|
||||||
|
ports:
|
||||||
|
- "8400:8400"
|
||||||
|
- "8500:8500"
|
||||||
|
- "8600:53/udp"
|
||||||
|
expose:
|
||||||
|
- "8300"
|
||||||
|
- "8301"
|
||||||
|
- "8301/udp"
|
||||||
|
- "8302"
|
||||||
|
- "8302/udp"
|
||||||
|
|
||||||
|
whoami1:
|
||||||
|
image: emilevauge/whoami
|
||||||
|
|
||||||
|
whoami2:
|
||||||
|
image: emilevauge/whoami
|
||||||
|
|
||||||
|
whoami3:
|
||||||
|
image: emilevauge/whoami
|
||||||
|
|
||||||
|
whoami4:
|
||||||
|
image: emilevauge/whoami
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upload the configuration in the Key-value store
|
||||||
|
|
||||||
|
We should now fill the store with the Træfɪk global configuration, as we do with a [TOML file configuration](/toml).
|
||||||
|
To do that, we can send the Key-value pairs via [curl commands](https://www.consul.io/intro/getting-started/kv.html) or via the [Web UI](https://www.consul.io/intro/getting-started/ui.html)
|
||||||
|
|
||||||
|
Here the toml configuration we would like to store in the Key-value Store :
|
||||||
|
|
||||||
|
```toml
|
||||||
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
|
defaultEntryPoints = ["http", "https"]
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||||
|
KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = """-----BEGIN CERTIFICATE-----
|
||||||
|
<cert file content>
|
||||||
|
-----END CERTIFICATE-----"""
|
||||||
|
KeyFile = """-----BEGIN CERTIFICATE-----
|
||||||
|
<key file content>
|
||||||
|
-----END CERTIFICATE-----"""
|
||||||
|
|
||||||
|
|
||||||
|
[consul]
|
||||||
|
endpoint = "127.0.0.1:8500"
|
||||||
|
watch = true
|
||||||
|
prefix = "traefik"
|
||||||
|
|
||||||
|
[web]
|
||||||
|
address = ":8081"
|
||||||
|
```
|
||||||
|
|
||||||
|
And there, the same global configuration in the Key-value Store (using `prefix = "traefik"`):
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|-----------------------------------------------------------|---------------------------------------------------------------|
|
||||||
|
| `/traefik/loglevel` | `DEBUG` |
|
||||||
|
| `/traefik/defaultentrypoints/0` | `http` |
|
||||||
|
| `/traefik/defaultentrypoints/1` | `https` |
|
||||||
|
| `/traefik/entrypoints/http/address` | `:80` |
|
||||||
|
| `/traefik/entrypoints/https/address` | `:443` |
|
||||||
|
| `/traefik/entrypoints/https/tls/certificates/0/certfile` | `integration/fixtures/https/snitest.com.cert` |
|
||||||
|
| `/traefik/entrypoints/https/tls/certificates/0/keyfile` | `integration/fixtures/https/snitest.com.key` |
|
||||||
|
| `/traefik/entrypoints/https/tls/certificates/1/certfile` | `--BEGIN CERTIFICATE--<cert file content>--END CERTIFICATE--` |
|
||||||
|
| `/traefik/entrypoints/https/tls/certificates/1/keyfile` | `--BEGIN CERTIFICATE--<key file content>--END CERTIFICATE--` |
|
||||||
|
| `/traefik/consul/endpoint` | `127.0.0.1:8500` |
|
||||||
|
| `/traefik/consul/watch` | `true` |
|
||||||
|
| `/traefik/consul/prefix` | `traefik` |
|
||||||
|
| `/traefik/web/address` | `:8081` |
|
||||||
|
|
||||||
|
Remember to specify the indexes (`0`,`1`, `2`, ... ) under prefixes `/traefik/defaultentrypoints/` and `/traefik/entrypoints/https/tls/certificates/` in order to match the global configuration structure.
|
||||||
|
|
||||||
|
Be careful to give the correct IP address and port on the key `/traefik/consul/endpoint`.
|
||||||
|
|
||||||
|
Note that we can either give path to certificate file or directly the file content itself.
|
||||||
|
|
||||||
|
## Launch Træfɪk
|
||||||
|
|
||||||
|
We will now launch Træfɪk in a container.
|
||||||
|
We use CLI flags to setup the connection between Træfɪk and Consul.
|
||||||
|
All the rest of the global configuration is stored in Consul.
|
||||||
|
|
||||||
|
Here the [docker-compose file](https://docs.docker.com/compose/compose-file/) :
|
||||||
|
|
||||||
|
```yml
|
||||||
|
traefik:
|
||||||
|
image: traefik
|
||||||
|
command: --consul --consul.endpoint=127.0.0.1:8500
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
NB : Be careful to give the correct IP address and port in the flag `--consul.endpoint`.
|
||||||
|
|
||||||
|
## TLS support
|
||||||
|
|
||||||
|
So far, only [Consul](https://consul.io) and [etcd](https://coreos.com/etcd/) support TLS connections.
|
||||||
|
To set it up, we should enable [consul security](https://www.consul.io/docs/internals/security.html) (or [etcd security](https://coreos.com/etcd/docs/latest/security.html)).
|
||||||
|
|
||||||
|
Then, we have to provide CA, Cert and Key to Træfɪk using `consul` flags :
|
||||||
|
|
||||||
|
- `--consul.tls`
|
||||||
|
- `--consul.tls.ca=path/to/the/file`
|
||||||
|
- `--consul.tls.cert=path/to/the/file`
|
||||||
|
- `--consul.tls.key=path/to/the/file`
|
||||||
|
|
||||||
|
Or etcd flags :
|
||||||
|
|
||||||
|
- `--etcd.tls`
|
||||||
|
- `--etcd.tls.ca=path/to/the/file`
|
||||||
|
- `--etcd.tls.cert=path/to/the/file`
|
||||||
|
- `--etcd.tls.key=path/to/the/file`
|
||||||
|
|
||||||
|
Note that we can either give directly directly the file content itself (instead of the path to certificate) in a TOML file configuration.
|
||||||
|
|
||||||
|
Remember the command `traefik --help` to display the updated list of flags.
|
||||||
|
|
||||||
|
# Dynamic configuration in Key-value store
|
||||||
|
Following our example, we will provide backends/frontends rules to Træfɪk.
|
||||||
|
|
||||||
|
Note that this section is independent of the way Træfɪk got its static configuration.
|
||||||
|
It means that the static configuration can either come from the same Key-value store or from any other sources.
|
||||||
|
|
||||||
|
## Key-value storage structure
|
||||||
|
Here the toml configuration we would like to store in the store :
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[file]
|
||||||
|
|
||||||
|
# rules
|
||||||
|
[backends]
|
||||||
|
[backends.backend1]
|
||||||
|
[backends.backend1.circuitbreaker]
|
||||||
|
expression = "NetworkErrorRatio() > 0.5"
|
||||||
|
[backends.backend1.servers.server1]
|
||||||
|
url = "http://172.17.0.2:80"
|
||||||
|
weight = 10
|
||||||
|
[backends.backend1.servers.server2]
|
||||||
|
url = "http://172.17.0.3:80"
|
||||||
|
weight = 1
|
||||||
|
[backends.backend2]
|
||||||
|
[backends.backend1.maxconn]
|
||||||
|
amount = 10
|
||||||
|
extractorfunc = "request.host"
|
||||||
|
[backends.backend2.LoadBalancer]
|
||||||
|
method = "drr"
|
||||||
|
[backends.backend2.servers.server1]
|
||||||
|
url = "http://172.17.0.4:80"
|
||||||
|
weight = 1
|
||||||
|
[backends.backend2.servers.server2]
|
||||||
|
url = "http://172.17.0.5:80"
|
||||||
|
weight = 2
|
||||||
|
|
||||||
|
[frontends]
|
||||||
|
[frontends.frontend1]
|
||||||
|
backend = "backend2"
|
||||||
|
[frontends.frontend1.routes.test_1]
|
||||||
|
rule = "Host:test.localhost"
|
||||||
|
[frontends.frontend2]
|
||||||
|
backend = "backend1"
|
||||||
|
passHostHeader = true
|
||||||
|
priority = 10
|
||||||
|
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||||
|
[frontends.frontend2.routes.test_1]
|
||||||
|
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||||
|
[frontends.frontend3]
|
||||||
|
entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
||||||
|
backend = "backend2"
|
||||||
|
rule = "Path:/test"
|
||||||
|
```
|
||||||
|
|
||||||
|
And there, the same dynamic configuration in a KV Store (using `prefix = "traefik"`):
|
||||||
|
|
||||||
|
- backend 1
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|--------------------------------------------------------|-----------------------------|
|
||||||
|
| `/traefik/backends/backend1/circuitbreaker/expression` | `NetworkErrorRatio() > 0.5` |
|
||||||
|
| `/traefik/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
||||||
|
| `/traefik/backends/backend1/servers/server1/weight` | `10` |
|
||||||
|
| `/traefik/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
|
||||||
|
| `/traefik/backends/backend1/servers/server2/weight` | `1` |
|
||||||
|
|
||||||
|
- backend 2
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|-----------------------------------------------------|------------------------|
|
||||||
|
| `/traefik/backends/backend2/maxconn/amount` | `10` |
|
||||||
|
| `/traefik/backends/backend2/maxconn/extractorfunc` | `request.host` |
|
||||||
|
| `/traefik/backends/backend2/loadbalancer/method` | `drr` |
|
||||||
|
| `/traefik/backends/backend2/servers/server1/url` | `http://172.17.0.4:80` |
|
||||||
|
| `/traefik/backends/backend2/servers/server1/weight` | `1` |
|
||||||
|
| `/traefik/backends/backend2/servers/server2/url` | `http://172.17.0.5:80` |
|
||||||
|
| `/traefik/backends/backend2/servers/server2/weight` | `2` |
|
||||||
|
|
||||||
|
- frontend 1
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|---------------------------------------------------|-----------------------|
|
||||||
|
| `/traefik/frontends/frontend1/backend` | `backend2` |
|
||||||
|
| `/traefik/frontends/frontend1/routes/test_1/rule` | `Host:test.localhost` |
|
||||||
|
|
||||||
|
- frontend 2
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|----------------------------------------------------|--------------------|
|
||||||
|
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||||
|
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||||
|
| `/traefik/frontends/frontend2/priority` | `10` |
|
||||||
|
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
|
||||||
|
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
|
||||||
|
|
||||||
|
## Atomic configuration changes
|
||||||
|
|
||||||
|
Træfɪk can watch the backends/frontends configuration changes and generate its configuration automatically.
|
||||||
|
|
||||||
|
Note that only backends/frontends rules are dynamic, the rest of the Træfɪk configuration stay static.
|
||||||
|
|
||||||
|
The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. As a result, it may be possible for Træfɪk to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. To solve this problem, Træfɪk supports a special key called `/traefik/alias`. If set, Træfɪk use the value as an alternative key prefix.
|
||||||
|
|
||||||
|
Given the key structure below, Træfɪk will use the `http://172.17.0.2:80` as its only backend (frontend keys have been omitted for brevity).
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|-------------------------------------------------------------------------|-----------------------------|
|
||||||
|
| `/traefik/alias` | `/traefik_configurations/1` |
|
||||||
|
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
||||||
|
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
|
||||||
|
|
||||||
|
When an atomic configuration change is required, you may write a new configuration at an alternative prefix. Here, although the `/traefik_configurations/2/...` keys have been set, the old configuration is still active because the `/traefik/alias` key still points to `/traefik_configurations/1`:
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|-------------------------------------------------------------------------|-----------------------------|
|
||||||
|
| `/traefik/alias` | `/traefik_configurations/1` |
|
||||||
|
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
||||||
|
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
|
||||||
|
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
||||||
|
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
|
||||||
|
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
|
||||||
|
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
|
||||||
|
|
||||||
|
Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` configuration becomes active atomically. Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://172.17.0.4:80` hosts while no traffic is sent to the `172.17.0.2:80` host:
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|-------------------------------------------------------------------------|-----------------------------|
|
||||||
|
| `/traefik/alias` | `/traefik_configurations/2` |
|
||||||
|
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
||||||
|
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
|
||||||
|
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.3:80` |
|
||||||
|
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
|
||||||
|
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` |
|
||||||
|
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
|
||||||
|
|
||||||
|
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik/alias`.
|
||||||
|
Further, if the `/traefik/alias` key is set, all other configuration with `/traefik/backends` or `/traefik/frontends` prefix are ignored.
|
16
glide.lock
generated
16
glide.lock
generated
|
@ -1,5 +1,5 @@
|
||||||
hash: 22c20a7d7419e9624267d7f0041cd8ad87afc876d2738fa559527c74f9917c3a
|
hash: 70ad4e576bc1fa845512cce6b4ade5c422ba4fb5bb0472b37e1d3a93f13809cd
|
||||||
updated: 2016-07-05T14:48:30.023831407+02:00
|
updated: 2016-07-07T17:33:16.358775373+02:00
|
||||||
imports:
|
imports:
|
||||||
- name: github.com/boltdb/bolt
|
- name: github.com/boltdb/bolt
|
||||||
version: 3f7947a25d970e1e5f512276c14d5dcf731ccd5e
|
version: 3f7947a25d970e1e5f512276c14d5dcf731ccd5e
|
||||||
|
@ -22,13 +22,13 @@ imports:
|
||||||
- name: github.com/containous/mux
|
- name: github.com/containous/mux
|
||||||
version: a819b77bba13f0c0cbe36e437bc2e948411b3996
|
version: a819b77bba13f0c0cbe36e437bc2e948411b3996
|
||||||
- name: github.com/containous/staert
|
- name: github.com/containous/staert
|
||||||
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
|
version: 436a2f21e5e28c7c761130720befe05381db0765
|
||||||
- name: github.com/coreos/etcd
|
- name: github.com/coreos/etcd
|
||||||
version: c400d05d0aa73e21e431c16145e558d624098018
|
version: c400d05d0aa73e21e431c16145e558d624098018
|
||||||
subpackages:
|
subpackages:
|
||||||
|
- client
|
||||||
- Godeps/_workspace/src/github.com/ugorji/go/codec
|
- Godeps/_workspace/src/github.com/ugorji/go/codec
|
||||||
- Godeps/_workspace/src/golang.org/x/net/context
|
- Godeps/_workspace/src/golang.org/x/net/context
|
||||||
- client
|
|
||||||
- pkg/pathutil
|
- pkg/pathutil
|
||||||
- pkg/types
|
- pkg/types
|
||||||
- name: github.com/davecgh/go-spew
|
- name: github.com/davecgh/go-spew
|
||||||
|
@ -100,7 +100,7 @@ imports:
|
||||||
- name: github.com/gorilla/context
|
- name: github.com/gorilla/context
|
||||||
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
|
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
|
||||||
- name: github.com/hashicorp/consul
|
- name: github.com/hashicorp/consul
|
||||||
version: 6e061b2d580d80347b7c5c4dfc8730de7403a145
|
version: 64e3033d3c7f80af3f925f4665a9bc1af75d6153
|
||||||
subpackages:
|
subpackages:
|
||||||
- api
|
- api
|
||||||
- name: github.com/hashicorp/go-cleanhttp
|
- name: github.com/hashicorp/go-cleanhttp
|
||||||
|
@ -135,12 +135,14 @@ imports:
|
||||||
version: ce2922f643c8fd76b46cadc7f404a06282678b34
|
version: ce2922f643c8fd76b46cadc7f404a06282678b34
|
||||||
- name: github.com/miekg/dns
|
- name: github.com/miekg/dns
|
||||||
version: 5d001d020961ae1c184f9f8152fdc73810481677
|
version: 5d001d020961ae1c184f9f8152fdc73810481677
|
||||||
|
- name: github.com/mitchellh/mapstructure
|
||||||
|
version: d2dd0262208475919e1a362f675cfc0e7c10e905
|
||||||
- name: github.com/moul/http2curl
|
- name: github.com/moul/http2curl
|
||||||
version: b1479103caacaa39319f75e7f57fc545287fca0d
|
version: b1479103caacaa39319f75e7f57fc545287fca0d
|
||||||
- name: github.com/ogier/pflag
|
- name: github.com/ogier/pflag
|
||||||
version: 45c278ab3607870051a2ea9040bb85fcb8557481
|
version: 45c278ab3607870051a2ea9040bb85fcb8557481
|
||||||
- name: github.com/opencontainers/runc
|
- name: github.com/opencontainers/runc
|
||||||
version: 7221e387826c9918fa9fd6e7975baf4d30c8fa54
|
version: 9d7831e41d3ef428b67685eeb27f2b4a22a92391
|
||||||
subpackages:
|
subpackages:
|
||||||
- libcontainer/user
|
- libcontainer/user
|
||||||
- name: github.com/parnurzeal/gorequest
|
- name: github.com/parnurzeal/gorequest
|
||||||
|
@ -217,7 +219,7 @@ imports:
|
||||||
subpackages:
|
subpackages:
|
||||||
- acme
|
- acme
|
||||||
- name: golang.org/x/crypto
|
- name: golang.org/x/crypto
|
||||||
version: 0c565bf13221fb55497d7ae2bb95694db1fd1bff
|
version: d81fdb778bf2c40a91b24519d60cdc5767318829
|
||||||
subpackages:
|
subpackages:
|
||||||
- ocsp
|
- ocsp
|
||||||
- name: golang.org/x/net
|
- name: golang.org/x/net
|
||||||
|
|
|
@ -21,7 +21,7 @@ import:
|
||||||
- stream
|
- stream
|
||||||
- utils
|
- utils
|
||||||
- package: github.com/containous/staert
|
- package: github.com/containous/staert
|
||||||
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
|
version: 436a2f21e5e28c7c761130720befe05381db0765
|
||||||
- package: github.com/docker/engine-api
|
- package: github.com/docker/engine-api
|
||||||
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
|
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/containous/traefik/integration/utils"
|
"github.com/containous/traefik/integration/utils"
|
||||||
|
"github.com/containous/traefik/provider"
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -24,7 +25,7 @@ type ConsulSuite struct {
|
||||||
kv store.Store
|
kv store.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConsulSuite) SetUpSuite(c *check.C) {
|
func (s *ConsulSuite) setupConsul(c *check.C) {
|
||||||
s.createComposeProject(c, "consul")
|
s.createComposeProject(c, "consul")
|
||||||
s.composeProject.Start(c)
|
s.composeProject.Start(c)
|
||||||
|
|
||||||
|
@ -52,7 +53,56 @@ func (s *ConsulSuite) SetUpSuite(c *check.C) {
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ConsulSuite) setupConsulTLS(c *check.C) {
|
||||||
|
s.createComposeProject(c, "consul_tls")
|
||||||
|
s.composeProject.Start(c)
|
||||||
|
|
||||||
|
consul.Register()
|
||||||
|
clientTLS := &provider.ClientTLS{
|
||||||
|
CA: "resources/tls/ca.cert",
|
||||||
|
Cert: "resources/tls/consul.cert",
|
||||||
|
Key: "resources/tls/consul.key",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
TLSConfig, err := clientTLS.CreateTLSConfig()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
kv, err := libkv.NewStore(
|
||||||
|
store.CONSUL,
|
||||||
|
[]string{s.composeProject.Container(c, "consul").NetworkSettings.IPAddress + ":8585"},
|
||||||
|
&store.Config{
|
||||||
|
ConnectionTimeout: 10 * time.Second,
|
||||||
|
TLS: TLSConfig,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal("Cannot create store consul")
|
||||||
|
}
|
||||||
|
s.kv = kv
|
||||||
|
|
||||||
|
// wait for consul
|
||||||
|
err = utils.Try(60*time.Second, func() error {
|
||||||
|
_, err := kv.Exists("test")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConsulSuite) TearDownTest(c *check.C) {
|
||||||
|
// shutdown and delete compose project
|
||||||
|
if s.composeProject != nil {
|
||||||
|
s.composeProject.Stop(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConsulSuite) TearDownSuite(c *check.C) {}
|
||||||
|
|
||||||
func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
||||||
|
s.setupConsul(c)
|
||||||
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
||||||
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost})
|
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost})
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
|
@ -70,6 +120,7 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConsulSuite) TestNominalConfiguration(c *check.C) {
|
func (s *ConsulSuite) TestNominalConfiguration(c *check.C) {
|
||||||
|
s.setupConsul(c)
|
||||||
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
||||||
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost})
|
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost})
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
|
@ -190,3 +241,154 @@ func (s *ConsulSuite) TestNominalConfiguration(c *check.C) {
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ConsulSuite) TestGlobalConfiguration(c *check.C) {
|
||||||
|
s.setupConsul(c)
|
||||||
|
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
||||||
|
err := s.kv.Put("traefik/entrypoints/http/address", []byte(":8001"), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// wait for consul
|
||||||
|
err = utils.Try(60*time.Second, func() error {
|
||||||
|
_, err := s.kv.Exists("traefik/entrypoints/http/address")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// start traefik
|
||||||
|
cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml", "--consul", "--consul.endpoint="+consulHost+":8500")
|
||||||
|
// cmd.Stdout = os.Stdout
|
||||||
|
// cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
whoami1 := s.composeProject.Container(c, "whoami1")
|
||||||
|
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||||
|
whoami3 := s.composeProject.Container(c, "whoami3")
|
||||||
|
whoami4 := s.composeProject.Container(c, "whoami4")
|
||||||
|
|
||||||
|
backend1 := map[string]string{
|
||||||
|
"traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5",
|
||||||
|
"traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80",
|
||||||
|
"traefik/backends/backend1/servers/server1/weight": "10",
|
||||||
|
"traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80",
|
||||||
|
"traefik/backends/backend1/servers/server2/weight": "1",
|
||||||
|
}
|
||||||
|
backend2 := map[string]string{
|
||||||
|
"traefik/backends/backend2/loadbalancer/method": "drr",
|
||||||
|
"traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80",
|
||||||
|
"traefik/backends/backend2/servers/server1/weight": "1",
|
||||||
|
"traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80",
|
||||||
|
"traefik/backends/backend2/servers/server2/weight": "2",
|
||||||
|
}
|
||||||
|
frontend1 := map[string]string{
|
||||||
|
"traefik/frontends/frontend1/backend": "backend2",
|
||||||
|
"traefik/frontends/frontend1/entrypoints": "http",
|
||||||
|
"traefik/frontends/frontend1/priority": "1",
|
||||||
|
"traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost",
|
||||||
|
}
|
||||||
|
frontend2 := map[string]string{
|
||||||
|
"traefik/frontends/frontend2/backend": "backend1",
|
||||||
|
"traefik/frontends/frontend2/entrypoints": "http",
|
||||||
|
"traefik/frontends/frontend2/priority": "10",
|
||||||
|
"traefik/frontends/frontend2/routes/test_2/rule": "Path:/test",
|
||||||
|
}
|
||||||
|
for key, value := range backend1 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
for key, value := range backend2 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
for key, value := range frontend1 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
for key, value := range frontend2 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for consul
|
||||||
|
err = utils.Try(60*time.Second, func() error {
|
||||||
|
_, err := s.kv.Exists("traefik/frontends/frontend2/routes/test_2/rule")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// wait for traefik
|
||||||
|
err = utils.TryRequest("http://127.0.0.1:8080/api/providers", 60*time.Second, func(res *http.Response) error {
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(body), "Path:/test") {
|
||||||
|
return errors.New("Incorrect traefik config")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
//check
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8001/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.localhost"
|
||||||
|
response, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(response.StatusCode, checker.Equals, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConsulSuite) skipTestGlobalConfigurationWithClientTLS(c *check.C) {
|
||||||
|
c.Skip("wait for relative path issue in the composefile")
|
||||||
|
s.setupConsulTLS(c)
|
||||||
|
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
||||||
|
|
||||||
|
err := s.kv.Put("traefik/web/address", []byte(":8081"), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// wait for consul
|
||||||
|
err = utils.Try(60*time.Second, func() error {
|
||||||
|
_, err := s.kv.Exists("traefik/web/address")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// start traefik
|
||||||
|
cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml",
|
||||||
|
"--consul", "--consul.endpoint="+consulHost+":8585",
|
||||||
|
"--consul.tls.ca=resources/tls/ca.cert",
|
||||||
|
"--consul.tls.cert=resources/tls/consul.cert",
|
||||||
|
"--consul.tls.key=resources/tls/consul.key",
|
||||||
|
"--consul.tls.insecureskipverify")
|
||||||
|
// cmd.Stdout = os.Stdout
|
||||||
|
// cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
// wait for traefik
|
||||||
|
err = utils.TryRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, func(res *http.Response) error {
|
||||||
|
_, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
|
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/containous/traefik/integration/utils"
|
"github.com/containous/traefik/integration/utils"
|
||||||
|
@ -25,7 +26,7 @@ type EtcdSuite struct {
|
||||||
kv store.Store
|
kv store.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *EtcdSuite) SetUpSuite(c *check.C) {
|
func (s *EtcdSuite) SetUpTest(c *check.C) {
|
||||||
s.createComposeProject(c, "etcd")
|
s.createComposeProject(c, "etcd")
|
||||||
s.composeProject.Start(c)
|
s.composeProject.Start(c)
|
||||||
|
|
||||||
|
@ -54,6 +55,15 @@ func (s *EtcdSuite) SetUpSuite(c *check.C) {
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *EtcdSuite) TearDownTest(c *check.C) {
|
||||||
|
// shutdown and delete compose project
|
||||||
|
if s.composeProject != nil {
|
||||||
|
s.composeProject.Stop(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EtcdSuite) TearDownSuite(c *check.C) {}
|
||||||
|
|
||||||
func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
|
func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
|
||||||
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
|
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
|
||||||
file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdHost string }{etcdHost})
|
file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdHost string }{etcdHost})
|
||||||
|
@ -193,3 +203,231 @@ func (s *EtcdSuite) TestNominalConfiguration(c *check.C) {
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *EtcdSuite) TestGlobalConfiguration(c *check.C) {
|
||||||
|
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
|
||||||
|
err := s.kv.Put("/traefik/entrypoints/http/address", []byte(":8001"), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// wait for etcd
|
||||||
|
err = utils.Try(60*time.Second, func() error {
|
||||||
|
_, err := s.kv.Exists("/traefik/entrypoints/http/address")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// start traefik
|
||||||
|
cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml", "--etcd", "--etcd.endpoint="+etcdHost+":4001")
|
||||||
|
// cmd.Stdout = os.Stdout
|
||||||
|
// cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
whoami1 := s.composeProject.Container(c, "whoami1")
|
||||||
|
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||||
|
whoami3 := s.composeProject.Container(c, "whoami3")
|
||||||
|
whoami4 := s.composeProject.Container(c, "whoami4")
|
||||||
|
|
||||||
|
backend1 := map[string]string{
|
||||||
|
"/traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5",
|
||||||
|
"/traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80",
|
||||||
|
"/traefik/backends/backend1/servers/server1/weight": "10",
|
||||||
|
"/traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80",
|
||||||
|
"/traefik/backends/backend1/servers/server2/weight": "1",
|
||||||
|
}
|
||||||
|
backend2 := map[string]string{
|
||||||
|
"/traefik/backends/backend2/loadbalancer/method": "drr",
|
||||||
|
"/traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80",
|
||||||
|
"/traefik/backends/backend2/servers/server1/weight": "1",
|
||||||
|
"/traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80",
|
||||||
|
"/traefik/backends/backend2/servers/server2/weight": "2",
|
||||||
|
}
|
||||||
|
frontend1 := map[string]string{
|
||||||
|
"/traefik/frontends/frontend1/backend": "backend2",
|
||||||
|
"/traefik/frontends/frontend1/entrypoints": "http",
|
||||||
|
"/traefik/frontends/frontend1/priority": "1",
|
||||||
|
"/traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost",
|
||||||
|
}
|
||||||
|
frontend2 := map[string]string{
|
||||||
|
"/traefik/frontends/frontend2/backend": "backend1",
|
||||||
|
"/traefik/frontends/frontend2/entrypoints": "http",
|
||||||
|
"/traefik/frontends/frontend2/priority": "10",
|
||||||
|
"/traefik/frontends/frontend2/routes/test_2/rule": "Path:/test",
|
||||||
|
}
|
||||||
|
for key, value := range backend1 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
for key, value := range backend2 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
for key, value := range frontend1 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
for key, value := range frontend2 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for etcd
|
||||||
|
err = utils.Try(60*time.Second, func() error {
|
||||||
|
_, err := s.kv.Exists("/traefik/frontends/frontend2/routes/test_2/rule")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// wait for traefik
|
||||||
|
err = utils.TryRequest("http://127.0.0.1:8080/api/providers", 60*time.Second, func(res *http.Response) error {
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(body), "Path:/test") {
|
||||||
|
return errors.New("Incorrect traefik config")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
//check
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8001/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.localhost"
|
||||||
|
response, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(response.StatusCode, checker.Equals, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EtcdSuite) TestCertificatesContentstWithSNIConfigHandshake(c *check.C) {
|
||||||
|
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
|
||||||
|
// start traefik
|
||||||
|
cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml", "--etcd", "--etcd.endpoint="+etcdHost+":4001")
|
||||||
|
// cmd.Stdout = os.Stdout
|
||||||
|
// cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
whoami1 := s.composeProject.Container(c, "whoami1")
|
||||||
|
whoami2 := s.composeProject.Container(c, "whoami2")
|
||||||
|
whoami3 := s.composeProject.Container(c, "whoami3")
|
||||||
|
whoami4 := s.composeProject.Container(c, "whoami4")
|
||||||
|
|
||||||
|
//Copy the contents of the certificate files into ETCD
|
||||||
|
snitestComCert, err := ioutil.ReadFile("fixtures/https/snitest.com.cert")
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
snitestComKey, err := ioutil.ReadFile("fixtures/https/snitest.com.key")
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
snitestOrgCert, err := ioutil.ReadFile("fixtures/https/snitest.org.cert")
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
snitestOrgKey, err := ioutil.ReadFile("fixtures/https/snitest.org.key")
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
globalConfig := map[string]string{
|
||||||
|
"/traefik/entrypoints/https/address": ":4443",
|
||||||
|
"/traefik/entrypoints/https/tls/certificates/0/certfile": string(snitestComCert),
|
||||||
|
"/traefik/entrypoints/https/tls/certificates/0/keyfile": string(snitestComKey),
|
||||||
|
"/traefik/entrypoints/https/tls/certificates/1/certfile": string(snitestOrgCert),
|
||||||
|
"/traefik/entrypoints/https/tls/certificates/1/keyfile": string(snitestOrgKey),
|
||||||
|
"/traefik/defaultentrypoints/0": "https",
|
||||||
|
}
|
||||||
|
|
||||||
|
backend1 := map[string]string{
|
||||||
|
"/traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5",
|
||||||
|
"/traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80",
|
||||||
|
"/traefik/backends/backend1/servers/server1/weight": "10",
|
||||||
|
"/traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80",
|
||||||
|
"/traefik/backends/backend1/servers/server2/weight": "1",
|
||||||
|
}
|
||||||
|
backend2 := map[string]string{
|
||||||
|
"/traefik/backends/backend2/loadbalancer/method": "drr",
|
||||||
|
"/traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80",
|
||||||
|
"/traefik/backends/backend2/servers/server1/weight": "1",
|
||||||
|
"/traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80",
|
||||||
|
"/traefik/backends/backend2/servers/server2/weight": "2",
|
||||||
|
}
|
||||||
|
frontend1 := map[string]string{
|
||||||
|
"/traefik/frontends/frontend1/backend": "backend2",
|
||||||
|
"/traefik/frontends/frontend1/entrypoints": "http",
|
||||||
|
"/traefik/frontends/frontend1/priority": "1",
|
||||||
|
"/traefik/frontends/frontend1/routes/test_1/rule": "Host:snitest.com",
|
||||||
|
}
|
||||||
|
frontend2 := map[string]string{
|
||||||
|
"/traefik/frontends/frontend2/backend": "backend1",
|
||||||
|
"/traefik/frontends/frontend2/entrypoints": "http",
|
||||||
|
"/traefik/frontends/frontend2/priority": "10",
|
||||||
|
"/traefik/frontends/frontend2/routes/test_2/rule": "Host:snitest.org",
|
||||||
|
}
|
||||||
|
for key, value := range globalConfig {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
for key, value := range backend1 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
for key, value := range backend2 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
for key, value := range frontend1 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
for key, value := range frontend2 {
|
||||||
|
err := s.kv.Put(key, []byte(value), nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for etcd
|
||||||
|
err = utils.Try(60*time.Second, func() error {
|
||||||
|
_, err := s.kv.Exists("/traefik/frontends/frontend2/routes/test_2/rule")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
// wait for traefik
|
||||||
|
err = utils.TryRequest("http://127.0.0.1:8080/api/providers", 60*time.Second, func(res *http.Response) error {
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(body), "Host:snitest.org") {
|
||||||
|
return errors.New("Incorrect traefik config")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
//check
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
ServerName: "snitest.com",
|
||||||
|
}
|
||||||
|
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||||
|
|
||||||
|
defer conn.Close()
|
||||||
|
err = conn.Handshake()
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("TLS handshake error"))
|
||||||
|
|
||||||
|
cs := conn.ConnectionState()
|
||||||
|
err = cs.PeerCertificates[0].VerifyHostname("snitest.com")
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("certificate did not match SNI servername"))
|
||||||
|
}
|
||||||
|
|
14
integration/resources/compose/consul_tls.yml
Normal file
14
integration/resources/compose/consul_tls.yml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
consul:
|
||||||
|
image: progrium/consul
|
||||||
|
command: -server -bootstrap -log-level debug -ui-dir /ui -config-dir /configs
|
||||||
|
ports:
|
||||||
|
- "8500:8500"
|
||||||
|
- "8585:8585"
|
||||||
|
expose:
|
||||||
|
- "8300"
|
||||||
|
- "8301"
|
||||||
|
- "8301/udp"
|
||||||
|
- "8302"
|
||||||
|
- "8302/udp"
|
||||||
|
volumes:
|
||||||
|
- ../tls:/configs
|
22
integration/resources/tls/ca.cert
Normal file
22
integration/resources/tls/ca.cert
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDrTCCApWgAwIBAgIJAO8QudN/gvGqMA0GCSqGSIb3DQEBCwUAMG0xCzAJBgNV
|
||||||
|
BAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxDTALBgNVBAcMBEx5b24xDzANBgNVBAoM
|
||||||
|
BlplbmlrYTENMAsGA1UEAwwEdGVzdDEeMBwGCSqGSIb3DQEJARYPdGVzdEB6ZW5p
|
||||||
|
a2EuY29tMB4XDTE2MDcwNjA5MTA1MloXDTI2MDcwNDA5MTA1MlowbTELMAkGA1UE
|
||||||
|
BhMCRlIxDzANBgNVBAgMBkZyYW5jZTENMAsGA1UEBwwETHlvbjEPMA0GA1UECgwG
|
||||||
|
WmVuaWthMQ0wCwYDVQQDDAR0ZXN0MR4wHAYJKoZIhvcNAQkBFg90ZXN0QHplbmlr
|
||||||
|
YS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDKwrXlde3J8hFY
|
||||||
|
hEvH1GH5SfA64/yb7pjXOwI3kvdS2dkbLglOL/jsolARAfFWxhhnnyJUy/BBzZtS
|
||||||
|
rZN/IukuLCypzjnF6I9koVwILU2EkhPcBUzPZWD6gDU42XZH/lgglZyTyLA/pi24
|
||||||
|
eAag5xVuTBMmBGbRsJJEq8MYgzSOAQLu2K8vFPARZdnvOMXVpfrC5+RxDj1AzyxU
|
||||||
|
5s7olWWG13cWkkh2PUNdb1gCXsz34ALG3EmD2S92tovkKHUZS5zHnOvFl8bF7bKC
|
||||||
|
MoXBi4bL2cUQXq815uFl0gfRrBgN4U+uT2UjzhIV9ax/xnkGueXi9wGPYP3Yanu8
|
||||||
|
dguEtevRAgMBAAGjUDBOMB0GA1UdDgQWBBSxdmZrC6APPhMg73JGRa1sKPB2CDAf
|
||||||
|
BgNVHSMEGDAWgBSxdmZrC6APPhMg73JGRa1sKPB2CDAMBgNVHRMEBTADAQH/MA0G
|
||||||
|
CSqGSIb3DQEBCwUAA4IBAQBEZNfJxlKr/hv/cyfbJX6yUKDRG/sIFVD4G9uNEKak
|
||||||
|
N9Dm5/FZ3pzosq/mBuMjXyY/5kYfiBPpyJfUK7CpWfa/U1RP76dDPm+3aaTNK0XS
|
||||||
|
rWWxP/n5plfb6bt53cfKrnk9ud9ZqY6jX0vQzbVp6F4+jN3ZZfl4SEwlbK0jnrYV
|
||||||
|
pbjPKbDS5o0RNgLuk/KN9x/KLb9FdgTYxVrB4orDUzpxx54sjfHRGodUAO9VIlbZ
|
||||||
|
WteavUhCqbVWvYBB64vxKY695PeX79nmwCMVmsy8luquJYgIn27Czexuei3+2mxX
|
||||||
|
f1rPZL+iCzi8cuShXqhrxH2dNyxsmYFjiPwFHSVgYtL2
|
||||||
|
-----END CERTIFICATE-----
|
19
integration/resources/tls/consul.cert
Normal file
19
integration/resources/tls/consul.cert
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDBDCCAeygAwIBAgIBEDANBgkqhkiG9w0BAQUFADBtMQswCQYDVQQGEwJGUjEP
|
||||||
|
MA0GA1UECAwGRnJhbmNlMQ0wCwYDVQQHDARMeW9uMQ8wDQYDVQQKDAZaZW5pa2Ex
|
||||||
|
DTALBgNVBAMMBHRlc3QxHjAcBgkqhkiG9w0BCQEWD3Rlc3RAemVuaWthLmNvbTAe
|
||||||
|
Fw0xNjA3MDYxMzMzMTJaFw0yNjA3MDQxMzMzMTJaMBQxEjAQBgNVBAMMCWxvY2Fs
|
||||||
|
aG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyOvmLF7FLDur27bX7OYo
|
||||||
|
lh3RF/9Bo0Eq1+uFbqs7/KNVp66njn4QYT+OLcRTovoCIbTqaFT7jeqIKxpJ+DWL
|
||||||
|
n61BENZvsfSPkxTyF/zekarMHhvrMSpPqEP+NFnfmEVQ4kUELAyREmq6qkZloavV
|
||||||
|
8X8obRjGbNGuWpNLAlO0g68CAwEAAaOBizCBiDAJBgNVHRMEAjAAMB0GA1UdDgQW
|
||||||
|
BBQPlr+xQCpVYfksoxb+tsnNkL63EjAfBgNVHSMEGDAWgBSxdmZrC6APPhMg73JG
|
||||||
|
Ra1sKPB2CDALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
|
||||||
|
BwMCMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcNAQEFBQADggEBAEUgwhGh48yJ
|
||||||
|
JDlR7lT+uWQkPGXuXuATYKOt2fnqYFQ3z3jzP2Og8mR/iZJm85GvsN7CyfVi8hZL
|
||||||
|
sNJOJ1KPRrGVrOFGD4fd8e1sYYw1wowyEiBQii9f/BGy8khw7rl5RrZotuffTulx
|
||||||
|
PWXF9EyO+vLhpkPzCXG7CkJdakWfJX/83C7xfC+wOpyeGG89IW2l4W6yofLV88hL
|
||||||
|
LqBLfuL3J9ZknplSmHDB4W4TFr9aHd2zXdgAUgGd+b0+JfxZtxClivn8RoIkR6Dr
|
||||||
|
JKhxFO9i714+0MKQMEWAvAFcTw8I9ddkQ4cWs9YoYKYdF/cxowAxGYuykI+H92PO
|
||||||
|
ABAA2aH8ilE=
|
||||||
|
-----END CERTIFICATE-----
|
16
integration/resources/tls/consul.key
Normal file
16
integration/resources/tls/consul.key
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMjr5ixexSw7q9u2
|
||||||
|
1+zmKJYd0Rf/QaNBKtfrhW6rO/yjVaeup45+EGE/ji3EU6L6AiG06mhU+43qiCsa
|
||||||
|
Sfg1i5+tQRDWb7H0j5MU8hf83pGqzB4b6zEqT6hD/jRZ35hFUOJFBCwMkRJquqpG
|
||||||
|
ZaGr1fF/KG0YxmzRrlqTSwJTtIOvAgMBAAECgYB2kymK4/8vRKP/DeBOkeI//abJ
|
||||||
|
p73/79SuCvP7RRko1ugVBrEiGenmypBJGEVXuH4LkG6KViUDMvdboK8oycj0zL6y
|
||||||
|
4naKuWct6EOcxSLhdFyCFLPY+0ggl3F9oG92D02H/3oU7ORBNFBaigSYRSP8EieT
|
||||||
|
5LxCkM2L1cElMJ/6cQJBAP901eNQZSvikAPma+9oPySD01e9yr0AE06wBxGNMkHH
|
||||||
|
OS07WknvIdJAMDKng5Umbp4EG/3UbV5ED/y3NoO22YkCQQDJWVrP5nEx53EXCENb
|
||||||
|
LWDA7SBxjBX60pqvuguDZSjsONQJUlMlqebZSzf/ezLGRUhkzRek8uOwz8MGKnTV
|
||||||
|
sf13AkAGvE3ncHc6cP7bG3g9F8KSc+deqOJvmVDpAjste0uX8GjRiH8Y8/UwVgDv
|
||||||
|
VPtjM2A3SmRyjOdVVPYW8728O1YBAkEAl4aPOPYLKasrCFJHnk5ACfBqAgmSYPgt
|
||||||
|
QSGZmICAk4UQzRMPT8DU4aIhujpUs7FgEbvml1PS1jUEZ5d75XXVcQJAZI50y14r
|
||||||
|
LJ4H+Q2NvvmeyuI8csX+63IGGd/Zt9/EYj4TQnKISnTV3cr/vkmsdoCevC4dT8rS
|
||||||
|
0d1rqCvfNzBUPA==
|
||||||
|
-----END PRIVATE KEY-----
|
9
integration/resources/tls/consul_config.json
Normal file
9
integration/resources/tls/consul_config.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"ports": {
|
||||||
|
"https": 8585
|
||||||
|
},
|
||||||
|
"ca_file": "/configs/ca.cert",
|
||||||
|
"cert_file": "/configs/consul.cert",
|
||||||
|
"key_file": "/configs/consul.key",
|
||||||
|
"verify_outgoing": true
|
||||||
|
}
|
|
@ -50,4 +50,4 @@ pages:
|
||||||
- 'Configuration examples': 'user-guide/examples.md'
|
- 'Configuration examples': 'user-guide/examples.md'
|
||||||
- 'Swarm cluster': 'user-guide/swarm.md'
|
- 'Swarm cluster': 'user-guide/swarm.md'
|
||||||
- 'Kubernetes': 'user-guide/kubernetes.md'
|
- 'Kubernetes': 'user-guide/kubernetes.md'
|
||||||
- Benchmarks: benchmarks.md
|
- 'Key-value store configuration': 'user-guide/kv-config.md'
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/docker/libkv/store"
|
"github.com/docker/libkv/store"
|
||||||
|
@ -9,13 +10,23 @@ import (
|
||||||
|
|
||||||
// BoltDb holds configurations of the BoltDb provider.
|
// BoltDb holds configurations of the BoltDb provider.
|
||||||
type BoltDb struct {
|
type BoltDb struct {
|
||||||
Kv
|
Kv `mapstructure:",squash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
provider.storeType = store.BOLTDB
|
store, err := provider.CreateStore()
|
||||||
boltdb.Register()
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||||
|
}
|
||||||
|
provider.kvclient = store
|
||||||
return provider.provide(configurationChan, pool, constraints)
|
return provider.provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateStore creates the KV store
|
||||||
|
func (provider *BoltDb) CreateStore() (store.Store, error) {
|
||||||
|
provider.storeType = store.BOLTDB
|
||||||
|
boltdb.Register()
|
||||||
|
return provider.createStore()
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/docker/libkv/store"
|
"github.com/docker/libkv/store"
|
||||||
|
@ -9,13 +10,23 @@ import (
|
||||||
|
|
||||||
// Consul holds configurations of the Consul provider.
|
// Consul holds configurations of the Consul provider.
|
||||||
type Consul struct {
|
type Consul struct {
|
||||||
Kv
|
Kv `mapstructure:",squash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
provider.storeType = store.CONSUL
|
store, err := provider.CreateStore()
|
||||||
consul.Register()
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||||
|
}
|
||||||
|
provider.kvclient = store
|
||||||
return provider.provide(configurationChan, pool, constraints)
|
return provider.provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateStore creates the KV store
|
||||||
|
func (provider *Consul) CreateStore() (store.Store, error) {
|
||||||
|
provider.storeType = store.CONSUL
|
||||||
|
consul.Register()
|
||||||
|
return provider.createStore()
|
||||||
|
}
|
||||||
|
|
|
@ -25,11 +25,11 @@ const (
|
||||||
|
|
||||||
// ConsulCatalog holds configurations of the Consul catalog provider.
|
// ConsulCatalog holds configurations of the Consul catalog provider.
|
||||||
type ConsulCatalog struct {
|
type ConsulCatalog struct {
|
||||||
BaseProvider
|
BaseProvider `mapstructure:",squash"`
|
||||||
Endpoint string `description:"Consul server endpoint"`
|
Endpoint string `description:"Consul server endpoint"`
|
||||||
Domain string `description:"Default domain used"`
|
Domain string `description:"Default domain used"`
|
||||||
client *api.Client
|
client *api.Client
|
||||||
Prefix string
|
Prefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
type serviceUpdate struct {
|
type serviceUpdate struct {
|
||||||
|
|
|
@ -20,7 +20,6 @@ import (
|
||||||
eventtypes "github.com/docker/engine-api/types/events"
|
eventtypes "github.com/docker/engine-api/types/events"
|
||||||
"github.com/docker/engine-api/types/filters"
|
"github.com/docker/engine-api/types/filters"
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
|
||||||
"github.com/vdemeester/docker-events"
|
"github.com/vdemeester/docker-events"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,21 +28,13 @@ const DockerAPIVersion string = "1.21"
|
||||||
|
|
||||||
// Docker holds configurations of the Docker provider.
|
// Docker holds configurations of the Docker provider.
|
||||||
type Docker struct {
|
type Docker struct {
|
||||||
BaseProvider
|
BaseProvider `mapstructure:",squash"`
|
||||||
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
|
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
|
||||||
Domain string `description:"Default domain used"`
|
Domain string `description:"Default domain used"`
|
||||||
TLS *DockerTLS `description:"Enable Docker TLS support"`
|
TLS *ClientTLS `description:"Enable Docker TLS support"`
|
||||||
ExposedByDefault bool `description:"Expose containers by default"`
|
ExposedByDefault bool `description:"Expose containers by default"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DockerTLS holds TLS specific configurations
|
|
||||||
type DockerTLS struct {
|
|
||||||
CA string `description:"TLS CA"`
|
|
||||||
Cert string `description:"TLS cert"`
|
|
||||||
Key string `description:"TLS key"`
|
|
||||||
InsecureSkipVerify bool `description:"TLS insecure skip verify"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Docker) createClient() (client.APIClient, error) {
|
func (provider *Docker) createClient() (client.APIClient, error) {
|
||||||
var httpClient *http.Client
|
var httpClient *http.Client
|
||||||
httpHeaders := map[string]string{
|
httpHeaders := map[string]string{
|
||||||
|
@ -51,13 +42,7 @@ func (provider *Docker) createClient() (client.APIClient, error) {
|
||||||
"User-Agent": "Traefik",
|
"User-Agent": "Traefik",
|
||||||
}
|
}
|
||||||
if provider.TLS != nil {
|
if provider.TLS != nil {
|
||||||
tlsOptions := tlsconfig.Options{
|
config, err := provider.TLS.CreateTLSConfig()
|
||||||
CAFile: provider.TLS.CA,
|
|
||||||
CertFile: provider.TLS.Cert,
|
|
||||||
KeyFile: provider.TLS.Key,
|
|
||||||
InsecureSkipVerify: provider.TLS.InsecureSkipVerify,
|
|
||||||
}
|
|
||||||
config, err := tlsconfig.Client(tlsOptions)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -74,6 +59,7 @@ func (provider *Docker) createClient() (client.APIClient, error) {
|
||||||
httpClient = &http.Client{
|
httpClient = &http.Client{
|
||||||
Transport: tr,
|
Transport: tr,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return client.NewClient(provider.Endpoint, DockerAPIVersion, httpClient, httpHeaders)
|
return client.NewClient(provider.Endpoint, DockerAPIVersion, httpClient, httpHeaders)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/docker/libkv/store"
|
"github.com/docker/libkv/store"
|
||||||
|
@ -9,13 +10,23 @@ import (
|
||||||
|
|
||||||
// Etcd holds configurations of the Etcd provider.
|
// Etcd holds configurations of the Etcd provider.
|
||||||
type Etcd struct {
|
type Etcd struct {
|
||||||
Kv
|
Kv `mapstructure:",squash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
provider.storeType = store.ETCD
|
store, err := provider.CreateStore()
|
||||||
etcd.Register()
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||||
|
}
|
||||||
|
provider.kvclient = store
|
||||||
return provider.provide(configurationChan, pool, constraints)
|
return provider.provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateStore creates the KV store
|
||||||
|
func (provider *Etcd) CreateStore() (store.Store, error) {
|
||||||
|
provider.storeType = store.ETCD
|
||||||
|
etcd.Register()
|
||||||
|
return provider.createStore()
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
|
|
||||||
// File holds configurations of the File provider.
|
// File holds configurations of the File provider.
|
||||||
type File struct {
|
type File struct {
|
||||||
BaseProvider
|
BaseProvider `mapstructure:",squash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
|
|
|
@ -51,7 +51,7 @@ func (ns *Namespaces) SetValue(val interface{}) {
|
||||||
|
|
||||||
// Kubernetes holds configurations of the Kubernetes provider.
|
// Kubernetes holds configurations of the Kubernetes provider.
|
||||||
type Kubernetes struct {
|
type Kubernetes struct {
|
||||||
BaseProvider
|
BaseProvider `mapstructure:",squash"`
|
||||||
Endpoint string `description:"Kubernetes server endpoint"`
|
Endpoint string `description:"Kubernetes server endpoint"`
|
||||||
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"`
|
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"`
|
||||||
Namespaces Namespaces `description:"Kubernetes namespaces"`
|
Namespaces Namespaces `description:"Kubernetes namespaces"`
|
||||||
|
|
|
@ -2,10 +2,7 @@
|
||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
@ -22,20 +19,32 @@ import (
|
||||||
|
|
||||||
// Kv holds common configurations of key-value providers.
|
// Kv holds common configurations of key-value providers.
|
||||||
type Kv struct {
|
type Kv struct {
|
||||||
BaseProvider
|
BaseProvider `mapstructure:",squash"`
|
||||||
Endpoint string `description:"Comma sepparated server endpoints"`
|
Endpoint string `description:"Comma sepparated server endpoints"`
|
||||||
Prefix string `description:"Prefix used for KV store"`
|
Prefix string `description:"Prefix used for KV store"`
|
||||||
TLS *KvTLS `description:"Enable TLS support"`
|
TLS *ClientTLS `description:"Enable TLS support"`
|
||||||
storeType store.Backend
|
storeType store.Backend
|
||||||
kvclient store.Store
|
kvclient store.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
// KvTLS holds TLS specific configurations
|
func (provider *Kv) createStore() (store.Store, error) {
|
||||||
type KvTLS struct {
|
storeConfig := &store.Config{
|
||||||
CA string `description:"TLS CA"`
|
ConnectionTimeout: 30 * time.Second,
|
||||||
Cert string `description:"TLS cert"`
|
Bucket: "traefik",
|
||||||
Key string `description:"TLS key"`
|
}
|
||||||
InsecureSkipVerify bool `description:"TLS insecure skip verify"`
|
|
||||||
|
if provider.TLS != nil {
|
||||||
|
var err error
|
||||||
|
storeConfig.TLS, err = provider.TLS.CreateTLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return libkv.NewStore(
|
||||||
|
provider.storeType,
|
||||||
|
strings.Split(provider.Endpoint, ","),
|
||||||
|
storeConfig,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error {
|
func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error {
|
||||||
|
@ -74,50 +83,10 @@ func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
storeConfig := &store.Config{
|
|
||||||
ConnectionTimeout: 30 * time.Second,
|
|
||||||
Bucket: "traefik",
|
|
||||||
}
|
|
||||||
|
|
||||||
if provider.TLS != nil {
|
|
||||||
caPool := x509.NewCertPool()
|
|
||||||
|
|
||||||
if provider.TLS.CA != "" {
|
|
||||||
ca, err := ioutil.ReadFile(provider.TLS.CA)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to read CA. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
caPool.AppendCertsFromPEM(ca)
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err := tls.LoadX509KeyPair(provider.TLS.Cert, provider.TLS.Key)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to load TLS keypair: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
storeConfig.TLS = &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{cert},
|
|
||||||
RootCAs: caPool,
|
|
||||||
InsecureSkipVerify: provider.TLS.InsecureSkipVerify,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
kv, err := libkv.NewStore(
|
if _, err := provider.kvclient.Exists("qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"); err != nil {
|
||||||
provider.storeType,
|
|
||||||
strings.Split(provider.Endpoint, ","),
|
|
||||||
storeConfig,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := kv.Exists("qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"); err != nil {
|
|
||||||
return fmt.Errorf("Failed to test KV store connection: %v", err)
|
return fmt.Errorf("Failed to test KV store connection: %v", err)
|
||||||
}
|
}
|
||||||
provider.kvclient = kv
|
|
||||||
if provider.Watch {
|
if provider.Watch {
|
||||||
pool.Go(func(stop chan bool) {
|
pool.Go(func(stop chan bool) {
|
||||||
err := provider.watchKv(configurationChan, provider.Prefix, stop)
|
err := provider.watchKv(configurationChan, provider.Prefix, stop)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"crypto/tls"
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/cenkalti/backoff"
|
"github.com/cenkalti/backoff"
|
||||||
|
@ -22,13 +21,13 @@ import (
|
||||||
// Marathon holds configuration of the Marathon provider.
|
// Marathon holds configuration of the Marathon provider.
|
||||||
type Marathon struct {
|
type Marathon struct {
|
||||||
BaseProvider
|
BaseProvider
|
||||||
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"`
|
Endpoint string `description:"Marathon server endpoint. You can also specify multiple endpoint for Marathon"`
|
||||||
Domain string `description:"Default domain used"`
|
Domain string `description:"Default domain used"`
|
||||||
ExposedByDefault bool `description:"Expose Marathon apps by default"`
|
ExposedByDefault bool `description:"Expose Marathon apps by default"`
|
||||||
GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"`
|
GroupsAsSubDomains bool `description:"Convert Marathon groups to subdomains"`
|
||||||
DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"`
|
DCOSToken string `description:"DCOSToken for DCOS environment, This will override the Authorization header"`
|
||||||
|
TLS *ClientTLS `description:"Enable Docker TLS support"`
|
||||||
Basic *MarathonBasic
|
Basic *MarathonBasic
|
||||||
TLS *tls.Config
|
|
||||||
marathonClient marathon.Marathon
|
marathonClient marathon.Marathon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,9 +57,13 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage,
|
||||||
if len(provider.DCOSToken) > 0 {
|
if len(provider.DCOSToken) > 0 {
|
||||||
config.DCOSToken = provider.DCOSToken
|
config.DCOSToken = provider.DCOSToken
|
||||||
}
|
}
|
||||||
|
TLSConfig, err := provider.TLS.CreateTLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
config.HTTPClient = &http.Client{
|
config.HTTPClient = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
TLSClientConfig: provider.TLS,
|
TLSClientConfig: TLSConfig,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
client, err := marathon.NewClient(config)
|
client, err := marathon.NewClient(config)
|
||||||
|
|
|
@ -7,10 +7,14 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/containous/traefik/autogen"
|
"github.com/containous/traefik/autogen"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider defines methods of a provider.
|
// Provider defines methods of a provider.
|
||||||
|
@ -92,3 +96,61 @@ func normalize(name string) string {
|
||||||
// get function
|
// get function
|
||||||
return strings.Join(strings.FieldsFunc(name, fargs), "-")
|
return strings.Join(strings.FieldsFunc(name, fargs), "-")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClientTLS holds TLS specific configurations as client
|
||||||
|
// CA, Cert and Key can be either path or file contents
|
||||||
|
type ClientTLS struct {
|
||||||
|
CA string `description:"TLS CA"`
|
||||||
|
Cert string `description:"TLS cert"`
|
||||||
|
Key string `description:"TLS key"`
|
||||||
|
InsecureSkipVerify bool `description:"TLS insecure skip verify"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTLSConfig creates a TLS config from ClientTLS structures
|
||||||
|
func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
|
||||||
|
var err error
|
||||||
|
caPool := x509.NewCertPool()
|
||||||
|
if clientTLS.CA != "" {
|
||||||
|
var ca []byte
|
||||||
|
if _, errCA := os.Stat(clientTLS.CA); errCA == nil {
|
||||||
|
ca, err = ioutil.ReadFile(clientTLS.CA)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to read CA. %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ca = []byte(clientTLS.CA)
|
||||||
|
}
|
||||||
|
caPool.AppendCertsFromPEM(ca)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert := tls.Certificate{}
|
||||||
|
_, errKeyIsFile := os.Stat(clientTLS.Key)
|
||||||
|
|
||||||
|
if _, errCertIsFile := os.Stat(clientTLS.Cert); errCertIsFile == nil {
|
||||||
|
if errKeyIsFile == nil {
|
||||||
|
cert, err = tls.LoadX509KeyPair(clientTLS.Cert, clientTLS.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to load TLS keypair: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("tls cert is a file, but tls key is not")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if errKeyIsFile != nil {
|
||||||
|
cert, err = tls.X509KeyPair([]byte(clientTLS.Cert), []byte(clientTLS.Key))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to load TLS keypair: %v", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("tls key is a file, but tls cert is not")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TLSConfig := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
RootCAs: caPool,
|
||||||
|
InsecureSkipVerify: clientTLS.InsecureSkipVerify,
|
||||||
|
}
|
||||||
|
return TLSConfig, nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/docker/libkv/store"
|
"github.com/docker/libkv/store"
|
||||||
|
@ -9,13 +10,23 @@ import (
|
||||||
|
|
||||||
// Zookepper holds configurations of the Zookepper provider.
|
// Zookepper holds configurations of the Zookepper provider.
|
||||||
type Zookepper struct {
|
type Zookepper struct {
|
||||||
Kv
|
Kv `mapstructure:",squash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide allows the provider to provide configurations to traefik
|
// Provide allows the provider to provide configurations to traefik
|
||||||
// using the given configuration channel.
|
// using the given configuration channel.
|
||||||
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
func (provider *Zookepper) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||||
provider.storeType = store.ZK
|
store, err := provider.CreateStore()
|
||||||
zookeeper.Register()
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||||
|
}
|
||||||
|
provider.kvclient = store
|
||||||
return provider.provide(configurationChan, pool, constraints)
|
return provider.provide(configurationChan, pool, constraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateStore creates the KV store
|
||||||
|
func (provider *Zookepper) CreateStore() (store.Store, error) {
|
||||||
|
provider.storeType = store.ZK
|
||||||
|
zookeeper.Register()
|
||||||
|
return provider.createStore()
|
||||||
|
}
|
||||||
|
|
11
server.go
11
server.go
|
@ -290,14 +290,9 @@ func (server *Server) createTLSConfig(entryPointName string, tlsOption *TLS, rou
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
config := &tls.Config{}
|
config, err := tlsOption.Certificates.CreateTLSConfig()
|
||||||
config.Certificates = []tls.Certificate{}
|
if err != nil {
|
||||||
for _, v := range tlsOption.Certificates {
|
return nil, err
|
||||||
cert, err := tls.LoadX509KeyPair(v.CertFile, v.KeyFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config.Certificates = append(config.Certificates, cert)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tlsOption.ClientCAFiles) > 0 {
|
if len(tlsOption.ClientCAFiles) > 0 {
|
||||||
|
|
50
traefik.go
50
traefik.go
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/containous/traefik/middlewares"
|
"github.com/containous/traefik/middlewares"
|
||||||
"github.com/containous/traefik/provider"
|
"github.com/containous/traefik/provider"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
|
"github.com/docker/libkv/store"
|
||||||
fmtlog "log"
|
fmtlog "log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -111,6 +112,19 @@ Complete documentation is available at https://traefik.io`,
|
||||||
|
|
||||||
traefikConfiguration.ConfigFile = toml.ConfigFileUsed()
|
traefikConfiguration.ConfigFile = toml.ConfigFileUsed()
|
||||||
|
|
||||||
|
kv, err := CreateKvSource(traefikConfiguration)
|
||||||
|
if err != nil {
|
||||||
|
fmtlog.Println(err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if kv != nil {
|
||||||
|
s.AddSource(kv)
|
||||||
|
if _, err := s.LoadConfig(); err != nil {
|
||||||
|
fmtlog.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.Run(); err != nil {
|
if err := s.Run(); err != nil {
|
||||||
fmtlog.Println(err)
|
fmtlog.Println(err)
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
|
@ -180,3 +194,39 @@ func run(traefikConfiguration *TraefikConfiguration) {
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
log.Info("Shutting down")
|
log.Info("Shutting down")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateKvSource creates KvSource
|
||||||
|
// TLS support is enable for Consul and ects backends
|
||||||
|
func CreateKvSource(traefikConfiguration *TraefikConfiguration) (*staert.KvSource, error) {
|
||||||
|
var kv *staert.KvSource
|
||||||
|
var store store.Store
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case traefikConfiguration.Consul != nil:
|
||||||
|
store, err = traefikConfiguration.Consul.CreateStore()
|
||||||
|
kv = &staert.KvSource{
|
||||||
|
Store: store,
|
||||||
|
Prefix: traefikConfiguration.Consul.Prefix,
|
||||||
|
}
|
||||||
|
case traefikConfiguration.Etcd != nil:
|
||||||
|
store, err = traefikConfiguration.Etcd.CreateStore()
|
||||||
|
kv = &staert.KvSource{
|
||||||
|
Store: store,
|
||||||
|
Prefix: traefikConfiguration.Etcd.Prefix,
|
||||||
|
}
|
||||||
|
case traefikConfiguration.Zookeeper != nil:
|
||||||
|
store, err = traefikConfiguration.Zookeeper.CreateStore()
|
||||||
|
kv = &staert.KvSource{
|
||||||
|
Store: store,
|
||||||
|
Prefix: traefikConfiguration.Zookeeper.Prefix,
|
||||||
|
}
|
||||||
|
case traefikConfiguration.Boltdb != nil:
|
||||||
|
store, err = traefikConfiguration.Boltdb.CreateStore()
|
||||||
|
kv = &staert.KvSource{
|
||||||
|
Store: store,
|
||||||
|
Prefix: traefikConfiguration.Boltdb.Prefix,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return kv, err
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue