mirror of
https://github.com/containous/traefik.git
synced 2025-09-06 05:44:21 +03:00
Compare commits
248 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7f3ae6edb0 | ||
|
1a993f5dfb | ||
|
4e527304d0 | ||
|
841be8d806 | ||
|
055cd01bb7 | ||
|
e34c364d5e | ||
|
926eb099f1 | ||
|
710508dc40 | ||
|
b4ea68b88a | ||
|
2bf9acd95e | ||
|
a8cb905255 | ||
|
fe1b982d13 | ||
|
221ae2427b | ||
|
29f780863b | ||
|
8aaca8e55c | ||
|
2dda3d2feb | ||
|
22ebaedb45 | ||
|
7065f00443 | ||
|
801e0f9ef7 | ||
|
ac20ddfc6c | ||
|
f6576cce27 | ||
|
d3b48cdd22 | ||
|
c26b36cf4f | ||
|
3095da64d7 | ||
|
07f961ecba | ||
|
3db6e185e0 | ||
|
4430befe90 | ||
|
1c4eb4322b | ||
|
3f3fa61a51 | ||
|
ddf24039e8 | ||
|
5b6a5f8aa9 | ||
|
3e6d2391f7 | ||
|
664ee9d82f | ||
|
c9cc3c9895 | ||
|
00c7e5c72b | ||
|
558b31f4d9 | ||
|
174a5e7f13 | ||
|
c821f191b0 | ||
|
3322e564fd | ||
|
7bf5d557c1 | ||
|
0c1e06199c | ||
|
85a20b9a39 | ||
|
5641af437e | ||
|
1c8d3ded3d | ||
|
c2a445370e | ||
|
8e5355f2d9 | ||
|
2492157833 | ||
|
7c375e8fd9 | ||
|
53b5d8ac33 | ||
|
e5a8fb390e | ||
|
79cbae0c73 | ||
|
22b0b8b750 | ||
|
ddbddf6edf | ||
|
adcf58da68 | ||
|
649cb548d0 | ||
|
870f378782 | ||
|
82a58010f5 | ||
|
f652c58367 | ||
|
468d138be7 | ||
|
f409d2f435 | ||
|
5780a17794 | ||
|
9b765d23fa | ||
|
4476861d9f | ||
|
e12ddca1a5 | ||
|
084d00a156 | ||
|
404a73a712 | ||
|
3b2410d904 | ||
|
bd5009058b | ||
|
d3f79c7ad3 | ||
|
3f65503a79 | ||
|
6ac1216f8c | ||
|
1cae35f96b | ||
|
0d13e91a62 | ||
|
b1b600e09e | ||
|
3692e1c4bd | ||
|
dcbd82ac3b | ||
|
d4f0541027 | ||
|
a30d8e7819 | ||
|
8ee6bf044a | ||
|
6632247c9c | ||
|
d68389dc52 | ||
|
4a43273ee5 | ||
|
66f52a6e21 | ||
|
640bfc4eff | ||
|
408ef0f5b7 | ||
|
b9f76394aa | ||
|
a96f483d56 | ||
|
84cb9f15a4 | ||
|
d4da14cf18 | ||
|
4ad4b8e0b8 | ||
|
bb29d9c8ca | ||
|
e72e65858f | ||
|
a42845502e | ||
|
bea5ad3f13 | ||
|
5a0440d6f8 | ||
|
38b62d4ae3 | ||
|
462d8b3e74 | ||
|
291c3b6dbc | ||
|
df225d9170 | ||
|
592e981bd2 | ||
|
3d7c44735a | ||
|
81fddb4ccf | ||
|
c9d4c5ae3e | ||
|
be5b1fd92b | ||
|
d78c419627 | ||
|
dc52abf4ce | ||
|
a13549cc28 | ||
|
baf4c474e3 | ||
|
a58750992d | ||
|
17546c3a08 | ||
|
067f13b61c | ||
|
e249983c77 | ||
|
454b191370 | ||
|
a882a9d79f | ||
|
89fc835bb2 | ||
|
364958cbaf | ||
|
1b6af2045e | ||
|
be09ff8e43 | ||
|
99c8bffcbf | ||
|
03d16d12d5 | ||
|
1624c51cb5 | ||
|
83aabefcc5 | ||
|
dfece708e1 | ||
|
5d0f82ffbd | ||
|
361dc94002 | ||
|
cc0fdf15ef | ||
|
928675a847 | ||
|
12c1131b0c | ||
|
bb1dde0469 | ||
|
ced69b8397 | ||
|
013808956c | ||
|
009057cb87 | ||
|
82cb21fca3 | ||
|
7e8937a332 | ||
|
e5dcfa0a2e | ||
|
f4520a011a | ||
|
98dd6ca460 | ||
|
c3d9312240 | ||
|
5ea761e19f | ||
|
46a7860427 | ||
|
af9b63eaed | ||
|
9a26e0db16 | ||
|
efe6989fd3 | ||
|
aa1c9b80e3 | ||
|
6981df3b9a | ||
|
0d1ed625a8 | ||
|
710fc56c6a | ||
|
d5a15d6756 | ||
|
b376da1829 | ||
|
f7f17f0057 | ||
|
d06b9c2992 | ||
|
99ca5d0a03 | ||
|
4783c7f70a | ||
|
d89bdfbd27 | ||
|
1e324ad3bc | ||
|
52737e91e5 | ||
|
1872e2b63d | ||
|
3c5605b793 | ||
|
9a2b7cf5be | ||
|
1a20e9f9b4 | ||
|
14d79e4eef | ||
|
71f48d2aef | ||
|
312adca226 | ||
|
d35c6e77d7 | ||
|
1de21c86ae | ||
|
c709a592eb | ||
|
a54c544eb4 | ||
|
7d936ec6aa | ||
|
f63ec1332f | ||
|
d340ccd601 | ||
|
95e8f0a31e | ||
|
97ddfcb17a | ||
|
7bb5f9a1e4 | ||
|
11297b38c5 | ||
|
fc19ab2868 | ||
|
5e01c0a7db | ||
|
f1c3d820f7 | ||
|
0757a75732 | ||
|
f0ea45a0f8 | ||
|
45f2335a60 | ||
|
d629939cf3 | ||
|
404f76dcb9 | ||
|
498ce6b00c | ||
|
e3a8fd116d | ||
|
d33e09bcf3 | ||
|
fb3bad3887 | ||
|
3a736ad4a8 | ||
|
c1b0c41769 | ||
|
c03274703e | ||
|
4cd08e88f6 | ||
|
e2c4872030 | ||
|
d4f190e995 | ||
|
039107e837 | ||
|
ef6c211275 | ||
|
1f3accc0d7 | ||
|
2815f80063 | ||
|
fa645abee3 | ||
|
a86649def3 | ||
|
1fc4c56bc4 | ||
|
79dd72f53d | ||
|
ffa060ce56 | ||
|
5ce9719951 | ||
|
914aa7d372 | ||
|
4a88cbde3a | ||
|
4882519c0f | ||
|
7abe68fac1 | ||
|
e62cca1e7c | ||
|
a016741918 | ||
|
2f95810fa3 | ||
|
16e2c3b1e0 | ||
|
bc8a92caa9 | ||
|
3a5b67a3e1 | ||
|
2a596b8162 | ||
|
e059239bc3 | ||
|
986ad9fc57 | ||
|
1bb3d9be73 | ||
|
ae31f19ef6 | ||
|
c170ddc7ae | ||
|
58b6d92ce2 | ||
|
87a4d73556 | ||
|
4c54a003fa | ||
|
a5f3eabf8b | ||
|
3bf6c59d23 | ||
|
ef83dea95c | ||
|
686c23d25b | ||
|
b153e90ec5 | ||
|
38cc36980f | ||
|
b83fb525a8 | ||
|
e26e0955b3 | ||
|
7ada80b619 | ||
|
056e0fe2d9 | ||
|
9be0c67d5c | ||
|
664bc9cae0 | ||
|
959c7dc783 | ||
|
8e333d0a03 | ||
|
5afcf17706 | ||
|
61b22316d6 | ||
|
d2f51fccb9 | ||
|
c13db04f6d | ||
|
d3aa056151 | ||
|
1c60f0b53b | ||
|
ca2b85f453 | ||
|
b80479f9ef | ||
|
d1112a0feb | ||
|
a73baded88 | ||
|
94fa95d747 | ||
|
9f6484a328 | ||
|
40c0ed092e |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
glide.lock binary
|
63
.github/CONTRIBUTING.md
vendored
63
.github/CONTRIBUTING.md
vendored
@@ -2,16 +2,9 @@
|
||||
|
||||
### Building
|
||||
|
||||
You need either [Docker](https://github.com/docker/docker) and `make`, or `go` and `glide` in order to build traefik.
|
||||
You need either [Docker](https://github.com/docker/docker) and `make` (Method 1), or `go` and `glide` (Method 2) in order to build traefik.
|
||||
|
||||
#### Setting up your `go` environment
|
||||
|
||||
- You need `go` v1.5
|
||||
- You need to set `export GO15VENDOREXPERIMENT=1` environment variable
|
||||
- You need `go-bindata` to be able to use `go generate` command (needed to build) : `go get github.com/jteeuwen/go-bindata/...`.
|
||||
- If you clone Træfɪk into something like `~/go/src/github.com/traefik`, your `GOPATH` variable will have to be set to `~/go`: export `GOPATH=~/go`.
|
||||
|
||||
#### Using `Docker` and `Makefile`
|
||||
#### Method 1: Using `Docker` and `Makefile`
|
||||
|
||||
You need to run the `binary` target. This will create binaries for Linux platform in the `dist` folder.
|
||||
|
||||
@@ -33,32 +26,51 @@ $ ls dist/
|
||||
traefik*
|
||||
```
|
||||
|
||||
#### Using `glide`
|
||||
#### Method 2: Using `go` and `glide`
|
||||
|
||||
###### Setting up your `go` environment
|
||||
|
||||
- You need `go` v1.5+ (1.7 is acceptable)
|
||||
- You need to set `$ export GO15VENDOREXPERIMENT=1` environment variable if you are using go v1.5 (it is already enabled in 1.6+)
|
||||
- It is recommended you clone Træfɪk into a directory like `~/go/src/github.com/containous/traefik` (This is the official golang workspace hierarchy, and will allow dependencies to resolve properly)
|
||||
- This will allow your `GOPATH` and `PATH` variable to be set to `~/go` via:
|
||||
```
|
||||
$ export GOPATH=~/go
|
||||
$ export PATH=$PATH:$GOPATH/bin
|
||||
```
|
||||
|
||||
This can be verified via `$ go env`
|
||||
- You will want to add those 2 export lines to your `.bashrc` or `.bash_profile`
|
||||
- You need `go-bindata` to be able to use `go generate` command (needed to build) : `$ go get github.com/jteeuwen/go-bindata/...` (Please note, the ellipses are required)
|
||||
|
||||
###### Setting up your `glide` environment
|
||||
|
||||
- Glide can be installed either via homebrew: `$ brew install glide` or via the official glide script: `$ curl https://glide.sh/get | sh`
|
||||
|
||||
The idea behind `glide` is the following :
|
||||
|
||||
- when checkout(ing) a project, **run `glide install`** to install
|
||||
(`go get …`) the dependencies in the `GOPATH`.
|
||||
- when checkout(ing) a project, run `$ glide install` from the cloned directory to install
|
||||
(`go get …`) the dependencies in your `GOPATH`.
|
||||
- if you need another dependency, import and use it in
|
||||
the source, and **run `glide get github.com/Masterminds/cookoo`** to save it in
|
||||
the source, and run `$ glide get github.com/Masterminds/cookoo` to save it in
|
||||
`vendor` and add it to your `glide.yaml`.
|
||||
|
||||
```bash
|
||||
$ glide install
|
||||
# generate
|
||||
# generate (Only required to integrate other components such as web dashboard)
|
||||
$ go generate
|
||||
# Simple go build
|
||||
# Standard go build
|
||||
$ go build
|
||||
# Using gox to build multiple platform
|
||||
$ gox "linux darwin" "386 amd64 arm" \
|
||||
-output="dist/traefik_{{.OS}}-{{.Arch}}"
|
||||
# run other commands like tests
|
||||
$ go test ./...
|
||||
ok _/home/vincent/src/github/vdemeester/traefik 0.004s
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
##### Method 1: `Docker` and `make`
|
||||
|
||||
You can run unit tests using the `test-unit` target and the
|
||||
integration test using the `test-integration` target.
|
||||
|
||||
@@ -77,7 +89,7 @@ ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
|
||||
Test success
|
||||
```
|
||||
|
||||
For development purpose, you can specifiy which tests to run by using:
|
||||
For development purposes, you can specify which tests to run by using:
|
||||
```
|
||||
# Run every tests in the MyTest suite
|
||||
TESTFLAGS="-check.f MyTestSuite" make test-integration
|
||||
@@ -94,6 +106,13 @@ TESTFLAGS="-check.f MyTestSuite.*Test" make test-integration
|
||||
|
||||
More: https://labix.org/gocheck
|
||||
|
||||
##### Method 2: `go` and `glide`
|
||||
|
||||
- Tests can be run from the cloned directory, by `$ go test ./...` which should return `ok` similar to:
|
||||
```
|
||||
ok _/home/vincent/src/github/vdemeester/traefik 0.004s
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)
|
||||
@@ -113,13 +132,13 @@ Then install mkdocs with pip
|
||||
$ pip install mkdocs
|
||||
```
|
||||
|
||||
To test documentaion localy run `mkdocs serve` in the root directory, this should start a server localy to preview your changes.
|
||||
To test documentation locally run `mkdocs serve` in the root directory, this should start a server locally to preview your changes.
|
||||
|
||||
```
|
||||
$ mkdocs serve
|
||||
INFO - Building documentation...
|
||||
WARNING - Config value: 'theme'. Warning: The theme 'united' will be removed in an upcoming MkDocs release. See http://www.mkdocs.org/about/release-notes/ for more details
|
||||
INFO - Cleaning site directory
|
||||
INFO - Building documentation...
|
||||
WARNING - Config value: 'theme'. Warning: The theme 'united' will be removed in an upcoming MkDocs release. See http://www.mkdocs.org/about/release-notes/ for more details
|
||||
INFO - Cleaning site directory
|
||||
[I 160505 22:31:24 server:281] Serving on http://127.0.0.1:8000
|
||||
[I 160505 22:31:24 handlers:59] Start watching changes
|
||||
[I 160505 22:31:24 handlers:61] Start detecting changes
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,7 +2,6 @@
|
||||
gen.go
|
||||
.idea
|
||||
.intellij
|
||||
log
|
||||
*.iml
|
||||
traefik
|
||||
traefik.toml
|
||||
@@ -13,3 +12,4 @@ static/
|
||||
site/
|
||||
*.log
|
||||
*.exe
|
||||
.DS_Store
|
||||
|
@@ -4,7 +4,7 @@ env:
|
||||
- secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg=
|
||||
- REPO: $TRAVIS_REPO_SLUG
|
||||
- VERSION: $TRAVIS_TAG
|
||||
- CODENAME: reblochon
|
||||
- CODENAME: camembert
|
||||
matrix:
|
||||
- DOCKER_VERSION=1.9.1
|
||||
- DOCKER_VERSION=1.10.1
|
||||
@@ -20,6 +20,7 @@ install:
|
||||
- docker version
|
||||
- pip install --user mkdocs
|
||||
- pip install --user pymdown-extensions
|
||||
- pip install --user mkdocs-bootswatch
|
||||
before_script:
|
||||
- make validate
|
||||
- make binary
|
||||
|
485
CHANGELOG.md
485
CHANGELOG.md
@@ -1,5 +1,490 @@
|
||||
# Change Log
|
||||
|
||||
## [v1.1.1](https://github.com/containous/traefik/tree/v1.1.1) (2016-11-29)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.1.0...v1.1.1)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Getting "Kubernetes connection error failed to decode watch event : unexpected EOF" every minute in Traefik log [\#732](https://github.com/containous/traefik/issues/732)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- 1.1.0 kubernetes panic: send on closed channel [\#877](https://github.com/containous/traefik/issues/877)
|
||||
- digest auth example is incorrect [\#869](https://github.com/containous/traefik/issues/869)
|
||||
- Marathon & Mesos providers' GroupsAsSubDomains option broken [\#867](https://github.com/containous/traefik/issues/867)
|
||||
- 404 responses when a new Marathon leader is elected [\#653](https://github.com/containous/traefik/issues/653)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- traefik:latest fails to auto-detect Docker containers [\#901](https://github.com/containous/traefik/issues/901)
|
||||
- Panic error on bare metal Kubernetes \(installed with Kubeadm\) [\#897](https://github.com/containous/traefik/issues/897)
|
||||
- api backend readOnly: what is the purpose of this setting [\#893](https://github.com/containous/traefik/issues/893)
|
||||
- file backend: using external file - doesn't work [\#892](https://github.com/containous/traefik/issues/892)
|
||||
- auth support for web backend [\#891](https://github.com/containous/traefik/issues/891)
|
||||
- Basic auth with docker labels [\#890](https://github.com/containous/traefik/issues/890)
|
||||
- file vs inline config [\#888](https://github.com/containous/traefik/issues/888)
|
||||
- combine Host and HostRegexp rules [\#882](https://github.com/containous/traefik/issues/882)
|
||||
- \[Question\] Traefik + Kubernetes + Let's Encrypt \(ssl not used\) [\#881](https://github.com/containous/traefik/issues/881)
|
||||
- Traefik security for dashboard [\#880](https://github.com/containous/traefik/issues/880)
|
||||
- Kubernetes Nginx Deployment Panic [\#879](https://github.com/containous/traefik/issues/879)
|
||||
- Kubernetes Example Address already in use [\#872](https://github.com/containous/traefik/issues/872)
|
||||
- ETCD Backend - frontend/backends missing [\#866](https://github.com/containous/traefik/issues/866)
|
||||
- \[Swarm mode\] Dashboard does not work on RC4 [\#864](https://github.com/containous/traefik/issues/864)
|
||||
- Docker v1.1.0 image does not exist [\#861](https://github.com/containous/traefik/issues/861)
|
||||
- ConsulService catalog do not support multiple rules [\#859](https://github.com/containous/traefik/issues/859)
|
||||
- Update official docker repo [\#858](https://github.com/containous/traefik/issues/858)
|
||||
- Still a memory leak with k8s - 1.1 RC4 [\#844](https://github.com/containous/traefik/issues/844)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix Swarm panic [\#908](https://github.com/containous/traefik/pull/908) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix k8s panic [\#900](https://github.com/containous/traefik/pull/900) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix missing value for k8s watch request parameter [\#874](https://github.com/containous/traefik/pull/874) ([codablock](https://github.com/codablock))
|
||||
- Fix GroupsAsSubDomains option for Mesos and Marathon [\#868](https://github.com/containous/traefik/pull/868) ([ryanleary](https://github.com/ryanleary))
|
||||
- Normalize backend even if is user-defined [\#865](https://github.com/containous/traefik/pull/865) ([WTFKr0](https://github.com/WTFKr0))
|
||||
- consul/kv.tmpl: weight default value should be a int [\#826](https://github.com/containous/traefik/pull/826) ([klausenbusk](https://github.com/klausenbusk))
|
||||
|
||||
## [v1.1.0](https://github.com/containous/traefik/tree/v1.1.0) (2016-11-17)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0...v1.1.0)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Support healthcheck if present for docker [\#666](https://github.com/containous/traefik/issues/666)
|
||||
- Standard unit for traefik latency in access log [\#559](https://github.com/containous/traefik/issues/559)
|
||||
- \[CI\] wiredep marked as unmaintained [\#550](https://github.com/containous/traefik/issues/550)
|
||||
- Feature Request: Enable Health checks to containers. [\#540](https://github.com/containous/traefik/issues/540)
|
||||
- Feature Request: SSL Cipher Selection [\#535](https://github.com/containous/traefik/issues/535)
|
||||
- Error with -consulcatalog and missing load balance method on 1.0.0 [\#524](https://github.com/containous/traefik/issues/524)
|
||||
- Running Traefik with Docker 1.12 Swarm Mode [\#504](https://github.com/containous/traefik/issues/504)
|
||||
- Kubernetes provider: should allow the master url to be override [\#501](https://github.com/containous/traefik/issues/501)
|
||||
- \[FRONTEND\]\[LE\] Pre-generate SSL certificates for "Host:" rules [\#483](https://github.com/containous/traefik/issues/483)
|
||||
- Frontend Rule evolution [\#437](https://github.com/containous/traefik/issues/437)
|
||||
- Add a Changelog [\#388](https://github.com/containous/traefik/issues/388)
|
||||
- Add label matching for kubernetes ingests [\#363](https://github.com/containous/traefik/issues/363)
|
||||
- Acme in HA Traefik Scenario [\#348](https://github.com/containous/traefik/issues/348)
|
||||
- HTTP Basic Auth support [\#77](https://github.com/containous/traefik/issues/77)
|
||||
- Session affinity / stickiness / persistence [\#5](https://github.com/containous/traefik/issues/5)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- 1.1.0-rc4 dashboard UX not displaying [\#828](https://github.com/containous/traefik/issues/828)
|
||||
- Traefik stopped serving on upgrade to v1.1.0-rc3 [\#807](https://github.com/containous/traefik/issues/807)
|
||||
- cannot access webui/dashboard [\#796](https://github.com/containous/traefik/issues/796)
|
||||
- Traefik cannot read constraints from KV [\#794](https://github.com/containous/traefik/issues/794)
|
||||
- HTTP2 - configuration [\#790](https://github.com/containous/traefik/issues/790)
|
||||
- Cannot provide multiple certificates using flag [\#757](https://github.com/containous/traefik/issues/757)
|
||||
- Allow multiple certificates on a single entrypoint when trying to use TLS? [\#747](https://github.com/containous/traefik/issues/747)
|
||||
- traefik \* Users: unsupported type: slice [\#743](https://github.com/containous/traefik/issues/743)
|
||||
- \[Docker swarm mode\] The traefik.docker.network seems to have no effect [\#719](https://github.com/containous/traefik/issues/719)
|
||||
- traefik hangs - stops handling requests [\#662](https://github.com/containous/traefik/issues/662)
|
||||
- Add long jobs in exponential backoff providers [\#626](https://github.com/containous/traefik/issues/626)
|
||||
- Tip of tree crashes on invalid pointer on Marathon provider [\#624](https://github.com/containous/traefik/issues/624)
|
||||
- ACME: revoke certificate on agreement update [\#579](https://github.com/containous/traefik/issues/579)
|
||||
- WebUI: Providers tabs disappeared [\#577](https://github.com/containous/traefik/issues/577)
|
||||
- traefik version command contains incorrect information when building from master branch [\#569](https://github.com/containous/traefik/issues/569)
|
||||
- Case sensitive domain names breaks routing [\#562](https://github.com/containous/traefik/issues/562)
|
||||
- Flag --etcd.endpoint default [\#508](https://github.com/containous/traefik/issues/508)
|
||||
- Conditional ACME on demand generation [\#505](https://github.com/containous/traefik/issues/505)
|
||||
- Important delay with streams \(Mozilla EventSource\) [\#503](https://github.com/containous/traefik/issues/503)
|
||||
- Traefik crashing [\#458](https://github.com/containous/traefik/issues/458)
|
||||
- traefik.toml constraints error: `Expected map but found 'string'.` [\#451](https://github.com/containous/traefik/issues/451)
|
||||
- Multiple path separators in the url path causing redirect [\#167](https://github.com/containous/traefik/issues/167)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- All path rules require paths to be lowercase [\#851](https://github.com/containous/traefik/issues/851)
|
||||
- The UI stops working after a time and have to restart the service. [\#840](https://github.com/containous/traefik/issues/840)
|
||||
- Incorrect Dashboard page returned [\#831](https://github.com/containous/traefik/issues/831)
|
||||
- LoadBalancing doesn't work in single node Swarm-mode [\#815](https://github.com/containous/traefik/issues/815)
|
||||
- cannot connect to docker daemon [\#813](https://github.com/containous/traefik/issues/813)
|
||||
- Let's encrypt configuration not working [\#805](https://github.com/containous/traefik/issues/805)
|
||||
- Multiple subdomains for Marathon backend. [\#785](https://github.com/containous/traefik/issues/785)
|
||||
- traefik-1.1.0-rc1: build error [\#781](https://github.com/containous/traefik/issues/781)
|
||||
- dependencies installation error [\#755](https://github.com/containous/traefik/issues/755)
|
||||
- k8s provider w/ acme? [\#752](https://github.com/containous/traefik/issues/752)
|
||||
- Swarm Docs - How to use a FQDN [\#744](https://github.com/containous/traefik/issues/744)
|
||||
- Documented ProvidersThrottleDuration value is invalid [\#741](https://github.com/containous/traefik/issues/741)
|
||||
- Sensible configuration for consulCatalog [\#737](https://github.com/containous/traefik/issues/737)
|
||||
- Traefik ignoring container listening in more than one TCP port [\#734](https://github.com/containous/traefik/issues/734)
|
||||
- Loadbalaning issues with traefik and Docker Swarm cluster [\#730](https://github.com/containous/traefik/issues/730)
|
||||
- issues with marathon app ids containing a dot [\#726](https://github.com/containous/traefik/issues/726)
|
||||
- Error when using HA acme in kubernetes with etcd [\#725](https://github.com/containous/traefik/issues/725)
|
||||
- \[Docker swarm mode\] No round robin when using service [\#718](https://github.com/containous/traefik/issues/718)
|
||||
- Dose it support docker swarm mode [\#712](https://github.com/containous/traefik/issues/712)
|
||||
- Kubernetes - Undefined backend [\#710](https://github.com/containous/traefik/issues/710)
|
||||
- How Routing traffic depending on path not domain in docker [\#706](https://github.com/containous/traefik/issues/706)
|
||||
- Constraints on Consul Catalogue not working as expected [\#703](https://github.com/containous/traefik/issues/703)
|
||||
- Global InsecureSkipVerify does not work [\#700](https://github.com/containous/traefik/issues/700)
|
||||
- Traefik crashes when using Consul catalog [\#699](https://github.com/containous/traefik/issues/699)
|
||||
- \[documentation/feature\] Consul/etcd support atomic multiple key changes now [\#698](https://github.com/containous/traefik/issues/698)
|
||||
- How to configure which network to use when starting traefik binary? [\#694](https://github.com/containous/traefik/issues/694)
|
||||
- How to get multiple host headers working for docker labels? [\#692](https://github.com/containous/traefik/issues/692)
|
||||
- Requests with URL-encoded characters are not forwarded correctly [\#684](https://github.com/containous/traefik/issues/684)
|
||||
- File Watcher for rules does not work [\#683](https://github.com/containous/traefik/issues/683)
|
||||
- Issue with global InsecureSkipVerify = true and self signed certificates [\#667](https://github.com/containous/traefik/issues/667)
|
||||
- Docker exposedbydefault = false didn't work [\#663](https://github.com/containous/traefik/issues/663)
|
||||
- swarm documentation needs update [\#656](https://github.com/containous/traefik/issues/656)
|
||||
- \[ACME\] Auto SAN Detection [\#655](https://github.com/containous/traefik/issues/655)
|
||||
- Fronting a domain with DNS A-record round-robin & ACME [\#654](https://github.com/containous/traefik/issues/654)
|
||||
- Overriding toml configuration with environment variables [\#650](https://github.com/containous/traefik/issues/650)
|
||||
- marathon provider exposedByDefault = false [\#647](https://github.com/containous/traefik/issues/647)
|
||||
- Add status URL for service up checks [\#642](https://github.com/containous/traefik/issues/642)
|
||||
- acme's storage file, containing private key, is word readable [\#638](https://github.com/containous/traefik/issues/638)
|
||||
- wildcard domain with exclusions [\#633](https://github.com/containous/traefik/issues/633)
|
||||
- Enable evenly distribution among backend [\#631](https://github.com/containous/traefik/issues/631)
|
||||
- Traefik sporadically failing when proxying requests [\#615](https://github.com/containous/traefik/issues/615)
|
||||
- TCP Proxy [\#608](https://github.com/containous/traefik/issues/608)
|
||||
- How to use in Windows? [\#605](https://github.com/containous/traefik/issues/605)
|
||||
- `ClientCAFiles` ignored [\#604](https://github.com/containous/traefik/issues/604)
|
||||
- Let`s Encrypt enable in etcd [\#600](https://github.com/containous/traefik/issues/600)
|
||||
- Support HTTP Basic Auth [\#599](https://github.com/containous/traefik/issues/599)
|
||||
- Consul KV seem broken [\#587](https://github.com/containous/traefik/issues/587)
|
||||
- HTTPS entryPoint not working [\#574](https://github.com/containous/traefik/issues/574)
|
||||
- Traefik stuck when used as frontend for a streaming API [\#560](https://github.com/containous/traefik/issues/560)
|
||||
- Exclude some frontends in consul catalog [\#555](https://github.com/containous/traefik/issues/555)
|
||||
- Update docs with new Mesos provider [\#548](https://github.com/containous/traefik/issues/548)
|
||||
- Can I use Traefik without a domain name? [\#539](https://github.com/containous/traefik/issues/539)
|
||||
- docker run syntax in swarm example has changed [\#528](https://github.com/containous/traefik/issues/528)
|
||||
- Priortities in 1.0.0 not behaving [\#506](https://github.com/containous/traefik/issues/506)
|
||||
- Route by path [\#500](https://github.com/containous/traefik/issues/500)
|
||||
- Secure WebSockets [\#467](https://github.com/containous/traefik/issues/467)
|
||||
- Container IP Lost [\#375](https://github.com/containous/traefik/issues/375)
|
||||
- Multiple routes support with Docker or Marathon labels [\#118](https://github.com/containous/traefik/issues/118)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix path case sensitive v1.1 [\#855](https://github.com/containous/traefik/pull/855) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix golint in v1.1 [\#849](https://github.com/containous/traefik/pull/849) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix Kubernetes watch leak [\#845](https://github.com/containous/traefik/pull/845) ([emilevauge](https://github.com/emilevauge))
|
||||
- Pass Version, Codename and Date to crosscompiled [\#842](https://github.com/containous/traefik/pull/842) ([guilhem](https://github.com/guilhem))
|
||||
- Add Nvd3 Dependency to fix UI / Dashboard [\#829](https://github.com/containous/traefik/pull/829) ([SantoDE](https://github.com/SantoDE))
|
||||
- Fix mkdoc theme [\#823](https://github.com/containous/traefik/pull/823) ([emilevauge](https://github.com/emilevauge))
|
||||
- Prepare release v1.1.0 rc4 [\#822](https://github.com/containous/traefik/pull/822) ([emilevauge](https://github.com/emilevauge))
|
||||
- Check that we serve HTTP/2 [\#820](https://github.com/containous/traefik/pull/820) ([trecloux](https://github.com/trecloux))
|
||||
- Fix multiple issues [\#814](https://github.com/containous/traefik/pull/814) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix ACME renew & add version check [\#783](https://github.com/containous/traefik/pull/783) ([emilevauge](https://github.com/emilevauge))
|
||||
- Use first port by default [\#782](https://github.com/containous/traefik/pull/782) ([guilhem](https://github.com/guilhem))
|
||||
- Prepare release v1.1.0-rc3 [\#779](https://github.com/containous/traefik/pull/779) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix ResponseRecorder Flush [\#776](https://github.com/containous/traefik/pull/776) ([emilevauge](https://github.com/emilevauge))
|
||||
- Use sdnotify for systemd [\#768](https://github.com/containous/traefik/pull/768) ([guilhem](https://github.com/guilhem))
|
||||
- Fix providers throttle duration doc [\#760](https://github.com/containous/traefik/pull/760) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix mapstructure issue with anonymous slice [\#759](https://github.com/containous/traefik/pull/759) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix multiple certificates using flag [\#758](https://github.com/containous/traefik/pull/758) ([emilevauge](https://github.com/emilevauge))
|
||||
- Really fix deploy ghr... [\#748](https://github.com/containous/traefik/pull/748) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fixes deploy ghr [\#742](https://github.com/containous/traefik/pull/742) ([emilevauge](https://github.com/emilevauge))
|
||||
- prepare v1.1.0-rc2 [\#740](https://github.com/containous/traefik/pull/740) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix case sensitive host [\#733](https://github.com/containous/traefik/pull/733) ([emilevauge](https://github.com/emilevauge))
|
||||
- Update Kubernetes examples [\#731](https://github.com/containous/traefik/pull/731) ([Starefossen](https://github.com/Starefossen))
|
||||
- fIx marathon template with dots in ID [\#728](https://github.com/containous/traefik/pull/728) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix networkMap construction in ListServices [\#724](https://github.com/containous/traefik/pull/724) ([vincentlepot](https://github.com/vincentlepot))
|
||||
- Add basic compatibility with marathon-lb [\#720](https://github.com/containous/traefik/pull/720) ([guilhem](https://github.com/guilhem))
|
||||
- Add Ed's video at ContainerCamp [\#717](https://github.com/containous/traefik/pull/717) ([emilevauge](https://github.com/emilevauge))
|
||||
- Add documentation for Træfik on docker swarm mode [\#715](https://github.com/containous/traefik/pull/715) ([vdemeester](https://github.com/vdemeester))
|
||||
- Remove duplicated link to Kubernetes.io in README.md [\#713](https://github.com/containous/traefik/pull/713) ([oscerd](https://github.com/oscerd))
|
||||
- Show current version in web UI [\#709](https://github.com/containous/traefik/pull/709) ([vhf](https://github.com/vhf))
|
||||
- Add support for docker healthcheck 👼 [\#708](https://github.com/containous/traefik/pull/708) ([vdemeester](https://github.com/vdemeester))
|
||||
- Fix syntax in Swarm example. Resolves \#528 [\#707](https://github.com/containous/traefik/pull/707) ([billglover](https://github.com/billglover))
|
||||
- Add HTTP compression [\#702](https://github.com/containous/traefik/pull/702) ([tuier](https://github.com/tuier))
|
||||
- Carry PR 446 - Add sticky session support \(round two!\) [\#701](https://github.com/containous/traefik/pull/701) ([emilevauge](https://github.com/emilevauge))
|
||||
- Remove unused endpoint when using constraints with Marathon provider [\#697](https://github.com/containous/traefik/pull/697) ([tuier](https://github.com/tuier))
|
||||
- Replace imagelayers.io with microbadger [\#696](https://github.com/containous/traefik/pull/696) ([solidnerd](https://github.com/solidnerd))
|
||||
- Selectable TLS Versions [\#690](https://github.com/containous/traefik/pull/690) ([dtomcej](https://github.com/dtomcej))
|
||||
- Carry pr 439 [\#689](https://github.com/containous/traefik/pull/689) ([emilevauge](https://github.com/emilevauge))
|
||||
- Disable gorilla/mux URL cleaning to prevent sending redirect [\#688](https://github.com/containous/traefik/pull/688) ([ydubreuil](https://github.com/ydubreuil))
|
||||
- Some fixes [\#687](https://github.com/containous/traefik/pull/687) ([emilevauge](https://github.com/emilevauge))
|
||||
- feat\(constraints\): Supports constraints for Marathon provider [\#686](https://github.com/containous/traefik/pull/686) ([tuier](https://github.com/tuier))
|
||||
- Update docs to improve contribution setup [\#685](https://github.com/containous/traefik/pull/685) ([dtomcej](https://github.com/dtomcej))
|
||||
- Add basic auth support for web backend [\#677](https://github.com/containous/traefik/pull/677) ([SantoDE](https://github.com/SantoDE))
|
||||
- Document accepted values for logLevel. [\#676](https://github.com/containous/traefik/pull/676) ([jimmycuadra](https://github.com/jimmycuadra))
|
||||
- If Marathon doesn't have healthcheck, assume it's ok [\#665](https://github.com/containous/traefik/pull/665) ([gomes](https://github.com/gomes))
|
||||
- ACME: renew certificates 30 days before expiry [\#660](https://github.com/containous/traefik/pull/660) ([JayH5](https://github.com/JayH5))
|
||||
- Update broken link and add a comment to sample config file [\#658](https://github.com/containous/traefik/pull/658) ([Yggdrasil](https://github.com/Yggdrasil))
|
||||
- Add possibility to use BindPort IPAddress 👼 [\#657](https://github.com/containous/traefik/pull/657) ([vdemeester](https://github.com/vdemeester))
|
||||
- Update marathon [\#648](https://github.com/containous/traefik/pull/648) ([emilevauge](https://github.com/emilevauge))
|
||||
- Add backend features to docker [\#646](https://github.com/containous/traefik/pull/646) ([jangie](https://github.com/jangie))
|
||||
- enable consul catalog to use maxconn [\#645](https://github.com/containous/traefik/pull/645) ([jangie](https://github.com/jangie))
|
||||
- Adopt the Code Of Coduct from http://contributor-covenant.org [\#641](https://github.com/containous/traefik/pull/641) ([errm](https://github.com/errm))
|
||||
- Use secure mode 600 instead of 644 for acme.json [\#639](https://github.com/containous/traefik/pull/639) ([discordianfish](https://github.com/discordianfish))
|
||||
- docker clarification, fix dead urls, misc typos [\#637](https://github.com/containous/traefik/pull/637) ([djalal](https://github.com/djalal))
|
||||
- add PING handler to dashboard API [\#630](https://github.com/containous/traefik/pull/630) ([jangie](https://github.com/jangie))
|
||||
- Migrate to JobBackOff [\#628](https://github.com/containous/traefik/pull/628) ([emilevauge](https://github.com/emilevauge))
|
||||
- Add long job exponential backoff [\#627](https://github.com/containous/traefik/pull/627) ([emilevauge](https://github.com/emilevauge))
|
||||
- HA acme support [\#625](https://github.com/containous/traefik/pull/625) ([emilevauge](https://github.com/emilevauge))
|
||||
- Bump go v1.7 [\#620](https://github.com/containous/traefik/pull/620) ([emilevauge](https://github.com/emilevauge))
|
||||
- Make duration logging consistent [\#619](https://github.com/containous/traefik/pull/619) ([jangie](https://github.com/jangie))
|
||||
- fix for nil clientTLS causing issue [\#617](https://github.com/containous/traefik/pull/617) ([jangie](https://github.com/jangie))
|
||||
- Add ability for marathon provider to set maxconn values, loadbalancer algorithm, and circuit breaker expression [\#616](https://github.com/containous/traefik/pull/616) ([jangie](https://github.com/jangie))
|
||||
- Make systemd unit installable [\#613](https://github.com/containous/traefik/pull/613) ([keis](https://github.com/keis))
|
||||
- Merge v1.0.2 master [\#610](https://github.com/containous/traefik/pull/610) ([emilevauge](https://github.com/emilevauge))
|
||||
- update staert and flaeg [\#609](https://github.com/containous/traefik/pull/609) ([cocap10](https://github.com/cocap10))
|
||||
- \#504 Initial support for Docker 1.12 Swarm Mode [\#602](https://github.com/containous/traefik/pull/602) ([diegofernandes](https://github.com/diegofernandes))
|
||||
- Add Host cert ACME generation [\#601](https://github.com/containous/traefik/pull/601) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fixed binary script so traefik version command doesn't just print default values [\#598](https://github.com/containous/traefik/pull/598) ([keiths-osc](https://github.com/keiths-osc))
|
||||
- Name servers after thier pods [\#596](https://github.com/containous/traefik/pull/596) ([errm](https://github.com/errm))
|
||||
- Fix Consul prefix [\#589](https://github.com/containous/traefik/pull/589) ([jippi](https://github.com/jippi))
|
||||
- Prioritize kubernetes routes by path length [\#588](https://github.com/containous/traefik/pull/588) ([philk](https://github.com/philk))
|
||||
- beautify help [\#580](https://github.com/containous/traefik/pull/580) ([cocap10](https://github.com/cocap10))
|
||||
- Upgrade directives name since we use angular-ui-bootstrap [\#578](https://github.com/containous/traefik/pull/578) ([micaelmbagira](https://github.com/micaelmbagira))
|
||||
- Fix basic docs for configuration of multiple rules [\#576](https://github.com/containous/traefik/pull/576) ([ajaegle](https://github.com/ajaegle))
|
||||
- Fix k8s watch [\#573](https://github.com/containous/traefik/pull/573) ([errm](https://github.com/errm))
|
||||
- Add requirements.txt for netlify [\#567](https://github.com/containous/traefik/pull/567) ([emilevauge](https://github.com/emilevauge))
|
||||
- Merge v1.0.1 master [\#565](https://github.com/containous/traefik/pull/565) ([emilevauge](https://github.com/emilevauge))
|
||||
- Move webui to FountainJS with Webpack [\#558](https://github.com/containous/traefik/pull/558) ([micaelmbagira](https://github.com/micaelmbagira))
|
||||
- Add global InsecureSkipVerify option to disable certificate checking [\#557](https://github.com/containous/traefik/pull/557) ([stuart-c](https://github.com/stuart-c))
|
||||
- Move version.go in its own package… [\#553](https://github.com/containous/traefik/pull/553) ([vdemeester](https://github.com/vdemeester))
|
||||
- Upgrade libkermit and dependencies [\#552](https://github.com/containous/traefik/pull/552) ([vdemeester](https://github.com/vdemeester))
|
||||
- Add command storeconfig [\#551](https://github.com/containous/traefik/pull/551) ([cocap10](https://github.com/cocap10))
|
||||
- Add basic/digest auth [\#547](https://github.com/containous/traefik/pull/547) ([emilevauge](https://github.com/emilevauge))
|
||||
- Bump node to 6 for webui [\#546](https://github.com/containous/traefik/pull/546) ([vdemeester](https://github.com/vdemeester))
|
||||
- Bump golang to 1.6.3 [\#545](https://github.com/containous/traefik/pull/545) ([vdemeester](https://github.com/vdemeester))
|
||||
- Fix typos [\#538](https://github.com/containous/traefik/pull/538) ([jimt](https://github.com/jimt))
|
||||
- Kubernetes user-guide [\#519](https://github.com/containous/traefik/pull/519) ([errm](https://github.com/errm))
|
||||
- Implement Kubernetes Selectors, minor kube endpoint fix [\#516](https://github.com/containous/traefik/pull/516) ([pnegahdar](https://github.com/pnegahdar))
|
||||
- Carry \#358 : Option to disable expose of all docker containers [\#514](https://github.com/containous/traefik/pull/514) ([vdemeester](https://github.com/vdemeester))
|
||||
- Remove traefik.frontend.value support in docker… [\#510](https://github.com/containous/traefik/pull/510) ([vdemeester](https://github.com/vdemeester))
|
||||
- Use KvStores as global config sources [\#481](https://github.com/containous/traefik/pull/481) ([cocap10](https://github.com/cocap10))
|
||||
- Add endpoint option to authenticate by client tls cert. [\#461](https://github.com/containous/traefik/pull/461) ([andersbetner](https://github.com/andersbetner))
|
||||
- add mesos provider inspired by mesos-dns & marathon provider [\#353](https://github.com/containous/traefik/pull/353) ([skydjol](https://github.com/skydjol))
|
||||
|
||||
## [v1.1.0-rc4](https://github.com/containous/traefik/tree/v1.1.0-rc4) (2016-11-10)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.1.0-rc3...v1.1.0-rc4)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Feature Request: Enable Health checks to containers. [\#540](https://github.com/containous/traefik/issues/540)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Traefik stopped serving on upgrade to v1.1.0-rc3 [\#807](https://github.com/containous/traefik/issues/807)
|
||||
- Traefik cannot read constraints from KV [\#794](https://github.com/containous/traefik/issues/794)
|
||||
- HTTP2 - configuration [\#790](https://github.com/containous/traefik/issues/790)
|
||||
- Allow multiple certificates on a single entrypoint when trying to use TLS? [\#747](https://github.com/containous/traefik/issues/747)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- LoadBalancing doesn't work in single node Swarm-mode [\#815](https://github.com/containous/traefik/issues/815)
|
||||
- cannot connect to docker daemon [\#813](https://github.com/containous/traefik/issues/813)
|
||||
- Let's encrypt configuration not working [\#805](https://github.com/containous/traefik/issues/805)
|
||||
- Question: Wildcard Host for Kubernetes Ingress [\#792](https://github.com/containous/traefik/issues/792)
|
||||
- Multiple subdomains for Marathon backend. [\#785](https://github.com/containous/traefik/issues/785)
|
||||
- traefik-1.1.0-rc1: build error [\#781](https://github.com/containous/traefik/issues/781)
|
||||
- Multiple routes support with Docker or Marathon labels [\#118](https://github.com/containous/traefik/issues/118)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Prepare release v1.1.0 rc4 [\#822](https://github.com/containous/traefik/pull/822) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix multiple issues [\#814](https://github.com/containous/traefik/pull/814) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix ACME renew & add version check [\#783](https://github.com/containous/traefik/pull/783) ([emilevauge](https://github.com/emilevauge))
|
||||
- Use first port by default [\#782](https://github.com/containous/traefik/pull/782) ([guilhem](https://github.com/guilhem))
|
||||
|
||||
## [v1.1.0-rc3](https://github.com/containous/traefik/tree/v1.1.0-rc3) (2016-10-26)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.1.0-rc2...v1.1.0-rc3)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Cannot provide multiple certificates using flag [\#757](https://github.com/containous/traefik/issues/757)
|
||||
- traefik \* Users: unsupported type: slice [\#743](https://github.com/containous/traefik/issues/743)
|
||||
- \[Docker swarm mode\] The traefik.docker.network seems to have no effect [\#719](https://github.com/containous/traefik/issues/719)
|
||||
- Case sensitive domain names breaks routing [\#562](https://github.com/containous/traefik/issues/562)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- dependencies installation error [\#755](https://github.com/containous/traefik/issues/755)
|
||||
- k8s provider w/ acme? [\#752](https://github.com/containous/traefik/issues/752)
|
||||
- Documented ProvidersThrottleDuration value is invalid [\#741](https://github.com/containous/traefik/issues/741)
|
||||
- Loadbalaning issues with traefik and Docker Swarm cluster [\#730](https://github.com/containous/traefik/issues/730)
|
||||
- issues with marathon app ids containing a dot [\#726](https://github.com/containous/traefik/issues/726)
|
||||
- How Routing traffic depending on path not domain in docker [\#706](https://github.com/containous/traefik/issues/706)
|
||||
- Traefik crashes when using Consul catalog [\#699](https://github.com/containous/traefik/issues/699)
|
||||
- File Watcher for rules does not work [\#683](https://github.com/containous/traefik/issues/683)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix ResponseRecorder Flush [\#776](https://github.com/containous/traefik/pull/776) ([emilevauge](https://github.com/emilevauge))
|
||||
- Use sdnotify for systemd [\#768](https://github.com/containous/traefik/pull/768) ([guilhem](https://github.com/guilhem))
|
||||
- Fix providers throttle duration doc [\#760](https://github.com/containous/traefik/pull/760) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix mapstructure issue with anonymous slice [\#759](https://github.com/containous/traefik/pull/759) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix multiple certificates using flag [\#758](https://github.com/containous/traefik/pull/758) ([emilevauge](https://github.com/emilevauge))
|
||||
- Really fix deploy ghr... [\#748](https://github.com/containous/traefik/pull/748) ([emilevauge](https://github.com/emilevauge))
|
||||
|
||||
## [v1.1.0-rc2](https://github.com/containous/traefik/tree/v1.1.0-rc2) (2016-10-17)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.1.0-rc1...v1.1.0-rc2)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Support healthcheck if present for docker [\#666](https://github.com/containous/traefik/issues/666)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Sensible configuration for consulCatalog [\#737](https://github.com/containous/traefik/issues/737)
|
||||
- Traefik ignoring container listening in more than one TCP port [\#734](https://github.com/containous/traefik/issues/734)
|
||||
- Error when using HA acme in kubernetes with etcd [\#725](https://github.com/containous/traefik/issues/725)
|
||||
- \[Docker swarm mode\] No round robin when using service [\#718](https://github.com/containous/traefik/issues/718)
|
||||
- Dose it support docker swarm mode [\#712](https://github.com/containous/traefik/issues/712)
|
||||
- Kubernetes - Undefined backend [\#710](https://github.com/containous/traefik/issues/710)
|
||||
- Constraints on Consul Catalogue not working as expected [\#703](https://github.com/containous/traefik/issues/703)
|
||||
- docker run syntax in swarm example has changed [\#528](https://github.com/containous/traefik/issues/528)
|
||||
- Secure WebSockets [\#467](https://github.com/containous/traefik/issues/467)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix case sensitive host [\#733](https://github.com/containous/traefik/pull/733) ([emilevauge](https://github.com/emilevauge))
|
||||
- Update Kubernetes examples [\#731](https://github.com/containous/traefik/pull/731) ([Starefossen](https://github.com/Starefossen))
|
||||
- fIx marathon template with dots in ID [\#728](https://github.com/containous/traefik/pull/728) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix networkMap construction in ListServices [\#724](https://github.com/containous/traefik/pull/724) ([vincentlepot](https://github.com/vincentlepot))
|
||||
- Add basic compatibility with marathon-lb [\#720](https://github.com/containous/traefik/pull/720) ([guilhem](https://github.com/guilhem))
|
||||
- Add Ed's video at ContainerCamp [\#717](https://github.com/containous/traefik/pull/717) ([emilevauge](https://github.com/emilevauge))
|
||||
- Add documentation for Træfik on docker swarm mode [\#715](https://github.com/containous/traefik/pull/715) ([vdemeester](https://github.com/vdemeester))
|
||||
- Remove duplicated link to Kubernetes.io in README.md [\#713](https://github.com/containous/traefik/pull/713) ([oscerd](https://github.com/oscerd))
|
||||
- Show current version in web UI [\#709](https://github.com/containous/traefik/pull/709) ([vhf](https://github.com/vhf))
|
||||
- Add support for docker healthcheck 👼 [\#708](https://github.com/containous/traefik/pull/708) ([vdemeester](https://github.com/vdemeester))
|
||||
- Fix syntax in Swarm example. Resolves \#528 [\#707](https://github.com/containous/traefik/pull/707) ([billglover](https://github.com/billglover))
|
||||
|
||||
## [v1.1.0-rc1](https://github.com/containous/traefik/tree/v1.1.0-rc1) (2016-09-30)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0...v1.1.0-rc1)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Feature Request: SSL Cipher Selection [\#535](https://github.com/containous/traefik/issues/535)
|
||||
- Error with -consulcatalog and missing load balance method on 1.0.0 [\#524](https://github.com/containous/traefik/issues/524)
|
||||
- Running Traefik with Docker 1.12 Swarm Mode [\#504](https://github.com/containous/traefik/issues/504)
|
||||
- Kubernetes provider: should allow the master url to be override [\#501](https://github.com/containous/traefik/issues/501)
|
||||
- \[FRONTEND\]\[LE\] Pre-generate SSL certificates for "Host:" rules [\#483](https://github.com/containous/traefik/issues/483)
|
||||
- Frontend Rule evolution [\#437](https://github.com/containous/traefik/issues/437)
|
||||
- Add a Changelog [\#388](https://github.com/containous/traefik/issues/388)
|
||||
- Add label matching for kubernetes ingests [\#363](https://github.com/containous/traefik/issues/363)
|
||||
- Acme in HA Traefik Scenario [\#348](https://github.com/containous/traefik/issues/348)
|
||||
- HTTP Basic Auth support [\#77](https://github.com/containous/traefik/issues/77)
|
||||
- Session affinity / stickiness / persistence [\#5](https://github.com/containous/traefik/issues/5)
|
||||
- Kubernetes provider: traefik.frontend.rule.type logging [\#668](https://github.com/containous/traefik/pull/668) ([yvespp](https://github.com/yvespp))
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- traefik hangs - stops handling requests [\#662](https://github.com/containous/traefik/issues/662)
|
||||
- Add long jobs in exponential backoff providers [\#626](https://github.com/containous/traefik/issues/626)
|
||||
- Tip of tree crashes on invalid pointer on Marathon provider [\#624](https://github.com/containous/traefik/issues/624)
|
||||
- ACME: revoke certificate on agreement update [\#579](https://github.com/containous/traefik/issues/579)
|
||||
- WebUI: Providers tabs disappeared [\#577](https://github.com/containous/traefik/issues/577)
|
||||
- traefik version command contains incorrect information when building from master branch [\#569](https://github.com/containous/traefik/issues/569)
|
||||
- Flag --etcd.endpoint default [\#508](https://github.com/containous/traefik/issues/508)
|
||||
- Conditional ACME on demand generation [\#505](https://github.com/containous/traefik/issues/505)
|
||||
- Important delay with streams \(Mozilla EventSource\) [\#503](https://github.com/containous/traefik/issues/503)
|
||||
- Traefik crashing [\#458](https://github.com/containous/traefik/issues/458)
|
||||
- traefik.toml constraints error: `Expected map but found 'string'.` [\#451](https://github.com/containous/traefik/issues/451)
|
||||
- Multiple path separators in the url path causing redirect [\#167](https://github.com/containous/traefik/issues/167)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Global InsecureSkipVerify does not work [\#700](https://github.com/containous/traefik/issues/700)
|
||||
- \[documentation/feature\] Consul/etcd support atomic multiple key changes now [\#698](https://github.com/containous/traefik/issues/698)
|
||||
- How to configure which network to use when starting traefik binary? [\#694](https://github.com/containous/traefik/issues/694)
|
||||
- How to get multiple host headers working for docker labels? [\#692](https://github.com/containous/traefik/issues/692)
|
||||
- Requests with URL-encoded characters are not forwarded correctly [\#684](https://github.com/containous/traefik/issues/684)
|
||||
- Issue with global InsecureSkipVerify = true and self signed certificates [\#667](https://github.com/containous/traefik/issues/667)
|
||||
- Docker exposedbydefault = false didn't work [\#663](https://github.com/containous/traefik/issues/663)
|
||||
- \[ACME\] Auto SAN Detection [\#655](https://github.com/containous/traefik/issues/655)
|
||||
- Fronting a domain with DNS A-record round-robin & ACME [\#654](https://github.com/containous/traefik/issues/654)
|
||||
- Overriding toml configuration with environment variables [\#650](https://github.com/containous/traefik/issues/650)
|
||||
- marathon provider exposedByDefault = false [\#647](https://github.com/containous/traefik/issues/647)
|
||||
- Add status URL for service up checks [\#642](https://github.com/containous/traefik/issues/642)
|
||||
- acme's storage file, containing private key, is word readable [\#638](https://github.com/containous/traefik/issues/638)
|
||||
- wildcard domain with exclusions [\#633](https://github.com/containous/traefik/issues/633)
|
||||
- Enable evenly distribution among backend [\#631](https://github.com/containous/traefik/issues/631)
|
||||
- Traefik sporadically failing when proxying requests [\#615](https://github.com/containous/traefik/issues/615)
|
||||
- TCP Proxy [\#608](https://github.com/containous/traefik/issues/608)
|
||||
- How to use in Windows? [\#605](https://github.com/containous/traefik/issues/605)
|
||||
- `ClientCAFiles` ignored [\#604](https://github.com/containous/traefik/issues/604)
|
||||
- Let`s Encrypt enable in etcd [\#600](https://github.com/containous/traefik/issues/600)
|
||||
- Support HTTP Basic Auth [\#599](https://github.com/containous/traefik/issues/599)
|
||||
- Consul KV seem broken [\#587](https://github.com/containous/traefik/issues/587)
|
||||
- HTTPS entryPoint not working [\#574](https://github.com/containous/traefik/issues/574)
|
||||
- Traefik stuck when used as frontend for a streaming API [\#560](https://github.com/containous/traefik/issues/560)
|
||||
- Exclude some frontends in consul catalog [\#555](https://github.com/containous/traefik/issues/555)
|
||||
- Can I use Traefik without a domain name? [\#539](https://github.com/containous/traefik/issues/539)
|
||||
- Priortities in 1.0.0 not behaving [\#506](https://github.com/containous/traefik/issues/506)
|
||||
- Route by path [\#500](https://github.com/containous/traefik/issues/500)
|
||||
- Container IP Lost [\#375](https://github.com/containous/traefik/issues/375)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Add HTTP compression [\#702](https://github.com/containous/traefik/pull/702) ([tuier](https://github.com/tuier))
|
||||
- Carry PR 446 - Add sticky session support \(round two!\) [\#701](https://github.com/containous/traefik/pull/701) ([emilevauge](https://github.com/emilevauge))
|
||||
- Remove unused endpoint when using constraints with Marathon provider [\#697](https://github.com/containous/traefik/pull/697) ([tuier](https://github.com/tuier))
|
||||
- Replace imagelayers.io with microbadger [\#696](https://github.com/containous/traefik/pull/696) ([solidnerd](https://github.com/solidnerd))
|
||||
- Selectable TLS Versions [\#690](https://github.com/containous/traefik/pull/690) ([dtomcej](https://github.com/dtomcej))
|
||||
- Carry pr 439 [\#689](https://github.com/containous/traefik/pull/689) ([emilevauge](https://github.com/emilevauge))
|
||||
- Disable gorilla/mux URL cleaning to prevent sending redirect [\#688](https://github.com/containous/traefik/pull/688) ([ydubreuil](https://github.com/ydubreuil))
|
||||
- Some fixes [\#687](https://github.com/containous/traefik/pull/687) ([emilevauge](https://github.com/emilevauge))
|
||||
- feat\(constraints\): Supports constraints for Marathon provider [\#686](https://github.com/containous/traefik/pull/686) ([tuier](https://github.com/tuier))
|
||||
- Update docs to improve contribution setup [\#685](https://github.com/containous/traefik/pull/685) ([dtomcej](https://github.com/dtomcej))
|
||||
- Add basic auth support for web backend [\#677](https://github.com/containous/traefik/pull/677) ([SantoDE](https://github.com/SantoDE))
|
||||
- Document accepted values for logLevel. [\#676](https://github.com/containous/traefik/pull/676) ([jimmycuadra](https://github.com/jimmycuadra))
|
||||
- If Marathon doesn't have healthcheck, assume it's ok [\#665](https://github.com/containous/traefik/pull/665) ([gomes](https://github.com/gomes))
|
||||
- ACME: renew certificates 30 days before expiry [\#660](https://github.com/containous/traefik/pull/660) ([JayH5](https://github.com/JayH5))
|
||||
- Update broken link and add a comment to sample config file [\#658](https://github.com/containous/traefik/pull/658) ([Yggdrasil](https://github.com/Yggdrasil))
|
||||
- Add possibility to use BindPort IPAddress 👼 [\#657](https://github.com/containous/traefik/pull/657) ([vdemeester](https://github.com/vdemeester))
|
||||
- Update marathon [\#648](https://github.com/containous/traefik/pull/648) ([emilevauge](https://github.com/emilevauge))
|
||||
- Add backend features to docker [\#646](https://github.com/containous/traefik/pull/646) ([jangie](https://github.com/jangie))
|
||||
- enable consul catalog to use maxconn [\#645](https://github.com/containous/traefik/pull/645) ([jangie](https://github.com/jangie))
|
||||
- Adopt the Code Of Coduct from http://contributor-covenant.org [\#641](https://github.com/containous/traefik/pull/641) ([errm](https://github.com/errm))
|
||||
- Use secure mode 600 instead of 644 for acme.json [\#639](https://github.com/containous/traefik/pull/639) ([discordianfish](https://github.com/discordianfish))
|
||||
- docker clarification, fix dead urls, misc typos [\#637](https://github.com/containous/traefik/pull/637) ([djalal](https://github.com/djalal))
|
||||
- add PING handler to dashboard API [\#630](https://github.com/containous/traefik/pull/630) ([jangie](https://github.com/jangie))
|
||||
- Migrate to JobBackOff [\#628](https://github.com/containous/traefik/pull/628) ([emilevauge](https://github.com/emilevauge))
|
||||
- Add long job exponential backoff [\#627](https://github.com/containous/traefik/pull/627) ([emilevauge](https://github.com/emilevauge))
|
||||
- HA acme support [\#625](https://github.com/containous/traefik/pull/625) ([emilevauge](https://github.com/emilevauge))
|
||||
- Bump go v1.7 [\#620](https://github.com/containous/traefik/pull/620) ([emilevauge](https://github.com/emilevauge))
|
||||
- Make duration logging consistent [\#619](https://github.com/containous/traefik/pull/619) ([jangie](https://github.com/jangie))
|
||||
- fix for nil clientTLS causing issue [\#617](https://github.com/containous/traefik/pull/617) ([jangie](https://github.com/jangie))
|
||||
- Add ability for marathon provider to set maxconn values, loadbalancer algorithm, and circuit breaker expression [\#616](https://github.com/containous/traefik/pull/616) ([jangie](https://github.com/jangie))
|
||||
- Make systemd unit installable [\#613](https://github.com/containous/traefik/pull/613) ([keis](https://github.com/keis))
|
||||
- Merge v1.0.2 master [\#610](https://github.com/containous/traefik/pull/610) ([emilevauge](https://github.com/emilevauge))
|
||||
- update staert and flaeg [\#609](https://github.com/containous/traefik/pull/609) ([cocap10](https://github.com/cocap10))
|
||||
- \#504 Initial support for Docker 1.12 Swarm Mode [\#602](https://github.com/containous/traefik/pull/602) ([diegofernandes](https://github.com/diegofernandes))
|
||||
- Add Host cert ACME generation [\#601](https://github.com/containous/traefik/pull/601) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fixed binary script so traefik version command doesn't just print default values [\#598](https://github.com/containous/traefik/pull/598) ([keiths-osc](https://github.com/keiths-osc))
|
||||
- Name servers after thier pods [\#596](https://github.com/containous/traefik/pull/596) ([errm](https://github.com/errm))
|
||||
- Fix Consul prefix [\#589](https://github.com/containous/traefik/pull/589) ([jippi](https://github.com/jippi))
|
||||
- Prioritize kubernetes routes by path length [\#588](https://github.com/containous/traefik/pull/588) ([philk](https://github.com/philk))
|
||||
- beautify help [\#580](https://github.com/containous/traefik/pull/580) ([cocap10](https://github.com/cocap10))
|
||||
- Upgrade directives name since we use angular-ui-bootstrap [\#578](https://github.com/containous/traefik/pull/578) ([micaelmbagira](https://github.com/micaelmbagira))
|
||||
- Fix basic docs for configuration of multiple rules [\#576](https://github.com/containous/traefik/pull/576) ([ajaegle](https://github.com/ajaegle))
|
||||
- Fix k8s watch [\#573](https://github.com/containous/traefik/pull/573) ([errm](https://github.com/errm))
|
||||
- Add requirements.txt for netlify [\#567](https://github.com/containous/traefik/pull/567) ([emilevauge](https://github.com/emilevauge))
|
||||
- Merge v1.0.1 master [\#565](https://github.com/containous/traefik/pull/565) ([emilevauge](https://github.com/emilevauge))
|
||||
- Move webui to FountainJS with Webpack [\#558](https://github.com/containous/traefik/pull/558) ([micaelmbagira](https://github.com/micaelmbagira))
|
||||
- Add global InsecureSkipVerify option to disable certificate checking [\#557](https://github.com/containous/traefik/pull/557) ([stuart-c](https://github.com/stuart-c))
|
||||
- Move version.go in its own package… [\#553](https://github.com/containous/traefik/pull/553) ([vdemeester](https://github.com/vdemeester))
|
||||
- Upgrade libkermit and dependencies [\#552](https://github.com/containous/traefik/pull/552) ([vdemeester](https://github.com/vdemeester))
|
||||
- Add command storeconfig [\#551](https://github.com/containous/traefik/pull/551) ([cocap10](https://github.com/cocap10))
|
||||
- Add basic/digest auth [\#547](https://github.com/containous/traefik/pull/547) ([emilevauge](https://github.com/emilevauge))
|
||||
- Bump node to 6 for webui [\#546](https://github.com/containous/traefik/pull/546) ([vdemeester](https://github.com/vdemeester))
|
||||
- Bump golang to 1.6.3 [\#545](https://github.com/containous/traefik/pull/545) ([vdemeester](https://github.com/vdemeester))
|
||||
- Fix typos [\#538](https://github.com/containous/traefik/pull/538) ([jimt](https://github.com/jimt))
|
||||
- Kubernetes user-guide [\#519](https://github.com/containous/traefik/pull/519) ([errm](https://github.com/errm))
|
||||
- Implement Kubernetes Selectors, minor kube endpoint fix [\#516](https://github.com/containous/traefik/pull/516) ([pnegahdar](https://github.com/pnegahdar))
|
||||
- Carry \#358 : Option to disable expose of all docker containers [\#514](https://github.com/containous/traefik/pull/514) ([vdemeester](https://github.com/vdemeester))
|
||||
- Remove traefik.frontend.value support in docker… [\#510](https://github.com/containous/traefik/pull/510) ([vdemeester](https://github.com/vdemeester))
|
||||
- Use KvStores as global config sources [\#481](https://github.com/containous/traefik/pull/481) ([cocap10](https://github.com/cocap10))
|
||||
- Add endpoint option to authenticate by client tls cert. [\#461](https://github.com/containous/traefik/pull/461) ([andersbetner](https://github.com/andersbetner))
|
||||
- add mesos provider inspired by mesos-dns & marathon provider [\#353](https://github.com/containous/traefik/pull/353) ([skydjol](https://github.com/skydjol))
|
||||
|
||||
## [v1.0.2](https://github.com/containous/traefik/tree/v1.0.2) (2016-08-02)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.1...v1.0.2)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- ACME: revoke certificate on agreement update [\#579](https://github.com/containous/traefik/issues/579)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Exclude some frontends in consul catalog [\#555](https://github.com/containous/traefik/issues/555)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Bump oxy version, fix streaming [\#584](https://github.com/containous/traefik/pull/584) ([emilevauge](https://github.com/emilevauge))
|
||||
- Fix ACME TOS [\#582](https://github.com/containous/traefik/pull/582) ([emilevauge](https://github.com/emilevauge))
|
||||
|
||||
## [v1.0.1](https://github.com/containous/traefik/tree/v1.0.1) (2016-07-19)
|
||||
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0...v1.0.1)
|
||||
|
||||
|
74
CODE_OF_CONDUCT.md
Normal file
74
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at contact@containo.us
|
||||
All complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
2
Makefile
2
Makefile
@@ -73,7 +73,7 @@ run-dev:
|
||||
generate-webui: build-webui
|
||||
if [ ! -d "static" ]; then \
|
||||
mkdir -p static; \
|
||||
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui gulp; \
|
||||
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui npm run build; \
|
||||
echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md; \
|
||||
fi
|
||||
|
||||
|
34
README.md
34
README.md
@@ -6,14 +6,14 @@
|
||||
[](https://travis-ci.org/containous/traefik)
|
||||
[](https://docs.traefik.io)
|
||||
[](http://goreportcard.com/report/containous/traefik)
|
||||
[](https://imagelayers.io/?images=traefik)
|
||||
[](https://microbadger.com/images/traefik)
|
||||
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
||||
[](https://traefik.herokuapp.com)
|
||||
[](https://twitter.com/intent/follow?screen_name=traefikproxy)
|
||||
|
||||
|
||||
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Kubernetes](http://kubernetes.io/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
|
||||
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Kubernetes](http://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Mesos](https://github.com/apache/mesos), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -45,14 +45,14 @@ Run it and forget it!
|
||||
- [It's fast](http://docs.traefik.io/benchmarks)
|
||||
- No dependency hell, single binary made with go
|
||||
- Rest API
|
||||
- Multiple backends supported: Docker, Mesos/Marathon, Consul, Etcd, and more to come
|
||||
- Watchers for backends, can listen change in backends to apply a new configuration automatically
|
||||
- Multiple backends supported: Docker, Swarm, Kubernetes, Marathon, Mesos, Consul, Etcd, and more to come
|
||||
- Watchers for backends, can listen for changes in backends to apply a new configuration automatically
|
||||
- Hot-reloading of configuration. No need to restart the process
|
||||
- Graceful shutdown http connections
|
||||
- Circuit breakers on backends
|
||||
- Round Robin, rebalancer load-balancers
|
||||
- Rest Metrics
|
||||
- [Tiny](https://imagelayers.io/?images=traefik) [official](https://hub.docker.com/r/_/traefik/) docker image included
|
||||
- [Tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image included
|
||||
- SSL backends support
|
||||
- SSL frontend support (with SNI)
|
||||
- Clean AngularJS Web UI
|
||||
@@ -60,12 +60,19 @@ Run it and forget it!
|
||||
- HTTP/2 support
|
||||
- Retry request if network error
|
||||
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS with renewal)
|
||||
- High Availability with cluster mode
|
||||
|
||||
## Demo
|
||||
## Quickstart
|
||||
|
||||
You can have a quick look at Træfɪk in this [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers.
|
||||
|
||||
Here is a talk (in french) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Lets'Encrypt.
|
||||
Here is a talk given by [Ed Robinson](https://github.com/errm) at the [ContainerCamp UK](https://container.camp) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Kubernetes.
|
||||
|
||||
[](https://www.youtube.com/watch?v=aFtpIShV60I)
|
||||
|
||||
Here is a talk (in French) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Let's Encrypt.
|
||||
|
||||
[](http://www.youtube.com/watch?v=QvAz9mVx5TI)
|
||||
|
||||
@@ -84,7 +91,7 @@ You can access to a simple HTML frontend of Træfik.
|
||||
- [Manners](https://github.com/mailgun/manners): graceful shutdown of http.Handler servers
|
||||
- [Lego](https://github.com/xenolf/lego): the best [Let's Encrypt](https://letsencrypt.org) library in go
|
||||
|
||||
## Quick start
|
||||
## Test it
|
||||
|
||||
- The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||
|
||||
@@ -112,10 +119,14 @@ You can find the complete documentation [here](https://docs.traefik.io).
|
||||
|
||||
Please refer to [this section](.github/CONTRIBUTING.md).
|
||||
|
||||
## Code Of Conduct
|
||||
|
||||
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
|
||||
|
||||
## Support
|
||||
|
||||
You can join [](https://traefik.herokuapp.com) to get basic support.
|
||||
If you prefer a commercial support, please contact [containo.us](https://containo.us) by mail: <mailto:support@containo.us>.
|
||||
If you prefer commercial support, please contact [containo.us](https://containo.us) by mail: <mailto:support@containo.us>.
|
||||
|
||||
## Træfɪk here and there
|
||||
|
||||
@@ -148,9 +159,10 @@ Founded in 2014, Asteris creates next-generation infrastructure software for the
|
||||
|
||||
- Emile Vauge [@emilevauge](https://github.com/emilevauge)
|
||||
- Vincent Demeester [@vdemeester](https://github.com/vdemeester)
|
||||
- Samuel Berthe [@samber](https://github.com/samber)
|
||||
- Russell Clare [@Russell-IO](https://github.com/Russell-IO)
|
||||
- Ed Robinson [@errm](https://github.com/errm)
|
||||
- Daniel Tomcej [@dtomcej](https://github.com/dtomcej)
|
||||
- Manuel Laufenberg [@SantoDE](https://github.com/SantoDE)
|
||||
|
||||
## Credits
|
||||
|
||||
|
202
acme/account.go
Normal file
202
acme/account.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Account is used to store lets encrypt registration info
|
||||
type Account struct {
|
||||
Email string
|
||||
Registration *acme.RegistrationResource
|
||||
PrivateKey []byte
|
||||
DomainsCertificate DomainsCertificates
|
||||
ChallengeCerts map[string]*ChallengeCert
|
||||
}
|
||||
|
||||
// ChallengeCert stores a challenge certificate
|
||||
type ChallengeCert struct {
|
||||
Certificate []byte
|
||||
PrivateKey []byte
|
||||
certificate *tls.Certificate
|
||||
}
|
||||
|
||||
// Init inits acccount struct
|
||||
func (a *Account) Init() error {
|
||||
err := a.DomainsCertificate.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, cert := range a.ChallengeCerts {
|
||||
if cert.certificate == nil {
|
||||
certificate, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert.certificate = &certificate
|
||||
}
|
||||
if cert.certificate.Leaf == nil {
|
||||
leaf, err := x509.ParseCertificate(cert.certificate.Certificate[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert.certificate.Leaf = leaf
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewAccount creates an account
|
||||
func NewAccount(email string) (*Account, error) {
|
||||
// Create a user. New accounts need an email and private key to start
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domainsCerts := DomainsCertificates{Certs: []*DomainsCertificate{}}
|
||||
domainsCerts.Init()
|
||||
return &Account{
|
||||
Email: email,
|
||||
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||
DomainsCertificate: DomainsCertificates{Certs: domainsCerts.Certs},
|
||||
ChallengeCerts: map[string]*ChallengeCert{}}, nil
|
||||
}
|
||||
|
||||
// GetEmail returns email
|
||||
func (a *Account) GetEmail() string {
|
||||
return a.Email
|
||||
}
|
||||
|
||||
// GetRegistration returns lets encrypt registration resource
|
||||
func (a *Account) GetRegistration() *acme.RegistrationResource {
|
||||
return a.Registration
|
||||
}
|
||||
|
||||
// GetPrivateKey returns private key
|
||||
func (a *Account) GetPrivateKey() crypto.PrivateKey {
|
||||
if privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey); err == nil {
|
||||
return privateKey
|
||||
}
|
||||
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Certificate is used to store certificate info
|
||||
type Certificate struct {
|
||||
Domain string
|
||||
CertURL string
|
||||
CertStableURL string
|
||||
PrivateKey []byte
|
||||
Certificate []byte
|
||||
}
|
||||
|
||||
// DomainsCertificates stores a certificate for multiple domains
|
||||
type DomainsCertificates struct {
|
||||
Certs []*DomainsCertificate
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// Init inits DomainsCertificates
|
||||
func (dc *DomainsCertificates) Init() error {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain Domain) error {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainsCertificate.Certificate = acmeCert
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("Certificate to renew not found for domain " + domain.Main)
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
|
||||
dc.Certs = append(dc.Certs, &cert)
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
domains := []string{}
|
||||
domains = append(domains, domainsCertificate.Domains.Main)
|
||||
domains = append(domains, domainsCertificate.Domains.SANs...)
|
||||
for _, domain := range domains {
|
||||
if domain == domainToFind {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// DomainsCertificate contains a certificate for multiple domains
|
||||
type DomainsCertificate struct {
|
||||
Domains Domain
|
||||
Certificate *Certificate
|
||||
tlsCert *tls.Certificate
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificate) needRenew() bool {
|
||||
for _, c := range dc.tlsCert.Certificate {
|
||||
crt, err := x509.ParseCertificate(c)
|
||||
if err != nil {
|
||||
// If there's an error, we assume the cert is broken, and needs update
|
||||
return true
|
||||
}
|
||||
// <= 7 days left, renew certificate
|
||||
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 30 * time.Hour))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
623
acme/acme.go
623
acme/acme.go
@@ -1,174 +1,40 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"golang.org/x/net/context"
|
||||
"io/ioutil"
|
||||
fmtlog "log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Account is used to store lets encrypt registration info
|
||||
type Account struct {
|
||||
Email string
|
||||
Registration *acme.RegistrationResource
|
||||
PrivateKey []byte
|
||||
DomainsCertificate DomainsCertificates
|
||||
}
|
||||
|
||||
// GetEmail returns email
|
||||
func (a Account) GetEmail() string {
|
||||
return a.Email
|
||||
}
|
||||
|
||||
// GetRegistration returns lets encrypt registration resource
|
||||
func (a Account) GetRegistration() *acme.RegistrationResource {
|
||||
return a.Registration
|
||||
}
|
||||
|
||||
// GetPrivateKey returns private key
|
||||
func (a Account) GetPrivateKey() crypto.PrivateKey {
|
||||
if privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey); err == nil {
|
||||
return privateKey
|
||||
}
|
||||
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Certificate is used to store certificate info
|
||||
type Certificate struct {
|
||||
Domain string
|
||||
CertURL string
|
||||
CertStableURL string
|
||||
PrivateKey []byte
|
||||
Certificate []byte
|
||||
}
|
||||
|
||||
// DomainsCertificates stores a certificate for multiple domains
|
||||
type DomainsCertificates struct {
|
||||
Certs []*DomainsCertificate
|
||||
lock *sync.RWMutex
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) init() error {
|
||||
if dc.lock == nil {
|
||||
dc.lock = &sync.RWMutex{}
|
||||
}
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain Domain) error {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainsCertificate.Certificate = acmeCert
|
||||
domainsCertificate.tlsCert = &tlsCert
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("Certificate to renew not found for domain " + domain.Main)
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) {
|
||||
dc.lock.Lock()
|
||||
defer dc.lock.Unlock()
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
|
||||
dc.Certs = append(dc.Certs, &cert)
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
domains := []string{}
|
||||
domains = append(domains, domainsCertificate.Domains.Main)
|
||||
domains = append(domains, domainsCertificate.Domains.SANs...)
|
||||
for _, domain := range domains {
|
||||
if domain == domainToFind {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, bool) {
|
||||
dc.lock.RLock()
|
||||
defer dc.lock.RUnlock()
|
||||
for _, domainsCertificate := range dc.Certs {
|
||||
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
|
||||
return domainsCertificate, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// DomainsCertificate contains a certificate for multiple domains
|
||||
type DomainsCertificate struct {
|
||||
Domains Domain
|
||||
Certificate *Certificate
|
||||
tlsCert *tls.Certificate
|
||||
}
|
||||
|
||||
func (dc *DomainsCertificate) needRenew() bool {
|
||||
for _, c := range dc.tlsCert.Certificate {
|
||||
crt, err := x509.ParseCertificate(c)
|
||||
if err != nil {
|
||||
// If there's an error, we assume the cert is broken, and needs update
|
||||
return true
|
||||
}
|
||||
// <= 7 days left, renew certificate
|
||||
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 7 * time.Hour))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ACME allows to connect to lets encrypt and retrieve certs
|
||||
type ACME struct {
|
||||
Email string `description:"Email address used for registration"`
|
||||
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
|
||||
StorageFile string `description:"File used for certificates storage."`
|
||||
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
|
||||
CAServer string `description:"CA server to use."`
|
||||
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
||||
storageLock sync.RWMutex
|
||||
Email string `description:"Email address used for registration"`
|
||||
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
|
||||
Storage string `description:"File or key used for certificates storage."`
|
||||
StorageFile string // deprecated
|
||||
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
|
||||
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
|
||||
CAServer string `description:"CA server to use."`
|
||||
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
||||
client *acme.Client
|
||||
defaultCertificate *tls.Certificate
|
||||
store cluster.Store
|
||||
challengeProvider *challengeProvider
|
||||
checkOnDemandDomain func(domain string) bool
|
||||
}
|
||||
|
||||
//Domains parse []Domain
|
||||
@@ -212,60 +78,191 @@ type Domain struct {
|
||||
SANs []string
|
||||
}
|
||||
|
||||
// CreateConfig creates a tls.config from using ACME configuration
|
||||
func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(domain string) bool) error {
|
||||
func (a *ACME) init() error {
|
||||
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
||||
|
||||
if len(a.StorageFile) == 0 {
|
||||
return errors.New("Empty StorageFile, please provide a filename for certs storage")
|
||||
}
|
||||
|
||||
log.Debugf("Generating default certificate...")
|
||||
if len(tlsConfig.Certificates) == 0 {
|
||||
// no certificates in TLS config, so we add a default one
|
||||
cert, err := generateDefaultCertificate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, *cert)
|
||||
}
|
||||
var account *Account
|
||||
var needRegister bool
|
||||
|
||||
// if certificates in storage, load them
|
||||
if fileInfo, err := os.Stat(a.StorageFile); err == nil && fileInfo.Size() != 0 {
|
||||
log.Infof("Loading ACME certificates...")
|
||||
// load account
|
||||
account, err = a.loadAccount(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Infof("Generating ACME Account...")
|
||||
// Create a user. New accounts need an email and private key to start
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account = &Account{
|
||||
Email: a.Email,
|
||||
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||
}
|
||||
account.DomainsCertificate = DomainsCertificates{Certs: []*DomainsCertificate{}, lock: &sync.RWMutex{}}
|
||||
needRegister = true
|
||||
}
|
||||
|
||||
client, err := a.buildACMEClient(account)
|
||||
// no certificates in TLS config, so we add a default one
|
||||
cert, err := generateDefaultCertificate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.defaultCertificate = cert
|
||||
// TODO: to remove in the futurs
|
||||
if len(a.StorageFile) > 0 && len(a.Storage) == 0 {
|
||||
log.Warnf("ACME.StorageFile is deprecated, use ACME.Storage instead")
|
||||
a.Storage = a.StorageFile
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateClusterConfig creates a tls.config using ACME configuration in cluster mode
|
||||
func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tls.Config, checkOnDemandDomain func(domain string) bool) error {
|
||||
err := a.init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(a.Storage) == 0 {
|
||||
return errors.New("Empty Store, please provide a key for certs storage")
|
||||
}
|
||||
a.checkOnDemandDomain = checkOnDemandDomain
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
|
||||
tlsConfig.GetCertificate = a.getCertificate
|
||||
listener := func(object cluster.Object) error {
|
||||
account := object.(*Account)
|
||||
account.Init()
|
||||
if !leadership.IsLeader() {
|
||||
a.client, err = a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
log.Errorf("Error building ACME client %+v: %s", object, err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
datastore, err := cluster.NewDataStore(
|
||||
leadership.Pool.Ctx(),
|
||||
staert.KvSource{
|
||||
Store: leadership.Store,
|
||||
Prefix: a.Storage,
|
||||
},
|
||||
&Account{},
|
||||
listener)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.store = datastore
|
||||
a.challengeProvider = &challengeProvider{store: a.store}
|
||||
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
leadership.Pool.AddGoCtx(func(ctx context.Context) {
|
||||
log.Infof("Starting ACME renew job...")
|
||||
defer log.Infof("Stopped ACME renew job...")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
if err := a.renewCertificates(); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
leadership.AddListener(func(elected bool) error {
|
||||
if elected {
|
||||
object, err := a.store.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account := object.(*Account)
|
||||
account.Init()
|
||||
var needRegister bool
|
||||
if account == nil || len(account.Email) == 0 {
|
||||
account, err = NewAccount(a.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needRegister = true
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.client, err = a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if needRegister {
|
||||
// New users will need to register; be sure to save it
|
||||
log.Debugf("Register...")
|
||||
reg, err := a.client.Register()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.Registration = reg
|
||||
}
|
||||
// The client has a URL to the current Let's Encrypt Subscriber
|
||||
// Agreement. The user will need to agree to it.
|
||||
log.Debugf("AgreeToTOS...")
|
||||
err = a.client.AgreeToTOS()
|
||||
if err != nil {
|
||||
// Let's Encrypt Subscriber Agreement renew ?
|
||||
reg, err := a.client.QueryRegistration()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.Registration = reg
|
||||
err = a.client.AgreeToTOS()
|
||||
if err != nil {
|
||||
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
|
||||
}
|
||||
}
|
||||
err = transaction.Commit(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
safe.Go(func() {
|
||||
a.retrieveCertificates()
|
||||
if err := a.renewCertificates(); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateLocalConfig creates a tls.config using local ACME configuration
|
||||
func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func(domain string) bool) error {
|
||||
err := a.init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(a.Storage) == 0 {
|
||||
return errors.New("Empty Store, please provide a filename for certs storage")
|
||||
}
|
||||
a.checkOnDemandDomain = checkOnDemandDomain
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
|
||||
tlsConfig.GetCertificate = a.getCertificate
|
||||
|
||||
localStore := NewLocalStore(a.Storage)
|
||||
a.store = localStore
|
||||
a.challengeProvider = &challengeProvider{store: a.store}
|
||||
|
||||
var needRegister bool
|
||||
var account *Account
|
||||
|
||||
if fileInfo, fileErr := os.Stat(a.Storage); fileErr == nil && fileInfo.Size() != 0 {
|
||||
log.Infof("Loading ACME Account...")
|
||||
// load account
|
||||
object, err := localStore.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account = object.(*Account)
|
||||
} else {
|
||||
log.Infof("Generating ACME Account...")
|
||||
account, err = NewAccount(a.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
needRegister = true
|
||||
}
|
||||
|
||||
a.client, err = a.buildACMEClient(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
|
||||
wrapperChallengeProvider := newWrapperChallengeProvider()
|
||||
client.SetChallengeProvider(acme.TLSSNI01, wrapperChallengeProvider)
|
||||
|
||||
if needRegister {
|
||||
// New users will need to register; be sure to save it
|
||||
reg, err := client.Register()
|
||||
log.Infof("Register...")
|
||||
reg, err := a.client.Register()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -274,42 +271,42 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
|
||||
|
||||
// The client has a URL to the current Let's Encrypt Subscriber
|
||||
// Agreement. The user will need to agree to it.
|
||||
err = client.AgreeToTOS()
|
||||
log.Debugf("AgreeToTOS...")
|
||||
err = a.client.AgreeToTOS()
|
||||
if err != nil {
|
||||
// Let's Encrypt Subscriber Agreement renew ?
|
||||
reg, err := a.client.QueryRegistration()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account.Registration = reg
|
||||
err = a.client.AgreeToTOS()
|
||||
if err != nil {
|
||||
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
|
||||
}
|
||||
}
|
||||
// save account
|
||||
transaction, _, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = transaction.Commit(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
safe.Go(func() {
|
||||
a.retrieveCertificates(client, account)
|
||||
if err := a.renewCertificates(client, account); err != nil {
|
||||
a.retrieveCertificates()
|
||||
if err := a.renewCertificates(); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if challengeCert, ok := wrapperChallengeProvider.getCertificate(clientHello.ServerName); ok {
|
||||
return challengeCert, nil
|
||||
}
|
||||
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
||||
return domainCert.tlsCert, nil
|
||||
}
|
||||
if a.OnDemand {
|
||||
if CheckOnDemandDomain != nil && !CheckOnDemandDomain(clientHello.ServerName) {
|
||||
return nil, nil
|
||||
}
|
||||
return a.loadCertificateOnDemand(client, account, clientHello)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
safe.Go(func() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := a.renewCertificates(client, account); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
||||
}
|
||||
for range ticker.C {
|
||||
if err := a.renewCertificates(); err != nil {
|
||||
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,25 +314,54 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACME) retrieveCertificates(client *acme.Client, account *Account) {
|
||||
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
domain := types.CanonicalDomain(clientHello.ServerName)
|
||||
account := a.store.Get().(*Account)
|
||||
if challengeCert, ok := a.challengeProvider.getCertificate(domain); ok {
|
||||
log.Debugf("ACME got challenge %s", domain)
|
||||
return challengeCert, nil
|
||||
}
|
||||
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok {
|
||||
log.Debugf("ACME got domain cert %s", domain)
|
||||
return domainCert.tlsCert, nil
|
||||
}
|
||||
if a.OnDemand {
|
||||
if a.checkOnDemandDomain != nil && !a.checkOnDemandDomain(domain) {
|
||||
return nil, nil
|
||||
}
|
||||
return a.loadCertificateOnDemand(clientHello)
|
||||
}
|
||||
log.Debugf("ACME got nothing %s", domain)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (a *ACME) retrieveCertificates() {
|
||||
log.Infof("Retrieving ACME certificates...")
|
||||
for _, domain := range a.Domains {
|
||||
// check if cert isn't already loaded
|
||||
account := a.store.Get().(*Account)
|
||||
if _, exists := account.DomainsCertificate.exists(domain); !exists {
|
||||
domains := []string{}
|
||||
domains = append(domains, domain.Main)
|
||||
domains = append(domains, domain.SANs...)
|
||||
certificateResource, err := a.getDomainsCertificates(client, domains)
|
||||
certificateResource, err := a.getDomainsCertificates(domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error())
|
||||
continue
|
||||
}
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
log.Errorf("Error creating ACME store transaction from domain %s: %s", domain, err.Error())
|
||||
continue
|
||||
}
|
||||
account = object.(*Account)
|
||||
_, err = account.DomainsCertificate.addCertificateForDomains(certificateResource, domain)
|
||||
if err != nil {
|
||||
log.Errorf("Error adding ACME certificate for domain %s: %s", domains, err.Error())
|
||||
continue
|
||||
}
|
||||
if err = a.saveAccount(account); err != nil {
|
||||
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||
continue
|
||||
}
|
||||
@@ -344,12 +370,13 @@ func (a *ACME) retrieveCertificates(client *acme.Client, account *Account) {
|
||||
log.Infof("Retrieved ACME certificates")
|
||||
}
|
||||
|
||||
func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
|
||||
func (a *ACME) renewCertificates() error {
|
||||
log.Debugf("Testing certificate renew...")
|
||||
account := a.store.Get().(*Account)
|
||||
for _, certificateResource := range account.DomainsCertificate.Certs {
|
||||
if certificateResource.needRenew() {
|
||||
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
|
||||
renewedCert, err := client.RenewCertificate(acme.CertificateResource{
|
||||
renewedCert, err := a.client.RenewCertificate(acme.CertificateResource{
|
||||
Domain: certificateResource.Certificate.Domain,
|
||||
CertURL: certificateResource.Certificate.CertURL,
|
||||
CertStableURL: certificateResource.Certificate.CertStableURL,
|
||||
@@ -368,13 +395,19 @@ func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
|
||||
PrivateKey: renewedCert.PrivateKey,
|
||||
Certificate: renewedCert.Certificate,
|
||||
}
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account = object.(*Account)
|
||||
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error renewing certificate: %v", err)
|
||||
continue
|
||||
}
|
||||
if err = a.saveAccount(account); err != nil {
|
||||
log.Errorf("Error saving ACME account: %v", err)
|
||||
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -382,74 +415,116 @@ func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACME) buildACMEClient(Account *Account) (*acme.Client, error) {
|
||||
func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
|
||||
log.Debugf("Building ACME client...")
|
||||
caServer := "https://acme-v01.api.letsencrypt.org/directory"
|
||||
if len(a.CAServer) > 0 {
|
||||
caServer = a.CAServer
|
||||
}
|
||||
client, err := acme.NewClient(caServer, Account, acme.RSA4096)
|
||||
client, err := acme.NewClient(caServer, account, acme.RSA4096)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
|
||||
err = client.SetChallengeProvider(acme.TLSSNI01, a.challengeProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (a *ACME) loadCertificateOnDemand(client *acme.Client, Account *Account, clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if certificateResource, ok := Account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
||||
func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
domain := types.CanonicalDomain(clientHello.ServerName)
|
||||
account := a.store.Get().(*Account)
|
||||
if certificateResource, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok {
|
||||
return certificateResource.tlsCert, nil
|
||||
}
|
||||
Certificate, err := a.getDomainsCertificates(client, []string{clientHello.ServerName})
|
||||
certificate, err := a.getDomainsCertificates([]string{domain})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Got certificate on demand for domain %s", clientHello.ServerName)
|
||||
cert, err := Account.DomainsCertificate.addCertificateForDomains(Certificate, Domain{Main: clientHello.ServerName})
|
||||
log.Debugf("Got certificate on demand for domain %s", domain)
|
||||
|
||||
transaction, object, err := a.store.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = a.saveAccount(Account); err != nil {
|
||||
account = object.(*Account)
|
||||
cert, err := account.DomainsCertificate.addCertificateForDomains(certificate, Domain{Main: domain})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cert.tlsCert, nil
|
||||
}
|
||||
|
||||
func (a *ACME) loadAccount(acmeConfig *ACME) (*Account, error) {
|
||||
a.storageLock.RLock()
|
||||
defer a.storageLock.RUnlock()
|
||||
Account := Account{
|
||||
DomainsCertificate: DomainsCertificates{},
|
||||
}
|
||||
file, err := ioutil.ReadFile(acmeConfig.StorageFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(file, &Account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = Account.DomainsCertificate.init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("Loaded ACME config from storage %s", acmeConfig.StorageFile)
|
||||
return &Account, nil
|
||||
// LoadCertificateForDomains loads certificates from ACME for given domains
|
||||
func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
||||
safe.Go(func() {
|
||||
operation := func() error {
|
||||
if a.client == nil {
|
||||
return fmt.Errorf("ACME client still not built")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Error getting ACME client: %v, retrying in %s", err, time)
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 30 * time.Second
|
||||
err := backoff.RetryNotify(operation, ebo, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME client: %v", err)
|
||||
return
|
||||
}
|
||||
account := a.store.Get().(*Account)
|
||||
var domain Domain
|
||||
if len(domains) == 0 {
|
||||
// no domain
|
||||
return
|
||||
|
||||
} else if len(domains) > 1 {
|
||||
domain = Domain{Main: domains[0], SANs: domains[1:]}
|
||||
} else {
|
||||
domain = Domain{Main: domains[0]}
|
||||
}
|
||||
if _, exists := account.DomainsCertificate.exists(domain); exists {
|
||||
// domain already exists
|
||||
return
|
||||
}
|
||||
certificate, err := a.getDomainsCertificates(domains)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting ACME certificates %+v : %v", domains, err)
|
||||
return
|
||||
}
|
||||
log.Debugf("Got certificate for domains %+v", domains)
|
||||
transaction, object, err := a.store.Begin()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error creating transaction %+v : %v", domains, err)
|
||||
return
|
||||
}
|
||||
account = object.(*Account)
|
||||
_, err = account.DomainsCertificate.addCertificateForDomains(certificate, domain)
|
||||
if err != nil {
|
||||
log.Errorf("Error adding ACME certificates %+v : %v", domains, err)
|
||||
return
|
||||
}
|
||||
if err = transaction.Commit(account); err != nil {
|
||||
log.Errorf("Error Saving ACME account %+v: %v", account, err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (a *ACME) saveAccount(Account *Account) error {
|
||||
a.storageLock.Lock()
|
||||
defer a.storageLock.Unlock()
|
||||
// write account to file
|
||||
data, err := json.MarshalIndent(Account, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(a.StorageFile, data, 0644)
|
||||
}
|
||||
|
||||
func (a *ACME) getDomainsCertificates(client *acme.Client, domains []string) (*Certificate, error) {
|
||||
func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
|
||||
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
||||
log.Debugf("Loading ACME certificates %s...", domains)
|
||||
bundle := true
|
||||
certificate, failures := client.ObtainCertificate(domains, bundle, nil)
|
||||
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil)
|
||||
if len(failures) > 0 {
|
||||
log.Error(failures)
|
||||
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
|
||||
|
@@ -63,7 +63,7 @@ func TestDomainsSetAppend(t *testing.T) {
|
||||
|
||||
func TestCertificatesRenew(t *testing.T) {
|
||||
domainsCertificates := DomainsCertificates{
|
||||
lock: &sync.RWMutex{},
|
||||
lock: sync.RWMutex{},
|
||||
Certs: []*DomainsCertificate{
|
||||
{
|
||||
Domains: Domain{
|
||||
|
@@ -2,55 +2,95 @@ package acme
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"time"
|
||||
)
|
||||
|
||||
type wrapperChallengeProvider struct {
|
||||
challengeCerts map[string]*tls.Certificate
|
||||
lock sync.RWMutex
|
||||
var _ acme.ChallengeProviderTimeout = (*challengeProvider)(nil)
|
||||
|
||||
type challengeProvider struct {
|
||||
store cluster.Store
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func newWrapperChallengeProvider() *wrapperChallengeProvider {
|
||||
return &wrapperChallengeProvider{
|
||||
challengeCerts: map[string]*tls.Certificate{},
|
||||
func (c *challengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
|
||||
log.Debugf("Challenge GetCertificate %s", domain)
|
||||
if !strings.HasSuffix(domain, ".acme.invalid") {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *wrapperChallengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
if cert, ok := c.challengeCerts[domain]; ok {
|
||||
return cert, true
|
||||
account := c.store.Get().(*Account)
|
||||
if account.ChallengeCerts == nil {
|
||||
return nil, false
|
||||
}
|
||||
return nil, false
|
||||
account.Init()
|
||||
var result *tls.Certificate
|
||||
operation := func() error {
|
||||
for _, cert := range account.ChallengeCerts {
|
||||
for _, dns := range cert.certificate.Leaf.DNSNames {
|
||||
if domain == dns {
|
||||
result = cert.certificate
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Cannot find challenge cert for domain %s", domain)
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Error getting cert: %v, retrying in %s", err, time)
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 60 * time.Second
|
||||
err := backoff.RetryNotify(operation, ebo, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting cert: %v", err)
|
||||
return nil, false
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
|
||||
func (c *wrapperChallengeProvider) Present(domain, token, keyAuth string) error {
|
||||
cert, _, err := acme.TLSSNI01ChallengeCert(keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
|
||||
func (c *challengeProvider) Present(domain, token, keyAuth string) error {
|
||||
log.Debugf("Challenge Present %s", domain)
|
||||
cert, _, err := TLSSNI01ChallengeCert(keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
for i := range cert.Leaf.DNSNames {
|
||||
c.challengeCerts[cert.Leaf.DNSNames[i]] = &cert
|
||||
transaction, object, err := c.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
account := object.(*Account)
|
||||
if account.ChallengeCerts == nil {
|
||||
account.ChallengeCerts = map[string]*ChallengeCert{}
|
||||
}
|
||||
account.ChallengeCerts[domain] = &cert
|
||||
return transaction.Commit(account)
|
||||
}
|
||||
|
||||
func (c *wrapperChallengeProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
func (c *challengeProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
log.Debugf("Challenge CleanUp %s", domain)
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
delete(c.challengeCerts, domain)
|
||||
return nil
|
||||
transaction, object, err := c.store.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
account := object.(*Account)
|
||||
delete(account.ChallengeCerts, domain)
|
||||
return transaction.Commit(account)
|
||||
}
|
||||
|
||||
func (c *challengeProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return 60 * time.Second, 5 * time.Second
|
||||
}
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
@@ -76,3 +78,48 @@ func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domain strin
|
||||
|
||||
return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
|
||||
}
|
||||
|
||||
// TLSSNI01ChallengeCert returns a certificate and target domain for the `tls-sni-01` challenge
|
||||
func TLSSNI01ChallengeCert(keyAuth string) (ChallengeCert, string, error) {
|
||||
// generate a new RSA key for the certificates
|
||||
var tempPrivKey crypto.PrivateKey
|
||||
tempPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return ChallengeCert{}, "", err
|
||||
}
|
||||
rsaPrivKey := tempPrivKey.(*rsa.PrivateKey)
|
||||
rsaPrivPEM := pemEncode(rsaPrivKey)
|
||||
|
||||
zBytes := sha256.Sum256([]byte(keyAuth))
|
||||
z := hex.EncodeToString(zBytes[:sha256.Size])
|
||||
domain := fmt.Sprintf("%s.%s.acme.invalid", z[:32], z[32:])
|
||||
tempCertPEM, err := generatePemCert(rsaPrivKey, domain)
|
||||
if err != nil {
|
||||
return ChallengeCert{}, "", err
|
||||
}
|
||||
|
||||
certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
|
||||
if err != nil {
|
||||
return ChallengeCert{}, "", err
|
||||
}
|
||||
|
||||
return ChallengeCert{Certificate: tempCertPEM, PrivateKey: rsaPrivPEM, certificate: &certificate}, domain, nil
|
||||
}
|
||||
func pemEncode(data interface{}) []byte {
|
||||
var pemBlock *pem.Block
|
||||
switch key := data.(type) {
|
||||
case *ecdsa.PrivateKey:
|
||||
keyBytes, _ := x509.MarshalECPrivateKey(key)
|
||||
pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}
|
||||
case *rsa.PrivateKey:
|
||||
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
|
||||
break
|
||||
case *x509.CertificateRequest:
|
||||
pemBlock = &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: key.Raw}
|
||||
break
|
||||
case []byte:
|
||||
pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.([]byte))}
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(pemBlock)
|
||||
}
|
||||
|
85
acme/localStore.go
Normal file
85
acme/localStore.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/log"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var _ cluster.Store = (*LocalStore)(nil)
|
||||
|
||||
// LocalStore is a store using a file as storage
|
||||
type LocalStore struct {
|
||||
file string
|
||||
storageLock sync.RWMutex
|
||||
account *Account
|
||||
}
|
||||
|
||||
// NewLocalStore create a LocalStore
|
||||
func NewLocalStore(file string) *LocalStore {
|
||||
return &LocalStore{
|
||||
file: file,
|
||||
}
|
||||
}
|
||||
|
||||
// Get atomically a struct from the file storage
|
||||
func (s *LocalStore) Get() cluster.Object {
|
||||
s.storageLock.RLock()
|
||||
defer s.storageLock.RUnlock()
|
||||
return s.account
|
||||
}
|
||||
|
||||
// Load loads file into store
|
||||
func (s *LocalStore) Load() (cluster.Object, error) {
|
||||
s.storageLock.Lock()
|
||||
defer s.storageLock.Unlock()
|
||||
account := &Account{}
|
||||
file, err := ioutil.ReadFile(s.file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(file, &account); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
account.Init()
|
||||
s.account = account
|
||||
log.Infof("Loaded ACME config from store %s", s.file)
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// Begin creates a transaction with the KV store.
|
||||
func (s *LocalStore) Begin() (cluster.Transaction, cluster.Object, error) {
|
||||
s.storageLock.Lock()
|
||||
return &localTransaction{LocalStore: s}, s.account, nil
|
||||
}
|
||||
|
||||
var _ cluster.Transaction = (*localTransaction)(nil)
|
||||
|
||||
type localTransaction struct {
|
||||
*LocalStore
|
||||
dirty bool
|
||||
}
|
||||
|
||||
// Commit allows to set an object in the file storage
|
||||
func (t *localTransaction) Commit(object cluster.Object) error {
|
||||
t.LocalStore.account = object.(*Account)
|
||||
defer t.storageLock.Unlock()
|
||||
if t.dirty {
|
||||
return fmt.Errorf("transaction already used, please begin a new one")
|
||||
}
|
||||
|
||||
// write account to file
|
||||
data, err := json.MarshalIndent(object, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(t.file, data, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.dirty = true
|
||||
return nil
|
||||
}
|
@@ -6,7 +6,7 @@ package main
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
)
|
||||
|
||||
// OxyLogger implements oxy Logger interface with logrus.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.6.2
|
||||
FROM golang:1.7
|
||||
|
||||
RUN go get github.com/Masterminds/glide \
|
||||
&& go get github.com/jteeuwen/go-bindata/... \
|
||||
@@ -22,4 +22,4 @@ COPY glide.yaml glide.yaml
|
||||
COPY glide.lock glide.lock
|
||||
RUN glide install
|
||||
|
||||
COPY . /go/src/github.com/containous/traefik
|
||||
COPY . /go/src/github.com/containous/traefik
|
||||
|
253
cluster/datastore.go
Normal file
253
cluster/datastore.go
Normal file
@@ -0,0 +1,253 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/staert"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/satori/go.uuid"
|
||||
"golang.org/x/net/context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Metadata stores Object plus metadata
|
||||
type Metadata struct {
|
||||
object Object
|
||||
Object []byte
|
||||
Lock string
|
||||
}
|
||||
|
||||
// NewMetadata returns new Metadata
|
||||
func NewMetadata(object Object) *Metadata {
|
||||
return &Metadata{object: object}
|
||||
}
|
||||
|
||||
// Marshall marshalls object
|
||||
func (m *Metadata) Marshall() error {
|
||||
var err error
|
||||
m.Object, err = json.Marshal(m.object)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Metadata) unmarshall() error {
|
||||
if len(m.Object) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(m.Object, m.object)
|
||||
}
|
||||
|
||||
// Listener is called when Object has been changed in KV store
|
||||
type Listener func(Object) error
|
||||
|
||||
var _ Store = (*Datastore)(nil)
|
||||
|
||||
// Datastore holds a struct synced in a KV store
|
||||
type Datastore struct {
|
||||
kv staert.KvSource
|
||||
ctx context.Context
|
||||
localLock *sync.RWMutex
|
||||
meta *Metadata
|
||||
lockKey string
|
||||
listener Listener
|
||||
}
|
||||
|
||||
// NewDataStore creates a Datastore
|
||||
func NewDataStore(ctx context.Context, kvSource staert.KvSource, object Object, listener Listener) (*Datastore, error) {
|
||||
datastore := Datastore{
|
||||
kv: kvSource,
|
||||
ctx: ctx,
|
||||
meta: &Metadata{object: object},
|
||||
lockKey: kvSource.Prefix + "/lock",
|
||||
localLock: &sync.RWMutex{},
|
||||
listener: listener,
|
||||
}
|
||||
err := datastore.watchChanges()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &datastore, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) watchChanges() error {
|
||||
stopCh := make(chan struct{})
|
||||
kvCh, err := d.kv.Watch(d.lockKey, stopCh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
ctx, cancel := context.WithCancel(d.ctx)
|
||||
operation := func() error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
stopCh <- struct{}{}
|
||||
return nil
|
||||
case _, ok := <-kvCh:
|
||||
if !ok {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
err = d.reload()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// log.Debugf("Datastore object change received: %+v", d.meta)
|
||||
if d.listener != nil {
|
||||
err := d.listener(d.meta.object)
|
||||
if err != nil {
|
||||
log.Errorf("Error calling datastore listener: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Error in watch datastore: %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Errorf("Error in watch datastore: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Datastore) reload() error {
|
||||
log.Debugf("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
|
||||
}
|
||||
|
||||
// Begin creates a transaction with the KV store.
|
||||
func (d *Datastore) Begin() (Transaction, Object, error) {
|
||||
id := uuid.NewV4().String()
|
||||
log.Debugf("Transaction %s begins", id)
|
||||
remoteLock, err := d.kv.NewLock(d.lockKey, &store.LockOptions{TTL: 20 * time.Second, Value: []byte(id)})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
stopCh := make(chan struct{})
|
||||
ctx, cancel := context.WithCancel(d.ctx)
|
||||
var errLock error
|
||||
go func() {
|
||||
_, errLock = remoteLock.Lock(stopCh)
|
||||
cancel()
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if errLock != nil {
|
||||
return nil, nil, errLock
|
||||
}
|
||||
case <-d.ctx.Done():
|
||||
stopCh <- struct{}{}
|
||||
return nil, nil, d.ctx.Err()
|
||||
}
|
||||
|
||||
// we got the lock! Now make sure we are synced with KV store
|
||||
operation := func() error {
|
||||
meta := d.get()
|
||||
if meta.Lock != id {
|
||||
return fmt.Errorf("Object lock value: expected %s, got %s", id, meta.Lock)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Datastore sync error: %v, retrying in %s", err, time)
|
||||
err = d.reload()
|
||||
if err != nil {
|
||||
log.Errorf("Error reloading: %+v", err)
|
||||
}
|
||||
}
|
||||
ebo := backoff.NewExponentialBackOff()
|
||||
ebo.MaxElapsedTime = 60 * time.Second
|
||||
err = backoff.RetryNotify(operation, ebo, notify)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Datastore cannot sync: %v", err)
|
||||
}
|
||||
|
||||
// we synced with KV store, we can now return Setter
|
||||
return &datastoreTransaction{
|
||||
Datastore: d,
|
||||
remoteLock: remoteLock,
|
||||
id: id,
|
||||
}, d.meta.object, nil
|
||||
}
|
||||
|
||||
func (d *Datastore) get() *Metadata {
|
||||
d.localLock.RLock()
|
||||
defer d.localLock.RUnlock()
|
||||
return d.meta
|
||||
}
|
||||
|
||||
// Load load atomically a struct from the KV store
|
||||
func (d *Datastore) Load() (Object, error) {
|
||||
d.localLock.Lock()
|
||||
defer d.localLock.Unlock()
|
||||
err := d.kv.LoadConfig(d.meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = d.meta.unmarshall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.meta.object, nil
|
||||
}
|
||||
|
||||
// Get atomically a struct from the KV store
|
||||
func (d *Datastore) Get() Object {
|
||||
d.localLock.RLock()
|
||||
defer d.localLock.RUnlock()
|
||||
return d.meta.object
|
||||
}
|
||||
|
||||
var _ Transaction = (*datastoreTransaction)(nil)
|
||||
|
||||
type datastoreTransaction struct {
|
||||
*Datastore
|
||||
remoteLock store.Locker
|
||||
dirty bool
|
||||
id string
|
||||
}
|
||||
|
||||
// Commit allows to set an object in the KV store
|
||||
func (s *datastoreTransaction) Commit(object Object) error {
|
||||
s.localLock.Lock()
|
||||
defer s.localLock.Unlock()
|
||||
if s.dirty {
|
||||
return fmt.Errorf("transaction already used, please begin a new one")
|
||||
}
|
||||
s.Datastore.meta.object = object
|
||||
err := s.Datastore.meta.Marshall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.kv.StoreConfig(s.Datastore.meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.remoteLock.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.dirty = true
|
||||
log.Debugf("Transaction commited %s", s.id)
|
||||
return nil
|
||||
}
|
102
cluster/leadership.go
Normal file
102
cluster/leadership.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/leadership"
|
||||
"golang.org/x/net/context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Leadership allows leadership election using a KV store
|
||||
type Leadership struct {
|
||||
*safe.Pool
|
||||
*types.Cluster
|
||||
candidate *leadership.Candidate
|
||||
leader safe.Safe
|
||||
listeners []LeaderListener
|
||||
}
|
||||
|
||||
// NewLeadership creates a leadership
|
||||
func NewLeadership(ctx context.Context, cluster *types.Cluster) *Leadership {
|
||||
return &Leadership{
|
||||
Pool: safe.NewPool(ctx),
|
||||
Cluster: cluster,
|
||||
candidate: leadership.NewCandidate(cluster.Store, cluster.Store.Prefix+"/leader", cluster.Node, 20*time.Second),
|
||||
listeners: []LeaderListener{},
|
||||
}
|
||||
}
|
||||
|
||||
// LeaderListener is called when leadership has changed
|
||||
type LeaderListener func(elected bool) error
|
||||
|
||||
// Participate tries to be a leader
|
||||
func (l *Leadership) Participate(pool *safe.Pool) {
|
||||
pool.GoCtx(func(ctx context.Context) {
|
||||
log.Debugf("Node %s running for election", l.Cluster.Node)
|
||||
defer log.Debugf("Node %s no more running for election", l.Cluster.Node)
|
||||
backOff := backoff.NewExponentialBackOff()
|
||||
operation := func() error {
|
||||
return l.run(ctx, l.candidate)
|
||||
}
|
||||
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Leadership election error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, backOff, notify)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot elect leadership %+v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// AddListener adds a leadership listerner
|
||||
func (l *Leadership) AddListener(listener LeaderListener) {
|
||||
l.listeners = append(l.listeners, listener)
|
||||
}
|
||||
|
||||
// Resign resigns from being a leader
|
||||
func (l *Leadership) Resign() {
|
||||
l.candidate.Resign()
|
||||
log.Infof("Node %s resigned", l.Cluster.Node)
|
||||
}
|
||||
|
||||
func (l *Leadership) run(ctx context.Context, candidate *leadership.Candidate) error {
|
||||
electedCh, errCh := candidate.RunForElection()
|
||||
for {
|
||||
select {
|
||||
case elected := <-electedCh:
|
||||
l.onElection(elected)
|
||||
case err := <-errCh:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
l.candidate.Resign()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Leadership) onElection(elected bool) {
|
||||
if elected {
|
||||
log.Infof("Node %s elected leader ♚", l.Cluster.Node)
|
||||
l.leader.Set(true)
|
||||
l.Start()
|
||||
} else {
|
||||
log.Infof("Node %s elected slave ♝", l.Cluster.Node)
|
||||
l.leader.Set(false)
|
||||
l.Stop()
|
||||
}
|
||||
for _, listener := range l.listeners {
|
||||
err := listener(elected)
|
||||
if err != nil {
|
||||
log.Errorf("Error calling Leadership listener: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsLeader returns true if current node is leader
|
||||
func (l *Leadership) IsLeader() bool {
|
||||
return l.leader.Get().(bool)
|
||||
}
|
16
cluster/store.go
Normal file
16
cluster/store.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package cluster
|
||||
|
||||
// Object is the struct to store
|
||||
type Object interface{}
|
||||
|
||||
// Store is a generic interface to represents a storage
|
||||
type Store interface {
|
||||
Load() (Object, error)
|
||||
Get() Object
|
||||
Begin() (Transaction, Object, error)
|
||||
}
|
||||
|
||||
// Transaction allows to set a struct in the KV store
|
||||
type Transaction interface {
|
||||
Commit(object Object) error
|
||||
}
|
161
configuration.go
161
configuration.go
@@ -1,20 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/types"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/acme"
|
||||
"github.com/containous/traefik/provider"
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
// TraefikConfiguration holds GlobalConfiguration and other stuff
|
||||
type TraefikConfiguration struct {
|
||||
GlobalConfiguration
|
||||
ConfigFile string `short:"c" description:"Configuration file to use (TOML)."`
|
||||
GlobalConfiguration `mapstructure:",squash"`
|
||||
ConfigFile string `short:"c" description:"Configuration file to use (TOML)."`
|
||||
}
|
||||
|
||||
// GlobalConfiguration holds global configuration (with providers, etc.).
|
||||
@@ -22,15 +25,18 @@ type TraefikConfiguration struct {
|
||||
type GlobalConfiguration struct {
|
||||
GraceTimeOut int64 `short:"g" description:"Duration to give active requests a chance to finish during hot-reload"`
|
||||
Debug bool `short:"d" description:"Enable debug mode"`
|
||||
CheckNewVersion bool `description:"Periodically check if a new version has been released"`
|
||||
AccessLogsFile string `description:"Access logs file"`
|
||||
TraefikLogsFile string `description:"Traefik logs file"`
|
||||
LogLevel string `short:"l" description:"Log level"`
|
||||
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"`
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags."`
|
||||
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key;prod/traefik.crt,prod/traefik.key'"`
|
||||
Cluster *types.Cluster `description:"Enable clustering"`
|
||||
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags"`
|
||||
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
|
||||
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
|
||||
ProvidersThrottleDuration time.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time."`
|
||||
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"`
|
||||
InsecureSkipVerify bool `description:"Disable SSL certificate verification"`
|
||||
Retry *Retry `description:"Enable retry sending request if network error"`
|
||||
Docker *provider.Docker `description:"Enable Docker backend"`
|
||||
File *provider.File `description:"Enable File backend"`
|
||||
@@ -42,6 +48,7 @@ type GlobalConfiguration struct {
|
||||
Zookeeper *provider.Zookepper `description:"Enable Zookeeper backend"`
|
||||
Boltdb *provider.BoltDb `description:"Enable Boltdb backend"`
|
||||
Kubernetes *provider.Kubernetes `description:"Enable Kubernetes backend"`
|
||||
Mesos *provider.Mesos `description:"Enable Mesos backend"`
|
||||
}
|
||||
|
||||
// DefaultEntryPoints holds default entry points
|
||||
@@ -68,7 +75,9 @@ func (dep *DefaultEntryPoints) Set(value string) error {
|
||||
}
|
||||
|
||||
// Get return the EntryPoints map
|
||||
func (dep *DefaultEntryPoints) Get() interface{} { return DefaultEntryPoints(*dep) }
|
||||
func (dep *DefaultEntryPoints) Get() interface{} {
|
||||
return DefaultEntryPoints(*dep)
|
||||
}
|
||||
|
||||
// SetValue sets the EntryPoints map with val
|
||||
func (dep *DefaultEntryPoints) SetValue(val interface{}) {
|
||||
@@ -93,7 +102,7 @@ func (ep *EntryPoints) String() string {
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (ep *EntryPoints) Set(value string) error {
|
||||
regex := regexp.MustCompile("(?:Name:(?P<Name>\\S*))\\s*(?:Address:(?P<Address>\\S*))?\\s*(?:TLS:(?P<TLS>\\S*))?\\s*((?P<TLSACME>TLS))?\\s*(?:Redirect.EntryPoint:(?P<RedirectEntryPoint>\\S*))?\\s*(?:Redirect.Regex:(?P<RedirectRegex>\\S*))?\\s*(?:Redirect.Replacement:(?P<RedirectReplacement>\\S*))?")
|
||||
regex := regexp.MustCompile("(?:Name:(?P<Name>\\S*))\\s*(?:Address:(?P<Address>\\S*))?\\s*(?:TLS:(?P<TLS>\\S*))?\\s*((?P<TLSACME>TLS))?\\s*(?:CA:(?P<CA>\\S*))?\\s*(?:Redirect.EntryPoint:(?P<RedirectEntryPoint>\\S*))?\\s*(?:Redirect.Regex:(?P<RedirectRegex>\\S*))?\\s*(?:Redirect.Replacement:(?P<RedirectReplacement>\\S*))?\\s*(?:Compress:(?P<Compress>\\S*))?")
|
||||
match := regex.FindAllStringSubmatch(value, -1)
|
||||
if match == nil {
|
||||
return errors.New("Bad EntryPoints format: " + value)
|
||||
@@ -119,6 +128,10 @@ func (ep *EntryPoints) Set(value string) error {
|
||||
Certificates: Certificates{},
|
||||
}
|
||||
}
|
||||
if len(result["CA"]) > 0 {
|
||||
files := strings.Split(result["CA"], ",")
|
||||
tls.ClientCAFiles = files
|
||||
}
|
||||
var redirect *Redirect
|
||||
if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 {
|
||||
redirect = &Redirect{
|
||||
@@ -128,17 +141,25 @@ func (ep *EntryPoints) Set(value string) error {
|
||||
}
|
||||
}
|
||||
|
||||
compress := false
|
||||
if len(result["Compress"]) > 0 {
|
||||
compress = strings.EqualFold(result["Compress"], "enable") || strings.EqualFold(result["Compress"], "on")
|
||||
}
|
||||
|
||||
(*ep)[result["Name"]] = &EntryPoint{
|
||||
Address: result["Address"],
|
||||
TLS: tls,
|
||||
Redirect: redirect,
|
||||
Compress: compress,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get return the EntryPoints map
|
||||
func (ep *EntryPoints) Get() interface{} { return EntryPoints(*ep) }
|
||||
func (ep *EntryPoints) Get() interface{} {
|
||||
return EntryPoints(*ep)
|
||||
}
|
||||
|
||||
// SetValue sets the EntryPoints map with val
|
||||
func (ep *EntryPoints) SetValue(val interface{}) {
|
||||
@@ -156,6 +177,8 @@ type EntryPoint struct {
|
||||
Address string
|
||||
TLS *TLS
|
||||
Redirect *Redirect
|
||||
Auth *types.Auth
|
||||
Compress bool
|
||||
}
|
||||
|
||||
// Redirect configures a redirection of an entry point to another, or to an URL
|
||||
@@ -167,33 +190,102 @@ type Redirect struct {
|
||||
|
||||
// TLS configures TLS for an entry point
|
||||
type TLS struct {
|
||||
Certificates Certificates
|
||||
MinVersion string
|
||||
CipherSuites []string
|
||||
Certificates Certificates
|
||||
ClientCAFiles []string
|
||||
}
|
||||
|
||||
// Map of allowed TLS minimum versions
|
||||
var minVersion = map[string]uint16{
|
||||
`VersionTLS10`: tls.VersionTLS10,
|
||||
`VersionTLS11`: tls.VersionTLS11,
|
||||
`VersionTLS12`: tls.VersionTLS12,
|
||||
}
|
||||
|
||||
// Map of TLS CipherSuites from crypto/tls
|
||||
var cipherSuites = map[string]uint16{
|
||||
`TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
`TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
`TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
`TLS_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
`TLS_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
`TLS_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
`TLS_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
`TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
`TLS_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
}
|
||||
|
||||
// Certificates defines traefik certificates type
|
||||
// Certs and Keys could be either a file path, or the file content itself
|
||||
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.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (certs *Certificates) String() string {
|
||||
if len(*certs) == 0 {
|
||||
return ""
|
||||
}
|
||||
return (*certs)[0].CertFile + "," + (*certs)[0].KeyFile
|
||||
var result []string
|
||||
for _, certificate := range *certs {
|
||||
result = append(result, certificate.CertFile+","+certificate.KeyFile)
|
||||
}
|
||||
return strings.Join(result, ";")
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (certs *Certificates) Set(value string) error {
|
||||
files := strings.Split(value, ",")
|
||||
if len(files) != 2 {
|
||||
return errors.New("Bad certificates format: " + value)
|
||||
certificates := strings.Split(value, ";")
|
||||
for _, certificate := range certificates {
|
||||
files := strings.Split(certificate, ",")
|
||||
if len(files) != 2 {
|
||||
return errors.New("Bad certificates format: " + value)
|
||||
}
|
||||
*certs = append(*certs, Certificate{
|
||||
CertFile: files[0],
|
||||
KeyFile: files[1],
|
||||
})
|
||||
}
|
||||
*certs = append(*certs, Certificate{
|
||||
CertFile: files[0],
|
||||
KeyFile: files[1],
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -203,6 +295,7 @@ func (certs *Certificates) Type() string {
|
||||
}
|
||||
|
||||
// Certificate holds a SSL cert/key pair
|
||||
// Certs and Key could be either a file path, or the file content itself
|
||||
type Certificate struct {
|
||||
CertFile string
|
||||
KeyFile string
|
||||
@@ -218,7 +311,9 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
//default Docker
|
||||
var defaultDocker provider.Docker
|
||||
defaultDocker.Watch = true
|
||||
defaultDocker.ExposedByDefault = true
|
||||
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
|
||||
defaultDocker.SwarmMode = false
|
||||
|
||||
// default File
|
||||
var defaultFile provider.File
|
||||
@@ -234,46 +329,54 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
defaultMarathon.Watch = true
|
||||
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
|
||||
defaultMarathon.ExposedByDefault = true
|
||||
defaultMarathon.Constraints = []types.Constraint{}
|
||||
defaultMarathon.Constraints = types.Constraints{}
|
||||
|
||||
// default Consul
|
||||
var defaultConsul provider.Consul
|
||||
defaultConsul.Watch = true
|
||||
defaultConsul.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsul.Prefix = "traefik"
|
||||
defaultConsul.Constraints = []types.Constraint{}
|
||||
defaultConsul.Constraints = types.Constraints{}
|
||||
|
||||
// default ConsulCatalog
|
||||
var defaultConsulCatalog provider.ConsulCatalog
|
||||
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
|
||||
defaultConsulCatalog.Constraints = []types.Constraint{}
|
||||
defaultConsulCatalog.Constraints = types.Constraints{}
|
||||
|
||||
// default Etcd
|
||||
var defaultEtcd provider.Etcd
|
||||
defaultEtcd.Watch = true
|
||||
defaultEtcd.Endpoint = "127.0.0.1:2379"
|
||||
defaultEtcd.Prefix = "/traefik"
|
||||
defaultEtcd.Constraints = []types.Constraint{}
|
||||
defaultEtcd.Constraints = types.Constraints{}
|
||||
|
||||
//default Zookeeper
|
||||
var defaultZookeeper provider.Zookepper
|
||||
defaultZookeeper.Watch = true
|
||||
defaultZookeeper.Endpoint = "127.0.0.1:2181"
|
||||
defaultZookeeper.Prefix = "/traefik"
|
||||
defaultZookeeper.Constraints = []types.Constraint{}
|
||||
defaultZookeeper.Constraints = types.Constraints{}
|
||||
|
||||
//default Boltdb
|
||||
var defaultBoltDb provider.BoltDb
|
||||
defaultBoltDb.Watch = true
|
||||
defaultBoltDb.Endpoint = "127.0.0.1:4001"
|
||||
defaultBoltDb.Prefix = "/traefik"
|
||||
defaultBoltDb.Constraints = []types.Constraint{}
|
||||
defaultBoltDb.Constraints = types.Constraints{}
|
||||
|
||||
//default Kubernetes
|
||||
var defaultKubernetes provider.Kubernetes
|
||||
defaultKubernetes.Watch = true
|
||||
defaultKubernetes.Endpoint = "http://127.0.0.1:8080"
|
||||
defaultKubernetes.Constraints = []types.Constraint{}
|
||||
defaultKubernetes.Endpoint = ""
|
||||
defaultKubernetes.LabelSelector = ""
|
||||
defaultKubernetes.Constraints = types.Constraints{}
|
||||
|
||||
// default Mesos
|
||||
var defaultMesos provider.Mesos
|
||||
defaultMesos.Watch = true
|
||||
defaultMesos.Endpoint = "http://127.0.0.1:5050"
|
||||
defaultMesos.ExposedByDefault = true
|
||||
defaultMesos.Constraints = types.Constraints{}
|
||||
|
||||
defaultConfiguration := GlobalConfiguration{
|
||||
Docker: &defaultDocker,
|
||||
@@ -286,6 +389,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
Zookeeper: &defaultZookeeper,
|
||||
Boltdb: &defaultBoltDb,
|
||||
Kubernetes: &defaultKubernetes,
|
||||
Mesos: &defaultMesos,
|
||||
Retry: &Retry{},
|
||||
}
|
||||
return &TraefikConfiguration{
|
||||
@@ -302,10 +406,11 @@ func NewTraefikConfiguration() *TraefikConfiguration {
|
||||
TraefikLogsFile: "",
|
||||
LogLevel: "ERROR",
|
||||
EntryPoints: map[string]*EntryPoint{},
|
||||
Constraints: []types.Constraint{},
|
||||
Constraints: types.Constraints{},
|
||||
DefaultEntryPoints: []string{},
|
||||
ProvidersThrottleDuration: time.Duration(2 * time.Second),
|
||||
MaxIdleConnsPerHost: 200,
|
||||
CheckNewVersion: true,
|
||||
},
|
||||
ConfigFile: "",
|
||||
}
|
||||
|
@@ -2,5 +2,9 @@
|
||||
Description=Traefik
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=/usr/bin/traefik --configFile=/etc/traefik.toml
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
117
docs/basics.md
117
docs/basics.md
@@ -30,7 +30,7 @@ Entrypoints are the network entry points into Træfɪk.
|
||||
They can be defined using:
|
||||
|
||||
- a port (80, 443...)
|
||||
- SSL (Certificates. Keys...)
|
||||
- SSL (Certificates, Keys, authentication with a client certificate signed by a trusted CA...)
|
||||
- redirection to another entrypoint (redirect `HTTP` to `HTTPS`)
|
||||
|
||||
Here is an example of entrypoints definition:
|
||||
@@ -54,6 +54,23 @@ Here is an example of entrypoints definition:
|
||||
- We enable SSL on `https` by giving a certificate and a key.
|
||||
- We also redirect all the traffic from entrypoint `http` to `https`.
|
||||
|
||||
And here is another example with client certificate authentication:
|
||||
|
||||
```toml
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.tls]
|
||||
clientCAFiles = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
certFile = "tests/traefik.crt"
|
||||
keyFile = "tests/traefik.key"
|
||||
```
|
||||
|
||||
- We enable SSL on `https` by giving a certificate and a key.
|
||||
- One or several files containing Certificate Authorities in PEM format are added.
|
||||
- It is possible to have multiple CA:s in the same file or keep them in separate files.
|
||||
|
||||
## Frontends
|
||||
|
||||
A frontend is a set of rules that forwards the incoming traffic from an entrypoint to a backend.
|
||||
@@ -110,7 +127,7 @@ In TOML file, you can use multiple routes:
|
||||
[frontends.frontend3.routes.test_1]
|
||||
rule = "Host:test3.localhost"
|
||||
[frontends.frontend3.routes.test_2]
|
||||
rule = "Host:Path:/test"
|
||||
rule = "Path:/test"
|
||||
```
|
||||
|
||||
Here `frontend3` will forward the traffic to the `backend2` if the rules `Host:test3.localhost` **AND** `Path:/test` are matched.
|
||||
@@ -205,6 +222,17 @@ For example:
|
||||
- Another possible value for `extractorfunc` is `client.ip` which will categorize requests based on client source ip.
|
||||
- Lastly `extractorfunc` can take the value of `request.header.ANY_HEADER` which will categorize requests based on `ANY_HEADER` that you provide.
|
||||
|
||||
Sticky sessions are supported with both load balancers. When sticky sessions are enabled, a cookie called `_TRAEFIK_BACKEND` is set on the initial
|
||||
request. On subsequent requests, the client will be directed to the backend stored in the cookie if it is still healthy. If not, a new backend
|
||||
will be assigned.
|
||||
|
||||
For example:
|
||||
```toml
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.loadbalancer]
|
||||
sticky = true
|
||||
```
|
||||
## Servers
|
||||
|
||||
Servers are simply defined using a `URL`. You can also apply a custom `weight` to each server (this will be used by load-balancing).
|
||||
@@ -238,9 +266,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.
|
||||
- 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:
|
||||
|
||||
- `/etc/traefik/`
|
||||
@@ -253,15 +302,63 @@ You can override this by setting a `configFile` argument:
|
||||
$ 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
|
||||
- configuration file
|
||||
- default
|
||||
### Arguments
|
||||
|
||||
It means that arguments overrides configuration file.
|
||||
Each argument is described in the help section:
|
||||
Each argument (and command) is described in the help section:
|
||||
|
||||
```bash
|
||||
$ 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.
|
||||
|
||||
# Commands
|
||||
|
||||
Usage: `traefik [command] [--flag=flag_argument]`
|
||||
|
||||
List of Træfɪk available commands with description :
|
||||
|
||||
- `version` : Print version
|
||||
- `storeconfig` : Store the static traefik configuration into a Key-value stores. Please refer to the [Store Træfɪk configuration](/user-guide/kv-config/#store-trfk-configuration) section to get documentation on it.
|
||||
|
||||
Each command may have related flags.
|
||||
All those related flags will be displayed with :
|
||||
|
||||
```bash
|
||||
$ traefik [command] --help
|
||||
```
|
||||
|
||||
Note that each command is described at the begining of the help section:
|
||||
|
||||
```bash
|
||||
$ traefik --help
|
||||
```
|
||||
|
||||
|
@@ -36,12 +36,19 @@ Routes to your services will be created instantly.
|
||||
Run it and forget it!
|
||||
|
||||
|
||||
## Demo
|
||||
## Quickstart
|
||||
|
||||
Here is a talk (in french) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Lets'Encrypt.
|
||||
You can have a quick look at Træfɪk in this [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers.
|
||||
|
||||
[](https://www.youtube.com/watch?v=QvAz9mVx5TI)
|
||||
Here is a talk given by [Ed Robinson](https://github.com/errm) at the [ContainerCamp UK](https://container.camp) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Kubernetes.
|
||||
|
||||
[](https://www.youtube.com/watch?v=aFtpIShV60I)
|
||||
|
||||
Here is a talk (in French) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
|
||||
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Let's Encrypt.
|
||||
|
||||
[](http://www.youtube.com/watch?v=QvAz9mVx5TI)
|
||||
|
||||
## Get it
|
||||
|
||||
|
417
docs/toml.md
417
docs/toml.md
@@ -9,6 +9,28 @@
|
||||
# Global configuration
|
||||
################################################################
|
||||
|
||||
# Timeout in seconds.
|
||||
# Duration to give active requests a chance to finish during hot-reloads
|
||||
#
|
||||
# Optional
|
||||
# Default: 10
|
||||
#
|
||||
# graceTimeOut = 10
|
||||
|
||||
# Enable debug mode
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# debug = true
|
||||
|
||||
# Periodically check if a new version has been released
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
# checkNewVersion = false
|
||||
|
||||
# Traefik logs file
|
||||
# If not defined, logs to stdout
|
||||
#
|
||||
@@ -26,17 +48,19 @@
|
||||
#
|
||||
# Optional
|
||||
# Default: "ERROR"
|
||||
# Accepted values, in order of severity: "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "PANIC"
|
||||
# Messages at and above the selected level will be logged.
|
||||
#
|
||||
# logLevel = "ERROR"
|
||||
|
||||
# Backends throttle duration: minimum duration between 2 events from providers
|
||||
# Backends throttle duration: minimum duration in seconds between 2 events from providers
|
||||
# before applying a new configuration. It avoids unnecessary reloads if multiples events
|
||||
# are sent in a short amount of time.
|
||||
#
|
||||
# Optional
|
||||
# Default: "2s"
|
||||
# Default: "2"
|
||||
#
|
||||
# ProvidersThrottleDuration = "5s"
|
||||
# ProvidersThrottleDuration = "5"
|
||||
|
||||
# If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used.
|
||||
# If you encounter 'too many open files' errors, you can either change this value, or change `ulimit` value.
|
||||
@@ -46,6 +70,13 @@
|
||||
#
|
||||
# MaxIdleConnsPerHost = 200
|
||||
|
||||
# If set to true invalid SSL certificates are accepted for backends.
|
||||
# Note: This disables detection of man-in-the-middle attacks so should only be used on secure backend networks.
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# InsecureSkipVerify = true
|
||||
|
||||
# Entrypoints to be used by frontends that do not specify any entrypoint.
|
||||
# Each frontend can specify its own entrypoints.
|
||||
#
|
||||
@@ -55,6 +86,53 @@
|
||||
# defaultEntryPoints = ["http", "https"]
|
||||
```
|
||||
|
||||
### Constraints
|
||||
|
||||
In a micro-service architecture, with a central service discovery, setting constraints limits Træfɪk scope to a smaller number of routes.
|
||||
|
||||
Træfɪk filters services according to service attributes/tags set in your configuration backends.
|
||||
|
||||
Supported backends:
|
||||
|
||||
- Docker
|
||||
- Consul K/V
|
||||
- BoltDB
|
||||
- Zookeeper
|
||||
- Etcd
|
||||
- Consul Catalog
|
||||
|
||||
Supported filters:
|
||||
|
||||
- ```tag```
|
||||
|
||||
```
|
||||
# Constraints definition
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# Simple matching constraint
|
||||
# constraints = ["tag==api"]
|
||||
#
|
||||
# Simple mismatching constraint
|
||||
# constraints = ["tag!=api"]
|
||||
#
|
||||
# Globbing
|
||||
# constraints = ["tag==us-*"]
|
||||
#
|
||||
# Backend-specific constraint
|
||||
# [consulCatalog]
|
||||
# endpoint = 127.0.0.1:8500
|
||||
# constraints = ["tag==api"]
|
||||
#
|
||||
# Multiple constraints
|
||||
# - "tag==" must match with at least one tag
|
||||
# - "tag!=" must match with none of tags
|
||||
# constraints = ["tag!=us-*", "tag!=asia-*"]
|
||||
# [consulCatalog]
|
||||
# endpoint = 127.0.0.1:8500
|
||||
# constraints = ["tag==api", "tag!=v*-beta"]
|
||||
```
|
||||
|
||||
## Entrypoints definition
|
||||
|
||||
```toml
|
||||
@@ -89,6 +167,64 @@
|
||||
# [entryPoints.http.redirect]
|
||||
# regex = "^http://localhost/(.*)"
|
||||
# replacement = "http://mydomain/$1"
|
||||
#
|
||||
# Only accept clients that present a certificate signed by a specified
|
||||
# Certificate Authority (CA)
|
||||
# ClientCAFiles can be configured with multiple CA:s in the same file or
|
||||
# use multiple files containing one or several CA:s. The CA:s has to be in PEM format.
|
||||
# All clients will be required to present a valid cert.
|
||||
# The requirement will apply to all server certs in the entrypoint
|
||||
# In the example below both snitest.com and snitest.org will require client certs
|
||||
#
|
||||
# [entryPoints]
|
||||
# [entryPoints.https]
|
||||
# address = ":443"
|
||||
# [entryPoints.https.tls]
|
||||
# ClientCAFiles = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||
# [[entryPoints.https.tls.certificates]]
|
||||
# CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||
# KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||
# [[entryPoints.https.tls.certificates]]
|
||||
# CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||
# KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||
#
|
||||
# To enable basic auth on an entrypoint
|
||||
# with 2 user/pass: test:test and test2:test2
|
||||
# Passwords can be encoded in MD5, SHA1 and BCrypt: you can use htpasswd to generate those ones
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
# [entryPoints.http.auth.basic]
|
||||
# users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
#
|
||||
# To enable digest auth on an entrypoint
|
||||
# with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
|
||||
# You can use htdigest to generate those ones
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
# [entryPoints.http.auth.basic]
|
||||
# users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
#
|
||||
# To specify an https entrypoint with a minimum TLS version, and specifying an array of cipher suites (from crypto/tls):
|
||||
# [entryPoints]
|
||||
# [entryPoints.https]
|
||||
# address = ":443"
|
||||
# [entryPoints.https.tls]
|
||||
# MinVersion = "VersionTLS12"
|
||||
# CipherSuites = ["TLS_RSA_WITH_AES_256_GCM_SHA384"]
|
||||
# [[entryPoints.https.tls.certificates]]
|
||||
# CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||
# KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||
# [[entryPoints.https.tls.certificates]]
|
||||
# CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||
# KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||
|
||||
# To enable compression support using gzip format:
|
||||
# [entryPoints]
|
||||
# [entryPoints.http]
|
||||
# address = ":80"
|
||||
# compress = true
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
@@ -133,18 +269,18 @@
|
||||
#
|
||||
email = "test@traefik.io"
|
||||
|
||||
# File used for certificates storage.
|
||||
# File or key used for certificates storage.
|
||||
# WARNING, if you use Traefik in Docker, you have 2 options:
|
||||
# - create a file on your host and mount it has a volume
|
||||
# - create a file on your host and mount it as a volume
|
||||
# storageFile = "acme.json"
|
||||
# $ docker run -v "/my/host/acme.json:acme.json" traefik
|
||||
# - mount the folder containing the file has a volume
|
||||
# - mount the folder containing the file as a volume
|
||||
# storageFile = "/etc/traefik/acme/acme.json"
|
||||
# $ docker run -v "/my/host/acme:/etc/traefik/acme" traefik
|
||||
#
|
||||
# Required
|
||||
#
|
||||
storageFile = "acme.json"
|
||||
storage = "acme.json" # or "traefik/acme/account" if using KV store
|
||||
|
||||
# Entrypoint to proxy acme challenge to.
|
||||
# WARNING, must point to an entrypoint on port 443
|
||||
@@ -155,12 +291,19 @@ entryPoint = "https"
|
||||
|
||||
# Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate.
|
||||
# WARNING, TLS handshakes will be slow when requesting a hostname certificate for the first time, this can leads to DoS attacks.
|
||||
# WARNING, Take note that Let's Encrypt have rate limiting: https://community.letsencrypt.org/t/quick-start-guide/1631
|
||||
# WARNING, Take note that Let's Encrypt have rate limiting: https://letsencrypt.org/docs/rate-limits
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# onDemand = true
|
||||
|
||||
# Enable certificate generation on frontends Host rules. This will request a certificate from Let's Encrypt for each frontend with a Host rule.
|
||||
# For example, a rule Host:test1.traefik.io,test2.traefik.io will request a certificate with main domain test1.traefik.io and SAN test2.traefik.io.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# OnHostRule = true
|
||||
|
||||
# CA server to use
|
||||
# Uncomment the line to run on the staging let's encrypt server
|
||||
# Leave comment to go to prod
|
||||
@@ -172,7 +315,7 @@ entryPoint = "https"
|
||||
# Domains list
|
||||
# You can provide SANs (alternative domains) to each main domain
|
||||
# All domains must have A/AAAA records pointing to Traefik
|
||||
# WARNING, Take note that Let's Encrypt have rate limiting: https://community.letsencrypt.org/t/quick-start-guide/1631
|
||||
# WARNING, Take note that Let's Encrypt have rate limiting: https://letsencrypt.org/docs/rate-limits
|
||||
# Each domain & SANs will lead to a certificate request.
|
||||
#
|
||||
# [[acme.domains]]
|
||||
@@ -200,7 +343,7 @@ entryPoint = "https"
|
||||
|
||||
Like any other reverse proxy, Træfɪk can be configured with a file. You have two choices:
|
||||
|
||||
- simply add your configuration at the end of the global configuration file `traefik.toml` :
|
||||
- simply add your configuration at the end of the global configuration file `traefik.toml`:
|
||||
|
||||
```toml
|
||||
# traefik.toml
|
||||
@@ -341,7 +484,7 @@ watch = true
|
||||
|
||||
## API backend
|
||||
|
||||
Træfik can be configured using a restful api.
|
||||
Træfik can be configured using a RESTful api.
|
||||
To enable it:
|
||||
|
||||
```toml
|
||||
@@ -359,6 +502,18 @@ address = ":8080"
|
||||
#
|
||||
# Optional
|
||||
# ReadOnly = false
|
||||
#
|
||||
# To enable basic auth on the webui
|
||||
# with 2 user/pass: test:test and test2:test2
|
||||
# Passwords can be encoded in MD5, SHA1 and BCrypt: you can use htpasswd to generate those ones
|
||||
# [web.auth.basic]
|
||||
# users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
# To enable digest auth on the webui
|
||||
# with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
|
||||
# You can use htdigest to generate those ones
|
||||
# [web.auth.digest]
|
||||
# users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||
|
||||
```
|
||||
|
||||
- `/`: provides a simple HTML frontend of Træfik
|
||||
@@ -366,6 +521,26 @@ address = ":8080"
|
||||

|
||||

|
||||
|
||||
- `/ping`: `GET` simple endpoint to check for Træfik process liveness.
|
||||
|
||||
```sh
|
||||
$ curl -sv "http://localhost:8080/ping"
|
||||
* Trying ::1...
|
||||
* Connected to localhost (::1) port 8080 (#0)
|
||||
> GET /ping HTTP/1.1
|
||||
> Host: localhost:8080
|
||||
> User-Agent: curl/7.43.0
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 200 OK
|
||||
< Date: Thu, 25 Aug 2016 01:35:36 GMT
|
||||
< Content-Length: 2
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
<
|
||||
* Connection #0 to host localhost left intact
|
||||
OK
|
||||
```
|
||||
|
||||
- `/health`: `GET` json metrics
|
||||
|
||||
```sh
|
||||
@@ -521,6 +696,28 @@ watch = true
|
||||
#
|
||||
# filename = "docker.tmpl"
|
||||
|
||||
# Expose containers by default in traefik
|
||||
#
|
||||
# Optional
|
||||
# Default: true
|
||||
#
|
||||
exposedbydefault = true
|
||||
|
||||
# Use the IP address from the binded port instead of the inner network one. For specific use-case :)
|
||||
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
usebindportip = true
|
||||
# Use Swarm Mode services as data provider
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
swarmmode = false
|
||||
|
||||
|
||||
# Enable docker TLS connection
|
||||
#
|
||||
# [docker.tls]
|
||||
@@ -533,6 +730,11 @@ watch = true
|
||||
Labels can be used on containers to override default behaviour:
|
||||
|
||||
- `traefik.backend=foo`: assign the container to `foo` backend
|
||||
- `traefik.backend.maxconn.amount=10`: set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect.
|
||||
- `traefik.backend.maxconn.extractorfunc=client.ip`: set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect.
|
||||
- `traefik.backend.loadbalancer.method=drr`: override the default `wrr` load balancer algorithm
|
||||
- `traefik.backend.loadbalancer.sticky=true`: enable backend sticky sessions
|
||||
- `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5`: create a [circuit breaker](/basics/#backends) to be used against the backend
|
||||
- `traefik.port=80`: register this port. Useful when the container exposes multiples ports.
|
||||
- `traefik.protocol=https`: override the default `http` protocol
|
||||
- `traefik.weight=10`: assign this weight to the container
|
||||
@@ -543,6 +745,7 @@ Labels can be used on containers to override default behaviour:
|
||||
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||
- `traefik.docker.network`: Set the docker network to use for connections to this container
|
||||
|
||||
NB: when running inside a container, Træfɪk will need network access through `docker network connect <network> <traefik-container>`
|
||||
|
||||
## Marathon backend
|
||||
|
||||
@@ -602,6 +805,13 @@ domain = "marathon.localhost"
|
||||
#
|
||||
# groupsAsSubDomains = true
|
||||
|
||||
# Enable compatibility with marathon-lb labels
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# marathonLBCompatibility = true
|
||||
|
||||
# Enable Marathon basic authentication
|
||||
#
|
||||
# Optional
|
||||
@@ -627,6 +837,11 @@ domain = "marathon.localhost"
|
||||
Labels can be used on containers to override default behaviour:
|
||||
|
||||
- `traefik.backend=foo`: assign the application to `foo` backend
|
||||
- `traefik.backend.maxconn.amount=10`: set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect.
|
||||
- `traefik.backend.maxconn.extractorfunc=client.ip`: set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect.
|
||||
- `traefik.backend.loadbalancer.method=drr`: override the default `wrr` load balancer algorithm
|
||||
- `traefik.backend.loadbalancer.sticky=true`: enable backend sticky sessions
|
||||
- `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5`: create a [circuit breaker](/basics/#backends) to be used against the backend
|
||||
- `traefik.portIndex=1`: register port by index in the application's ports array. Useful when the application exposes multiple ports.
|
||||
- `traefik.port=80`: register the explicit application port value. Cannot be used alongside `traefik.portIndex`.
|
||||
- `traefik.protocol=https`: override the default `http` protocol
|
||||
@@ -638,6 +853,89 @@ Labels can be used on containers to override default behaviour:
|
||||
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||
|
||||
|
||||
## Mesos generic backend
|
||||
|
||||
Træfɪk can be configured to use Mesos as a backend configuration:
|
||||
|
||||
|
||||
```toml
|
||||
################################################################
|
||||
# Mesos configuration backend
|
||||
################################################################
|
||||
|
||||
# Enable Mesos configuration backend
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
[mesos]
|
||||
|
||||
# Mesos server endpoint.
|
||||
# You can also specify multiple endpoint for Mesos:
|
||||
# endpoint = "192.168.35.40:5050,192.168.35.41:5050,192.168.35.42:5050"
|
||||
# endpoint = "zk://192.168.35.20:2181,192.168.35.21:2181,192.168.35.22:2181/mesos"
|
||||
#
|
||||
# Required
|
||||
#
|
||||
endpoint = "http://127.0.0.1:8080"
|
||||
|
||||
# Enable watch Mesos changes
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
watch = true
|
||||
|
||||
# Default domain used.
|
||||
# Can be overridden by setting the "traefik.domain" label on an application.
|
||||
#
|
||||
# Required
|
||||
#
|
||||
domain = "mesos.localhost"
|
||||
|
||||
# Override default configuration template. For advanced users :)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# filename = "mesos.tmpl"
|
||||
|
||||
# Expose Mesos apps by default in traefik
|
||||
#
|
||||
# Optional
|
||||
# Default: false
|
||||
#
|
||||
# ExposedByDefault = true
|
||||
|
||||
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [mesos.TLS]
|
||||
# InsecureSkipVerify = true
|
||||
|
||||
# Zookeeper timeout (in seconds)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# ZkDetectionTimeout = 30
|
||||
|
||||
# Polling interval (in seconds)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# RefreshSeconds = 30
|
||||
|
||||
# IP sources (e.g. host, docker, mesos, rkt)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# IPSources = "host"
|
||||
|
||||
# HTTP Timeout (in seconds)
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# StateTimeoutSecond = "host"
|
||||
```
|
||||
|
||||
## Kubernetes Ingress backend
|
||||
|
||||
|
||||
@@ -665,13 +963,17 @@ Træfɪk can be configured to use Kubernetes Ingress as a backend configuration:
|
||||
#
|
||||
# endpoint = "http://localhost:8080"
|
||||
# namespaces = ["default","production"]
|
||||
#
|
||||
# See: http://kubernetes.io/docs/user-guide/labels/#list-and-watch-filtering
|
||||
# labelselector = "A and not B"
|
||||
#
|
||||
```
|
||||
|
||||
Annotations can be used on containers to override default behaviour for the whole Ingress resource:
|
||||
|
||||
- `traefik.frontend.rule.type: PathPrefixStrip`: override the default frontend rule type (Default: `PathPrefix`).
|
||||
|
||||
You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.rc.yaml).
|
||||
You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik.yaml).
|
||||
|
||||
## Consul backend
|
||||
|
||||
@@ -723,7 +1025,7 @@ prefix = "traefik"
|
||||
# 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
|
||||
|
||||
@@ -769,6 +1071,8 @@ Additional settings can be defined using Consul Catalog tags:
|
||||
- `traefik.backend.weight=10`: assign this weight to the container
|
||||
- `traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5`
|
||||
- `traefik.backend.loadbalancer=drr`: override the default load balancing mode
|
||||
- `traefik.backend.maxconn.amount=10`: set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect.
|
||||
- `traefik.backend.maxconn.extractorfunc=client.ip`: set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect.
|
||||
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
|
||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
||||
- `traefik.frontend.priority=10`: override default frontend priority
|
||||
@@ -824,7 +1128,7 @@ prefix = "/traefik"
|
||||
# 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
|
||||
@@ -867,7 +1171,7 @@ prefix = "/traefik"
|
||||
# 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
|
||||
|
||||
@@ -909,85 +1213,4 @@ prefix = "/traefik"
|
||||
# filename = "boltdb.tmpl"
|
||||
```
|
||||
|
||||
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en 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.
|
||||
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure.
|
||||
|
19
docs/user-guide/cluster.md
Normal file
19
docs/user-guide/cluster.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Clustering / High Availability
|
||||
|
||||
This guide explains how tu use Træfɪk in high availability mode.
|
||||
In order to deploy and configure multiple Træfɪk instances, without copying the same configuration file on each instance, we will use a distributed Key-Value store.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You will need a working KV store cluster.
|
||||
|
||||
## File configuration to KV store migration
|
||||
|
||||
We created a special Træfɪk command to help configuring your Key Value store from a Træfɪk TOML configuration file.
|
||||
Please refer to [this section](/user-guide/kv-config/#store-configuration-in-key-value-store) to get more details.
|
||||
|
||||
## Deploy a Træfɪk cluster
|
||||
|
||||
Once your Træfɪk configuration is uploaded on your KV store, you can start each Træfɪk instance.
|
||||
A Træfɪk cluster is based on a master/slave model. When starting, Træfɪk will elect a master. If this instance fails, another master will be automatically elected.
|
||||
|
@@ -29,6 +29,7 @@ defaultEntryPoints = ["http", "https"]
|
||||
CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||
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
|
||||
|
||||
@@ -96,3 +97,21 @@ entryPoint = "https"
|
||||
backend = "backend2"
|
||||
rule = "Path:/test"
|
||||
```
|
||||
|
||||
## Enable Basic authentication in an entrypoint
|
||||
|
||||
With two user/pass:
|
||||
|
||||
- `test`:`test`
|
||||
- `test2`:`test2`
|
||||
|
||||
Passwords are encoded in MD5: you can use htpasswd to generate those ones.
|
||||
|
||||
```
|
||||
defaultEntryPoints = ["http"]
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
[entryPoints.http.auth.basic]
|
||||
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||
```
|
425
docs/user-guide/kubernetes.md
Normal file
425
docs/user-guide/kubernetes.md
Normal file
@@ -0,0 +1,425 @@
|
||||
# Kubernetes Ingress Controller
|
||||
|
||||
This guide explains how to use Træfɪk as an Ingress controller in a Kubernetes cluster.
|
||||
If you are not familiar with Ingresses in Kubernetes you might want to read the [Kubernetes user guide](http://kubernetes.io/docs/user-guide/ingress/)
|
||||
|
||||
The config files used in this guide can be found in the [examples directory](https://github.com/containous/traefik/tree/master/examples/k8s)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. A working Kubernetes cluster. If you want to follow along with this guide, you should setup [minikube](http://kubernetes.io/docs/getting-started-guides/minikube/)
|
||||
on your machine, as it is the quickest way to get a local Kubernetes cluster setup for experimentation and development.
|
||||
|
||||
2. The `kubectl` binary should be [installed on your workstation](http://kubernetes.io/docs/getting-started-guides/minikube/#download-kubectl).
|
||||
|
||||
## Deploy Træfɪk
|
||||
|
||||
We are going to deploy Træfɪk with a
|
||||
[Deployment](http://kubernetes.io/docs/user-guide/deployments/), as this will
|
||||
allow you to easily roll out config changes or update the image.
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
apiVersion: extensions/v1beta1
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
name: traefik-ingress-lb
|
||||
version: v1.0.0
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- image: traefik:v1.0.0
|
||||
name: traefik-ingress-lb
|
||||
resources:
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 30Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 20Mi
|
||||
ports:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
- containerPort: 8080
|
||||
args:
|
||||
- --web
|
||||
- --kubernetes
|
||||
```
|
||||
[examples/k8s/traefik.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik.yaml)
|
||||
|
||||
> notice that we binding port 80 on the Træfɪk container to port 80 on the host.
|
||||
> With a multi node cluster we might expose Træfɪk with a NodePort or LoadBalancer service
|
||||
> and run more than 1 replica of Træfɪk for high availability.
|
||||
|
||||
To deploy Træfɪk to your cluster start by submitting the deployment to the cluster with `kubectl`:
|
||||
|
||||
```sh
|
||||
kubectl apply -f examples/k8s/traefik.yaml
|
||||
```
|
||||
|
||||
### Check the deployment
|
||||
|
||||
Now lets check if our deployment was successful.
|
||||
|
||||
Start by listing the pods in the `kube-system` namespace:
|
||||
|
||||
```sh
|
||||
$kubectl --namespace=kube-system get pods
|
||||
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
kube-addon-manager-minikubevm 1/1 Running 0 4h
|
||||
kubernetes-dashboard-s8krj 1/1 Running 0 4h
|
||||
traefik-ingress-controller-678226159-eqseo 1/1 Running 0 7m
|
||||
```
|
||||
|
||||
You should see that after submitting the Deployment to Kubernetes it has launched
|
||||
a pod, and it is now running. _It might take a few moments for kubenetes to pull
|
||||
the Træfɪk image and start the container._
|
||||
|
||||
> You could also check the deployment with the Kubernetes dashboard, run
|
||||
> `minikube dashboard` to open it in your browser, then choose the `kube-system`
|
||||
> namespace from the menu at the top right of the screen.
|
||||
|
||||
You should now be able to access Træfɪk on port 80 of your minikube instance.
|
||||
|
||||
```sh
|
||||
curl $(minikube ip)
|
||||
404 page not found
|
||||
```
|
||||
|
||||
> We expect to see a 404 response here as we haven't yet given Træfɪk any configuration.
|
||||
|
||||
## Submitting An Ingress to the cluster.
|
||||
|
||||
Lets start by creating a Service and an Ingress that will expose the
|
||||
[Træfɪk Web UI](https://github.com/containous/traefik#web-ui).
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: traefik-web-ui
|
||||
namespace: kube-system
|
||||
spec:
|
||||
selector:
|
||||
k8s-app: traefik-ingress-lb
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: traefik-web-ui
|
||||
namespace: kube-system
|
||||
spec:
|
||||
rules:
|
||||
- host: traefik-ui.local
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: traefik-web-ui
|
||||
servicePort: 80
|
||||
```
|
||||
[examples/k8s/ui.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/ui.yaml)
|
||||
|
||||
```sh
|
||||
kubectl apply -f examples/k8s/ui.yaml
|
||||
```
|
||||
|
||||
Now lets setup an entry in our /etc/hosts file to route `traefik-ui.local`
|
||||
to our cluster.
|
||||
|
||||
> In production you would want to set up real dns entries.
|
||||
|
||||
> You can get the ip address of your minikube instance by running `minikube ip`
|
||||
|
||||
```
|
||||
echo "$(minikube ip) traefik-ui.local" | sudo tee -a /etc/hosts
|
||||
```
|
||||
|
||||
We should now be able to visit [traefik-ui.local](http://traefik-ui.local) in the browser and view the Træfɪk Web UI.
|
||||
|
||||
## Name based routing
|
||||
|
||||
In this example we are going to setup websites for 3 of the United Kingdoms
|
||||
best loved cheeses, Cheddar, Stilton and Wensleydale.
|
||||
|
||||
First lets start by launching the 3 pods for the cheese websites.
|
||||
|
||||
```yaml
|
||||
---
|
||||
kind: Deployment
|
||||
apiVersion: extensions/v1beta1
|
||||
metadata:
|
||||
name: stilton
|
||||
labels:
|
||||
app: cheese
|
||||
cheese: stilton
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cheese
|
||||
task: stilton
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cheese
|
||||
task: stilton
|
||||
version: v0.0.1
|
||||
spec:
|
||||
containers:
|
||||
- name: cheese
|
||||
image: errm/cheese:stilton
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 50Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 50Mi
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
kind: Deployment
|
||||
apiVersion: extensions/v1beta1
|
||||
metadata:
|
||||
name: cheddar
|
||||
labels:
|
||||
app: cheese
|
||||
cheese: cheddar
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cheese
|
||||
task: cheddar
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cheese
|
||||
task: cheddar
|
||||
version: v0.0.1
|
||||
spec:
|
||||
containers:
|
||||
- name: cheese
|
||||
image: errm/cheese:cheddar
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 50Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 50Mi
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
kind: Deployment
|
||||
apiVersion: extensions/v1beta1
|
||||
metadata:
|
||||
name: wensleydale
|
||||
labels:
|
||||
app: cheese
|
||||
cheese: wensleydale
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cheese
|
||||
task: wensleydale
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cheese
|
||||
task: wensleydale
|
||||
version: v0.0.1
|
||||
spec:
|
||||
containers:
|
||||
- name: cheese
|
||||
image: errm/cheese:wensleydale
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 50Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 50Mi
|
||||
ports:
|
||||
- containerPort: 80
|
||||
```
|
||||
[examples/k8s/cheese-deployments.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-deployments.yaml)
|
||||
|
||||
```sh
|
||||
kubectl apply -f examples/k8s/cheese-deployments.yaml
|
||||
```
|
||||
|
||||
Next we need to setup a service for each of the cheese pods.
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: stilton
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
targetPort: 80
|
||||
port: 80
|
||||
selector:
|
||||
app: cheese
|
||||
task: stilton
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: cheddar
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
targetPort: 80
|
||||
port: 80
|
||||
selector:
|
||||
app: cheese
|
||||
task: cheddar
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: wensleydale
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
targetPort: 80
|
||||
port: 80
|
||||
selector:
|
||||
app: cheese
|
||||
task: wensleydale
|
||||
```
|
||||
[examples/k8s/cheese-services.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-services.yaml)
|
||||
|
||||
```sh
|
||||
kubectl apply -f examples/k8s/cheese-services.yaml
|
||||
```
|
||||
|
||||
Now we can submit an ingress for the cheese websites.
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: cheese
|
||||
spec:
|
||||
rules:
|
||||
- host: stilton.local
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: stilton
|
||||
servicePort: http
|
||||
- host: cheddar.local
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: cheddar
|
||||
servicePort: http
|
||||
- host: wensleydale.local
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: wensleydale
|
||||
servicePort: http
|
||||
```
|
||||
[examples/k8s/cheese-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-ingress.yaml)
|
||||
|
||||
> Notice that we list each hostname, and add a backend service.
|
||||
|
||||
```sh
|
||||
kubectl apply -f examples/k8s/cheese-ingress.yaml
|
||||
```
|
||||
|
||||
Now visit the [Træfɪk dashboard](http://traefik-ui.local/) and you should
|
||||
see a frontend for each host. Along with a backend listing for each service
|
||||
with a Server set up for each pod.
|
||||
|
||||
If you edit your `/etc/hosts` again you should be able to access the cheese
|
||||
websites in your browser.
|
||||
|
||||
```sh
|
||||
echo "$(minikube ip) stilton.local cheddar.local wensleydale.local" | sudo tee -a /etc/hosts
|
||||
```
|
||||
|
||||
* [Stilton](http://stilton.local/)
|
||||
* [Cheddar](http://cheddar.local/)
|
||||
* [Wensleydale](http://wensleydale.local/)
|
||||
|
||||
## Path based routing
|
||||
|
||||
Now lets suppose that our fictional client has decided that while they are
|
||||
super happy about our cheesy web design, when they asked for 3 websites
|
||||
they had not really bargained on having to buy 3 domain names.
|
||||
|
||||
No problem, we say, why don't we reconfigure the sites to host all 3 under one domain.
|
||||
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: cheeses
|
||||
annotations:
|
||||
traefik.frontend.rule.type: pathprefixstrip
|
||||
spec:
|
||||
rules:
|
||||
- host: cheeses.local
|
||||
http:
|
||||
paths:
|
||||
- path: /stilton
|
||||
backend:
|
||||
serviceName: stilton
|
||||
servicePort: http
|
||||
- path: /cheddar
|
||||
backend:
|
||||
serviceName: cheddar
|
||||
servicePort: http
|
||||
- path: /wensleydale
|
||||
backend:
|
||||
serviceName: wensleydale
|
||||
servicePort: http
|
||||
```
|
||||
[examples/k8s/cheeses-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheeses-ingress.yaml)
|
||||
|
||||
> Notice that we are configuring Træfɪk to strip the prefix from the url path
|
||||
> with the `traefik.frontend.rule.type` annotation so that we can use
|
||||
> the containers from the previous example without modification.
|
||||
|
||||
```sh
|
||||
kubectl apply -f examples/k8s/cheeses-ingress.yaml
|
||||
```
|
||||
|
||||
```sh
|
||||
echo "$(minikube ip) cheeses.local" | sudo tee -a /etc/hosts
|
||||
```
|
||||
|
||||
You should now be able to visit the websites in your browser.
|
||||
|
||||
* [cheeses.local/stilton](http://cheeses.local/stilton/)
|
||||
* [cheeses.local/cheddar](http://cheeses.local/cheddar/)
|
||||
* [cheeses.local/wensleydale](http://cheeses.local/wensleydale/)
|
328
docs/user-guide/kv-config.md
Normal file
328
docs/user-guide/kv-config.md
Normal file
@@ -0,0 +1,328 @@
|
||||
|
||||
# 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).
|
||||
|
||||
Hopefully, Træfɪk allows automation of this process using the `storeconfig` subcommand.
|
||||
Please refer to the [store Træfɪk configuration](/user-guide/kv-config/#store-configuration-in-key-value-store) section to get documentation on it.
|
||||
|
||||
Here is 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` |
|
||||
|
||||
In case you are setting key values manually,:
|
||||
- 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 is 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"
|
||||
```
|
||||
|
||||
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 is 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` |
|
||||
| `/traefik/backends/backend1/servers/server2/tags` | `api,helloworld` |
|
||||
|
||||
- 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` |
|
||||
| `/traefik/backends/backend2/servers/server2/tags` | `web` |
|
||||
|
||||
- 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.
|
||||
|
||||
# Store configuration in Key-value store
|
||||
|
||||
Don't forget to [setup the connection between Træfɪk and Key-value store](/user-guide/kv-config/#launch-trfk).
|
||||
The static Træfɪk configuration in a key-value store can be automatically created and updated, using the [`storeconfig` subcommand](/basics/#commands).
|
||||
|
||||
```bash
|
||||
$ traefik storeconfig [flags] ...
|
||||
```
|
||||
This command is here only to automate the [process which upload the configuration into the Key-value store](/user-guide/kv-config/#upload-the-configuration-in-the-key-value-store).
|
||||
Træfɪk will not start but the [static configuration](/basics/#static-trfk-configuration) will be uploaded into the Key-value store.
|
||||
If you configured ACME (Let's Encrypt), your registration account and your certificates will also be uploaded.
|
||||
|
||||
To upload your ACME certificates to the KV store, get your traefik TOML file and add the new `storage` option in the `acme` section:
|
||||
|
||||
```
|
||||
[acme]
|
||||
email = "test@traefik.io"
|
||||
storage = "traefik/acme/account" # the key where to store your certificates in the KV store
|
||||
storageFile = "acme.json" # your old certificates store
|
||||
```
|
||||
|
||||
Call `traefik storeconfig` to upload your config in the KV store.
|
||||
Then remove the line `storageFile = "acme.json"` from your TOML config file.
|
||||
That's it!
|
||||
|
||||
|
223
docs/user-guide/swarm-mode.md
Normal file
223
docs/user-guide/swarm-mode.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# Docker Swarm (mode) cluster
|
||||
|
||||
This section explains how to create a multi-host docker cluster with
|
||||
swarm mode using [docker-machine](https://docs.docker.com/machine) and
|
||||
how to deploy Træfɪk on it.
|
||||
|
||||
The cluster constist of:
|
||||
|
||||
- 3 servers
|
||||
- 1 manager
|
||||
- 2 workers
|
||||
- 1 [overlay](https://docs.docker.com/engine/userguide/networking/dockernetworks/#an-overlay-network) network
|
||||
(multi-host networking)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. You will need to install [docker-machine](https://docs.docker.com/machine/)
|
||||
2. You will need the latest [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
|
||||
|
||||
## Cluster provisioning
|
||||
|
||||
First, let's create all the nodes required. It's a shorter version of
|
||||
the [swarm tutorial](https://docs.docker.com/engine/swarm/swarm-tutorial/).
|
||||
|
||||
```sh
|
||||
docker-machine create -d virtualbox manager
|
||||
docker-machine create -d virtualbox worker1
|
||||
docker-machine create -d virtualbox worker2
|
||||
```
|
||||
|
||||
Then, let's setup the cluster, in order :
|
||||
|
||||
1. initialize the cluster
|
||||
2. get the token for other host to join
|
||||
3. on both workers, join the cluster with the token
|
||||
|
||||
```sh
|
||||
docker-machine ssh manager "docker swarm init \
|
||||
--listen-addr $(docker-machine ip manager) \
|
||||
--advertise-addr $(docker-machine ip manager)"
|
||||
|
||||
export worker_token=$(docker-machine ssh manager "docker swarm \
|
||||
join-token worker -q")
|
||||
|
||||
docker-machine ssh worker1 "docker swarm join \
|
||||
--token=${worker_token} \
|
||||
--listen-addr $(docker-machine ip worker1) \
|
||||
--advertise-addr $(docker-machine ip worker1) \
|
||||
$(docker-machine ip manager)"
|
||||
docker-machine ssh worker2 "docker swarm join \
|
||||
--token=${worker_token} \
|
||||
--listen-addr $(docker-machine ip worker2) \
|
||||
--advertise-addr $(docker-machine ip worker2) \
|
||||
$(docker-machine ip manager)"
|
||||
```
|
||||
|
||||
Let's validate the cluster is up and running.
|
||||
|
||||
```sh
|
||||
docker-machine ssh manager docker node ls
|
||||
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
|
||||
2a770ov9vixeadep674265u1n worker1 Ready Active
|
||||
dbi3or4q8ii8elbws70g4hkdh * manager Ready Active Leader
|
||||
esbhhy6vnqv90xomjaomdgy46 worker2 Ready Active
|
||||
```
|
||||
|
||||
Finally, let's create a network for Træfik to use.
|
||||
|
||||
```sh
|
||||
docker-machine ssh manager "docker network create --driver=overlay traefik-net"
|
||||
```
|
||||
|
||||
## Deploy Træfik
|
||||
|
||||
Let's deploy Træfik as a docker service in our cluster. The only
|
||||
requirement for Træfik to work with swarm mode is that it needs to run
|
||||
on a manager node — we are going to use a
|
||||
[constraint](https://docs.docker.com/engine/reference/commandline/service_create/#/specify-service-constraints-constraint) for
|
||||
that.
|
||||
|
||||
```
|
||||
docker-machine ssh manager "docker service create \
|
||||
--name traefik \
|
||||
--constraint=node.role==manager \
|
||||
--publish 80:80 --publish 8080:8080 \
|
||||
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||
--network traefik-net \
|
||||
traefik \
|
||||
--docker \
|
||||
--docker.swarmmode \
|
||||
--docker.domain=traefik \
|
||||
--docker.watch \
|
||||
--web"
|
||||
```
|
||||
|
||||
Let's explain this command:
|
||||
|
||||
- `--publish 80:80 --publish 8080:8080`: we publish port `80` and
|
||||
`8080` on the cluster.
|
||||
- `--constraint=node.role==manager`: we ask docker to schedule Træfik
|
||||
on a manager node.
|
||||
- `--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock`:
|
||||
we bind mount the docker socket where Træfik is scheduled to be able
|
||||
to speak to the daemon.
|
||||
- `--network traefik-net`: we attach the Træfik service (and thus
|
||||
the underlined container) to the `traefik-net` network.
|
||||
- `--docker`: enable docker backend, and `--docker.swarmmode` to
|
||||
enable the swarm mode on Træfik.
|
||||
- `--web`: activate the webUI on port 8080
|
||||
|
||||
## Deploy your apps
|
||||
|
||||
We can now deploy our app on the cluster,
|
||||
here [whoami](https://github.com/emilevauge/whoami), a simple web
|
||||
server in Go. We start 2 services, on the `traefik-net` network.
|
||||
|
||||
```sh
|
||||
docker-machine ssh manager "docker service create \
|
||||
--name whoami0 \
|
||||
--label traefik.port=80 \
|
||||
--network traefik-net \
|
||||
emilevauge/whoami"
|
||||
docker-machine ssh manager "docker service create \
|
||||
--name whoami1 \
|
||||
--label traefik.port=80 \
|
||||
--network traefik-net \
|
||||
emilevauge/whoami"
|
||||
```
|
||||
|
||||
Check that everything is scheduled and started:
|
||||
|
||||
```sh
|
||||
docker-machine ssh manager "docker service ls"
|
||||
ID NAME REPLICAS IMAGE COMMAND
|
||||
ab046gpaqtln whoami0 1/1 emilevauge/whoami
|
||||
cgfg5ifzrpgm whoami1 1/1 emilevauge/whoami
|
||||
dtpl249tfghc traefik 1/1 traefik --docker --docker.swarmmode --docker.domain=traefik --docker.watch --web
|
||||
```
|
||||
|
||||
## Access to your apps through Træfɪk
|
||||
|
||||
```sh
|
||||
curl -H Host:whoami0.traefik http://$(docker-machine ip manager)
|
||||
Hostname: 8147a7746e7a
|
||||
IP: 127.0.0.1
|
||||
IP: ::1
|
||||
IP: 10.0.9.3
|
||||
IP: fe80::42:aff:fe00:903
|
||||
IP: 172.18.0.3
|
||||
IP: fe80::42:acff:fe12:3
|
||||
GET / HTTP/1.1
|
||||
Host: 10.0.9.3:80
|
||||
User-Agent: curl/7.35.0
|
||||
Accept: */*
|
||||
Accept-Encoding: gzip
|
||||
X-Forwarded-For: 192.168.99.1
|
||||
X-Forwarded-Host: 10.0.9.3:80
|
||||
X-Forwarded-Proto: http
|
||||
X-Forwarded-Server: 8fbc39271b4c
|
||||
|
||||
curl -H Host:whoami1.traefik http://$(docker-machine ip manager)
|
||||
Hostname: ba2c21488299
|
||||
IP: 127.0.0.1
|
||||
IP: ::1
|
||||
IP: 10.0.9.4
|
||||
IP: fe80::42:aff:fe00:904
|
||||
IP: 172.18.0.2
|
||||
IP: fe80::42:acff:fe12:2
|
||||
GET / HTTP/1.1
|
||||
Host: 10.0.9.4:80
|
||||
User-Agent: curl/7.35.0
|
||||
Accept: */*
|
||||
Accept-Encoding: gzip
|
||||
X-Forwarded-For: 192.168.99.1
|
||||
X-Forwarded-Host: 10.0.9.4:80
|
||||
X-Forwarded-Proto: http
|
||||
X-Forwarded-Server: 8fbc39271b4c
|
||||
```
|
||||
|
||||
Note that as Træfik is published, you can access it from any machine
|
||||
and not only the manager.
|
||||
|
||||
```sh
|
||||
curl -H Host:whoami0.traefik http://$(docker-machine ip worker1)
|
||||
Hostname: 8147a7746e7a
|
||||
IP: 127.0.0.1
|
||||
IP: ::1
|
||||
IP: 10.0.9.3
|
||||
IP: fe80::42:aff:fe00:903
|
||||
IP: 172.18.0.3
|
||||
IP: fe80::42:acff:fe12:3
|
||||
GET / HTTP/1.1
|
||||
Host: 10.0.9.3:80
|
||||
User-Agent: curl/7.35.0
|
||||
Accept: */*
|
||||
Accept-Encoding: gzip
|
||||
X-Forwarded-For: 192.168.99.1
|
||||
X-Forwarded-Host: 10.0.9.3:80
|
||||
X-Forwarded-Proto: http
|
||||
X-Forwarded-Server: 8fbc39271b4c
|
||||
|
||||
curl -H Host:whoami1.traefik http://$(docker-machine ip worker2)
|
||||
Hostname: ba2c21488299
|
||||
IP: 127.0.0.1
|
||||
IP: ::1
|
||||
IP: 10.0.9.4
|
||||
IP: fe80::42:aff:fe00:904
|
||||
IP: 172.18.0.2
|
||||
IP: fe80::42:acff:fe12:2
|
||||
GET / HTTP/1.1
|
||||
Host: 10.0.9.4:80
|
||||
User-Agent: curl/7.35.0
|
||||
Accept: */*
|
||||
Accept-Encoding: gzip
|
||||
X-Forwarded-For: 192.168.99.1
|
||||
X-Forwarded-Host: 10.0.9.4:80
|
||||
X-Forwarded-Proto: http
|
||||
X-Forwarded-Server: 8fbc39271b4c
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Swarm cluster
|
||||
|
||||
This section explains how to create a multi-host [swarm](https://docs.docker.com/swarm) cluster using [docker-machine](https://docs.docker.com/machine/) and how to deploy Træfɪk on it.
|
||||
The cluster will be made of:
|
||||
The cluster consists of:
|
||||
|
||||
- 2 servers
|
||||
- 1 swarm master
|
||||
@@ -10,16 +10,16 @@ The cluster will be made of:
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. You will need to install [docker-machine](https://docs.docker.com/machine/)
|
||||
2. You will need the latest [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
|
||||
1. You need to install [docker-machine](https://docs.docker.com/machine/)
|
||||
2. You need the latest [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
|
||||
|
||||
## Cluster provisioning
|
||||
|
||||
We will first follow [this guide](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) to create the cluster.
|
||||
We first follow [this guide](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) to create the cluster.
|
||||
|
||||
### Create machine `mh-keystore`
|
||||
|
||||
This machine will be the service registry of our cluster.
|
||||
This machine is the service registry of our cluster.
|
||||
|
||||
```sh
|
||||
docker-machine create -d virtualbox mh-keystore
|
||||
@@ -37,7 +37,7 @@ docker run -d \
|
||||
|
||||
### Create machine `mhs-demo0`
|
||||
|
||||
This machine will have a swarm master and a swarm agent on it.
|
||||
This machine is a swarm master and a swarm agent on it.
|
||||
|
||||
```sh
|
||||
docker-machine create -d virtualbox \
|
||||
@@ -50,7 +50,7 @@ docker-machine create -d virtualbox \
|
||||
|
||||
### Create machine `mhs-demo1`
|
||||
|
||||
This machine will have a swarm agent on it.
|
||||
This machine have a swarm agent on it.
|
||||
|
||||
```sh
|
||||
docker-machine create -d virtualbox \
|
||||
@@ -84,14 +84,14 @@ docker $(docker-machine config mhs-demo0) run \
|
||||
-l DEBUG \
|
||||
-c /dev/null \
|
||||
--docker \
|
||||
--docker.domain traefik \
|
||||
--docker.endpoint tcp://$(docker-machine ip mhs-demo0):3376 \
|
||||
--docker.domain=traefik \
|
||||
--docker.endpoint=tcp://$(docker-machine ip mhs-demo0):3376 \
|
||||
--docker.tls \
|
||||
--docker.tls.ca /ssl/ca.pem \
|
||||
--docker.tls.cert /ssl/server.pem \
|
||||
--docker.tls.key /ssl/server-key.pem \
|
||||
--docker.tls.ca=/ssl/ca.pem \
|
||||
--docker.tls.cert=/ssl/server.pem \
|
||||
--docker.tls.key=/ssl/server-key.pem \
|
||||
--docker.tls.insecureSkipVerify \
|
||||
--docker.watch \
|
||||
--docker.watch \
|
||||
--web
|
||||
```
|
||||
|
||||
@@ -102,7 +102,7 @@ Let's explain this command:
|
||||
- `-v /var/lib/boot2docker/:/ssl`: mount the ssl keys generated by docker-machine
|
||||
- `-c /dev/null`: empty config file
|
||||
- `--docker`: enable docker backend
|
||||
- `--docker.endpoint tcp://172.18.0.1:3376`: connect to the swarm master using the docker_gwbridge network
|
||||
- `--docker.endpoint=tcp://172.18.0.1:3376`: connect to the swarm master using the docker_gwbridge network
|
||||
- `--docker.tls`: enable TLS using the docker-machine keys
|
||||
- `--web`: activate the webUI on port 8080
|
||||
|
||||
|
@@ -1,43 +1,59 @@
|
||||
zk:
|
||||
image: bobrik/zookeeper
|
||||
net: host
|
||||
environment:
|
||||
ZK_CONFIG: tickTime=2000,initLimit=10,syncLimit=5,maxClientCnxns=128,forceSync=no,clientPort=2181
|
||||
ZK_ID: 1
|
||||
|
||||
master:
|
||||
image: mesosphere/mesos-master:0.28.1-2.0.20.ubuntu1404
|
||||
net: host
|
||||
environment:
|
||||
MESOS_ZK: zk://127.0.0.1:2181/mesos
|
||||
MESOS_HOSTNAME: 127.0.0.1
|
||||
MESOS_IP: 127.0.0.1
|
||||
MESOS_QUORUM: 1
|
||||
MESOS_CLUSTER: docker-compose
|
||||
MESOS_WORK_DIR: /var/lib/mesos
|
||||
|
||||
slave:
|
||||
image: mesosphere/mesos-slave:0.28.1-2.0.20.ubuntu1404
|
||||
net: host
|
||||
pid: host
|
||||
privileged: true
|
||||
environment:
|
||||
MESOS_MASTER: zk://127.0.0.1:2181/mesos
|
||||
MESOS_HOSTNAME: 127.0.0.1
|
||||
MESOS_IP: 127.0.0.1
|
||||
MESOS_CONTAINERIZERS: docker,mesos
|
||||
volumes:
|
||||
- /sys/fs/cgroup:/sys/fs/cgroup
|
||||
- /usr/bin/docker:/usr/bin/docker:ro
|
||||
- /usr/lib/x86_64-linux-gnu/libapparmor.so.1:/usr/lib/x86_64-linux-gnu/libapparmor.so.1:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /lib/x86_64-linux-gnu/libsystemd-journal.so.0:/lib/x86_64-linux-gnu/libsystemd-journal.so.0
|
||||
|
||||
marathon:
|
||||
image: mesosphere/marathon:v1.1.1
|
||||
net: host
|
||||
environment:
|
||||
MARATHON_MASTER: zk://127.0.0.1:2181/mesos
|
||||
MARATHON_ZK: zk://127.0.0.1:2181/marathon
|
||||
MARATHON_HOSTNAME: 127.0.0.1
|
||||
command: --event_subscriber http_callback
|
||||
version: '2'
|
||||
services:
|
||||
zookeeper:
|
||||
image: netflixoss/exhibitor:1.5.2
|
||||
hostname: zookeeper
|
||||
ports:
|
||||
- "2181:2181"
|
||||
mesos-master:
|
||||
image: mesosphere/marathon:v1.2.0-RC6
|
||||
hostname: mesos-master
|
||||
entrypoint: [ "mesos-master" ]
|
||||
ports:
|
||||
- "5050:5050"
|
||||
links:
|
||||
- zookeeper
|
||||
environment:
|
||||
- MESOS_CLUSTER=local
|
||||
- MESOS_HOSTNAME=mesos-master.docker
|
||||
- MESOS_LOG_DIR=/var/log
|
||||
- MESOS_WORK_DIR=/var/lib/mesos
|
||||
- MESOS_QUORUM=1
|
||||
- MESOS_ZK=zk://zookeeper:2181/mesos
|
||||
mesos-slave:
|
||||
image: mesosphere/mesos-slave-dind:0.2.4_mesos-0.27.2_docker-1.8.2_ubuntu-14.04.4
|
||||
entrypoint:
|
||||
- mesos-slave
|
||||
privileged: true
|
||||
hostname: mesos-slave
|
||||
ports:
|
||||
- "5051:5051"
|
||||
links:
|
||||
- zookeeper
|
||||
- mesos-master
|
||||
environment:
|
||||
- MESOS_CONTAINERIZERS=docker,mesos
|
||||
- MESOS_ISOLATOR=cgroups/cpu,cgroups/mem
|
||||
- MESOS_LOG_DIR=/var/log
|
||||
- MESOS_MASTER=zk://zookeeper:2181/mesos
|
||||
- MESOS_PORT=5051
|
||||
- MESOS_WORK_DIR=/var/lib/mesos
|
||||
- MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins
|
||||
- MESOS_EXECUTOR_SHUTDOWN_GRACE_PERIOD=90secs
|
||||
- MESOS_DOCKER_STOP_TIMEOUT=60secs
|
||||
- MESOS_RESOURCES=cpus:2;mem:2048;disk:20480;ports(*):[12000-12999]
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||
marathon:
|
||||
image: mesosphere/marathon:v1.2.0-RC6
|
||||
ports:
|
||||
- "8080:8080"
|
||||
links:
|
||||
- zookeeper
|
||||
- mesos-master
|
||||
extra_hosts:
|
||||
- "mesos-slave:172.17.0.1"
|
||||
environment:
|
||||
- MARATHON_ZK=zk://zookeeper:2181/marathon
|
||||
- MARATHON_MASTER=zk://zookeeper:2181/mesos
|
@@ -1,111 +0,0 @@
|
||||
# 3 Services for the 3 endpoints of the Ingress
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: service1
|
||||
labels:
|
||||
app: whoami
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- port: 80
|
||||
nodePort: 30283
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
name: https
|
||||
selector:
|
||||
app: whoami
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: service2
|
||||
labels:
|
||||
app: whoami
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- port: 80
|
||||
nodePort: 30284
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: whoami
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: service3
|
||||
labels:
|
||||
app: whoami
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- port: 80
|
||||
nodePort: 30285
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: whoami
|
||||
---
|
||||
# A single RC matching all Services
|
||||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
name: whoami
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: whoami
|
||||
spec:
|
||||
containers:
|
||||
- name: whoami
|
||||
image: emilevauge/whoami
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
# An Ingress with 2 hosts and 3 endpoints
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: whoami-ingress
|
||||
spec:
|
||||
rules:
|
||||
- host: foo.localhost
|
||||
http:
|
||||
paths:
|
||||
- path: /bar
|
||||
backend:
|
||||
serviceName: service1
|
||||
servicePort: 80
|
||||
- host: bar.localhost
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: service2
|
||||
servicePort: 80
|
||||
- backend:
|
||||
serviceName: service3
|
||||
servicePort: 80
|
||||
|
||||
---
|
||||
# Another Ingress with PathPrefixStrip
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: whoami-ingress-stripped
|
||||
annotations:
|
||||
traefik.frontend.rule.type: "PathPrefixStrip"
|
||||
spec:
|
||||
rules:
|
||||
- host: foo.localhost
|
||||
http:
|
||||
paths:
|
||||
- path: /prefixWillBeStripped
|
||||
backend:
|
||||
serviceName: service1
|
||||
servicePort: 80
|
@@ -1,10 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
kubectl create -f - << EOF
|
||||
kind: Namespace
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: kube-system
|
||||
labels:
|
||||
name: kube-system
|
||||
EOF
|
||||
name: kube-system
|
@@ -1,31 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
labels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
k8s-app: traefik-ingress-lb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
name: traefik-ingress-lb
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 60
|
||||
containers:
|
||||
- image: traefik
|
||||
name: traefik-ingress-lb
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 80
|
||||
hostPort: 80
|
||||
- containerPort: 443
|
||||
hostPort: 443
|
||||
- containerPort: 8080
|
||||
args:
|
||||
- --web
|
||||
- --kubernetes
|
||||
- --logLevel=DEBUG
|
99
examples/k8s/cheese-deployments.yaml
Normal file
99
examples/k8s/cheese-deployments.yaml
Normal file
@@ -0,0 +1,99 @@
|
||||
---
|
||||
kind: Deployment
|
||||
apiVersion: extensions/v1beta1
|
||||
metadata:
|
||||
name: stilton
|
||||
labels:
|
||||
app: cheese
|
||||
cheese: stilton
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cheese
|
||||
task: stilton
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cheese
|
||||
task: stilton
|
||||
version: v0.0.1
|
||||
spec:
|
||||
containers:
|
||||
- name: cheese
|
||||
image: errm/cheese:stilton
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 50Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 50Mi
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
kind: Deployment
|
||||
apiVersion: extensions/v1beta1
|
||||
metadata:
|
||||
name: cheddar
|
||||
labels:
|
||||
app: cheese
|
||||
cheese: cheddar
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cheese
|
||||
task: cheddar
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cheese
|
||||
task: cheddar
|
||||
version: v0.0.1
|
||||
spec:
|
||||
containers:
|
||||
- name: cheese
|
||||
image: errm/cheese:cheddar
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 50Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 50Mi
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
kind: Deployment
|
||||
apiVersion: extensions/v1beta1
|
||||
metadata:
|
||||
name: wensleydale
|
||||
labels:
|
||||
app: cheese
|
||||
cheese: wensleydale
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cheese
|
||||
task: wensleydale
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cheese
|
||||
task: wensleydale
|
||||
version: v0.0.1
|
||||
spec:
|
||||
containers:
|
||||
- name: cheese
|
||||
image: errm/cheese:wensleydale
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 50Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 50Mi
|
||||
ports:
|
||||
- containerPort: 80
|
27
examples/k8s/cheese-ingress.yaml
Normal file
27
examples/k8s/cheese-ingress.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: cheese
|
||||
spec:
|
||||
rules:
|
||||
- host: stilton.local
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: stilton
|
||||
servicePort: http
|
||||
- host: cheddar.local
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: cheddar
|
||||
servicePort: http
|
||||
- host: wensleydale.local
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: wensleydale
|
||||
servicePort: http
|
39
examples/k8s/cheese-services.yaml
Normal file
39
examples/k8s/cheese-services.yaml
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: stilton
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
targetPort: 80
|
||||
port: 80
|
||||
selector:
|
||||
app: cheese
|
||||
task: stilton
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: cheddar
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
targetPort: 80
|
||||
port: 80
|
||||
selector:
|
||||
app: cheese
|
||||
task: cheddar
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: wensleydale
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
targetPort: 80
|
||||
port: 80
|
||||
selector:
|
||||
app: cheese
|
||||
task: wensleydale
|
23
examples/k8s/cheeses-ingress.yaml
Normal file
23
examples/k8s/cheeses-ingress.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: cheeses
|
||||
annotations:
|
||||
traefik.frontend.rule.type: pathprefixstrip
|
||||
spec:
|
||||
rules:
|
||||
- host: cheeses.local
|
||||
http:
|
||||
paths:
|
||||
- path: /stilton
|
||||
backend:
|
||||
serviceName: stilton
|
||||
servicePort: http
|
||||
- path: /cheddar
|
||||
backend:
|
||||
serviceName: cheddar
|
||||
servicePort: http
|
||||
- path: /wensleydale
|
||||
backend:
|
||||
serviceName: wensleydale
|
||||
servicePort: http
|
41
examples/k8s/traefik.yaml
Normal file
41
examples/k8s/traefik.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
apiVersion: extensions/v1beta1
|
||||
metadata:
|
||||
name: traefik-ingress-controller
|
||||
namespace: kube-system
|
||||
labels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
k8s-app: traefik-ingress-lb
|
||||
name: traefik-ingress-lb
|
||||
version: v1.1.0
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 60
|
||||
hostNetwork: true
|
||||
containers:
|
||||
- image: traefik:v1.1.0
|
||||
name: traefik-ingress-lb
|
||||
resources:
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 30Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 20Mi
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
hostPort: 80
|
||||
- name: admin
|
||||
containerPort: 8080
|
||||
args:
|
||||
- --web
|
||||
- --kubernetes
|
28
examples/k8s/ui.yaml
Normal file
28
examples/k8s/ui.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: traefik-web-ui
|
||||
namespace: kube-system
|
||||
spec:
|
||||
selector:
|
||||
k8s-app: traefik-ingress-lb
|
||||
ports:
|
||||
- name: web
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: traefik-web-ui
|
||||
namespace: kube-system
|
||||
spec:
|
||||
rules:
|
||||
- host: traefik-ui.local
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: traefik-web-ui
|
||||
servicePort: web
|
@@ -6,7 +6,4 @@ Copyright
|
||||
//go:generate mkdir -p static
|
||||
//go:generate go-bindata -pkg autogen -o autogen/gen.go ./static/... ./templates/...
|
||||
|
||||
//go:generate mkdir -p vendor/github.com/docker/docker/autogen/dockerversion
|
||||
//go:generate cp script/dockerversion vendor/github.com/docker/docker/autogen/dockerversion/dockerversion.go
|
||||
|
||||
package main
|
||||
|
271
glide.lock
generated
271
glide.lock
generated
@@ -1,143 +1,153 @@
|
||||
hash: 234c57cf3696be155463b9a92cd8d104bb52c16c821b64dd24d8d88097d80dd8
|
||||
updated: 2016-07-18T17:58:15.732937572+02:00
|
||||
hash: 1bbeb842ee639ccc6e2edf8cc13fc2759cb96e3d839a1aec7b7f6af4fb89c8e1
|
||||
updated: 2016-11-09T19:24:00.762904389+01:00
|
||||
imports:
|
||||
- name: github.com/abbot/go-http-auth
|
||||
version: cb4372376e1e00e9f6ab9ec142e029302c9e7140
|
||||
- name: github.com/boltdb/bolt
|
||||
version: acc803f0ced151102ed51bf824f8709ebd6602bc
|
||||
version: f4c032d907f61f08dba2d719c58f108a1abb8e81
|
||||
- name: github.com/BurntSushi/toml
|
||||
version: 99064174e013895bbd9b025c31100bd1d9b590ca
|
||||
- name: github.com/BurntSushi/ty
|
||||
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
|
||||
subpackages:
|
||||
- fun
|
||||
- name: github.com/cenkalti/backoff
|
||||
version: cdf48bbc1eb78d1349cbda326a4a037f7ba565c6
|
||||
- name: github.com/cenk/backoff
|
||||
version: 8edc80b07f38c27352fb186d971c628a6c32552b
|
||||
- name: github.com/codahale/hdrhistogram
|
||||
version: f8ad88b59a584afeee9d334eff879b104439117b
|
||||
- name: github.com/codegangsta/cli
|
||||
version: 1efa31f08b9333f1bd4882d61f9d668a70cd902e
|
||||
- name: github.com/codegangsta/negroni
|
||||
version: dc6b9d037e8dab60cbfc09c61d6932537829be8b
|
||||
version: 3f7ce7b928e14ff890b067e5bbbc80af73690a9c
|
||||
- name: github.com/containous/flaeg
|
||||
version: b98687da5c323650f4513fda6b6203fcbdec9313
|
||||
version: a731c034dda967333efce5f8d276aeff11f8ff87
|
||||
- name: github.com/containous/mux
|
||||
version: a819b77bba13f0c0cbe36e437bc2e948411b3996
|
||||
- name: github.com/containous/staert
|
||||
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
|
||||
version: 1e26a71803e428fd933f5f9c8e50a26878f53147
|
||||
- name: github.com/coreos/etcd
|
||||
version: 1c9e0a0e33051fed6c05c141e6fcbfe5c7f2a899
|
||||
subpackages:
|
||||
- client
|
||||
- pkg/pathutil
|
||||
- pkg/types
|
||||
- name: github.com/coreos/go-systemd
|
||||
version: 43e4800a6165b4e02bb2a36673c54b230d6f7b26
|
||||
subpackages:
|
||||
- daemon
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
|
||||
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/docker/distribution
|
||||
version: f8083b7ff32b224921c5f66c0f4df3e58dab49f5
|
||||
version: 99cb7c0946d2f5a38015443e515dc916295064d7
|
||||
subpackages:
|
||||
- reference
|
||||
- context
|
||||
- digest
|
||||
- reference
|
||||
- registry/api/errcode
|
||||
- registry/api/v2
|
||||
- registry/client
|
||||
- registry/client/auth
|
||||
- registry/client/transport
|
||||
- registry/client
|
||||
- context
|
||||
- registry/api/v2
|
||||
- registry/storage/cache
|
||||
- registry/storage/cache/memory
|
||||
- uuid
|
||||
- name: github.com/docker/docker
|
||||
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
|
||||
version: 534753663161334baba06f13b8efa4cad22b5bc5
|
||||
subpackages:
|
||||
- namesgenerator
|
||||
- pkg/namesgenerator
|
||||
- pkg/random
|
||||
- api/types/backend
|
||||
- builder
|
||||
- builder/dockerignore
|
||||
- cliconfig
|
||||
- cliconfig/configfile
|
||||
- daemon/graphdriver
|
||||
- image
|
||||
- image/v1
|
||||
- layer
|
||||
- namesgenerator
|
||||
- opts
|
||||
- pkg/archive
|
||||
- pkg/chrootarchive
|
||||
- pkg/fileutils
|
||||
- pkg/gitutils
|
||||
- pkg/homedir
|
||||
- pkg/httputils
|
||||
- pkg/idtools
|
||||
- pkg/ioutils
|
||||
- pkg/jsonlog
|
||||
- pkg/jsonmessage
|
||||
- pkg/longpath
|
||||
- pkg/mflag
|
||||
- pkg/mount
|
||||
- pkg/namesgenerator
|
||||
- pkg/plugins
|
||||
- pkg/plugins/transport
|
||||
- pkg/pools
|
||||
- pkg/progress
|
||||
- pkg/promise
|
||||
- pkg/random
|
||||
- pkg/reexec
|
||||
- pkg/signal
|
||||
- pkg/stdcopy
|
||||
- pkg/streamformatter
|
||||
- pkg/stringid
|
||||
- pkg/symlink
|
||||
- pkg/system
|
||||
- pkg/tarsum
|
||||
- pkg/term
|
||||
- pkg/term/windows
|
||||
- pkg/urlutil
|
||||
- reference
|
||||
- registry
|
||||
- runconfig/opts
|
||||
- pkg/homedir
|
||||
- pkg/jsonlog
|
||||
- pkg/system
|
||||
- pkg/term/windows
|
||||
- image
|
||||
- image/v1
|
||||
- pkg/ioutils
|
||||
- opts
|
||||
- pkg/httputils
|
||||
- pkg/mflag
|
||||
- pkg/stringid
|
||||
- pkg/tarsum
|
||||
- pkg/mount
|
||||
- pkg/signal
|
||||
- pkg/urlutil
|
||||
- builder
|
||||
- builder/dockerignore
|
||||
- pkg/archive
|
||||
- pkg/fileutils
|
||||
- pkg/progress
|
||||
- pkg/streamformatter
|
||||
- layer
|
||||
- pkg/longpath
|
||||
- api/types/backend
|
||||
- pkg/chrootarchive
|
||||
- pkg/gitutils
|
||||
- pkg/symlink
|
||||
- pkg/idtools
|
||||
- pkg/pools
|
||||
- daemon/graphdriver
|
||||
- pkg/reexec
|
||||
- pkg/plugins
|
||||
- pkg/plugins/transport
|
||||
- name: github.com/docker/engine-api
|
||||
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
|
||||
version: 62043eb79d581a32ea849645277023c550732e52
|
||||
subpackages:
|
||||
- client
|
||||
- types
|
||||
- types/events
|
||||
- types/filters
|
||||
- types/container
|
||||
- types/network
|
||||
- client/transport
|
||||
- client/transport/cancellable
|
||||
- types
|
||||
- types/blkiodev
|
||||
- types/container
|
||||
- types/events
|
||||
- types/filters
|
||||
- types/network
|
||||
- types/reference
|
||||
- types/registry
|
||||
- types/strslice
|
||||
- types/swarm
|
||||
- types/time
|
||||
- types/versions
|
||||
- types/blkiodev
|
||||
- types/strslice
|
||||
- name: github.com/docker/go-connections
|
||||
version: 990a1a1a70b0da4c4cb70e117971a4f0babfbf1a
|
||||
version: 988efe982fdecb46f01d53465878ff1f2ff411ce
|
||||
subpackages:
|
||||
- nat
|
||||
- sockets
|
||||
- tlsconfig
|
||||
- nat
|
||||
- name: github.com/docker/go-units
|
||||
version: f2d77a61e3c169b43402a0a1e84f06daf29b8190
|
||||
version: f2145db703495b2e525c59662db69a7344b00bb8
|
||||
- name: github.com/docker/leadership
|
||||
version: 0a913e2d71a12fd14a028452435cb71ac8d82cb6
|
||||
- name: github.com/docker/libcompose
|
||||
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
|
||||
version: d1876c1d68527a49c0aac22a0b161acc7296b740
|
||||
subpackages:
|
||||
- docker
|
||||
- project
|
||||
- project/events
|
||||
- project/options
|
||||
- config
|
||||
- docker
|
||||
- docker/builder
|
||||
- docker/client
|
||||
- docker/network
|
||||
- labels
|
||||
- logger
|
||||
- lookup
|
||||
- project
|
||||
- project/events
|
||||
- project/options
|
||||
- utils
|
||||
- yaml
|
||||
- version
|
||||
- yaml
|
||||
- name: github.com/docker/libkv
|
||||
version: 35d3e2084c650109e7bcc7282655b1bc8ba924ff
|
||||
version: 3fce6a0f26e07da3eac45796a8e255547a47a750
|
||||
subpackages:
|
||||
- store
|
||||
- store/boltdb
|
||||
@@ -147,56 +157,93 @@ imports:
|
||||
- name: github.com/donovanhide/eventsource
|
||||
version: fd1de70867126402be23c306e1ce32828455d85b
|
||||
- name: github.com/elazarl/go-bindata-assetfs
|
||||
version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2
|
||||
version: 9a6736ed45b44bf3835afeebb3034b57ed329f3e
|
||||
- name: github.com/gambol99/go-marathon
|
||||
version: a558128c87724cd7430060ef5aedf39f83937f55
|
||||
- name: github.com/go-check/check
|
||||
version: 4f90aeace3a26ad7021961c297b22c42160c7b25
|
||||
- name: github.com/gogo/protobuf
|
||||
version: 99cb9b23110011cc45571c901ecae6f6f5e65cd3
|
||||
subpackages:
|
||||
- proto
|
||||
- name: github.com/golang/glog
|
||||
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
|
||||
- name: github.com/google/go-github
|
||||
version: 55263f30529cb06f5b478efc333390b791cfe3b1
|
||||
subpackages:
|
||||
- github
|
||||
- name: github.com/google/go-querystring
|
||||
version: 9235644dd9e52eeae6fa48efd539fdc351a0af53
|
||||
subpackages:
|
||||
- query
|
||||
- name: github.com/gorilla/context
|
||||
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
|
||||
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
|
||||
- name: github.com/hashicorp/consul
|
||||
version: 548fb6eb3f407147e20d923521296e0500f57ef0
|
||||
version: d8e2fb7dd594163e25a89bc52c1a4613f5c5bfb8
|
||||
subpackages:
|
||||
- api
|
||||
- name: github.com/hashicorp/go-cleanhttp
|
||||
version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d
|
||||
version: ad28ea4487f05916463e2423a55166280e8254b5
|
||||
- name: github.com/hashicorp/go-version
|
||||
version: e96d3840402619007766590ecea8dd7af1292276
|
||||
- name: github.com/hashicorp/serf
|
||||
version: 6c4672d66fc6312ddde18399262943e21175d831
|
||||
version: b03bf85930b2349eb04b97c8fac437495296e3e7
|
||||
subpackages:
|
||||
- coordinate
|
||||
- serf
|
||||
- name: github.com/jarcoal/httpmock
|
||||
version: 145b10d659265440f062c31ea15326166bae56ee
|
||||
- name: github.com/libkermit/compose
|
||||
version: cadc5a3b83a15790174bd7fbc75ea2529785e772
|
||||
subpackages:
|
||||
- check
|
||||
- name: github.com/libkermit/docker
|
||||
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
|
||||
subpackages:
|
||||
- compose
|
||||
- name: github.com/libkermit/docker-check
|
||||
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
|
||||
subpackages:
|
||||
- compose
|
||||
version: 55e3595409924fcfbb850811e5a7cdbe8960a0b7
|
||||
- name: github.com/mailgun/manners
|
||||
version: fada45142db3f93097ca917da107aa3fad0ffcb5
|
||||
version: a585afd9d65c0e05f6c003f921e71ebc05074f4f
|
||||
- name: github.com/mailgun/timetools
|
||||
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
|
||||
- name: github.com/mattn/go-shellwords
|
||||
version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24
|
||||
- name: github.com/mesos/mesos-go
|
||||
version: 068d5470506e3780189fe607af40892814197c5e
|
||||
subpackages:
|
||||
- detector
|
||||
- detector/zoo
|
||||
- mesos
|
||||
- mesosproto
|
||||
- mesosutil
|
||||
- upid
|
||||
- name: github.com/mesosphere/mesos-dns
|
||||
version: b47dc4c19f215e98da687b15b4c64e70f629bea5
|
||||
repo: https://github.com/containous/mesos-dns.git
|
||||
vcs: git
|
||||
subpackages:
|
||||
- detect
|
||||
- errorutil
|
||||
- logging
|
||||
- models
|
||||
- records
|
||||
- records/labels
|
||||
- records/state
|
||||
- util
|
||||
- name: github.com/Microsoft/go-winio
|
||||
version: ce2922f643c8fd76b46cadc7f404a06282678b34
|
||||
- name: github.com/miekg/dns
|
||||
version: 5d001d020961ae1c184f9f8152fdc73810481677
|
||||
- name: github.com/mitchellh/mapstructure
|
||||
version: f3009df150dadf309fdee4a54ed65c124afad715
|
||||
- name: github.com/moul/http2curl
|
||||
version: b1479103caacaa39319f75e7f57fc545287fca0d
|
||||
- name: github.com/NYTimes/gziphandler
|
||||
version: f6438dbf4a82c56684964b03956aa727b0d7816b
|
||||
- name: github.com/ogier/pflag
|
||||
version: 45c278ab3607870051a2ea9040bb85fcb8557481
|
||||
- name: github.com/opencontainers/runc
|
||||
version: 1b49d9b4db3fe7ffbe53698a79124e7b4aa78180
|
||||
version: 02f8fa7863dd3f82909a73e2061897828460d52f
|
||||
subpackages:
|
||||
- libcontainer/user
|
||||
- name: github.com/parnurzeal/gorequest
|
||||
version: 6e8ad4ebdee4bec2934ed5afaaa1c7b877832a17
|
||||
version: e30af16d4e485943aab0b0885ad6bdbb8c0d3dc7
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||
subpackages:
|
||||
@@ -204,82 +251,90 @@ imports:
|
||||
- name: github.com/ryanuber/go-glob
|
||||
version: 572520ed46dbddaed19ea3d9541bdd0494163693
|
||||
- name: github.com/samuel/go-zookeeper
|
||||
version: e64db453f3512cade908163702045e0f31137843
|
||||
version: 87e1bca4477a3cc767ca71be023ced183d74e538
|
||||
subpackages:
|
||||
- zk
|
||||
- name: github.com/satori/go.uuid
|
||||
version: 879c5887cd475cd7864858769793b2ceb0d44feb
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: a283a10442df8dc09befd873fab202bf8a253d6a
|
||||
version: 3ec0642a7fb6488f65b06f9040adc67e3990296a
|
||||
- name: github.com/streamrail/concurrent-map
|
||||
version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287
|
||||
version: 8bf1e9bacbf65b10c81d0f4314cf2b1ebef728b5
|
||||
- name: github.com/stretchr/objx
|
||||
version: cbeaeb16a013161a98496fad62933b1d21786672
|
||||
- name: github.com/stretchr/testify
|
||||
version: d77da356e56a7428ad25149ca77381849a6a5232
|
||||
version: 976c720a22c8eb4eb6a0b4348ad85ad12491a506
|
||||
subpackages:
|
||||
- mock
|
||||
- assert
|
||||
- mock
|
||||
- name: github.com/thoas/stats
|
||||
version: 69e3c072eec2df2df41afe6214f62eb940e4cd80
|
||||
version: 152b5d051953fdb6e45f14b6826962aadc032324
|
||||
- name: github.com/tv42/zbase32
|
||||
version: 03389da7e0bf9844767f82690f4d68fc097a1306
|
||||
- name: github.com/ugorji/go
|
||||
version: b94837a2404ab90efe9289e77a70694c355739cb
|
||||
subpackages:
|
||||
- codec
|
||||
- name: github.com/unrolled/render
|
||||
version: 198ad4d8b8a4612176b804ca10555b222a086b40
|
||||
version: 526faf80cd4b305bb8134abea8d20d5ced74faa6
|
||||
- name: github.com/urfave/negroni
|
||||
version: e0e50f7dc431c043cb33f91b09c3419d48b7cff5
|
||||
- name: github.com/vdemeester/docker-events
|
||||
version: 20e6d2db238723e68197a9e3c6c34c99a9893a9c
|
||||
version: be74d4929ec1ad118df54349fda4b0cba60f849b
|
||||
- name: github.com/vdemeester/shakers
|
||||
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
||||
- name: github.com/vulcand/oxy
|
||||
version: ab7796d7036b425fbc945853cd1b7e7adf43b0d6
|
||||
version: fcc76b52eb8568540a020b7a99e854d9d752b364
|
||||
repo: https://github.com/containous/oxy.git
|
||||
vcs: git
|
||||
subpackages:
|
||||
- cbreaker
|
||||
- connlimit
|
||||
- forward
|
||||
- memmetrics
|
||||
- roundrobin
|
||||
- stream
|
||||
- utils
|
||||
- memmetrics
|
||||
- name: github.com/vulcand/predicate
|
||||
version: 19b9dde14240d94c804ae5736ad0e1de10bf8fe6
|
||||
- name: github.com/vulcand/route
|
||||
version: cb89d787ddbb1c5849a7ac9f79004c1fd12a4a32
|
||||
- name: github.com/vulcand/vulcand
|
||||
version: 28a4e5c0892167589737b95ceecbcef00295be50
|
||||
version: bed092e10989250b48bdb6aa3b0557b207f05c80
|
||||
subpackages:
|
||||
- plugin/rewrite
|
||||
- plugin
|
||||
- conntracker
|
||||
- plugin
|
||||
- plugin/rewrite
|
||||
- router
|
||||
- name: github.com/xenolf/lego
|
||||
version: b2fad6198110326662e9e356a97199078a4a775c
|
||||
subpackages:
|
||||
- acme
|
||||
- name: golang.org/x/crypto
|
||||
version: f28b56427a527c2e35c0bcac123f0a6a8a943cd3
|
||||
version: d81fdb778bf2c40a91b24519d60cdc5767318829
|
||||
subpackages:
|
||||
- bcrypt
|
||||
- blowfish
|
||||
- ocsp
|
||||
- name: golang.org/x/net
|
||||
version: b400c2eff1badec7022a8c8f5bea058b6315eed7
|
||||
subpackages:
|
||||
- context
|
||||
- publicsuffix
|
||||
- proxy
|
||||
- publicsuffix
|
||||
- name: golang.org/x/sys
|
||||
version: 62bee037599929a6e9146f29d10dd5208c43507d
|
||||
subpackages:
|
||||
- unix
|
||||
- windows
|
||||
- name: gopkg.in/fsnotify.v1
|
||||
version: a8a77c9133d2d6fd8334f3260d06f60e8d80a5fb
|
||||
version: 944cff21b3baf3ced9a880365682152ba577d348
|
||||
- name: gopkg.in/mgo.v2
|
||||
version: 29cc868a5ca65f401ff318143f9408d02f4799cc
|
||||
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
|
||||
subpackages:
|
||||
- bson
|
||||
- name: gopkg.in/square/go-jose.v1
|
||||
version: e3f973b66b91445ec816dd7411ad1b6495a5a2fc
|
||||
version: aa2e30fdd1fe9dd3394119af66451ae790d50e0d
|
||||
subpackages:
|
||||
- cipher
|
||||
- json
|
||||
@@ -294,15 +349,19 @@ testImports:
|
||||
version: 3f9db97f856818214da2e1057f8ad84803971cff
|
||||
- name: github.com/gorilla/mux
|
||||
version: 9fa818a44c2bf1396a17f9d5a3c0f6dd39d2ff8e
|
||||
- name: github.com/libkermit/docker-check
|
||||
version: cbe0ef03b3d23070eac4d00ba8828f2cc7f7e5a3
|
||||
- name: github.com/spf13/pflag
|
||||
version: 5644820622454e71517561946e3d94b9f9db6842
|
||||
- name: github.com/vbatts/tar-split
|
||||
version: 28bc4c32f9fa9725118a685c9ddd7ffdbdbfe2c8
|
||||
version: bd4c5d64c3e9297f410025a3b1bd0c58f659e721
|
||||
subpackages:
|
||||
- archive/tar
|
||||
- tar/asm
|
||||
- tar/storage
|
||||
- archive/tar
|
||||
- name: github.com/xeipuuv/gojsonpointer
|
||||
version: e0fe6f68307607d540ed8eac07a342c33fa1b54a
|
||||
- name: github.com/xeipuuv/gojsonreference
|
||||
version: e02fc20de94c78484cd5ffb007f8af96be030a45
|
||||
- name: github.com/xeipuuv/gojsonschema
|
||||
version: 66a3de92def23708184148ae337750915875e7c1
|
||||
version: 00f9fafb54d2244d291b86ab63d12c38bd5c3886
|
||||
|
52
glide.yaml
52
glide.yaml
@@ -5,12 +5,12 @@ import:
|
||||
subpackages:
|
||||
- fun
|
||||
- package: github.com/Sirupsen/logrus
|
||||
- package: github.com/cenkalti/backoff
|
||||
- package: github.com/codegangsta/negroni
|
||||
- package: github.com/cenk/backoff
|
||||
- package: github.com/urfave/negroni
|
||||
- package: github.com/containous/flaeg
|
||||
version: b98687da5c323650f4513fda6b6203fcbdec9313
|
||||
version: a731c034dda967333efce5f8d276aeff11f8ff87
|
||||
- package: github.com/vulcand/oxy
|
||||
version: ab7796d7036b425fbc945853cd1b7e7adf43b0d6
|
||||
version: fcc76b52eb8568540a020b7a99e854d9d752b364
|
||||
repo: https://github.com/containous/oxy.git
|
||||
vcs: git
|
||||
subpackages:
|
||||
@@ -21,9 +21,9 @@ import:
|
||||
- stream
|
||||
- utils
|
||||
- package: github.com/containous/staert
|
||||
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
|
||||
version: 1e26a71803e428fd933f5f9c8e50a26878f53147
|
||||
- package: github.com/docker/engine-api
|
||||
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
|
||||
version: 62043eb79d581a32ea849645277023c550732e52
|
||||
subpackages:
|
||||
- client
|
||||
- types
|
||||
@@ -56,7 +56,7 @@ import:
|
||||
- package: github.com/thoas/stats
|
||||
- package: github.com/unrolled/render
|
||||
- package: github.com/vdemeester/docker-events
|
||||
version: 20e6d2db238723e68197a9e3c6c34c99a9893a9c
|
||||
version: be74d4929ec1ad118df54349fda4b0cba60f849b
|
||||
- package: github.com/vulcand/vulcand
|
||||
subpackages:
|
||||
- plugin/rewrite
|
||||
@@ -68,17 +68,45 @@ import:
|
||||
subpackages:
|
||||
- context
|
||||
- package: gopkg.in/fsnotify.v1
|
||||
- package: github.com/libkermit/docker-check
|
||||
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
|
||||
- package: github.com/libkermit/compose
|
||||
version: cadc5a3b83a15790174bd7fbc75ea2529785e772
|
||||
- package: github.com/libkermit/docker
|
||||
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
|
||||
version: 55e3595409924fcfbb850811e5a7cdbe8960a0b7
|
||||
- package: github.com/docker/docker
|
||||
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
|
||||
version: 534753663161334baba06f13b8efa4cad22b5bc5
|
||||
subpackages:
|
||||
- namesgenerator
|
||||
- package: github.com/go-check/check
|
||||
- package: github.com/docker/libcompose
|
||||
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
|
||||
version: d1876c1d68527a49c0aac22a0b161acc7296b740
|
||||
- package: github.com/mattn/go-shellwords
|
||||
- package: github.com/vdemeester/shakers
|
||||
- package: github.com/ryanuber/go-glob
|
||||
- package: github.com/mesos/mesos-go
|
||||
subpackages:
|
||||
- mesosproto
|
||||
- mesos
|
||||
- upid
|
||||
- mesosutil
|
||||
- detector
|
||||
- package: github.com/jarcoal/httpmock
|
||||
- package: github.com/mesosphere/mesos-dns
|
||||
version: b47dc4c19f215e98da687b15b4c64e70f629bea5
|
||||
repo: https://github.com/containous/mesos-dns.git
|
||||
vcs: git
|
||||
- package: github.com/tv42/zbase32
|
||||
- package: github.com/abbot/go-http-auth
|
||||
- package: github.com/miekg/dns
|
||||
version: 5d001d020961ae1c184f9f8152fdc73810481677
|
||||
- package: github.com/NYTimes/gziphandler
|
||||
- package: github.com/docker/leadership
|
||||
- package: github.com/satori/go.uuid
|
||||
version: ^1.1.0
|
||||
- package: github.com/mitchellh/mapstructure
|
||||
version: f3009df150dadf309fdee4a54ed65c124afad715
|
||||
- package: github.com/coreos/go-systemd
|
||||
version: v12
|
||||
subpackages:
|
||||
- daemon
|
||||
- package: github.com/google/go-github
|
||||
- package: github.com/hashicorp/go-version
|
@@ -75,7 +75,7 @@ func (s *AccessLogSuite) TestAccessLog(c *check.C) {
|
||||
c.Assert(tokens[9], checker.Equals, fmt.Sprintf("%d", i+1))
|
||||
c.Assert(strings.HasPrefix(tokens[10], "frontend"), checker.True)
|
||||
c.Assert(strings.HasPrefix(tokens[11], "http://127.0.0.1:808"), checker.True)
|
||||
c.Assert(regexp.MustCompile("^\\d+\\.\\d+.*s$").MatchString(tokens[12]), checker.True)
|
||||
c.Assert(regexp.MustCompile("^\\d+ms$").MatchString(tokens[12]), checker.True)
|
||||
}
|
||||
}
|
||||
c.Assert(count, checker.Equals, 3)
|
||||
|
@@ -5,17 +5,22 @@ import (
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/containous/staert"
|
||||
"github.com/docker/libkv"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/consul"
|
||||
"github.com/go-check/check"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"errors"
|
||||
"github.com/containous/traefik/cluster"
|
||||
"github.com/containous/traefik/integration/utils"
|
||||
"github.com/containous/traefik/provider"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Consul test suites (using libcompose)
|
||||
@@ -24,7 +29,7 @@ type ConsulSuite struct {
|
||||
kv store.Store
|
||||
}
|
||||
|
||||
func (s *ConsulSuite) SetUpSuite(c *check.C) {
|
||||
func (s *ConsulSuite) setupConsul(c *check.C) {
|
||||
s.createComposeProject(c, "consul")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
@@ -52,7 +57,56 @@ func (s *ConsulSuite) SetUpSuite(c *check.C) {
|
||||
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) {
|
||||
s.setupConsul(c)
|
||||
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
||||
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost})
|
||||
defer os.Remove(file)
|
||||
@@ -70,6 +124,7 @@ func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
|
||||
}
|
||||
|
||||
func (s *ConsulSuite) TestNominalConfiguration(c *check.C) {
|
||||
s.setupConsul(c)
|
||||
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
||||
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost})
|
||||
defer os.Remove(file)
|
||||
@@ -190,3 +245,279 @@ func (s *ConsulSuite) TestNominalConfiguration(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
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)
|
||||
|
||||
}
|
||||
func (s *ConsulSuite) TestCommandStoreConfig(c *check.C) {
|
||||
s.setupConsul(c)
|
||||
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
||||
|
||||
cmd := exec.Command(traefikBinary, "storeconfig", "--configFile=fixtures/simple_web.toml", "--consul.endpoint="+consulHost+":8500")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// wait for traefik finish without error
|
||||
cmd.Wait()
|
||||
|
||||
//CHECK
|
||||
checkmap := map[string]string{
|
||||
"/traefik/loglevel": "DEBUG",
|
||||
"/traefik/defaultentrypoints/0": "http",
|
||||
"/traefik/entrypoints/http/address": ":8000",
|
||||
"/traefik/web/address": ":8080",
|
||||
"/traefik/consul/endpoint": (consulHost + ":8500"),
|
||||
}
|
||||
|
||||
for key, value := range checkmap {
|
||||
var p *store.KVPair
|
||||
err = utils.Try(60*time.Second, func() error {
|
||||
p, err = s.kv.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
c.Assert(string(p.Value), checker.Equals, value)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
type TestStruct struct {
|
||||
String string
|
||||
Int int
|
||||
}
|
||||
|
||||
func (s *ConsulSuite) TestDatastore(c *check.C) {
|
||||
s.setupConsul(c)
|
||||
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
|
||||
kvSource, err := staert.NewKvSource(store.CONSUL, []string{consulHost + ":8500"}, &store.Config{
|
||||
ConnectionTimeout: 10 * time.Second,
|
||||
}, "traefik")
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
ctx := context.Background()
|
||||
datastore1, err := cluster.NewDataStore(ctx, *kvSource, &TestStruct{}, nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
datastore2, err := cluster.NewDataStore(ctx, *kvSource, &TestStruct{}, nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
setter1, _, err := datastore1.Begin()
|
||||
c.Assert(err, checker.IsNil)
|
||||
err = setter1.Commit(&TestStruct{
|
||||
String: "foo",
|
||||
Int: 1,
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
time.Sleep(2 * time.Second)
|
||||
test1 := datastore1.Get().(*TestStruct)
|
||||
c.Assert(test1.String, checker.Equals, "foo")
|
||||
|
||||
test2 := datastore2.Get().(*TestStruct)
|
||||
c.Assert(test2.String, checker.Equals, "foo")
|
||||
|
||||
setter2, _, err := datastore2.Begin()
|
||||
c.Assert(err, checker.IsNil)
|
||||
err = setter2.Commit(&TestStruct{
|
||||
String: "bar",
|
||||
Int: 2,
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
time.Sleep(2 * time.Second)
|
||||
test1 = datastore1.Get().(*TestStruct)
|
||||
c.Assert(test1.String, checker.Equals, "bar")
|
||||
|
||||
test2 = datastore2.Get().(*TestStruct)
|
||||
c.Assert(test2.String, checker.Equals, "bar")
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(4)
|
||||
go func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
setter1, _, err := datastore1.Begin()
|
||||
c.Assert(err, checker.IsNil)
|
||||
err = setter1.Commit(&TestStruct{
|
||||
String: "datastore1",
|
||||
Int: i,
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
setter2, _, err := datastore2.Begin()
|
||||
c.Assert(err, checker.IsNil)
|
||||
err = setter2.Commit(&TestStruct{
|
||||
String: "datastore2",
|
||||
Int: i,
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
test1 := datastore1.Get().(*TestStruct)
|
||||
c.Assert(test1, checker.NotNil)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
test2 := datastore2.Get().(*TestStruct)
|
||||
c.Assert(test2, checker.NotNil)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
checker "github.com/vdemeester/shakers"
|
||||
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/containous/traefik/integration/utils"
|
||||
@@ -25,7 +26,7 @@ type EtcdSuite struct {
|
||||
kv store.Store
|
||||
}
|
||||
|
||||
func (s *EtcdSuite) SetUpSuite(c *check.C) {
|
||||
func (s *EtcdSuite) SetUpTest(c *check.C) {
|
||||
s.createComposeProject(c, "etcd")
|
||||
s.composeProject.Start(c)
|
||||
|
||||
@@ -54,6 +55,15 @@ func (s *EtcdSuite) SetUpSuite(c *check.C) {
|
||||
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) {
|
||||
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
|
||||
file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdHost string }{etcdHost})
|
||||
@@ -193,3 +203,266 @@ func (s *EtcdSuite) TestNominalConfiguration(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
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"))
|
||||
}
|
||||
|
||||
func (s *EtcdSuite) TestCommandStoreConfig(c *check.C) {
|
||||
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
|
||||
|
||||
cmd := exec.Command(traefikBinary, "storeconfig", "--configFile=fixtures/simple_web.toml", "--etcd.endpoint="+etcdHost+":4001")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// wait for traefik finish without error
|
||||
cmd.Wait()
|
||||
|
||||
//CHECK
|
||||
checkmap := map[string]string{
|
||||
"/traefik/loglevel": "DEBUG",
|
||||
"/traefik/defaultentrypoints/0": "http",
|
||||
"/traefik/entrypoints/http/address": ":8000",
|
||||
"/traefik/web/address": ":8080",
|
||||
"/traefik/etcd/endpoint": (etcdHost + ":4001"),
|
||||
}
|
||||
|
||||
for key, value := range checkmap {
|
||||
var p *store.KVPair
|
||||
err = utils.Try(60*time.Second, func() error {
|
||||
p, err = s.kv.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
c.Assert(string(p.Value), checker.Equals, value)
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -12,3 +12,4 @@ logLevel = "DEBUG"
|
||||
endpoint = "{{.DockerHost}}"
|
||||
|
||||
domain = "docker.localhost"
|
||||
exposedbydefault = true
|
25
integration/fixtures/https/clientca/README.md
Normal file
25
integration/fixtures/https/clientca/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# This is how the certs were created
|
||||
|
||||
```bash
|
||||
openssl req -new -newkey rsa:2048 -x509 -days 3650 -extensions v3_ca -keyout ca1.pem -out ca1.crt
|
||||
openssl req -new -newkey rsa:2048 -x509 -days 3650 -extensions v3_ca -keyout ca2.pem -out ca2.crt
|
||||
openssl req -new -newkey rsa:2048 -x509 -days 3650 -extensions v3_ca -keyout ca3.pem -out ca3.crt
|
||||
openssl rsa -in ca1.pem -out ca1.key
|
||||
openssl rsa -in ca2.pem -out ca2.key
|
||||
openssl rsa -in ca3.pem -out ca3.key
|
||||
cat ca1.crt ca2.crt > ca1and2.crt
|
||||
rm ca1.pem ca2.pem ca3.pem
|
||||
|
||||
openssl genrsa -out client1.key 2048
|
||||
openssl genrsa -out client2.key 2048
|
||||
openssl genrsa -out client3.key 2048
|
||||
|
||||
openssl req -key client1.key -new -out client1.csr
|
||||
openssl req -key client2.key -new -out client2.csr
|
||||
openssl req -key client3.key -new -out client3.csr
|
||||
|
||||
openssl x509 -req -days 3650 -in client1.csr -CA ca1.crt -CAkey ca1.key -CAcreateserial -out client1.crt
|
||||
openssl x509 -req -days 3650 -in client2.csr -CA ca2.crt -CAkey ca2.key -CAcreateserial -out client2.crt
|
||||
openssl x509 -req -days 3650 -in client3.csr -CA ca3.crt -CAkey ca3.key -CAcreateserial -out client3.crt
|
||||
|
||||
```
|
20
integration/fixtures/https/clientca/ca1.crt
Normal file
20
integration/fixtures/https/clientca/ca1.crt
Normal file
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDMjCCAhqgAwIBAgIJAKXHiSnQw6LqMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV
|
||||
BAMTD2NhMS5leGFtcGxlLmNvbTAeFw0xNjA2MTgxMzAyNDdaFw0yNjA2MTYxMzAy
|
||||
NDdaMBoxGDAWBgNVBAMTD2NhMS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
|
||||
BQADggEPADCCAQoCggEBAL9ZNf1Pqu30i/DUyAAbEVFfCvGEmN9hfGAK44IrBqfC
|
||||
1ziW2Lfg2AkswNIC/T6M+lcoN0ftPhJpnP2Cdz9U/gF9FMd/XAGY/SOiun7wC8so
|
||||
qdab7CMDlHP1c/XiL7lGEdm9RfynLcJ5JJn2X7mXwEZTviFtiJVmaoAl3TVNy3MZ
|
||||
ZyfjNac9sA5idpX66TpVO9tE1gu71nRkBvTEzO/IYv8rcWQmogvH7DN3UurP3RUK
|
||||
weij01rekG3OOOXUlQgZO6mhuvrKes9Xoc901bmTkOgTq7wIFf2AZozU4wy6kZfM
|
||||
0sdzmjMpuEr7oROepvtzFiVyNIEGDJ3QvEEY4QJaFvcCAwEAAaN7MHkwHQYDVR0O
|
||||
BBYEFFyJ/cSOOvcsfu+WLZbi/u3t8W/uMEoGA1UdIwRDMEGAFFyJ/cSOOvcsfu+W
|
||||
LZbi/u3t8W/uoR6kHDAaMRgwFgYDVQQDEw9jYTEuZXhhbXBsZS5jb22CCQClx4kp
|
||||
0MOi6jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCOBLJJF0esBVLX
|
||||
xmj0xa0TREXTxco40e/fmUU1cGYgl1UCCZI7MLDcl6k6Km9Sbp/LCpZx88mtLwGY
|
||||
wUss2mQ058kqiUrpb/U8xEbglLrRtsp1y8z7lood/8ru39zj1/9X4MFyqNi6390I
|
||||
zxZNf2QauUS1TMxgv6UhVE52JaAL+sn2hqA6IaSYeT9NFzFsulCr29mxlIC9SzUr
|
||||
Mbqri9LKX5aciy78+hQBKdXoJ5raRwttBvULabOrLhZdyvvL6QfcdgRV+JOT7vKn
|
||||
htQahWSKoqhdpM6Q2pXP42/MyuKXFB5Nk8fnFiIoXH0Bs9vlPLOvToM2jYJ+LlDd
|
||||
85qbL4eP
|
||||
-----END CERTIFICATE-----
|
27
integration/fixtures/https/clientca/ca1.key
Normal file
27
integration/fixtures/https/clientca/ca1.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAv1k1/U+q7fSL8NTIABsRUV8K8YSY32F8YArjgisGp8LXOJbY
|
||||
t+DYCSzA0gL9Poz6Vyg3R+0+Emmc/YJ3P1T+AX0Ux39cAZj9I6K6fvALyyip1pvs
|
||||
IwOUc/Vz9eIvuUYR2b1F/KctwnkkmfZfuZfARlO+IW2IlWZqgCXdNU3LcxlnJ+M1
|
||||
pz2wDmJ2lfrpOlU720TWC7vWdGQG9MTM78hi/ytxZCaiC8fsM3dS6s/dFQrB6KPT
|
||||
Wt6Qbc445dSVCBk7qaG6+sp6z1ehz3TVuZOQ6BOrvAgV/YBmjNTjDLqRl8zSx3Oa
|
||||
Mym4SvuhE56m+3MWJXI0gQYMndC8QRjhAloW9wIDAQABAoIBAGJ9g8mn6R5kImfK
|
||||
zksno4lTt2lLS/im0AMLd8E3bkyJgIgTNOeopupKC9HNUhaRMAYOoC24kpudmv3t
|
||||
2n1RvRB9FmX9SxlTavCdwQq3egqPGqRpS2lWXWI2dAKa8t+VjniZ8N00G9yeyFUr
|
||||
OGhqEMDiN9oy6/uiZK0jUDIwocjS5FZMBh+epM7/CnKj3uvqarmFXKcJ4ni28ww4
|
||||
RPrXDm+VvXa30/hK8q8Eo3C3u39TMvNEaRqMP/zqRY89fbpd1+Okno79dugFhz7D
|
||||
r/Jae9z4ChFBXegDmA/OkWOdLY5LyvwvpJpONjD/5wImY1OAJlFTg7S+2FcSVvCF
|
||||
diUJ7/ECgYEA9pHYlJsWAo/izRUVhKRtBAVVjnlidxExuvOGNXpyPjZd5ruXochu
|
||||
J6tAKA0rSE4RsISFVCrkQmjDgjyKa2D+o/hsTTlW3yrD4TSLI8/MrDtfCw9XRqeE
|
||||
KqfeqT79Hh0icnsUVYH4eoND9CKuJ/B9NcdyUqRPm7Pnrx07SnhGHd8CgYEAxqqy
|
||||
MPIDO2dadRqUIhWwMPIBegkZC1eeuv4pNEyukZc4+pXRshKXhvhmvz5NgsaSsKxZ
|
||||
O6FgqzgTceLEubVYF4hvy1TC+3Fc/PFvh4Fo3SKjtiJRJjRREDWBu6hl16Cw/83j
|
||||
k6Im//8WD1ri9iFf8RjrBwYH1xHqGTkNEUHl+ekCgYEAzlIWD6uCDFzIJGGLIvXP
|
||||
fvjTsadivE039r7Fw8QVCnfFtUetxyOHAUysH5d9a0BgTvtk8Zv+ao9tYXI1RUrh
|
||||
aOV8AlaDmbQYOj8UWsAL/OalTgTlO+r6jhLwH2DkvqkUZQUWa8KY4DMszoGihysW
|
||||
KsUcpYh2UMyGhqKINXVU/rMCgYEAqJxbG9trDtHLHjRuoPcTUJc01aQ/EzdMSpxH
|
||||
0FF8n6he/Z6GGMJaxHyyh4GTO3jZKwU7vrZaWzb+mdvC53KXz3FGoKXRzqIKL8uh
|
||||
wrn8jCJIG97ITMp+OmmPL/veY8HIN3NAwR4QR5jx2hpjIk51JSTm5FEj+k8EBmA7
|
||||
TPhG/XECgYA9e9B0jgR2aFSAWzpGMZYPW+NdGQlySv94AJmfF8U5J7PmU2BojvVn
|
||||
bhWNSQk2LI/mTjLgB+liYtLqFGkgIrJdbBOQ8hKSBPGQltSR0Dvf0ZK/0F1hqDTW
|
||||
m3AUvPZthNMNJIYkTav5a246tyKkmg11nUQsgoqdxCrEiLyv48PFnw==
|
||||
-----END RSA PRIVATE KEY-----
|
1
integration/fixtures/https/clientca/ca1.srl
Normal file
1
integration/fixtures/https/clientca/ca1.srl
Normal file
@@ -0,0 +1 @@
|
||||
83E81F36599F4400
|
40
integration/fixtures/https/clientca/ca1and2.crt
Normal file
40
integration/fixtures/https/clientca/ca1and2.crt
Normal file
@@ -0,0 +1,40 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDMjCCAhqgAwIBAgIJAKXHiSnQw6LqMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV
|
||||
BAMTD2NhMS5leGFtcGxlLmNvbTAeFw0xNjA2MTgxMzAyNDdaFw0yNjA2MTYxMzAy
|
||||
NDdaMBoxGDAWBgNVBAMTD2NhMS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
|
||||
BQADggEPADCCAQoCggEBAL9ZNf1Pqu30i/DUyAAbEVFfCvGEmN9hfGAK44IrBqfC
|
||||
1ziW2Lfg2AkswNIC/T6M+lcoN0ftPhJpnP2Cdz9U/gF9FMd/XAGY/SOiun7wC8so
|
||||
qdab7CMDlHP1c/XiL7lGEdm9RfynLcJ5JJn2X7mXwEZTviFtiJVmaoAl3TVNy3MZ
|
||||
ZyfjNac9sA5idpX66TpVO9tE1gu71nRkBvTEzO/IYv8rcWQmogvH7DN3UurP3RUK
|
||||
weij01rekG3OOOXUlQgZO6mhuvrKes9Xoc901bmTkOgTq7wIFf2AZozU4wy6kZfM
|
||||
0sdzmjMpuEr7oROepvtzFiVyNIEGDJ3QvEEY4QJaFvcCAwEAAaN7MHkwHQYDVR0O
|
||||
BBYEFFyJ/cSOOvcsfu+WLZbi/u3t8W/uMEoGA1UdIwRDMEGAFFyJ/cSOOvcsfu+W
|
||||
LZbi/u3t8W/uoR6kHDAaMRgwFgYDVQQDEw9jYTEuZXhhbXBsZS5jb22CCQClx4kp
|
||||
0MOi6jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCOBLJJF0esBVLX
|
||||
xmj0xa0TREXTxco40e/fmUU1cGYgl1UCCZI7MLDcl6k6Km9Sbp/LCpZx88mtLwGY
|
||||
wUss2mQ058kqiUrpb/U8xEbglLrRtsp1y8z7lood/8ru39zj1/9X4MFyqNi6390I
|
||||
zxZNf2QauUS1TMxgv6UhVE52JaAL+sn2hqA6IaSYeT9NFzFsulCr29mxlIC9SzUr
|
||||
Mbqri9LKX5aciy78+hQBKdXoJ5raRwttBvULabOrLhZdyvvL6QfcdgRV+JOT7vKn
|
||||
htQahWSKoqhdpM6Q2pXP42/MyuKXFB5Nk8fnFiIoXH0Bs9vlPLOvToM2jYJ+LlDd
|
||||
85qbL4eP
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDMjCCAhqgAwIBAgIJAKjhXgiuPQexMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV
|
||||
BAMTD2NhMi5leGFtcGxlLmNvbTAeFw0xNjA2MTgxMzAzMjJaFw0yNjA2MTYxMzAz
|
||||
MjJaMBoxGDAWBgNVBAMTD2NhMi5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
|
||||
BQADggEPADCCAQoCggEBAMx8S4U3tdeMGn1NEUNWCmD7pIYUCUhtORrn2rqF5b2M
|
||||
ZQJZXAIfWJ7KrGjn8W7KPx8/V2FREHF1Z6v1fpB2rfCIFo97HszhQEt6lduKup2j
|
||||
09ItpFjec7RahwaMksYDwl4PaxgKe2OYdLFJ/QIv8+I01vWPXFmHgZkBHQWhR5nV
|
||||
TvGM6MU834e+PXxCXfcaC8VYpbHYKYxHmM5Sxa5V9WlppBBshB0OL+KrCPXwPqHl
|
||||
StZPkG2p2qJUjCZ38uDx605RYaORZ0eDhrKj4M3lJzOTTcC4I77BzTb74+GcRT+R
|
||||
lJMrWrS22jNZONnawBdbTWIFM4PzaqVvE7qVwZK1M5UCAwEAAaN7MHkwHQYDVR0O
|
||||
BBYEFPooSq3ZvoyIzRQ96/dwUC0LDBvRMEoGA1UdIwRDMEGAFPooSq3ZvoyIzRQ9
|
||||
6/dwUC0LDBvRoR6kHDAaMRgwFgYDVQQDEw9jYTIuZXhhbXBsZS5jb22CCQCo4V4I
|
||||
rj0HsTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCvRgu11LrF7G9X
|
||||
yuvUwBZJ8FgjAMPwXQIAYg47tlvD9ZDiZgXVulWOm6aHpT520MjNO9f0oKpsrSsh
|
||||
7bsO4GSkbTPgGekbw4P3JtXAvlBEB5uabpdmF37Pg9s7dU/MeXCElzWF+yLVAo7o
|
||||
Hj1UlENxh08FzlErNw6Djy2FZAADeSZ3LmHUl+50rrp5/DxrEhkHFm8dTTjFVPnK
|
||||
KrnYLM8R7+v2Ysk6hTy4kwyiTKVZurK7ELRvS0RxWhtbVCXJ2HS1lv/LgEH1hyIP
|
||||
SwvyZ25JhcGrBAL/jpzTxdDEGsPfUSVfrUhrhDWxg0dzY+ptwdTWHqxyR2YKmOgU
|
||||
dKYIz/nK
|
||||
-----END CERTIFICATE-----
|
20
integration/fixtures/https/clientca/ca2.crt
Normal file
20
integration/fixtures/https/clientca/ca2.crt
Normal file
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDMjCCAhqgAwIBAgIJAKjhXgiuPQexMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV
|
||||
BAMTD2NhMi5leGFtcGxlLmNvbTAeFw0xNjA2MTgxMzAzMjJaFw0yNjA2MTYxMzAz
|
||||
MjJaMBoxGDAWBgNVBAMTD2NhMi5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
|
||||
BQADggEPADCCAQoCggEBAMx8S4U3tdeMGn1NEUNWCmD7pIYUCUhtORrn2rqF5b2M
|
||||
ZQJZXAIfWJ7KrGjn8W7KPx8/V2FREHF1Z6v1fpB2rfCIFo97HszhQEt6lduKup2j
|
||||
09ItpFjec7RahwaMksYDwl4PaxgKe2OYdLFJ/QIv8+I01vWPXFmHgZkBHQWhR5nV
|
||||
TvGM6MU834e+PXxCXfcaC8VYpbHYKYxHmM5Sxa5V9WlppBBshB0OL+KrCPXwPqHl
|
||||
StZPkG2p2qJUjCZ38uDx605RYaORZ0eDhrKj4M3lJzOTTcC4I77BzTb74+GcRT+R
|
||||
lJMrWrS22jNZONnawBdbTWIFM4PzaqVvE7qVwZK1M5UCAwEAAaN7MHkwHQYDVR0O
|
||||
BBYEFPooSq3ZvoyIzRQ96/dwUC0LDBvRMEoGA1UdIwRDMEGAFPooSq3ZvoyIzRQ9
|
||||
6/dwUC0LDBvRoR6kHDAaMRgwFgYDVQQDEw9jYTIuZXhhbXBsZS5jb22CCQCo4V4I
|
||||
rj0HsTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCvRgu11LrF7G9X
|
||||
yuvUwBZJ8FgjAMPwXQIAYg47tlvD9ZDiZgXVulWOm6aHpT520MjNO9f0oKpsrSsh
|
||||
7bsO4GSkbTPgGekbw4P3JtXAvlBEB5uabpdmF37Pg9s7dU/MeXCElzWF+yLVAo7o
|
||||
Hj1UlENxh08FzlErNw6Djy2FZAADeSZ3LmHUl+50rrp5/DxrEhkHFm8dTTjFVPnK
|
||||
KrnYLM8R7+v2Ysk6hTy4kwyiTKVZurK7ELRvS0RxWhtbVCXJ2HS1lv/LgEH1hyIP
|
||||
SwvyZ25JhcGrBAL/jpzTxdDEGsPfUSVfrUhrhDWxg0dzY+ptwdTWHqxyR2YKmOgU
|
||||
dKYIz/nK
|
||||
-----END CERTIFICATE-----
|
27
integration/fixtures/https/clientca/ca2.key
Normal file
27
integration/fixtures/https/clientca/ca2.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAzHxLhTe114wafU0RQ1YKYPukhhQJSG05GufauoXlvYxlAllc
|
||||
Ah9YnsqsaOfxbso/Hz9XYVEQcXVnq/V+kHat8IgWj3sezOFAS3qV24q6naPT0i2k
|
||||
WN5ztFqHBoySxgPCXg9rGAp7Y5h0sUn9Ai/z4jTW9Y9cWYeBmQEdBaFHmdVO8Yzo
|
||||
xTzfh749fEJd9xoLxVilsdgpjEeYzlLFrlX1aWmkEGyEHQ4v4qsI9fA+oeVK1k+Q
|
||||
banaolSMJnfy4PHrTlFho5FnR4OGsqPgzeUnM5NNwLgjvsHNNvvj4ZxFP5GUkyta
|
||||
tLbaM1k42drAF1tNYgUzg/NqpW8TupXBkrUzlQIDAQABAoIBAGFMg2LQL2Zw8+nL
|
||||
UfuIZUfgdViXEBO2ZQW4bQtzyu12cFm9y1n3MGPebEs+klL1STPFH/7eY8SY6MuZ
|
||||
9K8oyXs6RgHfw7gZNk6z9bqROFrqKVBJB3qB3uxiZv1mxjASednn3D2EP1IUqPHz
|
||||
EsCHsLRiECaoIHk5USFMtlKHe1pmmsvQrQX7EV9Qg0VSGvQlgxc/Pcg/WeB6uT6u
|
||||
CS2serWpUE2dBUTJisnUuL7F5/3JbPEPbUG4eeTcO8IafvgdOgFEc5qUlYCFFai0
|
||||
fvjSabXrJO9QE1Huw0gyC/5FHlVr5x4aJ8NzPKcMRYqn7jpdwA0eyLyBo/KtPIbJ
|
||||
6s0PFAECgYEA98cKuyaBXpPyG7/Y0C89Mzlt5+Qr0fpPksH6GEelPJVdhrdXP32W
|
||||
66ROgCVZpf2pQeCCHfXyWdZQwEdSf+8ee1DJMSNgIm4Usqp6yIDS0iZ7pPWz0KSI
|
||||
un/dm3lRE7hFMIQfbNf3rA0WD8Ani3c76eZruwQ5DNdXNOM+z1DN38UCgYEA00V4
|
||||
6UOCcA3romkXuIyeyh/tuJ6K1J3ApUxA+E42f4raSMSMgnlAwpL0Wmt11bBOmToi
|
||||
UAtwFcTfJRJSOvfmM/nd66592FAV/D4xcDIiNGh4xNDi8LSKmSj0WRYPU3YjkdFN
|
||||
SwI48LmQKMfj3P8fClazKsdcDccfO4pyhEK98ZECgYEAt8QZw1/1hw22/Lm2tgCz
|
||||
JTCswNXLYjqBldjkAenxNROaf/WucdpVeoMr7YLGEIQnakJ2fn4QtmxrC5BaMaRJ
|
||||
OTBbZ2RTQnXeR/yEf/x7X31HKrtIF7BP7/Ixi8PYTAXY2vjCzdkHScWS3S+opJlU
|
||||
CE/rCpNBNLLpbMI1rVDCv/kCgYEAkP3/sg67yQ00prx7JBOVsl/hNK/R1YMCQC8p
|
||||
838x1axEjGYfjDeM4zwZaKiRMPsTpgMIo2iGHtqCzh1Zw9B38znLPMD+6uJjhD5m
|
||||
jXpKkS8VmvVEmi89Y0mBEFacZAoS9TLwWccHruWa8vHkBror4luIEJbLLUV3wNQO
|
||||
LYjkdJECgYBcIjZ1iQiOmFL8lm/JlPOs2JcT33fjnubreHkiG42dZFN2S8D5MdU5
|
||||
JBP6IVVllPmbptw9T4wcw+bjVa0LQtQMGZLMxdx5nJp5dmFE0Pj8MjLpLy641Vlx
|
||||
5sv2O+eRpt4yCiuHcuvDrKPGTyM2YqF7ilQwSC5Cfki155InnU2QUg==
|
||||
-----END RSA PRIVATE KEY-----
|
1
integration/fixtures/https/clientca/ca2.srl
Normal file
1
integration/fixtures/https/clientca/ca2.srl
Normal file
@@ -0,0 +1 @@
|
||||
9EAC6D05226C2216
|
20
integration/fixtures/https/clientca/ca3.crt
Normal file
20
integration/fixtures/https/clientca/ca3.crt
Normal file
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDMjCCAhqgAwIBAgIJAK/JGxwwmv1jMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNV
|
||||
BAMTD2NhMy5leGFtcGxlLmNvbTAeFw0xNjA2MTgxMzAzNTNaFw0yNjA2MTYxMzAz
|
||||
NTNaMBoxGDAWBgNVBAMTD2NhMy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEB
|
||||
BQADggEPADCCAQoCggEBAO0B9iUp5w0m1NWC9QYWhxSE/emmmKcx99DWnzKZoIbj
|
||||
TSRQtyhx+9c2z1dYAFZQpdVRSKQFn1IO8s51wlIc01KLFflz4EvSfAKZiAnkOOez
|
||||
wzVQ8JWgKfOJV/ZctFPo4xtdhQmO1+U+YgSfU0ASEhHvHbIPTUJNRTfkJsGygq4q
|
||||
/p9uA1TsjM4bh6AkiD1OlGjp0lbkzn3LLYpXWvgGsuejsdVkJS5pn2NKjkqVhhEg
|
||||
g7hKKqm8Nc3mb+vGhw/fNppN/xeOswpMPaW77LppyFoDd/OmqqWrbzn2Fqw1nELh
|
||||
zfo7AkKPyRm8eU3wSTIdmaXx1R5qPjqEmYrrDZ2HXa8CAwEAAaN7MHkwHQYDVR0O
|
||||
BBYEFMR6dBZAeGgkxwSC/62xGwLEdXCdMEoGA1UdIwRDMEGAFMR6dBZAeGgkxwSC
|
||||
/62xGwLEdXCdoR6kHDAaMRgwFgYDVQQDEw9jYTMuZXhhbXBsZS5jb22CCQCvyRsc
|
||||
MJr9YzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQB3VgvPnLEEfbj4
|
||||
Z61q8oKneklZV+WpDyWSodI6M1l/0pXJCTDRROJ37KaQHLJRQo+rMJiYKvQkCU+y
|
||||
9JhLdRdMEzy++9hIWiNbDiy3BNMUiQOS1234WVFBosQ6uXNhXbL/Anl4xgiFFRZG
|
||||
FehjPo0XRvxmBHnrnE1Rce0EmU/1bwVglu8e7mG5bs0gQrXTRlTkxvucyi+B6npF
|
||||
2vuzxj4q+KgeEYURxCt95JoULtMY2c0VifcdweYDO/2sYEhOVi1N+PhPvZxJD6vR
|
||||
CxIuT6K3nRe58b1J/f7TH/dvURIb1mVG8+EDQVqa1bzH3JfytsIVG5VL1hppQlgZ
|
||||
Y0G4haMn
|
||||
-----END CERTIFICATE-----
|
27
integration/fixtures/https/clientca/ca3.key
Normal file
27
integration/fixtures/https/clientca/ca3.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA7QH2JSnnDSbU1YL1BhaHFIT96aaYpzH30NafMpmghuNNJFC3
|
||||
KHH71zbPV1gAVlCl1VFIpAWfUg7yznXCUhzTUosV+XPgS9J8ApmICeQ457PDNVDw
|
||||
laAp84lX9ly0U+jjG12FCY7X5T5iBJ9TQBISEe8dsg9NQk1FN+QmwbKCrir+n24D
|
||||
VOyMzhuHoCSIPU6UaOnSVuTOfcstilda+Aay56Ox1WQlLmmfY0qOSpWGESCDuEoq
|
||||
qbw1zeZv68aHD982mk3/F46zCkw9pbvsumnIWgN386aqpatvOfYWrDWcQuHN+jsC
|
||||
Qo/JGbx5TfBJMh2ZpfHVHmo+OoSZiusNnYddrwIDAQABAoIBAD87j71YkaFro8sX
|
||||
NmIabo2l8cx9uyqYZUKdkDnCzRZP3Iv80PEEgClqISVvgB+HQsdH+XZxXZFaFaPJ
|
||||
vT+FG0hhfUphhQ0VqipTZf0lm50N094MqzNwWOD12rcLAr2EW9s4Nz9WkflCjIop
|
||||
K9/jMlkAj86q0HUJApen0kNJah4nLPnkqKC9BQipGe2goERHA5N8MS/k/ODJrOzI
|
||||
qdD77wE5oov5sIePsGp3zCKNw89qoVTfkH8eYos6lPsAibYfgm5z7LwEtfe0ZizG
|
||||
myQfAYZx3Orl2eNxAb0c1dw+hNYKfeNAwn6h4J8AKuBHawZMb2ztlTj0ZludrhQC
|
||||
VuwAcrkCgYEA9sFsszjoSO8pXDnbaQ8UNGwy+C1t0fcZIOxIebKPcfipGio0R9vr
|
||||
SXEEfRQb+YdIFkQpe4hwAHt1Q75zh8z+oOTq8EHprxAwI9bzgyaEIHtGibvs99XT
|
||||
iWSPtL274CISiwSL8NzMl/orD6sDhmJqiXhwtf2SDubUJu3gz13CeRsCgYEA9eMM
|
||||
CYiOc4wLxKqyCqe3R86vnBFVauxp9eq9XTLvD+XoGqOksXupP8rE0jx26ILmKiQZ
|
||||
z99MGJoQicEpo+BW3L9wr6OJQZSrs+NqWCxlmFRJL+p3sw53B4zjgYaimNl5KH4G
|
||||
8pn7XbyRXtqhSBQ2kuNrkVI4SNxdEi1K+PoZ6v0CgYEAkwVcRsy5WftloVW3rTkW
|
||||
yMVO+R/YNyoLBtrBtAD4BugpmTVcQRR/dBqqmfvJTzuTb/Dc5oW8dg0ZKWvoWhmB
|
||||
/Utn0A71tSDoDfKc1J+2ScQpmxclceUtTMdl+EK0Fi827S2gU7q7DDI6RfOW/hLV
|
||||
d2MThNu4krhl32wMboFmxdECgYACwAhZbvKQ7kcPaw1Uuy18mx4xs6vt5zkELBz0
|
||||
Fua/mcWvzpa/+W8aLI1pAI4f6Z7jZ8X2Ijw6pjZ7I/LwR0kRbP64qC6X0i7dczS0
|
||||
ScLVIlQzOf8evJGuPvAoebYF2aDWSBqRyhEaqkpB8lYNdVRq7io81NuWTQipdGI7
|
||||
SKjTjQKBgBNSbDUWS2CAc+fsM/fBvYHKgrigVcKyvWwvb5LRXpWgPQH4LbqhG4uA
|
||||
g/mFTB5B1UBg9exN/dX6uegREdRA1/X+jRAzCqXYTFESo0/UrJhJQZ3waFKJ5PZK
|
||||
WChrSl6Lg5IMF2jYP5W0HwzbPPgRGibyELYBS5gZdAZpHgZToXeT
|
||||
-----END RSA PRIVATE KEY-----
|
1
integration/fixtures/https/clientca/ca3.srl
Normal file
1
integration/fixtures/https/clientca/ca3.srl
Normal file
@@ -0,0 +1 @@
|
||||
813218563E2DA0DE
|
17
integration/fixtures/https/clientca/client1.crt
Normal file
17
integration/fixtures/https/clientca/client1.crt
Normal file
@@ -0,0 +1,17 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICszCCAZsCCQCD6B82WZ9D/jANBgkqhkiG9w0BAQUFADAaMRgwFgYDVQQDEw9j
|
||||
YTEuZXhhbXBsZS5jb20wHhcNMTYwNjE4MTMzODQ0WhcNMjYwNjE2MTMzODQ0WjAd
|
||||
MRswGQYDVQQDExJjbGllbjEuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA
|
||||
A4IBDwAwggEKAoIBAQDH75aclHoZkQfmeH3XpapxyF2/K73SpesY8Y8I3B33WnQc
|
||||
vIy5y554pPJMtGH3ZwiN6ifo3TBEs/2WjSOWYwxfXh3utllYArApelSgUrI7SBkw
|
||||
0MqVm9NG+X9cCTeWsCf+nldHOCnCARuyBEpLeRDPVlNmfgdNK2ar0KqqEPnN5UV+
|
||||
k968nAuqSDtRL7Yl7R/uxEq4MglM/ocxOpGIrLTFh1eclPVaQ/dNsEJpkrnYQlFZ
|
||||
aI1sWDzWoqtpAO15PgBBNnkW9EJGrF8dAds64U2jYBZLMKuHwvuERkEgOKEdUrB3
|
||||
uu1dWJxS5BCumWM1C3xs6qsLeonWxZ5GXjjWObZNAgMBAAEwDQYJKoZIhvcNAQEF
|
||||
BQADggEBAJKME0zm/0eokmXMCLJhKYgm8hDKOHKRFRZl7vwy9SC9cwhdlhcPEeeP
|
||||
5M+dXQCtEQWgo7phoJX8nBipZ/Y0lsvDD/I3XucIkUlbOW4rk18L83nBIN4paKzW
|
||||
I4CMJ6FQ72thP7L7wC/lzp3+qUCxmcpGjw9pkU3b1pQPkxBfOvfGtRFMG6E5+xj/
|
||||
MtL3owJzpIH2f7vtmIszBPcgFWpvB0Sq0eJ+TwuC1huvcnmP+YZ7Iz0JhsSRw+pU
|
||||
yiO9ByItBbGfK8x+DfUwCVsCL7vNscpjvTCgT3x2FNvS+XmiHZmZtpRGJPzvdI0m
|
||||
Bd615VD5z+SoG/SiemqDGmt2Ank/zcI=
|
||||
-----END CERTIFICATE-----
|
15
integration/fixtures/https/clientca/client1.csr
Normal file
15
integration/fixtures/https/clientca/client1.csr
Normal file
@@ -0,0 +1,15 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICYjCCAUoCAQAwHTEbMBkGA1UEAxMSY2xpZW4xLmV4YW1wbGUuY29tMIIBIjAN
|
||||
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx++WnJR6GZEH5nh916Wqcchdvyu9
|
||||
0qXrGPGPCNwd91p0HLyMucueeKTyTLRh92cIjeon6N0wRLP9lo0jlmMMX14d7rZZ
|
||||
WAKwKXpUoFKyO0gZMNDKlZvTRvl/XAk3lrAn/p5XRzgpwgEbsgRKS3kQz1ZTZn4H
|
||||
TStmq9CqqhD5zeVFfpPevJwLqkg7US+2Je0f7sRKuDIJTP6HMTqRiKy0xYdXnJT1
|
||||
WkP3TbBCaZK52EJRWWiNbFg81qKraQDteT4AQTZ5FvRCRqxfHQHbOuFNo2AWSzCr
|
||||
h8L7hEZBIDihHVKwd7rtXVicUuQQrpljNQt8bOqrC3qJ1sWeRl441jm2TQIDAQAB
|
||||
oAAwDQYJKoZIhvcNAQEFBQADggEBAEZ67vahAVydtW6LTXFI0cVY88vqunCWpOzz
|
||||
UgJAzUnWG84CGDiyezj/llv/Nq3YbEEpBuxp/prOEwrJXAi/+tjx7wCh2iLJDqo2
|
||||
aNRUiAvR/XZgafxq4NUrAze70u7BWR3QX+XSaxmIEEX1z1KJDGTfY6tYpCZNlUr+
|
||||
/Hl6MXwlpWX0WR26zIrjx5u0dEsY4pviN6NxTZRQJxbQO1H1wHr6poVngOhIdErp
|
||||
h2ZcqvTcASTkIEdKR6R8E2iYklgxIHNLWKaHZ6aBqW7lW17WKNSiGPfPVAtFhUTk
|
||||
tBmgdVreAwMj+AdaweBVt0uBqb/9UKhqNThEnh4kJn1I0pMJzP4=
|
||||
-----END CERTIFICATE REQUEST-----
|
27
integration/fixtures/https/clientca/client1.key
Normal file
27
integration/fixtures/https/clientca/client1.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAx++WnJR6GZEH5nh916Wqcchdvyu90qXrGPGPCNwd91p0HLyM
|
||||
ucueeKTyTLRh92cIjeon6N0wRLP9lo0jlmMMX14d7rZZWAKwKXpUoFKyO0gZMNDK
|
||||
lZvTRvl/XAk3lrAn/p5XRzgpwgEbsgRKS3kQz1ZTZn4HTStmq9CqqhD5zeVFfpPe
|
||||
vJwLqkg7US+2Je0f7sRKuDIJTP6HMTqRiKy0xYdXnJT1WkP3TbBCaZK52EJRWWiN
|
||||
bFg81qKraQDteT4AQTZ5FvRCRqxfHQHbOuFNo2AWSzCrh8L7hEZBIDihHVKwd7rt
|
||||
XVicUuQQrpljNQt8bOqrC3qJ1sWeRl441jm2TQIDAQABAoIBAQCtD942uu7VooxM
|
||||
GpATUfsvclhzWdF9vNC7TpyY9q+ZpFpNZYgKaw5JL73sV1dVZ4IoFT9mec+GKKag
|
||||
4pqjWikjg7w1HPJJFEqYHKOUAwDz/3yOnKw+xBslnGF5sSDE9sYnx7eUljDPFVZ7
|
||||
yOrmWW0Li5W1afG4ApFkt8KCYx9X8E0Mren2nfqoobM2l2LKFcF1Xs+M0iUAOoeN
|
||||
ojS/NTvxjZibm92CMblp7x6e51y+oq3TJFoUwFSAj3U26jyubL5sYpJeAeTxyZg6
|
||||
Y+UcEGmCpW5gsZSvRxvNxzCS4bCl9KOZXvyFtcVswHppfTynba6x8hDF7LkfJS7H
|
||||
z/Ut+e+hAoGBAO2d2M2316eBpwP7x99DQSFpg7E/emdcfdRuHDEonBeJr2X2Fw0a
|
||||
O/ZtUxcccovy4wrJcqiZsmjqetRUZ6ymsOaaASsPG21ChegFjm9D6+ZtejHpbuo3
|
||||
8HQ//LW7hqoiRQh3ODihfYCTwwxIIuwAdUzoxpM9Yu57Zg7reYuNh48LAoGBANdn
|
||||
c003ay1cq1fuuDJKENj10UZGotRdBxt+X1A7MhpMAqSaYJ+V2XOXpuMbIiX/qDfF
|
||||
M4hcQhJygoCozNzsynztyIjpGtl57AG95igOi2Hah0OOMt/1Z9UwaukIaHHo8Tyk
|
||||
sPZYoxBTstZcdsyHnGdU6n90SA9oYLBB89E3AocHAoGBAITR27M6FTCLl2jxn0qc
|
||||
FFbx3OwB2JDYMXnBxr5vvbimfMWYpk/rnyLi/zQG8bxqmyCXdCDsML7WeqwfNgha
|
||||
8L0lzotcGW+cZK9KE9D7/WvDPC+UFSyU8jJ45fBLjz2ghEf0JBf7pORvM/K0i9ix
|
||||
dN/1qbH5+Ufm8Chc1Yb9KI37AoGAAbxDoYugwWzNtJenxD/0gsr4NKi9Bxj4xa/u
|
||||
9KaFcNDL9KeJv79lURkXrxy42bWFlW1xTNfxcFSb2I2DmQQPXZJM202FedsRm7H7
|
||||
+LalSNSJ4nFy13sSqxUIx3fZ35EQ4HwzMMjmB2ulNTTpgBxXlj2I5h35tqYQoVrm
|
||||
q/jVfGECgYEAkU0L9bp1NPMzXgVJ2Os1VPSzoOywUQfx4NCJhTA1oZR+20JFsQBN
|
||||
b6g0q6xean0xDuXjDRrjPET5V/GPOQ7stAPTLtqN42XPuRcFzSNj7Skh+ALTP5JS
|
||||
bNZgBMwxQsbS89bUjRTDlRK/isuNIyUn0Zn7QlEsZEvJw0cNR3wPOc4=
|
||||
-----END RSA PRIVATE KEY-----
|
17
integration/fixtures/https/clientca/client2.crt
Normal file
17
integration/fixtures/https/clientca/client2.crt
Normal file
@@ -0,0 +1,17 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICtDCCAZwCCQCerG0FImwiFjANBgkqhkiG9w0BAQUFADAaMRgwFgYDVQQDEw9j
|
||||
YTIuZXhhbXBsZS5jb20wHhcNMTYwNjE4MTgxNDUxWhcNMjYwNjE2MTgxNDUxWjAe
|
||||
MRwwGgYDVQQDExNjbGllbnQyLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAQ8AMIIBCgKCAQEA9LaFxeiG1qpBTUIEl2vxvojOSWR1xkayaGtGsXBuqucf
|
||||
wifulsVC7dw+t7lW42pWutIuR98iflAZ9+tFX7TsEITNIyV64ePn9TF935LW2DFm
|
||||
AFkqYdZcTJ4qOkdsbbiHznlaIDPbMhIZFd9L8NEhrDTHuTtCav3g5B5V4okJfeNh
|
||||
iSpKm3WLHP6lFwG1RISLhHTCeIMFxer49iHiQ+A33TV2l0bQGcv4e1+OoRsXGKGs
|
||||
3oY6RJZ0GqzjeYqoybsLGBvZPPd2e8nH3RZac66XHMexsHHTV7L2tpWZm+JunMRg
|
||||
hMXONc0b3V9mbUdrjHY/aGDPADEevZA4LLztGUc3VQIDAQABMA0GCSqGSIb3DQEB
|
||||
BQUAA4IBAQCFo6IXUznH/iSuJtWrMtMkJTEg7o2qKRDgFApzw1J2URdvyYery15w
|
||||
6FddKFvkYhNLFl9Nb3Z8HLxruZrrItqwjR2kIG9lW00uxnwIcgwTibmwDQL5nr7m
|
||||
1cWzelhY/TVwBpLXRMg1YOXU8NRkT1VjkTUCpyIETI8b+wed67MkrofOadaY+FUL
|
||||
gk1F3yDKz35UYIKnlxKwvrdySE8WFza0PmiXQDtTG1moTpe1BDEK1b60vhfudMBK
|
||||
9vhE8kTooF01+su9gLUcrjVknI9H5PHtXID7FDiZ/disIAaWqSQLuvg/Kvb/cAFd
|
||||
PwTKgnJQVcTKXkz2leJ6fsvzYwlANob1
|
||||
-----END CERTIFICATE-----
|
15
integration/fixtures/https/clientca/client2.csr
Normal file
15
integration/fixtures/https/clientca/client2.csr
Normal file
@@ -0,0 +1,15 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICYzCCAUsCAQAwHjEcMBoGA1UEAxMTY2xpZW50Mi5leGFtcGxlLmNvbTCCASIw
|
||||
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPS2hcXohtaqQU1CBJdr8b6Izklk
|
||||
dcZGsmhrRrFwbqrnH8In7pbFQu3cPre5VuNqVrrSLkffIn5QGffrRV+07BCEzSMl
|
||||
euHj5/Uxfd+S1tgxZgBZKmHWXEyeKjpHbG24h855WiAz2zISGRXfS/DRIaw0x7k7
|
||||
Qmr94OQeVeKJCX3jYYkqSpt1ixz+pRcBtUSEi4R0wniDBcXq+PYh4kPgN901dpdG
|
||||
0BnL+HtfjqEbFxihrN6GOkSWdBqs43mKqMm7Cxgb2Tz3dnvJx90WWnOulxzHsbBx
|
||||
01ey9raVmZvibpzEYITFzjXNG91fZm1Ha4x2P2hgzwAxHr2QOCy87RlHN1UCAwEA
|
||||
AaAAMA0GCSqGSIb3DQEBBQUAA4IBAQDHvJVKkKIqCWrJ9sZWQEYBaki76woJMjFW
|
||||
Ihyd12mzNfUW25hqfk7stablqu+CM/DhwOqLkxQleGAlp0BFo1wBOUDOgfrH5NVS
|
||||
9lAl7L/roEyRGH6V5/Hsbwi8zDsGOzWCuZk/gNGIZpB1c3TRXBUHsdqpz9FReDZf
|
||||
0HRD/7CH8hl96ZQTqhHE6+ysHzBB/4CuqbXVtTEhH52FdzCOpt5X0D6Pl/3lNlVd
|
||||
gMHAssoEa5E00XtjeJdxXuIKYbGLgldj6v+hHFX7k9UNveAXgYBbGtUQ9gA+uEf/
|
||||
qosVPEyvULj3aCJ8BSBulzPlhl9rNa/8Q1qUmzyCj28j3E4I22Oo
|
||||
-----END CERTIFICATE REQUEST-----
|
27
integration/fixtures/https/clientca/client2.key
Normal file
27
integration/fixtures/https/clientca/client2.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA9LaFxeiG1qpBTUIEl2vxvojOSWR1xkayaGtGsXBuqucfwifu
|
||||
lsVC7dw+t7lW42pWutIuR98iflAZ9+tFX7TsEITNIyV64ePn9TF935LW2DFmAFkq
|
||||
YdZcTJ4qOkdsbbiHznlaIDPbMhIZFd9L8NEhrDTHuTtCav3g5B5V4okJfeNhiSpK
|
||||
m3WLHP6lFwG1RISLhHTCeIMFxer49iHiQ+A33TV2l0bQGcv4e1+OoRsXGKGs3oY6
|
||||
RJZ0GqzjeYqoybsLGBvZPPd2e8nH3RZac66XHMexsHHTV7L2tpWZm+JunMRghMXO
|
||||
Nc0b3V9mbUdrjHY/aGDPADEevZA4LLztGUc3VQIDAQABAoIBAQDRxjVetjoAgup/
|
||||
w/wToeEVqEjN+WRMmAYQJQXwzaTQtFgxI/IPJQJ+zLKm5CZrxJichdhOnCUBisD4
|
||||
GaLarElAz9baLiLsyWXqdoakxUePBKmf2s/OFugAdgVU+C0m0Wz5vmVX/ZwFjCYc
|
||||
7dI3mc73xDcBvp7tAL1sT+Tn0PlmA3xURssiqC0J+4EtYzfHl1MvcQuU8JsVQjO3
|
||||
GvGWMr9EBO3oPa6yx3oWD4dn7xHLcCkuSJ6arIvASEaTyPg0Iu7roPrC7AXA+oGq
|
||||
+fbzJMqYZW6pMb8HZmxMt7X/srEq1kiyMYFy5fr+aun/vQ6596xjfFroEENJQY96
|
||||
+jir0biBAoGBAPutA9/2yo/fchRWLgpsWZ1SLXRWewFYqxlA7DluYXvqciYCXuKe
|
||||
S/+gLqHklHsc8YUwbEgW2oI9GPJ3iQps6XVNBaF9GvGjSrA+R6Ha3IT9ZUgvN4/d
|
||||
WOYiNRw5+eZ5PfxTufNK6EwXNwKR/siGEnWJ8AH0oNibTVzJHvMYSP2ZAoGBAPjq
|
||||
4a9MV6X7eShKHJtkqp33WWQWa3bidlmthhxjhPFlVnjJDj70oKGT5b/YcEFGBxPN
|
||||
JvTFJplQe0kLaeC49fPaEefARJe+HuCfUc1C73/q2o0pzvWf6Ut+W8ZZLKSC7aHH
|
||||
ZFAiZeMzzbCiqAbAAQFIgDp6m2U9mRYPTxKskoUdAoGBAMPEJzl1XMdhBfnvt0yA
|
||||
T4ziOV0/T9sSP7UbHSTnSYj8KuKKAYjBnVgwH1Xq2dyR/QSfT/sbW8jnAZrJhJ3J
|
||||
bifCS9j72ZOQcy54o3uxJMuF19y4bb3IbbhFb46PQmYWdTLrZb9ryxo1DKNBMTCF
|
||||
qaIoM7sxsFQNKbY884ggodYxAoGBAIbKopXL0HbIe65um5kmZSIPjK+fWGhTb+VW
|
||||
CxaaaaZSywWzUmyTCd0VesdtjDQ8mJ6HbH4FuMYzB9/hN1+CqWV4hFOsETjeslfO
|
||||
znxJr+nkIp9osXLfOnUwIsCBD6SyZb5CfDbMucHUDqGFI1osZR7txMpmFHo5ZgnF
|
||||
Fnu1Sc6dAoGAbgA9Cf6y8JGwr4/zGPDtaemBTLYMD8m/emdqGPgR7yVWXP/jTMqi
|
||||
o1EEWtpehALZMVZOsmSg7C/1J+nlHbuxPKsjjYK+V7aGsqXGx95lPyemd2cGcJN4
|
||||
fgoCyCahp2BnVCp7Gm3B/AzeZlH7n23qvbkJOsKGuocDycR9TIud+fk=
|
||||
-----END RSA PRIVATE KEY-----
|
17
integration/fixtures/https/clientca/client3.crt
Normal file
17
integration/fixtures/https/clientca/client3.crt
Normal file
@@ -0,0 +1,17 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICtDCCAZwCCQCBMhhWPi2g3jANBgkqhkiG9w0BAQUFADAaMRgwFgYDVQQDEw9j
|
||||
YTMuZXhhbXBsZS5jb20wHhcNMTYwNjE4MTgxNTAwWhcNMjYwNjE2MTgxNTAwWjAe
|
||||
MRwwGgYDVQQDExNjbGllbnQzLmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAQ8AMIIBCgKCAQEAyW1BnimfqeE2TFVCVaFSOnKucZMezOUs5CiNAECbgBPU
|
||||
ehAYNZCpKlD0ejZjc8/x2m0fnfHnmRdmCDprpI1gZV/dUMQCgmsq83pccnk3qFyn
|
||||
tdDTo0vxTKZhusihipKmVEpvtQP0hMH2De7QjwOpjxnIZwFH8anLr8EyUFNyF8fK
|
||||
k5emkMh8Xe5ppOTof36v9N/WPBW2/gxM9aj0l47CUSXjAUD8Fy8DeRtq/COywlnG
|
||||
DK25tnrQcX4RBwU9s8pHrXVrvmgLUEc3pWuxrwGJzQ/iY8l1mDDmhqjmcg1uGYOe
|
||||
hs/Olnx7pttUbd36mNXSSkjPeTabgpZDFtljMcTJwwIDAQABMA0GCSqGSIb3DQEB
|
||||
BQUAA4IBAQBUSxHFcGKaTBBj9peCgzr+buhPQ7F72uNe0uYZhcCn91KXECiFM+rh
|
||||
W13qcfsHDM/PPWN+TXHKzTxCHYv3fGkcAR/bUD206dXbO/T1Oc8UTciJFWXCxMK9
|
||||
zKlZgn48TcAIEhJodVcqWXr8hZ5Grxw4wB2DnTUTr5FuFS/f4gtlflPJzirxZGe8
|
||||
LPZb7QZ+LHxGK39QVY/g9LJxlWzbCytPBR0enb8ijjVj2+Sc+NntvQHqXedNFIql
|
||||
ns6X98nQtwFn9/MgLGbqOYNN36b15HddyDRgfZ6zMO3Aeve7GM5GqnpqhyprN91t
|
||||
gVaVUIxZCUNmcmtWu+a1QtK/MgLIpX4I
|
||||
-----END CERTIFICATE-----
|
15
integration/fixtures/https/clientca/client3.csr
Normal file
15
integration/fixtures/https/clientca/client3.csr
Normal file
@@ -0,0 +1,15 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICYzCCAUsCAQAwHjEcMBoGA1UEAxMTY2xpZW50My5leGFtcGxlLmNvbTCCASIw
|
||||
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMltQZ4pn6nhNkxVQlWhUjpyrnGT
|
||||
HszlLOQojQBAm4AT1HoQGDWQqSpQ9Ho2Y3PP8dptH53x55kXZgg6a6SNYGVf3VDE
|
||||
AoJrKvN6XHJ5N6hcp7XQ06NL8UymYbrIoYqSplRKb7UD9ITB9g3u0I8DqY8ZyGcB
|
||||
R/Gpy6/BMlBTchfHypOXppDIfF3uaaTk6H9+r/Tf1jwVtv4MTPWo9JeOwlEl4wFA
|
||||
/BcvA3kbavwjssJZxgytubZ60HF+EQcFPbPKR611a75oC1BHN6Vrsa8Bic0P4mPJ
|
||||
dZgw5oao5nINbhmDnobPzpZ8e6bbVG3d+pjV0kpIz3k2m4KWQxbZYzHEycMCAwEA
|
||||
AaAAMA0GCSqGSIb3DQEBBQUAA4IBAQB3ApcHgGHXXYqkNHDp3xaXBrsYNnGSoqQq
|
||||
PoFqNh2SVVh9D25hcfTrCXbv8Ng+rTEZqb4BMrSPxl5vNRQL78M70NMNE1bXcdW3
|
||||
XWSh7vLxCAmHx7DKNxQI/96o5lG6FFkmZNYZ4CllqXaW0hV3CTuy4ixGwz4JJ6vI
|
||||
caS4OEMB/r+kEm0jReGjalS/KWk61+bZnHWKAkvpPxIFKp8YMi84I+GlE1YfbXiC
|
||||
kYtwCFmEd5T6Ztz/f/DtCF0JuH+S/2+R+7APD1RbaU8lCMDA292zlVP2mro2WRnZ
|
||||
GAbynaqxaPQIn+2LBD5ytRx9aHALj58vq/PVpUUKb20RwIf74+t1
|
||||
-----END CERTIFICATE REQUEST-----
|
27
integration/fixtures/https/clientca/client3.key
Normal file
27
integration/fixtures/https/clientca/client3.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAyW1BnimfqeE2TFVCVaFSOnKucZMezOUs5CiNAECbgBPUehAY
|
||||
NZCpKlD0ejZjc8/x2m0fnfHnmRdmCDprpI1gZV/dUMQCgmsq83pccnk3qFyntdDT
|
||||
o0vxTKZhusihipKmVEpvtQP0hMH2De7QjwOpjxnIZwFH8anLr8EyUFNyF8fKk5em
|
||||
kMh8Xe5ppOTof36v9N/WPBW2/gxM9aj0l47CUSXjAUD8Fy8DeRtq/COywlnGDK25
|
||||
tnrQcX4RBwU9s8pHrXVrvmgLUEc3pWuxrwGJzQ/iY8l1mDDmhqjmcg1uGYOehs/O
|
||||
lnx7pttUbd36mNXSSkjPeTabgpZDFtljMcTJwwIDAQABAoIBAFYbTqG+SXLlw8B9
|
||||
8g2JGQ3DWK9UpSYSEk62xxAEjnUCBSLpHnBHlwlv8hMMjRdFHa6yV4G9l7PqPMPn
|
||||
tXxys3KiuIl+QVRfW80Z0ctd5l0ivs8Kpm54WH7b4YtnmScT6ea+q2JGfpECGZ17
|
||||
Kcz5U9LIwtLFyWuVmm1XuZp9EZj4HI7XgaktPom2f97k7oyQgKvgHVMUU+KYjS0X
|
||||
KTTf+PkSR/laV9TXNTRlFOwblh8tjrb/CohR/REQ5yTM04oRHhD+TkIV2qIUuM5Z
|
||||
P2hmIrMGhoJt01eJlZz73vXBBFAd/rzEIuKBroEG+culxje56qBbMKvfhmVJNuMz
|
||||
6AvkPYECgYEA+yXVRcU7Zqz7rqq7Mw3s3yC6LzE5SNO1hRl9J2N4ldHf4bUeHEuh
|
||||
ztW8Q7XiiftLcCGJPc3QxWRWwC8omhwefJzEmmVgpDeTMVjI/nNpgdgKSQfM77LX
|
||||
jZtH8q5JH4cMvJbFMV3JN24LSTrctryt+p/Jps3x0FzbgNGEDik7/y0CgYEAzVGB
|
||||
UESd09YvBjjW+1CJXHwgTh9Qc4L4VhAt7+O83SxGJEeVMYeTi/YHM2X5ytFBq/1w
|
||||
M+j9CWpNi7XoHOCVWZfQjOvsrX5R0s7iBrL9ikkmaEy95VEgywsXTaoJE37/l5GR
|
||||
j9s8P6o3KeCyce++2Qi+dHvAUEspLS1nW8KvAq8CgYAKwrw4mRLKe27tNPOAZIBZ
|
||||
rxVLIFjL/gYxBb6PCXwJL0zgZto7bCIqso22ePyT3OiGjWlL9J2VV48//MVIlRvZ
|
||||
Sv5Bf0Z8wsTTwHIcNOW4YoFOT79AJfGGZ7jVdRI8/5RUIEGis9oDPfvNz2/VhJAP
|
||||
xPjm5LwPqWreQhveX3XqoQKBgQC86JANVYTNotTTabrLspcf5AkpOACit09ciDhr
|
||||
7uMXsKO8v6wSzUZBUZXggaQqKwy8fUweRvGCFy/QKwesgiqIK3m0H2I9YutQBg/K
|
||||
0CcddB6Feo6CDnoYt1SynY8KRCBQyZvfe3zcqvVkb5xf3pF/SV9K943DktQJACyI
|
||||
LgEuewKBgF48AjNyi7zMgH0h5rsVimtjS7p8PhwZXyN70B/poykR3sxhCnChuNmt
|
||||
47MNOQrNgXCQivrZk1uh4v/itoVCDX/GYz+bqHh8MihALuSX3eRnVPk8oDjoM1lN
|
||||
Hks5wU7RK8gy14DPtTxfr0ca65v7kzu3nY1UwdFuTjcDwIv8EdGj
|
||||
-----END RSA PRIVATE KEY-----
|
35
integration/fixtures/https/clientca/https_1ca1config.toml
Normal file
35
integration/fixtures/https/clientca/https_1ca1config.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
logLevel = "DEBUG"
|
||||
|
||||
defaultEntryPoints = ["https"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":4443"
|
||||
[entryPoints.https.tls]
|
||||
ClientCAFiles = ["fixtures/https/clientca/ca1.crt"]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "fixtures/https/snitest.com.cert"
|
||||
KeyFile = "fixtures/https/snitest.com.key"
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "fixtures/https/snitest.org.cert"
|
||||
KeyFile = "fixtures/https/snitest.org.key"
|
||||
|
||||
[file]
|
||||
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://127.0.0.1:9010"
|
||||
[backends.backend2]
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://127.0.0.1:9020"
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host:snitest.com"
|
||||
[frontends.frontend2]
|
||||
backend = "backend2"
|
||||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Host:snitest.org"
|
35
integration/fixtures/https/clientca/https_2ca1config.toml
Normal file
35
integration/fixtures/https/clientca/https_2ca1config.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
logLevel = "DEBUG"
|
||||
|
||||
defaultEntryPoints = ["https"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":4443"
|
||||
[entryPoints.https.tls]
|
||||
ClientCAFiles = ["fixtures/https/clientca/ca1and2.crt"]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "fixtures/https/snitest.com.cert"
|
||||
KeyFile = "fixtures/https/snitest.com.key"
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "fixtures/https/snitest.org.cert"
|
||||
KeyFile = "fixtures/https/snitest.org.key"
|
||||
|
||||
[file]
|
||||
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://127.0.0.1:9010"
|
||||
[backends.backend2]
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://127.0.0.1:9020"
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host:snitest.com"
|
||||
[frontends.frontend2]
|
||||
backend = "backend2"
|
||||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Host:snitest.org"
|
35
integration/fixtures/https/clientca/https_2ca2config.toml
Normal file
35
integration/fixtures/https/clientca/https_2ca2config.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
logLevel = "DEBUG"
|
||||
|
||||
defaultEntryPoints = ["https"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.https]
|
||||
address = ":4443"
|
||||
[entryPoints.https.tls]
|
||||
ClientCAFiles = ["fixtures/https/clientca/ca1.crt", "fixtures/https/clientca/ca2.crt"]
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "fixtures/https/snitest.com.cert"
|
||||
KeyFile = "fixtures/https/snitest.com.key"
|
||||
[[entryPoints.https.tls.certificates]]
|
||||
CertFile = "fixtures/https/snitest.org.cert"
|
||||
KeyFile = "fixtures/https/snitest.org.key"
|
||||
|
||||
[file]
|
||||
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
url = "http://127.0.0.1:9010"
|
||||
[backends.backend2]
|
||||
[backends.backend2.servers.server1]
|
||||
url = "http://127.0.0.1:9020"
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Host:snitest.com"
|
||||
[frontends.frontend2]
|
||||
backend = "backend2"
|
||||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Host:snitest.org"
|
9
integration/fixtures/mesos/simple.toml
Normal file
9
integration/fixtures/mesos/simple.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
|
||||
logLevel = "DEBUG"
|
||||
|
||||
[mesos]
|
@@ -30,6 +30,7 @@ func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) {
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
}
|
||||
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
@@ -41,6 +42,9 @@ func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) {
|
||||
cs := conn.ConnectionState()
|
||||
err = cs.PeerCertificates[0].VerifyHostname("snitest.com")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("certificate did not match SNI servername"))
|
||||
|
||||
proto := conn.ConnectionState().NegotiatedProtocol
|
||||
c.Assert(proto, checker.Equals, "h2")
|
||||
}
|
||||
|
||||
// TestWithSNIConfigRoute involves a client sending HTTPS requests with
|
||||
@@ -93,6 +97,164 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) {
|
||||
c.Assert(resp.StatusCode, checker.Equals, 205)
|
||||
}
|
||||
|
||||
// TestWithClientCertificateAuthentication
|
||||
// The client has to send a certificate signed by a CA trusted by the server
|
||||
func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/https/clientca/https_1ca1config.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
Certificates: []tls.Certificate{},
|
||||
}
|
||||
// Connection without client certificate should fail
|
||||
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
|
||||
|
||||
// Connect with client certificate signed by ca1
|
||||
cert, err := tls.LoadX509KeyPair("fixtures/https/clientca/client1.crt", "fixtures/https/clientca/client1.key")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
|
||||
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
|
||||
conn.Close()
|
||||
|
||||
// Connect with client signed by ca2 should fail
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
Certificates: []tls.Certificate{},
|
||||
}
|
||||
cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client2.crt", "fixtures/https/clientca/client2.key")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
|
||||
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
|
||||
|
||||
}
|
||||
|
||||
// TestWithClientCertificateAuthentication
|
||||
// Use two CA:s and test that clients with client signed by either of them can connect
|
||||
func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAs(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/https/clientca/https_2ca1config.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
Certificates: []tls.Certificate{},
|
||||
}
|
||||
// Connection without client certificate should fail
|
||||
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
|
||||
|
||||
// Connect with client signed by ca1
|
||||
cert, err := tls.LoadX509KeyPair("fixtures/https/clientca/client1.crt", "fixtures/https/clientca/client1.key")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
|
||||
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
|
||||
conn.Close()
|
||||
|
||||
// Connect with client signed by ca2
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
Certificates: []tls.Certificate{},
|
||||
}
|
||||
cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client2.crt", "fixtures/https/clientca/client2.key")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
|
||||
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
conn.Close()
|
||||
|
||||
// Connect with client signed by ca3 should fail
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
Certificates: []tls.Certificate{},
|
||||
}
|
||||
cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client3.crt", "fixtures/https/clientca/client3.key")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
|
||||
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
|
||||
}
|
||||
|
||||
// TestWithClientCertificateAuthentication
|
||||
// Use two CA:s in two different files and test that clients with client signed by either of them can connect
|
||||
func (s *HTTPSSuite) TestWithClientCertificateAuthenticationMultipeCAsMultipleFiles(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/https/clientca/https_2ca2config.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
Certificates: []tls.Certificate{},
|
||||
}
|
||||
// Connection without client certificate should fail
|
||||
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
|
||||
|
||||
// Connect with client signed by ca1
|
||||
cert, err := tls.LoadX509KeyPair("fixtures/https/clientca/client1.crt", "fixtures/https/clientca/client1.key")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
|
||||
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
|
||||
conn.Close()
|
||||
|
||||
// Connect with client signed by ca2
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
Certificates: []tls.Certificate{},
|
||||
}
|
||||
cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client2.crt", "fixtures/https/clientca/client2.key")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
|
||||
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
|
||||
conn.Close()
|
||||
|
||||
// Connect with client signed by ca3 should fail
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
Certificates: []tls.Certificate{},
|
||||
}
|
||||
cert, err = tls.LoadX509KeyPair("fixtures/https/clientca/client3.crt", "fixtures/https/clientca/client3.key")
|
||||
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
|
||||
|
||||
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
|
||||
}
|
||||
|
||||
func startTestServer(port string, statusCode int) (ts *httptest.Server) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(statusCode)
|
||||
|
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/containous/traefik/integration/utils"
|
||||
"github.com/go-check/check"
|
||||
|
||||
"github.com/libkermit/docker-check/compose"
|
||||
compose "github.com/libkermit/compose/check"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
)
|
||||
|
||||
@@ -32,6 +32,7 @@ func init() {
|
||||
check.Suite(&EtcdSuite{})
|
||||
check.Suite(&MarathonSuite{})
|
||||
check.Suite(&ConstraintSuite{})
|
||||
check.Suite(&MesosSuite{})
|
||||
}
|
||||
|
||||
var traefikBinary = "../dist/traefik"
|
||||
|
33
integration/mesos_test.go
Normal file
33
integration/mesos_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/go-check/check"
|
||||
|
||||
checker "github.com/vdemeester/shakers"
|
||||
)
|
||||
|
||||
// Mesos test suites (using libcompose)
|
||||
type MesosSuite struct{ BaseSuite }
|
||||
|
||||
func (s *MesosSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "mesos")
|
||||
}
|
||||
|
||||
func (s *MesosSuite) TestSimpleConfiguration(c *check.C) {
|
||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/mesos/simple.toml")
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
// TODO validate : run on 80
|
||||
resp, err := http.Get("http://127.0.0.1:8000/")
|
||||
|
||||
// Expected a 404 as we did not configure anything
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||
}
|
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
|
34
integration/resources/compose/mesos.yml
Normal file
34
integration/resources/compose/mesos.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
zk:
|
||||
image: bobrik/zookeeper
|
||||
net: host
|
||||
environment:
|
||||
ZK_CONFIG: tickTime=2000,initLimit=10,syncLimit=5,maxClientCnxns=128,forceSync=no,clientPort=2181
|
||||
ZK_ID: " 1"
|
||||
|
||||
master:
|
||||
image: mesosphere/mesos-master:0.28.1-2.0.20.ubuntu1404
|
||||
net: host
|
||||
environment:
|
||||
MESOS_ZK: zk://127.0.0.1:2181/mesos
|
||||
MESOS_HOSTNAME: 127.0.0.1
|
||||
MESOS_IP: 127.0.0.1
|
||||
MESOS_QUORUM: " 1"
|
||||
MESOS_CLUSTER: docker-compose
|
||||
MESOS_WORK_DIR: /var/lib/mesos
|
||||
|
||||
slave:
|
||||
image: mesosphere/mesos-slave:0.28.1-2.0.20.ubuntu1404
|
||||
net: host
|
||||
pid: host
|
||||
privileged: true
|
||||
environment:
|
||||
MESOS_MASTER: zk://127.0.0.1:2181/mesos
|
||||
MESOS_HOSTNAME: 127.0.0.1
|
||||
MESOS_IP: 127.0.0.1
|
||||
MESOS_CONTAINERIZERS: docker,mesos
|
||||
volumes:
|
||||
- /sys/fs/cgroup:/sys/fs/cgroup
|
||||
- /usr/bin/docker:/usr/bin/docker:ro
|
||||
- /usr/lib/x86_64-linux-gnu/libapparmor.so.1:/usr/lib/x86_64-linux-gnu/libapparmor.so.1:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /lib/x86_64-linux-gnu/libsystemd-journal.so.0:/lib/x86_64-linux-gnu/libsystemd-journal.so.0
|
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
|
||||
}
|
@@ -2,7 +2,7 @@ package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/cenk/backoff"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
39
job/job.go
Normal file
39
job/job.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"github.com/cenk/backoff"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
_ backoff.BackOff = (*BackOff)(nil)
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMinJobInterval = 30 * time.Second
|
||||
)
|
||||
|
||||
// BackOff is an exponential backoff implementation for long running jobs.
|
||||
// In long running jobs, an operation() that fails after a long Duration should not increments the backoff period.
|
||||
// If operation() takes more than MinJobInterval, Reset() is called in NextBackOff().
|
||||
type BackOff struct {
|
||||
*backoff.ExponentialBackOff
|
||||
MinJobInterval time.Duration
|
||||
}
|
||||
|
||||
// NewBackOff creates an instance of BackOff using default values.
|
||||
func NewBackOff(backOff *backoff.ExponentialBackOff) *BackOff {
|
||||
backOff.MaxElapsedTime = 0
|
||||
return &BackOff{
|
||||
ExponentialBackOff: backOff,
|
||||
MinJobInterval: defaultMinJobInterval,
|
||||
}
|
||||
}
|
||||
|
||||
// NextBackOff calculates the next backoff interval.
|
||||
func (b *BackOff) NextBackOff() time.Duration {
|
||||
if b.GetElapsedTime() >= b.MinJobInterval {
|
||||
b.Reset()
|
||||
}
|
||||
return b.ExponentialBackOff.NextBackOff()
|
||||
}
|
44
job/job_test.go
Normal file
44
job/job_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"github.com/cenk/backoff"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestJobBackOff(t *testing.T) {
|
||||
var (
|
||||
testInitialInterval = 500 * time.Millisecond
|
||||
testRandomizationFactor = 0.1
|
||||
testMultiplier = 2.0
|
||||
testMaxInterval = 5 * time.Second
|
||||
testMinJobInterval = 1 * time.Second
|
||||
)
|
||||
|
||||
exp := NewBackOff(backoff.NewExponentialBackOff())
|
||||
exp.InitialInterval = testInitialInterval
|
||||
exp.RandomizationFactor = testRandomizationFactor
|
||||
exp.Multiplier = testMultiplier
|
||||
exp.MaxInterval = testMaxInterval
|
||||
exp.MinJobInterval = testMinJobInterval
|
||||
exp.Reset()
|
||||
|
||||
var expectedResults = []time.Duration{500, 500, 500, 1000, 2000, 4000, 5000, 5000, 500, 1000, 2000, 4000, 5000, 5000}
|
||||
for i, d := range expectedResults {
|
||||
expectedResults[i] = d * time.Millisecond
|
||||
}
|
||||
|
||||
for i, expected := range expectedResults {
|
||||
// Assert that the next backoff falls in the expected range.
|
||||
var minInterval = expected - time.Duration(testRandomizationFactor*float64(expected))
|
||||
var maxInterval = expected + time.Duration(testRandomizationFactor*float64(expected))
|
||||
if i < 3 || i == 8 {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
var actualInterval = exp.NextBackOff()
|
||||
if !(minInterval <= actualInterval && actualInterval <= maxInterval) {
|
||||
t.Error("error")
|
||||
}
|
||||
// assertEquals(t, expected, exp.currentInterval)
|
||||
}
|
||||
}
|
188
log/logger.go
Normal file
188
log/logger.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
logger *logrus.Entry
|
||||
)
|
||||
|
||||
func init() {
|
||||
logger = logrus.StandardLogger().WithFields(logrus.Fields{})
|
||||
}
|
||||
|
||||
// Context sets the Context of the logger
|
||||
func Context(context interface{}) *logrus.Entry {
|
||||
return logger.WithField("context", context)
|
||||
}
|
||||
|
||||
// SetOutput sets the standard logger output.
|
||||
func SetOutput(out io.Writer) {
|
||||
logrus.SetOutput(out)
|
||||
}
|
||||
|
||||
// SetFormatter sets the standard logger formatter.
|
||||
func SetFormatter(formatter logrus.Formatter) {
|
||||
logrus.SetFormatter(formatter)
|
||||
}
|
||||
|
||||
// SetLevel sets the standard logger level.
|
||||
func SetLevel(level logrus.Level) {
|
||||
logrus.SetLevel(level)
|
||||
}
|
||||
|
||||
// GetLevel returns the standard logger level.
|
||||
func GetLevel() logrus.Level {
|
||||
return logrus.GetLevel()
|
||||
}
|
||||
|
||||
// AddHook adds a hook to the standard logger hooks.
|
||||
func AddHook(hook logrus.Hook) {
|
||||
logrus.AddHook(hook)
|
||||
}
|
||||
|
||||
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
||||
func WithError(err error) *logrus.Entry {
|
||||
return logger.WithError(err)
|
||||
}
|
||||
|
||||
// WithField creates an entry from the standard logger and adds a field to
|
||||
// it. If you want multiple fields, use `WithFields`.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithField(key string, value interface{}) *logrus.Entry {
|
||||
return logger.WithField(key, value)
|
||||
}
|
||||
|
||||
// WithFields creates an entry from the standard logger and adds multiple
|
||||
// fields to it. This is simply a helper for `WithField`, invoking it
|
||||
// once for each field.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithFields(fields logrus.Fields) *logrus.Entry {
|
||||
return logger.WithFields(fields)
|
||||
}
|
||||
|
||||
// Debug logs a message at level Debug on the standard logger.
|
||||
func Debug(args ...interface{}) {
|
||||
logger.Debug(args...)
|
||||
}
|
||||
|
||||
// Print logs a message at level Info on the standard logger.
|
||||
func Print(args ...interface{}) {
|
||||
logger.Print(args...)
|
||||
}
|
||||
|
||||
// Info logs a message at level Info on the standard logger.
|
||||
func Info(args ...interface{}) {
|
||||
logger.Info(args...)
|
||||
}
|
||||
|
||||
// Warn logs a message at level Warn on the standard logger.
|
||||
func Warn(args ...interface{}) {
|
||||
logger.Warn(args...)
|
||||
}
|
||||
|
||||
// Warning logs a message at level Warn on the standard logger.
|
||||
func Warning(args ...interface{}) {
|
||||
logger.Warning(args...)
|
||||
}
|
||||
|
||||
// Error logs a message at level Error on the standard logger.
|
||||
func Error(args ...interface{}) {
|
||||
logger.Error(args...)
|
||||
}
|
||||
|
||||
// Panic logs a message at level Panic on the standard logger.
|
||||
func Panic(args ...interface{}) {
|
||||
logger.Panic(args...)
|
||||
}
|
||||
|
||||
// Fatal logs a message at level Fatal on the standard logger.
|
||||
func Fatal(args ...interface{}) {
|
||||
logger.Fatal(args...)
|
||||
}
|
||||
|
||||
// Debugf logs a message at level Debug on the standard logger.
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
logger.Debugf(format, args...)
|
||||
}
|
||||
|
||||
// Printf logs a message at level Info on the standard logger.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
logger.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Infof logs a message at level Info on the standard logger.
|
||||
func Infof(format string, args ...interface{}) {
|
||||
logger.Infof(format, args...)
|
||||
}
|
||||
|
||||
// Warnf logs a message at level Warn on the standard logger.
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
logger.Warnf(format, args...)
|
||||
}
|
||||
|
||||
// Warningf logs a message at level Warn on the standard logger.
|
||||
func Warningf(format string, args ...interface{}) {
|
||||
logger.Warningf(format, args...)
|
||||
}
|
||||
|
||||
// Errorf logs a message at level Error on the standard logger.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
logger.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Panicf logs a message at level Panic on the standard logger.
|
||||
func Panicf(format string, args ...interface{}) {
|
||||
logger.Panicf(format, args...)
|
||||
}
|
||||
|
||||
// Fatalf logs a message at level Fatal on the standard logger.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
logger.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// Debugln logs a message at level Debug on the standard logger.
|
||||
func Debugln(args ...interface{}) {
|
||||
logger.Debugln(args...)
|
||||
}
|
||||
|
||||
// Println logs a message at level Info on the standard logger.
|
||||
func Println(args ...interface{}) {
|
||||
logger.Println(args...)
|
||||
}
|
||||
|
||||
// Infoln logs a message at level Info on the standard logger.
|
||||
func Infoln(args ...interface{}) {
|
||||
logger.Infoln(args...)
|
||||
}
|
||||
|
||||
// Warnln logs a message at level Warn on the standard logger.
|
||||
func Warnln(args ...interface{}) {
|
||||
logger.Warnln(args...)
|
||||
}
|
||||
|
||||
// Warningln logs a message at level Warn on the standard logger.
|
||||
func Warningln(args ...interface{}) {
|
||||
logger.Warningln(args...)
|
||||
}
|
||||
|
||||
// Errorln logs a message at level Error on the standard logger.
|
||||
func Errorln(args ...interface{}) {
|
||||
logger.Errorln(args...)
|
||||
}
|
||||
|
||||
// Panicln logs a message at level Panic on the standard logger.
|
||||
func Panicln(args ...interface{}) {
|
||||
logger.Panicln(args...)
|
||||
}
|
||||
|
||||
// Fatalln logs a message at level Fatal on the standard logger.
|
||||
func Fatalln(args ...interface{}) {
|
||||
logger.Fatalln(args...)
|
||||
}
|
99
middlewares/authenticator.go
Normal file
99
middlewares/authenticator.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/abbot/go-http-auth"
|
||||
"github.com/codegangsta/negroni"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/types"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Authenticator is a middleware that provides HTTP basic and digest authentication
|
||||
type Authenticator struct {
|
||||
handler negroni.Handler
|
||||
users map[string]string
|
||||
}
|
||||
|
||||
// NewAuthenticator builds a new Autenticator given a config
|
||||
func NewAuthenticator(authConfig *types.Auth) (*Authenticator, error) {
|
||||
if authConfig == nil {
|
||||
return nil, fmt.Errorf("Error creating Authenticator: auth is nil")
|
||||
}
|
||||
var err error
|
||||
authenticator := Authenticator{}
|
||||
if authConfig.Basic != nil {
|
||||
authenticator.users, err = parserBasicUsers(authConfig.Basic.Users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
basicAuth := auth.NewBasicAuthenticator("traefik", authenticator.secretBasic)
|
||||
authenticator.handler = negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
if username := basicAuth.CheckAuth(r); username == "" {
|
||||
log.Debugf("Auth failed...")
|
||||
basicAuth.RequireAuth(w, r)
|
||||
} else {
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
} else if authConfig.Digest != nil {
|
||||
authenticator.users, err = parserDigestUsers(authConfig.Digest.Users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
digestAuth := auth.NewDigestAuthenticator("traefik", authenticator.secretDigest)
|
||||
authenticator.handler = negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
if username, _ := digestAuth.CheckAuth(r); username == "" {
|
||||
digestAuth.RequireAuth(w, r)
|
||||
} else {
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
return &authenticator, nil
|
||||
}
|
||||
|
||||
func parserBasicUsers(users types.Users) (map[string]string, error) {
|
||||
userMap := make(map[string]string)
|
||||
for _, user := range users {
|
||||
split := strings.Split(user, ":")
|
||||
if len(split) != 2 {
|
||||
return nil, fmt.Errorf("Error parsing Authenticator user: %v", user)
|
||||
}
|
||||
userMap[split[0]] = split[1]
|
||||
}
|
||||
return userMap, nil
|
||||
}
|
||||
|
||||
func parserDigestUsers(users types.Users) (map[string]string, error) {
|
||||
userMap := make(map[string]string)
|
||||
for _, user := range users {
|
||||
split := strings.Split(user, ":")
|
||||
if len(split) != 3 {
|
||||
return nil, fmt.Errorf("Error parsing Authenticator user: %v", user)
|
||||
}
|
||||
userMap[split[0]+":"+split[1]] = split[2]
|
||||
}
|
||||
return userMap, nil
|
||||
}
|
||||
|
||||
func (a *Authenticator) secretBasic(user, realm string) string {
|
||||
if secret, ok := a.users[user]; ok {
|
||||
return secret
|
||||
}
|
||||
log.Debugf("User not found: %s", user)
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *Authenticator) secretDigest(user, realm string) string {
|
||||
if secret, ok := a.users[user+":"+realm]; ok {
|
||||
return secret
|
||||
}
|
||||
log.Debugf("User not found: %s:%s", user, realm)
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *Authenticator) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
a.handler.ServeHTTP(rw, r, next)
|
||||
}
|
103
middlewares/authenticator_test.go
Normal file
103
middlewares/authenticator_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/codegangsta/negroni"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBasicAuthFail(t *testing.T) {
|
||||
authMiddleware, err := NewAuthenticator(&types.Auth{
|
||||
Basic: &types.Basic{
|
||||
Users: []string{"test"},
|
||||
},
|
||||
})
|
||||
assert.Contains(t, err.Error(), "Error parsing Authenticator user", "should contains")
|
||||
|
||||
authMiddleware, err = NewAuthenticator(&types.Auth{
|
||||
Basic: &types.Basic{
|
||||
Users: []string{"test:test"},
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err, "there should be no error")
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "traefik")
|
||||
})
|
||||
n := negroni.New(authMiddleware)
|
||||
n.UseHandler(handler)
|
||||
ts := httptest.NewServer(n)
|
||||
defer ts.Close()
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", ts.URL, nil)
|
||||
req.SetBasicAuth("test", "test")
|
||||
res, err := client.Do(req)
|
||||
assert.NoError(t, err, "there should be no error")
|
||||
assert.Equal(t, http.StatusUnauthorized, res.StatusCode, "they should be equal")
|
||||
}
|
||||
|
||||
func TestBasicAuthSuccess(t *testing.T) {
|
||||
authMiddleware, err := NewAuthenticator(&types.Auth{
|
||||
Basic: &types.Basic{
|
||||
Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"},
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err, "there should be no error")
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "traefik")
|
||||
})
|
||||
n := negroni.New(authMiddleware)
|
||||
n.UseHandler(handler)
|
||||
ts := httptest.NewServer(n)
|
||||
defer ts.Close()
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", ts.URL, nil)
|
||||
req.SetBasicAuth("test", "test")
|
||||
res, err := client.Do(req)
|
||||
assert.NoError(t, err, "there should be no error")
|
||||
assert.Equal(t, http.StatusOK, res.StatusCode, "they should be equal")
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
assert.NoError(t, err, "there should be no error")
|
||||
assert.Equal(t, "traefik\n", string(body), "they should be equal")
|
||||
}
|
||||
|
||||
func TestDigestAuthFail(t *testing.T) {
|
||||
authMiddleware, err := NewAuthenticator(&types.Auth{
|
||||
Digest: &types.Digest{
|
||||
Users: []string{"test"},
|
||||
},
|
||||
})
|
||||
assert.Contains(t, err.Error(), "Error parsing Authenticator user", "should contains")
|
||||
|
||||
authMiddleware, err = NewAuthenticator(&types.Auth{
|
||||
Digest: &types.Digest{
|
||||
Users: []string{"test:traefik:test"},
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err, "there should be no error")
|
||||
assert.NotNil(t, authMiddleware, "this should not be nil")
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "traefik")
|
||||
})
|
||||
n := negroni.New(authMiddleware)
|
||||
n.UseHandler(handler)
|
||||
ts := httptest.NewServer(n)
|
||||
defer ts.Close()
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("GET", ts.URL, nil)
|
||||
req.SetBasicAuth("test", "test")
|
||||
res, err := client.Do(req)
|
||||
assert.NoError(t, err, "there should be no error")
|
||||
assert.Equal(t, http.StatusUnauthorized, res.StatusCode, "they should be equal")
|
||||
}
|
16
middlewares/compress.go
Normal file
16
middlewares/compress.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"github.com/NYTimes/gziphandler"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Compress is a middleware that allows redirections
|
||||
type Compress struct {
|
||||
}
|
||||
|
||||
// ServerHTTP is a function used by negroni
|
||||
func (c *Compress) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
newGzipHandler := gziphandler.GzipHandler(next)
|
||||
newGzipHandler.ServeHTTP(rw, r)
|
||||
}
|
@@ -3,8 +3,6 @@ package middlewares
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/streamrail/concurrent-map"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -13,6 +11,9 @@ import (
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/streamrail/concurrent-map"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -55,7 +56,7 @@ func NewLogger(file string) *Logger {
|
||||
if len(file) > 0 {
|
||||
fi, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening file", err)
|
||||
log.Error("Error opening file", err)
|
||||
}
|
||||
return &Logger{fi}
|
||||
}
|
||||
@@ -138,8 +139,9 @@ func (fblh frontendBackendLoggingHandler) ServeHTTP(rw http.ResponseWriter, req
|
||||
size := infoRw.GetSize()
|
||||
|
||||
elapsed := time.Now().UTC().Sub(startTime.UTC())
|
||||
fmt.Fprintf(fblh.writer, `%s - %s [%s] "%s %s %s" %d %d "%s" "%s" %s "%s" "%s" %s%s`,
|
||||
host, username, ts, method, uri, proto, status, size, referer, agent, fblh.reqid, frontend, backend, elapsed, "\n")
|
||||
elapsedMillis := elapsed.Nanoseconds() / 1000000
|
||||
fmt.Fprintf(fblh.writer, `%s - %s [%s] "%s %s %s" %d %d "%s" "%s" %s "%s" "%s" %dms%s`,
|
||||
host, username, ts, method, uri, proto, status, size, referer, agent, fblh.reqid, frontend, backend, elapsedMillis, "\n")
|
||||
|
||||
}
|
||||
|
||||
|
@@ -3,12 +3,19 @@ package middlewares
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
_ http.ResponseWriter = &ResponseRecorder{}
|
||||
_ http.Hijacker = &ResponseRecorder{}
|
||||
_ http.Flusher = &ResponseRecorder{}
|
||||
_ http.CloseNotifier = &ResponseRecorder{}
|
||||
)
|
||||
|
||||
// Retry is a middleware that retries requests
|
||||
type Retry struct {
|
||||
attempts int
|
||||
@@ -52,6 +59,7 @@ type ResponseRecorder struct {
|
||||
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
|
||||
|
||||
responseWriter http.ResponseWriter
|
||||
err error
|
||||
}
|
||||
|
||||
// NewRecorder returns an initialized ResponseRecorder.
|
||||
@@ -75,10 +83,10 @@ func (rw *ResponseRecorder) Header() http.Header {
|
||||
|
||||
// Write always succeeds and writes to rw.Body, if not nil.
|
||||
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
|
||||
if rw.Body != nil {
|
||||
return rw.Body.Write(buf)
|
||||
if rw.err != nil {
|
||||
return 0, rw.err
|
||||
}
|
||||
return 0, nil
|
||||
return rw.Body.Write(buf)
|
||||
}
|
||||
|
||||
// WriteHeader sets rw.Code.
|
||||
@@ -90,3 +98,24 @@ func (rw *ResponseRecorder) WriteHeader(code int) {
|
||||
func (rw *ResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return rw.responseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
// CloseNotify returns a channel that receives at most a
|
||||
// single value (true) when the client connection has gone
|
||||
// away.
|
||||
func (rw *ResponseRecorder) CloseNotify() <-chan bool {
|
||||
return rw.responseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
// Flush sends any buffered data to the client.
|
||||
func (rw *ResponseRecorder) Flush() {
|
||||
_, err := rw.responseWriter.Write(rw.Body.Bytes())
|
||||
if err != nil {
|
||||
log.Errorf("Error writing response in ResponseRecorder: %s", err)
|
||||
rw.err = err
|
||||
}
|
||||
rw.Body.Reset()
|
||||
flusher, ok := rw.responseWriter.(http.Flusher)
|
||||
if ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/vulcand/vulcand/plugin/rewrite"
|
||||
"net/http"
|
||||
)
|
||||
|
11
mkdocs.yml
11
mkdocs.yml
@@ -1,5 +1,5 @@
|
||||
site_name: Traefik
|
||||
site_description: Traefik Documentation
|
||||
site_name: Træfɪk
|
||||
site_description: Træfɪk Documentation
|
||||
site_author: containo.us
|
||||
site_url: https://docs.traefik.io
|
||||
|
||||
@@ -16,7 +16,7 @@ theme: united
|
||||
site_favicon: 'img/traefik.icon.png'
|
||||
|
||||
# Copyright
|
||||
copyright: Copyright (c) 2016 Containous SAS
|
||||
copyright: "Copyright © 2016 Containous SAS"
|
||||
|
||||
# Options
|
||||
extra:
|
||||
@@ -49,4 +49,7 @@ pages:
|
||||
- User Guide:
|
||||
- 'Configuration examples': 'user-guide/examples.md'
|
||||
- 'Swarm cluster': 'user-guide/swarm.md'
|
||||
- Benchmarks: benchmarks.md
|
||||
- 'Swarm mode cluster': 'user-guide/swarm-mode.md'
|
||||
- 'Kubernetes': 'user-guide/kubernetes.md'
|
||||
- 'Key-value store configuration': 'user-guide/kv-config.md'
|
||||
- 'Clustering/HA': 'user-guide/cluster.md'
|
||||
|
@@ -1,21 +1,34 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/boltdb"
|
||||
)
|
||||
|
||||
var _ Provider = (*BoltDb)(nil)
|
||||
|
||||
// BoltDb holds configurations of the BoltDb provider.
|
||||
type BoltDb struct {
|
||||
Kv
|
||||
Kv `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.storeType = store.BOLTDB
|
||||
boltdb.Register()
|
||||
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
store, err := provider.CreateStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
}
|
||||
provider.kvclient = store
|
||||
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,21 +1,34 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/consul"
|
||||
)
|
||||
|
||||
var _ Provider = (*Consul)(nil)
|
||||
|
||||
// Consul holds configurations of the Consul provider.
|
||||
type Consul struct {
|
||||
Kv
|
||||
Kv `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.storeType = store.CONSUL
|
||||
consul.Register()
|
||||
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
store, err := provider.CreateStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
}
|
||||
provider.kvclient = store
|
||||
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()
|
||||
}
|
||||
|
@@ -9,8 +9,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/hashicorp/consul/api"
|
||||
@@ -23,13 +25,15 @@ const (
|
||||
DefaultConsulCatalogTagPrefix = "traefik"
|
||||
)
|
||||
|
||||
var _ Provider = (*ConsulCatalog)(nil)
|
||||
|
||||
// ConsulCatalog holds configurations of the Consul catalog provider.
|
||||
type ConsulCatalog struct {
|
||||
BaseProvider
|
||||
Endpoint string `description:"Consul server endpoint"`
|
||||
Domain string `description:"Default domain used"`
|
||||
client *api.Client
|
||||
Prefix string
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string `description:"Consul server endpoint"`
|
||||
Domain string `description:"Default domain used"`
|
||||
client *api.Client
|
||||
Prefix string
|
||||
}
|
||||
|
||||
type serviceUpdate struct {
|
||||
@@ -209,12 +213,13 @@ func (provider *ConsulCatalog) getContraintTags(tags []string) []string {
|
||||
|
||||
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
|
||||
var FuncMap = template.FuncMap{
|
||||
"getBackend": provider.getBackend,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"getBackendName": provider.getBackendName,
|
||||
"getBackendAddress": provider.getBackendAddress,
|
||||
"getAttribute": provider.getAttribute,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"getBackend": provider.getBackend,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"getBackendName": provider.getBackendName,
|
||||
"getBackendAddress": provider.getBackendAddress,
|
||||
"getAttribute": provider.getAttribute,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"hasMaxconnAttributes": provider.hasMaxconnAttributes,
|
||||
}
|
||||
|
||||
allNodes := []*api.ServiceEntry{}
|
||||
@@ -249,6 +254,15 @@ func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Confi
|
||||
return configuration
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) hasMaxconnAttributes(attributes []string) bool {
|
||||
amount := provider.getAttribute("backend.maxconn.amount", attributes, "")
|
||||
extractorfunc := provider.getAttribute("backend.maxconn.extractorfunc", attributes, "")
|
||||
if amount != "" && extractorfunc != "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpdate, error) {
|
||||
visited := make(map[string]bool)
|
||||
|
||||
@@ -257,7 +271,7 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd
|
||||
name := strings.ToLower(service)
|
||||
if !strings.Contains(name, " ") && !visited[name] {
|
||||
visited[name] = true
|
||||
log.WithFields(log.Fields{
|
||||
log.WithFields(logrus.Fields{
|
||||
"service": name,
|
||||
}).Debug("Fetching service")
|
||||
healthy, err := provider.healthyNodes(name)
|
||||
@@ -303,7 +317,7 @@ func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessag
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
config := api.DefaultConfig()
|
||||
config.Address = provider.Endpoint
|
||||
client, err := api.NewClient(config)
|
||||
@@ -317,12 +331,12 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Consul connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
worker := func() error {
|
||||
operation := func() error {
|
||||
return provider.watch(configurationChan, stop)
|
||||
}
|
||||
err := backoff.RetryNotify(worker, backoff.NewExponentialBackOff(), notify)
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot connect to consul server %+v", err)
|
||||
log.Errorf("Cannot connect to consul server %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
|
@@ -61,7 +61,7 @@ func TestConsulCatalogGetAttribute(t *testing.T) {
|
||||
"traefik.backend.weight=42",
|
||||
},
|
||||
key: "backend.weight",
|
||||
defaultValue: "",
|
||||
defaultValue: "0",
|
||||
expected: "42",
|
||||
},
|
||||
{
|
||||
@@ -70,8 +70,8 @@ func TestConsulCatalogGetAttribute(t *testing.T) {
|
||||
"traefik.backend.wei=42",
|
||||
},
|
||||
key: "backend.weight",
|
||||
defaultValue: "",
|
||||
expected: "",
|
||||
defaultValue: "0",
|
||||
expected: "0",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -212,6 +212,8 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
|
||||
"traefik.backend.loadbalancer=drr",
|
||||
"traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5",
|
||||
"random.foo=bar",
|
||||
"traefik.backend.maxconn.amount=1000",
|
||||
"traefik.backend.maxconn.extractorfunc=client.ip",
|
||||
},
|
||||
},
|
||||
Nodes: []*api.ServiceEntry{
|
||||
@@ -260,6 +262,10 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
|
||||
LoadBalancer: &types.LoadBalancer{
|
||||
Method: "drr",
|
||||
},
|
||||
MaxConn: &types.MaxConn{
|
||||
Amount: 1000,
|
||||
ExtractorFunc: "client.ip",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@@ -2,6 +2,8 @@ package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -11,52 +13,77 @@ import (
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/BurntSushi/ty/fun"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/cenk/backoff"
|
||||
"github.com/containous/traefik/job"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/containous/traefik/version"
|
||||
"github.com/docker/engine-api/client"
|
||||
dockertypes "github.com/docker/engine-api/types"
|
||||
dockercontainertypes "github.com/docker/engine-api/types/container"
|
||||
eventtypes "github.com/docker/engine-api/types/events"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
swarmtypes "github.com/docker/engine-api/types/swarm"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/vdemeester/docker-events"
|
||||
)
|
||||
|
||||
// DockerAPIVersion is a constant holding the version of the Docker API traefik will use
|
||||
const DockerAPIVersion string = "1.21"
|
||||
const (
|
||||
// DockerAPIVersion is a constant holding the version of the Docker API traefik will use
|
||||
DockerAPIVersion string = "1.21"
|
||||
// SwarmAPIVersion is a constant holding the version of the Docker API traefik will use
|
||||
SwarmAPIVersion string = "1.24"
|
||||
// SwarmDefaultWatchTime is the duration of the interval when polling docker
|
||||
SwarmDefaultWatchTime = 15 * time.Second
|
||||
)
|
||||
|
||||
var _ Provider = (*Docker)(nil)
|
||||
|
||||
// Docker holds configurations of the Docker provider.
|
||||
type Docker struct {
|
||||
BaseProvider
|
||||
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
|
||||
Domain string `description:"Default domain used"`
|
||||
TLS *DockerTLS `description:"Enable Docker TLS support"`
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
|
||||
Domain string `description:"Default domain used"`
|
||||
TLS *ClientTLS `description:"Enable Docker TLS support"`
|
||||
ExposedByDefault bool `description:"Expose containers by default"`
|
||||
UseBindPortIP bool `description:"Use the ip address from the bound port, rather than from the inner network"`
|
||||
SwarmMode bool `description:"Use Docker on Swarm Mode"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
// dockerData holds the need data to the Docker provider
|
||||
type dockerData struct {
|
||||
Name string
|
||||
Labels map[string]string // List of labels set to container or service
|
||||
NetworkSettings networkSettings
|
||||
Health string
|
||||
}
|
||||
|
||||
// NetworkSettings holds the networks data to the Docker provider
|
||||
type networkSettings struct {
|
||||
NetworkMode dockercontainertypes.NetworkMode
|
||||
Ports nat.PortMap
|
||||
Networks map[string]*networkData
|
||||
}
|
||||
|
||||
// Network holds the network data to the Docker provider
|
||||
type networkData struct {
|
||||
Name string
|
||||
Addr string
|
||||
Port int
|
||||
Protocol string
|
||||
ID string
|
||||
}
|
||||
|
||||
func (provider *Docker) createClient() (client.APIClient, error) {
|
||||
var httpClient *http.Client
|
||||
httpHeaders := map[string]string{
|
||||
// FIXME(vdemeester) use version here O:)
|
||||
"User-Agent": "Traefik",
|
||||
"User-Agent": "Traefik " + version.Version,
|
||||
}
|
||||
if provider.TLS != nil {
|
||||
tlsOptions := tlsconfig.Options{
|
||||
CAFile: provider.TLS.CA,
|
||||
CertFile: provider.TLS.Cert,
|
||||
KeyFile: provider.TLS.Key,
|
||||
InsecureSkipVerify: provider.TLS.InsecureSkipVerify,
|
||||
}
|
||||
config, err := tlsconfig.Client(tlsOptions)
|
||||
config, err := provider.TLS.CreateTLSConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -73,13 +100,21 @@ func (provider *Docker) createClient() (client.APIClient, error) {
|
||||
httpClient = &http.Client{
|
||||
Transport: tr,
|
||||
}
|
||||
|
||||
}
|
||||
return client.NewClient(provider.Endpoint, DockerAPIVersion, httpClient, httpHeaders)
|
||||
var version string
|
||||
if provider.SwarmMode {
|
||||
version = SwarmAPIVersion
|
||||
} else {
|
||||
version = DockerAPIVersion
|
||||
}
|
||||
return client.NewClient(provider.Endpoint, version, httpClient, httpHeaders)
|
||||
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
provider.Constraints = append(provider.Constraints, constraints...)
|
||||
// TODO register this routine in pool, and watch for stop channel
|
||||
safe.Go(func() {
|
||||
@@ -95,56 +130,99 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
|
||||
ctx := context.Background()
|
||||
version, err := dockerClient.ServerVersion(ctx)
|
||||
log.Debugf("Docker connection established with docker %s (API %s)", version.Version, version.APIVersion)
|
||||
containers, err := listContainers(ctx, dockerClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list containers for docker, error %s", err)
|
||||
return err
|
||||
var dockerDataList []dockerData
|
||||
if provider.SwarmMode {
|
||||
dockerDataList, err = listServices(ctx, dockerClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list services for docker swarm mode, error %s", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dockerDataList, err = listContainers(ctx, dockerClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list containers for docker, error %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
configuration := provider.loadDockerConfig(containers)
|
||||
|
||||
configuration := provider.loadDockerConfig(dockerDataList)
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "docker",
|
||||
Configuration: configuration,
|
||||
}
|
||||
if provider.Watch {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
pool.Go(func(stop chan bool) {
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
if provider.SwarmMode {
|
||||
// TODO: This need to be change. Linked to Swarm events docker/docker#23827
|
||||
ticker := time.NewTicker(SwarmDefaultWatchTime)
|
||||
pool.Go(func(stop chan bool) {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
services, err := listServices(ctx, dockerClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list services for docker, error %s", err)
|
||||
return
|
||||
}
|
||||
configuration := provider.loadDockerConfig(services)
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "docker",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
|
||||
case <-stop:
|
||||
ticker.Stop()
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
} else {
|
||||
pool.Go(func(stop chan bool) {
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
f := filters.NewArgs()
|
||||
f.Add("type", "container")
|
||||
options := dockertypes.EventsOptions{
|
||||
Filters: f,
|
||||
}
|
||||
eventHandler := events.NewHandler(events.ByAction)
|
||||
startStopHandle := func(m eventtypes.Message) {
|
||||
log.Debugf("Docker event received %+v", m)
|
||||
containers, err := listContainers(ctx, dockerClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list containers for docker, error %s", err)
|
||||
// Call cancel to get out of the monitor
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
f := filters.NewArgs()
|
||||
f.Add("type", "container")
|
||||
options := dockertypes.EventsOptions{
|
||||
Filters: f,
|
||||
}
|
||||
eventHandler := events.NewHandler(events.ByAction)
|
||||
startStopHandle := func(m eventtypes.Message) {
|
||||
log.Debugf("Docker event received %+v", m)
|
||||
containers, err := listContainers(ctx, dockerClient)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list containers for docker, error %s", err)
|
||||
// Call cancel to get out of the monitor
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
configuration := provider.loadDockerConfig(containers)
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "docker",
|
||||
Configuration: configuration,
|
||||
configuration := provider.loadDockerConfig(containers)
|
||||
if configuration != nil {
|
||||
configurationChan <- types.ConfigMessage{
|
||||
ProviderName: "docker",
|
||||
Configuration: configuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
eventHandler.Handle("start", startStopHandle)
|
||||
eventHandler.Handle("die", startStopHandle)
|
||||
eventHandler.Handle("start", startStopHandle)
|
||||
eventHandler.Handle("die", startStopHandle)
|
||||
eventHandler.Handle("health_status: healthy", startStopHandle)
|
||||
eventHandler.Handle("health_status: unhealthy", startStopHandle)
|
||||
eventHandler.Handle("health_status: starting", startStopHandle)
|
||||
|
||||
errChan := events.MonitorWithHandler(ctx, dockerClient, options, eventHandler)
|
||||
if err := <-errChan; err != nil {
|
||||
return err
|
||||
errChan := events.MonitorWithHandler(ctx, dockerClient, options, eventHandler)
|
||||
if err := <-errChan; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -152,46 +230,65 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po
|
||||
notify := func(err error, time time.Duration) {
|
||||
log.Errorf("Docker connection error %+v, retrying in %s", err, time)
|
||||
}
|
||||
err := backoff.RetryNotify(operation, backoff.NewExponentialBackOff(), notify)
|
||||
err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot connect to docker server %+v", err)
|
||||
log.Errorf("Cannot connect to docker server %+v", err)
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration {
|
||||
func (provider *Docker) loadDockerConfig(containersInspected []dockerData) *types.Configuration {
|
||||
var DockerFuncMap = template.FuncMap{
|
||||
"getBackend": provider.getBackend,
|
||||
"getIPAddress": provider.getIPAddress,
|
||||
"getPort": provider.getPort,
|
||||
"getWeight": provider.getWeight,
|
||||
"getDomain": provider.getDomain,
|
||||
"getProtocol": provider.getProtocol,
|
||||
"getPassHostHeader": provider.getPassHostHeader,
|
||||
"getPriority": provider.getPriority,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"replace": replace,
|
||||
"getBackend": provider.getBackend,
|
||||
"getIPAddress": provider.getIPAddress,
|
||||
"getPort": provider.getPort,
|
||||
"getWeight": provider.getWeight,
|
||||
"getDomain": provider.getDomain,
|
||||
"getProtocol": provider.getProtocol,
|
||||
"getPassHostHeader": provider.getPassHostHeader,
|
||||
"getPriority": provider.getPriority,
|
||||
"getEntryPoints": provider.getEntryPoints,
|
||||
"getFrontendRule": provider.getFrontendRule,
|
||||
"hasCircuitBreakerLabel": provider.hasCircuitBreakerLabel,
|
||||
"getCircuitBreakerExpression": provider.getCircuitBreakerExpression,
|
||||
"hasLoadBalancerLabel": provider.hasLoadBalancerLabel,
|
||||
"getLoadBalancerMethod": provider.getLoadBalancerMethod,
|
||||
"hasMaxConnLabels": provider.hasMaxConnLabels,
|
||||
"getMaxConnAmount": provider.getMaxConnAmount,
|
||||
"getMaxConnExtractorFunc": provider.getMaxConnExtractorFunc,
|
||||
"getSticky": provider.getSticky,
|
||||
"replace": replace,
|
||||
}
|
||||
|
||||
// filter containers
|
||||
filteredContainers := fun.Filter(provider.ContainerFilter, containersInspected).([]dockertypes.ContainerJSON)
|
||||
filteredContainers := fun.Filter(func(container dockerData) bool {
|
||||
return provider.containerFilter(container)
|
||||
}, containersInspected).([]dockerData)
|
||||
|
||||
frontends := map[string][]dockertypes.ContainerJSON{}
|
||||
frontends := map[string][]dockerData{}
|
||||
backends := map[string]dockerData{}
|
||||
servers := map[string][]dockerData{}
|
||||
for _, container := range filteredContainers {
|
||||
frontendName := provider.getFrontendName(container)
|
||||
frontends[frontendName] = append(frontends[frontendName], container)
|
||||
backendName := provider.getBackend(container)
|
||||
backends[backendName] = container
|
||||
servers[backendName] = append(servers[backendName], container)
|
||||
}
|
||||
|
||||
templateObjects := struct {
|
||||
Containers []dockertypes.ContainerJSON
|
||||
Frontends map[string][]dockertypes.ContainerJSON
|
||||
Containers []dockerData
|
||||
Frontends map[string][]dockerData
|
||||
Backends map[string]dockerData
|
||||
Servers map[string][]dockerData
|
||||
Domain string
|
||||
}{
|
||||
filteredContainers,
|
||||
frontends,
|
||||
backends,
|
||||
servers,
|
||||
provider.Domain,
|
||||
}
|
||||
|
||||
@@ -202,24 +299,78 @@ func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.Conta
|
||||
return configuration
|
||||
}
|
||||
|
||||
// ContainerFilter checks if container have to be exposed
|
||||
func (provider *Docker) ContainerFilter(container dockertypes.ContainerJSON) bool {
|
||||
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
|
||||
func (provider *Docker) hasCircuitBreakerLabel(container dockerData) bool {
|
||||
if _, err := getLabel(container, "traefik.backend.circuitbreaker.expression"); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Docker) hasLoadBalancerLabel(container dockerData) bool {
|
||||
_, errMethod := getLabel(container, "traefik.backend.loadbalancer.method")
|
||||
_, errSticky := getLabel(container, "traefik.backend.loadbalancer.sticky")
|
||||
if errMethod != nil && errSticky != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Docker) hasMaxConnLabels(container dockerData) bool {
|
||||
if _, err := getLabel(container, "traefik.backend.maxconn.amount"); err != nil {
|
||||
return false
|
||||
}
|
||||
if _, err := getLabel(container, "traefik.backend.maxconn.extractorfunc"); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Docker) getCircuitBreakerExpression(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.backend.circuitbreaker.expression"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "NetworkErrorRatio() > 1"
|
||||
}
|
||||
|
||||
func (provider *Docker) getLoadBalancerMethod(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.backend.loadbalancer.method"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "wrr"
|
||||
}
|
||||
|
||||
func (provider *Docker) getMaxConnAmount(container dockerData) int64 {
|
||||
if label, err := getLabel(container, "traefik.backend.maxconn.amount"); err == nil {
|
||||
i, errConv := strconv.ParseInt(label, 10, 64)
|
||||
if errConv != nil {
|
||||
log.Errorf("Unable to parse traefik.backend.maxconn.amount %s", label)
|
||||
return math.MaxInt64
|
||||
}
|
||||
return i
|
||||
}
|
||||
return math.MaxInt64
|
||||
}
|
||||
|
||||
func (provider *Docker) getMaxConnExtractorFunc(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.backend.maxconn.extractorfunc"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "request.host"
|
||||
}
|
||||
|
||||
func (provider *Docker) containerFilter(container dockerData) bool {
|
||||
_, err := strconv.Atoi(container.Labels["traefik.port"])
|
||||
if len(container.NetworkSettings.Ports) == 0 && err != nil {
|
||||
log.Debugf("Filtering container without port and no traefik.port label %s", container.Name)
|
||||
return false
|
||||
}
|
||||
if len(container.NetworkSettings.Ports) > 1 && err != nil {
|
||||
log.Debugf("Filtering container with more than 1 port and no traefik.port label %s", container.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
if container.Config.Labels["traefik.enable"] == "false" {
|
||||
if !isContainerEnabled(container, provider.ExposedByDefault) {
|
||||
log.Debugf("Filtering disabled container %s", container.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
constraintTags := strings.Split(container.Config.Labels["traefik.tags"], ",")
|
||||
constraintTags := strings.Split(container.Labels["traefik.tags"], ",")
|
||||
if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok {
|
||||
if failingConstraint != nil {
|
||||
log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String())
|
||||
@@ -227,63 +378,70 @@ func (provider *Docker) ContainerFilter(container dockertypes.ContainerJSON) boo
|
||||
return false
|
||||
}
|
||||
|
||||
if container.Health != "" && container.Health != "healthy" {
|
||||
log.Debugf("Filtering unhealthy or starting container %s", container.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (provider *Docker) getFrontendName(container dockertypes.ContainerJSON) string {
|
||||
func (provider *Docker) getFrontendName(container dockerData) string {
|
||||
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
|
||||
return normalize(provider.getFrontendRule(container))
|
||||
}
|
||||
|
||||
// GetFrontendRule returns the frontend rule for the specified container, using
|
||||
// it's label. It returns a default one (Host) if the label is not present.
|
||||
func (provider *Docker) getFrontendRule(container dockertypes.ContainerJSON) string {
|
||||
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
|
||||
// TODO: backwards compatibility with DEPRECATED rule.Value
|
||||
if value, ok := container.Config.Labels["traefik.frontend.value"]; ok {
|
||||
log.Warnf("Label traefik.frontend.value=%s is DEPRECATED (will be removed in v1.0.0), please refer to the rule label: https://github.com/containous/traefik/blob/master/docs/index.md#docker", value)
|
||||
rule, _ := container.Config.Labels["traefik.frontend.rule"]
|
||||
return rule + ":" + value
|
||||
}
|
||||
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
|
||||
|
||||
func (provider *Docker) getFrontendRule(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "Host:" + provider.getSubDomain(container.Name) + "." + provider.Domain
|
||||
}
|
||||
|
||||
func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
|
||||
func (provider *Docker) getBackend(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.backend"); err == nil {
|
||||
return label
|
||||
return normalize(label)
|
||||
}
|
||||
return normalize(container.Name)
|
||||
}
|
||||
|
||||
func (provider *Docker) getIPAddress(container dockertypes.ContainerJSON) string {
|
||||
func (provider *Docker) getIPAddress(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.docker.network"); err == nil && label != "" {
|
||||
networks := container.NetworkSettings.Networks
|
||||
if networks != nil {
|
||||
network := networks[label]
|
||||
networkSettings := container.NetworkSettings
|
||||
if networkSettings.Networks != nil {
|
||||
network := networkSettings.Networks[label]
|
||||
if network != nil {
|
||||
return network.IPAddress
|
||||
return network.Addr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If net==host, quick n' dirty, we return 127.0.0.1
|
||||
// This will work locally, but will fail with swarm.
|
||||
if container.HostConfig != nil && "host" == container.HostConfig.NetworkMode {
|
||||
if "host" == container.NetworkSettings.NetworkMode {
|
||||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
if provider.UseBindPortIP {
|
||||
port := provider.getPort(container)
|
||||
for netport, portBindings := range container.NetworkSettings.Ports {
|
||||
if string(netport) == port+"/TCP" || string(netport) == port+"/UDP" {
|
||||
for _, p := range portBindings {
|
||||
return p.HostIP
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, network := range container.NetworkSettings.Networks {
|
||||
return network.IPAddress
|
||||
return network.Addr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (provider *Docker) getPort(container dockertypes.ContainerJSON) string {
|
||||
func (provider *Docker) getPort(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.port"); err == nil {
|
||||
return label
|
||||
}
|
||||
@@ -293,50 +451,61 @@ func (provider *Docker) getPort(container dockertypes.ContainerJSON) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (provider *Docker) getWeight(container dockertypes.ContainerJSON) string {
|
||||
func (provider *Docker) getWeight(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.weight"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "1"
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (provider *Docker) getDomain(container dockertypes.ContainerJSON) string {
|
||||
func (provider *Docker) getSticky(container dockerData) string {
|
||||
if _, err := getLabel(container, "traefik.backend.loadbalancer.sticky"); err == nil {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func (provider *Docker) getDomain(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.domain"); err == nil {
|
||||
return label
|
||||
}
|
||||
return provider.Domain
|
||||
}
|
||||
|
||||
func (provider *Docker) getProtocol(container dockertypes.ContainerJSON) string {
|
||||
func (provider *Docker) getProtocol(container dockerData) string {
|
||||
if label, err := getLabel(container, "traefik.protocol"); err == nil {
|
||||
return label
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (provider *Docker) getPassHostHeader(container dockertypes.ContainerJSON) string {
|
||||
func (provider *Docker) getPassHostHeader(container dockerData) string {
|
||||
if passHostHeader, err := getLabel(container, "traefik.frontend.passHostHeader"); err == nil {
|
||||
return passHostHeader
|
||||
}
|
||||
return "true"
|
||||
}
|
||||
|
||||
func (provider *Docker) getPriority(container dockertypes.ContainerJSON) string {
|
||||
func (provider *Docker) getPriority(container dockerData) string {
|
||||
if priority, err := getLabel(container, "traefik.frontend.priority"); err == nil {
|
||||
return priority
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (provider *Docker) getEntryPoints(container dockertypes.ContainerJSON) []string {
|
||||
func (provider *Docker) getEntryPoints(container dockerData) []string {
|
||||
if entryPoints, err := getLabel(container, "traefik.frontend.entryPoints"); err == nil {
|
||||
return strings.Split(entryPoints, ",")
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func getLabel(container dockertypes.ContainerJSON, label string) (string, error) {
|
||||
for key, value := range container.Config.Labels {
|
||||
func isContainerEnabled(container dockerData, exposedByDefault bool) bool {
|
||||
return exposedByDefault && container.Labels["traefik.enable"] != "false" || container.Labels["traefik.enable"] == "true"
|
||||
}
|
||||
|
||||
func getLabel(container dockerData, label string) (string, error) {
|
||||
for key, value := range container.Labels {
|
||||
if key == label {
|
||||
return value, nil
|
||||
}
|
||||
@@ -344,7 +513,7 @@ func getLabel(container dockertypes.ContainerJSON, label string) (string, error)
|
||||
return "", errors.New("Label not found:" + label)
|
||||
}
|
||||
|
||||
func getLabels(container dockertypes.ContainerJSON, labels []string) (map[string]string, error) {
|
||||
func getLabels(container dockerData, labels []string) (map[string]string, error) {
|
||||
var globalErr error
|
||||
foundLabels := map[string]string{}
|
||||
for _, label := range labels {
|
||||
@@ -360,12 +529,12 @@ func getLabels(container dockertypes.ContainerJSON, labels []string) (map[string
|
||||
return foundLabels, globalErr
|
||||
}
|
||||
|
||||
func listContainers(ctx context.Context, dockerClient client.APIClient) ([]dockertypes.ContainerJSON, error) {
|
||||
func listContainers(ctx context.Context, dockerClient client.ContainerAPIClient) ([]dockerData, error) {
|
||||
containerList, err := dockerClient.ContainerList(ctx, dockertypes.ContainerListOptions{})
|
||||
if err != nil {
|
||||
return []dockertypes.ContainerJSON{}, err
|
||||
return []dockerData{}, err
|
||||
}
|
||||
containersInspected := []dockertypes.ContainerJSON{}
|
||||
containersInspected := []dockerData{}
|
||||
|
||||
// get inspect containers
|
||||
for _, container := range containerList {
|
||||
@@ -373,13 +542,119 @@ func listContainers(ctx context.Context, dockerClient client.APIClient) ([]docke
|
||||
if err != nil {
|
||||
log.Warnf("Failed to inspect container %s, error: %s", container.ID, err)
|
||||
} else {
|
||||
containersInspected = append(containersInspected, containerInspected)
|
||||
dockerData := parseContainer(containerInspected)
|
||||
containersInspected = append(containersInspected, dockerData)
|
||||
}
|
||||
}
|
||||
return containersInspected, nil
|
||||
}
|
||||
|
||||
func parseContainer(container dockertypes.ContainerJSON) dockerData {
|
||||
dockerData := dockerData{
|
||||
NetworkSettings: networkSettings{},
|
||||
}
|
||||
|
||||
if container.ContainerJSONBase != nil {
|
||||
dockerData.Name = container.ContainerJSONBase.Name
|
||||
|
||||
if container.ContainerJSONBase.HostConfig != nil {
|
||||
dockerData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode
|
||||
}
|
||||
|
||||
if container.State != nil && container.State.Health != nil {
|
||||
dockerData.Health = container.State.Health.Status
|
||||
}
|
||||
}
|
||||
|
||||
if container.Config != nil && container.Config.Labels != nil {
|
||||
dockerData.Labels = container.Config.Labels
|
||||
}
|
||||
|
||||
if container.NetworkSettings != nil {
|
||||
if container.NetworkSettings.Ports != nil {
|
||||
dockerData.NetworkSettings.Ports = container.NetworkSettings.Ports
|
||||
}
|
||||
if container.NetworkSettings.Networks != nil {
|
||||
dockerData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||
for name, containerNetwork := range container.NetworkSettings.Networks {
|
||||
dockerData.NetworkSettings.Networks[name] = &networkData{
|
||||
ID: containerNetwork.NetworkID,
|
||||
Name: name,
|
||||
Addr: containerNetwork.IPAddress,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return dockerData
|
||||
}
|
||||
|
||||
// Escape beginning slash "/", convert all others to dash "-"
|
||||
func (provider *Docker) getSubDomain(name string) string {
|
||||
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
|
||||
}
|
||||
|
||||
func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerData, error) {
|
||||
serviceList, err := dockerClient.ServiceList(ctx, dockertypes.ServiceListOptions{})
|
||||
if err != nil {
|
||||
return []dockerData{}, err
|
||||
}
|
||||
networkListArgs := filters.NewArgs()
|
||||
networkListArgs.Add("driver", "overlay")
|
||||
|
||||
networkList, err := dockerClient.NetworkList(ctx, dockertypes.NetworkListOptions{Filters: networkListArgs})
|
||||
|
||||
networkMap := make(map[string]*dockertypes.NetworkResource)
|
||||
if err != nil {
|
||||
log.Debug("Failed to network inspect on client for docker, error: %s", err)
|
||||
return []dockerData{}, err
|
||||
}
|
||||
for _, network := range networkList {
|
||||
networkToAdd := network
|
||||
networkMap[network.ID] = &networkToAdd
|
||||
}
|
||||
|
||||
var dockerDataList []dockerData
|
||||
|
||||
for _, service := range serviceList {
|
||||
dockerData := parseService(service, networkMap)
|
||||
|
||||
dockerDataList = append(dockerDataList, dockerData)
|
||||
}
|
||||
return dockerDataList, err
|
||||
|
||||
}
|
||||
|
||||
func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes.NetworkResource) dockerData {
|
||||
dockerData := dockerData{
|
||||
Name: service.Spec.Annotations.Name,
|
||||
Labels: service.Spec.Annotations.Labels,
|
||||
NetworkSettings: networkSettings{},
|
||||
}
|
||||
|
||||
if service.Spec.EndpointSpec != nil {
|
||||
switch service.Spec.EndpointSpec.Mode {
|
||||
case swarm.ResolutionModeDNSRR:
|
||||
log.Debug("Ignored endpoint-mode not supported, service name: %s", dockerData.Name)
|
||||
case swarm.ResolutionModeVIP:
|
||||
dockerData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||
for _, virtualIP := range service.Endpoint.VirtualIPs {
|
||||
networkService := networkMap[virtualIP.NetworkID]
|
||||
if networkService != nil {
|
||||
ip, _, _ := net.ParseCIDR(virtualIP.Addr)
|
||||
network := &networkData{
|
||||
Name: networkService.Name,
|
||||
ID: virtualIP.NetworkID,
|
||||
Addr: ip.String(),
|
||||
}
|
||||
dockerData.NetworkSettings.Networks[network.Name] = network
|
||||
} else {
|
||||
log.Debug("Network not found, id: %s", virtualIP.NetworkID)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return dockerData
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,34 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/docker/libkv/store/etcd"
|
||||
)
|
||||
|
||||
var _ Provider = (*Etcd)(nil)
|
||||
|
||||
// Etcd holds configurations of the Etcd provider.
|
||||
type Etcd struct {
|
||||
Kv
|
||||
Kv `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
|
||||
provider.storeType = store.ETCD
|
||||
etcd.Register()
|
||||
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
store, err := provider.CreateStore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to Connect to KV store: %v", err)
|
||||
}
|
||||
provider.kvclient = store
|
||||
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()
|
||||
}
|
||||
|
@@ -6,20 +6,22 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/containous/traefik/types"
|
||||
"gopkg.in/fsnotify.v1"
|
||||
)
|
||||
|
||||
var _ Provider = (*File)(nil)
|
||||
|
||||
// File holds configurations of the File provider.
|
||||
type File struct {
|
||||
BaseProvider
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
// Provide allows the provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error {
|
||||
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Error("Error creating file watcher", err)
|
||||
|
@@ -1,10 +1,13 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/safe"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -21,10 +24,10 @@ const (
|
||||
|
||||
// Client is a client for the Kubernetes master.
|
||||
type Client interface {
|
||||
GetIngresses(predicate func(Ingress) bool) ([]Ingress, error)
|
||||
GetIngresses(labelSelector string, predicate func(Ingress) bool) ([]Ingress, error)
|
||||
GetService(name, namespace string) (Service, error)
|
||||
GetEndpoints(name, namespace string) (Endpoints, error)
|
||||
WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error)
|
||||
WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error)
|
||||
}
|
||||
|
||||
type clientImpl struct {
|
||||
@@ -50,11 +53,26 @@ func NewClient(baseURL string, caCert []byte, token string) (Client, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetIngresses returns all ingresses in the cluster
|
||||
func (c *clientImpl) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) {
|
||||
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
|
||||
func makeQueryString(baseParams map[string]string, labelSelector string) (string, error) {
|
||||
if labelSelector != "" {
|
||||
baseParams["labelSelector"] = labelSelector
|
||||
}
|
||||
queryData, err := json.Marshal(baseParams)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(queryData), nil
|
||||
}
|
||||
|
||||
body, err := c.do(c.request(getURL))
|
||||
// GetIngresses returns all ingresses in the cluster
|
||||
func (c *clientImpl) GetIngresses(labelSelector string, predicate func(Ingress) bool) ([]Ingress, error) {
|
||||
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
|
||||
queryParams := map[string]string{}
|
||||
queryData, err := makeQueryString(queryParams, labelSelector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Had problems constructing query string %s : %v", queryParams, err)
|
||||
}
|
||||
body, err := c.do(c.request(getURL, queryData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create ingresses request: GET %q : %v", getURL, err)
|
||||
}
|
||||
@@ -73,16 +91,16 @@ func (c *clientImpl) GetIngresses(predicate func(Ingress) bool) ([]Ingress, erro
|
||||
}
|
||||
|
||||
// WatchIngresses returns all ingresses in the cluster
|
||||
func (c *clientImpl) WatchIngresses(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
func (c *clientImpl) WatchIngresses(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
|
||||
return c.watch(getURL, stopCh)
|
||||
return c.watch(getURL, labelSelector, stopCh)
|
||||
}
|
||||
|
||||
// GetService returns the named service from the named namespace
|
||||
func (c *clientImpl) GetService(name, namespace string) (Service, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/services/" + name
|
||||
|
||||
body, err := c.do(c.request(getURL))
|
||||
body, err := c.do(c.request(getURL, ""))
|
||||
if err != nil {
|
||||
return Service{}, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err)
|
||||
}
|
||||
@@ -97,7 +115,7 @@ func (c *clientImpl) GetService(name, namespace string) (Service, error) {
|
||||
// WatchServices returns all services in the cluster
|
||||
func (c *clientImpl) WatchServices(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + "/services"
|
||||
return c.watch(getURL, stopCh)
|
||||
return c.watch(getURL, "", stopCh)
|
||||
}
|
||||
|
||||
// GetEndpoints returns the named Endpoints
|
||||
@@ -105,7 +123,7 @@ func (c *clientImpl) WatchServices(stopCh <-chan bool) (chan interface{}, chan e
|
||||
func (c *clientImpl) GetEndpoints(name, namespace string) (Endpoints, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/endpoints/" + name
|
||||
|
||||
body, err := c.do(c.request(getURL))
|
||||
body, err := c.do(c.request(getURL, ""))
|
||||
if err != nil {
|
||||
return Endpoints{}, fmt.Errorf("failed to create endpoints request: GET %q : %v", getURL, err)
|
||||
}
|
||||
@@ -120,30 +138,30 @@ func (c *clientImpl) GetEndpoints(name, namespace string) (Endpoints, error) {
|
||||
// WatchEndpoints returns endpoints in the cluster
|
||||
func (c *clientImpl) WatchEndpoints(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
getURL := c.endpointURL + APIEndpoint + "/endpoints"
|
||||
return c.watch(getURL, stopCh)
|
||||
return c.watch(getURL, "", stopCh)
|
||||
}
|
||||
|
||||
// WatchAll returns events in the cluster
|
||||
func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
watchCh := make(chan interface{}, 10)
|
||||
errCh := make(chan error, 10)
|
||||
func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
watchCh := make(chan interface{}, 100)
|
||||
errCh := make(chan error, 100)
|
||||
|
||||
stopIngresses := make(chan bool)
|
||||
chanIngresses, chanIngressesErr, err := c.WatchIngresses(stopIngresses)
|
||||
stopIngresses := make(chan bool, 10)
|
||||
chanIngresses, chanIngressesErr, err := c.WatchIngresses(labelSelector, stopIngresses)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
stopServices := make(chan bool)
|
||||
stopServices := make(chan bool, 10)
|
||||
chanServices, chanServicesErr, err := c.WatchServices(stopServices)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
stopEndpoints := make(chan bool)
|
||||
stopEndpoints := make(chan bool, 10)
|
||||
chanEndpoints, chanEndpointsErr, err := c.WatchEndpoints(stopEndpoints)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
|
||||
}
|
||||
go func() {
|
||||
safe.Go(func() {
|
||||
defer close(watchCh)
|
||||
defer close(errCh)
|
||||
defer close(stopIngresses)
|
||||
@@ -171,7 +189,7 @@ func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error,
|
||||
watchCh <- event
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
return watchCh, errCh, nil
|
||||
}
|
||||
@@ -188,22 +206,26 @@ func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) {
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func (c *clientImpl) request(url string) *gorequest.SuperAgent {
|
||||
func (c *clientImpl) request(reqURL string, queryContent interface{}) *gorequest.SuperAgent {
|
||||
// Make request to Kubernetes API
|
||||
request := gorequest.New().Get(url)
|
||||
parsedURL, parseErr := url.Parse(reqURL)
|
||||
if parseErr != nil {
|
||||
log.Errorf("Had issues parsing url %s. Trying anyway.", reqURL)
|
||||
}
|
||||
request := gorequest.New().Get(reqURL)
|
||||
request.Transport.DisableKeepAlives = true
|
||||
|
||||
if strings.HasPrefix(url, "http://") {
|
||||
return request
|
||||
}
|
||||
|
||||
if len(c.token) > 0 {
|
||||
request.Header["Authorization"] = "Bearer " + c.token
|
||||
if parsedURL.Scheme == "https" {
|
||||
pool := x509.NewCertPool()
|
||||
pool.AppendCertsFromPEM(c.caCert)
|
||||
c.tls = &tls.Config{RootCAs: pool}
|
||||
request.TLSClientConfig(c.tls)
|
||||
}
|
||||
return request.TLSClientConfig(c.tls)
|
||||
if len(c.token) > 0 {
|
||||
request.Header["Authorization"] = "Bearer " + c.token
|
||||
}
|
||||
request.Query(queryContent)
|
||||
return request
|
||||
}
|
||||
|
||||
// GenericObject generic object
|
||||
@@ -212,12 +234,12 @@ type GenericObject struct {
|
||||
ListMeta `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
func (c *clientImpl) watch(url string, labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||
watchCh := make(chan interface{}, 10)
|
||||
errCh := make(chan error, 10)
|
||||
|
||||
// get version
|
||||
body, err := c.do(c.request(url))
|
||||
body, err := c.do(c.request(url, ""))
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to do version request: GET %q : %v", url, err)
|
||||
}
|
||||
@@ -227,48 +249,52 @@ func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, ch
|
||||
return watchCh, errCh, fmt.Errorf("failed to decode version %v", err)
|
||||
}
|
||||
resourceVersion := generic.ResourceVersion
|
||||
|
||||
url = url + "?watch&resourceVersion=" + resourceVersion
|
||||
// Make request to Kubernetes API
|
||||
request := c.request(url)
|
||||
queryParams := map[string]string{"watch": "true", "resourceVersion": resourceVersion}
|
||||
queryData, err := makeQueryString(queryParams, labelSelector)
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("Unable to construct query args")
|
||||
}
|
||||
request := c.request(url, queryData)
|
||||
req, err := request.MakeRequest()
|
||||
if err != nil {
|
||||
return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
req = req.WithContext(ctx)
|
||||
request.Client.Transport = request.Transport
|
||||
|
||||
res, err := request.Client.Do(req)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
finishCh := make(chan bool)
|
||||
defer close(finishCh)
|
||||
safe.Go(func() {
|
||||
EndCh := make(chan bool, 1)
|
||||
defer close(watchCh)
|
||||
defer close(errCh)
|
||||
go func() {
|
||||
defer close(EndCh)
|
||||
safe.Go(func() {
|
||||
defer res.Body.Close()
|
||||
defer func() {
|
||||
EndCh <- true
|
||||
}()
|
||||
for {
|
||||
var eventList interface{}
|
||||
if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil {
|
||||
if !strings.Contains(err.Error(), "net/http: request canceled") {
|
||||
errCh <- fmt.Errorf("failed to decode watch event: GET %q : %v", url, err)
|
||||
}
|
||||
finishCh <- true
|
||||
return
|
||||
}
|
||||
watchCh <- eventList
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-stopCh:
|
||||
go func() {
|
||||
request.Transport.CancelRequest(req)
|
||||
}()
|
||||
<-finishCh
|
||||
return
|
||||
}
|
||||
}()
|
||||
})
|
||||
<-stopCh
|
||||
safe.Go(func() {
|
||||
cancel() // cancel watch request
|
||||
})
|
||||
<-EndCh
|
||||
})
|
||||
return watchCh, errCh, nil
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user