1
0
mirror of https://github.com/containous/traefik.git synced 2025-09-15 13:44:21 +03:00

Compare commits

...

290 Commits

Author SHA1 Message Date
Vincent Demeester
fad7ec6b7f Merge pull request #299 from containous/add-better-benchmarks
add better benchmarks
2016-04-13 12:56:17 +02:00
Emile Vauge
82a49a8e89 add better benchmarks
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-13 12:17:53 +02:00
Vincent Demeester
2bcc5a2ac7 Merge pull request #294 from samber/TRAEFIK-275-consul-catalog-backend-using-container-internal-ip
feat(consul-catalog-provider): + setting unique backend name + backendd redirecting to internal container ip
2016-04-13 09:38:09 +02:00
Samuel BERTHE
4f044cf2f9 feat(consul-catalog-provider): + setting unique backend name + backend redirecting to internal container ip 2016-04-13 08:05:44 +02:00
Emile Vauge
9a407f79ff Merge pull request #291 from vdemeester/kewl-makefile
Add a make help target
2016-04-12 10:35:48 +02:00
Vincent Demeester
affec30c64 Add a make help target
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-04-12 10:09:16 +02:00
Emile Vauge
d050e60da2 Merge pull request #278 from vdemeester/migrate-to-engine-api
Migrate docker provider traefik to engine-api
2016-04-08 15:21:26 +02:00
Vincent Demeester
866b9835a6 Migrate traefik to engine-api
The docker provider now uses docker/engine-api and
vdemeester/docker-events instead of fsouza-dockerclient.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-04-08 14:21:02 +02:00
Emile Vauge
f6564909aa Merge pull request #279 from vdemeester/update-dockerignore
Add **/*.test to .dockerignore
2016-04-07 17:22:53 +02:00
Vincent Demeester
315e8b64b8 Add **/*.test to .dockerignore
`*.test` files are generated by `go test`, do not include them into the
build context. It will lighter a bit the build context..

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-04-07 16:48:35 +02:00
Emile Vauge
f99f634816 Merge pull request #290 from containous/fix-issues
Fix issues
2016-04-07 16:36:13 +02:00
Emile Vauge
5292a5b9d4 Migrate to official docker image
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-07 16:24:11 +02:00
Emile Vauge
cf22d62a74 Fix mkdoc deploy
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-07 16:24:11 +02:00
Emile Vauge
9363e2ab83 Fix broken table in webUI
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-07 16:24:11 +02:00
Emile Vauge
e5ddd92677 Fix port support in host rule
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-07 16:23:57 +02:00
Vincent Demeester
04628056af Merge pull request #287 from containous/fix-doc-deploy
Fix doc deploy...
2016-04-06 19:17:50 +02:00
Emile Vauge
dada86c0b0 Fix doc deploy...
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-06 18:50:35 +02:00
Vincent Demeester
92c269c972 Merge pull request #286 from containous/fix-CI-env-variable
Fix CI env variable...
2016-04-06 17:45:34 +02:00
Emile Vauge
6991e3c99b Fix CI env variable...
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-06 17:31:43 +02:00
Vincent Demeester
3ee3daee00 Merge pull request #285 from containous/add-multiple-rules
Add multiple rules
2016-04-06 16:24:16 +02:00
Emile Vauge
85fcff4cf7 Multiple rules docs
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-06 16:10:20 +02:00
Emile Vauge
30db47d9b6 Fix SSH key, I hope...
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-06 15:28:25 +02:00
Emile Vauge
4d2c85ffdc Fix multiple response.WriteHeader calls
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-06 14:30:29 +02:00
Emile Vauge
e36433c23a Fix retry attempts
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-06 14:07:51 +02:00
Emile Vauge
8486766a60 Add multiple rules
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-06 13:27:54 +02:00
Vincent Demeester
ef72d355d6 Merge pull request #283 from containous/fix-openssl-travis
Fix SSH key
2016-04-06 00:46:30 +02:00
Emile Vauge
7d013ad5e8 Fix SSH key
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-05 22:40:42 +02:00
Vincent Demeester
5fcce6567e Merge pull request #282 from containous/fix-openssl-travis
Fix openssl travis
2016-04-05 22:24:25 +02:00
Emile Vauge
00af537b0d Fix link in README
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-05 22:07:16 +02:00
Emile Vauge
78449fa62f Fix openssl load key in CI
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-05 20:47:23 +02:00
Vincent Demeester
ab0d648a03 Merge pull request #280 from containous/add-doc-site
Add docs.traefik.io
2016-04-05 17:26:26 +02:00
Emile Vauge
43d2107493 Add mkdoc in CI
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-05 17:13:08 +02:00
Emile Vauge
fd8b4a3305 add documentation website
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-05 17:13:08 +02:00
Emile Vauge
79dc4f9a70 Merge pull request #277 from vdemeester/micro-libkermit-update
Update libkermit again for compose enhancements
2016-04-04 10:34:43 +02:00
Vincent Demeester
b0fa11b8b8 Update libkermit again for compose enhancements
Using `.Container(…)`, it's way easier to get the container from a
project's service.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-04-02 15:33:12 +02:00
Emile Vauge
6e7bb93fd6 Merge pull request #276 from vdemeester/mini-kermit-update
A small update of libkermit
2016-04-02 13:26:16 +02:00
Vincent Demeester
e1448eb238 A small update of libkermit
Using compose/check package (and less code)

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-04-02 12:40:21 +02:00
Emile Vauge
585aeb8f0b Merge pull request #272 from wallies/patch-1
Add Go Report Card badge
2016-04-01 14:41:18 +02:00
Cameron
563823189a Merge branch 'master' into patch-1 2016-04-01 13:28:51 +01:00
Vincent Demeester
e9bf916a74 Merge pull request #270 from containous/fix-acme-renew
Fix acme renew panic
2016-04-01 14:12:18 +02:00
Emile Vauge
bcc5f24c0f Add GoSafe goroutine launch
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-01 14:01:31 +02:00
Cameron
9462c2e476 Add Go Report Card badge 2016-04-01 12:43:55 +01:00
Emile Vauge
af41c79798 Fix acme renew panic
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-31 17:21:05 +02:00
Vincent Demeester
733cbb5304 Merge pull request #266 from containous/refactor-frontend-rules
Refactor frontends rules
2016-03-31 16:29:51 +02:00
Emile Vauge
d5e1d2efd5 Fix documentation
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-31 16:17:59 +02:00
Emile Vauge
bb072a1f8f Add backwards compatibility
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-31 13:11:18 +02:00
Emile Vauge
8737530a7d Refactor frontends rules
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-31 13:11:07 +02:00
Vincent Demeester
dd160dc342 Merge pull request #267 from containous/add-retries
add retries request
2016-03-30 19:04:39 +02:00
Emile Vauge
4a9e82903e add retries request
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-29 22:25:32 +02:00
Vincent Demeester
1d040dbdd2 Merge pull request #265 from antoinecarton/master
Fix typo
2016-03-29 11:12:18 +02:00
Antoine Carton
e4db9c72dd Fix typo 2016-03-28 20:54:06 +02:00
Emile Vauge
6308ce2740 Merge pull request #264 from vdemeester/libkermit-integration
Libkermit integration 😇
2016-03-28 19:16:37 +02:00
Vincent Demeester
87bad71bec Use libkermit for integration test
Using the compose package for starting and stopping project.
2016-03-27 19:58:08 +02:00
Vincent Demeester
50f09c8e4d Move Suite definition to their respective file 2016-03-27 16:27:56 +02:00
Emile Vauge
bb1ecdd3c9 Merge pull request #262 from containous/fix-marathon-exposedByDefault
Add exposedByDefault doc
2016-03-25 11:14:19 +01:00
Emile Vauge
a2c3e6e405 Add exposedByDefault doc
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-25 10:38:41 +01:00
Emile Vauge
cddbb44c75 Merge pull request #259 from vdemeester/few-build-adjustements
Add a verbose mode to builds
2016-03-24 00:50:09 +01:00
Vincent Demeester
7aa0c91401 Add a verbose mode to builds
Using the VERBOSE environment variable, tests and binary compilation are
ran in verbose mode (using -v), but by default there are more quiet O:).

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-03-23 23:06:22 +01:00
Vincent Demeester
6bfc849a24 Merge pull request #224 from containous/add-lets-encrypt-suppport
Add let's encrypt support
2016-03-23 16:52:21 +01:00
Emile Vauge
ac4aa0d182 add errcheck validation
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-23 16:39:09 +01:00
Emile Vauge
d9ffc39075 add acme package, refactor acme as resuable API
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-22 00:12:43 +01:00
Emile Vauge
87e8393b07 Fix tests with lets encrypt
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-21 20:15:28 +01:00
Emile Vauge
1ab9c82dfb Let's Encrypt Doc
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-21 20:15:28 +01:00
Emile Vauge
6e484e5c2d add let's encrypt support
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-21 20:15:28 +01:00
Emile Vauge
087b68e14d Merge pull request #251 from sample/master
Add defaultExpose option to marathon section
2016-03-21 14:01:57 +01:00
Nikita Borzykh
c313950891 Add exposedByDefault option to marathon section 2016-03-21 12:37:02 +03:00
Vincent Demeester
7716d3377a Merge pull request #256 from containous/move-to-containous-oxy
Move to containous/oxy
2016-03-16 11:41:20 +01:00
Emile Vauge
0cbe34eef3 Move to containous/oxy, remove websocket specific support
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-15 23:42:52 +01:00
Vincent Demeester
08d8c334a3 Merge pull request #255 from containous/Fix-remove-travis-build-tag
Avoid travis building on tags
2016-03-14 18:28:51 +01:00
Emile Vauge
d75a151df3 Avoid travis building on tags
due to https://github.com/travis-ci/travis-ci/issues/1532
2016-03-14 17:36:12 +01:00
Emile Vauge
10e223ede2 Merge pull request #246 from keis/new-style-argument-systemd
Update systemd service file with new cli flags
2016-03-14 16:53:05 +01:00
David Keijser
6a8bacf01c Update systemd service file with new cli flags 2016-03-14 16:34:31 +01:00
Emile Vauge
d4cc3900bd Merge pull request #250 from containous/bump-beta-semver
Bump to beta, using semver
2016-03-09 23:55:26 +01:00
Emile Vauge
ab619a4a3f Bump to beta, using semver
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-09 23:40:01 +01:00
Emile Vauge
4c447985b6 Merge pull request #247 from containous/fix-redirect-bug
fixes redirect bug en port 80
2016-03-09 19:54:16 +01:00
Emile Vauge
eaadd2d0cd fixes redirect bug en port 80 2016-03-09 18:59:08 +01:00
Emile Vauge
9830086790 Merge pull request #242 from containous/fix-old-docker-repo
Fix push to old docker repo emilevauge/traefik
2016-03-08 17:24:26 +01:00
Emile Vauge
8393746e02 Fix benchmarks 2016-03-08 16:27:12 +01:00
Emile Vauge
2314ad9bf9 Fix push to old docker repo emilevauge/traefik 2016-03-08 13:16:58 +01:00
Vincent Demeester
3af21612b6 Merge pull request #239 from goguardian/kv-watch-tree
Support libkv.WatchTree chan errors:
2016-03-05 21:58:48 +01:00
Advait Shinde
7674a82801 Fatalf for timeout cases. 2016-03-05 20:43:44 +00:00
Advait Shinde
d63d2a8a26 Support libkv.WatchTree chan errors:
- libkv.WatchTree returns a channel whose messages represent changes
    to the watched tree. In situations where libkv cannot read from the
    underlying store, libkv will close the provided channel.
  - This PR handles this edge case and fixes #238.
2016-03-05 20:38:33 +00:00
Vincent Demeester
a458018aa2 Merge pull request #240 from containous/update-benchmarks
update benchmarks with haproxy and latest results
2016-03-05 18:39:00 +01:00
Emile Vauge
33cde6aacd update benchmarks with haproxy and latest results
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-05 13:34:18 +01:00
Vincent Demeester
4ded2682d2 Merge pull request #235 from containous/refactor-hot-reload
Refactor hot reload
2016-03-04 16:41:45 +01:00
Emile Vauge
4042938556 add handler switcher instead of Manners
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-04 15:55:28 +01:00
Vincent Demeester
0e683cc535 Merge pull request #234 from thermeon/feature/quick_typo
Fix typo Unkown -> unknown in error
2016-03-04 09:01:49 +01:00
Gareth Kirwan
4923da7f4d Fix typo Unkown -> unknown in error 2016-03-03 20:29:52 +00:00
Vincent Demeester
11781087ca Merge pull request #230 from tayzlor/marathon-event-stream
Use event stream API instead of event subscriptions
2016-03-02 15:24:43 +01:00
Graham Taylor
3063251d43 Use event stream API instead of event subscriptions 2016-03-02 09:22:14 +00:00
Vincent Demeester
b42b170ad2 Merge pull request #227 from containous/fix-docker-network
Fix docker network
2016-03-01 13:12:08 +01:00
Emile Vauge
defbb44b35 Fix docker network
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-03-01 11:52:30 +00:00
Emile Vauge
a00eb81f03 Merge pull request #228 from ProPheT777/patch-1
My $0.02 - Center gopher
2016-02-29 19:35:47 +01:00
Johann Saunier
a63d989a35 My $0.02 - Center gopher 2016-02-29 18:27:09 +01:00
Vincent Demeester
6c3c5578c6 Merge pull request #225 from containous/add-path-prefix
Add PathPrefixStrip and PathStrip rules
2016-02-26 16:52:12 +01:00
Emile Vauge
122783e36b Add PathPrefixStrip and PathStrip rules
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-02-26 15:58:55 +01:00
Emile Vauge
b84b95fe97 Merge pull request #223 from goguardian/kv-multi
Support multiple endpoints for KV stores:
2016-02-26 11:41:18 +01:00
Advait Shinde
a99010b8c2 Create an integration test for Etcd:
- Integration test specifically spins up an Etcd cluster with three
    nodes.
2016-02-25 23:34:51 +00:00
Advait Shinde
8954aa7118 Update docs to mention commas. 2016-02-25 23:34:51 +00:00
Advait Shinde
3cf848958f Support multiple endpoints for KV stores:
- Fixes #222
2016-02-25 23:34:51 +00:00
Vincent Demeester
1a5668377c Merge pull request #209 from tboerger/feature/tls-auth
Integrated TLS auth for etcd and consul
2016-02-25 10:58:23 +01:00
Thomas Boerger
dc10c56b35 Integrated TLS auth for etcd and consul 2016-02-24 23:32:34 +01:00
Vincent Demeester
331cd173ce Merge pull request #220 from containous/transfer-repo-to-containous-org
Transfer emilevauge/traefik to containous/traefik
2016-02-24 22:22:57 +01:00
Emile Vauge
1881d5eeed Transfer emilevauge/traefik to containous/traefik
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-02-24 17:38:36 +01:00
Vincent Demeester
e0872b6157 Merge pull request #219 from emilevauge/add-traefik-library-image
Add publish to traefik-library-image
2016-02-24 16:11:15 +01:00
Emile Vauge
63fb9c7135 publish binary to traefik-library-image repo
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-02-24 15:48:03 +01:00
Vincent Demeester
9964654495 Merge pull request #183 from keis/consul-catalog
WIP consul catalog provider
2016-02-24 09:35:26 +01:00
David Keijser
ae275c9e60 Consul catalog provider
Fixes #176
2016-02-24 09:23:27 +01:00
David Keijser
4277fe2fdb Bump libcompose 2016-02-24 09:23:27 +01:00
Vincent Demeester
7acc2beae0 Merge pull request #214 from octoblu/add-frontend-determinism
Deterministic frontend registration
2016-02-24 00:05:03 +01:00
Roy van de Water
847deeac79 Deterministic frontend registration
Conflicts:
	server.go
2016-02-22 13:37:54 -07:00
Emile Vauge
ac56c1310c Merge pull request #206 from emilevauge/add-partners
Add partners
2016-02-22 17:33:28 +01:00
emile
7460b343fe Cleanup configuration management 2016-02-22 17:15:45 +01:00
emile
ec16011e31 Add partners, move contributing 2016-02-22 16:26:20 +01:00
emile
71b0e27517 bash instead? 2016-02-22 16:03:57 +01:00
emile
60e9282f0a fixes typo ghr 2016-02-22 15:35:51 +01:00
emile
6cd35a50ce after succes make deploy 2016-02-22 15:17:14 +01:00
emile
b35ad76ec6 rewrite deploy tests 2016-02-22 15:01:00 +01:00
emile
54208f6fc3 travis after_success instead of deploy
Signed-off-by: emile <emile@vauge.com>
2016-02-22 14:33:46 +01:00
emile
6282bf33a0 travis skip_cleanup
Signed-off-by: emile <emile@vauge.com>
2016-02-22 14:11:17 +01:00
Emile Vauge
a1c1958235 Merge pull request #208 from emilevauge/migrate-on-travisci
Migrate on travisci
2016-02-22 13:35:00 +01:00
emile
91b699fbe0 Migrate CI to travis
- Add travis build file
- Use golang alpine image
- Clean scripts a little bit
- Disable CGO for test-integration >_<
2016-02-22 12:20:56 +01:00
Vincent Demeester
3a08655b06 Merge pull request #188 from emilevauge/add-marathon-tls-client-config
Add Marathon TLS client config
2016-02-18 14:23:38 +01:00
emile
9a9c8e5709 Add Marathon TLS client config
Signed-off-by: emile <emile@vauge.com>
2016-02-18 12:42:59 +00:00
Vincent Demeester
c7d34b54aa Merge pull request #205 from emilevauge/bump-go-1.6
Bump go 1.6
2016-02-18 11:07:18 +01:00
emile
8d860c84c8 Add HTTP2 support
Signed-off-by: emile <emile@vauge.com>
2016-02-18 10:31:56 +01:00
emile
1dc086730e Bump golang 1.6 2016-02-18 10:30:41 +01:00
Emile Vauge
5d79e56d30 Merge pull request #193 from dontrebootme/fix192
fix mantl url typo
2016-02-17 23:05:26 +01:00
Vincent Demeester
6e7677de79 Merge pull request #203 from goguardian/alias
Implement `/traefik/alias` for KV stores.
2016-02-17 09:56:43 +01:00
Patrick O'Connor
fab6b8be3c fix mantl url typo 2016-02-16 12:30:33 -08:00
Advait Shinde
f1c1eed437 Fix typo in documentation 2016-02-16 11:55:42 -05:00
Advait Shinde
348ab794c9 Add documentation for /traefik/alias. 2016-02-15 18:46:03 -05:00
Advait Shinde
aacedcc4b3 Implement /traefik/alias for KV stores. 2016-02-15 18:14:57 -05:00
Emile Vauge
786acc961a Merge pull request #200 from vdemeester/some-fixes
Do some build / test-integration fixes
2016-02-13 16:33:42 +01:00
Vincent Demeester
7adffdbd78 Run npm in quiet mode O:)
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-02-13 16:14:26 +01:00
Vincent Demeester
e3b519cdd8 Do some build / test-integration fixes
- target generate-webui depends on build-webui
- generate-webui will run only if the folder static does not exists
- create compose project before starting it >_<'', otherwise it does
  nothing :'D

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-02-13 16:04:08 +01:00
Vincent Demeester
e9c23195a0 Merge pull request #195 from emilevauge/fix-regression-marathon-backends
Fix regression on marathon backend
2016-02-12 15:31:59 +01:00
emile
c6c3af8099 Fix regression on marathon backend
Signed-off-by: emile <emile@vauge.com>
2016-02-12 15:03:28 +01:00
Emile Vauge
07c077cf94 Merge pull request #189 from vdemeester/fix-make-all
Fix make all
2016-02-10 14:10:26 +01:00
Vincent Demeester
4ac18f1989 Fix make all
Add build-ui as dependent target for all. Otherwise it does not build >_<

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-02-10 13:55:47 +01:00
Vincent Demeester
4ecb919787 Merge pull request #178 from emilevauge/add-multiple-entrypoints-support
Add multiple entry points support
2016-02-10 13:54:30 +01:00
emile
4152bd5e26 Update doc with entrypoints 2016-02-10 12:18:50 +01:00
emile
a8cc26fd91 Add entrypoints to providers 2016-02-10 12:14:16 +01:00
emile
81cb00573f Fix tests to accept entrypoints 2016-02-10 12:14:16 +01:00
emile
c22598c8ff Add multiple entry points support, add entry point redirection 2016-02-10 12:14:16 +01:00
Vincent Demeester
bb3b9f61cd Merge pull request #187 from emilevauge/add-max-idle-conns-per-host
Add MaxIdleConnsPerHost. Fixes too many open files error.
2016-02-09 22:49:03 +01:00
emile
49cd7f799e Add MaxIdleConnsPerHost. Fixes too many open files error. 2016-02-09 22:29:01 +01:00
Vincent Demeester
8b334551d8 Merge pull request #185 from ReadmeCritic/master
Update README URLs based on HTTP redirects
2016-02-08 19:15:41 +01:00
ReadmeCritic
5ef6d53d00 Update README URLs based on HTTP redirects 2016-02-08 07:43:43 -08:00
Vincent Demeester
901c9b29bc Merge pull request #171 from tayzlor/fix-170-marathon-getbackend
Fix #170 - getBackend for marathon plugin
2016-02-06 14:06:42 +01:00
Graham Taylor
2d79c500df Fix #170 - lookup backend for marathon plugin so we can specify traefik.backend via labels on the container 2016-02-05 18:01:47 +00:00
Vincent Demeester
d3598021b7 Merge pull request #184 from dontrebootme/master
Updated getEscapedName with slash to dash instead of space
2016-02-03 22:09:52 +01:00
Patrick O'Connor
31e0340959 Updated getEscapedName with slash to dash instead of space
Remove only the first slash, convert the rest to dash
2016-02-03 12:07:08 -08:00
Emile Vauge
cb46e8751b Merge pull request #177 from Hugues-Antoine/master
Updating documentation to make a more explicit usage of the traefik.frontend.rule rule when combined with traefik.frontend.value
2016-01-29 19:46:12 +01:00
Hugues-Antoine
739a836c52 Updating documentation to make a more explicit usage of the traefik.frontend.rule rule when combined with traefik.frontend.value 2016-01-29 18:59:31 +01:00
Vincent Demeester
1cbe00d613 Merge pull request #161 from emilevauge/add-env-args
Add environnement variables and arguments configuration
2016-01-29 11:49:33 +01:00
Vincent Demeester
10d92ca176 Merge branch 'master' into add-env-args 2016-01-29 11:34:15 +01:00
Vincent Demeester
257dbd188f Merge pull request #174 from ldez/feature/clean-static
refactor(webui): better build command
2016-01-27 22:55:22 +01:00
Fernandez Ludovic
38cc0579a6 refactor: add static to gitignore 2016-01-27 22:38:48 +01:00
Fernandez Ludovic
a9c8cda5ec refactor: remove static folder 2016-01-27 22:38:47 +01:00
Fernandez Ludovic
06654ff3a6 chore(webui): better build command
- clean before build (mode force)
- update documentation
- update `Makefile`
- ordering build
2016-01-27 22:38:10 +01:00
emile
f1b62b45f4 Fixes following review 2016-01-27 19:47:20 +01:00
emile
8adadaa5d4 Add launch configuration documentation 2016-01-27 13:56:46 +01:00
emile
35070f7c1c Use of Viper and cobra 2016-01-27 13:56:46 +01:00
emile
a0b15a0efd Main file refactoring, extract a Server object 2016-01-27 13:55:40 +01:00
Emile Vauge
b906e9361f Merge pull request #175 from emilevauge/glide-quick
Glide up quick
2016-01-27 11:18:09 +01:00
emile
ec2d7efe0e Add --quick to glide up 2016-01-27 11:00:13 +01:00
Emile Vauge
81e9fdfe75 Merge pull request #172 from keis/sysd-service
Add minimal systemd service file
2016-01-22 18:33:43 +01:00
David Keijser
465bb133c7 Add minimal systemd service file 2016-01-22 14:46:38 +01:00
Vincent Demeester
c15d7e03b4 Merge pull request #149 from ldez/feature/webui-reload
New build system for Web UI
2016-01-21 10:17:17 +01:00
Fernandez Ludovic
5bfcfeb779 feat: build static file in global process
- Create a webui DockerFile
- add task `generate-webui`
- use task `generate-webui` in build process
2016-01-20 23:24:19 +01:00
Fernandez Ludovic
faa7fd0f05 docs(webui): add more documentation 2016-01-20 20:06:07 +01:00
Fernandez Ludovic
ab50b10d1b feat: generate new static files
- use new build system for generate static files
2016-01-20 20:06:07 +01:00
Fernandez Ludovic
b7a71edfcb feat: new Web UI build system
- use generator-gulp-angular by @swiip
- remove old static file
2016-01-20 20:06:07 +01:00
Vincent Demeester
587b17c120 Merge pull request #168 from emilevauge/add-marathon-basic-authentication
Add basic authentication support in Marathon
2016-01-19 09:17:01 +01:00
emile
c46ffed846 Add basic authentication support in Marathon 2016-01-18 13:23:38 +01:00
Vincent Demeester
707b6f9a95 Merge pull request #166 from danzel/patch-1
Consul config doesn't need "http://"
2016-01-15 09:04:12 +01:00
Dave Leaver
4bdc704a25 http here doesn't work 2016-01-15 11:53:13 +13:00
Emile Vauge
c0fd700904 Merge pull request #164 from vdemeester/bump-go
Bump Go 1.5.3
2016-01-14 16:00:51 +01:00
Vincent Demeester
72177c676e Bump Go 1.5.3
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-01-14 15:45:23 +01:00
Vincent Demeester
784fd74d3f Merge pull request #138 from dylanmei/marathon_uses_port_from_label
Marathon provider uses port from label.
2016-01-13 08:30:00 +01:00
Dylan Meissner
cfbd43d1ee Marathon provider uses port or portIndex from label. 2016-01-12 06:46:29 -08:00
Vincent Demeester
f10bbd8c69 Merge pull request #153 from PierreZ/master
Fix Issue #150: Add possibility to have REST API in read-only mode
2016-01-09 00:32:53 +01:00
Pierre Zemb
6bcb6f92f5 Update Doc about read-only mode 2016-01-03 20:45:53 +01:00
PierreZ
f6b5684a5b adding read only mode 2016-01-03 20:42:09 +01:00
Vincent Demeester
866e8db5f7 Merge pull request #137 from dylanmei/marathon_route_names_with_slashes
Marathon tmpl converts slashes in Marathon app names to dashes
2015-12-07 22:12:45 +01:00
Dylan Meissner
a9925c7521 The included Marathon tmpl converts multiple slashes in Marathon app names to dashes. 2015-12-05 10:02:39 -08:00
Vincent Demeester
f955cc33c5 Merge pull request #134 from emilevauge/add-getBool-kvprovider-or-not
Removes getBool from kv provider
2015-12-04 09:41:36 +01:00
emile
e728f32a15 Removes getBool from kv provider, fixes https://github.com/emilevauge/traefik/issues/117 2015-12-04 09:28:43 +01:00
Vincent Demeester
4abb4c6489 Merge pull request #135 from ViBiOh/master
Adding expose for default port
2015-12-03 17:45:22 +01:00
Vincent Boutour
66998e60b8 Adding expose for default port 2015-12-03 17:15:01 +01:00
Vincent Demeester
71288e5799 Merge pull request #123 from emilevauge/alltasks-status-marathon
Add filter task by running status in marathon
2015-12-02 09:49:28 +01:00
emile
8fdd0b20d1 Add filter task by running status in marathon 2015-12-01 22:53:31 +01:00
Emile Vauge
4e9ff45747 Merge pull request #121 from janeczku/sni-support
Add TLS SNI support
2015-12-01 22:52:27 +01:00
Jan Broer
d6e28a923c Adds TLS SNI support for the frontends 2015-12-01 22:26:17 +01:00
Vincent Demeester
1604786285 Merge pull request #126 from emilevauge/lock-prepareserver
Add mutex around prepareserver
2015-11-24 10:20:44 +01:00
emile
35cb9100cd Add mutex around prepareserver 2015-11-24 09:12:20 +01:00
Vincent Demeester
4729e3e999 Merge pull request #124 from emilevauge/panic-bad-configuration
Add check in invoked method
2015-11-23 20:54:16 +01:00
emile
b0e66a4aa6 Add check invoked method 2015-11-23 16:06:47 +01:00
Vincent Demeester
4218467ab3 Merge pull request #122 from emilevauge/add-getBool-kvprovider
Add GetBool function in KV provider, used by passHostHeader
2015-11-23 08:38:35 +01:00
emile
6e62625ebf Add GetBool function in KV provider, used by passHostHeader 2015-11-22 23:39:43 +01:00
Vincent Demeester
c8a0a83e2b Merge pull request #120 from starpost/docker-tls
Docker TLS support
2015-11-20 18:51:36 +01:00
KM Tong
76bd04e349 Merge branch 'master' into docker-tls 2015-11-20 23:13:26 +08:00
唐家文
a8a78b8ea3 docker TLS support 2015-11-20 23:05:06 +08:00
Emile Vauge
3435ebfe42 Merge pull request #108 from vdemeester/refactor-providers-with-test
Refactor providers and add unit tests
2015-11-19 19:42:03 +01:00
Vincent Demeester
4d485e1b6b Refactor providers and add tests
- Add a `baseProvider` struct with common
- Refactor docker, kv(s) and marathon providers (spliting into small pieces)
- Add unit tests

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-19 17:22:17 +01:00
Vincent Demeester
3f905ee7d0 Merge pull request #112 from bennyhansson/master
Add passHostHeader to kv frontend definition.
2015-11-17 16:28:59 +01:00
Benny Hansson
e90cb6b53b Add passHostHeader to frontend definition. 2015-11-16 22:51:52 +01:00
Vincent Demeester
400655f212 Merge pull request #109 from polds/docker/clean-frontend
Replace [ and ] in frontend names
2015-11-13 09:35:14 +01:00
Peter Olds
481a4b2096 Replace [ and ] in frontend names
Signed-off-by: Peter Olds <polds@kyanicorp.com>
2015-11-12 17:04:19 -07:00
Emile Vauge
85bbd49798 Merge pull request #96 from vdemeester/validate-golint
Add validate-golint target and script …
2015-11-08 20:05:01 +01:00
Vincent Demeester
40391c57c2 Add validate-golint target and script …
… and *lint* the latest piece of code.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-06 18:11:57 +01:00
Emile Vauge
7607eb173b Merge pull request #94 from vdemeester/fix-labels-docker
Fix docker labels (frontend.*) non-presence
2015-11-05 15:50:30 +01:00
Vincent Demeester
15318c4631 Fix docker labels (frontend.*)
Using Docker provider, you can specify `traefik.frontend.rule` and
`traefik.frontend.value` labels. If they are not both provided, there is
a default behavior. On the current master, if they are not defined, the
container is filtered (and thus the default behavior is broken).

Fixes that.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-05 15:14:25 +01:00
Emile Vauge
7be566ef7c Merge pull request #93 from vdemeester/integration-test-simple
Updates and additions on some integration tests
2015-11-04 09:12:41 +01:00
Vincent Demeester
3c9ec55f0a Updates and additions on some integration tests
- Use defer to kill traefik process (to fix the still running traefik
  binaries if the given tests is failing before the kill)
- Add TestWithWebConfig
- Add *.test to gitignore to ignore the test binaries generated by go.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-03 23:06:31 +01:00
Emile Vauge
5ee6981410 Merge pull request #92 from vdemeester/linting-some-packages
Linting some packages
2015-11-02 22:47:45 +01:00
Vincent Demeester
c32f82baee Linting types package
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-02 21:15:10 +01:00
Vincent Demeester
89bb1ae835 Linting provider package
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-02 21:15:03 +01:00
Vincent Demeester
9387235a04 Linting middlewares package
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-02 21:14:54 +01:00
Emile Vauge
7766d0ddaa Merge pull request #88 from vdemeester/refactor-package
Refactor traefik with package
2015-11-02 21:08:29 +01:00
Vincent Demeester
cdade5f649 Rename NameProvider to Name
Because golint is gonna cry at some point otherwise.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-02 19:48:34 +01:00
Vincent Demeester
de0a57ec76 Refactor traefik with package
Split a bit traefik into package. The idea behind this refactor is to
start move inter-dependencies away and do some DRY or SRP.

- Adds a `provider` package, with providers except `web.go`
- Adds a `types` package with common struct.
- Move `gen.go` to an `autogen` package

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-02 18:35:55 +01:00
Emile Vauge
6e1a0554c0 Merge pull request #91 from vdemeester/go-bindata-out-of-generation
Remove go get go-bindata from generate.go
2015-11-02 16:36:20 +01:00
Vincent Demeester
ae73d08d67 Remove go get go-bindata from generate.go
This mades the build impossible offline (as when doing a go generate it
was trying to go get something)

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-02 13:56:55 +01:00
Emile Vauge
ddceefa4e1 Merge pull request #90 from vdemeester/move-version-away
Move version info in its own file.
2015-11-02 10:29:34 +01:00
Vincent Demeester
80cd6c3699 Move version info in its own file.
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-11-02 09:14:49 +01:00
Vincent Demeester
9cfd0a6b26 Merge pull request #87 from emilevauge/add-passhostheader
Add passhostheader in frontends configuration
2015-11-02 09:07:14 +01:00
emile
1e99ecf583 Add passHostHeader in frontend configuration, added traefik.frontend.passHostHeader label 2015-11-02 08:40:54 +01:00
Vincent Demeester
aae7941689 Merge pull request #83 from emilevauge/marathon-filter-healthchecks
Add healthcheck filter in marathon tasks
2015-11-01 22:27:28 +01:00
emile
d888b4fcb5 Added healthcheck filter in marathon tasks 2015-11-01 22:06:05 +01:00
Emile Vauge
b029e7eded Merge pull request #84 from vdemeester/ignore-me
Add .dockerignore to lightweight build context
2015-10-30 13:15:34 +01:00
Vincent Demeester
6f3afe8213 Add .dockerignore to lightweight build context
Ignoring vendor/ and dist/

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-10-30 12:10:13 +01:00
Emile Vauge
b4c019afb6 Merge pull request #85 from vdemeester/make-me-happy
Add a all target than runs default tasks
2015-10-30 12:03:06 +01:00
Vincent Demeester
143ea86ab9 Add a all target than runs default tasks
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-10-30 09:11:43 +01:00
Vincent Demeester
287d5c59da Merge pull request #82 from vdemeester/pr-78
Carry #78 Pass websocket headers to backend
2015-10-29 22:56:51 +01:00
Jaime Pillora
ae6bda3220 Pass websocket headers to backend 2015-10-29 22:45:41 +01:00
Emile Vauge
0a6be92290 Merge pull request #80 from vdemeester/use-generate-for-dockerversion
Use go generate for dockerversion
2015-10-29 22:37:47 +01:00
Vincent Demeester
b71b5dd0d4 Use go generate for dockerversion
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-10-29 22:10:59 +01:00
Vincent Demeester
b12c4ac55a Merge pull request #76 from emilevauge/yet-another-refactoring
Yet another refactoring
2015-10-29 21:39:48 +01:00
Emile Vauge
9f736f4235 Merge branch 'master' into yet-another-refactoring 2015-10-29 17:52:04 +01:00
Emile Vauge
b59c54d560 Merge pull request #79 from vdemeester/fix-the-squares
Fixing circleci builds
2015-10-29 14:56:16 +01:00
Vincent Demeester
0429faf65d Fixing circleci builds
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-10-29 14:41:32 +01:00
emile
33d912290b Update docs with Slack 2015-10-28 13:25:57 +01:00
emile
d390f86de2 Code review corrections 2015-10-27 00:26:35 +01:00
emile
aaeb7cdffd Correct BoltDB backend. Fixes #68 2015-10-23 22:21:16 +02:00
emile
32bfecff83 Docs on traefik.frontend. rule and value labels in Docker and Marathon. 2015-10-23 17:46:50 +02:00
emile
d671cc3821 Adds traefik.frontend. rule and value labels in Docker and Marathon. Fixes #64. Fixes #73 2015-10-23 17:46:50 +02:00
emile
5dea2e7902 Remove providerTemplates dir, moved in templates 2015-10-23 17:46:50 +02:00
emile
1fdff9dae4 Move config objects to configuration.go 2015-10-23 17:46:50 +02:00
emile
46d7cc83c9 Better logs http status in websocket 2015-10-23 17:46:50 +02:00
Vincent Demeester
539fd5bafc Merge pull request #72 from emilevauge/ssl-frontend-manners
SSL frontend correction
2015-10-23 10:56:57 +02:00
emile
e8eec77df4 SSL frontend fixes #66 2015-10-23 10:46:13 +02:00
Emile Vauge
9a8d30a0b8 Merge pull request #71 from vdemeester/56-simple-file-panic
Add a regression test for #56 :)
2015-10-18 23:29:37 +02:00
Vincent Demeester
812ff77cec Add a regression test for #56 :)
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-10-17 14:46:31 +02:00
Vincent Demeester
86f95924a9 Merge pull request #70 from vdemeester/carry-pr-48
Carry Add backend throttle duration #48
2015-10-17 14:26:25 +02:00
Vincent Demeester
a0df7ab921 Rename BackendsThrottleDuration to ProvidersThrottleDuration
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-10-17 14:14:20 +02:00
emile
2e5f4598f0 Corrects marathon test 2015-10-17 14:12:24 +02:00
emile
46e162e6a9 Add backend throttle duration, resolves https://github.com/EmileVauge/traefik/issues/46 2015-10-17 14:12:03 +02:00
Vincent Demeester
fd234c683c Merge pull request #65 from EmileVauge/version-in-binary
Adds version in binary
2015-10-15 11:38:27 +02:00
Emile Vauge
67bc87dcda Merge branch 'master' into version-in-binary 2015-10-14 23:44:17 +02:00
Vincent Demeester
c452fd2195 Merge pull request #62 from EmileVauge/websockets-support
Websockets support
2015-10-14 23:13:07 +02:00
emile
8f38337757 Adds version in binary 2015-10-14 22:18:01 +02:00
emile
5454299bf0 update docs 2015-10-14 13:21:40 +02:00
emile
80f4884d50 Added websocket support https://github.com/EmileVauge/traefik/issues/8 2015-10-14 10:42:27 +02:00
emile
4ea48c2d19 Removed panicing spew https://github.com/EmileVauge/traefik/issues/56 2015-10-14 10:39:26 +02:00
Vincent Demeester
37438a6395 Merge pull request #63 from EmileVauge/no-more-godep-ever
Update package management with Glide
2015-10-13 23:38:27 +02:00
emile
784dc9ea62 update docs 2015-10-13 22:57:10 +02:00
emile
6362b1da7f Update package management with Glide 2015-10-13 22:56:44 +02:00
Emile Vauge
31c7aba8c4 Merge pull request #57 from vdemeester/no-more-sleep-in-integration
Update integration setups to use libcompose events
2015-10-12 15:43:08 +02:00
Vincent Demeester
45ea23ecc1 Update integration setups to use libcompose events
Now that docker/libcompose#55 is merged, use it \o/
No more sleeps !

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2015-10-12 08:44:56 +02:00
Vincent Demeester
661ac977d3 Merge pull request #55 from ldez/feature/json-case
refactor(rest) : add json annotation on configuration
2015-10-10 14:52:50 +02:00
Fernandez Ludovic
c11cf801ca refactor(rest) : add json annotation on configuration
- update Web UI
- update documentation
2015-10-10 14:24:36 +02:00
Vincent Demeester
adca5dc55b Merge pull request #52 from EmileVauge/emilevauge-add-traefik-domain
Add traefik.domain label #51
2015-10-09 23:40:31 +02:00
Emile Vauge
641638ba3e Merge branch 'master' into emilevauge-add-traefik-domain 2015-10-09 20:33:25 +02:00
Vincent Demeester
fb7457eba0 Merge pull request #54 from EmileVauge/better-doc-on-kv-structure
Update of the doc about kv
2015-10-09 14:13:03 +02:00
emile
ddf1922eba Update doc about kv structure https://github.com/EmileVauge/traefik/issues/40 2015-10-09 10:34:56 +02:00
Emile Vauge
13f621a9ed Merge branch 'master' into emilevauge-add-traefik-domain 2015-10-08 22:56:34 +02:00
Vincent Demeester
74c5562c2b Merge pull request #43 from ldez/feature/health-graph
Health graph
2015-10-08 22:55:41 +02:00
Fernandez Ludovic
09320b99f9 docs(webui): update screenshots of the web ui 2015-10-08 22:32:39 +02:00
Fernandez Ludovic
a422f775e6 refactor(webui): homepage link correction 2015-10-08 22:32:39 +02:00
Fernandez Ludovic
facc936fe4 docs: enhance Health API documentation 2015-10-08 22:32:38 +02:00
Fernandez Ludovic
28458345b4 feat(webui): new Health screen
- add realtime chart
  - Total Status Code Count
  - Average response time
  - remove status code count
  - D3 & NVD3 & Angular NVD3
2015-10-08 22:32:38 +02:00
Emile Vauge
398dfbd8a5 Merge pull request #53 from EmileVauge/emilevauge-sleep-well
Sleep well
2015-10-08 22:30:56 +02:00
Emile Vauge
3a877a51b9 Merge branch 'master' into emilevauge-sleep-well 2015-10-08 22:21:29 +02:00
emile
5a979b3dd6 Changed sleep to 500ms in tests 2015-10-08 22:11:34 +02:00
Vincent Demeester
f89b727ad1 Merge pull request #47 from ldez/feature/api-frontend-routes
feat(api): add api routes for Frontend Routes
2015-10-08 21:55:12 +02:00
Fernandez Ludovic
781c6aaafa feat(api): add api routes for Frontend Routes
- add `/api/providers/{provider}/frontends/{frontend}/routes`
- add `/api/providers/{provider}/frontends/{frontend}/routes/{route}`
2015-10-08 21:44:29 +02:00
emile
f126e7585d Doc update with traefik.domain label 2015-10-08 21:25:13 +02:00
emile
27eae04e87 Added traefik.domain label. Corrects https://github.com/EmileVauge/traefik/issues/50 2015-10-08 21:21:51 +02:00
223 changed files with 19612 additions and 3801 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
dist/
vendor/
!dist/traefik
site/
**/*.test

78
.github/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,78 @@
# Contributing
### Building
You need either [Docker](https://github.com/docker/docker) and `make`, or `go` and `glide` 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`
You need to run the `binary` target. This will create binaries for Linux platform in the `dist` folder.
```bash
$ make binary
docker build -t "traefik-dev:no-more-godep-ever" -f build.Dockerfile .
Sending build context to Docker daemon 295.3 MB
Step 0 : FROM golang:1.5
---> 8c6473912976
Step 1 : RUN go get github.com/Masterminds/glide
[...]
docker run --rm -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/emile/dev/go/src/github.com/containous/traefik/"dist":/go/src/github.com/containous/traefik/"dist"" "traefik-dev:no-more-godep-ever" ./script/make.sh generate binary
---> Making bundle: generate (in .)
removed 'gen.go'
---> Making bundle: binary (in .)
$ ls dist/
traefik*
```
#### Using `glide`
The idea behind `glide` is the following :
- when checkout(ing) a project, **run `glide install`** to install
(`go get …`) the dependencies in the `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
`vendor` and add it to your `glide.yaml`.
```bash
$ glide install
# generate
$ go generate
# Simple 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
You can run unit tests using the `test-unit` target and the
integration test using the `test-integration` target.
```bash
$ make test-unit
docker build -t "traefik-dev:your-feature-branch" -f build.Dockerfile .
# […]
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/vincent/src/github/vdemeester/traefik/dist:/go/src/github.com/containous/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate test-unit
---> Making bundle: generate (in .)
removed 'gen.go'
---> Making bundle: test-unit (in .)
+ go test -cover -coverprofile=cover.out .
ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
Test success
```

9
.gitignore vendored
View File

@@ -1,10 +1,13 @@
/dist
gen.go
.idea
.intellij
log
*.iml
traefik
traefik.toml
Godeps/_workspace/bin
Godeps/_workspace/pkg
*.test
vendor/
static/
.vscode/
site/

10
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,10 @@
- repo: git://github.com/pre-commit/pre-commit-hooks
sha: 44e1753f98b0da305332abe26856c3e621c5c439
hooks:
- id: detect-private-key
- repo: git://github.com/containous/pre-commit-hooks
sha: 35e641b5107671e94102b0ce909648559e568d61
hooks:
- id: goFmt
- id: goLint
- id: goErrcheck

28
.travis.yml Normal file
View File

@@ -0,0 +1,28 @@
branches:
except:
- /^v\d\.\d\.\d.*$/
env:
global:
- 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: v1.0.0-beta.$TRAVIS_BUILD_NUMBER
sudo: required
services:
- docker
install:
- sudo service docker stop
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-1.10.1 -o /usr/bin/docker
- sudo chmod +x /usr/bin/docker
- sudo service docker start
- pip install --user mkdocs
- pip install --user pymdown-extensions
before_script:
- make validate
- make binary
script:
- make test-unit
- make test-integration
- make crossbinary
- make image
after_success:
- make deploy

BIN
.travis/traefik.id_rsa.enc Normal file

Binary file not shown.

View File

@@ -1,4 +1,5 @@
FROM scratch
COPY script/ca-certificates.crt /etc/ssl/certs/
COPY dist/traefik /
EXPOSE 80
ENTRYPOINT ["/traefik"]

302
Godeps/Godeps.json generated
View File

@@ -1,302 +0,0 @@
{
"ImportPath": "github.com/emilevauge/traefik",
"GoVersion": "go1.4.2",
"Packages": [
"./..."
],
"Deps": [{
"ImportPath": "github.com/BurntSushi/toml",
"Rev": "bd2bdf7f18f849530ef7a1c29a4290217cab32a1"
}, {
"ImportPath": "github.com/BurntSushi/ty",
"Rev": "6add9cd6ad42d389d6ead1dde60b4ad71e46fd74"
}, {
"ImportPath": "github.com/Sirupsen/logrus",
"Comment": "v0.8.7",
"Rev": "418b41d23a1bf978c06faea5313ba194650ac088"
}, {
"ImportPath": "github.com/alecthomas/template",
"Rev": "b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0"
}, {
"ImportPath": "github.com/alecthomas/units",
"Rev": "6b4e7dc5e3143b85ea77909c72caf89416fc2915"
}, {
"ImportPath": "github.com/boltdb/bolt",
"Rev": "51f99c862475898df9773747d3accd05a7ca33c1"
}, {
"ImportPath": "github.com/cenkalti/backoff",
"Rev": "4dc77674aceaabba2c7e3da25d4c823edfb73f99"
}, {
"ImportPath": "github.com/codahale/hdrhistogram",
"Rev": "954f16e8b9ef0e5d5189456aa4c1202758e04f17"
}, {
"ImportPath": "github.com/codegangsta/negroni",
"Comment": "v0.1-70-gc7477ad",
"Rev": "c7477ad8e330bef55bf1ebe300cf8aa67c492d1b"
}, {
"ImportPath": "github.com/coreos/go-etcd/etcd",
"Comment": "v2.0.0-11-gcc90c7b",
"Rev": "cc90c7b091275e606ad0ca7102a23fb2072f3f5e"
}, {
"ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "2df174808ee097f90d259e432cc04442cf60be21"
}, {
"ImportPath": "github.com/docker/libkv",
"Rev": "3732f7ff1b56057c3158f10bceb1e79133025373"
}, {
"ImportPath": "github.com/docker/distribution",
"Comment": "v2.0.0-467-g9038e48",
"Rev": "9038e48c3b982f8e82281ea486f078a73731ac4e"
}, {
"ImportPath": "github.com/docker/docker/api",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/cliconfig",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/daemon/network",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/graph/tags",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/image",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/opts",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/archive",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/fileutils",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/homedir",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/httputils",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/ioutils",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/jsonmessage",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/mflag",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/nat",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/parsers",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/pools",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/promise",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/random",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/stdcopy",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/stringid",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/symlink",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/system",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/tarsum",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/term",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/timeutils",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/tlsconfig",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/ulimit",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/units",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/urlutil",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/useragent",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/pkg/version",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/registry",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/runconfig",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/utils",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/docker/volume",
"Comment": "v1.4.1-5200-gf39987a",
"Rev": "f39987afe8d611407887b3094c03d6ba6a766a67"
}, {
"ImportPath": "github.com/docker/libcompose/docker",
"Rev": "aad672800904307e96a2c21cad1420f3080e0f35"
}, {
"ImportPath": "github.com/docker/libcompose/logger",
"Rev": "aad672800904307e96a2c21cad1420f3080e0f35"
}, {
"ImportPath": "github.com/docker/libcompose/lookup",
"Rev": "aad672800904307e96a2c21cad1420f3080e0f35"
}, {
"ImportPath": "github.com/docker/libcompose/project",
"Rev": "aad672800904307e96a2c21cad1420f3080e0f35"
}, {
"ImportPath": "github.com/docker/libcompose/utils",
"Rev": "aad672800904307e96a2c21cad1420f3080e0f35"
}, {
"ImportPath": "github.com/docker/libtrust",
"Rev": "9cbd2a1374f46905c68a4eb3694a130610adc62a"
}, {
"ImportPath": "github.com/elazarl/go-bindata-assetfs",
"Rev": "d5cac425555ca5cf00694df246e04f05e6a55150"
}, {
"ImportPath": "github.com/flynn/go-shlex",
"Rev": "3f9db97f856818214da2e1057f8ad84803971cff"
}, {
"ImportPath": "github.com/fsouza/go-dockerclient",
"Rev": "0239034d42f665efa17fd77c39f891c2f9f32922"
}, {
"ImportPath": "github.com/gambol99/go-marathon",
"Rev": "0ba31bcb0d7633ba1888d744c42990eb15281cf1"
}, {
"ImportPath": "github.com/gorilla/context",
"Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd"
}, {
"ImportPath": "github.com/gorilla/handlers",
"Rev": "40694b40f4a928c062f56849989d3e9cd0570e5f"
}, {
"ImportPath": "github.com/gorilla/mux",
"Rev": "f15e0c49460fd49eebe2bcc8486b05d1bef68d3a"
}, {
"ImportPath": "github.com/hashicorp/consul/api",
"Comment": "v0.5.2-313-gde08067",
"Rev": "de080672fee9e6104572eeea89eccdca135bb918"
}, {
"ImportPath": "github.com/mailgun/log",
"Rev": "44874009257d4d47ba9806f1b7f72a32a015e4d8"
}, {
"ImportPath": "github.com/mailgun/manners",
"Comment": "0.3.1-30-g37136f7",
"Rev": "37136f736785d7c6aa3b9a27b4b2dd1028ca6d79"
}, {
"ImportPath": "github.com/mailgun/oxy/cbreaker",
"Rev": "547c334d658398c05b346c0b79d8f47ba2e1473b"
}, {
"ImportPath": "github.com/mailgun/oxy/forward",
"Rev": "547c334d658398c05b346c0b79d8f47ba2e1473b"
}, {
"ImportPath": "github.com/mailgun/oxy/memmetrics",
"Rev": "547c334d658398c05b346c0b79d8f47ba2e1473b"
}, {
"ImportPath": "github.com/mailgun/oxy/roundrobin",
"Rev": "547c334d658398c05b346c0b79d8f47ba2e1473b"
}, {
"ImportPath": "github.com/mailgun/oxy/utils",
"Rev": "547c334d658398c05b346c0b79d8f47ba2e1473b"
}, {
"ImportPath": "github.com/mailgun/predicate",
"Rev": "cb0bff91a7ab7cf7571e661ff883fc997bc554a3"
}, {
"ImportPath": "github.com/mailgun/timetools",
"Rev": "fd192d755b00c968d312d23f521eb0cdc6f66bd0"
}, {
"ImportPath": "github.com/samuel/go-zookeeper/zk",
"Rev": "fa6674abf3f4580b946a01bf7a1ce4ba8766205b"
}, {
"ImportPath": "github.com/opencontainers/runc/libcontainer/user",
"Comment": "v0.0.4-21-g4ab1324",
"Rev": "4ab132458fc3e9dbeea624153e0331952dc4c8d5"
}, {
"ImportPath": "github.com/samalba/dockerclient",
"Rev": "cfb489c624b635251a93e74e1e90eb0959c5367f"
}, {
"ImportPath": "github.com/thoas/stats",
"Rev": "54ed61c2b47e263ae2f01b86837b0c4bd1da28e8"
}, {
"ImportPath": "github.com/unrolled/render",
"Rev": "26b4e3aac686940fe29521545afad9966ddfc80c"
}, {
"ImportPath": "github.com/vdemeester/shakers",
"Rev": "8fe734f75f3a70b651cbfbf8a55a009da09e8dc5"
}, {
"ImportPath": "golang.org/x/net/context",
"Rev": "d9558e5c97f85372afee28cf2b6059d7d3818919"
}, {
"ImportPath": "gopkg.in/alecthomas/kingpin.v2",
"Comment": "v2.0.12",
"Rev": "639879d6110b1b0409410c7b737ef0bb18325038"
}, {
"ImportPath": "gopkg.in/check.v1",
"Rev": "11d3bc7aa68e238947792f30573146a3231fc0f1"
}, {
"ImportPath": "gopkg.in/fsnotify.v1",
"Comment": "v1.2.0",
"Rev": "96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0"
}, {
"ImportPath": "gopkg.in/mgo.v2/bson",
"Comment": "r2015.06.03-5-g22287ba",
"Rev": "22287bab4379e1fbf6002fb4eb769888f3fb224c"
}, {
"ImportPath": "gopkg.in/yaml.v2",
"Rev": "7ad95dd0798a40da1ccdff6dff35fd177b5edf40"
}]
}

5
Godeps/Readme generated
View File

@@ -1,5 +0,0 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

3
Godeps/_workspace/.gitignore generated vendored
View File

@@ -1,3 +0,0 @@
/pkg
/bin
/src

View File

@@ -1,12 +0,0 @@
// AUTOGENERATED FILE; see ./hack/make/.go-autogen
package dockerversion
var (
GITCOMMIT string = ""
VERSION string = ""
BUILDTIME string = ""
IAMSTATIC string = "true"
INITSHA1 string = ""
INITPATH string = ""
)

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015 Emile Vauge, emile@vauge.com
Copyright (c) 2016 Containous SAS, Emile Vauge, emile@vauge.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
THE SOFTWARE.

View File

@@ -3,57 +3,61 @@
TRAEFIK_ENVS := \
-e OS_ARCH_ARG \
-e OS_PLATFORM_ARG \
-e TESTFLAGS
-e TESTFLAGS \
-e VERBOSE \
-e VERSION
SRCS = $(shell git ls-files '*.go' | grep -v '^external/')
BIND_DIR := "dist"
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/emilevauge/traefik/$(BIND_DIR)"
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/containous/traefik/$(BIND_DIR)"
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"emilevauge/traefik")
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock")
DOCKER_RUN_TRAEFIK := docker run $(if $(CIRCLECI),,--rm) $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
print-%: ; @echo $*=$($*)
default: binary
binary: build
all: generate-webui build ## validate all checks, build linux binary, run all tests\ncross non-linux binaries
$(DOCKER_RUN_TRAEFIK) ./script/make.sh
binary: generate-webui build ## build the linux binary
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary
crossbinary: build
crossbinary: generate-webui build ## cross build the non-linux binaries
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate crossbinary
test: build
test: build ## run the unit and integration tests
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit binary test-integration
test-unit: build
test-unit: build ## run the unit tests
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit
test-integration: build
test-integration: build ## run the integration tests
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-integration
validate: build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet
validate-gofmt: build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt
validate-govet: build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-govet
validate: build ## validate gofmt, golint and go vet
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
build: dist
docker build -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
build-webui:
docker build -t traefik-webui -f webui/Dockerfile webui
build-no-cache: dist
docker build --no-cache -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
shell: build
shell: build ## start a shell inside the build env
$(DOCKER_RUN_TRAEFIK) /bin/bash
image: build
image: build ## build a docker traefik image
docker build -t $(TRAEFIK_IMAGE) .
dist:
@@ -63,3 +67,22 @@ run-dev:
go generate
go build
./traefik
generate-webui: build-webui
if [ ! -d "static" ]; then \
mkdir -p static; \
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui gulp; \
echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md; \
fi
lint:
script/validate-golint
fmt:
gofmt -s -l -w $(SRCS)
deploy:
./script/deploy.sh
help: ## this help
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

165
README.md
View File

@@ -1,31 +1,65 @@
![Træfɪk](http://traefik.github.io/traefik.logo.svg "Træfɪk")
___
[![Circle CI](https://img.shields.io/circleci/project/EmileVauge/traefik.svg)](https://circleci.com/gh/EmileVauge/traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/EmileVauge/traefik/blob/master/LICENSE.md)
[![Join the chat at https://gitter.im/EmileVauge/traefik](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/EmileVauge/traefik?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<p align="center">
<img src="docs/img/traefik.logo.png" alt="Træfɪk" title="Træfɪk" />
</p>
[![Build Status](https://travis-ci.org/containous/traefik.svg?branch=master)](https://travis-ci.org/containous/traefik)
[![Docs](https://img.shields.io/badge/docs-current-brightgreen.svg)](https://docs.traefik.io)
[![Go Report Card](https://goreportcard.com/badge/kubernetes/helm)](http://goreportcard.com/report/containous/traefik)
[![Image Layer](https://badge.imagelayers.io/traefik:latest.svg)](https://imagelayers.io/?images=traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/containous/traefik/blob/master/LICENSE.md)
[![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com)
[![Twitter](https://img.shields.io/twitter/follow/traefikproxy.svg?style=social)](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 :whale:](https://www.docker.com/), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Consul](https://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), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [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
Imagine that you have deployed a bunch of microservices on your infrastructure. You probably used a service registry (like etcd or consul) and/or an orchestrator (swarm, Mesos/Marathon) to manage all these services.
If you want your users to access some of your microservices from the Internet, you will have to use a reverse proxy and configure it using virtual hosts or prefix paths:
- domain `api.domain.com` will point the microservice `api` in your private network
- path `domain.com/web` will point the microservice `web` in your private network
- domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances
But a microservices architecture is dynamic... Services are added, removed, killed or upgraded often, eventually several times a day.
Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily.
Here enters Træfɪk.
![Architecture](docs/img/architecture.png)
Træfɪk can listen to your service registry/orchestrator API, and knows each time a microservice is added, removed, killed or upgraded, and can generate its configuration automatically.
Routes to your services will be created instantly.
Run it and forget it!
## Features
- [It's fast](http://docs.traefik.io/benchmarks)
- No dependency hell, single binary made with go
- Simple json Rest API
- Simple TOML file configuration
- 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
- Hot-reloading of configuration. No need to restart the process
- Graceful shutdown http connections during hot-reloads
- Graceful shutdown http connections
- Circuit breakers on backends
- Round Robin, rebalancer load-balancers
- Rest Metrics
- Tiny docker image included
- [Tiny](https://imagelayers.io/?images=traefik) [official](https://hub.docker.com/r/_/traefik/) docker image included
- SSL backends support
- SSL frontend support
- WebUI
- SSL frontend support (with SNI)
- Clean AngularJS Web UI
- Websocket support
- HTTP/2 support
- Retry request if network error
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS)
## Demo
@@ -33,119 +67,74 @@ Here is a demo of Træfɪk using Docker backend, showing a load-balancing betwee
[![asciicast](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko.png)](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko)
## Web UI
You can access to a simple HTML frontend of Træfik.
![Web UI Providers](docs/img/web.frontend.png)
![Web UI Health](docs/img/traefik-health.png)
## Plumbing
- [Oxy](https://github.com/mailgun/oxy/): an awsome proxy library made by Mailgun guys
- [Oxy](https://github.com/vulcand/oxy): an awsome proxy library made by Mailgun guys
- [Gorilla mux](https://github.com/gorilla/mux): famous request router
- [Negroni](https://github.com/codegangsta/negroni): web middlewares made simple
- [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
- The simple way: grab the latest binary from the [releases](https://github.com/emilevauge/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/EmileVauge/traefik/master/traefik.sample.toml):
- 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):
```shell
./traefik traefik.toml
./traefik -c traefik.toml
```
- Use the tiny Docker image:
```shell
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/traefik.toml emilevauge/traefik
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
```
- From sources:
```shell
git clone https://github.com/EmileVauge/traefik
git clone https://github.com/containous/traefik
```
## Documentation
You can find the complete documentation [here](docs/index.md).
## Benchmarks
Refer to the [benchmarks section](docs/index.md#benchmarks) in the documentation.
## Web UI
You can access to a simple HTML frontend of Træfik.
![HTML frontend](docs/img/web.frontend.png)
You can find the complete documentation [here](https://docs.traefik.io).
## Contributing
### Building
Please refer to [this section](.github/CONTRIBUTING.md).
You need either [Docker](https://github.com/docker/docker) and `make`, or `go` and `godep` in order to build traefik.
## Træfɪk here and there
#### Using Docker and Makefile
These projects use Træfɪk internally. If your company uses Træfɪk, we would be glad to get your feedback :) Contact us on [![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com)
You need to run the `binary` target. This will create binaries for
linux and darwin platforms in the `dist` folder.
- Project [Mantl](https://mantl.io/) from Cisco
```bash
$ make binary
docker build -t "traefik-dev:your-feature-branch" -f build.Dockerfile .
# […]
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/vincent/src/github/vdemeester/traefik/dist:/go/src/github.com/emilevauge/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate binary
---> Making bundle: generate (in .)
removed 'gen.go'
![Web UI Providers](docs/img/mantl-logo.png)
> Mantl is a modern platform for rapidly deploying globally distributed services. A container orchestrator, docker, a network stack, something to pool your logs, something to monitor health, a sprinkle of service discovery and some automation.
---> Making bundle: binary (in .)
Number of parallel builds: 8
- Project [Apollo](http://capgemini.github.io/devops/apollo/) from Cap Gemini
--> linux/arm: github.com/emilevauge/traefik
--> darwin/amd64: github.com/emilevauge/traefik
--> darwin/386: github.com/emilevauge/traefik
--> linux/386: github.com/emilevauge/traefik
--> linux/amd64: github.com/emilevauge/traefik
![Web UI Providers](docs/img/apollo-logo.png)
> Apollo is an open source project to aid with building and deploying IAAS and PAAS services. It is particularly geared towards managing containerized applications across multiple hosts, and big data type workloads. Apollo leverages other open source components to provide basic mechanisms for deployment, maintenance, and scaling of infrastructure and applications.
$ ls dist/
traefik* traefik_darwin-386* traefik_darwin-amd64* traefik_linux-386* traefik_linux-amd64* traefik_linux-arm*
```
## Partners
#### Using `godep`
[![Zenika](docs/img/zenika.logo.png)](https://zenika.com)
The idea behind `godep` is the following :
Zenika is one of the leading providers of professional Open Source services and agile methodologies in
Europe. We provide consulting, development, training and support for the worlds leading Open Source
software products.
- when checkout(ing) a project, **run `godep restore`** to install
(`go get …`) the dependencies in the `GOPATH`.
- if you need another dependency, `go get` it, import and use it in
the source, and **run `godep save ./...`** to save it in
`Godeps/Godeps.json`.
```bash
$ godep restore
# Generate
$ godep go generate
# Simple go build
$ godep go build
# Using gox to build multiple platform
$ GOPATH=`godep path`:$GOPATH gox "linux darwin" "386 amd64 arm" \
-output="dist/traefik_{{.OS}}-{{.Arch}}"
# run other commands like tests
$ godep go test ./...
ok _/home/vincent/src/github/vdemeester/traefik 0.004s
```
### Tests
[![Asteris](docs/img/asteris.logo.png)](https://aster.is)
You can run unit tests using the `test-unit` target and the
integration test using the `test-integration` target.
```bash
$ make test-unit
docker build -t "traefik-dev:your-feature-branch" -f build.Dockerfile .
# […]
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/vincent/src/github/vdemeester/traefik/dist:/go/src/github.com/emilevauge/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate test-unit
---> Making bundle: generate (in .)
removed 'gen.go'
---> Making bundle: test-unit (in .)
+ go test -cover -coverprofile=cover.out .
ok github.com/emilevauge/traefik 0.005s coverage: 4.1% of statements
Test success
```
Founded in 2014, Asteris creates next-generation infrastructure software for the modern datacenter. Asteris writes software that makes it easy for companies to implement continuous delivery and realtime data pipelines. We support the HashiCorp stack, along with Kubernetes, Apache Mesos, Spark and Kafka. We're core committers on mantl.io, consul-cli and mesos-consul.
.

423
acme/acme.go Normal file
View File

@@ -0,0 +1,423 @@
package acme
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/safe"
"github.com/xenolf/lego/acme"
"io/ioutil"
fmtlog "log"
"os"
"reflect"
"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) {
domainsCertificate.Certificate = acmeCert
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
if err != nil {
return err
}
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
Domains []Domain
StorageFile string
OnDemand bool
CAServer string
EntryPoint string
storageLock sync.RWMutex
}
// Domain holds a domain name with SANs
type Domain struct {
Main string
SANs []string
}
// CreateConfig creates a tls.config from using ACME configuration
func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(domain string) bool) error {
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
if len(a.StorageFile) == 0 {
return errors.New("Empty StorageFile, please provide a filenmae 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)
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()
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.
err = client.AgreeToTOS()
if err != nil {
return err
}
safe.Go(func() {
a.retrieveCertificates(client, account)
})
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())
}
}
}
})
return nil
}
func (a *ACME) retrieveCertificates(client *acme.Client, account *Account) {
log.Infof("Retrieving ACME certificates...")
for _, domain := range a.Domains {
// check if cert isn't already loaded
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)
if err != nil {
log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error())
continue
}
_, 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 {
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
continue
}
}
}
log.Infof("Retrieved ACME certificates")
}
func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
for _, certificateResource := range account.DomainsCertificate.Certs {
if certificateResource.needRenew() {
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
renewedCert, err := client.RenewCertificate(acme.CertificateResource{
Domain: certificateResource.Certificate.Domain,
CertURL: certificateResource.Certificate.CertURL,
CertStableURL: certificateResource.Certificate.CertStableURL,
PrivateKey: certificateResource.Certificate.PrivateKey,
Certificate: certificateResource.Certificate.Certificate,
}, false)
if err != nil {
return err
}
log.Debugf("Renewed certificate %+v", certificateResource.Domains)
renewedACMECert := &Certificate{
Domain: renewedCert.Domain,
CertURL: renewedCert.CertURL,
CertStableURL: renewedCert.CertStableURL,
PrivateKey: renewedCert.PrivateKey,
Certificate: renewedCert.Certificate,
}
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
if err != nil {
return err
}
if err = a.saveAccount(account); err != nil {
return err
}
}
}
return nil
}
func (a *ACME) buildACMEClient(Account *Account) (*acme.Client, error) {
caServer := "https://acme-v01.api.letsencrypt.org/directory"
if len(a.CAServer) > 0 {
caServer = a.CAServer
}
client, err := acme.NewClient(caServer, Account, acme.RSA4096)
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 {
return certificateResource.tlsCert, nil
}
Certificate, err := a.getDomainsCertificates(client, []string{clientHello.ServerName})
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})
if err != nil {
return nil, err
}
if err = a.saveAccount(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
}
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) {
log.Debugf("Loading ACME certificates %s...", domains)
bundle := false
certificate, failures := client.ObtainCertificate(domains, bundle, nil)
if len(failures) > 0 {
log.Error(failures)
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
}
log.Debugf("Loaded ACME certificates %s", domains)
return &Certificate{
Domain: certificate.Domain,
CertURL: certificate.CertURL,
CertStableURL: certificate.CertStableURL,
PrivateKey: certificate.PrivateKey,
Certificate: certificate.Certificate,
}, nil
}

56
acme/challengeProvider.go Normal file
View File

@@ -0,0 +1,56 @@
package acme
import (
"crypto/tls"
"sync"
"crypto/x509"
"github.com/xenolf/lego/acme"
)
type wrapperChallengeProvider struct {
challengeCerts map[string]*tls.Certificate
lock sync.RWMutex
}
func newWrapperChallengeProvider() *wrapperChallengeProvider {
return &wrapperChallengeProvider{
challengeCerts: map[string]*tls.Certificate{},
}
}
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
}
return nil, false
}
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])
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
}
return nil
}
func (c *wrapperChallengeProvider) CleanUp(domain, token, keyAuth string) error {
c.lock.Lock()
defer c.lock.Unlock()
delete(c.challengeCerts, domain)
return nil
}

78
acme/crypto.go Normal file
View File

@@ -0,0 +1,78 @@
package acme
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/pem"
"fmt"
"math/big"
"time"
)
func generateDefaultCertificate() (*tls.Certificate, error) {
rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
rsaPrivPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)})
randomBytes := make([]byte, 100)
_, err = rand.Read(randomBytes)
if err != nil {
return nil, err
}
zBytes := sha256.Sum256(randomBytes)
z := hex.EncodeToString(zBytes[:sha256.Size])
domain := fmt.Sprintf("%s.%s.traefik.default", z[:32], z[32:])
tempCertPEM, err := generatePemCert(rsaPrivKey, domain)
if err != nil {
return nil, err
}
certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
if err != nil {
return nil, err
}
return &certificate, nil
}
func generatePemCert(privKey *rsa.PrivateKey, domain string) ([]byte, error) {
derBytes, err := generateDerCert(privKey, time.Time{}, domain)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
}
func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domain string) ([]byte, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, err
}
if expiration.IsZero() {
expiration = time.Now().Add(365)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "TRAEFIK DEFAULT CERT",
},
NotBefore: time.Now(),
NotAfter: expiration,
KeyUsage: x509.KeyUsageKeyEncipherment,
BasicConstraintsValid: true,
DNSNames: []string{domain},
}
return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
}

View File

@@ -4,42 +4,31 @@ Copyright
package main
import (
log "github.com/Sirupsen/logrus"
"github.com/gorilla/mux"
"github.com/mailgun/oxy/utils"
"net/http"
log "github.com/Sirupsen/logrus"
)
// OxyLogger implements oxy Logger interface with logrus.
type OxyLogger struct {
}
// Infof logs specified string as Debug level in logrus.
func (oxylogger *OxyLogger) Infof(format string, args ...interface{}) {
log.Debugf(format, args...)
}
// Warningf logs specified string as Warning level in logrus.
func (oxylogger *OxyLogger) Warningf(format string, args ...interface{}) {
log.Warningf(format, args...)
}
// Errorf logs specified string as Error level in logrus.
func (oxylogger *OxyLogger) Errorf(format string, args ...interface{}) {
log.Errorf(format, args...)
}
type ErrorHandler struct {
}
func (e *ErrorHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, err error) {
log.Error("server error ", err.Error())
utils.DefaultHandler.ServeHTTP(w, req, err)
}
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
//templatesRenderer.HTML(w, http.StatusNotFound, "notFound", nil)
}
func LoadDefaultConfig(globalConfiguration *GlobalConfiguration) *mux.Router {
router := mux.NewRouter()
router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
return router
}

0
autogen/.placeholder Normal file
View File

View File

@@ -1,14 +0,0 @@
package main
type BoltDbProvider struct {
Watch bool
Endpoint string
Prefix string
Filename string
KvProvider *KvProvider
}
func (provider *BoltDbProvider) Provide(configurationChan chan<- configMessage) error {
provider.KvProvider = NewBoltDbProvider(provider)
return provider.KvProvider.provide(configurationChan)
}

View File

@@ -1,11 +1,17 @@
FROM golang:1.5
FROM golang:1.6.0-alpine
RUN go get github.com/tools/godep
RUN go get github.com/mitchellh/gox
RUN go get github.com/tcnksm/ghr
RUN apk update && apk add git bash gcc musl-dev \
&& go get github.com/Masterminds/glide \
&& go get github.com/mitchellh/gox \
&& go get github.com/jteeuwen/go-bindata/... \
&& go get github.com/golang/lint/golint \
&& go get github.com/kisielk/errcheck
# Which docker version to test on
ENV DOCKER_VERSION 1.6.2
ENV DOCKER_VERSION 1.10.1
# enable GO15VENDOREXPERIMENT
ENV GO15VENDOREXPERIMENT 1
# Download docker
RUN set -ex; \
@@ -15,17 +21,10 @@ RUN set -ex; \
# Set the default Docker to be run
RUN ln -s /usr/local/bin/docker-${DOCKER_VERSION} /usr/local/bin/docker
ENV PATH /go/src/github.com/emilevauge/traefik/Godeps/_workspace/bin:$PATH
WORKDIR /go/src/github.com/containous/traefik
WORKDIR /go/src/github.com/emilevauge/traefik
COPY glide.yaml glide.yaml
COPY glide.lock glide.lock
RUN glide install
# This is a hack (see libcompose#32) - will be removed when libcompose will be fixed
# (i.e go get able)
RUN mkdir -p /go/src/github.com/docker/docker/autogen/dockerversion/
COPY Godeps/_workspace/src/github.com/docker/docker/autogen/dockerversion/dockerversion.go /go/src/github.com/docker/docker/autogen/dockerversion/dockerversion.go
RUN mkdir Godeps
COPY Godeps/Godeps.json Godeps/
RUN godep restore
COPY . /go/src/github.com/emilevauge/traefik
COPY . /go/src/github.com/containous/traefik

View File

@@ -6,6 +6,8 @@ machine:
environment:
REPO: $CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
DOCKER_HOST: tcp://172.17.42.1:2375
MAKE_DOCKER_HOST: $DOCKER_HOST
VERSION: v1.0.alpha.$CIRCLE_BUILD_NUM
dependencies:
pre:
@@ -18,7 +20,7 @@ dependencies:
test:
override:
- make test-unit
- make MAKE_DOCKER_HOST=$DOCKER_HOST test-integration
- make test-integration
post:
- make crossbinary
- make image
@@ -27,8 +29,8 @@ deployment:
hub:
branch: master
commands:
- ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME --prerelease v1.0.alpha.$CIRCLE_BUILD_NUM dist/
- ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME --prerelease ${VERSION} dist/
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
- docker push ${REPO,,}:latest
- docker tag ${REPO,,}:latest ${REPO,,}:v1.0.alpha.$CIRCLE_BUILD_NUM
- docker push ${REPO,,}:v1.0.alpha.$CIRCLE_BUILD_NUM
- docker tag ${REPO,,}:latest ${REPO,,}:${VERSION}
- docker push ${REPO,,}:${VERSION}

219
cmd.go Normal file
View File

@@ -0,0 +1,219 @@
/*
Copyright
*/
package main
import (
"encoding/json"
fmtlog "log"
"os"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/middlewares"
"github.com/containous/traefik/provider"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"net/http"
)
var traefikCmd = &cobra.Command{
Use: "traefik",
Short: "traefik, a modern reverse proxy",
Long: `traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
Complete documentation is available at http://traefik.io`,
Run: func(cmd *cobra.Command, args []string) {
run()
},
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print version",
Long: `Print version`,
Run: func(cmd *cobra.Command, args []string) {
fmtlog.Println(Version + " built on the " + BuildDate)
os.Exit(0)
},
}
var arguments = struct {
GlobalConfiguration
web bool
file bool
docker bool
dockerTLS bool
marathon bool
consul bool
consulTLS bool
consulCatalog bool
zookeeper bool
etcd bool
etcdTLS bool
boltdb bool
}{
GlobalConfiguration{
EntryPoints: make(EntryPoints),
Docker: &provider.Docker{
TLS: &provider.DockerTLS{},
},
File: &provider.File{},
Web: &WebProvider{},
Marathon: &provider.Marathon{},
Consul: &provider.Consul{
Kv: provider.Kv{
TLS: &provider.KvTLS{},
},
},
ConsulCatalog: &provider.ConsulCatalog{},
Zookeeper: &provider.Zookepper{},
Etcd: &provider.Etcd{
Kv: provider.Kv{
TLS: &provider.KvTLS{},
},
},
Boltdb: &provider.BoltDb{},
},
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
}
func init() {
traefikCmd.AddCommand(versionCmd)
traefikCmd.PersistentFlags().StringP("configFile", "c", "", "Configuration file to use (TOML, JSON, YAML, HCL).")
traefikCmd.PersistentFlags().StringP("graceTimeOut", "g", "10", "Timeout in seconds. Duration to give active requests a chance to finish during hot-reloads")
traefikCmd.PersistentFlags().String("accessLogsFile", "log/access.log", "Access logs file")
traefikCmd.PersistentFlags().String("traefikLogsFile", "log/traefik.log", "Traefik logs file")
traefikCmd.PersistentFlags().Var(&arguments.EntryPoints, "entryPoints", "Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'")
traefikCmd.PersistentFlags().Var(&arguments.DefaultEntryPoints, "defaultEntryPoints", "Entrypoints to be used by frontends that do not specify any entrypoint")
traefikCmd.PersistentFlags().StringP("logLevel", "l", "ERROR", "Log level")
traefikCmd.PersistentFlags().DurationVar(&arguments.ProvidersThrottleDuration, "providersThrottleDuration", time.Duration(2*time.Second), "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.")
traefikCmd.PersistentFlags().Int("maxIdleConnsPerHost", 0, "If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used")
traefikCmd.PersistentFlags().BoolVar(&arguments.web, "web", false, "Enable Web backend")
traefikCmd.PersistentFlags().StringVar(&arguments.Web.Address, "web.address", ":8080", "Web administration port")
traefikCmd.PersistentFlags().StringVar(&arguments.Web.CertFile, "web.cerFile", "", "SSL certificate")
traefikCmd.PersistentFlags().StringVar(&arguments.Web.KeyFile, "web.keyFile", "", "SSL certificate")
traefikCmd.PersistentFlags().BoolVar(&arguments.Web.ReadOnly, "web.readOnly", false, "Enable read only API")
traefikCmd.PersistentFlags().BoolVar(&arguments.file, "file", false, "Enable File backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.File.Watch, "file.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.File.Filename, "file.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().BoolVar(&arguments.docker, "docker", false, "Enable Docker backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Docker.Watch, "docker.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Filename, "docker.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Endpoint, "docker.endpoint", "unix:///var/run/docker.sock", "Docker server endpoint. Can be a tcp or a unix socket endpoint")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.Domain, "docker.domain", "", "Default domain used")
traefikCmd.PersistentFlags().BoolVar(&arguments.dockerTLS, "docker.tls", false, "Enable Docker TLS support")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.CA, "docker.tls.ca", "", "TLS CA")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.Cert, "docker.tls.cert", "", "TLS cert")
traefikCmd.PersistentFlags().StringVar(&arguments.Docker.TLS.Key, "docker.tls.key", "", "TLS key")
traefikCmd.PersistentFlags().BoolVar(&arguments.Docker.TLS.InsecureSkipVerify, "docker.tls.insecureSkipVerify", false, "TLS insecure skip verify")
traefikCmd.PersistentFlags().BoolVar(&arguments.marathon, "marathon", false, "Enable Marathon backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Marathon.Watch, "marathon.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Filename, "marathon.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Endpoint, "marathon.endpoint", "http://127.0.0.1:8080", "Marathon server endpoint. You can also specify multiple endpoint for Marathon")
traefikCmd.PersistentFlags().StringVar(&arguments.Marathon.Domain, "marathon.domain", "", "Default domain used")
traefikCmd.PersistentFlags().BoolVar(&arguments.Marathon.ExposedByDefault, "marathon.exposedByDefault", true, "Expose Marathon apps by default")
traefikCmd.PersistentFlags().BoolVar(&arguments.consul, "consul", false, "Enable Consul backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.Watch, "consul.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Filename, "consul.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Endpoint, "consul.endpoint", "127.0.0.1:8500", "Comma sepparated Consul server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.Prefix, "consul.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.consulTLS, "consul.tls", false, "Enable Consul TLS support")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.CA, "consul.tls.ca", "", "TLS CA")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.Cert, "consul.tls.cert", "", "TLS cert")
traefikCmd.PersistentFlags().StringVar(&arguments.Consul.TLS.Key, "consul.tls.key", "", "TLS key")
traefikCmd.PersistentFlags().BoolVar(&arguments.Consul.TLS.InsecureSkipVerify, "consul.tls.insecureSkipVerify", false, "TLS insecure skip verify")
traefikCmd.PersistentFlags().BoolVar(&arguments.consulCatalog, "consulCatalog", false, "Enable Consul catalog backend")
traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Domain, "consulCatalog.domain", "", "Default domain used")
traefikCmd.PersistentFlags().StringVar(&arguments.ConsulCatalog.Endpoint, "consulCatalog.endpoint", "127.0.0.1:8500", "Consul server endpoint")
traefikCmd.PersistentFlags().BoolVar(&arguments.zookeeper, "zookeeper", false, "Enable Zookeeper backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Zookeeper.Watch, "zookeeper.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Filename, "zookeeper.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Endpoint, "zookeeper.endpoint", "127.0.0.1:2181", "Comma sepparated Zookeeper server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Zookeeper.Prefix, "zookeeper.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.etcd, "etcd", false, "Enable Etcd backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Etcd.Watch, "etcd.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Filename, "etcd.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Endpoint, "etcd.endpoint", "127.0.0.1:4001", "Comma sepparated Etcd server endpoints")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.Prefix, "etcd.prefix", "/traefik", "Prefix used for KV store")
traefikCmd.PersistentFlags().BoolVar(&arguments.etcdTLS, "etcd.tls", false, "Enable Etcd TLS support")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.CA, "etcd.tls.ca", "", "TLS CA")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.Cert, "etcd.tls.cert", "", "TLS cert")
traefikCmd.PersistentFlags().StringVar(&arguments.Etcd.TLS.Key, "etcd.tls.key", "", "TLS key")
traefikCmd.PersistentFlags().BoolVar(&arguments.Etcd.TLS.InsecureSkipVerify, "etcd.tls.insecureSkipVerify", false, "TLS insecure skip verify")
traefikCmd.PersistentFlags().BoolVar(&arguments.boltdb, "boltdb", false, "Enable Boltdb backend")
traefikCmd.PersistentFlags().BoolVar(&arguments.Boltdb.Watch, "boltdb.watch", true, "Watch provider")
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Filename, "boltdb.filename", "", "Override default configuration template. For advanced users :)")
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Endpoint, "boltdb.endpoint", "127.0.0.1:4001", "Boltdb server endpoint")
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Prefix, "boltdb.prefix", "/traefik", "Prefix used for KV store")
_ = viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile"))
_ = viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut"))
_ = viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel"))
// TODO: wait for this issue to be corrected: https://github.com/spf13/viper/issues/105
_ = viper.BindPFlag("providersThrottleDuration", traefikCmd.PersistentFlags().Lookup("providersThrottleDuration"))
_ = viper.BindPFlag("maxIdleConnsPerHost", traefikCmd.PersistentFlags().Lookup("maxIdleConnsPerHost"))
viper.SetDefault("providersThrottleDuration", time.Duration(2*time.Second))
viper.SetDefault("logLevel", "ERROR")
viper.SetDefault("MaxIdleConnsPerHost", 200)
}
func run() {
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
// load global configuration
globalConfiguration := LoadConfiguration()
http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = globalConfiguration.MaxIdleConnsPerHost
loggerMiddleware := middlewares.NewLogger(globalConfiguration.AccessLogsFile)
defer loggerMiddleware.Close()
// logging
level, err := log.ParseLevel(strings.ToLower(globalConfiguration.LogLevel))
if err != nil {
log.Fatal("Error getting level", err)
}
log.SetLevel(level)
if len(globalConfiguration.TraefikLogsFile) > 0 {
fi, err := os.OpenFile(globalConfiguration.TraefikLogsFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
defer func() {
if err := fi.Close(); err != nil {
log.Error("Error closinf file", err)
}
}()
if err != nil {
log.Fatal("Error opening file", err)
} else {
log.SetOutput(fi)
log.SetFormatter(&log.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true})
}
} else {
log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true})
}
jsonConf, _ := json.Marshal(globalConfiguration)
log.Debugf("Global configuration loaded %s", string(jsonConf))
server := NewServer(*globalConfiguration)
server.Start()
defer server.Close()
log.Info("Shutting down")
}

View File

@@ -2,94 +2,297 @@ package main
import (
"errors"
"fmt"
fmtlog "log"
"regexp"
"strings"
"time"
"github.com/containous/traefik/acme"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/types"
"github.com/mitchellh/mapstructure"
"github.com/spf13/viper"
)
// GlobalConfiguration holds global configuration (with providers, etc.).
// It's populated from the traefik configuration file passed as an argument to the binary.
type GlobalConfiguration struct {
Port string
GraceTimeOut int64
AccessLogsFile string
TraefikLogsFile string
CertFile, KeyFile string
LogLevel string
Docker *DockerProvider
File *FileProvider
Web *WebProvider
Marathon *MarathonProvider
Consul *ConsulProvider
Etcd *EtcdProvider
Zookeeper *ZookepperProvider
Boltdb *BoltDbProvider
GraceTimeOut int64
AccessLogsFile string
TraefikLogsFile string
LogLevel string
EntryPoints EntryPoints
ACME *acme.ACME
DefaultEntryPoints DefaultEntryPoints
ProvidersThrottleDuration time.Duration
MaxIdleConnsPerHost int
Retry *Retry
Docker *provider.Docker
File *provider.File
Web *WebProvider
Marathon *provider.Marathon
Consul *provider.Consul
ConsulCatalog *provider.ConsulCatalog
Etcd *provider.Etcd
Zookeeper *provider.Zookepper
Boltdb *provider.BoltDb
}
func NewGlobalConfiguration() *GlobalConfiguration {
globalConfiguration := new(GlobalConfiguration)
// default values
globalConfiguration.Port = ":80"
globalConfiguration.GraceTimeOut = 10
globalConfiguration.LogLevel = "ERROR"
// DefaultEntryPoints holds default entry points
type DefaultEntryPoints []string
return globalConfiguration
// 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 (dep *DefaultEntryPoints) String() string {
return fmt.Sprintf("%#v", dep)
}
type Backend struct {
Servers map[string]Server
CircuitBreaker *CircuitBreaker
LoadBalancer *LoadBalancer
// 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 (dep *DefaultEntryPoints) Set(value string) error {
entrypoints := strings.Split(value, ",")
if len(entrypoints) == 0 {
return errors.New("Bad DefaultEntryPoints format: " + value)
}
for _, entrypoint := range entrypoints {
*dep = append(*dep, entrypoint)
}
return nil
}
type LoadBalancer struct {
Method string
// Type is type of the struct
func (dep *DefaultEntryPoints) Type() string {
return fmt.Sprint("defaultentrypoints²")
}
type CircuitBreaker struct {
Expression string
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
type EntryPoints map[string]*EntryPoint
// 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 (ep *EntryPoints) String() string {
return ""
}
type Server struct {
URL string
Weight int
}
type Route struct {
Rule string
Value string
}
type Frontend struct {
Backend string
Routes map[string]Route
}
type Configuration struct {
Backends map[string]*Backend
Frontends map[string]*Frontend
}
// Load Balancer Method
type LoadBalancerMethod uint8
const (
// wrr (default) = Weighted Round Robin
wrr LoadBalancerMethod = iota
// drr = Dynamic Round Robin
drr
)
var loadBalancerMethodNames = []string{
"wrr",
"drr",
}
func NewLoadBalancerMethod(loadBalancer *LoadBalancer) (LoadBalancerMethod, error) {
if loadBalancer != nil {
for i, name := range loadBalancerMethodNames {
if strings.EqualFold(name, loadBalancer.Method) {
return LoadBalancerMethod(i), nil
}
// 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 (ep *EntryPoints) Set(value string) error {
regex := regexp.MustCompile("(?:Name:(?P<Name>\\S*))\\s*(?:Address:(?P<Address>\\S*))?\\s*(?:TLS:(?P<TLS>\\S*))?\\s*(?:Redirect.EntryPoint:(?P<RedirectEntryPoint>\\S*))?\\s*(?:Redirect.Regex:(?P<RedirectRegex>\\S*))?\\s*(?:Redirect.Replacement:(?P<RedirectReplacement>\\S*))?")
match := regex.FindAllStringSubmatch(value, -1)
if match == nil {
return errors.New("Bad EntryPoints format: " + value)
}
matchResult := match[0]
result := make(map[string]string)
for i, name := range regex.SubexpNames() {
if i != 0 {
result[name] = matchResult[i]
}
}
return wrr, ErrInvalidLoadBalancerMethod
var tls *TLS
if len(result["TLS"]) > 0 {
certs := Certificates{}
if err := certs.Set(result["TLS"]); err != nil {
return err
}
tls = &TLS{
Certificates: certs,
}
}
var redirect *Redirect
if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 {
redirect = &Redirect{
EntryPoint: result["RedirectEntryPoint"],
Regex: result["RedirectRegex"],
Replacement: result["RedirectReplacement"],
}
}
(*ep)[result["Name"]] = &EntryPoint{
Address: result["Address"],
TLS: tls,
Redirect: redirect,
}
return nil
}
var ErrInvalidLoadBalancerMethod = errors.New("Invalid method, using default")
// Type is type of the struct
func (ep *EntryPoints) Type() string {
return fmt.Sprint("entrypoints²")
}
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
type EntryPoint struct {
Network string
Address string
TLS *TLS
Redirect *Redirect
}
// Redirect configures a redirection of an entry point to another, or to an URL
type Redirect struct {
EntryPoint string
Regex string
Replacement string
}
// TLS configures TLS for an entry point
type TLS struct {
Certificates Certificates
}
// Certificates defines traefik certificates type
type Certificates []Certificate
// 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
}
// 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)
}
*certs = append(*certs, Certificate{
CertFile: files[0],
KeyFile: files[1],
})
return nil
}
// Type is type of the struct
func (certs *Certificates) Type() string {
return fmt.Sprint("certificates")
}
// Certificate holds a SSL cert/key pair
type Certificate struct {
CertFile string
KeyFile string
}
// Retry contains request retry config
type Retry struct {
Attempts int
MaxMem int64
}
// NewGlobalConfiguration returns a GlobalConfiguration with default values.
func NewGlobalConfiguration() *GlobalConfiguration {
return new(GlobalConfiguration)
}
// LoadConfiguration returns a GlobalConfiguration.
func LoadConfiguration() *GlobalConfiguration {
configuration := NewGlobalConfiguration()
viper.SetEnvPrefix("traefik")
viper.SetConfigType("toml")
viper.AutomaticEnv()
if len(viper.GetString("configFile")) > 0 {
viper.SetConfigFile(viper.GetString("configFile"))
} else {
viper.SetConfigName("traefik") // name of config file (without extension)
}
viper.AddConfigPath("/etc/traefik/") // path to look for the config file in
viper.AddConfigPath("$HOME/.traefik/") // call multiple times to add many search paths
viper.AddConfigPath(".") // optionally look for config in the working directory
if err := viper.ReadInConfig(); err != nil {
fmtlog.Fatalf("Error reading file: %s", err)
}
if len(arguments.EntryPoints) > 0 {
viper.Set("entryPoints", arguments.EntryPoints)
}
if len(arguments.DefaultEntryPoints) > 0 {
viper.Set("defaultEntryPoints", arguments.DefaultEntryPoints)
}
if arguments.web {
viper.Set("web", arguments.Web)
}
if arguments.file {
viper.Set("file", arguments.File)
}
if !arguments.dockerTLS {
arguments.Docker.TLS = nil
}
if arguments.docker {
viper.Set("docker", arguments.Docker)
}
if arguments.marathon {
viper.Set("marathon", arguments.Marathon)
}
if !arguments.consulTLS {
arguments.Consul.TLS = nil
}
if arguments.consul {
viper.Set("consul", arguments.Consul)
}
if arguments.consulCatalog {
viper.Set("consulCatalog", arguments.ConsulCatalog)
}
if arguments.zookeeper {
viper.Set("zookeeper", arguments.Zookeeper)
}
if !arguments.etcdTLS {
arguments.Etcd.TLS = nil
}
if arguments.etcd {
viper.Set("etcd", arguments.Etcd)
}
if arguments.boltdb {
viper.Set("boltdb", arguments.Boltdb)
}
if err := unmarshal(&configuration); err != nil {
fmtlog.Fatalf("Error reading file: %s", err)
}
if len(configuration.EntryPoints) == 0 {
configuration.EntryPoints = make(map[string]*EntryPoint)
configuration.EntryPoints["http"] = &EntryPoint{
Address: ":80",
}
configuration.DefaultEntryPoints = []string{"http"}
}
if configuration.File != nil && len(configuration.File.Filename) == 0 {
// no filename, setting to global config file
configuration.File.Filename = viper.ConfigFileUsed()
}
return configuration
}
func unmarshal(rawVal interface{}) error {
config := &mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
Metadata: nil,
Result: rawVal,
WeaklyTypedInput: true,
}
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
return err
}
err = decoder.Decode(viper.AllSettings())
if err != nil {
return err
}
return nil
}
type configs map[string]*types.Configuration

View File

@@ -1,14 +0,0 @@
package main
type ConsulProvider struct {
Watch bool
Endpoint string
Prefix string
Filename string
KvProvider *KvProvider
}
func (provider *ConsulProvider) Provide(configurationChan chan<- configMessage) error {
provider.KvProvider = NewConsulProvider(provider)
return provider.KvProvider.provide(configurationChan)
}

View File

@@ -0,0 +1,6 @@
[Unit]
Description=Traefik
[Service]
ExecStart=/usr/bin/traefik --configFile=/etc/traefik.toml
Restart=on-failure

190
docker.go
View File

@@ -1,190 +0,0 @@
package main
import (
"bytes"
"errors"
"strconv"
"strings"
"text/template"
"time"
"github.com/BurntSushi/toml"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/fsouza/go-dockerclient"
)
type DockerProvider struct {
Watch bool
Endpoint string
Filename string
Domain string
}
var DockerFuncMap = template.FuncMap{
"getBackend": func(container docker.Container) string {
for key, value := range container.Config.Labels {
if key == "traefik.backend" {
return value
}
}
return getHost(container)
},
"getPort": func(container docker.Container) string {
for key, value := range container.Config.Labels {
if key == "traefik.port" {
return value
}
}
for key := range container.NetworkSettings.Ports {
return key.Port()
}
return ""
},
"getWeight": func(container docker.Container) string {
for key, value := range container.Config.Labels {
if key == "traefik.weight" {
return value
}
}
return "0"
},
"replace": func(s1 string, s2 string, s3 string) string {
return strings.Replace(s3, s1, s2, -1)
},
"getHost": getHost,
}
func (provider *DockerProvider) Provide(configurationChan chan<- configMessage) error {
if dockerClient, err := docker.NewClient(provider.Endpoint); err != nil {
log.Errorf("Failed to create a client for docker, error: %s", err)
return err
} else {
err := dockerClient.Ping()
if err != nil {
log.Errorf("Docker connection error %+v", err)
return err
}
log.Debug("Docker connection established")
if provider.Watch {
dockerEvents := make(chan *docker.APIEvents)
dockerClient.AddEventListener(dockerEvents)
log.Debug("Docker listening")
go func() {
operation := func() error {
for {
event := <-dockerEvents
if event == nil {
return errors.New("Docker event nil")
// log.Fatalf("Docker connection error")
}
if event.Status == "start" || event.Status == "die" {
log.Debugf("Docker event receveived %+v", event)
configuration := provider.loadDockerConfig(dockerClient)
if configuration != nil {
configurationChan <- configMessage{"docker", configuration}
}
}
}
}
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)
if err != nil {
log.Fatalf("Cannot connect to docker server %+v", err)
}
}()
}
configuration := provider.loadDockerConfig(dockerClient)
configurationChan <- configMessage{"docker", configuration}
}
return nil
}
func (provider *DockerProvider) loadDockerConfig(dockerClient *docker.Client) *Configuration {
configuration := new(Configuration)
containerList, _ := dockerClient.ListContainers(docker.ListContainersOptions{})
containersInspected := []docker.Container{}
hosts := map[string][]docker.Container{}
// get inspect containers
for _, container := range containerList {
containerInspected, _ := dockerClient.InspectContainer(container.ID)
containersInspected = append(containersInspected, *containerInspected)
}
// filter containers
filteredContainers := fun.Filter(func(container docker.Container) bool {
if len(container.NetworkSettings.Ports) == 0 {
log.Debugf("Filtering container without port %s", container.Name)
return false
}
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
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" {
log.Debugf("Filtering disabled container %s", container.Name)
return false
}
return true
}, containersInspected).([]docker.Container)
for _, container := range filteredContainers {
hosts[getHost(container)] = append(hosts[getHost(container)], container)
}
templateObjects := struct {
Containers []docker.Container
Hosts map[string][]docker.Container
Domain string
}{
filteredContainers,
hosts,
provider.Domain,
}
tmpl := template.New(provider.Filename).Funcs(DockerFuncMap)
if len(provider.Filename) > 0 {
_, err := tmpl.ParseFiles(provider.Filename)
if err != nil {
log.Error("Error reading file", err)
return nil
}
} else {
buf, err := Asset("providerTemplates/docker.tmpl")
if err != nil {
log.Error("Error reading file", err)
}
_, err = tmpl.Parse(string(buf))
if err != nil {
log.Error("Error reading file", err)
return nil
}
}
var buffer bytes.Buffer
err := tmpl.Execute(&buffer, templateObjects)
if err != nil {
log.Error("Error with docker template", err)
return nil
}
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
log.Error("Error creating docker configuration", err)
return nil
}
return configuration
}
func getHost(container docker.Container) string {
for key, value := range container.Config.Labels {
if key == "traefik.host" {
return value
}
}
return strings.Replace(strings.Replace(container.Name, "/", "", -1), ".", "-", -1)
}

1
docs/CNAME Normal file
View File

@@ -0,0 +1 @@
docs.traefik.io

182
docs/basics.md Normal file
View File

@@ -0,0 +1,182 @@
# Concepts
Let's take our example from the [overview](https://docs.traefik.io/#overview) again:
> Imagine that you have deployed a bunch of microservices on your infrastructure. You probably used a service registry (like etcd or consul) and/or an orchestrator (swarm, Mesos/Marathon) to manage all these services.
> If you want your users to access some of your microservices from the Internet, you will have to use a reverse proxy and configure it using virtual hosts or prefix paths:
> - domain `api.domain.com` will point the microservice `api` in your private network
> - path `domain.com/web` will point the microservice `web` in your private network
> - domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances
> ![Architecture](img/architecture.png)
Let's zoom on Træfɪk and have an overview of its internal architecture:
![Architecture](img/internal.png)
- Incoming requests end on [entrypoints](#entrypoints), as the name suggests, they are the network entry points into Træfɪk (listening port, SSL, traffic redirection...).
- Traffic is then forwared to a matching [frontend](#frontends). A frontend defines routes from [entrypoints](#entrypoints) to [backends](#backends).
Routes are created using requests fields (`Host`, `Path`, `Headers`...) and can match or not a request.
- The [frontend](#frontends) will then send the request to a [backend](#backends). A backend can be composed by one or more [servers](#servers), and by a load-balancing strategy.
- Finally, the [server](#servers) will forward the request to the corresponding microservice in the private network.
## Entrypoints
Entrypoints are the network entry points into Træfɪk.
They can be defined using:
- a port (80, 443...)
- SSL (Certificates. Keys...)
- redirection to another entrypoint (redirect `HTTP` to `HTTPS`)
Here is an example of entrypoints definition:
```toml
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
certFile = "tests/traefik.crt"
keyFile = "tests/traefik.key"
```
- Two entrypoints are defined `http` and `https`.
- `http` listens on port `80` et `https` on port `443`.
- We enable SSL en `https` by giving a certificate and a key.
- We also redirect all the traffic from entrypoint `http` to `https`.
## Frontends
A frontend is a set of rules that forwards the incoming traffic from an entrypoint to a backend.
Frontends can be defined using the following rules:
- `Headers: Content-Type, application/json`: Headers adds a matcher for request header values. It accepts a sequence of key/value pairs to be matched.
- `HeadersRegexp: Content-Type, application/(text|json)`: Regular expressions can be used with headers as well. It accepts a sequence of key/value pairs, where the value has regex support.
- `Host: traefik.io, www.traefik.io`: Match request host with given host list.
- `HostRegexp: traefik.io, {subdomain:[a-z]+}.traefik.io`: Adds a matcher for the URL hosts. It accepts templates with zero or more URL variables enclosed by `{}`. Variables can define an optional regexp pattern to be matched.
- `Method: GET, POST, PUT`: Method adds a matcher for HTTP methods. It accepts a sequence of one or more methods to be matched.
- `Path: /products/, /articles/{category}/{id:[0-9]+}`: Path adds a matcher for the URL paths. It accepts templates with zero or more URL variables enclosed by `{}`.
- `PathStrip`: Same as `Path` but strip the given prefix from the request URL's Path.
- `PathPrefix`: PathPrefix adds a matcher for the URL path prefixes. This matches if the given template is a prefix of the full URL path.
- `PathPrefixStrip`: Same as `PathPrefix` but strip the given prefix from the request URL's Path.
You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
Here is an example of frontends definition:
```toml
[frontends]
[frontends.frontend1]
backend = "backend2"
[frontends.frontend1.routes.test_1]
rule = "Host: test.localhost, test2.localhost"
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host: localhost, {subdomain:[a-z]+}.localhost"
[frontends.frontend3]
backend = "backend2"
rule = "Path:/test"
```
- Three frontends are defined: `frontend1`, `frontend2` and `frontend3`
- `frontend1` will forward the traffic to the `backend2` if the rule `Host: test.localhost, test2.localhost` is matched
- `frontend2` will forward the traffic to the `backend1` if the rule `Host: localhost, {subdomain:[a-z]+}.localhost` is matched (forwarding client `Host` header to the backend)
- `frontend3` will forward the traffic to the `backend2` if the rule `Path:/test` is matched
## Backends
A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers.
Various methods of load-balancing is supported:
- `wrr`: Weighted Round Robin
- `drr`: Dynamic Round Robin: increases weights on servers that perform better than others. It also rolls back to original weights if the servers have changed.
A circuit breaker can also be applied to a backend, preventing high loads on failing servers.
Initial state is Standby. CB observes the statistics and does not modify the request.
In case if condition matches, CB enters Tripped state, where it responds with predefines code or redirects to another frontend.
Once Tripped timer expires, CB enters Recovering state and resets all stats.
In case if the condition does not match and recovery timer expries, CB enters Standby state.
It can be configured using:
- Methods: `LatencyAtQuantileMS`, `NetworkErrorRatio`, `ResponseCodeRatio`
- Operators: `AND`, `OR`, `EQ`, `NEQ`, `LT`, `LE`, `GT`, `GE`
For example:
- `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window for a frontend
- `LatencyAtQuantileMS(50.0) > 50`: watch latency at quantile in milliseconds.
- `ResponseCodeRatio(500, 600, 0, 600) > 0.5`: ratio of response codes in range [500-600) to [0-600)
## Servers
Servers are simply defined using a `URL`. You can also apply a custom `weight` to each server (this will be used by load-balacning).
Here is an example of backends and servers definition:
```toml
[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.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
```
- Two backends are defined: `backend1` and `backend2`
- `backend1` will forward the traffic to two servers: `http://172.17.0.2:80"` with weight `10` and `http://172.17.0.3:80` with weight `1` using default `wrr` load-balancing strategy.
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2` using `drr` load-balancing strategy.
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
# Launch
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/`
- `$HOME/.traefik/`
- `.` *the working directory*
You can override this by setting a `configFile` argument:
```bash
$ traefik --configFile=foo/bar/myconfigfile.toml
```
Træfɪk uses the following precedence order. Each item takes precedence over the item below it:
- arguments
- configuration file
- default
It means that arguments overrides configuration file.
Each argument is described in the help section:
```bash
$ traefik --help
```

212
docs/benchmarks.md Normal file
View File

@@ -0,0 +1,212 @@
# Benchmarks
## Configuration
I would like to thanks [vincentbernat](https://github.com/vincentbernat) from [exoscale.ch](https://www.exoscale.ch) who kindly provided the infrastructure needed for the benchmarks.
I used 4 VMs for the tests with the following configuration:
- 32 GB RAM
- 8 CPU Cores
- 10 GB SSD
- Ubuntu 14.04 LTS 64-bit
## Setup
1. One VM used to launch the benchmarking tool [wrk](https://github.com/wg/wrk)
2. One VM for traefik (v1.0.0-beta.416) / nginx (v1.4.6)
3. Two VMs for 2 backend servers in go [whoami](https://github.com/emilevauge/whoamI/)
Each VM has been tuned using the following limits:
```bash
sysctl -w fs.file-max="9999999"
sysctl -w fs.nr_open="9999999"
sysctl -w net.core.netdev_max_backlog="4096"
sysctl -w net.core.rmem_max="16777216"
sysctl -w net.core.somaxconn="65535"
sysctl -w net.core.wmem_max="16777216"
sysctl -w net.ipv4.ip_local_port_range="1025 65535"
sysctl -w net.ipv4.tcp_fin_timeout="30"
sysctl -w net.ipv4.tcp_keepalive_time="30"
sysctl -w net.ipv4.tcp_max_syn_backlog="20480"
sysctl -w net.ipv4.tcp_max_tw_buckets="400000"
sysctl -w net.ipv4.tcp_no_metrics_save="1"
sysctl -w net.ipv4.tcp_syn_retries="2"
sysctl -w net.ipv4.tcp_synack_retries="2"
sysctl -w net.ipv4.tcp_tw_recycle="1"
sysctl -w net.ipv4.tcp_tw_reuse="1"
sysctl -w vm.min_free_kbytes="65536"
sysctl -w vm.overcommit_memory="1"
ulimit -n 9999999
```
### Nginx
Here is the config Nginx file use `/etc/nginx/nginx.conf`:
```
user www-data;
worker_processes auto;
worker_rlimit_nofile 200000;
pid /var/run/nginx.pid;
events {
worker_connections 10000;
use epoll;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 300;
keepalive_requests 10000;
types_hash_max_size 2048;
open_file_cache max=200000 inactive=300s;
open_file_cache_valid 300s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
server_tokens off;
dav_methods off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log combined;
error_log /var/log/nginx/error.log warn;
gzip off;
gzip_vary off;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*.conf;
}
```
Here is the Nginx vhost file used:
```
upstream whoami {
server IP-whoami1:80;
server IP-whoami2:80;
keepalive 300;
}
server {
listen 8001;
server_name test.traefik;
access_log off;
error_log /dev/null crit;
if ($host != "test.traefik") {
return 404;
}
location / {
proxy_pass http://whoami;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Forwarded-Host $host;
}
}
```
### Traefik
Here is the `traefik.toml` file used:
```
MaxIdleConnsPerHost = 100000
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
[file]
[backends]
[backends.backend1]
[backends.backend1.servers.server1]
url = "http://IP-whoami1:80"
weight = 1
[backends.backend1.servers.server2]
url = "http://IP-whoami2:80"
weight = 1
[frontends]
[frontends.frontend1]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Host: test.traefik"
```
## Results
### whoami:
```
wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench
Running 1m test @ http://IP-whoami:80/bench
20 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 70.28ms 134.72ms 1.91s 89.94%
Req/Sec 2.92k 742.42 8.78k 68.80%
Latency Distribution
50% 10.63ms
75% 75.64ms
90% 205.65ms
99% 668.28ms
3476705 requests in 1.00m, 384.61MB read
Socket errors: connect 0, read 0, write 0, timeout 103
Requests/sec: 57894.35
Transfer/sec: 6.40MB
```
### nginx:
```
wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-nginx:8001/bench
Running 1m test @ http://IP-nginx:8001/bench
20 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 101.25ms 180.09ms 1.99s 89.34%
Req/Sec 1.69k 567.69 9.39k 72.62%
Latency Distribution
50% 15.46ms
75% 129.11ms
90% 302.44ms
99% 846.59ms
2018427 requests in 1.00m, 298.36MB read
Socket errors: connect 0, read 0, write 0, timeout 90
Requests/sec: 33591.67
Transfer/sec: 4.97MB
```
### traefik:
```
wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench
Running 1m test @ http://IP-traefik:8000/bench
20 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 91.72ms 150.43ms 2.00s 90.50%
Req/Sec 1.43k 266.37 2.97k 69.77%
Latency Distribution
50% 19.74ms
75% 121.98ms
90% 237.39ms
99% 687.49ms
1705073 requests in 1.00m, 188.63MB read
Socket errors: connect 0, read 0, write 0, timeout 7
Requests/sec: 28392.44
Transfer/sec: 3.14MB
```
## Conclusion
Traefik is obviously slower than Nginx, but not so much: Traefik can serve 28392 requests/sec and Nginx 33591 requests/sec which gives a ratio of 85%.
Not bad for young project :) !
Some areas of possible improvements:
- Use [GO_REUSEPORT](https://github.com/kavu/go_reuseport) listener
- Run a separate server instance per CPU core with `GOMAXPROCS=1` (it appears during benchmarks that there is a lot more context switches with traefik than with nginx)

43
docs/css/traefik.css Normal file
View File

@@ -0,0 +1,43 @@
a {
color: #37ABC8;
text-decoration: none;
}
a:hover, a:focus {
color: #25606F;
text-decoration: underline;
}
h1, h2, h3, H4 {
color: #37ABC8;
}
.navbar-default {
background-color: #37ABC8;
border-color: #25606F;
}
.navbar-default .navbar-nav>.active>a, .navbar-default .navbar-nav>.active>a:hover, .navbar-default .navbar-nav>.active>a:focus {
color: #fff;
background-color: #25606F;
}
.navbar-default .navbar-nav>li>a:hover, .navbar-default .navbar-nav>li>a:focus {
color: #fff;
background-color: #25606F;
}
.navbar-default .navbar-toggle {
border-color: #25606F;
}
.navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus .navbar-toggle {
background-color: #25606F;
}
.navbar-default .navbar-collapse, .navbar-default .navbar-form {
border-color: #25606F;
}
blockquote p {
font-size: 14px;
}

BIN
docs/img/apollo-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
docs/img/architecture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

2407
docs/img/architecture.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 100 KiB

BIN
docs/img/asteris.logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/img/internal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="337.37802"
height="107.921"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="letsencrypt-logo-horizontal.svg">
<metadata
id="metadata37">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs35" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="640"
inkscape:window-height="480"
id="namedview33"
showgrid="false"
fit-margin-bottom="30"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
inkscape:zoom="0.72861357"
inkscape:cx="168.57"
inkscape:cy="69.027001"
inkscape:window-x="0"
inkscape:window-y="30"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<g
id="g4"
transform="translate(-0.930001,-1.606)">
<title
id="title6">Layer 1</title>
<g
id="svg_1">
<g
id="svg_2">
<g
id="svg_3">
<path
id="svg_4"
d="m 76.621002,68.878998 0,-31.406998 7.629997,0 0,24.796997 12.153999,0 0,6.609001 -19.783997,0 0,9.99e-4 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_5"
d="m 121.547,58.098999 c 0,0.295998 0,0.592003 0,0.888 0,0.295997 -0.015,0.576004 -0.044,0.843002 l -16.01301,0 c 0.059,0.620995 0.244,1.182999 0.555,1.685997 0.311,0.502998 0.71,0.938004 1.197,1.308998 0.488,0.370003 1.035,0.658005 1.642,0.864006 0.605,0.208 1.234,0.310997 1.885,0.310997 1.153,0 2.13,-0.213997 2.928,-0.642998 0.799,-0.429001 1.449,-0.983002 1.952,-1.664001 l 5.05699,3.194 c -1.03498,1.507996 -2.40199,2.668999 -4.10299,3.482002 -1.701,0.811996 -3.676,1.219994 -5.922,1.219994 -1.657,0 -3.224,-0.259995 -4.702,-0.775993 -1.479,-0.518005 -2.772,-1.271004 -3.882,-2.263 -1.108,-0.990005 -1.981,-2.210007 -2.616996,-3.659004 -0.635994,-1.448997 -0.953003,-3.104996 -0.953003,-4.969002 0,-1.802994 0.309998,-3.437996 0.931,-4.900997 0.620999,-1.463001 1.463999,-2.706001 2.528999,-3.726002 1.064,-1.021 2.32,-1.811996 3.771,-2.373997 1.448,-0.561001 3.016,-0.843002 4.701,-0.843002 1.626,0 3.12,0.274002 4.48,0.820999 1.36,0.546997 2.528,1.338001 3.505,2.373001 0.976,1.035 1.73599,2.292 2.284,3.771 0.546,1.478001 0.819,3.165001 0.819,5.056 z m -6.698,-2.794998 c 0,-1.153 -0.362,-2.144001 -1.087,-2.972 -0.725,-0.827 -1.812,-1.242001 -3.26,-1.242001 -0.71,0 -1.36,0.111 -1.952,0.333 -0.59199,0.222 -1.108,0.525002 -1.553,0.909 -0.443,0.384998 -0.798,0.835999 -1.064,1.354 -0.266,0.517998 -0.414,1.057999 -0.443,1.618 l 9.359,0 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_6"
d="m 133.168,52.200001 0,8.461002 c 0,1.038994 0.2,1.816994 0.60001,2.337997 0.39799,0.519997 1.11499,0.778 2.151,0.778 0.35399,0 0.73098,-0.028 1.13099,-0.089 0.39901,-0.05901 0.73101,-0.147003 0.998,-0.266006 l 0.089,5.323006 c -0.50299,0.176994 -1.13899,0.332001 -1.90699,0.465996 -0.76999,0.133003 -1.538,0.199005 -2.307,0.199005 -1.479,0 -2.722,-0.186005 -3.727,-0.556007 C 129.19,68.484002 128.384,67.949998 127.77901,67.252 127.172,66.556001 126.73599,65.725999 126.47,64.762002 126.203,63.799005 126.071,62.724 126.071,61.538003 l 0,-9.338001 -3.549,0 0,-5.412003 3.504,0 0,-5.810997 7.142,0 0,5.810997 5.19,0 0,5.412003 -5.19,0 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_7"
d="m 161.91299,53.307999 c -0.59201,-0.560997 -1.28601,-1.034 -2.085,-1.418999 -0.79801,-0.383999 -1.64099,-0.577 -2.528,-0.577 -0.681,0 -1.30899,0.133999 -1.885,0.398998 -0.57699,0.267002 -0.865,0.726002 -0.865,1.375 0,0.621002 0.317,1.064003 0.953,1.331001 0.636,0.266998 1.664,0.562 3.08299,0.887001 0.82801,0.177998 1.664,0.43 2.50701,0.754997 0.843,0.324997 1.604,0.754005 2.28399,1.286003 0.68001,0.531998 1.22701,1.182999 1.64202,1.951996 0.41299,0.769005 0.62098,1.686005 0.62098,2.75 0,1.391006 -0.28099,2.565002 -0.84298,3.526001 -0.56201,0.960999 -1.29401,1.737 -2.19602,2.329002 -0.902,0.592002 -1.91499,1.019997 -3.03799,1.286003 -1.12399,0.266998 -2.248,0.398994 -3.371,0.398994 -1.80499,0 -3.571,-0.287994 -5.302,-0.864998 C 149.161,68.146002 147.719,67.294996 146.566,66.170995 l 4.08099,-4.303001 c 0.649,0.710007 1.448,1.302002 2.395,1.774002 0.946,0.473999 1.952,0.709999 3.017,0.709999 0.592,0 1.176,-0.140999 1.752,-0.421997 0.577,-0.279999 0.86501,-0.776001 0.86501,-1.485001 0,-0.681 -0.35401,-1.182999 -1.06401,-1.509003 -0.71,-0.324997 -1.818,-0.664993 -3.327,-1.020996 -0.769,-0.177002 -1.53799,-0.413002 -2.30699,-0.709 -0.77001,-0.295998 -1.457,-0.694 -2.06202,-1.197998 -0.60598,-0.502007 -1.10199,-1.123001 -1.48599,-1.863007 -0.384,-0.737995 -0.576,-1.625996 -0.576,-2.660995 0,-1.331001 0.28,-2.462002 0.843,-3.394001 0.562,-0.931999 1.286,-1.692001 2.174,-2.284 0.88701,-0.591999 1.87001,-1.027 2.949,-1.308998 1.079,-0.281998 2.151,-0.422001 3.217,-0.422001 1.655,0 3.274,0.259998 4.856,0.776001 1.582,0.517998 2.921,1.293999 4.015,2.328999 l -3.995,4.127998 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_8"
d="m 179.56799,68.878998 0,-31.406998 21.114,0 0,6.388 -13.795,0 0,5.944 13.041,0 0,6.077 -13.041,0 0,6.521 14.594,0 0,6.476997 -21.913,0 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_9"
d="m 220.675,68.878998 0,-12.065994 c 0,-0.621002 -0.053,-1.212002 -0.155,-1.774002 -0.104,-0.562 -0.274,-1.057003 -0.511,-1.486 -0.237,-0.428001 -0.569,-0.769001 -0.998,-1.021 -0.429,-0.25 -0.96899,-0.377003 -1.619,-0.377003 -0.65001,0 -1.22,0.127003 -1.70799,0.377003 -0.487,0.251999 -0.89501,0.599998 -1.22001,1.042999 -0.32499,0.443001 -0.569,0.953999 -0.731,1.529999 -0.16299,0.577 -0.244,1.175999 -0.244,1.797001 l 0,11.976997 -7.319,0 0,-22.091 7.05301,0 0,3.061001 0.089,0 c 0.26699,-0.473 0.613,-0.938 1.043,-1.396 0.428,-0.459 0.932,-0.850998 1.50801,-1.175999 0.57699,-0.325001 1.20498,-0.591999 1.88598,-0.799 0.68001,-0.206001 1.40401,-0.311001 2.17301,-0.311001 1.479,0 2.735,0.266998 3.77099,0.799 1.036,0.532002 1.87001,1.220001 2.50701,2.062 0.636,0.842999 1.09401,1.812 1.375,2.904999 0.28,1.095001 0.421,2.189003 0.421,3.283001 l 0,13.661999 -7.321,0 0,9.99e-4 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_10"
d="m 246.71301,53.929001 c -0.41501,-0.532001 -0.977,-0.959999 -1.686,-1.285999 -0.70999,-0.325001 -1.43601,-0.488003 -2.174,-0.488003 -0.77,0 -1.464,0.155003 -2.085,0.466 -0.62101,0.310997 -1.153,0.726002 -1.59701,1.242001 -0.44299,0.518002 -0.79199,1.117001 -1.04299,1.797001 -0.251,0.681004 -0.377,1.404003 -0.377,2.174 0,0.768997 0.11799,1.493004 0.35499,2.173004 0.23601,0.681 0.58301,1.279999 1.04201,1.796997 0.45799,0.517998 1.005,0.924995 1.642,1.220001 0.636,0.295998 1.35299,0.443001 2.151,0.443001 0.73801,0 1.47099,-0.139999 2.19501,-0.421005 0.72401,-0.281006 1.30899,-0.687996 1.75198,-1.220001 l 4.03702,4.924004 c -0.91703,0.887001 -2.10102,1.582001 -3.54901,2.084999 -1.44899,0.501999 -2.987,0.753998 -4.61299,0.753998 -1.74501,0 -3.37401,-0.266998 -4.88701,-0.798996 -1.512,-0.531998 -2.82601,-1.308998 -3.941,-2.329002 -1.11599,-1.019997 -1.99299,-2.253998 -2.63299,-3.702995 -0.64,-1.448997 -0.959,-3.090004 -0.959,-4.924004 0,-1.804001 0.31898,-3.431 0.959,-4.880001 0.64,-1.447998 1.51699,-2.683998 2.63299,-3.703999 1.11499,-1.021 2.43,-1.804001 3.941,-2.351002 1.513,-0.546997 3.127,-0.820999 4.843,-0.820999 0.798,0 1.589,0.074 2.373,0.223 0.783,0.147003 1.53699,0.348 2.26199,0.599003 0.72501,0.251003 1.39002,0.562 1.996,0.931999 0.60599,0.369999 1.13202,0.776001 1.57502,1.219997 l -4.21201,4.877003 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_11"
d="m 268.03201,52.776001 c -0.32599,-0.089 -0.64401,-0.146999 -0.95401,-0.177002 -0.30999,-0.03 -0.61398,-0.045 -0.90899,-0.045 -0.97599,0 -1.797,0.177998 -2.46201,0.530998 -0.66498,0.354 -1.19699,0.781002 -1.59698,1.283001 -0.39902,0.500999 -0.68802,1.047001 -0.86503,1.636997 -0.177,0.589996 -0.26599,1.105003 -0.26599,1.548004 l 0,11.324997 -7.27499,0 0,-22.063999 7.009,0 0,3.194 0.089,0 c 0.56201,-1.132 1.35901,-2.055 2.396,-2.77 1.03402,-0.715 2.23202,-1.071999 3.59302,-1.071999 0.29498,0 0.58398,0.016 0.86499,0.045 0.27999,0.029 0.51001,0.074 0.68801,0.133003 L 268.03201,52.776 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_12"
d="m 285.12201,72.206001 c -0.44299,1.153 -0.939,2.181 -1.48599,3.083 -0.547,0.901001 -1.19702,1.669998 -1.95102,2.306999 -0.754,0.636002 -1.642,1.114998 -2.66199,1.441002 -1.01999,0.324997 -2.22601,0.487999 -3.61499,0.487999 -0.681,0 -1.38299,-0.045 -2.10602,-0.134003 -0.72598,-0.089 -1.354,-0.207001 -1.88598,-0.353996 L 272.215,72.916 c 0.354,0.116997 0.746,0.213997 1.17602,0.288002 0.42798,0.073 0.81998,0.110001 1.17499,0.110001 1.12399,0 1.93701,-0.259003 2.44,-0.776001 0.50199,-0.518005 0.931,-1.249001 1.28601,-2.195 l 0.70999,-1.818001 -9.22699,-21.736 8.073,0 4.92398,14.195 0.133,0 4.392,-14.195 7.71802,0 -9.89301,25.417 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_13"
d="m 321.496,57.745003 c 0,1.537994 -0.237,3.016998 -0.70999,4.435997 -0.474,1.419998 -1.16101,2.668999 -2.06201,3.748001 -0.90201,1.080002 -2.004,1.945 -3.30499,2.596001 -1.30201,0.649002 -2.78,0.975998 -4.43702,0.975998 -1.35998,0 -2.64599,-0.273003 -3.85901,-0.82 -1.21301,-0.546997 -2.15799,-1.293999 -2.83898,-2.239998 l -0.088,0 0,13.085999 -7.27502,0 0,-32.739002 6.92001,0 0,2.706001 0.133,0 c 0.681,-0.887001 1.61899,-1.662998 2.81698,-2.328999 C 307.98801,46.5 309.39999,46.167 311.02701,46.167 c 1.59698,0 3.04498,0.311001 4.34698,0.931999 1.301,0.621002 2.40201,1.464001 3.305,2.528 0.90298,1.063999 1.59701,2.299999 2.08502,3.704002 0.488,1.404999 0.73199,2.876999 0.73199,4.414001 z m -7.05301,0 c 0,-0.709999 -0.11001,-1.403999 -0.332,-2.085003 -0.22201,-0.68 -0.548,-1.278999 -0.97699,-1.797001 -0.42901,-0.516998 -0.96902,-0.938 -1.61902,-1.264 -0.64999,-0.326 -1.40399,-0.487999 -2.26199,-0.487999 -0.828,0 -1.56799,0.162998 -2.21799,0.487999 -0.651,0.325001 -1.20602,0.754002 -1.664,1.285999 -0.45901,0.532001 -0.81302,1.139 -1.06402,1.818001 -0.25199,0.681004 -0.37699,1.375004 -0.37699,2.085003 0,0.709999 0.125,1.404999 0.37699,2.084999 0.251,0.681 0.60501,1.285995 1.06402,1.818001 0.45798,0.531998 1.013,0.961998 1.664,1.286995 0.64899,0.325005 1.38999,0.487 2.21799,0.487 0.85699,0 1.61099,-0.161995 2.26199,-0.487 0.651,-0.325005 1.19001,-0.754997 1.61902,-1.286995 0.42902,-0.531998 0.75498,-1.146004 0.97699,-1.841003 0.22101,-0.693001 0.332,-1.394997 0.332,-2.104996 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_14"
d="m 333.11801,52.200001 0,8.461002 c 0,1.038994 0.20001,1.816994 0.60001,2.337997 0.39798,0.519997 1.11499,0.778 2.151,0.778 0.354,0 0.73099,-0.028 1.13098,-0.089 0.39902,-0.05901 0.73102,-0.147003 0.99802,-0.266006 l 0.089,5.323006 c -0.50299,0.176994 -1.139,0.332001 -1.90698,0.465996 -0.77002,0.133003 -1.53802,0.199005 -2.307,0.199005 -1.47901,0 -2.72202,-0.186005 -3.72702,-0.556007 -1.00599,-0.369995 -1.81199,-0.903999 -2.417,-1.601997 -0.60699,-0.695999 -1.043,-1.526001 -1.30899,-2.489998 C 326.15302,63.799005 326.021,62.724 326.021,61.538003 l 0,-9.338001 -3.54898,0 0,-5.412003 3.50399,0 0,-5.810997 7.142,0 0,5.810997 5.19,0 0,5.412003 -5.19,0 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
</g>
</g>
<path
id="svg_15"
d="m 145.00999,36.869999 c -2.18299,0 -3.89199,1.573002 -3.89199,3.582001 0,2.116001 1.43899,3.536999 3.582,3.536999 0.183,0 0.35599,-0.017 0.51899,-0.05 -0.343,1.566002 -1.852,2.690002 -3.27799,2.915001 l -0.29001,0.046 0,3.376999 0.376,-0.036 c 1.73,-0.165001 3.439,-0.951 4.691,-2.157001 1.632,-1.572998 2.49501,-3.843998 2.49501,-6.568001 0,-2.691998 -1.76799,-4.646 -4.20301,-4.646 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
</g>
<g
id="svg_16">
<path
id="svg_17"
d="m 46.488998,37.568001 -8.039997,0 0,-4.128002 c 0,-3.296997 -2.683002,-5.979 -5.98,-5.979 -3.297001,0 -5.979,2.683002 -5.979,5.979 l 0,4.128002 -8.040001,0 0,-4.128002 c 0,-7.73 6.288998,-14.019999 14.02,-14.019999 7.731002,0 14.02,6.289 14.02,14.019999 l 0,4.128002 -0.001,0 z"
inkscape:connector-curvature="0"
style="fill:#f9a11d" />
</g>
<path
id="svg_18"
d="m 49.731998,37.568001 -34.524998,0 c -1.474001,0 -2.68,1.205997 -2.68,2.68 l 0,25.540001 c 0,1.473999 1.205999,2.68 2.68,2.68 l 34.524998,0 c 1.474003,0 2.68,-1.206001 2.68,-2.68 l 0,-25.540001 c 0,-1.474003 -1.205997,-2.68 -2.68,-2.68 z m -15.512997,16.769001 0,3.460995 c 0,0.966003 -0.784,1.749001 -1.749001,1.749001 -0.965001,0 -1.749001,-0.783997 -1.749001,-1.749001 l 0,-3.459995 c -1.076,-0.611 -1.803001,-1.764 -1.803001,-3.09 0,-1.962002 1.591,-3.552002 3.552002,-3.552002 1.961998,0 3.551998,1.591 3.551998,3.552002 0,1.325001 -0.727001,2.478001 -1.802998,3.089001 z"
inkscape:connector-curvature="0"
style="fill:#2c3c69" />
<path
id="svg_19"
d="m 11.707001,33.759998 -8.331,0 c -1.351001,0 -2.446,-1.094997 -2.446,-2.445999 0,-1.351002 1.094999,-2.445999 2.446,-2.445999 l 8.331,0 c 1.351,0 2.445999,1.095001 2.445999,2.445999 0,1.350998 -1.096001,2.445999 -2.445999,2.445999 z"
inkscape:connector-curvature="0"
style="fill:#f9a11d" />
<path
id="svg_20"
d="m 17.575001,20.655001 c -0.546001,0 -1.097,-0.182001 -1.552,-0.557001 l -6.59,-5.418999 C 8.39,13.820999 8.239001,12.280001 9.098,11.236 9.956,10.193001 11.497,10.042 12.541001,10.9 l 6.59,5.419001 c 1.042999,0.858 1.194,2.399 0.334999,3.442999 -0.483,0.589001 -1.184,0.893002 -1.890999,0.893002 z"
inkscape:connector-curvature="0"
style="fill:#f9a11d" />
<path
id="svg_21"
d="m 32.469002,14.895 c -1.351002,0 -2.446003,-1.095001 -2.446003,-2.446001 l 0,-8.396999 c 0,-1.351 1.095001,-2.446 2.446003,-2.446 1.351002,0 2.445999,1.095 2.445999,2.446 l 0,8.396999 c 0,1.351 -1.095001,2.446001 -2.445999,2.446001 z"
inkscape:connector-curvature="0"
style="fill:#f9a11d" />
<g
id="svg_22">
<g
id="svg_23">
<path
id="svg_24"
d="M 47.362999,20.655001 C 46.655998,20.655001 45.956001,20.351 45.472,19.761999 44.613998,18.719 44.764,17.177 45.806999,16.319 l 6.59,-5.419001 c 1.044003,-0.858 2.585003,-0.706999 3.442997,0.336 0.858002,1.042999 0.708,2.584999 -0.334999,3.443001 l -6.589996,5.418999 C 48.459999,20.472999 47.91,20.655 47.362999,20.655 z"
inkscape:connector-curvature="0"
style="fill:#f9a11d" />
</g>
</g>
<path
id="svg_25"
d="m 61.563004,33.759998 -8.410004,0 c -1.351002,0 -2.445999,-1.094997 -2.445999,-2.445999 0,-1.351002 1.094997,-2.445999 2.445999,-2.445999 l 8.410004,0 c 1.350998,0 2.445999,1.095001 2.445999,2.445999 0,1.350998 -1.095001,2.445999 -2.445999,2.445999 z"
inkscape:connector-curvature="0"
style="fill:#f9a11d" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/img/mantl-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

5394
docs/img/overview.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 218 KiB

BIN
docs/img/traefik-health.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
docs/img/traefik.icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
docs/img/traefik.logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 53 KiB

BIN
docs/img/zenika.logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -1,732 +1,131 @@
![Træfɪk](http://traefik.github.io/traefik.logo.svg "Træfɪk")
___
<p align="center">
<img src="img/traefik.logo.png" alt="Træfɪk" title="Træfɪk" />
</p>
# <a id="top"></a> Documentation
* [Basics](#basics)
* [Global configuration](#global)
* [File backend](#file)
* [API backend](#api)
* [Docker backend](#docker)
* [Mesos/Marathon backend](#marathon)
* [Consul backend](#consul)
* [Etcd backend](#etcd)
* [Zookeeper backend](#zk)
* [Boltdb backend](#boltdb)
* [Benchmarks](#benchmarks)
## <a id="basics"></a> Basics
[![Build Status](https://travis-ci.org/containous/traefik.svg?branch=master)](https://travis-ci.org/containous/traefik)
[![Docs](https://img.shields.io/badge/docs-current-brightgreen.svg)](https://docs.traefik.io)
[![Go Report Card](https://goreportcard.com/badge/kubernetes/helm)](http://goreportcard.com/report/containous/traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/containous/traefik/blob/master/LICENSE.md)
[![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com)
[![Twitter](https://img.shields.io/twitter/follow/traefikproxy.svg?style=social)](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 :whale:](https://www.docker.com/), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Consul](https://consul.io/), [Etcd](https://coreos.com/etcd/), 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), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [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.
Basically, Træfɪk is a http router, which sends traffic from frontends to http backends, following rules you have configured.
## Overview
### Frontends
Imagine that you have deployed a bunch of microservices on your infrastructure. You probably used a service registry (like etcd or consul) and/or an orchestrator (swarm, Mesos/Marathon) to manage all these services.
If you want your users to access some of your microservices from the Internet, you will have to use a reverse proxy and configure it using virtual hosts or prefix paths:
Frontends can be defined using the following rules:
- domain `api.domain.com` will point the microservice `api` in your private network
- path `domain.com/web` will point the microservice `web` in your private network
- domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances
* `Headers`: Headers adds a matcher for request header values. It accepts a sequence of key/value pairs to be matched. For example: `application/json`
* `HeadersRegexp`: Regular expressions can be used with headers as well. It accepts a sequence of key/value pairs, where the value has regex support. For example: `application/(text|json)`
* `Host`: Host adds a matcher for the URL host. It accepts a template with zero or more URL variables enclosed by `{}`. Variables can define an optional regexp pattern to be matched: `www.traefik.io`, `{subdomain:[a-z]+}.traefik.io`
* `Methods`: Methods adds a matcher for HTTP methods. It accepts a sequence of one or more methods to be matched, e.g.: `GET`, `POST`, `PUT`
* `Path`: Path adds a matcher for the URL path. It accepts a template with zero or more URL variables enclosed by `{}`. The template must start with a `/`. For exemple `/products/` `/articles/{category}/{id:[0-9]+}`
* `PathPrefix`: PathPrefix adds a matcher for the URL path prefix. This matches if the given template is a prefix of the full URL path.
But a microservices architecture is dynamic... Services are added, removed, killed or upgraded often, eventually several times a day.
Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily.
A frontend is a set of rules that forwards the incoming http traffic to a backend.
Here enters Træfɪk.
### HTTP Backends
![Architecture](img/architecture.png)
A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers.
Various methods of load-balancing is supported:
Træfɪk can listen to your service registry/orchestrator API, and knows each time a microservice is added, removed, killed or upgraded, and can generate its configuration automatically.
Routes to your services will be created instantly.
* `wrr`: Weighted Round Robin
* `drr`: Dynamic Round Robin: increases weights on servers that perform better than others. It also rolls back to original weights if the servers have changed.
Run it and forget it!
A circuit breaker can also be applied to a backend, preventing high loads on failing servers.
It can be configured using:
## Demo
* Methods: `LatencyAtQuantileMS`, `NetworkErrorRatio`, `ResponseCodeRatio`
* Operators: `AND`, `OR`, `EQ`, `NEQ`, `LT`, `LE`, `GT`, `GE`
Here is a demo of Træfɪk using Docker backend, showing a load-balancing between two servers, hot reloading of configuration, and graceful shutdown.
For example:
* `NetworkErrorRatio() > 0.5`
* `LatencyAtQuantileMS(50.0) > 50`
* `ResponseCodeRatio(500, 600, 0, 600) > 0.5`
[![asciicast](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko.png)](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko)
## <a id="global"></a> Global configuration
## Get it
```toml
# traefik.toml
################################################################
# Global configuration
################################################################
### Binary
# Reverse proxy port
#
# Optional
# Default: ":80"
#
# port = ":80"
You can 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):
# Timeout in seconds.
# Duration to give active requests a chance to finish during hot-reloads
#
# Optional
# Default: 10
#
# graceTimeOut = 10
# Traefik logs file
# If not defined, logs to stdout
#
# Optional
#
# traefikLogsFile = "log/traefik.log"
# Access logs file
#
# Optional
#
# accessLogsFile = "log/access.log"
# Log level
#
# Optional
# Default: "ERROR"
#
# logLevel = "ERROR"
# SSL certificate and key used
#
# Optional
#
# CertFile = "traefik.crt"
# KeyFile = "traefik.key"
```shell
./traefik -c traefik.toml
```
### Docker
## <a id="file"></a> File backend
Using the tiny Docker image:
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` :
```toml
# traefik.toml
port = ":80"
graceTimeOut = 10
logLevel = "DEBUG"
[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.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"
value = "test.localhost"
[frontends.frontend2]
backend = "backend1"
[frontends.frontend2.routes.test_2]
rule = "Path"
value = "/test"
```shell
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
```
* or put your rules in a separate file, for example `rules.tml`:
## Test it
```toml
# traefik.toml
port = ":80"
graceTimeOut = 10
logLevel = "DEBUG"
You can test Træfɪk easily using [Docker compose](https://docs.docker.com/compose), with this `docker-compose.yml` file:
[file]
filename = "rules.toml"
```yaml
traefik:
image: traefik
command: --web --docker --docker.domain=docker.localhost --logLevel=DEBUG
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /dev/null:/traefik.toml
whoami1:
image: emilevauge/whoami
labels:
- "traefik.backend=whoami"
- "traefik.frontend.rule=Host:whoami.docker.localhost"
whoami2:
image: emilevauge/whoami
labels:
- "traefik.backend=whoami"
- "traefik.frontend.rule=Host:whoami.docker.localhost"
```
```toml
# rules.toml
[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.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"
value = "test.localhost"
[frontends.frontend2]
backend = "backend1"
[frontends.frontend2.routes.test_2]
rule = "Path"
value = "/test"
Then, start it:
```
docker-compose up -d
```
If you want Træfɪk to watch file changes automatically, just add:
```toml
[file]
watch = true
```
## <a id="api"></a> API backend
Træfik can be configured using a restful api.
To enable it:
```toml
[web]
address = ":8080"
# SSL certificate and key used
#
# Optional
#
# CertFile = "traefik.crt"
# KeyFile = "traefik.key"
```
* `/`: provides a simple HTML frontend of Træfik
![HTML frontend](img/web.frontend.png)
* `/health`: `GET` json metrics
```sh
$ curl -s "http://localhost:8080/health" | jq .
{
"average_response_time_sec": 0,
"average_response_time": "0",
"total_response_time_sec": 0,
"total_response_time": "0",
"total_count": 0,
"pid": 12861,
"uptime": "7m12.80607635s",
"uptime_sec": 432.80607635,
"time": "2015-09-22 10:25:16.448023473 +0200 CEST",
"unixtime": 1442910316,
"status_code_count": {},
"total_status_code_count": {},
"count": 0
}
```
* `/api`: `GET` configuration for all providers
```sh
$ curl -s "http://localhost:8080/api" | jq .
{
"file": {
"Frontends": {
"frontend2": {
"Routes": {
"test_2": {
"Value": "/test",
"Rule": "Path"
}
},
"Backend": "backend1"
},
"frontend1": {
"Routes": {
"test_1": {
"Value": "test.localhost",
"Rule": "Host"
}
},
"Backend": "backend2"
}
},
"Backends": {
"backend2": {
"LoadBalancer": {
"Method": "drr"
},
"CircuitBreaker": null,
"Servers": {
"server2": {
"Weight": 2,
"URL": "http://172.17.0.5:80"
},
"server1": {
"Weight": 1,
"URL": "http://172.17.0.4:80"
}
}
},
"backend1": {
"LoadBalancer": {
"Method": "wrr"
},
"CircuitBreaker": {
"Expression": "NetworkErrorRatio() > 0.5"
},
"Servers": {
"server2": {
"Weight": 1,
"URL": "http://172.17.0.3:80"
},
"server1": {
"Weight": 10,
"URL": "http://172.17.0.2:80"
}
}
}
}
}
}
```
* `/api/providers`: `GET` providers
* `/api/providers/{provider}`: `GET` or `PUT` provider
* `/api/providers/{provider}/backends`: `GET` backends
* `/api/providers/{provider}/backends/{backend}`: `GET` a backend
* `/api/providers/{provider}/backends/{backend}/servers`: `GET` servers in a backend
* `/api/providers/{provider}/backends/{backend}/servers/{server}`: `GET` a server in a backend
* `/api/providers/{provider}/frontends`: `GET` frontends
* `/api/providers/{provider}/frontends/{frontend}`: `GET` a frontend
## <a id="docker"></a> Docker backend
Træfɪk can be configured to use Docker as a backend configuration:
```toml
################################################################
# Docker configuration backend
################################################################
# Enable Docker configuration backend
#
# Optional
#
[docker]
# Docker server endpoint. Can be a tcp or a unix socket endpoint.
#
# Required
#
endpoint = "unix:///var/run/docker.sock"
# Default domain used.
# Can be overridden by setting the "traefik.domain" label on a container.
#
# Required
#
domain = "docker.localhost"
# Enable watch docker changes
#
# Optional
#
watch = true
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "docker.tmpl"
```
Labels can be used on containers to override default behaviour:
* `traefik.backend=foo`: assign the container to `foo` backend
* `traefik.port=80`: register this port. Useful when the container exposes multiples ports.
* `traefik.weight=10`: assign this weight to the container
* `traefik.enable=false`: disable this container in Træfɪk
* `traefik.host=bar`: override the default routing from {containerName}.{domain} to bar.{domain}
## <a id="marathon"></a> Marathon backend
Træfɪk can be configured to use Marathon as a backend configuration:
```toml
################################################################
# Mesos/Marathon configuration backend
################################################################
# Enable Marathon configuration backend
#
# Optional
#
[marathon]
# Marathon server endpoint.
# You can also specify multiple endpoint for Marathon:
# endpoint := "http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080"
#
# Required
#
endpoint = "http://127.0.0.1:8080"
# Network interface used to call Marathon web services
# Optional
# Default: "eth0"
#
# networkInterface = "eth0"
# Enable watch Marathon changes
#
# Optional
#
watch = true
# Default domain used.
# Can be overridden by setting the "traefik.domain" label on an application.
#
# Required
#
domain = "marathon.localhost"
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "marathon.tmpl"
```
Labels can be used on containers to override default behaviour:
* `traefik.backend=foo`: assign the application to `foo` backend
* `traefik.port=80`: register this port. Useful when the application exposes multiples ports.
* `traefik.weight=10`: assign this weight to the application
* `traefik.enable=false`: disable this application in Træfɪk
* `traefik.host=bar`: override the default routing from {appName}.{domain} to bar.{domain}
* `traefik.prefixes=pf1,pf2`: use PathPrefix(es) instead of hostname for routing, use filename="providerTemplates/marathon-prefix.tmpl" with this option
## <a id="consul"></a> Consul backend
Træfɪk can be configured to use Consul as a backend configuration:
```toml
################################################################
# Consul KV configuration backend
################################################################
# Enable Consul KV configuration backend
#
# Optional
#
[consul]
# Consul server endpoint
#
# Required
#
endpoint = "http://127.0.0.1:8500"
# Enable watch Consul changes
#
# Optional
#
watch = true
# Prefix used for KV store.
#
# Optional
#
prefix = "traefik"
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "consul.tmpl"
```
## <a id="etcd"></a> Etcd backend
Træfɪk can be configured to use Etcd as a backend configuration:
```toml
################################################################
# Etcd configuration backend
################################################################
# Enable Etcd configuration backend
#
# Optional
#
# [etcd]
# Etcd server endpoint
#
# Required
#
# endpoint = "127.0.0.1:4001"
# Enable watch Etcd changes
#
# Optional
#
# watch = true
# Prefix used for KV store.
#
# Optional
#
# prefix = "/traefik"
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "etcd.tmpl"
```
## <a id="zk"></a> Zookeeper backend
Træfɪk can be configured to use Zookeeper as a backend configuration:
```toml
################################################################
# Zookeeper configuration backend
################################################################
# Enable Zookeeperconfiguration backend
#
# Optional
#
# [zookeeper]
# Zookeeper server endpoint
#
# Required
#
# endpoint = "127.0.0.1:2181"
# Enable watch Zookeeper changes
#
# Optional
#
# watch = true
# Prefix used for KV store.
#
# Optional
#
# prefix = "/traefik"
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "zookeeper.tmpl"
```
## <a id="boltdb"></a> BoltDB backend
Træfɪk can be configured to use BoltDB as a backend configuration:
```toml
################################################################
# BoltDB configuration backend
################################################################
# Enable BoltDB configuration backend
#
# Optional
#
# [boltdb]
# BoltDB file
#
# Required
#
# endpoint = "/my.db"
# Enable watch BoltDB changes
#
# Optional
#
# watch = true
# Prefix used for KV store.
#
# Optional
#
# prefix = "/traefik"
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "boltdb.tmpl"
```
## <a id="benchmarks"></a> Benchmarks
Here are some early Benchmarks between Nginx and Træfɪk acting as simple load balancers between two servers.
* Nginx:
```sh
$ docker run -d -e VIRTUAL_HOST=test1.localhost emilevauge/whoami
$ docker run -d -e VIRTUAL_HOST=test1.localhost emilevauge/whoami
$ docker run --log-driver=none -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock:ro jwilder/nginx-proxy
$ ab -n 20000 -c 20 -r http://test1.localhost/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking test1.localhost (be patient)
Completed 2000 requests
Completed 4000 requests
Completed 6000 requests
Completed 8000 requests
Completed 10000 requests
Completed 12000 requests
Completed 14000 requests
Completed 16000 requests
Completed 18000 requests
Completed 20000 requests
Finished 20000 requests
Server Software: nginx/1.9.2
Server Hostname: test1.localhost
Server Port: 80
Document Path: /
Document Length: 287 bytes
Concurrency Level: 20
Time taken for tests: 5.874 seconds
Complete requests: 20000
Failed requests: 0
Total transferred: 8900000 bytes
HTML transferred: 5740000 bytes
Requests per second: 3404.97 [#/sec] (mean)
Time per request: 5.874 [ms] (mean)
Time per request: 0.294 [ms] (mean, across all concurrent requests)
Transfer rate: 1479.70 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 2
Processing: 0 6 2.4 6 35
Waiting: 0 5 2.3 5 33
Total: 0 6 2.4 6 36
Percentage of the requests served within a certain time (ms)
50% 6
66% 6
75% 7
80% 7
90% 9
95% 10
98% 12
99% 13
100% 36 (longest request)
```
* Træfɪk:
```sh
$ docker run -d -l traefik.backend=test1 -l traefik.host=test1 emilevauge/whoami
$ docker run -d -l traefik.backend=test1 -l traefik.host=test1 emilevauge/whoami
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/traefik.toml -v /var/run/docker.sock:/var/run/docker.sock emilevauge/traefik
$ ab -n 20000 -c 20 -r http://test1.docker.localhost/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking test1.docker.localhost (be patient)
Completed 2000 requests
Completed 4000 requests
Completed 6000 requests
Completed 8000 requests
Completed 10000 requests
Completed 12000 requests
Completed 14000 requests
Completed 16000 requests
Completed 18000 requests
Completed 20000 requests
Finished 20000 requests
Server Software: .
Server Hostname: test1.docker.localhost
Server Port: 80
Document Path: /
Document Length: 312 bytes
Concurrency Level: 20
Time taken for tests: 6.545 seconds
Complete requests: 20000
Failed requests: 0
Total transferred: 8600000 bytes
HTML transferred: 6240000 bytes
Requests per second: 3055.60 [#/sec] (mean)
Time per request: 6.545 [ms] (mean)
Time per request: 0.327 [ms] (mean, across all concurrent requests)
Transfer rate: 1283.11 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.2 0 7
Processing: 1 6 2.2 6 22
Waiting: 1 6 2.1 6 21
Total: 1 7 2.2 6 22
Percentage of the requests served within a certain time (ms)
50% 6
66% 7
75% 8
80% 8
90% 9
95% 10
98% 11
99% 13
100% 22 (longest request)
Finally, test load-balancing between the two servers `whoami1` and `whoami2`:
```bash
$ curl -H Host:whoami.docker.localhost http://127.0.0.1
Hostname: ef194d07634a
IP: 127.0.0.1
IP: ::1
IP: 172.17.0.4
IP: fe80::42:acff:fe11:4
GET / HTTP/1.1
Host: 172.17.0.4:80
User-Agent: curl/7.35.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 172.17.0.1
X-Forwarded-Host: 172.17.0.4:80
X-Forwarded-Proto: http
X-Forwarded-Server: dbb60406010d
$ curl -H Host:whoami.docker.localhost http://127.0.0.1
Hostname: 6c3c5df0c79a
IP: 127.0.0.1
IP: ::1
IP: 172.17.0.3
IP: fe80::42:acff:fe11:3
GET / HTTP/1.1
Host: 172.17.0.3:80
User-Agent: curl/7.35.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 172.17.0.1
X-Forwarded-Host: 172.17.0.3:80
X-Forwarded-Proto: http
X-Forwarded-Server: dbb60406010d
```

994
docs/toml.md Normal file
View File

@@ -0,0 +1,994 @@
# Global configuration
## Main section
```toml
# traefik.toml
################################################################
# Global configuration
################################################################
# Traefik logs file
# If not defined, logs to stdout
#
# Optional
#
# traefikLogsFile = "log/traefik.log"
# Access logs file
#
# Optional
#
# accessLogsFile = "log/access.log"
# Log level
#
# Optional
# Default: "ERROR"
#
# logLevel = "ERROR"
# 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.
#
# Optional
# Default: "2s"
#
# ProvidersThrottleDuration = "5s"
# 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.
#
# Optional
# Default: http.DefaultMaxIdleConnsPerHost
#
# MaxIdleConnsPerHost = 200
# Entrypoints to be used by frontends that do not specify any entrypoint.
# Each frontend can specify its own entrypoints.
#
# Optional
# Default: ["http"]
#
# defaultEntryPoints = ["http", "https"]
```
## Entrypoints definition
```toml
# Entrypoints definition
#
# Optional
# Default:
# [entryPoints]
# [entryPoints.http]
# address = ":80"
#
# To redirect an http entrypoint to an https entrypoint (with SNI support):
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# [entryPoints.http.redirect]
# entryPoint = "https"
# [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 = "integration/fixtures/https/snitest.org.cert"
# KeyFile = "integration/fixtures/https/snitest.org.key"
#
# To redirect an entrypoint rewriting the URL:
# [entryPoints]
# [entryPoints.http]
# address = ":80"
# [entryPoints.http.redirect]
# regex = "^http://localhost/(.*)"
# replacement = "http://mydomain/$1"
```
## Retry configuration
```toml
# Enable retry sending request if network error
#
# Optional
#
# [retry]
# Number of attempts
#
# Optional
# Default: (number servers in backend) -1
#
# attempts = 3
# Sets the maximum request body to be stored in memory in Mo
#
# Optional
# Default: 2
#
# maxMem = 3
```
## ACME (Let's Encrypt) configuration
```toml
# Enable ACME (Let's Encrypt): automatic SSL
#
# Optional
#
# [acme]
# Email address used for registration
#
# Required
#
# email = "test@traefik.io"
# File used for certificates storage.
# WARNING, if you use Traefik in Docker, don't forget to mount this file as a volume.
#
# Required
#
# storageFile = "acme.json"
# Entrypoint to proxy acme challenge to.
# WARNING, must point to an entrypoint on port 443
#
# Required
#
# 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
#
# Optional
#
# onDemand = true
# CA server to use
# Uncomment the line to run on the staging let's encrypt server
# Leave comment to go to prod
#
# Optional
#
# caServer = "https://acme-staging.api.letsencrypt.org/directory"
# Domains list
# You can provide SANs (alternative domains) to each main domain
# WARNING, Take note that Let's Encrypt have rate limiting: https://community.letsencrypt.org/t/quick-start-guide/1631
# Each domain & SANs will lead to a certificate request.
#
# [[acme.domains]]
# main = "local1.com"
# sans = ["test1.local1.com", "test2.local1.com"]
# [[acme.domains]]
# main = "local2.com"
# sans = ["test1.local2.com", "test2x.local2.com"]
# [[acme.domains]]
# main = "local3.com"
# [[acme.domains]]
# main = "local4.com"
```
# Configuration backends
## File backend
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` :
```toml
# traefik.toml
logLevel = "DEBUG"
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[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 = "integration/fixtures/https/snitest.org.cert"
KeyFile = "integration/fixtures/https/snitest.org.key"
[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.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
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"
```
- or put your rules in a separate file, for example `rules.tml`:
```toml
# traefik.toml
logLevel = "DEBUG"
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[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 = "integration/fixtures/https/snitest.org.cert"
KeyFile = "integration/fixtures/https/snitest.org.key"
[file]
filename = "rules.toml"
```
```toml
# rules.toml
[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.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
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"
```
If you want Træfɪk to watch file changes automatically, just add:
```toml
[file]
watch = true
```
## API backend
Træfik can be configured using a restful api.
To enable it:
```toml
[web]
address = ":8080"
# SSL certificate and key used
#
# Optional
#
# CertFile = "traefik.crt"
# KeyFile = "traefik.key"
#
# Set REST API to read-only mode
#
# Optional
# ReadOnly = false
```
- `/`: provides a simple HTML frontend of Træfik
![Web UI Providers](img/web.frontend.png)
![Web UI Health](img/traefik-health.png)
- `/health`: `GET` json metrics
```sh
$ curl -s "http://localhost:8080/health" | jq .
{
// Træfɪk PID
"pid": 2458,
// Træfɪk server uptime (formated time)
"uptime": "39m6.885931127s",
// Træfɪk server uptime in seconds
"uptime_sec": 2346.885931127,
// current server date
"time": "2015-10-07 18:32:24.362238909 +0200 CEST",
// current server date in seconds
"unixtime": 1444235544,
// count HTTP response status code in realtime
"status_code_count": {
"502": 1
},
// count HTTP response status code since Træfɪk started
"total_status_code_count": {
"200": 7,
"404": 21,
"502": 13
},
// count HTTP response
"count": 1,
// count HTTP response
"total_count": 41,
// sum of all response time (formated time)
"total_response_time": "35.456865605s",
// sum of all response time in seconds
"total_response_time_sec": 35.456865605,
// average response time (formated time)
"average_response_time": "864.8016ms",
// average response time in seconds
"average_response_time_sec": 0.8648016000000001
}
```
- `/api`: `GET` configuration for all providers
```sh
$ curl -s "http://localhost:8080/api" | jq .
{
"file": {
"frontends": {
"frontend2": {
"routes": {
"test_2": {
"rule": "Path:/test"
}
},
"backend": "backend1"
},
"frontend1": {
"routes": {
"test_1": {
"rule": "Host:test.localhost"
}
},
"backend": "backend2"
}
},
"backends": {
"backend2": {
"loadBalancer": {
"method": "drr"
},
"servers": {
"server2": {
"weight": 2,
"URL": "http://172.17.0.5:80"
},
"server1": {
"weight": 1,
"url": "http://172.17.0.4:80"
}
}
},
"backend1": {
"loadBalancer": {
"method": "wrr"
},
"circuitBreaker": {
"expression": "NetworkErrorRatio() > 0.5"
},
"servers": {
"server2": {
"weight": 1,
"url": "http://172.17.0.3:80"
},
"server1": {
"weight": 10,
"url": "http://172.17.0.2:80"
}
}
}
}
}
}
```
- `/api/providers`: `GET` providers
- `/api/providers/{provider}`: `GET` or `PUT` provider
- `/api/providers/{provider}/backends`: `GET` backends
- `/api/providers/{provider}/backends/{backend}`: `GET` a backend
- `/api/providers/{provider}/backends/{backend}/servers`: `GET` servers in a backend
- `/api/providers/{provider}/backends/{backend}/servers/{server}`: `GET` a server in a backend
- `/api/providers/{provider}/frontends`: `GET` frontends
- `/api/providers/{provider}/frontends/{frontend}`: `GET` a frontend
- `/api/providers/{provider}/frontends/{frontend}/routes`: `GET` routes in a frontend
- `/api/providers/{provider}/frontends/{frontend}/routes/{route}`: `GET` a route in a frontend
## Docker backend
Træfɪk can be configured to use Docker as a backend configuration:
```toml
################################################################
# Docker configuration backend
################################################################
# Enable Docker configuration backend
#
# Optional
#
[docker]
# Docker server endpoint. Can be a tcp or a unix socket endpoint.
#
# Required
#
endpoint = "unix:///var/run/docker.sock"
# Default domain used.
# Can be overridden by setting the "traefik.domain" label on a container.
#
# Required
#
domain = "docker.localhost"
# Enable watch docker changes
#
# Optional
#
watch = true
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "docker.tmpl"
# Enable docker TLS connection
#
# [docker.tls]
# ca = "/etc/ssl/ca.crt"
# cert = "/etc/ssl/docker.crt"
# key = "/etc/ssl/docker.key"
# insecureskipverify = true
```
Labels can be used on containers to override default behaviour:
- `traefik.backend=foo`: assign the container to `foo` 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
- `traefik.enable=false`: disable this container in Træfɪk
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`). See [frontends](#frontends).
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
* `traefik.domain=traefik.localhost`: override the default domain
## Marathon backend
Træfɪk can be configured to use Marathon as a backend configuration:
```toml
################################################################
# Mesos/Marathon configuration backend
################################################################
# Enable Marathon configuration backend
#
# Optional
#
[marathon]
# Marathon server endpoint.
# You can also specify multiple endpoint for Marathon:
# endpoint := "http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080"
#
# Required
#
endpoint = "http://127.0.0.1:8080"
# Enable watch Marathon changes
#
# Optional
#
watch = true
# Default domain used.
# Can be overridden by setting the "traefik.domain" label on an application.
#
# Required
#
domain = "marathon.localhost"
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "marathon.tmpl"
# Expose Marathon apps by default in traefik
#
# Optional
# Default: false
#
# ExposedByDefault = true
# Enable Marathon basic authentication
#
# Optional
#
# [marathon.basic]
# httpBasicAuthUser = "foo"
# httpBasicPassword = "bar"
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
#
# Optional
#
# [marathon.TLS]
# InsecureSkipVerify = true
```
Labels can be used on containers to override default behaviour:
- `traefik.backend=foo`: assign the application to `foo` 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
- `traefik.weight=10`: assign this weight to the application
- `traefik.enable=false`: disable this application in Træfɪk
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`). See [frontends](#frontends).
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
* `traefik.domain=traefik.localhost`: override the default domain
## Consul backend
Træfɪk can be configured to use Consul as a backend configuration:
```toml
################################################################
# Consul KV configuration backend
################################################################
# Enable Consul KV configuration backend
#
# Optional
#
[consul]
# Consul server endpoint
#
# Required
#
endpoint = "127.0.0.1:8500"
# Enable watch Consul changes
#
# Optional
#
watch = true
# Prefix used for KV store.
#
# Optional
#
prefix = "traefik"
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "consul.tmpl"
# Enable consul TLS connection
#
# Optional
#
# [consul.tls]
# ca = "/etc/ssl/ca.crt"
# cert = "/etc/ssl/consul.crt"
# key = "/etc/ssl/consul.key"
# insecureskipverify = true
```
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
## Consul catalog backend
Træfɪk can be configured to use service discovery catalog of Consul as a backend configuration:
```toml
################################################################
# Consul Catalog configuration backend
################################################################
# Enable Consul Catalog configuration backend
#
# Optional
#
[consulCatalog]
# Consul server endpoint
#
# Required
#
endpoint = "127.0.0.1:8500"
# Default domain used.
#
# Optional
#
domain = "consul.localhost"
```
This backend will create routes matching on hostname based on the service name
used in consul.
## Etcd backend
Træfɪk can be configured to use Etcd as a backend configuration:
```toml
################################################################
# Etcd configuration backend
################################################################
# Enable Etcd configuration backend
#
# Optional
#
# [etcd]
# Etcd server endpoint
#
# Required
#
# endpoint = "127.0.0.1:4001"
# Enable watch Etcd changes
#
# Optional
#
# watch = true
# Prefix used for KV store.
#
# Optional
#
# prefix = "/traefik"
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "etcd.tmpl"
# Enable etcd TLS connection
#
# Optional
#
# [etcd.tls]
# ca = "/etc/ssl/ca.crt"
# cert = "/etc/ssl/etcd.crt"
# key = "/etc/ssl/etcd.key"
# insecureskipverify = true
```
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
## Zookeeper backend
Træfɪk can be configured to use Zookeeper as a backend configuration:
```toml
################################################################
# Zookeeper configuration backend
################################################################
# Enable Zookeeperconfiguration backend
#
# Optional
#
# [zookeeper]
# Zookeeper server endpoint
#
# Required
#
# endpoint = "127.0.0.1:2181"
# Enable watch Zookeeper changes
#
# Optional
#
# watch = true
# Prefix used for KV store.
#
# Optional
#
# prefix = "/traefik"
# Override default configuration template. For advanced users :)
#
# Optional
#
# filename = "zookeeper.tmpl"
```
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
## BoltDB backend
Træfɪk can be configured to use BoltDB as a backend configuration:
```toml
################################################################
# BoltDB configuration backend
################################################################
# Enable BoltDB configuration backend
#
# Optional
#
# [boltdb]
# BoltDB file
#
# Required
#
# endpoint = "/my.db"
# Enable watch BoltDB changes
#
# Optional
#
# watch = true
# Prefix used for KV store.
#
# Optional
#
# prefix = "/traefik"
# Override default configuration template. For advanced users :)
#
# Optional
#
# 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/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/entrypoints` | `http,https` |
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path:/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.
# Examples
## HTTP only
```
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":80"
```
## HTTP + HTTPS (with SNI)
```
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 = "integration/fixtures/https/snitest.org.cert"
KeyFile = "integration/fixtures/https/snitest.org.key"
```
## HTTP redirect on HTTPS
```
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
certFile = "tests/traefik.crt"
keyFile = "tests/traefik.key"
```
## Let's Encrypt support
```
[entryPoints]
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
# certs used as default certs
[[entryPoints.https.tls.certificates]]
certFile = "tests/traefik.crt"
keyFile = "tests/traefik.key"
[acme]
email = "test@traefik.io"
storageFile = "acme.json"
onDemand = true
caServer = "http://172.18.0.1:4000/directory"
entryPoint = "https"
[[acme.domains]]
main = "local1.com"
sans = ["test1.local1.com", "test2.local1.com"]
[[acme.domains]]
main = "local2.com"
sans = ["test1.local2.com", "test2x.local2.com"]
[[acme.domains]]
main = "local3.com"
[[acme.domains]]
main = "local4.com"
```
## Override entrypoints in frontends
```
[frontends]
[frontends.frontend1]
backend = "backend2"
[frontends.frontend1.routes.test_1]
rule = "Host:test.localhost"
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
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"
```

14
etcd.go
View File

@@ -1,14 +0,0 @@
package main
type EtcdProvider struct {
Watch bool
Endpoint string
Prefix string
Filename string
KvProvider *KvProvider
}
func (provider *EtcdProvider) Provide(configurationChan chan<- configMessage) error {
provider.KvProvider = NewEtcdProvider(provider)
return provider.KvProvider.provide(configurationChan)
}

View File

@@ -6,7 +6,7 @@ zk:
ZK_ID: 1
master:
image: mesosphere/mesos-master:0.23.0-1.0.ubuntu1404
image: mesosphere/mesos-master:0.26.0-0.2.145.ubuntu1404
net: host
environment:
MESOS_ZK: zk://127.0.0.1:2181/mesos
@@ -17,7 +17,7 @@ master:
MESOS_WORK_DIR: /var/lib/mesos
slave:
image: mesosphere/mesos-slave:0.23.0-1.0.ubuntu1404
image: mesosphere/mesos-slave:0.26.0-0.2.145.ubuntu1404
net: host
pid: host
privileged: true
@@ -31,9 +31,10 @@ slave:
- /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:v0.9.2
image: mesosphere/marathon:v0.13.0
net: host
environment:
MARATHON_MASTER: zk://127.0.0.1:2181/mesos

View File

@@ -0,0 +1,21 @@
traefik:
image: traefik
command: --web --docker --docker.domain=docker.localhost --logLevel=DEBUG
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /dev/null:/traefik.toml
whoami1:
image: emilevauge/whoami
labels:
- "traefik.backend=whoami"
- "traefik.frontend.rule=Host:whoami.docker.localhost"
whoami2:
image: emilevauge/whoami
labels:
- "traefik.backend=whoami"
- "traefik.frontend.rule=Host:whoami.docker.localhost"

View File

@@ -16,10 +16,12 @@ curl -i -H "Accept: application/json" -X PUT -d "2" ht
# frontend 1
curl -i -H "Accept: application/json" -X PUT -d "backend2" http://localhost:8500/v1/kv/traefik/frontends/frontend1/backend
curl -i -H "Accept: application/json" -X PUT -d "http" http://localhost:8500/v1/kv/traefik/frontends/frontend1/entrypoints
curl -i -H "Accept: application/json" -X PUT -d "Host" http://localhost:8500/v1/kv/traefik/frontends/frontend1/routes/test_1/rule
curl -i -H "Accept: application/json" -X PUT -d "test.localhost" http://localhost:8500/v1/kv/traefik/frontends/frontend1/routes/test_1/value
# frontend 2
curl -i -H "Accept: application/json" -X PUT -d "backend1" http://localhost:8500/v1/kv/traefik/frontends/frontend2/backend
curl -i -H "Accept: application/json" -X PUT -d "http,https" http://localhost:8500/v1/kv/traefik/frontends/frontend2/entrypoints
curl -i -H "Accept: application/json" -X PUT -d "Path" http://localhost:8500/v1/kv/traefik/frontends/frontend2/routes/test_2/rule
curl -i -H "Accept: application/json" -X PUT -d "/test" http://localhost:8500/v1/kv/traefik/frontends/frontend2/routes/test_2/value

21
examples/traefik.crt Normal file
View File

@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAPPVb4fq4kkvMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTUxMDE5MTk0MTU4WhcNMTYxMDE4MTk0MTU4WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAsPnpfnUPbQxSu3oq38OaX/Q6LKZ5gnS04F8kREF2RvCDMWiKOWru+hXb
udkwU7Fx+7BcDBGsnJGFpY23dDcRurxF1DVs1jIFukH/vbYyHE8JQEgvOGSpDEiv
rfbcxqK8E/VMrI10eXYGxWzaTFWQOND2PAJ1b5JvZrrzc8rfJ7h5Q24GKnw1999t
hwsZgpUOh9te7fz1M4XxxRRoliMg0oH9EV3P9Yqq635tjWOix8PcnpcqnRKXVDhk
TcNtE+45RsPoSgM6nkiXt8HP4afaVUAGAzF41kDm94SNexcyk7gyVsLs2cEI61Eu
mhvpP3z91md+eAa3If7kU1w70WiY1wIDAQABo1AwTjAdBgNVHQ4EFgQUue6v2TkZ
1oR0ZzEnnxfKdsGuBPMwHwYDVR0jBBgwFoAUue6v2TkZ1oR0ZzEnnxfKdsGuBPMw
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAk+xxO8gC40R7+5WVtWvA
+chNsOoxKyFBOPvGzrYGQbt4OBWKrwQmMXSY3VnjY4GzVaZpOCJOxnupKfZrK4AP
G+M+NI+J6fHJRCQdov7Xoje5M14FmgjRiLg+haDZhh//11C7P6MQPAzGNUTpUyqV
Hsi/wwCYvre5bApb/4uDkDlZkLrgN4e1q8+gh6XLj8NPEOEBEI4VpMVoieC1PwnK
pRfNlTsEhyjeMmOllw9fBKMEvEf1BKsJGaKmQ7zCr1nWznCxyI1Fuf66TfmL8/up
lK6sQysLEOIgn2gZEjQz4O/9Jj9v8+TvyP4GZIDsCiv33AaeKJVuSkoeCH0Ls2V8
aQ==
-----END CERTIFICATE-----

28
examples/traefik.key Normal file
View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCw+el+dQ9tDFK7
eirfw5pf9DospnmCdLTgXyREQXZG8IMxaIo5au76Fdu52TBTsXH7sFwMEayckYWl
jbd0NxG6vEXUNWzWMgW6Qf+9tjIcTwlASC84ZKkMSK+t9tzGorwT9UysjXR5dgbF
bNpMVZA40PY8AnVvkm9muvNzyt8nuHlDbgYqfDX3322HCxmClQ6H217t/PUzhfHF
FGiWIyDSgf0RXc/1iqrrfm2NY6LHw9yelyqdEpdUOGRNw20T7jlGw+hKAzqeSJe3
wc/hp9pVQAYDMXjWQOb3hI17FzKTuDJWwuzZwQjrUS6aG+k/fP3WZ354Brch/uRT
XDvRaJjXAgMBAAECggEAHvnvO5ojtBOXG4d7n6TuDWODFzOgSwxAaJFemK/Ykvwg
CnLg1sH3yEAxMGtqgQurBsHMqrQhQVpbSSnv9WB6MvQnSMh9H1SsGfjZWYxdYwUW
enDoCvfbevHyBgISjJYJU3j5Da7It0XIU6AE6Z2EW91/a+uGQJwh8ZpBaIAW5S2j
B3k+bASANtwEcDdhGE7iLYeHiAttZo89oSSFZP/mwh84pIU29zUVUtsUaHXrob0p
iyGXKPa8NqTvIsbX5Kh/lbbCO4KwsOqgs/eqL7cLSv2VfTmSQCJz+ikiVzcw/vJU
PaT9H4SCBLP73/Gyjf5P14esWvprPQ3ZnWNNDDGWsQKBgQDoWqxQUy6PKY9or7QH
M985y52Y0QlWdmRaLc8gxfWLU4/3Wn0NH1flkFXJ5X9uZFNoGMQpidJBajepzkNO
/54V+1NCLUWl7SE5gMeFG8QtEE7ISyjut71CUDSn5mOp7EBARmqRpMZhmXT42RZi
1zVDkG08ArKdH0Jnvkq5lWHGbwKBgQDC/IYJXkd27XZO+Ti8TdzaU+SSJV26aY++
0N4pzq0cC6IWadHugH/XrgkfH+ImPzkf6XHrCSqSipJJLZMd473/8IjdOsf54wDP
/yHKPXWhfC4W2L+6+l34Jo/ebnuDVvDme1nKLcdmxhwz4YZfg/TYbWaFzANrl3St
beGg9ENIGQKBgBr6/GtPXWauUsK7NFJpyY/yfthR3Z22nayDCTwrAHovN9ZnIYI2
k4RKoEuTZJqy96Rsy8pvAIUsCk6jbtlrgTXYOzDCBQZhZKxCsehY8wywihVj9NrT
ZxyeJ58fd48xqbxM8O78jTSkFxsWSi0sBDlWOfjv70GjcZiOVir6l6HtAoGBAJeA
MAENcQeV4AviltOwx/4Xmwx23gmeRaMklMn1HQoie9FgbU4cJ7kEL3AwjL3c99y0
vN+7Ion0A0+6iol5z8ISObVzG7gsShBSkwWZlVFgtErqJKb6K5NJGxXf0DYvkkPy
6cQup7VSDs282HRUiiSzdCpXZvztFCpAq0QtJi3ZAoGACjtJ7zEVs0hB7+sCq/SI
UHjjv/fjGSm1TVDP46Joqbm62FRdYkEhd+pGMjtGs80OhM+psTZIqe/fgKdKl5yX
nS9m6f4ny6XCcilfI3+bxXtsmWnpQnybSU2goe2n+Eoi3RcEB68Hp8U0aPjgDULM
9YDU/ZMupHh/eT79n67QIXw=
-----END PRIVATE KEY-----

31
examples/whoami.json Normal file
View File

@@ -0,0 +1,31 @@
{
"id": "whoami",
"cpus": 0.1,
"mem": 64.0,
"instances": 3,
"container": {
"type": "DOCKER",
"docker": {
"image": "emilevauge/whoami",
"network": "BRIDGE",
"portMappings": [
{ "containerPort": 80, "hostPort": 0, "protocol": "tcp" }
]
}
},
"healthChecks": [
{
"protocol": "HTTP",
"portIndex": 0,
"path": "/",
"gracePeriodSeconds": 5,
"intervalSeconds": 20,
"maxConsecutiveFailures": 3
}
],
"labels": {
"traefik.weight": "1",
"traefik.protocole": "http",
"traefik.frontend.rule" : "Headers:Host,test.localhost"
}
}

View File

@@ -1 +0,0 @@
package main

View File

@@ -2,8 +2,10 @@
Copyright
*/
//go:generate go get github.com/jteeuwen/go-bindata/...
//go:generate rm -vf gen.go
//go:generate go-bindata -o gen.go static/... templates/... providerTemplates/...
//go:generate rm -vf autogen/gen.go
//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

272
glide.lock generated Normal file
View File

@@ -0,0 +1,272 @@
hash: 79b6eb2a613b5e2ce5c57150eec41ac04def3f232a3613fd8b5a88b5e1041b38
updated: 2016-04-02T15:42:37.505896092+02:00
imports:
- name: github.com/alecthomas/template
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
- name: github.com/alecthomas/units
version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- name: github.com/boltdb/bolt
version: 51f99c862475898df9773747d3accd05a7ca33c1
- name: github.com/BurntSushi/toml
version: bd2bdf7f18f849530ef7a1c29a4290217cab32a1
- name: github.com/BurntSushi/ty
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
subpackages:
- fun
- name: github.com/cenkalti/backoff
version: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
- name: github.com/codahale/hdrhistogram
version: 954f16e8b9ef0e5d5189456aa4c1202758e04f17
- name: github.com/codegangsta/cli
version: bf4a526f48af7badd25d2cb02d587e1b01be3b50
- name: github.com/codegangsta/negroni
version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
- name: github.com/containous/oxy
version: 0b5b371bce661385d35439204298fa6fb5db5463
subpackages:
- cbreaker
- forward
- memmetrics
- roundrobin
- utils
- stream
- name: github.com/coreos/go-etcd
version: cc90c7b091275e606ad0ca7102a23fb2072f3f5e
subpackages:
- etcd
- name: github.com/davecgh/go-spew
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
subpackages:
- spew
- name: github.com/docker/distribution
version: ff6f38ccb69afa96214c7ee955359465d1fc767a
subpackages:
- reference
- name: github.com/docker/docker
version: f39987afe8d611407887b3094c03d6ba6a766a67
subpackages:
- autogen
- api
- cliconfig
- daemon/network
- graph/tags
- image
- opts
- pkg/archive
- pkg/fileutils
- pkg/homedir
- pkg/httputils
- pkg/ioutils
- pkg/jsonmessage
- pkg/mflag
- pkg/nat
- pkg/parsers
- pkg/pools
- pkg/promise
- pkg/random
- pkg/stdcopy
- pkg/stringid
- pkg/symlink
- pkg/system
- pkg/tarsum
- pkg/term
- pkg/timeutils
- pkg/tlsconfig
- pkg/ulimit
- pkg/units
- pkg/urlutil
- pkg/useragent
- pkg/version
- registry
- runconfig
- utils
- volume
- name: github.com/docker/engine-api
version: 8924d6900370b4c7e7984be5adc61f50a80d7537
subpackages:
- client
- types
- types/container
- types/filters
- types/strslice
- client/transport
- client/transport/cancellable
- types/network
- types/registry
- types/time
- types/blkiodev
- name: github.com/docker/go-connections
version: f549a9393d05688dff0992ef3efd8bbe6c628aeb
subpackages:
- nat
- sockets
- tlsconfig
- name: github.com/docker/go-units
version: 5d2041e26a699eaca682e2ea41c8f891e1060444
- name: github.com/docker/libcompose
version: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
- name: github.com/docker/libkv
version: 3732f7ff1b56057c3158f10bceb1e79133025373
subpackages:
- store
- store/boltdb
- store/consul
- store/etcd
- store/zookeeper
- name: github.com/docker/libtrust
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
- name: github.com/donovanhide/eventsource
version: d8a3071799b98cacd30b6da92f536050ccfe6da4
- name: github.com/elazarl/go-bindata-assetfs
version: d5cac425555ca5cf00694df246e04f05e6a55150
- name: github.com/flynn/go-shlex
version: 3f9db97f856818214da2e1057f8ad84803971cff
- name: github.com/gambol99/go-marathon
version: ade11d1dc2884ee1f387078fc28509559b6235d1
- name: github.com/go-check/check
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
- name: github.com/golang/glog
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
- name: github.com/google/go-querystring
version: 6bb77fe6f42b85397288d4f6f67ac72f8f400ee7
subpackages:
- query
- name: github.com/gorilla/context
version: 215affda49addc4c8ef7e2534915df2c8c35c6cd
- name: github.com/gorilla/handlers
version: 40694b40f4a928c062f56849989d3e9cd0570e5f
- name: github.com/gorilla/mux
version: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a
- name: github.com/gorilla/websocket
version: e2e3d8414d0fbae04004f151979f4e27c6747fe7
- name: github.com/hashicorp/consul
version: de080672fee9e6104572eeea89eccdca135bb918
subpackages:
- api
- name: github.com/hashicorp/hcl
version: 2604f3bda7e8960c1be1063709e7d7f0765048d0
subpackages:
- hcl/ast
- hcl/parser
- hcl/token
- json/parser
- hcl/scanner
- hcl/strconv
- json/scanner
- json/token
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/kr/pretty
version: add1dbc86daf0f983cd4a48ceb39deb95c729b67
- name: github.com/kr/text
version: bb797dc4fb8320488f47bf11de07a733d7233e1f
- name: github.com/magiconair/properties
version: c265cfa48dda6474e208715ca93e987829f572f8
- name: github.com/mailgun/log
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8
- name: github.com/mailgun/manners
version: fada45142db3f93097ca917da107aa3fad0ffcb5
- name: github.com/mailgun/multibuf
version: 565402cd71fbd9c12aa7e295324ea357e970a61e
- name: github.com/mailgun/timetools
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
- name: github.com/Microsoft/go-winio
version: 9e2895e5f6c3f16473b91d37fae6e89990a4520c
- name: github.com/miekg/dns
version: 7e024ce8ce18b21b475ac6baf8fa3c42536bf2fa
- name: github.com/mitchellh/mapstructure
version: d2dd0262208475919e1a362f675cfc0e7c10e905
- name: github.com/opencontainers/runc
version: 4ab132458fc3e9dbeea624153e0331952dc4c8d5
subpackages:
- libcontainer/user
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/samuel/go-zookeeper
version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
subpackages:
- zk
- name: github.com/Sirupsen/logrus
version: 418b41d23a1bf978c06faea5313ba194650ac088
- name: github.com/spf13/cast
version: ee7b3e0353166ab1f3a605294ac8cd2b77953778
- name: github.com/spf13/cobra
version: 2ccf9e982a3e3eb21eba9c9ad8e546529fd74c71
subpackages:
- cobra
- name: github.com/spf13/jwalterweatherman
version: 33c24e77fb80341fe7130ee7c594256ff08ccc46
- name: github.com/spf13/pflag
version: 7f60f83a2c81bc3c3c0d5297f61ddfa68da9d3b7
- name: github.com/spf13/viper
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
- name: github.com/stretchr/objx
version: cbeaeb16a013161a98496fad62933b1d21786672
- name: github.com/stretchr/testify
version: 6fe211e493929a8aac0469b93f28b1d0688a9a3a
subpackages:
- mock
- assert
- name: github.com/thoas/stats
version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
- name: github.com/unrolled/render
version: 26b4e3aac686940fe29521545afad9966ddfc80c
- name: github.com/vdemeester/docker-events
version: bd72e1848b08db4b5ed1a2e9277621b9f5e5d1f3
- name: github.com/vdemeester/libkermit
version: 7e4e689a6fa9281e0fb9b7b9c297e22d5342a5ec
- name: github.com/vdemeester/shakers
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
- name: github.com/vulcand/oxy
version: 8aaf36279137ac04ace3792a4f86098631b27d5a
subpackages:
- memmetrics
- utils
- name: github.com/vulcand/predicate
version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
- name: github.com/vulcand/route
version: cb89d787ddbb1c5849a7ac9f79004c1fd12a4a32
- name: github.com/vulcand/vulcand
version: 475540bb016702d5b7cc4674e37f48ee3e144a69
subpackages:
- plugin/rewrite
- plugin
- router
- name: github.com/wendal/errors
version: f66c77a7882b399795a8987ebf87ef64a427417e
- name: github.com/xenolf/lego
version: ca19a90028e242e878585941c2a27c8f3b3efc25
subpackages:
- acme
- name: golang.org/x/crypto
version: 9e7f5dc375abeb9619ea3c5c58502c428f457aa2
subpackages:
- ocsp
- name: golang.org/x/net
version: d9558e5c97f85372afee28cf2b6059d7d3818919
subpackages:
- context
- publicsuffix
- proxy
- name: golang.org/x/sys
version: eb2c74142fd19a79b3f237334c7384d5167b1b46
subpackages:
- unix
- name: gopkg.in/alecthomas/kingpin.v2
version: 639879d6110b1b0409410c7b737ef0bb18325038
- name: gopkg.in/fsnotify.v1
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
- name: gopkg.in/mgo.v2
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
subpackages:
- bson
- name: gopkg.in/square/go-jose.v1
version: 40d457b439244b546f023d056628e5184136899b
subpackages:
- cipher
- json
- name: gopkg.in/yaml.v2
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40
devImports: []

176
glide.yaml Normal file
View File

@@ -0,0 +1,176 @@
package: main
import:
- package: github.com/coreos/go-etcd
ref: cc90c7b091275e606ad0ca7102a23fb2072f3f5e
subpackages:
- etcd
- package: github.com/mailgun/log
ref: 44874009257d4d47ba9806f1b7f72a32a015e4d8
- package: github.com/containous/oxy
ref: 0b5b371bce661385d35439204298fa6fb5db5463
subpackages:
- cbreaker
- forward
- memmetrics
- roundrobin
- utils
- package: github.com/hashicorp/consul
ref: de080672fee9e6104572eeea89eccdca135bb918
subpackages:
- api
- package: github.com/samuel/go-zookeeper
ref: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
subpackages:
- zk
- package: github.com/docker/libtrust
ref: 9cbd2a1374f46905c68a4eb3694a130610adc62a
- package: github.com/go-check/check
ref: 11d3bc7aa68e238947792f30573146a3231fc0f1
- package: golang.org/x/net
ref: d9558e5c97f85372afee28cf2b6059d7d3818919
subpackages:
- context
- package: github.com/gorilla/handlers
ref: 40694b40f4a928c062f56849989d3e9cd0570e5f
- package: github.com/docker/libkv
ref: 3732f7ff1b56057c3158f10bceb1e79133025373
- package: github.com/alecthomas/template
ref: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
- package: github.com/vdemeester/shakers
ref: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
- package: github.com/alecthomas/units
ref: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- package: github.com/gambol99/go-marathon
ref: ade11d1dc2884ee1f387078fc28509559b6235d1
- package: github.com/vulcand/predicate
ref: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
- package: github.com/thoas/stats
ref: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
- package: github.com/Sirupsen/logrus
ref: 418b41d23a1bf978c06faea5313ba194650ac088
- package: github.com/unrolled/render
ref: 26b4e3aac686940fe29521545afad9966ddfc80c
- package: github.com/flynn/go-shlex
ref: 3f9db97f856818214da2e1057f8ad84803971cff
- package: github.com/boltdb/bolt
ref: 51f99c862475898df9773747d3accd05a7ca33c1
- package: gopkg.in/mgo.v2
ref: 22287bab4379e1fbf6002fb4eb769888f3fb224c
subpackages:
- bson
- package: github.com/docker/docker
ref: f39987afe8d611407887b3094c03d6ba6a766a67
subpackages:
- autogen
- api
- cliconfig
- daemon/network
- graph/tags
- image
- opts
- pkg/archive
- pkg/fileutils
- pkg/homedir
- pkg/httputils
- pkg/ioutils
- pkg/jsonmessage
- pkg/mflag
- pkg/nat
- pkg/parsers
- pkg/pools
- pkg/promise
- pkg/random
- pkg/stdcopy
- pkg/stringid
- pkg/symlink
- pkg/system
- pkg/tarsum
- pkg/term
- pkg/timeutils
- pkg/tlsconfig
- pkg/ulimit
- pkg/units
- pkg/urlutil
- pkg/useragent
- pkg/version
- registry
- runconfig
- utils
- volume
- package: github.com/mailgun/timetools
ref: fd192d755b00c968d312d23f521eb0cdc6f66bd0
- package: github.com/codegangsta/negroni
ref: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
- package: gopkg.in/yaml.v2
ref: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40
- package: github.com/opencontainers/runc
ref: 4ab132458fc3e9dbeea624153e0331952dc4c8d5
subpackages:
- libcontainer/user
- package: github.com/gorilla/mux
ref: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a
- package: github.com/BurntSushi/ty
ref: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
- package: github.com/elazarl/go-bindata-assetfs
ref: d5cac425555ca5cf00694df246e04f05e6a55150
- package: github.com/BurntSushi/toml
ref: bd2bdf7f18f849530ef7a1c29a4290217cab32a1
- package: gopkg.in/alecthomas/kingpin.v2
ref: 639879d6110b1b0409410c7b737ef0bb18325038
- package: github.com/cenkalti/backoff
ref: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
- package: gopkg.in/fsnotify.v1
ref: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
- package: github.com/mailgun/manners
ref: fada45142db3f93097ca917da107aa3fad0ffcb5
- package: github.com/gorilla/context
ref: 215affda49addc4c8ef7e2534915df2c8c35c6cd
- package: github.com/codahale/hdrhistogram
ref: 954f16e8b9ef0e5d5189456aa4c1202758e04f17
- package: github.com/gorilla/websocket
- package: github.com/donovanhide/eventsource
ref: d8a3071799b98cacd30b6da92f536050ccfe6da4
- package: github.com/golang/glog
ref: fca8c8854093a154ff1eb580aae10276ad6b1b5f
- package: github.com/spf13/cast
ref: ee7b3e0353166ab1f3a605294ac8cd2b77953778
- package: github.com/mitchellh/mapstructure
- package: github.com/spf13/jwalterweatherman
- package: github.com/spf13/pflag
- package: github.com/wendal/errors
- package: github.com/hashicorp/hcl
- package: github.com/kr/pretty
- package: github.com/magiconair/properties
- package: github.com/kr/text
- package: github.com/spf13/viper
ref: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
- package: github.com/spf13/cobra
subpackages:
- /cobra
- package: github.com/google/go-querystring/query
- package: github.com/vulcand/vulcand/plugin/rewrite
- package: github.com/stretchr/testify/mock
- package: github.com/xenolf/lego
- package: github.com/vdemeester/libkermit
ref: 7e4e689a6fa9281e0fb9b7b9c297e22d5342a5ec
- package: github.com/docker/libcompose
version: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
- package: github.com/docker/distribution
version: ff6f38ccb69afa96214c7ee955359465d1fc767a
subpackages:
- reference
- package: github.com/docker/engine-api
subpackages:
- client
- types
- types/container
- types/filters
- types/strslice
- package: github.com/vdemeester/docker-events
- package: github.com/docker/go-connections
subpackages:
- nat
- sockets
- tlsconfig
- package: github.com/docker/go-units
- package: github.com/mailgun/multibuf

View File

@@ -1,24 +1,28 @@
package main
import (
"fmt"
"net/http"
"os/exec"
"time"
"fmt"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// SimpleSuite
type SimpleSuite struct{ BaseSuite }
func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) {
cmd := exec.Command(traefikBinary)
output, err := cmd.CombinedOutput()
c.Assert(err, checker.NotNil)
c.Assert(string(output), checker.Contains, "Error reading file: open traefik.toml: no such file or directory")
c.Assert(string(output), checker.Contains, "Error reading file: open : no such file or directory")
nonExistentFile := "non/existent/file.toml"
cmd = exec.Command(traefikBinary, nonExistentFile)
cmd = exec.Command(traefikBinary, "--configFile="+nonExistentFile)
output, err = cmd.CombinedOutput()
c.Assert(err, checker.NotNil)
@@ -26,26 +30,38 @@ func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) {
}
func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
cmd := exec.Command(traefikBinary, "fixtures/invalid_configuration.toml")
cmd := exec.Command(traefikBinary, "--configFile=fixtures/invalid_configuration.toml")
output, err := cmd.CombinedOutput()
c.Assert(err, checker.NotNil)
c.Assert(string(output), checker.Contains, "Error reading file: Near line 1")
c.Assert(string(output), checker.Contains, "Error reading file: While parsing config: Near line 1")
}
func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
cmd := exec.Command(traefikBinary, "fixtures/simple_default.toml")
cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_default.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(100 * time.Millisecond)
time.Sleep(500 * time.Millisecond)
// TODO validate : run on 80
resp, err := http.Get("http://127.0.0.1/")
resp, err := http.Get("http://127.0.0.1:8000/")
// Expected a 404 as we did not comfigure anything
// Expected a 404 as we did not configure anything
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
killErr := cmd.Process.Kill()
c.Assert(killErr, checker.IsNil)
}
func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/simple_web.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(500 * time.Millisecond)
resp, err := http.Get("http://127.0.0.1:8080/api")
// Expected a 200
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200)
}

View File

@@ -0,0 +1,112 @@
package main
import (
"io/ioutil"
"net/http"
"os/exec"
"time"
"github.com/go-check/check"
"github.com/hashicorp/consul/api"
checker "github.com/vdemeester/shakers"
)
// Consul catalog test suites
type ConsulCatalogSuite struct {
BaseSuite
consulIP string
consulClient *api.Client
}
func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "consul_catalog")
s.composeProject.Start(c)
consul := s.composeProject.Container(c, "consul")
s.consulIP = consul.NetworkSettings.IPAddress
config := api.DefaultConfig()
config.Address = s.consulIP + ":8500"
consulClient, err := api.NewClient(config)
if err != nil {
c.Fatalf("Error creating consul client")
}
s.consulClient = consulClient
// Wait for consul to elect itself leader
time.Sleep(2000 * time.Millisecond)
}
func (s *ConsulCatalogSuite) registerService(name string, address string, port int) error {
catalog := s.consulClient.Catalog()
_, err := catalog.Register(
&api.CatalogRegistration{
Node: address,
Address: address,
Service: &api.AgentService{
ID: name,
Service: name,
Address: address,
Port: port,
},
},
&api.WriteOptions{},
)
return err
}
func (s *ConsulCatalogSuite) deregisterService(name string, address string) error {
catalog := s.consulClient.Catalog()
_, err := catalog.Deregister(
&api.CatalogDeregistration{
Node: address,
Address: address,
ServiceID: name,
},
&api.WriteOptions{},
)
return err
}
func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) {
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--configFile=fixtures/consul_catalog/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)
}
func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
nginx := s.composeProject.Container(c, "nginx")
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80)
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
time.Sleep(5000 * time.Millisecond)
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.consul.localhost"
resp, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200)
_, err = ioutil.ReadAll(resp.Body)
c.Assert(err, checker.IsNil)
}

View File

@@ -5,23 +5,29 @@ import (
"os/exec"
"time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// Consul test suites (using libcompose)
type ConsulSuite struct{ BaseSuite }
func (s *ConsulSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "consul")
}
func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
cmd := exec.Command(traefikBinary, "fixtures/consul/simple.toml")
cmd := exec.Command(traefikBinary, "--configFile=fixtures/consul/simple.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(100 * time.Millisecond)
time.Sleep(500 * time.Millisecond)
// TODO validate : run on 80
resp, err := http.Get("http://127.0.0.1/")
resp, err := http.Get("http://127.0.0.1:8000/")
// Expected a 404 as we did not comfigure anything
// Expected a 404 as we did not configure anything
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
killErr := cmd.Process.Kill()
c.Assert(killErr, checker.IsNil)
}

View File

@@ -1,31 +1,190 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"strings"
"time"
"github.com/docker/docker/pkg/namesgenerator"
"github.com/go-check/check"
d "github.com/vdemeester/libkermit/docker"
docker "github.com/vdemeester/libkermit/docker/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
var (
// Label added to started container to identify them as part of the integration test
TestLabel = "io.traefik.test"
// Images to have or pull before the build in order to make it work
// FIXME handle this offline but loading them before build
RequiredImages = map[string]string{
"swarm": "1.0.0",
"nginx": "1",
}
)
// Docker test suites
type DockerSuite struct {
BaseSuite
project *docker.Project
}
func (s *DockerSuite) startContainer(c *check.C, image string, args ...string) string {
return s.startContainerWithConfig(c, image, d.ContainerConfig{
Cmd: args,
})
}
func (s *DockerSuite) startContainerWithLabels(c *check.C, image string, labels map[string]string, args ...string) string {
return s.startContainerWithConfig(c, image, d.ContainerConfig{
Cmd: args,
Labels: labels,
})
}
func (s *DockerSuite) startContainerWithConfig(c *check.C, image string, config d.ContainerConfig) string {
if config.Name == "" {
config.Name = namesgenerator.GetRandomName(10)
}
container := s.project.StartWithConfig(c, image, config)
// FIXME(vdemeester) this is ugly (it's because of the / in front of the name in docker..)
return strings.SplitAfter(container.Name, "/")[1]
}
func (s *DockerSuite) SetUpSuite(c *check.C) {
project := docker.NewProjectFromEnv(c)
s.project = project
// Pull required images
for repository, tag := range RequiredImages {
image := fmt.Sprintf("%s:%s", repository, tag)
s.project.Pull(c, image)
}
}
func (s *DockerSuite) TearDownTest(c *check.C) {
s.project.Clean(c, os.Getenv("CIRCLECI") != "")
}
func (s *DockerSuite) TestSimpleConfiguration(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
defer os.Remove(file)
cmd := exec.Command(traefikBinary, file)
cmd := exec.Command(traefikBinary, "--configFile="+file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(100 * time.Millisecond)
time.Sleep(500 * time.Millisecond)
// TODO validate : run on 80
resp, err := http.Get("http://127.0.0.1/")
resp, err := http.Get("http://127.0.0.1:8000/")
c.Assert(err, checker.IsNil)
// Expected a 404 as we did not comfigure anything
c.Assert(resp.StatusCode, checker.Equals, 404)
}
func (s *DockerSuite) TestDefaultDockerContainers(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
defer os.Remove(file)
name := s.startContainer(c, "swarm:1.0.0", "manage", "token://blablabla")
// Start traefik
cmd := exec.Command(traefikBinary, "--configFile="+file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
time.Sleep(1500 * time.Millisecond)
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/version", nil)
c.Assert(err, checker.IsNil)
req.Host = fmt.Sprintf("%s.docker.localhost", name)
resp, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200)
body, err := ioutil.ReadAll(resp.Body)
c.Assert(err, checker.IsNil)
var version map[string]interface{}
c.Assert(json.Unmarshal(body, &version), checker.IsNil)
c.Assert(version["Version"], checker.Equals, "swarm/1.0.0")
}
func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
defer os.Remove(file)
// Start a container with some labels
labels := map[string]string{
"traefik.frontend.rule": "Host:my.super.host",
}
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
// Start traefik
cmd := exec.Command(traefikBinary, "--configFile="+file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
time.Sleep(1500 * time.Millisecond)
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/version", nil)
c.Assert(err, checker.IsNil)
req.Host = fmt.Sprintf("my.super.host")
resp, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200)
body, err := ioutil.ReadAll(resp.Body)
c.Assert(err, checker.IsNil)
var version map[string]interface{}
c.Assert(json.Unmarshal(body, &version), checker.IsNil)
c.Assert(version["Version"], checker.Equals, "swarm/1.0.0")
}
func (s *DockerSuite) TestDockerContainersWithOneMissingLabels(c *check.C) {
file := s.adaptFileForHost(c, "fixtures/docker/simple.toml")
defer os.Remove(file)
// Start a container with some labels
labels := map[string]string{
"traefik.frontend.value": "my.super.host",
}
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")
// Start traefik
cmd := exec.Command(traefikBinary, "--configFile="+file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// FIXME Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
time.Sleep(1500 * time.Millisecond)
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/version", nil)
c.Assert(err, checker.IsNil)
req.Host = fmt.Sprintf("my.super.host")
resp, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
killErr := cmd.Process.Kill()
c.Assert(killErr, checker.IsNil)
}

33
integration/etcd_test.go Normal file
View File

@@ -0,0 +1,33 @@
package main
import (
"net/http"
"os/exec"
"time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
)
// Etcd test suites (using libcompose)
type EtcdSuite struct{ BaseSuite }
func (s *EtcdSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "etcd")
}
func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/etcd/simple.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(1000 * 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)
}

View File

@@ -5,23 +5,45 @@ import (
"os/exec"
"time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// File test suites
type FileSuite struct{ BaseSuite }
func (s *FileSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "file")
s.composeProject.Start(c)
}
func (s *FileSuite) TestSimpleConfiguration(c *check.C) {
cmd := exec.Command(traefikBinary, "fixtures/file/simple.toml")
cmd := exec.Command(traefikBinary, "--configFile=fixtures/file/simple.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(100 * time.Millisecond)
// TODO validate : run on 80
resp, err := http.Get("http://127.0.0.1/")
time.Sleep(1000 * time.Millisecond)
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)
}
// #56 regression test, make sure it does not fail
func (s *FileSuite) TestSimpleConfigurationNoPanic(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/file/56-simple-panic.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(1000 * time.Millisecond)
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)
killErr := cmd.Process.Kill()
c.Assert(killErr, checker.IsNil)
}

View File

@@ -1,11 +1,9 @@
# Reverse proxy port
#
# Optional
# Default: ":80"
#
# port = ":80"
#
# LogLevel
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[consul]

View File

@@ -0,0 +1,9 @@
defaultEntryPoints = ["http"]
logLevel = "DEBUG"
[entryPoints]
[entryPoints.http]
address = ":8000"
[consulCatalog]
domain = "consul.localhost"

View File

@@ -1,11 +1,9 @@
# Reverse proxy port
#
# Optional
# Default: ":80"
#
# port = ":80"
#
# LogLevel
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[docker]

View File

@@ -0,0 +1,10 @@
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[etcd]
endpoint = "127.0.0.1:4003,127.0.0.1:4002,127.0.0.1:4001"

View File

@@ -0,0 +1,9 @@
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[file]

View File

@@ -1,11 +1,40 @@
# Reverse proxy port
#
# Optional
# Default: ":80"
#
# port = ":80"
#
# LogLevel
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[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.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"
[frontends.frontend2.routes.test_2]
rule = "Path:/test"

View File

@@ -0,0 +1,34 @@
logLevel = "DEBUG"
defaultEntryPoints = ["https"]
[entryPoints]
[entryPoints.https]
address = ":4443"
[entryPoints.https.tls]
[[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"

View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIC/zCCAeegAwIBAgIJAL858pci5XyjMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV
BAMMC3NuaXRlc3QuY29tMB4XDTE1MTEyMzIyMDU1NloXDTI1MTEyMDIyMDU1Nlow
FjEUMBIGA1UEAwwLc25pdGVzdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDVF25wEroSIUO/dNgHxlyt8pZFVpJ8fNaJw7cnlZ1JP2hLuEbmjAFT
dHqS8wKDNYHktsBEOUfN/qbk0AiGb+SvhQw6kfM/QSj9fXVQ7KhYP9XYOekTOH7d
M0Z2L3RGgqs8z+83exOOnAFVvIJCMZJXEeijV6iJlmpCcJa0Kg/JHlxhoWTEeZuU
G+hITafk1yWOKorTCPlMhB30wuQoWfbHP+3G0bsERLXFiMANE8EtQu8+ZhfseBUh
5Tu5gIC4Fnria7mRixAZeEiAblFP9h0vrNRcP3nmuVz0tHPIeQsJQiEhxaZ09oUW
h9WqTsYCP1821+SVazM9oFRTpy6chZyTAgMBAAGjUDBOMB0GA1UdDgQWBBSz9mbX
ia1TM5FG4Zgagaet24S8HDAfBgNVHSMEGDAWgBSz9mbXia1TM5FG4Zgagaet24S8
HDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQB79W8XTxlozh9w/W7T
5vDev67G4T/wetABSb68CRrqojt78PMJuS89JarA8I3ts00O+0JYnsHxp+9qC7pf
jWHcDSiLwRUMu7MXW/KIen1EB8BQNA0xWbAiQaWYPHzsBlX48+9wBe0HTDx7Lcxb
OsmnXHBF5fd2EY+R8qJu+PyTDDL1WLItFJpzHiFiGiYF8Tyic3kkPjje6eIOxRmT
hq+qbwApzbzz6h/VD5xR3zBDFBo2Xs5tdP264KIw/YXDpaXVBiJ5DDjQ3dtJw1G5
yzgrHQZWJN8Gs8ZZgGdgRf7PHox8xEZtqPiMkChDz6T7Ha3U0xYN6TZGNZOR6DHs
K9/8
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA1RducBK6EiFDv3TYB8ZcrfKWRVaSfHzWicO3J5WdST9oS7hG
5owBU3R6kvMCgzWB5LbARDlHzf6m5NAIhm/kr4UMOpHzP0Eo/X11UOyoWD/V2Dnp
Ezh+3TNGdi90RoKrPM/vN3sTjpwBVbyCQjGSVxHoo1eoiZZqQnCWtCoPyR5cYaFk
xHmblBvoSE2n5NcljiqK0wj5TIQd9MLkKFn2xz/txtG7BES1xYjADRPBLULvPmYX
7HgVIeU7uYCAuBZ64mu5kYsQGXhIgG5RT/YdL6zUXD955rlc9LRzyHkLCUIhIcWm
dPaFFofVqk7GAj9fNtfklWszPaBUU6cunIWckwIDAQABAoIBABAdQYDAKcoNMe5c
i6mq2n9dBPghX9qCJkcswcEAk3BilySCvvnYRJFnEY3jSqFZfoUpPMjr+/4b78sF
4F8qPwT27sHPH7H833ir8B86hlCGI0nCt1l4wD9CDWYKmKRsZT6oCtMLP6NdMMyn
AMK4tPRYqlsP2fLtqQN1ODBPrfnraoNHtOVE784iBCD5dewICA5RIQG2i/d2+CGF
+bahFqUXVCqHoxBz4AVvrRFL99VcP7P2iZyk6hDQ7fci7Xay8Wb/HutRxuqvF0aU
bG6Enk6CCtNZHLwNPp4Hqft0Udvg2tG8okYwbEmoEO40nQsCSzRCpq5Uvzi+LX1k
LykQ6+ECgYEA7x8vQoyOK60Q3LPpJFGDec2+XJPoesTfJTT6idaP7ukUL8p3FsUo
9vtxRRfhSOdPoAINmrL0TyMekO2B6zXx0pmWVpMrFwZW6zMwZAnLp/w+3USpbGCy
K12IIwvRYzTzKwoMTVAKTXm36b6oqr2La4bTdJR7REY6G374FrJb/H0CgYEA5CHk
Ym0h7cf00fw9UEHRfzUZxmCfRWY6K8InOuHdLi+u4TiyXzs8x5s0e/DN/raNmTGx
QO81UzuS3nKwc4n5QyXjVnhzR5DbbSACDwHtdnxZByL0D1KvPjtRF8F+rWXViXv2
TM7UiOmn6R375FPSAPxeyMx8Womc3EnAAfLWGk8CgYEAv8I2WBv3dzcWqqbsdF+a
G/fOjNdgO/PdLy1JLXiPfHwV4C1xSyVZMJd7wnjgBWLaC+sZldGk8kGrpXWSFlnw
T38zfMIQcCp5Uax/RfpFA7XZhAAoDe2NdBFRtyknBXPU/dLVArsJSBAwWJa5FBNk
1xoMQRVBtQLMXnh341utQNECgYEA4o1R2/ka16NaWmpPjXM/lD9skFgF84p4vFn8
UXpaB3LtDdcbNH2Ed4mHToouWAR8jCUQLTcg0r53tRdaafMcKfXnVUka2nhdoHpH
8RVt99u3IeIxU0I+q+OGPbw3jAV0UStcxpwj7q9zw4q2SuJ+y+HUUz7XQ6Yjs5Q9
7PF2c/sCgYEAhdVn5gZ5FvYKrBi46t3pxPsWK476HmQEVHVi5+od7wg+araDelAe
8QE8hc8qdZGbjdB/AHSPCeUxfO2vnpsCoSRs29o6pDvQuqvHYs+M53l5LEYeOjof
t6J/DK5Pim2CAFjYFcZk8/Gyl5HjTw3PpdWxoPD5v2Xw3bbY57IIbm4=
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIC/zCCAeegAwIBAgIJALAYHG/vGqWEMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV
BAMMC3NuaXRlc3Qub3JnMB4XDTE1MTEyMzIyMDU0NFoXDTI1MTEyMDIyMDU0NFow
FjEUMBIGA1UEAwwLc25pdGVzdC5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQC8b2Qv68Xnv4wgJ6HNupxABSUA5KXmv9g7pwwsFMSOK15o2qGFzx/x
9loIi5pMIYIy4SVwJNrYUi772nCYMqSIVXlwct/CE70j2Jb2geIHu3jHbFWXruWb
W1tGGUYzvnsOUziPE3rLWa/NObNYLLlUKJaxfHrxnpuKpQUsXzoLl25cJEVr4jg2
ZITpdraxaBLisdlWY7EwwHBLu2nxH5Rn+nIjenFfdUwKF9s5dGy63tfBc8LX9yJk
+kOwy1al/Wxa0DUb6rSt0QDCcD+rXnjk2zWPtsHz1btwtqM+FLtN5z0Lmnx7DF3C
tCf1TMzduzZ6aeHk77zc664ZQun5cH33AgMBAAGjUDBOMB0GA1UdDgQWBBRn/nNz
PUsmDKmKv3GGo3km5KKvUDAfBgNVHSMEGDAWgBRn/nNzPUsmDKmKv3GGo3km5KKv
UDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBkuutIcbBdESgvNLLr
k/8HUDuFm72lYHZFE+c76CxqYN52w02NCTiq1InoDUvqZXb/StATBwRRduTUPCj9
KUkC7pOjAFxjzjExsHrtZSq01WinrxNI+qSKvI8jFngMHnwN1omTt7/D7nxeW5Of
FJTkElnxtELAGHoIwZ+bKprnexefpn9UW84VJvJ2crSR63vBvdTrgsrEGW6kQj1I
62laDpax4+x8t2h+sfG6uNIA1cFrG8Sk+O2Bi3ogB7Y/4e8r6WA23IRP+aSv0J2b
k5fvuuXbIc979pQOoO03zG0S7Wpmpsw+9dQB9TOxGITOLfCZwEuIhnv+M9lLqCks
7H2A
-----END CERTIFICATE-----

View File

@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAvG9kL+vF57+MICehzbqcQAUlAOSl5r/YO6cMLBTEjiteaNqh
hc8f8fZaCIuaTCGCMuElcCTa2FIu+9pwmDKkiFV5cHLfwhO9I9iW9oHiB7t4x2xV
l67lm1tbRhlGM757DlM4jxN6y1mvzTmzWCy5VCiWsXx68Z6biqUFLF86C5duXCRF
a+I4NmSE6Xa2sWgS4rHZVmOxMMBwS7tp8R+UZ/pyI3pxX3VMChfbOXRsut7XwXPC
1/ciZPpDsMtWpf1sWtA1G+q0rdEAwnA/q1545Ns1j7bB89W7cLajPhS7Tec9C5p8
ewxdwrQn9UzM3bs2emnh5O+83OuuGULp+XB99wIDAQABAoIBAGOn9bByXQQnhZAr
5aLMIn6pOdyzEBptM4q42fMmOJ2HyjJiDjKaTCbHRu5mBoBk6FrIP+iDVUo6jKad
7BZSEjoYGlWiKzyU+97NWWmdX1D/kOzHGq1RzhTPyAHWtA4Bm0sEMFFa2AJbuGIt
NfBYFtuva6MKVmsamuBETewdoLEnxzzDFcuOaxXRfTC/ikWcYyB4KEWA5fjroUQC
Llo9/UTGTkh1Hynv9AXY6Qia/RbrIQjKveKCRj6PjxyE/qN9qfmngczz2pK0hRhL
Z+K06y8G+Yj1I1zm5jNg1kakVQKoBsnaYkmIUBUSmWv6ERotedOWtOAMlOKa+0l2
DS7Ou2ECgYEA91doi+3XrMVsgyTEm/ArzEyRUfM5dCSvBCRFhO7QQp2OYAbjJk5S
pmdpqmwTsXNNMU+XNkWCLug5pk0PTJwP0mVLE2fLYqCCXoyaMltQ0Yk2gaun/RwE
w5EfyMwOQakLFY/ODvduQfyNpaoWgFz4/WPNTVNCGs04LepSGKaFNy0CgYEAwwgV
jKeFA+QZGooTInyk07ZlAbenEPu/c2y3UUFxclP0CjP2/VBOpz9B62vhzCKbjD1c
/L3x1CKC4n4lbeyHi4vrF69LX9SHr1Jm0SUtyKeV3g0EAzIWI0HFhVUkMvtbibQ4
HXrLVCJO77xetQ7RQszss1z9g3WotAAiBMiQgDMCgYBTLjoilOIrYFmV4Q+dwa95
DWbxwHJZ9NxG8EvQ4N95B7OR578Matqwy6ZlgeM9kiErrDCWN9oIHGEG5HN4uCM6
BoaxB/8GNCSj13Uj6kHLtfF2ulvMa1fOzUd7J+TDgC4SGkKaFewmlOCuDf1zPdEe
pimtD4rzqIB0MJFbaOT0IQKBgDBPjlb7IB3ooLdMQJUoXwP6iGa2gXHZioEjCv3b
wihZ13e3i5UQEYuoRcH1RUd1wyYoBSKuQnsT2WwVZ1wlXSYaELAbQgaI9NtfBA0G
sqKjsKICg13vSECPiEgQ4Rin3vLra4MR6c/7d6Y2+RbMhtWPQYrkm/+2Y4XDCqo4
rGK1AoGAOFZ3RVhuwXzFdKNe32LM1wm1eZ7waxjI4bQS2xUN/3C/uWS7A3LaSlc3
eRG3DaVpez4DQVupZDHMgxJUYqqKynUj6GD1YiaxGROj3TYCu6e7OxyhalhCllSu
w/X5M802XqzLjeec5zHoZDfknnAkgR9MsxZYmZPFaDyL6GOKUB8=
-----END RSA PRIVATE KEY-----

View File

@@ -1,11 +1,9 @@
# Reverse proxy port
#
# Optional
# Default: ":80"
#
# port = ":80"
#
# LogLevel
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[marathon]

View File

@@ -1,9 +1,5 @@
# Reverse proxy port
#
# Optional
# Default: ":80"
#
# port = ":80"
#
# LogLevel
logLevel = "DEBUG"
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"

View File

@@ -0,0 +1,9 @@
logLevel = "DEBUG"
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
[web]
address = ":8080"

111
integration/https_test.go Normal file
View File

@@ -0,0 +1,111 @@
package main
import (
"crypto/tls"
"net"
"net/http"
"net/http/httptest"
"os/exec"
"time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
)
// HTTPSSuite
type HTTPSSuite struct{ BaseSuite }
// TestWithSNIConfigHandshake involves a client sending a SNI hostname of
// "snitest.com", which happens to match the CN of 'snitest.com.crt'. The test
// verifies that traefik presents the correct certificate.
func (s *HTTPSSuite) TestWithSNIConfigHandshake(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/https/https_sni.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",
}
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"))
}
// TestWithSNIConfigRoute involves a client sending HTTPS requests with
// SNI hostnames of "snitest.org" and "snitest.com". The test verifies
// that traefik routes the requests to the expected backends.
func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/https/https_sni.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
backend1 := startTestServer("9010", 204)
backend2 := startTestServer("9020", 205)
defer backend1.Close()
defer backend2.Close()
time.Sleep(2000 * time.Millisecond)
tr1 := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
ServerName: "snitest.com",
},
}
tr2 := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
ServerName: "snitest.org",
},
}
client := &http.Client{Transport: tr1}
req, _ := http.NewRequest("GET", "https://127.0.0.1:4443/", nil)
req.Host = "snitest.com"
req.Header.Set("Host", "snitest.com")
req.Header.Set("Accept", "*/*")
resp, err := client.Do(req)
c.Assert(err, checker.IsNil)
// Expected a 204 (from backend1)
c.Assert(resp.StatusCode, checker.Equals, 204)
client = &http.Client{Transport: tr2}
req, _ = http.NewRequest("GET", "https://127.0.0.1:4443/", nil)
req.Host = "snitest.org"
req.Header.Set("Host", "snitest.org")
req.Header.Set("Accept", "*/*")
resp, err = client.Do(req)
c.Assert(err, checker.IsNil)
// Expected a 205 (from backend2)
c.Assert(resp.StatusCode, checker.Equals, 205)
}
func startTestServer(port string, statusCode int) (ts *httptest.Server) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(statusCode)
})
listener, err := net.Listen("tcp", "127.0.0.1:"+port)
if err != nil {
panic(err)
}
ts = &httptest.Server{
Listener: listener,
Config: &http.Server{Handler: handler},
}
ts.Start()
return
}

View File

@@ -9,14 +9,12 @@ import (
"path/filepath"
"testing"
"text/template"
"time"
"github.com/docker/libcompose/docker"
"github.com/docker/libcompose/project"
"github.com/emilevauge/traefik/integration/utils"
"github.com/containous/traefik/integration/utils"
"github.com/go-check/check"
compose "github.com/vdemeester/libkermit/compose/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
func Test(t *testing.T) {
@@ -25,113 +23,32 @@ func Test(t *testing.T) {
func init() {
check.Suite(&SimpleSuite{})
check.Suite(&HTTPSSuite{})
check.Suite(&FileSuite{})
check.Suite(&DockerSuite{})
check.Suite(&ConsulSuite{})
check.Suite(&ConsulCatalogSuite{})
check.Suite(&EtcdSuite{})
check.Suite(&MarathonSuite{})
}
var traefikBinary = "../dist/traefik"
// SimpleSuite
type SimpleSuite struct{ BaseSuite }
// File test suites
type FileSuite struct{ BaseSuite }
func (s *FileSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "file")
s.composeProject.Up()
}
// Docker test suites
type DockerSuite struct{ BaseSuite }
func (s *DockerSuite) SetUpSuite(c *check.C) {
// Make sure we can speak to docker
}
func (s *DockerSuite) TearDownSuite(c *check.C) {
// Clean the mess
}
// Consul test suites (using libcompose)
type ConsulSuite struct{ BaseSuite }
func (s *ConsulSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "consul")
}
// Marathon test suites (using libcompose)
type MarathonSuite struct{ BaseSuite }
func (s *MarathonSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "marathon")
}
type BaseSuite struct {
composeProject *project.Project
listenChan chan project.ProjectEvent
started chan bool
stopped chan bool
deleted chan bool
composeProject *compose.Project
}
func (s *BaseSuite) TearDownSuite(c *check.C) {
// shutdown and delete compose project
if s.composeProject != nil {
s.composeProject.Down()
// Waiting for libcompose#55 to be merged
// <-s.stopped
time.Sleep(2 * time.Second)
s.composeProject.Delete()
// Waiting for libcompose#55 to be merged
// <-s.deleted
time.Sleep(2 * time.Second)
s.composeProject.Stop(c)
}
}
func (s *BaseSuite) createComposeProject(c *check.C, name string) {
composeProject, err := docker.NewProject(&docker.Context{
Context: project.Context{
ComposeFile: fmt.Sprintf("resources/compose/%s.yml", name),
ProjectName: fmt.Sprintf("integration-test-%s", name),
},
})
c.Assert(err, checker.IsNil)
s.composeProject = composeProject
s.listenChan = make(chan project.ProjectEvent)
go s.startListening(c)
composeProject.AddListener(s.listenChan)
composeProject.Start()
// FIXME Wait for compose to start
// Waiting for libcompose#55 to be merged
// <-s.started
time.Sleep(2 * time.Second)
}
func (s *BaseSuite) startListening(c *check.C) {
for event := range s.listenChan {
// FIXME Remove this when it's working (libcompose#55)
// fmt.Fprintf(os.Stdout, "Event: %s (%v)\n", event.Event, event)
// FIXME Add a timeout on event
if event.Event == project.PROJECT_UP_DONE {
s.started <- true
}
if event.Event == project.PROJECT_DOWN_DONE {
s.stopped <- true
}
if event.Event == project.PROJECT_DELETE_DONE {
s.deleted <- true
}
}
projectName := fmt.Sprintf("integration-test-%s", name)
composeFile := fmt.Sprintf("resources/compose/%s.yml", name)
s.composeProject = compose.CreateProject(c, projectName, composeFile)
}
func (s *BaseSuite) traefikCmd(c *check.C, args ...string) (*exec.Cmd, string) {

View File

@@ -5,23 +5,29 @@ import (
"os/exec"
"time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// Marathon test suites (using libcompose)
type MarathonSuite struct{ BaseSuite }
func (s *MarathonSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "marathon")
}
func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) {
cmd := exec.Command(traefikBinary, "fixtures/consul/simple.toml")
cmd := exec.Command(traefikBinary, "--configFile=fixtures/marathon/simple.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
time.Sleep(100 * time.Millisecond)
time.Sleep(500 * time.Millisecond)
// TODO validate : run on 80
resp, err := http.Get("http://127.0.0.1/")
resp, err := http.Get("http://127.0.0.1:8000/")
// Expected a 404 as we did not comfigure anything
// Expected a 404 as we did not configure anything
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
killErr := cmd.Process.Kill()
c.Assert(killErr, checker.IsNil)
}

View File

@@ -0,0 +1,17 @@
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"
nginx:
image: nginx
ports:
- "8881:80"

View File

@@ -0,0 +1,30 @@
etcd1:
image: quay.io/coreos/etcd:v2.2.0
net: "host"
command: >
--name etcd1
--listen-peer-urls http://localhost:7001
--listen-client-urls http://localhost:4001
--initial-advertise-peer-urls http://localhost:7001
--advertise-client-urls http://localhost:4001
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
etcd2:
image: quay.io/coreos/etcd:v2.2.0
net: "host"
command: >
--name etcd2
--listen-peer-urls http://localhost:7002
--listen-client-urls http://localhost:4002
--initial-advertise-peer-urls http://localhost:7002
--advertise-client-urls http://localhost:4002
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003
etcd3:
image: quay.io/coreos/etcd:v2.2.0
net: "host"
command: >
--name etcd3
--listen-peer-urls http://localhost:7003
--listen-client-urls http://localhost:4003
--initial-advertise-peer-urls http://localhost:7003
--advertise-client-urls http://localhost:4003
--initial-cluster etcd1=http://localhost:7001,etcd2=http://localhost:7002,etcd3=http://localhost:7003

View File

@@ -3,7 +3,7 @@ zk:
net: host
environment:
ZK_CONFIG: tickTime=2000,initLimit=10,syncLimit=5,maxClientCnxns=128,forceSync=no,clientPort=2181
ZK_ID: 1
ZK_ID: " 1"
master:
image: mesosphere/mesos-master:0.23.0-1.0.ubuntu1404
@@ -12,7 +12,7 @@ master:
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_QUORUM: " 1"
MESOS_CLUSTER: docker-compose
MESOS_WORK_DIR: /var/lib/mesos

195
kv.go
View File

@@ -1,195 +0,0 @@
/*
Copyright
*/
package main
import (
"bytes"
"github.com/docker/libkv"
"github.com/docker/libkv/store/boltdb"
"github.com/docker/libkv/store/consul"
"github.com/docker/libkv/store/etcd"
"github.com/docker/libkv/store/zookeeper"
"strings"
"text/template"
"errors"
"github.com/BurntSushi/toml"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/docker/libkv/store"
"time"
)
type KvProvider struct {
Watch bool
Endpoint string
Prefix string
Filename string
StoreType store.Backend
kvclient store.Store
}
func NewConsulProvider(provider *ConsulProvider) *KvProvider {
kvProvider := new(KvProvider)
kvProvider.Watch = provider.Watch
kvProvider.Endpoint = provider.Endpoint
kvProvider.Prefix = provider.Prefix
kvProvider.Filename = provider.Filename
kvProvider.StoreType = store.CONSUL
return kvProvider
}
func NewEtcdProvider(provider *EtcdProvider) *KvProvider {
kvProvider := new(KvProvider)
kvProvider.Watch = provider.Watch
kvProvider.Endpoint = provider.Endpoint
kvProvider.Prefix = provider.Prefix
kvProvider.Filename = provider.Filename
kvProvider.StoreType = store.ETCD
return kvProvider
}
func NewZkProvider(provider *ZookepperProvider) *KvProvider {
kvProvider := new(KvProvider)
kvProvider.Watch = provider.Watch
kvProvider.Endpoint = provider.Endpoint
kvProvider.Prefix = provider.Prefix
kvProvider.Filename = provider.Filename
kvProvider.StoreType = store.ZK
return kvProvider
}
func NewBoltDbProvider(provider *BoltDbProvider) *KvProvider {
kvProvider := new(KvProvider)
kvProvider.Watch = provider.Watch
kvProvider.Endpoint = provider.Endpoint
kvProvider.Prefix = provider.Prefix
kvProvider.Filename = provider.Filename
kvProvider.StoreType = store.BOLTDB
return kvProvider
}
func (provider *KvProvider) provide(configurationChan chan<- configMessage) error {
switch provider.StoreType {
case store.CONSUL:
consul.Register()
case store.ETCD:
etcd.Register()
case store.ZK:
zookeeper.Register()
case store.BOLTDB:
boltdb.Register()
default:
return errors.New("Invalid kv store: " + string(provider.StoreType))
}
kv, err := libkv.NewStore(
provider.StoreType,
[]string{provider.Endpoint},
&store.Config{
ConnectionTimeout: 30 * time.Second,
},
)
if err != nil {
return err
}
if _, err := kv.List(""); err != nil {
return err
}
provider.kvclient = kv
if provider.Watch {
stopCh := make(chan struct{})
chanKeys, err := kv.WatchTree(provider.Prefix, stopCh)
if err != nil {
return err
}
go func() {
for {
<-chanKeys
configuration := provider.loadConfig()
if configuration != nil {
configurationChan <- configMessage{string(provider.StoreType), configuration}
}
defer close(stopCh)
}
}()
}
configuration := provider.loadConfig()
configurationChan <- configMessage{string(provider.StoreType), configuration}
return nil
}
func (provider *KvProvider) loadConfig() *Configuration {
configuration := new(Configuration)
templateObjects := struct {
Prefix string
}{
provider.Prefix,
}
var KvFuncMap = template.FuncMap{
"List": func(keys ...string) []string {
joinedKeys := strings.Join(keys, "")
keysPairs, err := provider.kvclient.List(joinedKeys)
if err != nil {
log.Error("Error getting keys: ", joinedKeys, err)
return nil
}
directoryKeys := make(map[string]string)
for _, key := range keysPairs {
directory := strings.Split(strings.TrimPrefix(key.Key, strings.TrimPrefix(joinedKeys, "/")), "/")[0]
directoryKeys[directory] = joinedKeys + directory
}
return fun.Values(directoryKeys).([]string)
},
"Get": func(keys ...string) string {
joinedKeys := strings.Join(keys, "")
keyPair, err := provider.kvclient.Get(joinedKeys)
if err != nil {
log.Debug("Error getting key: ", joinedKeys, err)
return ""
} else if keyPair == nil {
return ""
}
return string(keyPair.Value)
},
"Last": func(key string) string {
splittedKey := strings.Split(key, "/")
return splittedKey[len(splittedKey)-1]
},
}
tmpl := template.New(provider.Filename).Funcs(KvFuncMap)
if len(provider.Filename) > 0 {
_, err := tmpl.ParseFiles(provider.Filename)
if err != nil {
log.Error("Error reading file", err)
return nil
}
} else {
buf, err := Asset("providerTemplates/kv.tmpl")
if err != nil {
log.Error("Error reading file", err)
}
_, err = tmpl.Parse(string(buf))
if err != nil {
log.Error("Error reading file", err)
return nil
}
}
var buffer bytes.Buffer
err := tmpl.Execute(&buffer, templateObjects)
if err != nil {
log.Error("Error with kv template:", err)
return nil
}
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
log.Error("Error creating kv configuration:", err)
log.Error(buffer.String())
return nil
}
return configuration
}

View File

@@ -1,197 +0,0 @@
package main
import (
"bytes"
"strconv"
"strings"
"text/template"
"github.com/BurntSushi/toml"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/gambol99/go-marathon"
)
type MarathonProvider struct {
Watch bool
Endpoint string
marathonClient marathon.Marathon
Domain string
Filename string
NetworkInterface string
}
var MarathonFuncMap = template.FuncMap{
"getPort": func(task marathon.Task) string {
for _, port := range task.Ports {
return strconv.Itoa(port)
}
return ""
},
"getHost": func(application marathon.Application) string {
for key, value := range application.Labels {
if key == "traefik.host" {
return value
}
}
return strings.Replace(application.ID, "/", "", 1)
},
"getWeight": func(application marathon.Application) string {
for key, value := range application.Labels {
if key == "traefik.weight" {
return value
}
}
return "0"
},
"getPrefixes": func(application marathon.Application) ([]string, error) {
for key, value := range application.Labels {
if key == "traefik.prefixes" {
return strings.Split(value, ","), nil
}
}
return []string{}, nil
},
"replace": func(s1 string, s2 string, s3 string) string {
return strings.Replace(s3, s1, s2, -1)
},
}
func (provider *MarathonProvider) Provide(configurationChan chan<- configMessage) error {
config := marathon.NewDefaultConfig()
config.URL = provider.Endpoint
config.EventsInterface = provider.NetworkInterface
client, err := marathon.NewClient(config)
if err != nil {
log.Errorf("Failed to create a client for marathon, error: %s", err)
return err
}
provider.marathonClient = client
update := make(marathon.EventsChannel, 5)
if provider.Watch {
if err := client.AddEventsListener(update, marathon.EVENTS_APPLICATIONS); err != nil {
log.Errorf("Failed to register for subscriptions, %s", err)
} else {
go func() {
for {
event := <-update
log.Debug("Marathon event receveived", event)
configuration := provider.loadMarathonConfig()
if configuration != nil {
configurationChan <- configMessage{"marathon", configuration}
}
}
}()
}
}
configuration := provider.loadMarathonConfig()
configurationChan <- configMessage{"marathon", configuration}
return nil
}
func (provider *MarathonProvider) loadMarathonConfig() *Configuration {
configuration := new(Configuration)
applications, err := provider.marathonClient.Applications(nil)
if err != nil {
log.Errorf("Failed to create a client for marathon, error: %s", err)
return nil
}
tasks, err := provider.marathonClient.AllTasks()
if err != nil {
log.Errorf("Failed to create a client for marathon, error: %s", err)
return nil
}
//filter tasks
filteredTasks := fun.Filter(func(task marathon.Task) bool {
if len(task.Ports) == 0 {
log.Debug("Filtering marathon task without port", task.AppID)
return false
}
application := getApplication(task, applications.Apps)
if application == nil {
log.Errorf("Unable to get marathon application from task %s", task.AppID)
return false
}
_, err := strconv.Atoi(application.Labels["traefik.port"])
if len(application.Ports) > 1 && err != nil {
log.Debug("Filtering marathon task with more than 1 port and no traefik.port label", task.AppID)
return false
}
if application.Labels["traefik.enable"] == "false" {
log.Debug("Filtering disabled marathon task", task.AppID)
return false
}
return true
}, tasks.Tasks).([]marathon.Task)
//filter apps
filteredApps := fun.Filter(func(app marathon.Application) bool {
//get ports from app tasks
if !fun.Exists(func(task marathon.Task) bool {
if task.AppID == app.ID {
return true
}
return false
}, filteredTasks) {
return false
}
return true
}, applications.Apps).([]marathon.Application)
templateObjects := struct {
Applications []marathon.Application
Tasks []marathon.Task
Domain string
}{
filteredApps,
filteredTasks,
provider.Domain,
}
tmpl := template.New(provider.Filename).Funcs(MarathonFuncMap)
if len(provider.Filename) > 0 {
_, err := tmpl.ParseFiles(provider.Filename)
if err != nil {
log.Error("Error reading file", err)
return nil
}
} else {
buf, err := Asset("providerTemplates/marathon.tmpl")
if err != nil {
log.Error("Error reading file", err)
}
_, err = tmpl.Parse(string(buf))
if err != nil {
log.Error("Error reading file", err)
return nil
}
}
var buffer bytes.Buffer
err = tmpl.Execute(&buffer, templateObjects)
if err != nil {
log.Error("Error with marathon template:", err)
return nil
}
if _, err := toml.Decode(buffer.String(), configuration); err != nil {
log.Error("Error creating marathon configuration:", err)
return nil
}
return configuration
}
func getApplication(task marathon.Task, apps []marathon.Application) *marathon.Application {
for _, application := range apps {
if application.ID == task.AppID {
return &application
}
}
return nil
}

View File

@@ -1,18 +1,17 @@
/*
Copyright
*/
package middlewares
import (
"net/http"
"github.com/mailgun/oxy/cbreaker"
"github.com/containous/oxy/cbreaker"
)
// CircuitBreaker holds the oxy circuit breaker.
type CircuitBreaker struct {
circuitBreaker *cbreaker.CircuitBreaker
}
// NewCircuitBreaker returns a new CircuitBreaker.
func NewCircuitBreaker(next http.Handler, expression string, options ...cbreaker.CircuitBreakerOption) *CircuitBreaker {
circuitBreaker, _ := cbreaker.New(next, expression, options...)
return &CircuitBreaker{circuitBreaker}

View File

@@ -0,0 +1,40 @@
package middlewares
import (
"github.com/gorilla/mux"
"net/http"
"sync"
)
// HandlerSwitcher allows hot switching of http.ServeMux
type HandlerSwitcher struct {
handler *mux.Router
handlerLock *sync.Mutex
}
// NewHandlerSwitcher builds a new instance of HandlerSwitcher
func NewHandlerSwitcher(newHandler *mux.Router) (hs *HandlerSwitcher) {
return &HandlerSwitcher{
handler: newHandler,
handlerLock: &sync.Mutex{},
}
}
func (hs *HandlerSwitcher) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
hs.handlerLock.Lock()
handlerBackup := hs.handler
hs.handlerLock.Unlock()
handlerBackup.ServeHTTP(rw, r)
}
// GetHandler returns the current http.ServeMux
func (hs *HandlerSwitcher) GetHandler() (newHandler *mux.Router) {
return hs.handler
}
// UpdateHandler safely updates the current http.ServeMux with a new one
func (hs *HandlerSwitcher) UpdateHandler(newHandler *mux.Router) {
hs.handlerLock.Lock()
hs.handler = newHandler
defer hs.handlerLock.Unlock()
}

View File

@@ -1,6 +1,3 @@
/*
Copyright
*/
package middlewares
import (
@@ -16,7 +13,7 @@ type Logger struct {
file *os.File
}
// NewLogger returns a new Logger instance
// NewLogger returns a new Logger instance.
func NewLogger(file string) *Logger {
if len(file) > 0 {
fi, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
@@ -36,6 +33,7 @@ func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.Ha
}
}
// Close closes the logger (i.e. the file).
func (l *Logger) Close() {
l.file.Close()
}

31
middlewares/rewrite.go Normal file
View File

@@ -0,0 +1,31 @@
package middlewares
import (
log "github.com/Sirupsen/logrus"
"github.com/vulcand/vulcand/plugin/rewrite"
"net/http"
)
// Rewrite is a middleware that allows redirections
type Rewrite struct {
rewriter *rewrite.Rewrite
}
// NewRewrite creates a Rewrite middleware
func NewRewrite(regex, replacement string, redirect bool) (*Rewrite, error) {
rewriter, err := rewrite.NewRewrite(regex, replacement, false, redirect)
if err != nil {
return nil, err
}
return &Rewrite{rewriter: rewriter}, nil
}
//
func (rewrite *Rewrite) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
handler, err := rewrite.rewriter.NewHandler(next)
if err != nil {
log.Error("Error in rewrite middleware ", err)
return
}
handler.ServeHTTP(rw, r)
}

View File

@@ -1,6 +1,3 @@
/*
Copyright
*/
package middlewares
import (
@@ -11,10 +8,12 @@ import (
"github.com/gorilla/mux"
)
// Routes holds the gorilla mux routes (for the API & co).
type Routes struct {
router *mux.Router
}
// NewRoutes return a Routes based on the given router.
func NewRoutes(router *mux.Router) *Routes {
return &Routes{router}
}

View File

@@ -0,0 +1,29 @@
package middlewares
import (
"net/http"
"strings"
)
// StripPrefix is a middleware used to strip prefix from an URL request
type StripPrefix struct {
Handler http.Handler
Prefixes []string
}
func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, prefix := range s.Prefixes {
if p := strings.TrimPrefix(r.URL.Path, strings.TrimSpace(prefix)); len(p) < len(r.URL.Path) {
r.URL.Path = p
r.RequestURI = r.URL.RequestURI()
s.Handler.ServeHTTP(w, r)
return
}
}
http.NotFound(w, r)
}
// SetHandler sets handler
func (s *StripPrefix) SetHandler(Handler http.Handler) {
s.Handler = Handler
}

49
mkdocs.yml Normal file
View File

@@ -0,0 +1,49 @@
site_name: Traefik
site_description: Traefik Documentation
site_author: containo.us
site_url: https://docs.traefik.io
repo_name: 'GitHub'
repo_url: 'https://github.com/containous/traefik'
# Documentation and theme
docs_dir: 'docs'
theme: united
# theme: readthedocs
# theme: 'material'
# theme: bootstrap
site_favicon: 'img/traefik.icon.png'
# Copyright
copyright: Copyright (c) 2016 Containous SAS
# Options
extra:
# version: 0.2.2
logo: img/traefik.logo.png
# author:
# twitter: traefikproxy
palette:
primary: 'blue'
accent: 'light blue'
i18n:
prev: 'Previous'
next: 'Next'
markdown_extensions:
# - codehilite(css_class=code)
- admonition
# - toc:
# permalink: '##'
# - fenced_code
extra_css:
- css/traefik.css
# Page tree
pages:
- Getting Started: index.md
- Basics: basics.md
- traefik.toml: toml.md
- Benchmarks: benchmarks.md

829
mocks/Marathon.go Normal file
View File

@@ -0,0 +1,829 @@
package mocks
import "github.com/gambol99/go-marathon"
import "github.com/stretchr/testify/mock"
import "net/url"
import "time"
// Marathon is a mock of marathon.Marathon
type Marathon struct {
mock.Mock
}
// ListApplications provides a mock function with given fields: _a0
func (_m *Marathon) ListApplications(_a0 url.Values) ([]string, error) {
ret := _m.Called(_a0)
var r0 []string
if rf, ok := ret.Get(0).(func(url.Values) []string); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(url.Values) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ApplicationVersions provides a mock function with given fields: name
func (_m *Marathon) ApplicationVersions(name string) (*marathon.ApplicationVersions, error) {
ret := _m.Called(name)
var r0 *marathon.ApplicationVersions
if rf, ok := ret.Get(0).(func(string) *marathon.ApplicationVersions); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.ApplicationVersions)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// HasApplicationVersion provides a mock function with given fields: name, version
func (_m *Marathon) HasApplicationVersion(name string, version string) (bool, error) {
ret := _m.Called(name, version)
var r0 bool
if rf, ok := ret.Get(0).(func(string, string) bool); ok {
r0 = rf(name, version)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(name, version)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SetApplicationVersion provides a mock function with given fields: name, version
func (_m *Marathon) SetApplicationVersion(name string, version *marathon.ApplicationVersion) (*marathon.DeploymentID, error) {
ret := _m.Called(name, version)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, *marathon.ApplicationVersion) *marathon.DeploymentID); ok {
r0 = rf(name, version)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *marathon.ApplicationVersion) error); ok {
r1 = rf(name, version)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ApplicationOK provides a mock function with given fields: name
func (_m *Marathon) ApplicationOK(name string) (bool, error) {
ret := _m.Called(name)
var r0 bool
if rf, ok := ret.Get(0).(func(string) bool); ok {
r0 = rf(name)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateApplication provides a mock function with given fields: application
func (_m *Marathon) CreateApplication(application *marathon.Application) (*marathon.Application, error) {
ret := _m.Called(application)
var r0 *marathon.Application
if rf, ok := ret.Get(0).(func(*marathon.Application) *marathon.Application); ok {
r0 = rf(application)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Application)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*marathon.Application) error); ok {
r1 = rf(application)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeleteApplication provides a mock function with given fields: name
func (_m *Marathon) DeleteApplication(name string) (*marathon.DeploymentID, error) {
ret := _m.Called(name)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string) *marathon.DeploymentID); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateApplication provides a mock function with given fields: application
func (_m *Marathon) UpdateApplication(application *marathon.Application) (*marathon.DeploymentID, error) {
ret := _m.Called(application)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(*marathon.Application) *marathon.DeploymentID); ok {
r0 = rf(application)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*marathon.Application) error); ok {
r1 = rf(application)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ApplicationDeployments provides a mock function with given fields: name
func (_m *Marathon) ApplicationDeployments(name string) ([]*marathon.DeploymentID, error) {
ret := _m.Called(name)
var r0 []*marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string) []*marathon.DeploymentID); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ScaleApplicationInstances provides a mock function with given fields: name, instances, force
func (_m *Marathon) ScaleApplicationInstances(name string, instances int, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(name, instances, force)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, int, bool) *marathon.DeploymentID); ok {
r0 = rf(name, instances, force)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, int, bool) error); ok {
r1 = rf(name, instances, force)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RestartApplication provides a mock function with given fields: name, force
func (_m *Marathon) RestartApplication(name string, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(name, force)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, bool) *marathon.DeploymentID); ok {
r0 = rf(name, force)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, bool) error); ok {
r1 = rf(name, force)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Applications provides a mock function with given fields: _a0
func (_m *Marathon) Applications(_a0 url.Values) (*marathon.Applications, error) {
ret := _m.Called(_a0)
var r0 *marathon.Applications
if rf, ok := ret.Get(0).(func(url.Values) *marathon.Applications); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Applications)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(url.Values) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Application provides a mock function with given fields: name
func (_m *Marathon) Application(name string) (*marathon.Application, error) {
ret := _m.Called(name)
var r0 *marathon.Application
if rf, ok := ret.Get(0).(func(string) *marathon.Application); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Application)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// WaitOnApplication provides a mock function with given fields: name, timeout
func (_m *Marathon) WaitOnApplication(name string, timeout time.Duration) error {
ret := _m.Called(name, timeout)
var r0 error
if rf, ok := ret.Get(0).(func(string, time.Duration) error); ok {
r0 = rf(name, timeout)
} else {
r0 = ret.Error(0)
}
return r0
}
// Tasks provides a mock function with given fields: application
func (_m *Marathon) Tasks(application string) (*marathon.Tasks, error) {
ret := _m.Called(application)
var r0 *marathon.Tasks
if rf, ok := ret.Get(0).(func(string) *marathon.Tasks); ok {
r0 = rf(application)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Tasks)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(application)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AllTasks provides a mock function with given fields: opts
func (_m *Marathon) AllTasks(opts *marathon.AllTasksOpts) (*marathon.Tasks, error) {
ret := _m.Called(opts)
var r0 *marathon.Tasks
if rf, ok := ret.Get(0).(func(*marathon.AllTasksOpts) *marathon.Tasks); ok {
r0 = rf(opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Tasks)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*marathon.AllTasksOpts) error); ok {
r1 = rf(opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// TaskEndpoints provides a mock function with given fields: name, port, healthCheck
func (_m *Marathon) TaskEndpoints(name string, port int, healthCheck bool) ([]string, error) {
ret := _m.Called(name, port, healthCheck)
var r0 []string
if rf, ok := ret.Get(0).(func(string, int, bool) []string); ok {
r0 = rf(name, port, healthCheck)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, int, bool) error); ok {
r1 = rf(name, port, healthCheck)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// KillApplicationTasks provides a mock function with given fields: applicationID, opts
func (_m *Marathon) KillApplicationTasks(applicationID string, opts *marathon.KillApplicationTasksOpts) (*marathon.Tasks, error) {
ret := _m.Called(applicationID, opts)
var r0 *marathon.Tasks
if rf, ok := ret.Get(0).(func(string, *marathon.KillApplicationTasksOpts) *marathon.Tasks); ok {
r0 = rf(applicationID, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Tasks)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *marathon.KillApplicationTasksOpts) error); ok {
r1 = rf(applicationID, opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// KillTask provides a mock function with given fields: taskID, opts
func (_m *Marathon) KillTask(taskID string, opts *marathon.KillTaskOpts) (*marathon.Task, error) {
ret := _m.Called(taskID, opts)
var r0 *marathon.Task
if rf, ok := ret.Get(0).(func(string, *marathon.KillTaskOpts) *marathon.Task); ok {
r0 = rf(taskID, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Task)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *marathon.KillTaskOpts) error); ok {
r1 = rf(taskID, opts)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// KillTasks provides a mock function with given fields: taskIDs, opts
func (_m *Marathon) KillTasks(taskIDs []string, opts *marathon.KillTaskOpts) error {
ret := _m.Called(taskIDs, opts)
var r0 error
if rf, ok := ret.Get(0).(func([]string, *marathon.KillTaskOpts) error); ok {
r0 = rf(taskIDs, opts)
} else {
r0 = ret.Error(0)
}
return r0
}
// Groups provides a mock function with given fields:
func (_m *Marathon) Groups() (*marathon.Groups, error) {
ret := _m.Called()
var r0 *marathon.Groups
if rf, ok := ret.Get(0).(func() *marathon.Groups); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Groups)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Group provides a mock function with given fields: name
func (_m *Marathon) Group(name string) (*marathon.Group, error) {
ret := _m.Called(name)
var r0 *marathon.Group
if rf, ok := ret.Get(0).(func(string) *marathon.Group); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Group)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateGroup provides a mock function with given fields: group
func (_m *Marathon) CreateGroup(group *marathon.Group) error {
ret := _m.Called(group)
var r0 error
if rf, ok := ret.Get(0).(func(*marathon.Group) error); ok {
r0 = rf(group)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteGroup provides a mock function with given fields: name
func (_m *Marathon) DeleteGroup(name string) (*marathon.DeploymentID, error) {
ret := _m.Called(name)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string) *marathon.DeploymentID); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateGroup provides a mock function with given fields: id, group
func (_m *Marathon) UpdateGroup(id string, group *marathon.Group) (*marathon.DeploymentID, error) {
ret := _m.Called(id, group)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, *marathon.Group) *marathon.DeploymentID); ok {
r0 = rf(id, group)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *marathon.Group) error); ok {
r1 = rf(id, group)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// HasGroup provides a mock function with given fields: name
func (_m *Marathon) HasGroup(name string) (bool, error) {
ret := _m.Called(name)
var r0 bool
if rf, ok := ret.Get(0).(func(string) bool); ok {
r0 = rf(name)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// WaitOnGroup provides a mock function with given fields: name, timeout
func (_m *Marathon) WaitOnGroup(name string, timeout time.Duration) error {
ret := _m.Called(name, timeout)
var r0 error
if rf, ok := ret.Get(0).(func(string, time.Duration) error); ok {
r0 = rf(name, timeout)
} else {
r0 = ret.Error(0)
}
return r0
}
// Deployments provides a mock function with given fields:
func (_m *Marathon) Deployments() ([]*marathon.Deployment, error) {
ret := _m.Called()
var r0 []*marathon.Deployment
if rf, ok := ret.Get(0).(func() []*marathon.Deployment); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*marathon.Deployment)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeleteDeployment provides a mock function with given fields: id, force
func (_m *Marathon) DeleteDeployment(id string, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(id, force)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(string, bool) *marathon.DeploymentID); ok {
r0 = rf(id, force)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, bool) error); ok {
r1 = rf(id, force)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// HasDeployment provides a mock function with given fields: id
func (_m *Marathon) HasDeployment(id string) (bool, error) {
ret := _m.Called(id)
var r0 bool
if rf, ok := ret.Get(0).(func(string) bool); ok {
r0 = rf(id)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(id)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// WaitOnDeployment provides a mock function with given fields: id, timeout
func (_m *Marathon) WaitOnDeployment(id string, timeout time.Duration) error {
ret := _m.Called(id, timeout)
var r0 error
if rf, ok := ret.Get(0).(func(string, time.Duration) error); ok {
r0 = rf(id, timeout)
} else {
r0 = ret.Error(0)
}
return r0
}
// Subscriptions provides a mock function with given fields:
func (_m *Marathon) Subscriptions() (*marathon.Subscriptions, error) {
ret := _m.Called()
var r0 *marathon.Subscriptions
if rf, ok := ret.Get(0).(func() *marathon.Subscriptions); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Subscriptions)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AddEventsListener provides a mock function with given fields: channel, filter
func (_m *Marathon) AddEventsListener(channel marathon.EventsChannel, filter int) error {
ret := _m.Called(channel, filter)
var r0 error
if rf, ok := ret.Get(0).(func(marathon.EventsChannel, int) error); ok {
r0 = rf(channel, filter)
} else {
r0 = ret.Error(0)
}
return r0
}
// RemoveEventsListener provides a mock function with given fields: channel
func (_m *Marathon) RemoveEventsListener(channel marathon.EventsChannel) {
_m.Called(channel)
}
// Unsubscribe provides a mock function with given fields: _a0
func (_m *Marathon) Unsubscribe(_a0 string) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetMarathonURL provides a mock function with given fields:
func (_m *Marathon) GetMarathonURL() string {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// Ping provides a mock function with given fields:
func (_m *Marathon) Ping() (bool, error) {
ret := _m.Called()
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Info provides a mock function with given fields:
func (_m *Marathon) Info() (*marathon.Info, error) {
ret := _m.Called()
var r0 *marathon.Info
if rf, ok := ret.Get(0).(func() *marathon.Info); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Info)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Leader provides a mock function with given fields:
func (_m *Marathon) Leader() (string, error) {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AbdicateLeader provides a mock function with given fields:
func (_m *Marathon) AbdicateLeader() (string, error) {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@@ -1,5 +0,0 @@
package main
type Provider interface {
Provide(configurationChan chan<- configMessage) error
}

20
provider/boltdb.go Normal file
View File

@@ -0,0 +1,20 @@
package provider
import (
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/boltdb"
)
// BoltDb holds configurations of the BoltDb provider.
type BoltDb struct {
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) error {
provider.storeType = store.BOLTDB
boltdb.Register()
return provider.provide(configurationChan)
}

20
provider/consul.go Normal file
View File

@@ -0,0 +1,20 @@
package provider
import (
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/consul"
)
// Consul holds configurations of the Consul provider.
type Consul struct {
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) error {
provider.storeType = store.CONSUL
consul.Register()
return provider.provide(configurationChan)
}

200
provider/consul_catalog.go Normal file
View File

@@ -0,0 +1,200 @@
package provider
import (
"errors"
"strings"
"text/template"
"time"
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/hashicorp/consul/api"
)
const (
// DefaultWatchWaitTime is the duration to wait when polling consul
DefaultWatchWaitTime = 15 * time.Second
)
// ConsulCatalog holds configurations of the Consul catalog provider.
type ConsulCatalog struct {
BaseProvider `mapstructure:",squash"`
Endpoint string
Domain string
client *api.Client
}
type catalogUpdate struct {
Service string
Nodes []*api.ServiceEntry
}
func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[string][]string {
watchCh := make(chan map[string][]string)
catalog := provider.client.Catalog()
safe.Go(func() {
defer close(watchCh)
opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
for {
select {
case <-stopCh:
return
default:
}
data, meta, err := catalog.Services(opts)
if err != nil {
log.WithError(err).Errorf("Failed to list services")
return
}
// If LastIndex didn't change then it means `Get` returned
// because of the WaitTime and the key didn't changed.
if opts.WaitIndex == meta.LastIndex {
continue
}
opts.WaitIndex = meta.LastIndex
if data != nil {
watchCh <- data
}
}
})
return watchCh
}
func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, error) {
health := provider.client.Health()
opts := &api.QueryOptions{}
data, _, err := health.Service(service, "", true, opts)
if err != nil {
log.WithError(err).Errorf("Failed to fetch details of " + service)
return catalogUpdate{}, err
}
return catalogUpdate{
Service: service,
Nodes: data,
}, nil
}
func (provider *ConsulCatalog) getBackend(node *api.ServiceEntry) string {
return strings.ToLower(node.Service.Service)
}
func (provider *ConsulCatalog) getFrontendValue(service string) string {
return "Host:" + service + "." + provider.Domain
}
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
var FuncMap = template.FuncMap{
"getBackend": provider.getBackend,
"getFrontendValue": provider.getFrontendValue,
"replace": replace,
}
allNodes := []*api.ServiceEntry{}
serviceNames := []string{}
for _, info := range catalog {
if len(info.Nodes) > 0 {
serviceNames = append(serviceNames, info.Service)
allNodes = append(allNodes, info.Nodes...)
}
}
templateObjects := struct {
Services []string
Nodes []*api.ServiceEntry
}{
Services: serviceNames,
Nodes: allNodes,
}
configuration, err := provider.getConfiguration("templates/consul_catalog.tmpl", FuncMap, templateObjects)
if err != nil {
log.WithError(err).Error("Failed to create config")
}
return configuration
}
func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpdate, error) {
visited := make(map[string]bool)
nodes := []catalogUpdate{}
for service := range index {
name := strings.ToLower(service)
if !strings.Contains(name, " ") && !visited[name] {
visited[name] = true
log.WithFields(log.Fields{
"service": name,
}).Debug("Fetching service")
healthy, err := provider.healthyNodes(name)
if err != nil {
return nil, err
}
nodes = append(nodes, healthy)
}
}
return nodes, nil
}
func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessage) error {
stopCh := make(chan struct{})
serviceCatalog := provider.watchServices(stopCh)
defer close(stopCh)
for {
select {
case index, ok := <-serviceCatalog:
if !ok {
return errors.New("Consul service list nil")
}
log.Debug("List of services changed")
nodes, err := provider.getNodes(index)
if err != nil {
return err
}
configuration := provider.buildConfig(nodes)
configurationChan <- types.ConfigMessage{
ProviderName: "consul_catalog",
Configuration: configuration,
}
}
}
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage) error {
config := api.DefaultConfig()
config.Address = provider.Endpoint
client, err := api.NewClient(config)
if err != nil {
return err
}
provider.client = client
safe.Go(func() {
notify := func(err error, time time.Duration) {
log.Errorf("Consul connection error %+v, retrying in %s", err, time)
}
worker := func() error {
return provider.watch(configurationChan)
}
err := backoff.RetryNotify(worker, backoff.NewExponentialBackOff(), notify)
if err != nil {
log.Fatalf("Cannot connect to consul server %+v", err)
}
})
return err
}

View File

@@ -0,0 +1,110 @@
package provider
import (
"reflect"
"testing"
"github.com/containous/traefik/types"
"github.com/hashicorp/consul/api"
)
func TestConsulCatalogGetFrontendRule(t *testing.T) {
provider := &ConsulCatalog{
Domain: "localhost",
}
services := []struct {
service string
expected string
}{
{
service: "foo",
expected: "Host:foo.localhost",
},
}
for _, e := range services {
actual := provider.getFrontendValue(e.service)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestConsulCatalogBuildConfig(t *testing.T) {
provider := &ConsulCatalog{
Domain: "localhost",
}
cases := []struct {
nodes []catalogUpdate
expectedFrontends map[string]*types.Frontend
expectedBackends map[string]*types.Backend
}{
{
nodes: []catalogUpdate{},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
nodes: []catalogUpdate{
{
Service: "test",
},
},
expectedFrontends: map[string]*types.Frontend{},
expectedBackends: map[string]*types.Backend{},
},
{
nodes: []catalogUpdate{
{
Service: "test",
Nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "test",
Address: "127.0.0.1",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
},
},
},
expectedFrontends: map[string]*types.Frontend{
"frontend-test": {
Backend: "backend-test",
Routes: map[string]types.Route{
"route-host-test": {
Rule: "Host:test.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-test": {
Servers: map[string]types.Server{
"test--127-0-0-1--80": {
URL: "http://127.0.0.1:80",
},
},
CircuitBreaker: nil,
LoadBalancer: nil,
},
},
},
}
for _, c := range cases {
actualConfig := provider.buildConfig(c.nodes)
if !reflect.DeepEqual(actualConfig.Backends, c.expectedBackends) {
t.Fatalf("expected %#v, got %#v", c.expectedBackends, actualConfig.Backends)
}
if !reflect.DeepEqual(actualConfig.Frontends, c.expectedFrontends) {
t.Fatalf("expected %#v, got %#v", c.expectedFrontends, actualConfig.Frontends)
}
}
}

322
provider/docker.go Normal file
View File

@@ -0,0 +1,322 @@
package provider
import (
"errors"
"net/http"
"strconv"
"strings"
"text/template"
"time"
"golang.org/x/net/context"
"github.com/BurntSushi/ty/fun"
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/engine-api/client"
dockertypes "github.com/docker/engine-api/types"
eventtypes "github.com/docker/engine-api/types/events"
"github.com/docker/engine-api/types/filters"
"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"
// Docker holds configurations of the Docker provider.
type Docker struct {
BaseProvider `mapstructure:",squash"`
Endpoint string
Domain string
TLS *DockerTLS
}
// DockerTLS holds TLS specific configurations
type DockerTLS struct {
CA string
Cert string
Key string
InsecureSkipVerify bool
}
func (provider *Docker) createClient() (client.APIClient, error) {
var httpClient *http.Client
httpHeaders := map[string]string{
// FIXME(vdemeester) use version here O:)
"User-Agent": "Traefik",
}
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)
if err != nil {
return nil, err
}
tr := &http.Transport{
TLSClientConfig: config,
}
proto, addr, _, err := client.ParseHost(provider.Endpoint)
if err != nil {
return nil, err
}
sockets.ConfigureTransport(tr, proto, addr)
httpClient = &http.Client{
Transport: tr,
}
}
return client.NewClient(provider.Endpoint, DockerAPIVersion, httpClient, httpHeaders)
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage) error {
safe.Go(func() {
operation := func() error {
var err error
dockerClient, err := provider.createClient()
if err != nil {
log.Errorf("Failed to create a client for docker, error: %s", err)
return err
}
version, err := dockerClient.ServerVersion(context.Background())
log.Debugf("Docker connection established with docker %s (API %s)", version.Version, version.APIVersion)
containers, err := listContainers(dockerClient)
if err != nil {
log.Errorf("Failed to list containers for docker, error %s", err)
return err
}
configuration := provider.loadDockerConfig(containers)
configurationChan <- types.ConfigMessage{
ProviderName: "docker",
Configuration: configuration,
}
if provider.Watch {
ctx, cancel := context.WithCancel(context.Background())
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(dockerClient)
if err != nil {
log.Errorf("Failed to list containers for docker, error %s", err)
// Call cancel to get out of the monitor
cancel()
}
configuration := provider.loadDockerConfig(containers)
if configuration != nil {
configurationChan <- types.ConfigMessage{
ProviderName: "docker",
Configuration: configuration,
}
}
}
eventHandler.Handle("start", startStopHandle)
eventHandler.Handle("die", startStopHandle)
errChan := events.MonitorWithHandler(ctx, dockerClient, options, eventHandler)
if err := <-errChan; err != nil {
return err
}
}
return nil
}
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)
if err != nil {
log.Fatalf("Cannot connect to docker server %+v", err)
}
})
return nil
}
func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration {
var DockerFuncMap = template.FuncMap{
"getBackend": provider.getBackend,
"getPort": provider.getPort,
"getWeight": provider.getWeight,
"getDomain": provider.getDomain,
"getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader,
"getEntryPoints": provider.getEntryPoints,
"getFrontendRule": provider.getFrontendRule,
"replace": replace,
}
// filter containers
filteredContainers := fun.Filter(containerFilter, containersInspected).([]dockertypes.ContainerJSON)
frontends := map[string][]dockertypes.ContainerJSON{}
for _, container := range filteredContainers {
frontends[provider.getFrontendName(container)] = append(frontends[provider.getFrontendName(container)], container)
}
templateObjects := struct {
Containers []dockertypes.ContainerJSON
Frontends map[string][]dockertypes.ContainerJSON
Domain string
}{
filteredContainers,
frontends,
provider.Domain,
}
configuration, err := provider.getConfiguration("templates/docker.tmpl", DockerFuncMap, templateObjects)
if err != nil {
log.Error(err)
}
return configuration
}
func containerFilter(container dockertypes.ContainerJSON) bool {
if len(container.NetworkSettings.Ports) == 0 {
log.Debugf("Filtering container without port %s", container.Name)
return false
}
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
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" {
log.Debugf("Filtering disabled container %s", container.Name)
return false
}
return true
}
func (provider *Docker) getFrontendName(container dockertypes.ContainerJSON) 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
}
// ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
if label, err := getLabel(container, "traefik.frontend.rule"); err == nil {
return label
}
return "Host:" + getEscapedName(container.Name) + "." + provider.Domain
}
func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.backend"); err == nil {
return label
}
return normalize(container.Name)
}
func (provider *Docker) getPort(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.port"); err == nil {
return label
}
for key := range container.NetworkSettings.Ports {
return key.Port()
}
return ""
}
func (provider *Docker) getWeight(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.weight"); err == nil {
return label
}
return "1"
}
func (provider *Docker) getDomain(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.domain"); err == nil {
return label
}
return provider.Domain
}
func (provider *Docker) getProtocol(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.protocol"); err == nil {
return label
}
return "http"
}
func (provider *Docker) getPassHostHeader(container dockertypes.ContainerJSON) string {
if passHostHeader, err := getLabel(container, "traefik.frontend.passHostHeader"); err == nil {
return passHostHeader
}
return "false"
}
func (provider *Docker) getEntryPoints(container dockertypes.ContainerJSON) []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 {
if key == label {
return value, nil
}
}
return "", errors.New("Label not found:" + label)
}
func getLabels(container dockertypes.ContainerJSON, labels []string) (map[string]string, error) {
var globalErr error
foundLabels := map[string]string{}
for _, label := range labels {
foundLabel, err := getLabel(container, label)
// Error out only if one of them is defined.
if err != nil {
globalErr = errors.New("Label not found: " + label)
continue
}
foundLabels[label] = foundLabel
}
return foundLabels, globalErr
}
func listContainers(dockerClient client.APIClient) ([]dockertypes.ContainerJSON, error) {
containerList, err := dockerClient.ContainerList(context.Background(), dockertypes.ContainerListOptions{})
if err != nil {
return []dockertypes.ContainerJSON{}, err
}
containersInspected := []dockertypes.ContainerJSON{}
// get inspect containers
for _, container := range containerList {
containerInspected, err := dockerClient.ContainerInspect(context.Background(), container.ID)
if err != nil {
log.Warnf("Failed to inpsect container %s, error: %s", container.ID, err)
}
containersInspected = append(containersInspected, containerInspected)
}
return containersInspected, nil
}

Some files were not shown because too many files have changed in this diff Show More