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

Compare commits

...

315 Commits

Author SHA1 Message Date
Emile Vauge
51f2433ba5 Merge pull request #695 from containous/prepare-release-v1.0.3
Prepare release v1.0.3
2016-09-22 15:00:43 +02:00
Emile Vauge
df710fc89e Prepare release v1.0.3
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-22 14:15:53 +02:00
Emile Vauge
cb8a8b5cbb Merge pull request #693 from containous/fix-health-race
Fix health race
2016-09-22 14:09:20 +02:00
Emile Vauge
7dfcdcec10 Fix health race
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-22 13:47:06 +02:00
Emile Vauge
35c74ba56c Merge pull request #585 from containous/prepare-release-1.0.2
Prepare release v1.0.2
2016-08-02 19:20:43 +02:00
Emile Vauge
89fc0d2d0e Prepare release v1.0.2
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-08-02 13:47:03 +02:00
Emile Vauge
5306981923 Merge pull request #582 from containous/fix-acme-tos
Fix ACME TOS
2016-08-02 13:41:05 +02:00
Emile Vauge
b466413b11 Fix ACME TOS
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-08-02 12:30:05 +02:00
Emile Vauge
2c411767de Merge pull request #584 from containous/bump-oxy-version
Bump oxy version, fix streaming
2016-08-02 12:23:37 +02:00
Emile Vauge
54e80492bd Bump oxy version, fix streaming
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-08-02 10:17:18 +02:00
Emile Vauge
a8d05294bc Merge pull request #542 from containous/prepare-release-1.0.1
Prepare release v1.0.1
2016-07-19 16:54:16 +02:00
Emile Vauge
1b25e492c7 Prepare release v1.0.1
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-19 16:05:03 +02:00
Emile Vauge
be8ebdba46 Merge pull request #527 from containous/add-sse-support
Add sse support
2016-07-19 11:51:21 +02:00
Emile Vauge
2d759df47a Fix basics doc
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-19 10:29:48 +02:00
Emile Vauge
d1b5cf99d0 Add GraceTimeOut support
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-18 18:01:59 +02:00
Emile Vauge
516608d883 Bump containous/oxy version with SSE support
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-18 17:59:50 +02:00
Emile Vauge
bf95e6def9 Add deploy-pr on non master branch
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-18 17:59:50 +02:00
Emile Vauge
3c5cb31775 default values logs from WARN to DEBUG
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-18 17:59:50 +02:00
Vincent Demeester
0a9070c394 Merge pull request #531 from Jsewill/patch-2
Update server.go
2016-07-14 11:28:29 +02:00
Jsewill
bd29bac716 Update server.go
Fixed a bug that caused a panic when sending multiple signals: signals are no longer sent on the server.signals channel after it has closed.
2016-07-14 03:22:18 -05:00
Emile Vauge
d42a22f446 Merge pull request #512 from containous/fix-acme-ondemand
Fix acme checkOnDemandDomain
2016-07-14 10:13:24 +02:00
Emile Vauge
24d3a698a0 Fix acme checkOnDemandDomain
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-13 19:25:47 +02:00
Emile Vauge
1eeba34806 Merge pull request #511 from errm/etcd-endpoint-default
Fix default etcd port
2016-07-13 19:23:58 +02:00
Emile Vauge
c98a561722 Fix GraceTimeOut flag description
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-13 17:49:00 +02:00
Ed Robinson
c719aa3db8 Fix default etcd port
Fixes #508
2016-07-11 13:14:43 +01:00
Emile Vauge
8f8f72fa76 Merge pull request #496 from containous/prepare-release-1.0.0
Prepare release 1.0.0
2016-07-06 01:09:33 +02:00
Emile Vauge
4ae6d42871 Add Changelog
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 21:17:30 +02:00
Emile Vauge
64243382cf Add codename
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 21:03:37 +02:00
Emile Vauge
c7acb2d2c4 Update doc on combining multiple rules and priorities
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 21:03:06 +02:00
Emile Vauge
20795cf884 Add Russell-IO and errm in maintainers
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 21:02:14 +02:00
Vincent Demeester
6b9f64a273 Merge pull request #495 from containous/fix-windows-build
Fix windows builds
2016-07-05 16:40:07 +02:00
Emile Vauge
9e270c951a Fix windows builds
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 16:02:36 +02:00
Vincent Demeester
20308dc804 Merge pull request #494 from containous/fix-docker-network-host
Fix host Docker network
2016-07-05 14:41:48 +02:00
Emile Vauge
b1ecb1f61f Use of container.HostConfig.NetworkMode to detect host mode
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 14:11:33 +02:00
Emile Vauge
6fd8979754 Remove deprecated traefik.domain label
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 11:29:13 +02:00
Emile Vauge
050416224d Fix host Docker network
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 11:29:13 +02:00
Vincent Demeester
6e5a221180 Merge pull request #493 from containous/fix-empty-tls-flag
Fix empty tls flag
2016-07-05 11:27:53 +02:00
Emile Vauge
a1ab252303 Fix empty tls flag
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 10:56:48 +02:00
Emile Vauge
3c89fd51ee Merge pull request #491 from containous/fix-default-weight-in-loadConfig
Fix default weight in server.LoadConfig
2016-07-05 10:56:26 +02:00
Emile Vauge
018b8a6315 Fix default weight in server.LoadConfig
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 10:26:28 +02:00
Emile Vauge
ecaa146d5b Merge pull request #492 from containous/fix-webui-proxy
Fix webui proxying
2016-07-05 10:17:49 +02:00
Emile Vauge
f50a4d8c2a Fix webui proxying
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-05 09:34:17 +02:00
Emile Vauge
68b0e44fbd Merge pull request #490 from containous/fix-retry-headers
Fix retry headers, simplify ResponseRecorder
2016-07-05 09:18:36 +02:00
Emile Vauge
ac9946c697 Fix retry headers, simplify ResponseRecorder
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-07-04 19:32:19 +02:00
Vincent Demeester
a0a8bc24e8 Merge pull request #479 from containous/disable-constraints-temporarily
Disable constraints in doc until 1.1
2016-06-23 17:46:05 +02:00
Emile Vauge
06ab802bc6 Disable constraints in doc until 1.1
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 17:25:12 +02:00
Vincent Demeester
04ec757083 Merge pull request #477 from containous/fix-spamming-events
Fix spamming events in listenProviders
2016-06-23 17:09:59 +02:00
Emile Vauge
15e04bb55d Fix consul catalog issue with dot in serviceName
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 16:33:54 +02:00
Emile Vauge
e4ed7fd8f7 Fix bad circuit breaker expression
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 16:33:54 +02:00
Emile Vauge
fd5352b0c6 Fix empty rule
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 16:33:54 +02:00
Emile Vauge
606e667b88 Fix spamming events in listenProviders
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-23 16:33:54 +02:00
Vincent Demeester
2a209c23c4 Merge pull request #478 from keis/consul-stable-ordering-of-nodes
Sort nodes before creating consul catalog config
2016-06-23 16:33:22 +02:00
David Keijser
70305266dc Sort nodes before creating consul catalog config
The watch of consul can return for various reasons and not of all of
them require a reload of the config. The order of nodes provided by
consul is not stable so to ensure a identical config is generated for an
identical server set the nodes needs to be sorted before creating the
config.
2016-06-23 13:08:12 +02:00
Vincent Demeester
8e561d9f95 Merge pull request #441 from NicolasGeraud/patch-1
mount acme folder instead of file
2016-06-23 10:00:09 +02:00
Nicolas Géraud
f446cac43c mount acme folder instead of file
I you use traefik in Docker and use Let's Encrypt, you have to mount the folder containing the acme.json file instead of the file itself.
2016-06-23 00:02:01 +02:00
Vincent Demeester
7e1ceb9a3e Merge pull request #476 from containous/fix-empty-response
Fix empty responses
2016-06-22 16:39:40 +02:00
Emile Vauge
1b5e35461d Bump oxy to b57d6706e9ff606343c596940b60df7f90012d29
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-22 15:33:39 +02:00
Vincent Demeester
df75700015 Merge pull request #468 from containous/fix-marathon-tls-auth
Fix marathon TLS/basic auth
2016-06-22 14:23:16 +02:00
Emile Vauge
b586df6689 Fix marathon tests
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-22 13:56:28 +02:00
Emile Vauge
4ca2ff0495 Bump go-marathon a558128c87724cd7430060ef5aedf39f83937f55, add DCOS support
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-20 17:11:32 +02:00
Emile Vauge
93494c7e35 Fix errors load config (#470)
* Trim spaces in rules

Signed-off-by: Emile Vauge <emile@vauge.com>

* dont break the whole config while only one part is wrong

Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-20 15:19:52 +02:00
Emile Vauge
11874bc4ae Fix acme renew, add test (#472)
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-20 13:55:50 +02:00
Kevin Busse
dcf98d13c8 Fix typo in error message. (#471) 2016-06-20 12:15:31 +02:00
Vincent Demeester
2a735e815a Merge pull request #469 from kumy/patch-1
Typo: Replace French words by English ones
2016-06-18 16:24:54 +02:00
kumy
52de16b4c9 Merge branch 'master' into patch-1 2016-06-18 16:06:49 +02:00
Vincent Demeester
7133a28fdb Merge pull request #460 from containous/fix-websocket-hijack
Fix websocket connection Hijack
2016-06-18 15:50:30 +02:00
kumy
ade2ff97e0 Typo: Replace French words by English ones 2016-06-18 15:43:35 +02:00
Emile Vauge
450d86be7d Fix websocket connection Hijack
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-18 13:37:21 +02:00
Vincent Demeester
c9caf612eb Merge pull request #464 from containous/fix-memory-leak
Fix memory leak in listenProviders
2016-06-17 00:52:21 +02:00
Emile Vauge
56ef678c09 Fix memory leak in listenProviders
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-16 22:49:57 +02:00
Emile Vauge
29e647763a Merge pull request #438 from samber/TRAEFIK-311--support-docker-backend
feat(constraints): Supports constraints for docker backend
2016-06-13 00:29:28 +02:00
Samuel BERTHE
357150bcab fix(constainrs,docker): Syntax 2016-06-11 19:06:39 +02:00
Samuel BERTHE
f7224ff403 feat(constraints): Supports constraints for docker backend 2016-06-11 19:06:39 +02:00
Vincent Demeester
01ffad2e6e Merge pull request #450 from containous/fix-default-kv-configuration
Fix default KV configuration
2016-06-11 15:03:57 +02:00
Emile Vauge
223e8cafac Fix default KV configuration
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-09 23:44:49 +02:00
Emile Vauge
d1ffbd8a03 Merge pull request #443 from vdemeester/442-and-share-context
Fix panic if listContainers fails…
2016-06-09 09:37:48 +02:00
Vincent Demeester
f286cb9a34 Fix panic if listContainers fails…
… and also share context accross API call, as this is how it's meant to
be used (and it allows to skip some calls if `cancel` is called).

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-06-08 19:39:38 +02:00
Emile Vauge
5c63855cc0 Merge branch 'AlmogBaku-master' 2016-06-07 20:19:51 +02:00
Emile Vauge
2a96ae9ec2 Merge branch 'master' of https://github.com/AlmogBaku/traefik into AlmogBaku-master 2016-06-07 20:19:21 +02:00
Samuel BERTHE
36a2da0659 Merge pull request #440 from containous/emilevauge-add-samber-to-maintainers
Add @samber to maintainers
2016-06-07 20:15:08 +02:00
Emile Vauge
38abec520c Add @samber to maintainers 2016-06-07 19:45:12 +02:00
Almog Baku
1274d26b4c Merge branch 'master' into master 2016-06-07 19:45:53 +03:00
Vincent Demeester
6556c79207 Merge pull request #433 from containous/add-routes-priorities
Add routes priorities
2016-06-07 10:10:19 +02:00
Emile Vauge
7e6c580130 Add routes priorities in documentation
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-07 09:54:12 +02:00
Emile Vauge
cc4fb64b34 Add routes priorities in integration tests
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-07 09:18:37 +02:00
Emile Vauge
f4cb4bb1b8 Add routes priorities in providers
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-07 09:18:37 +02:00
Emile Vauge
287b3ba1f4 Add routes priorities test
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-07 09:18:37 +02:00
Emile Vauge
208998972a Add routes priorities
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-07 09:17:39 +02:00
Vincent Demeester
7cdd062432 Merge pull request #435 from fclaeys/multiRules
Allow multiple rules
2016-06-07 09:12:39 +02:00
Fabrice CLAEYS
eccb529605 update docs 2016-06-07 08:52:43 +02:00
Fabrice CLAEYS
78dc28cce8 test rules parsing 2016-06-06 17:20:01 +02:00
Fabrice CLAEYS
84076db78e allow multiple rules 2016-06-06 09:22:23 +02:00
Almog Baku
c3779f0e94 Merge branch 'master' into master 2016-06-04 18:49:26 +03:00
Emile Vauge
c5ac563e74 Merge pull request #426 from containous/fix-marathon-directory-subdomain
Fix marathon groups subdomain
2016-06-03 11:39:40 +02:00
Emile Vauge
92ca220890 Add groupsAsSubDomains option
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-03 09:10:59 +02:00
Emile Vauge
72f88e5c0f Add marathon directory subdomain
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-03 09:10:59 +02:00
Vincent Demeester
1a75a71ad6 Merge pull request #428 from containous/fix-default-configuration
Fix default configuration
2016-06-02 17:43:01 +02:00
Emile Vauge
3c3b179c29 Deploy PR Docker image
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-02 17:22:45 +02:00
Emile Vauge
3f08bb4cdf Fix panic on help, Better version
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-02 15:17:04 +02:00
Emile Vauge
423268f485 Fix default configuration
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-06-02 11:51:36 +02:00
Vincent Demeester
d3f003a15f Merge pull request #415 from download13/docker_network
Added getIPAddress helper for docker template
2016-06-02 10:57:32 +02:00
Erin Dachtler
7386378cc0 Merge branch 'master' into docker_network 2016-06-01 08:38:23 -07:00
Vincent Demeester
d6547462e5 Merge pull request #342 from samber/TRAEFIK-311--adding-constraint-filtering
feat(constraints): Implementation of constraint filtering (cmd + toml + matching functions), implementation proposal with consul
2016-06-01 16:01:00 +02:00
Samuel BERTHE
d297a220ce fix(constraints): Syntax 2016-06-01 10:30:34 +02:00
Samuel BERTHE
1de5434e1a refacto(constraints): Migration to Flaeg cli library 2016-06-01 10:30:34 +02:00
Samuel BERTHE
f46accc74d test(constraint): unit tests + integration tests + make validate 2016-06-01 10:30:34 +02:00
Samuel BERTHE
cd2100ed84 doc(constraints): Added in ConsulCatalog backend + new 'Constraint' section 2016-06-01 10:30:34 +02:00
Samuel BERTHE
ac087921d8 feat(constraints): Implementation of constraint filtering (cmd + toml + matching functions), implementation proposal with consul 2016-06-01 10:30:34 +02:00
Erin Dachtler
82b1f14e2b Merge branch 'master' into docker_network 2016-05-31 22:19:45 -07:00
Erin Dachtler
df7e1cf078 Squashed commit of the following:
commit 468cdf5c74b8df80fe6cc093feda84d124d47460
Author: Erin Dachtler <download333@gmail.com>
Date:   Mon May 30 17:21:50 2016 -0700

    Documentation update

commit bcbe622141fc333579177e056b49d418997c511d
Author: Erin Dachtler <download333@gmail.com>
Date:   Sat May 28 15:32:34 2016 -0700

    Whoops, forgot to fmt

commit 1ad5f1052541372722adc372069da094b422c793
Author: Erin Dachtler <download333@gmail.com>
Date:   Sat May 28 14:56:04 2016 -0700

    Added getIPAddress helper for docker template, and tests
2016-05-31 22:11:17 -07:00
Emile Vauge
39fa8f7be4 Merge pull request #418 from samber/doc--tooling--integration-test-filtering
Doc about skipping some integration tests with '-check.f ConsulCatalogSuite'
2016-05-31 21:40:57 +02:00
Samuel BERTHE
46c2184de4 doc(tooling): Doc about selecting some tests with argument '-check.f' of gochecker library 2016-05-31 12:07:54 +02:00
Emile Vauge
a9f9894f29 Merge pull request #422 from containous/fix-travis-tag
Fix travis tag check
2016-05-31 00:15:58 +02:00
Emile Vauge
a6c360eeda Fix travis hangs on docker version
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-30 23:59:27 +02:00
Emile Vauge
01a4002169 Fix travis tag check
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-30 23:44:53 +02:00
Emile Vauge
8caaf317ae Merge pull request #412 from containous/prepare-release-candidate
Prepare release candidate
2016-05-30 18:27:48 +02:00
Emile Vauge
0e3c2ef10f Fix log config file
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-30 17:57:57 +02:00
Emile Vauge
db6c85d3d7 Prepare release candidate
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-30 17:48:50 +02:00
Vincent Demeester
2bd95620a5 Merge pull request #420 from cocap10/fixes-log
log info about TOML configuration file using
2016-05-30 17:48:00 +02:00
Martin
d8ad30f38a log info about TOML configuration file using
+glide update flaeg & staert
+fix README.md
+fix configFile flag description

Signed-off-by: Martin <martin.piegay@zenika.com>
2016-05-30 17:04:26 +02:00
Vincent Demeester
aad5f52968 Merge pull request #416 from download13/no_require_expose
Don't filter out containers with no exposed port if they have a traefik.port label set
2016-05-30 11:21:49 +02:00
Erin Dachtler
f5d49f6657 Merge branch 'master' into no_require_expose 2016-05-28 16:36:59 -07:00
Erin Dachtler
53ae64e578 Filter containers with no exposed ports unless they have a traefik.port label 2016-05-28 15:16:57 -07:00
Vincent Demeester
1a936b6aca Merge pull request #403 from cocap10/migrate-to-staert
Migrate to staert
2016-05-27 16:41:44 +02:00
Martin
4776fa1361 add parsers tests
Signed-off-by: Martin <martin.piegay@zenika.com>
2016-05-27 13:23:38 +02:00
Martin
c5084fd025 update staert + glide pin version 2016-05-27 10:06:19 +02:00
Martin
cc2735f733 add Debug StructTag
Signed-off-by: Martin <martin.piegay@zenika.com>
2016-05-27 10:06:19 +02:00
Martin
7f6b2b80f8 rm useless TestNoOrInexistentConfigShouldNotFail
Signed-off-by: Martin <martin.piegay@zenika.com>
2016-05-27 10:06:19 +02:00
Martin
f64c2bc065 add flag on ACME
add flag on Retry

set Retry.MaxMem to 2 by default

rm useless import

rm useless structtag

add custom parser on []acme.Domain type

add commants + refactor
2016-05-27 10:06:19 +02:00
Martin
6752b49536 rm useless StrucTag 2016-05-27 10:06:19 +02:00
Martin
ab138e7df1 update to new version go-bindata-assetfs 2016-05-27 10:06:19 +02:00
Martin
059da90a96 clean glide dependancies 2016-05-27 10:06:19 +02:00
Martin
0821c7bdd9 Add version in logs 2016-05-27 10:06:19 +02:00
Martin
89e00eb5a4 add staert & fleag 2016-05-27 10:06:19 +02:00
Martin
1a0f347023 update default value 2016-05-27 10:06:19 +02:00
Martin
1e27c2dabe fix TestNoOrInexistentConfigShouldNotFail 2016-05-27 10:06:19 +02:00
Martin
629be45c4a fix DisablePassHostHeaders 2016-05-27 10:06:19 +02:00
Martin
e115e3c4e7 fix default value 2016-05-27 10:06:19 +02:00
Martin
414fb1f406 add kubernetes.Namespaces parser 2016-05-27 10:06:19 +02:00
Martin
fe0a8f3363 Flaeg integration 2016-05-27 10:06:19 +02:00
Erin Dachtler
45589d5133 Reminder 2016-05-26 11:03:40 -07:00
Vincent Demeester
7804787e9e Merge pull request #408 from errm/k8s-endpoints
Build backend config using the K8S endpoint resource.
2016-05-26 17:12:23 +02:00
Ed Robinson
2e735f622f Adds some more coverage of the endpoint port selection logic. 2016-05-26 12:09:36 +01:00
Ed Robinson
6accb90c47 Simplify Service Lookup
Since we already know the name and namespace
of the service(s) we want we can just get the
correct one back from the API without filtering
the results.
2016-05-26 11:17:38 +01:00
Ed Robinson
e948a013cd Build backend config using the K8S endpoint resource.
* Potentialy saves a network hop
* Ability to configure LB algothim (given some work to expose an
anotation etc...)
* K8s config Watch is triggered far less often
2016-05-26 10:52:30 +01:00
Ed Robinson
b79535f369 Support ingresses without a host (#406)
fixes #370
2016-05-25 14:16:19 +02:00
Vincent Demeester
ed3bcc6d9a Merge pull request #387 from containous/fix-k8s-memory-leak
Fix k8s memory leak
2016-05-25 09:57:42 +02:00
Emile Vauge
0f23581f64 Fix k8s memory leak
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-24 23:37:17 +02:00
Emile Vauge
2af1e4b192 Fix k8s compose file
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-24 23:31:35 +02:00
Emile Vauge
dc404b365f Add expvar endpoint
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-24 23:31:35 +02:00
Emile Vauge
86f3891a2b Add debug flag
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-24 23:31:35 +02:00
Vincent Demeester
86053ea54b Update Makefile to fix local builds (#397)
Use --build-arg only if DOCKER_VERSION is specified

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-05-23 16:07:35 +02:00
Vincent Demeester
938600ba95 Merge pull request #398 from containous/fix-safari-http2
Fix safari http2
2016-05-23 14:37:19 +02:00
Emile Vauge
80ab967d39 Fix benchmarks doc
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-23 14:02:25 +02:00
Emile Vauge
43acbaa702 Fix safari error with http2
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-23 14:00:14 +02:00
Vincent Demeester
5d6492e6f5 Merge pull request #395 from vdemeester/carry-pr-382
Carry: http protocol should not use TLS
2016-05-23 13:11:56 +02:00
Jonas Falck
aeb9cc1732 http protocol should not use TLS
I need this in order to run kubectl proxy and then
make traefik use http://localhost to get to my cluster
when developing
2016-05-23 09:51:05 +02:00
Vincent Demeester
fa25c8ef22 Merge pull request #394 from vdemeester/carry-pr-312
Carry: Updating Toml to sure PathPrefix instead of Path
2016-05-23 09:50:38 +02:00
Russell Clare
77a9613c3a Updating Toml to sure PathPrefix instead of Path 2016-05-23 09:35:18 +02:00
Almog Baku
ba62a1f630 Merge branch 'master' into master 2016-05-20 21:13:59 +03:00
Vincent Demeester
153ab8f0fa Update engine-api to fix versions issues (#383)
Updating an engine-api that has fixed some versioning issues (filters
json marshalling)

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-05-19 21:11:30 +02:00
Vincent Demeester
f6c860afc0 Merge pull request #380 from errm/k8s-ref-ports-by-name
Allow k8s ports to be referenced by name as well as number
2016-05-19 15:00:12 +02:00
Ed Robinson
d13b755df2 Allow k8s ports to be referenced by name as well as number 2016-05-19 13:36:19 +01:00
Vincent Demeester
6bacbf6cac Merge pull request #379 from errm/k8s-services-from-same-namespace
The referenced k8s service(s) must be in namespace
2016-05-19 14:08:53 +02:00
AlmogBaku
0d5baa2219 Merge branch 'master' of https://github.com/containous/traefik 2016-05-19 01:10:44 +03:00
AlmogBaku
97c8a1d7ab fixes wrong "default" for k8s annotation in document... 2016-05-19 01:09:32 +03:00
Ed Robinson
5923d22379 The referenced k8s service(s) must be in namespace
By design k8s ingress is only designed to ballance services from within
the namespace of the ingress.

This is disscuessed a little in
https://github.com/kubernetes/kubernetes/issues/17088.

For now traefik should only reference the services in the current
namespace. For me this was a confusing change of behaviour
from the reference implimentations, as I have services
with the same name in each namespace.
2016-05-18 17:38:47 +01:00
Vincent Demeester
70494117d1 Merge pull request #373 from AlmogBaku/master
K8s: add an option to configure "PathPrefixStrip" for the ingress-resource using annotation
2016-05-18 17:19:11 +02:00
AlmogBaku
8210743dad change log error of "ruleType" to warning 2016-05-17 16:54:40 +03:00
AlmogBaku
895f3cc109 fixes golint error 2016-05-17 16:22:37 +03:00
AlmogBaku
71f160dddc Following up to the conversation on Slack & GitHub:
- Change the annotation to define the rule type in `traefik.frontend.rule.type`
 - Update tests
 - Add documentation
 - Add example
2016-05-17 13:50:06 +03:00
AlmogBaku
92abaa0d47 fixes wrong actual on unit-test 2016-05-16 01:24:23 +03:00
AlmogBaku
47710c1385 fixes wrong names on tests - make it pass 2016-05-16 01:06:30 +03:00
AlmogBaku
df3abcbc9a gofmt to tests.. 2016-05-15 20:01:26 +03:00
AlmogBaku
dbb7ad41e5 Add tests for k8s PathPrefixStrip annotation 2016-05-15 12:16:27 +03:00
AlmogBaku
9773d4e409 K8s: add an option to configure "PathPrefixStrip" for the ingress-resource using annotation 2016-05-15 12:00:20 +03:00
Emile Vauge
993165fa66 Added Devoxx Video & support (#371)
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-13 17:32:39 +02:00
Emile Vauge
c49f5dad05 Merge pull request #368 from vdemeester/update-docker-deps
Update docker dependencies to fix issues
2016-05-13 08:21:09 +02:00
Vincent Demeester
c0bdedfed3 Add travis matrix for 1.9.1 and 1.10.1
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-05-12 17:18:58 +02:00
Vincent Demeester
061107b65f Update docker dependencies to fix issues
Update engine-api, libcompose and related dependencies (including
libkermit), to fix issues with docker v1.9.1 and lower.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-05-12 17:10:42 +02:00
Vincent Demeester
7bf421f847 Merge pull request #369 from containous/fix-hijack-logger
fix hijack logger middleware
2016-05-12 17:06:15 +02:00
Emile Vauge
cb0c1d34a2 fix hijack logger middleware
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-12 16:32:12 +02:00
Vincent Demeester
749b381f26 Merge pull request #355 from containous/change-PathPrefixStrip-k8s
Replace PathPrefixStrip by PathPrefix in k8s
2016-05-12 13:56:43 +02:00
Emile Vauge
d89279d708 Replace PathPrefixStrip by PathPrefix in k8s
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-12 12:44:26 +02:00
Marcus Stong
be209ed30c #216: sets passHostHeader to true by default (#351) 2016-05-10 13:43:24 +02:00
Emile Vauge
4a4ba2791d Merge pull request #357 from s7anley/generate-fix
Create static folder on first generate
2016-05-09 23:47:23 +02:00
Ján Koščo
c61d9776e7 Create static folder on first generate 2016-05-09 23:13:22 +02:00
Vincent Demeester
b5716abd3e Merge pull request #347 from keis/consul-catalog-replace-space-in-tags
Replace whitespace in tags in consul_catalog
2016-05-07 17:26:17 +02:00
David Keijser
b9bb78d04b Normalise tags in backend name of consul_catalog
Another fun thing consul lets you do is use spaces in your tags. This
means when including tags in backend name it's possible to generate
invalid names.
2016-05-07 16:30:38 +02:00
Vincent Demeester
8a39ee65cd Merge pull request #341 from errm/k8s-namespacing
Adds option to namespace k8s ingresses
2016-05-07 13:30:35 +02:00
Ed Robinson
301a463aeb Adds option to namespace k8s ingresses
If the flag kubernetes.namespaces is set...
Then we only select ingresses from that/those namespace(s)

This allows multiple instances of traefik to
independently load balance for each namespace.
This could be for logical or security reasons.

Addresses #336
2016-05-06 11:36:53 +01:00
Vincent Demeester
d1b0bece47 Merge pull request #356 from errm/documentation-documentation
Adds some documentation about adding documentation
2016-05-05 23:33:35 +02:00
Ed Robinson
63fd7d1d63 Adds some documentation about adding documentation 2016-05-05 21:45:57 +01:00
Vincent Demeester
f4fb2518a1 Merge pull request #352 from containous/remove-alpine-from-build
Go back to standard golang image
2016-05-05 19:45:12 +02:00
Emile Vauge
ee486de947 Go back to standard golang image
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-05 18:31:55 +02:00
Vincent Demeester
c1a12a58eb Merge pull request #340 from containous/fix-etcd-backend
Fix etcd backend with prefix /
2016-05-03 16:16:10 +02:00
Emile Vauge
c3aadab615 Add Consul integration tests
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-03 15:41:09 +02:00
Emile Vauge
26774d2317 Add Etcd integration tests
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-03 15:41:09 +02:00
Emile Vauge
61def880db Fix etcd backend with prefix /
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-05-03 15:41:09 +02:00
Vincent Demeester
11a6331185 Merge pull request #349 from stongo/acme-bundle
#345: bundle intermediate certificates
2016-05-03 11:49:35 +02:00
Marcus Stong
378509cef4 #345: bundle intermediate certificates
fixes warnings and gives generated certs an A rating on ssl labs
2016-05-02 20:01:10 -04:00
Vincent Demeester
4a1fa03b2d Merge pull request #318 from stongo/master
#304: enhance acme documentation
2016-05-02 18:21:48 +02:00
Marcus Stong
52bff85dda Merge remote-tracking branch 'upstream/master' 2016-05-02 11:44:07 -04:00
Vincent Demeester
e5b0b34604 Merge pull request #325 from CiscoCloud/master
Add frontend, backend, and timing to access log
2016-04-28 23:19:27 +02:00
David Tootill
0a0063fa27 Tweak comments to satisfy golint 2016-04-28 04:00:38 -07:00
David Tootill
bf1f6f663a Minor refactor as requested in PR comments 2016-04-28 10:53:02 +00:00
David Tootill
8bac454792 Merge branch 'master' of https://github.com/CiscoCloud/traefik 2016-04-28 10:20:24 +00:00
Vincent Demeester
7eaf09b3da Merge pull request #310 from samber/TRAEFIK-294--consul--use-service-addess-or-node-if-nil
feat(consul-provider): If service ip is nil then use node ip
2016-04-27 22:34:06 +02:00
Poney baker
378a261e64 feat(consul-provider): If Service.Address is nil then use Node.Address
test(consul-catalog): Test on backend-name and backend-address
2016-04-27 21:09:42 +02:00
David Tootill
53c99f7469 Add moul/http2curl to glide.yaml 2016-04-27 10:24:00 -07:00
David Tootill
f93e618f67 Merge remote-tracking branch 'refs/remotes/containous/master'
# Conflicts:
#	glide.lock
#	glide.yaml
2016-04-27 09:41:51 -07:00
David Tootill
64b78461f6 Remove some debug logs (requested in review) 2016-04-27 09:25:13 -07:00
Vincent Demeester
2f5c9273ee Merge pull request #334 from containous/fix-k8s-watch-ssl
Fix Kubernetes watch SSL & empty schema
2016-04-27 10:20:26 +02:00
Emile Vauge
38371234a2 Add logo credits
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-27 10:05:04 +02:00
Emile Vauge
10cb606578 Add Kubernetes URL
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-27 10:05:04 +02:00
Emile Vauge
87caf458df Fix Kubernetes schema
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-27 10:05:04 +02:00
Emile Vauge
4ff4e4e626 Fix Kubernetes watch SSL
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-27 10:05:04 +02:00
Vincent Demeester
9053f9bb98 Merge pull request #335 from tboerger/feature/libkv-update
Updated libkv dependency
2016-04-27 09:57:38 +02:00
Thomas Boerger
b6b72c861f Replaced etcd-go with etcd/client
In order to meet the requirements of the updated libkv library I have
also replaced etcd-go with the up2date etcd client.

Signed-off-by: Thomas Boerger <thomas@webhippie.de>
2016-04-26 23:18:53 +02:00
Thomas Boerger
478eed6603 Updated libkv dependency
In order to fix the TLS client authentication I have updated the libkv
dependency. Now the connection to secured etcd and consuld should work
properly.

Signed-off-by: Thomas Boerger <thomas@webhippie.de>
2016-04-26 22:37:19 +02:00
Vincent Demeester
6b76f64b48 Merge pull request #186 from containous/add-kubernetes-ingress-support
Add kubernetes Ingress backend
2016-04-25 18:40:29 +02:00
Emile Vauge
53a2787626 Fix watch pods/services/rc/ingresses
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-25 17:13:26 +02:00
Emile Vauge
cac9927395 Fix namespace, fix PathPrefixStrip
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-22 13:57:00 +02:00
Emile Vauge
9e14619a0b Add doc
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-22 13:57:00 +02:00
Emile Vauge
c0dd4c3209 Add unit test
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-22 13:57:00 +02:00
Emile Vauge
d82e1342fb Fix integration test
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-22 13:57:00 +02:00
emile
720912e880 Add kubernetes Ingress backend
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-22 13:57:00 +02:00
Vincent Demeester
6f47434833 Merge pull request #328 from samber/consul-catalog--remove-disable-frontend
feat(consul-catalog): Remove frontend when backends disabled
2016-04-22 12:09:14 +02:00
Poney baker
6f13a2c0c7 feat(consul-catalog): Remove frontend when backends disabled 2016-04-22 11:55:31 +02:00
Vincent Demeester
b7a150bc64 Merge pull request #327 from pborreli/typos
Fixed typos
2016-04-22 11:38:02 +02:00
Pascal Borreli
4d22c45b76 Fixed typos 2016-04-21 23:38:44 +01:00
David Tootill
2a76a717e6 Add access log integration test 2016-04-20 11:54:57 -07:00
Vincent Demeester
b3b658a955 Merge pull request #324 from containous/fix-kv-backend
Fix KV backend
2016-04-20 08:01:32 +02:00
David Tootill
c8c0d208be Update glide files for mattn/shellwords 2016-04-20 01:36:51 +00:00
David Tootill
04dd41ac3b Minor corrections 2016-04-20 01:25:22 +00:00
David Tootill
10815eca8e Initial update - manage access log 2016-04-19 16:45:59 -07:00
Emile Vauge
06d2f343dd Fix KV backend
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-19 23:49:26 +02:00
Vincent Demeester
a6c5e85ae7 Merge pull request #320 from samber/consul-provider-compare-tag-lowercase
fix(consul-provider): Compare consul catalog tag keys with lowered case
2016-04-19 18:23:47 +02:00
Samuel BERTHE
45d6a326cd fix(consul-provider): Compare consul catalog tag keys with lowered case 2016-04-19 17:27:19 +02:00
Vincent Demeester
0332e32293 Merge pull request #322 from containous/fix-marathon-backend
Fix Marathon backend
2016-04-19 12:32:56 +02:00
Emile Vauge
2a3a34a80c Fix Marathon backend
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-19 12:05:28 +02:00
Emile Vauge
68da47b59a Merge pull request #321 from samber/minor-doc-fix
fix(doc)
2016-04-19 12:04:13 +02:00
Samuel BERTHE
b1f0f048cd fix(doc) 2016-04-19 10:00:33 +02:00
Marcus Stong
a7b4463f86 #304: enhance acme documentation 2016-04-18 12:31:45 -04:00
Vincent Demeester
ee60adc45a Merge pull request #315 from containous/add-backoff-marathon
Add backoff to marathon provider
2016-04-16 17:32:01 +02:00
Emile Vauge
36338b4928 add backoff to marathon provider
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-16 17:21:24 +02:00
Emile Vauge
23d3c512c2 Merge pull request #316 from vdemeester/docker-provider-stop-support
Support stop chan on docker provider
2016-04-16 17:20:55 +02:00
Vincent Demeester
4144638be4 Support stop chan on docker provider
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2016-04-16 14:46:35 +02:00
Emile Vauge
f2320ee648 Merge pull request #313 from containous/add-user-guide
Add doc user guide with swarm
2016-04-15 19:13:53 +02:00
Emile Vauge
17afa3e672 Add doc user guide with swarm
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-15 19:00:08 +02:00
Vincent Demeester
5b2c355c38 Merge pull request #305 from containous/fix-races
Fix races
2016-04-15 18:09:50 +02:00
Emile Vauge
61d54903e3 Fix doc
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-14 15:27:42 +02:00
Emile Vauge
c1078c4374 Fix races
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-14 15:27:42 +02:00
Emile Vauge
4e427b5a9e remove error oxy log
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-04-14 15:27:42 +02:00
Vincent Demeester
227ec71db3 Merge pull request #306 from kevioke/oxy-glide-update
Update glide files to use most recent version of containous/oxy
2016-04-14 15:08:31 +02:00
kevin
d047b8daa1 Update glide files to use most recent version of containous/oxy 2016-04-13 14:51:14 -07:00
Emile Vauge
c2009b71b1 Merge pull request #303 from containous/emilevauge-bump-go1.6.1
Bump to go v1.6.1
2016-04-13 21:28:53 +02:00
Emile Vauge
ba8629e2ac Bump to go v1.6.1 2016-04-13 21:09:39 +02:00
Vincent Demeester
6aba453afb Merge pull request #301 from kevioke/maxconns
Add support for maximum connections for backends.
2016-04-13 18:50:29 +02:00
kevin
a15578a8f6 Add support for maximum connections for backends. 2016-04-13 09:37:11 -07:00
Vincent Demeester
5c8d9f4eb9 Merge pull request #274 from samber/consul-catalog-with-tags-settings
feat(traefik,consul-catalog): Set attributes from consul catalog tags (loadbalancer,circuitbreaker,weight)
2016-04-13 17:17:10 +02:00
Emile Vauge
a9e615b3c7 Fix period in frontend name in KV store 2016-04-13 14:56:51 +02:00
Emile Vauge
94ad21020c Merge pull request #297 from containous/emilevauge-fix-period-frontend-kvstore
Fix period in frontend name in KV store
2016-04-13 13:25:52 +02:00
Emile Vauge
4b76cb4318 Fix period in frontend name in KV store 2016-04-13 13:00:20 +02:00
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
133 changed files with 18378 additions and 3083 deletions

View File

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

View File

@@ -76,3 +76,51 @@ ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
Test success
```
For development purpose, you can specifiy which tests to run by using:
```
# Run every tests in the MyTest suite
TESTFLAGS="-check.f MyTestSuite" make test-integration
# Run the test "MyTest" in the MyTest suite
TESTFLAGS="-check.f MyTestSuite.MyTest" make test-integration
# Run every tests starting with "My", in the MyTest suite
TESTFLAGS="-check.f MyTestSuite.My" make test-integration
# Run every tests ending with "Test", in the MyTest suite
TESTFLAGS="-check.f MyTestSuite.*Test" make test-integration
```
More: https://labix.org/gocheck
### Documentation
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)
First make sure you have python and pip installed
```
$ python --version
Python 2.7.2
$ pip --version
pip 1.5.2
```
Then install mkdocs with pip
```
$ pip install mkdocs
```
To test documentaion localy run `mkdocs serve` in the root directory, this should start a server localy to preview your changes.
```
$ mkdocs serve
INFO - Building documentation...
WARNING - Config value: 'theme'. Warning: The theme 'united' will be removed in an upcoming MkDocs release. See http://www.mkdocs.org/about/release-notes/ for more details
INFO - Cleaning site directory
[I 160505 22:31:24 server:281] Serving on http://127.0.0.1:8000
[I 160505 22:31:24 handlers:59] Start watching changes
[I 160505 22:31:24 handlers:61] Start detecting changes
```

5
.gitignore vendored
View File

@@ -9,4 +9,7 @@ traefik.toml
*.test
vendor/
static/
.vscode/
.vscode/
site/
*.log
*.exe

View File

@@ -1,31 +1,33 @@
branches:
except:
- /^v\d\.\d\.\d.*$/
env:
REPO: $TRAVIS_REPO_SLUG
VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER
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: $TRAVIS_TAG
- CODENAME: reblochon
matrix:
- DOCKER_VERSION=1.9.1
- DOCKER_VERSION=1.10.1
sudo: required
services:
- docker
- 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
- sudo service docker stop
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/bin/docker
- sudo chmod +x /usr/bin/docker
- sudo service docker start
- sleep 5
- docker version
- pip install --user mkdocs
- pip install --user pymdown-extensions
before_script:
- make validate
- make binary
- make validate
- make binary
script:
- make test-unit
- make test-integration
- make crossbinary
- make image
- make test-unit
- make test-integration
- make crossbinary
- make image
after_success:
- make deploy
- make deploy
- make deploy-pr

BIN
.travis/traefik.id_rsa.enc Normal file

Binary file not shown.

162
CHANGELOG.md Normal file
View File

@@ -0,0 +1,162 @@
# Change Log
## [v1.0.3](https://github.com/containous/traefik/tree/v1.0.3) (2016-09-22)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.2...v1.0.3)
**Fixed bugs:**
- traefik hangs - stops handling requests [\#662](https://github.com/containous/traefik/issues/662)
- Traefik crashing [\#458](https://github.com/containous/traefik/issues/458)
**Merged pull requests:**
- Fix health race [\#693](https://github.com/containous/traefik/pull/693) ([emilevauge](https://github.com/emilevauge))
## [v1.0.2](https://github.com/containous/traefik/tree/v1.0.2) (2016-08-02)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.1...v1.0.2)
**Fixed bugs:**
- ACME: revoke certificate on agreement update [\#579](https://github.com/containous/traefik/issues/579)
**Closed issues:**
- Exclude some frontends in consul catalog [\#555](https://github.com/containous/traefik/issues/555)
**Merged pull requests:**
- Bump oxy version, fix streaming [\#584](https://github.com/containous/traefik/pull/584) ([emilevauge](https://github.com/emilevauge))
- Fix ACME TOS [\#582](https://github.com/containous/traefik/pull/582) ([emilevauge](https://github.com/emilevauge))
## [v1.0.1](https://github.com/containous/traefik/tree/v1.0.1) (2016-07-19)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0...v1.0.1)
**Implemented enhancements:**
- Error with -consulcatalog and missing load balance method on 1.0.0 [\#524](https://github.com/containous/traefik/issues/524)
- Kubernetes provider: should allow the master url to be override [\#501](https://github.com/containous/traefik/issues/501)
**Fixed bugs:**
- Flag --etcd.endpoint default [\#508](https://github.com/containous/traefik/issues/508)
- Conditional ACME on demand generation [\#505](https://github.com/containous/traefik/issues/505)
- Important delay with streams \(Mozilla EventSource\) [\#503](https://github.com/containous/traefik/issues/503)
**Closed issues:**
- Can I use Traefik without a domain name? [\#539](https://github.com/containous/traefik/issues/539)
- Priortities in 1.0.0 not behaving [\#506](https://github.com/containous/traefik/issues/506)
- Route by path [\#500](https://github.com/containous/traefik/issues/500)
**Merged pull requests:**
- Update server.go [\#531](https://github.com/containous/traefik/pull/531) ([Jsewill](https://github.com/Jsewill))
- Add sse support [\#527](https://github.com/containous/traefik/pull/527) ([emilevauge](https://github.com/emilevauge))
- Fix acme checkOnDemandDomain [\#512](https://github.com/containous/traefik/pull/512) ([emilevauge](https://github.com/emilevauge))
- Fix default etcd port [\#511](https://github.com/containous/traefik/pull/511) ([errm](https://github.com/errm))
## [v1.0.0](https://github.com/containous/traefik/tree/v1.0.0) (2016-07-05)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0-rc3...v1.0.0)
**Fixed bugs:**
- Enable to define empty TLS option by flag for Let's Encrypt [\#488](https://github.com/containous/traefik/issues/488)
- \[Docker\] No IP in backend in host networking mode [\#487](https://github.com/containous/traefik/issues/487)
- Response is compressed when not requested [\#485](https://github.com/containous/traefik/issues/485)
- loadConfig modifies configuration causing same config check to fail [\#480](https://github.com/containous/traefik/issues/480)
**Closed issues:**
- svg logo [\#482](https://github.com/containous/traefik/issues/482)
- etcd tries to connect with TLS even with --etcd.tls=false [\#456](https://github.com/containous/traefik/issues/456)
- Zookeeper - KV connection error: Failed to test KV store connection [\#455](https://github.com/containous/traefik/issues/455)
- "Not Found" api response needed instead of 404 [\#454](https://github.com/containous/traefik/issues/454)
- domain label doesn't work on docker [\#447](https://github.com/containous/traefik/issues/447)
- Any chance of a windows release? [\#425](https://github.com/containous/traefik/issues/425)
**Merged pull requests:**
- Fix windows builds [\#495](https://github.com/containous/traefik/pull/495) ([emilevauge](https://github.com/emilevauge))
- Fix host Docker network [\#494](https://github.com/containous/traefik/pull/494) ([emilevauge](https://github.com/emilevauge))
- Fix empty tls flag [\#493](https://github.com/containous/traefik/pull/493) ([emilevauge](https://github.com/emilevauge))
- Fix webui proxying [\#492](https://github.com/containous/traefik/pull/492) ([emilevauge](https://github.com/emilevauge))
- Fix default weight in server.LoadConfig [\#491](https://github.com/containous/traefik/pull/491) ([emilevauge](https://github.com/emilevauge))
- Fix retry headers, simplify ResponseRecorder [\#490](https://github.com/containous/traefik/pull/490) ([emilevauge](https://github.com/emilevauge))
## [v1.0.0-rc3](https://github.com/containous/traefik/tree/v1.0.0-rc3) (2016-06-23)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0-rc2...v1.0.0-rc3)
**Implemented enhancements:**
- support more than one rule to Docker backend [\#419](https://github.com/containous/traefik/issues/419)
**Fixed bugs:**
- consulCatalog issue when serviceName contains a dot [\#475](https://github.com/containous/traefik/issues/475)
- Issue with empty responses [\#463](https://github.com/containous/traefik/issues/463)
- Severe memory leak in beta.470 and beyond crashes Traefik server [\#462](https://github.com/containous/traefik/issues/462)
- Marathon that starts with a space causes parsing errors. [\#459](https://github.com/containous/traefik/issues/459)
- A frontend route without a rule \(or empty rule\) causes a crash when traefik starts [\#453](https://github.com/containous/traefik/issues/453)
- container dropped out when connecting to Docker Swarm [\#442](https://github.com/containous/traefik/issues/442)
- Traefik setting Accept-Encoding: gzip on requests \(Traefik may also be broken with chunked responses\) [\#421](https://github.com/containous/traefik/issues/421)
**Closed issues:**
- HTTP headers case gets modified [\#466](https://github.com/containous/traefik/issues/466)
- File frontend \> Marathon Backend [\#465](https://github.com/containous/traefik/issues/465)
- Websocket: Unable to hijack the connection [\#452](https://github.com/containous/traefik/issues/452)
- kubernetes: Received event spamming? [\#449](https://github.com/containous/traefik/issues/449)
- kubernetes: backends not updated when i scale replication controller? [\#448](https://github.com/containous/traefik/issues/448)
- Add href link on frontend [\#436](https://github.com/containous/traefik/issues/436)
- Multiple Domains Rule [\#430](https://github.com/containous/traefik/issues/430)
**Merged pull requests:**
- Disable constraints in doc until 1.1 [\#479](https://github.com/containous/traefik/pull/479) ([emilevauge](https://github.com/emilevauge))
- Sort nodes before creating consul catalog config [\#478](https://github.com/containous/traefik/pull/478) ([keis](https://github.com/keis))
- Fix spamming events in listenProviders [\#477](https://github.com/containous/traefik/pull/477) ([emilevauge](https://github.com/emilevauge))
- Fix empty responses [\#476](https://github.com/containous/traefik/pull/476) ([emilevauge](https://github.com/emilevauge))
- Fix acme renew [\#472](https://github.com/containous/traefik/pull/472) ([emilevauge](https://github.com/emilevauge))
- Fix typo in error message. [\#471](https://github.com/containous/traefik/pull/471) ([KevinBusse](https://github.com/KevinBusse))
- Fix errors load config [\#470](https://github.com/containous/traefik/pull/470) ([emilevauge](https://github.com/emilevauge))
- Typo: Replace French words by English ones [\#469](https://github.com/containous/traefik/pull/469) ([kumy](https://github.com/kumy))
- Fix marathon TLS/basic auth [\#468](https://github.com/containous/traefik/pull/468) ([emilevauge](https://github.com/emilevauge))
- Fix memory leak in listenProviders [\#464](https://github.com/containous/traefik/pull/464) ([emilevauge](https://github.com/emilevauge))
- Fix websocket connection Hijack [\#460](https://github.com/containous/traefik/pull/460) ([emilevauge](https://github.com/emilevauge))
- Fix default KV configuration [\#450](https://github.com/containous/traefik/pull/450) ([emilevauge](https://github.com/emilevauge))
- Fix panic if listContainers fails… [\#443](https://github.com/containous/traefik/pull/443) ([vdemeester](https://github.com/vdemeester))
- mount acme folder instead of file [\#441](https://github.com/containous/traefik/pull/441) ([NicolasGeraud](https://github.com/NicolasGeraud))
- feat\(constraints\): Supports constraints for docker backend [\#438](https://github.com/containous/traefik/pull/438) ([samber](https://github.com/samber))
## [v1.0.0-rc2](https://github.com/containous/traefik/tree/v1.0.0-rc2) (2016-06-07)
[Full Changelog](https://github.com/containous/traefik/compare/v1.0.0-rc1...v1.0.0-rc2)
**Implemented enhancements:**
- Add @samber to maintainers [\#440](https://github.com/containous/traefik/pull/440) ([emilevauge](https://github.com/emilevauge))
**Fixed bugs:**
- Panic on help [\#429](https://github.com/containous/traefik/issues/429)
- Bad default values in configuration [\#427](https://github.com/containous/traefik/issues/427)
**Closed issues:**
- Traefik doesn't listen on IPv4 ports [\#434](https://github.com/containous/traefik/issues/434)
- Not listening on port 80 [\#432](https://github.com/containous/traefik/issues/432)
- docs need updating for new frontend rules format [\#423](https://github.com/containous/traefik/issues/423)
- Does traefik supports for Mac? \(For devlelopment\) [\#417](https://github.com/containous/traefik/issues/417)
**Merged pull requests:**
- Allow multiple rules [\#435](https://github.com/containous/traefik/pull/435) ([fclaeys](https://github.com/fclaeys))
- Add routes priorities [\#433](https://github.com/containous/traefik/pull/433) ([emilevauge](https://github.com/emilevauge))
- Fix default configuration [\#428](https://github.com/containous/traefik/pull/428) ([emilevauge](https://github.com/emilevauge))
- Fix marathon groups subdomain [\#426](https://github.com/containous/traefik/pull/426) ([emilevauge](https://github.com/emilevauge))
- Fix travis tag check [\#422](https://github.com/containous/traefik/pull/422) ([emilevauge](https://github.com/emilevauge))
- log info about TOML configuration file using [\#420](https://github.com/containous/traefik/pull/420) ([cocap10](https://github.com/cocap10))
- Doc about skipping some integration tests with '-check.f ConsulCatalogSuite' [\#418](https://github.com/containous/traefik/pull/418) ([samber](https://github.com/samber))
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*

View File

@@ -5,7 +5,8 @@ TRAEFIK_ENVS := \
-e OS_PLATFORM_ARG \
-e TESTFLAGS \
-e VERBOSE \
-e VERSION
-e VERSION \
-e CODENAME
SRCS = $(shell git ls-files '*.go' | grep -v '^external/')
@@ -18,44 +19,36 @@ REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
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_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",)
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
print-%: ; @echo $*=$($*)
default: binary
all: generate-webui 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
binary: generate-webui build ## build the linux binary
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary
crossbinary: generate-webui 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
validate: build ## validate gofmt, golint and go vet
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
validate-gofmt: build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt
validate-govet: build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-govet
validate-golint: build
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-golint
build: dist
docker build -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
build-webui:
docker build -t traefik-webui -f webui/Dockerfile webui
@@ -63,10 +56,10 @@ build-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:
@@ -92,3 +85,9 @@ fmt:
deploy:
./script/deploy.sh
deploy-pr:
./script/deploy-pr.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)

View File

@@ -1,22 +1,48 @@
<p align="center">
<img src="http://traefik.github.io/traefik.logo.svg" alt="Træfɪk" title="Træfɪk" />
<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://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Kubernetes](http://kubernetes.io/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
## 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](docs/index.md#benchmarks)
- [It's fast](http://docs.traefik.io/benchmarks)
- No dependency hell, single binary made with go
- Rest API
- Multiple backends supported: Docker, Mesos/Marathon, Consul, Etcd, and more to come
@@ -26,19 +52,22 @@ It supports several backends ([Docker :whale:](https://www.docker.com/), [Mesos/
- Circuit breakers on backends
- Round Robin, rebalancer load-balancers
- Rest Metrics
- Tiny docker image included [![Image Layers](https://badge.imagelayers.io/containous/traefik:latest.svg)](https://imagelayers.io/?images=containous/traefik:latest)
- [Tiny](https://imagelayers.io/?images=traefik) [official](https://hub.docker.com/r/_/traefik/) docker image included
- SSL backends support
- SSL frontend support (with SNI)
- Clean AngularJS Web UI
- Websocket support
- HTTP/2 support
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS)
- Retry request if network error
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS with renewal)
## Demo
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.
[![asciicast](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko.png)](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko)
Here is a talk (in french) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Lets'Encrypt.
[![Traefik Devoxx France](http://img.youtube.com/vi/QvAz9mVx5TI/0.jpg)](http://www.youtube.com/watch?v=QvAz9mVx5TI)
## Web UI
@@ -49,7 +78,7 @@ You can access to a simple HTML frontend of Træfik.
## Plumbing
- [Oxy](https://github.com/vulcand/oxy): an awsome proxy library made by Mailgun guys
- [Oxy](https://github.com/vulcand/oxy): an awesome 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
@@ -60,13 +89,13 @@ You can access to a simple HTML frontend of Træfik.
- 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 -c traefik.toml
./traefik --configFile=traefik.toml
```
- Use the tiny Docker image:
```shell
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml containous/traefik
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
```
- From sources:
@@ -77,12 +106,17 @@ git clone https://github.com/containous/traefik
## Documentation
You can find the complete documentation [here](docs/index.md).
You can find the complete documentation [here](https://docs.traefik.io).
## Contributing
Please refer to [this section](.github/CONTRIBUTING.md).
## Support
You can join [![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) to get basic support.
If you prefer a commercial support, please contact [containo.us](https://containo.us) by mail: <mailto:support@containo.us>.
## Træfɪk here and there
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)
@@ -106,8 +140,18 @@ Europe. We provide consulting, development, training and support for the world
software products.
[![Asteris](docs/img/asteris.logo.png)](https://aster.is)
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.
.
## Maintainers
- Emile Vauge [@emilevauge](https://github.com/emilevauge)
- Vincent Demeester [@vdemeester](https://github.com/vdemeester)
- Samuel Berthe [@samber](https://github.com/samber)
- Russell Clare [@Russell-IO](https://github.com/Russell-IO)
- Ed Robinson [@errm](https://github.com/errm)
## Credits
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo ![logo](docs/img/traefik.icon.png)

View File

@@ -10,11 +10,13 @@ import (
"errors"
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/safe"
"github.com/xenolf/lego/acme"
"io/ioutil"
fmtlog "log"
"os"
"reflect"
"strings"
"sync"
"time"
)
@@ -83,11 +85,11 @@ func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain D
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.Certificate = acmeCert
domainsCertificate.tlsCert = &tlsCert
return nil
}
@@ -142,17 +144,68 @@ type DomainsCertificate struct {
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
Email string `description:"Email address used for registration"`
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
StorageFile string `description:"File used for certificates storage."`
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
CAServer string `description:"CA server to use."`
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
storageLock sync.RWMutex
}
//Domains parse []Domain
type Domains []Domain
//Set []Domain
func (ds *Domains) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
if len(slice) < 1 {
return fmt.Errorf("Parse error ACME.Domain. Imposible to parse %s", str)
}
d := Domain{
Main: slice[0],
SANs: []string{},
}
if len(slice) > 1 {
d.SANs = slice[1:]
}
*ds = append(*ds, d)
return nil
}
//Get []Domain
func (ds *Domains) Get() interface{} { return []Domain(*ds) }
//String returns []Domain in string
func (ds *Domains) String() string { return fmt.Sprintf("%+v", *ds) }
//SetValue sets []Domain into the parser
func (ds *Domains) SetValue(val interface{}) {
*ds = Domains(val.([]Domain))
}
// Domain holds a domain name with SANs
type Domain struct {
Main string
@@ -164,7 +217,7 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
if len(a.StorageFile) == 0 {
return errors.New("Empty StorageFile, please provide a filenmae for certs storage")
return errors.New("Empty StorageFile, please provide a filename for certs storage")
}
log.Debugf("Generating default certificate...")
@@ -222,11 +275,30 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
// The client has a URL to the current Let's Encrypt Subscriber
// Agreement. The user will need to agree to it.
err = client.AgreeToTOS()
if err != nil {
// Let's Encrypt Subscriber Agreement renew ?
reg, err := client.QueryRegistration()
if err != nil {
return err
}
account.Registration = reg
err = client.AgreeToTOS()
if err != nil {
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
}
}
// save account
err = a.saveAccount(account)
if err != nil {
return err
}
go a.retrieveCertificates(client, account)
safe.Go(func() {
a.retrieveCertificates(client, account)
if err := a.renewCertificates(client, account); err != nil {
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
}
})
tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
if challengeCert, ok := wrapperChallengeProvider.getCertificate(clientHello.ServerName); ok {
@@ -245,18 +317,17 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
}
ticker := time.NewTicker(24 * time.Hour)
go func() {
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
}
@@ -288,9 +359,9 @@ func (a *ACME) retrieveCertificates(client *acme.Client, account *Account) {
}
func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
log.Debugf("Testing certificate renew...")
for _, certificateResource := range account.DomainsCertificate.Certs {
// <= 7 days left, renew certificate
if certificateResource.tlsCert.Leaf.NotAfter.Before(time.Now().Add(time.Duration(24 * 7 * time.Hour))) {
if certificateResource.needRenew() {
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
renewedCert, err := client.RenewCertificate(acme.CertificateResource{
Domain: certificateResource.Certificate.Domain,
@@ -298,9 +369,10 @@ func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
CertStableURL: certificateResource.Certificate.CertStableURL,
PrivateKey: certificateResource.Certificate.PrivateKey,
Certificate: certificateResource.Certificate.Certificate,
}, false)
}, true)
if err != nil {
return err
log.Errorf("Error renewing certificate: %v", err)
continue
}
log.Debugf("Renewed certificate %+v", certificateResource.Domains)
renewedACMECert := &Certificate{
@@ -312,10 +384,12 @@ func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
}
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
if err != nil {
return err
log.Errorf("Error renewing certificate: %v", err)
continue
}
if err = a.saveAccount(account); err != nil {
return err
log.Errorf("Error saving ACME account: %v", err)
continue
}
}
}
@@ -388,7 +462,7 @@ func (a *ACME) saveAccount(Account *Account) error {
func (a *ACME) getDomainsCertificates(client *acme.Client, domains []string) (*Certificate, error) {
log.Debugf("Loading ACME certificates %s...", domains)
bundle := false
bundle := true
certificate, failures := client.ObtainCertificate(domains, bundle, nil)
if len(failures) > 0 {
log.Error(failures)

258
acme/acme_test.go Normal file
View File

@@ -0,0 +1,258 @@
package acme
import (
"reflect"
"sync"
"testing"
)
func TestDomainsSet(t *testing.T) {
checkMap := map[string]Domains{
"": {},
"foo.com": {Domain{Main: "foo.com", SANs: []string{}}},
"foo.com,bar.net": {Domain{Main: "foo.com", SANs: []string{"bar.net"}}},
"foo.com,bar1.net,bar2.net,bar3.net": {Domain{Main: "foo.com", SANs: []string{"bar1.net", "bar2.net", "bar3.net"}}},
}
for in, check := range checkMap {
ds := Domains{}
ds.Set(in)
if !reflect.DeepEqual(check, ds) {
t.Errorf("Expected %+v\nGot %+v", check, ds)
}
}
}
func TestDomainsSetAppend(t *testing.T) {
inSlice := []string{
"",
"foo1.com",
"foo2.com,bar.net",
"foo3.com,bar1.net,bar2.net,bar3.net",
}
checkSlice := []Domains{
{},
{
Domain{
Main: "foo1.com",
SANs: []string{}}},
{
Domain{
Main: "foo1.com",
SANs: []string{}},
Domain{
Main: "foo2.com",
SANs: []string{"bar.net"}}},
{
Domain{
Main: "foo1.com",
SANs: []string{}},
Domain{
Main: "foo2.com",
SANs: []string{"bar.net"}},
Domain{Main: "foo3.com",
SANs: []string{"bar1.net", "bar2.net", "bar3.net"}}},
}
ds := Domains{}
for i, in := range inSlice {
ds.Set(in)
if !reflect.DeepEqual(checkSlice[i], ds) {
t.Errorf("Expected %s %+v\nGot %+v", in, checkSlice[i], ds)
}
}
}
func TestCertificatesRenew(t *testing.T) {
domainsCertificates := DomainsCertificates{
lock: &sync.RWMutex{},
Certs: []*DomainsCertificate{
{
Domains: Domain{
Main: "foo1.com",
SANs: []string{}},
Certificate: &Certificate{
Domain: "foo1.com",
CertURL: "url",
CertStableURL: "url",
PrivateKey: []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA6OqHGdwGy20+3Jcz9IgfN4IR322X2Hhwk6n8Hss/Ws7FeTZo
PvXW8uHeI1bmQJsy9C6xo3odzO64o7prgMZl5eDw5fk1mmUij3J3nM3gwtc/Cc+8
ADXGldauASdHBFTRvWQge0Pv/Q5U0fyL2VCHoR9mGv4CQ7nRNKPus0vYJMbXoTbO
8z4sIbNz3Ov9o/HGMRb8D0rNPTMdC62tHSbiO1UoxLXr9dcBOGt786AsiRTJ8bq9
GCVQgzd0Wftb8z6ddW2YuWrmExlkHdfC4oG0D5SU1QB4ldPyl7fhVWlfHwC1NX+c
RnDSEeYkAcdvvIekdM/yH+z62XhwToM0E9TCzwIDAQABAoIBACq3EC3S50AZeeTU
qgeXizoP1Z1HKQjfFa5PB1jSZ30M3LRdIQMi7NfASo/qmPGSROb5RUS42YxC34PP
ZXXJbNiaxzM13/m/wHXURVFxhF3XQc1X1p+nPRMvutulS2Xk9E4qdbaFgBbFsRKN
oUwqc6U97+jVWq72/gIManNhXnNn1n1SRLBEkn+WStMPn6ZvWRlpRMjhy0c1mpwg
u6em92HvMvfKPQ60naUhdKp+q0rsLp2YKWjiytos9ENSYI5gAGLIDhKeqiD8f92E
4FGPmNRipwxCE2SSvZFlM26tRloWVcBPktRN79hUejE8iopiqVS0+4h/phZ2wG0D
18cqVpECgYEA+qmagnhm0LLvwVkUN0B2nRARQEFinZDM4Hgiv823bQvc9I8dVTqJ
aIQm5y4Y5UA3xmyDsRoO7GUdd0oVeh9GwTONzMRCOny/mOuOC51wXPhKHhI0O22u
sfbOHszl+bxl6ZQMUJa2/I8YIWBLU5P+fTgrfNwBEgZ3YPwUV5tyHNcCgYEA7eAv
pjQkbJNRq/fv/67sojN7N9QoH84egN5cZFh5d8PJomnsvy5JDV4WaG1G6mJpqjdD
YRVdFw5oZ4L8yCVdCeK9op896Uy51jqvfSe3+uKmNqE0qDHgaLubQNI8yYc5sacW
fYJBmDR6rNIeE7Q2240w3CdKfREuXdDnhyTTEskCgYBFeAnFTP8Zqe2+hSSQJ4J4
BwLw7u4Yww+0yja/N5E1XItRD/TOMRnx6GYrvd/ScVjD2kEpLRKju2ZOMC8BmHdw
hgwvitjcAsTK6cWFPI3uhjVsXhkxuzUmR0Naz+iQrQEFmi1LjGmMV1AVt+1IbYSj
SZTr1sFJMJeXPmWY3hDjIwKBgQC4H9fCJoorIL0PB5NVreishHzT8fw84ibqSTPq
2DDtazcf6C3AresN1c4ydqN1uUdg4fXdp9OujRBzTwirQ4CIrmFrBye89g7CrBo6
Hgxivh06G/3OUw0JBG5f9lvnAiy+Pj9CVxi+36A1NU7ioZP0zY0MW71koW/qXlFY
YkCfQQKBgBqwND/c3mPg7iY4RMQ9XjrKfV9o6FMzA51lAinjujHlNgsBmqiR951P
NA3kWZQ73D3IxeLEMaGHpvS7andPN3Z2qPhe+FbJKcF6ZZNTrFQkh/Fpz3wmYPo1
GIL4+09kNgMRWapaROqI+/3+qJQ+GVJZIPfYC0poJOO6vYqifWe8
-----END RSA PRIVATE KEY-----
`),
Certificate: []byte(`
-----BEGIN CERTIFICATE-----
MIIC+TCCAeGgAwIBAgIJAK78ukR/Qu4rMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV
BAMMCGZvbzEuY29tMB4XDTE2MDYxOTIyMDMyM1oXDTI2MDYxNzIyMDMyM1owEzER
MA8GA1UEAwwIZm9vMS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDo6ocZ3AbLbT7clzP0iB83ghHfbZfYeHCTqfweyz9azsV5Nmg+9dby4d4jVuZA
mzL0LrGjeh3M7rijumuAxmXl4PDl+TWaZSKPcneczeDC1z8Jz7wANcaV1q4BJ0cE
VNG9ZCB7Q+/9DlTR/IvZUIehH2Ya/gJDudE0o+6zS9gkxtehNs7zPiwhs3Pc6/2j
8cYxFvwPSs09Mx0Lra0dJuI7VSjEtev11wE4a3vzoCyJFMnxur0YJVCDN3RZ+1vz
Pp11bZi5auYTGWQd18LigbQPlJTVAHiV0/KXt+FVaV8fALU1f5xGcNIR5iQBx2+8
h6R0z/If7PrZeHBOgzQT1MLPAgMBAAGjUDBOMB0GA1UdDgQWBBRFLH1wF6BT51uq
yWNqBnCrPFIglzAfBgNVHSMEGDAWgBRFLH1wF6BT51uqyWNqBnCrPFIglzAMBgNV
HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAr7aH3Db6TeAZkg4Zd7SoF2q11
erzv552PgQUyezMZcRBo2q1ekmUYyy2600CBiYg51G+8oUqjJKiKnBuaqbMX7pFa
FsL7uToZCGA57cBaVejeB+p24P5bxoJGKCMeZcEBe5N93Tqu5WBxNEX7lQUo6TSs
gSN2Olf3/grNKt5V4BduSIQZ+YHlPUWLTaz5B1MXKSUqjmabARP9lhjO14u9USvi
dMBDFskJySQ6SUfz3fyoXELoDOVbRZETuSodpw+aFCbEtbcQCLT3A0FG+BEPayZH
tt19zKUlr6e+YFpyjQPGZ7ZkY7iMgHEkhKrXx2DiZ1+cif3X1xfXWQr0S5+E
-----END CERTIFICATE-----
`),
},
},
{
Domains: Domain{
Main: "foo2.com",
SANs: []string{}},
Certificate: &Certificate{
Domain: "foo2.com",
CertURL: "url",
CertStableURL: "url",
PrivateKey: []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA7rIVuSrZ3FfYXhR3qaWwfVcgiqKS//yXFzNqkJS6mz9nRCNT
lPawvrCFIRKdR7UO7xD7A5VTcbrGOAaTvrEaH7mB/4FGL+gN4AiTbVFpKXngAYEW
A3//zeBZ7XUSWaQ+CNC+l796JeoDvQD++KwCke4rVD1pGN1hpVEeGhwzyKOYPKLo
4+AGVe1LFWw4U/v8Iil1/gBBehZBILuhASpXy4W132LJPl76/EbGqh0nVz2UlFqU
HRxO+2U2ba4YIpI+0/VOQ9Cq/TzHSUdTTLfBHE/Qb+aDBfptMWTRvAngLqUglOcZ
Fi6SAljxEkJO6z6btmoVUWsoKBpbIHDC5++dZwIDAQABAoIBAAD8rYhRfAskNdnV
vdTuwXcTOCg6md8DHWDULpmgc9EWhwfKGZthFcQEGNjVKd9VCVXFvTP7lxe+TPmI
VW4Rb2k4LChxUWf7TqthfbKTBptMTLfU39Ft4xHn3pdTx5qlSjhhHJimCwxDFnbe
nS9MDsqpsHYtttSKfc/gMP6spS4sNPZ/r9zseT3eWkBEhn+FQABxJiuPcQ7q7S+Q
uOghmr7f3FeYvizQOhBtULsLrK/hsmQIIB4amS1QlpNWKbIoiUPNPjCA5PVQyAER
waYjuc7imBbeD98L/z8bRTlEskSKjtPSEXGVHa9OYdBU+02Ci6TjKztUp6Ho7JE9
tcHj+eECgYEA+9Ntv6RqIdpT/4/52JYiR+pOem3U8tweCOmUqm/p/AWyfAJTykqt
cJ8RcK1MfM+uoa5Sjm8hIcA2XPVEqH2J50PC4w04Q3xtfsz3xs7KJWXQCoha8D0D
ZIFNroEPnld0qOuJzpIIteXTrCLhSu17ZhN+Wk+5gJ7Ewu/QMM5OPjECgYEA8qbw
zfwSjE6jkrqO70jzqSxgi2yjo0vMqv+BNBuhxhDTBXnKQI1KsHoiS0FkSLSJ9+DS
CT3WEescD2Lumdm2s9HXvaMmnDSKBY58NqCGsNzZifSgmj1H/yS9FX8RXfSjXcxq
RDvTbD52/HeaCiOxHZx8JjmJEb+ZKJC4MDvjtxcCgYBM516GvgEjYXdxfliAiijh
6W4Z+Vyk5g/ODPc3rYG5U0wUjuljx7Z7xDghPusy2oGsIn5XvRxTIE35yXU0N1Jb
69eiWzEpeuA9bv7kGdal4RfNf6K15wwYL1y3w/YvFuorg/LLwNEkK5Ge6e//X9Ll
c2KM1fgCjXntRitAHGDMoQKBgDnkgodioLpA+N3FDN0iNqAiKlaZcOFA8G/LzfO0
tAAhe3dO+2YzT6KTQSNbUqXWDSTKytHRowVbZrJ1FCA4xVJZunNQPaH/Fv8EY7ZU
zk3cIzq61qZ2AHtrNIGwc2BLQb7bSm9FJsgojxLlJidNJLC/6Q7lo0JMyCnZfVhk
sYu5AoGAZt/MfyFTKm674UddSNgGEt86PyVYbLMnRoAXOaNB38AE12kaYHPil1tL
FnL8OQLpbX5Qo2JGgeZRlpMJ4Jxw2zzvUKr/n+6khaLxHmtX48hMu2QM7ZvnkZCs
Kkgz6v+Wcqm94ugtl3HSm+u9xZzVQxN6gu/jZQv3VpQiAZHjPYc=
-----END RSA PRIVATE KEY-----
`),
Certificate: []byte(`
-----BEGIN CERTIFICATE-----
MIIC+TCCAeGgAwIBAgIJAK25/Z9Jz6IBMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV
BAMMCGZvbzIuY29tMB4XDTE2MDYyMDA5MzUyNloXDTI2MDYxODA5MzUyNlowEzER
MA8GA1UEAwwIZm9vMi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDushW5KtncV9heFHeppbB9VyCKopL//JcXM2qQlLqbP2dEI1OU9rC+sIUhEp1H
tQ7vEPsDlVNxusY4BpO+sRofuYH/gUYv6A3gCJNtUWkpeeABgRYDf//N4FntdRJZ
pD4I0L6Xv3ol6gO9AP74rAKR7itUPWkY3WGlUR4aHDPIo5g8oujj4AZV7UsVbDhT
+/wiKXX+AEF6FkEgu6EBKlfLhbXfYsk+Xvr8RsaqHSdXPZSUWpQdHE77ZTZtrhgi
kj7T9U5D0Kr9PMdJR1NMt8EcT9Bv5oMF+m0xZNG8CeAupSCU5xkWLpICWPESQk7r
Ppu2ahVRaygoGlsgcMLn751nAgMBAAGjUDBOMB0GA1UdDgQWBBQ6FZWqB9qI4NN+
2jFY6xH8uoUTnTAfBgNVHSMEGDAWgBQ6FZWqB9qI4NN+2jFY6xH8uoUTnTAMBgNV
HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCRhuf2dQhIEOmSOGgtRELF2wB6
NWXt0lCty9x4u+zCvITXV8Z0C34VQGencO3H2bgyC3ZxNpPuwZfEc2Pxe8W6bDc/
OyLckk9WLo00Tnr2t7rDOeTjEGuhXFZkhIbJbKdAH8cEXrxKR8UXWtZgTv/b8Hv/
g6tbeH6TzBsdMoFtUCsyWxygYwnLU+quuYvE2s9FiCegf2mdYTCh/R5J5n/51gfB
uC+NakKMfaCvNg3mOAFSYC/0r0YcKM/5ldKGTKTCVJAMhnmBnyRc/70rKkVRFy2g
iIjUFs+9aAgfCiL0WlyyXYAtIev2gw4FHUVlcT/xKks+x8Kgj6e5LTIrRRwW
-----END CERTIFICATE-----
`),
},
},
},
}
newCertificate := &Certificate{
Domain: "foo1.com",
CertURL: "url",
CertStableURL: "url",
PrivateKey: []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA1OdSuXK2zeSLf0UqgrI4pjkpaqhra++pnda4Li4jXo151svi
Sn7DSynJOoq1jbfRJAoyDhxsBC4S4RuD54U5elJ4wLPZXmHRsvb+NwiHs9VmDqwu
It21btuqeNMebkab5cnDnC6KKufMhXRcRAlluYXyCkQe/+N+LlUQd6Js34TixMpk
eQOX4/OVrokSyVRnIq4u+o0Ufe7z5+41WVH63tcy7Hwi7244aLUzZCs+QQa2Dw6f
qEwjbonr974fM68UxDjTZEQy9u24yDzajhDBp1OTAAklh7U+li3g9dSyNVBFXqEu
nW2fyBvLqeJOSTihqfcrACB/YYhYOX94vMXELQIDAQABAoIBAFYK3t3fxI1VTiMz
WsjTKh3TgC+AvVkz1ILbojfXoae22YS7hUrCDD82NgMYx+LsZPOBw1T8m5Lc4/hh
3F8W8nHDHtYSWUjRk6QWOgsXwXAmUEahw0uH+qlA0ZZfDC9ZDexCLHHURTat03Qj
4J4GhjwCLB2GBlk4IWisLCmNVR7HokrpfIw4oM1aB5E21Tl7zh/x7ikRijEkUsKw
7YhaMeLJqBnMnAdV63hhF7FaDRjl8P2s/3octz/6pqDIABrDrUW3KAkNYCZIWdhF
Kk0wRMbZ/WrYT9GIGoJe7coQC7ezTrlrEkAFEIPGHCLkgXB/0TyuSy0yY59e4zmi
VvHoWUECgYEA/rOL2KJ/p+TZW7+YbsUzs0+F+M+G6UCr0nWfYN9MKmNtmns3eLDG
+pIpBMc5mjqeJR/sCCdkD8OqHC202Y8e4sr0pKSBeBofh2BmXtpyu3QQ50Pa63RS
SK6mYUrFqPmFFDbNGpFI4sIeI+Vf6hm96FQPnyPtUTGqk39m0RbWM/UCgYEA1f04
Nf3wbqwqIHZjYpPmymfjleyMn3hGUjpi7pmI6inXGMk3nkeG1cbOhnfPxL5BWD12
3RqHI2B4Z4r0BMyjctDNb1TxhMIpm5+PKm5KeeKfoYA85IS0mEeq6VdMm3mL1x/O
3LYvcUvAEVf6pWX/+ZFLMudqhF3jbTrdNOC6ZFkCgYBKpEeJdyW+CD0CvEVpwPUD
yXxTjE3XMZKpHLtWYlop2fWW3iFFh1jouci3k8L3xdHuw0oioZibXhYOJ/7l+yFs
CVpknakrj0xKGiAmEBKriLojbClN80rh7fzoakc+29D6OY0mCgm4GndGwcO4EU8s
NOZXFupHbyy0CRQSloSzuQKBgQC1Z/MtIlefGuijmHlsakGuuR+gS2ZzEj1bHBAe
gZ4mFM46PuqdjblqpR0TtaI3AarXqVOI4SJLBU9NR+jR4MF3Zjeh9/q/NvKa8Usn
B1Svu0TkXphAiZenuKnVIqLY8tNvzZFKXlAd1b+/dDwR10SHR3rebnxINmfEg7Bf
UVvyEQKBgAEjI5O6LSkLNpbVn1l2IO8u8D2RkFqs/Sbx78uFta3f9Gddzb4wMnt3
jVzymghCLp4Qf1ump/zC5bcQ8L97qmnjJ+H8X9HwmkqetuI362JNnz+12YKVDIWi
wI7SJ8BwDqYMrLw6/nE+degn39KedGDH8gz5cZcdlKTZLjbuBOfU
-----END RSA PRIVATE KEY-----
`),
Certificate: []byte(`
-----BEGIN CERTIFICATE-----
MIIC+TCCAeGgAwIBAgIJAPQiOiQcwYaRMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV
BAMMCGZvbzEuY29tMB4XDTE2MDYxOTIyMTE1NFoXDTI2MDYxNzIyMTE1NFowEzER
MA8GA1UEAwwIZm9vMS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDU51K5crbN5It/RSqCsjimOSlqqGtr76md1rguLiNejXnWy+JKfsNLKck6irWN
t9EkCjIOHGwELhLhG4PnhTl6UnjAs9leYdGy9v43CIez1WYOrC4i3bVu26p40x5u
RpvlycOcLooq58yFdFxECWW5hfIKRB7/434uVRB3omzfhOLEymR5A5fj85WuiRLJ
VGciri76jRR97vPn7jVZUfre1zLsfCLvbjhotTNkKz5BBrYPDp+oTCNuiev3vh8z
rxTEONNkRDL27bjIPNqOEMGnU5MACSWHtT6WLeD11LI1UEVeoS6dbZ/IG8up4k5J
OKGp9ysAIH9hiFg5f3i8xcQtAgMBAAGjUDBOMB0GA1UdDgQWBBQPfkS5ehpstmSb
8CGJE7GxSCxl2DAfBgNVHSMEGDAWgBQPfkS5ehpstmSb8CGJE7GxSCxl2DAMBgNV
HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQA99A+itS9ImdGRGgHZ5fSusiEq
wkK5XxGyagL1S0f3VM8e78VabSvC0o/xdD7DHVg6Az8FWxkkksH6Yd7IKfZZUzvs
kXQhlOwWpxgmguSmAs4uZTymIoMFRVj3nG664BcXkKu4Yd9UXKNOWP59zgvrCJMM
oIsmYiq5u0MFpM31BwfmmW3erqIcfBI9OJrmr1XDzlykPZNWtUSSfVuNQ8d4bim9
XH8RfVLeFbqDydSTCHIFvYthH/ESbpRCiGJHoJ8QLfOkhD1k2fI0oJZn5RVtG2W8
bZME3gHPYCk1QFZUptriMCJ5fMjCgxeOTR+FAkstb/lTRuCc4UyILJguIMar
-----END CERTIFICATE-----
`),
}
err := domainsCertificates.renewCertificates(
newCertificate,
Domain{
Main: "foo1.com",
SANs: []string{}})
if err != nil {
t.Errorf("Error in renewCertificates :%v", err)
}
if len(domainsCertificates.Certs) != 2 {
t.Errorf("Expected domainsCertificates length %d %+v\nGot %+v", 2, domainsCertificates.Certs, len(domainsCertificates.Certs))
}
if !reflect.DeepEqual(domainsCertificates.Certs[0].Certificate, newCertificate) {
t.Errorf("Expected new certificate %+v \nGot %+v", newCertificate, domainsCertificates.Certs[0].Certificate)
}
}

View File

@@ -29,7 +29,7 @@ func (c *wrapperChallengeProvider) getCertificate(domain string) (cert *tls.Cert
}
func (c *wrapperChallengeProvider) Present(domain, token, keyAuth string) error {
cert, err := acme.TLSSNI01ChallengeCert(keyAuth)
cert, _, err := acme.TLSSNI01ChallengeCert(keyAuth)
if err != nil {
return err
}

View File

@@ -23,9 +23,9 @@ func (oxylogger *OxyLogger) Warningf(format string, args ...interface{}) {
log.Warningf(format, args...)
}
// Errorf logs specified string as Error level in logrus.
// Errorf logs specified string as Warningf level in logrus.
func (oxylogger *OxyLogger) Errorf(format string, args ...interface{}) {
log.Errorf(format, args...)
log.Warningf(format, args...)
}
func notFoundHandler(w http.ResponseWriter, r *http.Request) {

View File

@@ -1,17 +1,12 @@
FROM golang:1.6.0-alpine
FROM golang:1.6.2
RUN apk update && apk add git bash gcc musl-dev \
&& go get github.com/Masterminds/glide \
&& go get github.com/mitchellh/gox \
RUN go get github.com/Masterminds/glide \
&& 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.10.1
# enable GO15VENDOREXPERIMENT
ENV GO15VENDOREXPERIMENT 1
ARG DOCKER_VERSION=1.10.1
# Download docker
RUN set -ex; \
@@ -27,4 +22,4 @@ COPY glide.yaml glide.yaml
COPY glide.lock glide.lock
RUN glide install
COPY . /go/src/github.com/containous/traefik
COPY . /go/src/github.com/containous/traefik

219
cmd.go
View File

@@ -1,219 +0,0 @@
/*
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

@@ -3,39 +3,45 @@ 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"
"regexp"
"strings"
"time"
)
// TraefikConfiguration holds GlobalConfiguration and other stuff
type TraefikConfiguration struct {
GlobalConfiguration
ConfigFile string `short:"c" description:"Configuration file to use (TOML)."`
}
// 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 {
GraceTimeOut int64
AccessLogsFile string
TraefikLogsFile string
LogLevel string
EntryPoints EntryPoints
ACME *acme.ACME
DefaultEntryPoints DefaultEntryPoints
ProvidersThrottleDuration time.Duration
MaxIdleConnsPerHost int
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
GraceTimeOut int64 `short:"g" description:"Duration to give active requests a chance to finish during hot-reload"`
Debug bool `short:"d" description:"Enable debug mode"`
AccessLogsFile string `description:"Access logs file"`
TraefikLogsFile string `description:"Traefik logs file"`
LogLevel string `short:"l" description:"Log level"`
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"`
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags."`
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
ProvidersThrottleDuration time.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time."`
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"`
Retry *Retry `description:"Enable retry sending request if network error"`
Docker *provider.Docker `description:"Enable Docker backend"`
File *provider.File `description:"Enable File backend"`
Web *WebProvider `description:"Enable Web backend"`
Marathon *provider.Marathon `description:"Enable Marathon backend"`
Consul *provider.Consul `description:"Enable Consul backend"`
ConsulCatalog *provider.ConsulCatalog `description:"Enable Consul catalog backend"`
Etcd *provider.Etcd `description:"Enable Etcd backend"`
Zookeeper *provider.Zookepper `description:"Enable Zookeeper backend"`
Boltdb *provider.BoltDb `description:"Enable Boltdb backend"`
Kubernetes *provider.Kubernetes `description:"Enable Kubernetes backend"`
}
// DefaultEntryPoints holds default entry points
@@ -44,7 +50,7 @@ type DefaultEntryPoints []string
// 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)
return strings.Join(*dep, ",")
}
// Set is the method to set the flag value, part of the flag.Value interface.
@@ -61,9 +67,17 @@ func (dep *DefaultEntryPoints) Set(value string) error {
return nil
}
// Get return the EntryPoints map
func (dep *DefaultEntryPoints) Get() interface{} { return DefaultEntryPoints(*dep) }
// SetValue sets the EntryPoints map with val
func (dep *DefaultEntryPoints) SetValue(val interface{}) {
*dep = DefaultEntryPoints(val.(DefaultEntryPoints))
}
// Type is type of the struct
func (dep *DefaultEntryPoints) Type() string {
return fmt.Sprint("defaultentrypoints²")
return fmt.Sprint("defaultentrypoints")
}
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
@@ -72,14 +86,14 @@ 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 ""
return fmt.Sprintf("%+v", *ep)
}
// 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*))?")
regex := regexp.MustCompile("(?:Name:(?P<Name>\\S*))\\s*(?:Address:(?P<Address>\\S*))?\\s*(?:TLS:(?P<TLS>\\S*))?\\s*((?P<TLSACME>TLS))?\\s*(?:Redirect.EntryPoint:(?P<RedirectEntryPoint>\\S*))?\\s*(?:Redirect.Regex:(?P<RedirectRegex>\\S*))?\\s*(?:Redirect.Replacement:(?P<RedirectReplacement>\\S*))?")
match := regex.FindAllStringSubmatch(value, -1)
if match == nil {
return errors.New("Bad EntryPoints format: " + value)
@@ -100,6 +114,10 @@ func (ep *EntryPoints) Set(value string) error {
tls = &TLS{
Certificates: certs,
}
} else if len(result["TLSACME"]) > 0 {
tls = &TLS{
Certificates: Certificates{},
}
}
var redirect *Redirect
if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 {
@@ -119,9 +137,17 @@ func (ep *EntryPoints) Set(value string) error {
return nil
}
// Get return the EntryPoints map
func (ep *EntryPoints) Get() interface{} { return EntryPoints(*ep) }
// SetValue sets the EntryPoints map with val
func (ep *EntryPoints) SetValue(val interface{}) {
*ep = EntryPoints(val.(EntryPoints))
}
// Type is type of the struct
func (ep *EntryPoints) Type() string {
return fmt.Sprint("entrypoints²")
return fmt.Sprint("entrypoints")
}
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
@@ -182,110 +208,107 @@ type Certificate struct {
KeyFile string
}
// NewGlobalConfiguration returns a GlobalConfiguration with default values.
func NewGlobalConfiguration() *GlobalConfiguration {
return new(GlobalConfiguration)
// Retry contains request retry config
type Retry struct {
Attempts int `description:"Number of attempts"`
}
// 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)
}
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
//default Docker
var defaultDocker provider.Docker
defaultDocker.Watch = true
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
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 {
// default File
var defaultFile provider.File
defaultFile.Watch = true
defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed()
fmtlog.Fatalf("Error reading file: %s", err)
}
// default Web
var defaultWeb WebProvider
defaultWeb.Address = ":8080"
if len(configuration.EntryPoints) == 0 {
configuration.EntryPoints = make(map[string]*EntryPoint)
configuration.EntryPoints["http"] = &EntryPoint{
Address: ":80",
}
configuration.DefaultEntryPoints = []string{"http"}
}
// default Marathon
var defaultMarathon provider.Marathon
defaultMarathon.Watch = true
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
defaultMarathon.ExposedByDefault = true
defaultMarathon.Constraints = []types.Constraint{}
if configuration.File != nil && len(configuration.File.Filename) == 0 {
// no filename, setting to global config file
configuration.File.Filename = viper.ConfigFileUsed()
}
// default Consul
var defaultConsul provider.Consul
defaultConsul.Watch = true
defaultConsul.Endpoint = "127.0.0.1:8500"
defaultConsul.Prefix = "traefik"
defaultConsul.Constraints = []types.Constraint{}
return configuration
// default ConsulCatalog
var defaultConsulCatalog provider.ConsulCatalog
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
defaultConsulCatalog.Constraints = []types.Constraint{}
// default Etcd
var defaultEtcd provider.Etcd
defaultEtcd.Watch = true
defaultEtcd.Endpoint = "127.0.0.1:2379"
defaultEtcd.Prefix = "/traefik"
defaultEtcd.Constraints = []types.Constraint{}
//default Zookeeper
var defaultZookeeper provider.Zookepper
defaultZookeeper.Watch = true
defaultZookeeper.Endpoint = "127.0.0.1:2181"
defaultZookeeper.Prefix = "/traefik"
defaultZookeeper.Constraints = []types.Constraint{}
//default Boltdb
var defaultBoltDb provider.BoltDb
defaultBoltDb.Watch = true
defaultBoltDb.Endpoint = "127.0.0.1:4001"
defaultBoltDb.Prefix = "/traefik"
defaultBoltDb.Constraints = []types.Constraint{}
//default Kubernetes
var defaultKubernetes provider.Kubernetes
defaultKubernetes.Watch = true
defaultKubernetes.Endpoint = "http://127.0.0.1:8080"
defaultKubernetes.Constraints = []types.Constraint{}
defaultConfiguration := GlobalConfiguration{
Docker: &defaultDocker,
File: &defaultFile,
Web: &defaultWeb,
Marathon: &defaultMarathon,
Consul: &defaultConsul,
ConsulCatalog: &defaultConsulCatalog,
Etcd: &defaultEtcd,
Zookeeper: &defaultZookeeper,
Boltdb: &defaultBoltDb,
Kubernetes: &defaultKubernetes,
Retry: &Retry{},
}
return &TraefikConfiguration{
GlobalConfiguration: defaultConfiguration,
}
}
func unmarshal(rawVal interface{}) error {
config := &mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
Metadata: nil,
Result: rawVal,
WeaklyTypedInput: true,
// NewTraefikConfiguration creates a TraefikConfiguration with default values
func NewTraefikConfiguration() *TraefikConfiguration {
return &TraefikConfiguration{
GlobalConfiguration: GlobalConfiguration{
GraceTimeOut: 10,
AccessLogsFile: "",
TraefikLogsFile: "",
LogLevel: "ERROR",
EntryPoints: map[string]*EntryPoint{},
Constraints: []types.Constraint{},
DefaultEntryPoints: []string{},
ProvidersThrottleDuration: time.Duration(2 * time.Second),
MaxIdleConnsPerHost: 200,
},
ConfigFile: "",
}
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

1
docs/CNAME Normal file
View File

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

267
docs/basics.md Normal file
View File

@@ -0,0 +1,267 @@
# 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 forwarded 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` and `https` on port `443`.
- We enable SSL on `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 use multiple rules by separating them by `;`
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
priority = 10
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host:localhost,{subdomain:[a-z]+}.localhost"
[frontends.frontend3]
backend = "backend2"
[frontends.frontend3.routes.test_1]
rule = "Host:test3.localhost;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 rules `Host:test3.localhost` **AND** `Path:/test` are matched
### Combining multiple rules
As seen in the previous example, you can combine multiple rules.
In TOML file, you can use multiple routes:
```toml
[frontends.frontend3]
backend = "backend2"
[frontends.frontend3.routes.test_1]
rule = "Host:test3.localhost"
[frontends.frontend3.routes.test_2]
rule = "Host:Path:/test"
```
Here `frontend3` will forward the traffic to the `backend2` if the rules `Host:test3.localhost` **AND** `Path:/test` are matched.
You can also use the notation using a `;` separator, same result:
```toml
[frontends.frontend3]
backend = "backend2"
[frontends.frontend3.routes.test_1]
rule = "Host:test3.localhost;Path:/test"
```
Finally, you can create a rule to bind multiple domains or Path to a frontend, using the `,` separator:
```toml
[frontends.frontend2]
[frontends.frontend2.routes.test_1]
rule = "Host:test1.localhost,test2.localhost"
[frontends.frontend3]
backend = "backend2"
[frontends.frontend3.routes.test_1]
rule = "Path:/test1,/test2"
```
### Priorities
By default, routes will be sorted (in descending order) using rules length (to avoid path overlap):
`PathPrefix:/12345` will be matched before `PathPrefix:/1234` that will be matched before `PathPrefix:/1`.
You can customize priority by frontend:
```
[frontends]
[frontends.frontend1]
backend = "backend1"
priority = 10
passHostHeader = true
[frontends.frontend1.routes.test_1]
rule = "PathPrefix:/to"
[frontends.frontend2]
priority = 5
backend = "backend2"
passHostHeader = true
[frontends.frontend2.routes.test_1]
rule = "PathPrefix:/toto"
```
Here, `frontend1` will be matched before `frontend2` (`10 > 5`).
## 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 expires, 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)
To proactively prevent backends from being overwhelmed with high load, a maximum connection limit can
also be applied to each backend.
Maximum connections can be configured by specifying an integer value for `maxconn.amount` and
`maxconn.extractorfunc` which is a strategy used to determine how to categorize requests in order to
evaluate the maximum connections.
For example:
```toml
[backends]
[backends.backend1]
[backends.backend1.maxconn]
amount = 10
extractorfunc = "request.host"
```
- `backend1` will return `HTTP code 429 Too Many Requests` if there are already 10 requests in progress for the same Host header.
- Another possible value for `extractorfunc` is `client.ip` which will categorize requests based on client source ip.
- Lastly `extractorfunc` can take the value of `request.header.ANY_HEADER` which will categorize requests based on `ANY_HEADER` that you provide.
## Servers
Servers are simply defined using a `URL`. You can also apply a custom `weight` to each server (this will be used by load-balancing).
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
```

213
docs/benchmarks.md Normal file
View File

@@ -0,0 +1,213 @@
# 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 -t20 -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 -t20 -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)

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

@@ -0,0 +1,61 @@
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;
}
.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:hover, .navbar-default .navbar-nav>.open>a:focus {
color: #fff;
background-color: #25606F;
}
.dropdown-menu>li>a:hover, .dropdown-menu>li>a:focus {
color: #fff;
text-decoration: none;
background-color: #25606F;
}
.dropdown-menu>.active>a, .dropdown-menu>.active>a:hover, .dropdown-menu>.active>a:focus {
color: #fff;
text-decoration: none;
background-color: #25606F;
outline: 0;
}

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/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

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.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

File diff suppressed because it is too large Load Diff

993
docs/toml.md Normal file
View File

@@ -0,0 +1,993 @@
# 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"
[entryPoints]
[entryPoints.http]
address = ":80"
```
## Retry configuration
```toml
# Enable retry sending request if network error
#
# Optional
#
[retry]
# Number of attempts
#
# Optional
# Default: (number servers in backend) -1
#
# attempts = 3
```
## ACME (Let's Encrypt) configuration
```toml
# Sample entrypoint configuration when using ACME
[entryPoints]
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
# 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, you have 2 options:
# - create a file on your host and mount it has a volume
# storageFile = "acme.json"
# $ docker run -v "/my/host/acme.json:acme.json" traefik
# - mount the folder containing the file has a volume
# storageFile = "/etc/traefik/acme/acme.json"
# $ docker run -v "/my/host/acme:/etc/traefik/acme" traefik
#
# 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
# All domains must have A/AAAA records pointing to Traefik
# WARNING, Take note that Let's Encrypt have rate limiting: https://community.letsencrypt.org/t/quick-start-guide/1631
# 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"
[[acme.domains]]
main = "local1.com"
sans = ["test1.local1.com", "test2.local1.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.backend1.maxconn]
amount = 10
extractorfunc = "request.host"
[backends.backend2.LoadBalancer]
method = "drr"
[backends.backend2.servers.server1]
url = "http://172.17.0.4:80"
weight = 1
[backends.backend2.servers.server2]
url = "http://172.17.0.5:80"
weight = 2
[frontends]
[frontends.frontend1]
backend = "backend2"
[frontends.frontend1.routes.test_1]
rule = "Host:test.localhost"
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
priority = 10
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host:{subdomain:[a-z]+}.localhost"
[frontends.frontend3]
entrypoints = ["http", "https"] # overrides defaultEntryPoints
backend = "backend2"
rule = "Path:/test"
```
- or put your rules in a separate file, for example `rules.toml`:
```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.backend1.maxconn]
amount = 10
extractorfunc = "request.host"
[backends.backend2.LoadBalancer]
method = "drr"
[backends.backend2.servers.server1]
url = "http://172.17.0.4:80"
weight = 1
[backends.backend2.servers.server2]
url = "http://172.17.0.5:80"
weight = 2
[frontends]
[frontends.frontend1]
backend = "backend2"
[frontends.frontend1.routes.test_1]
rule = "Host:test.localhost"
[frontends.frontend2]
backend = "backend1"
passHostHeader = true
priority = 10
entrypoints = ["https"] # overrides defaultEntryPoints
[frontends.frontend2.routes.test_1]
rule = "Host:{subdomain:[a-z]+}.localhost"
[frontends.frontend3]
entrypoints = ["http", "https"] # overrides defaultEntryPoints
backend = "backend2"
rule = "Path:/test"
```
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}`).
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.priority=10`: override default frontend priority
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
- `traefik.docker.network`: Set the docker network to use for connections to this container
## 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.
#
# 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
# Convert Marathon groups to subdomains
# Default behavior: /foo/bar/myapp => foo-bar-myapp.{defaultDomain}
# with groupsAsSubDomains enabled: /foo/bar/myapp => myapp.bar.foo.{defaultDomain}
#
# Optional
# Default: false
#
# groupsAsSubDomains = 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
# DCOSToken for DCOS environment, This will override the Authorization header
#
# Optional
#
# dcosToken = "xxxxxx"
```
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}`).
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.priority=10`: override default frontend priority
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
## Kubernetes Ingress backend
Træfɪk can be configured to use Kubernetes Ingress as a backend configuration:
```toml
################################################################
# Kubernetes Ingress configuration backend
################################################################
# Enable Kubernetes Ingress configuration backend
#
# Optional
#
[kubernetes]
# Kubernetes server endpoint
#
# When deployed as a replication controller in Kubernetes,
# Traefik will use env variable KUBERNETES_SERVICE_HOST
# and KUBERNETES_SERVICE_PORT_HTTPS as endpoint
# Secure token will be found in /var/run/secrets/kubernetes.io/serviceaccount/token
# and SSL CA cert in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
#
# Optional
#
# endpoint = "http://localhost:8080"
# namespaces = ["default","production"]
```
Annotations can be used on containers to override default behaviour for the whole Ingress resource:
- `traefik.frontend.rule.type: PathPrefixStrip`: override the default frontend rule type (Default: `PathPrefix`).
You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.rc.yaml).
## 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"
# Prefix for Consul catalog tags
#
# Optional
#
prefix = "traefik"
```
This backend will create routes matching on hostname based on the service name
used in consul.
Additional settings can be defined using Consul Catalog tags:
- `traefik.enable=false`: disable this container in Træfɪk
- `traefik.protocol=https`: override the default `http` protocol
- `traefik.backend.weight=10`: assign this weight to the container
- `traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5`
- `traefik.backend.loadbalancer=drr`: override the default load balancing mode
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
- `traefik.frontend.priority=10`: override default frontend priority
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
## 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:2379"
# 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/maxconn/amount` | `10` |
| `/traefik/backends/backend2/maxconn/extractorfunc` | `request.host` |
| `/traefik/backends/backend2/loadbalancer/method` | `drr` |
| `/traefik/backends/backend2/servers/server1/url` | `http://172.17.0.4:80` |
| `/traefik/backends/backend2/servers/server1/weight` | `1` |
| `/traefik/backends/backend2/servers/server2/url` | `http://172.17.0.5:80` |
| `/traefik/backends/backend2/servers/server2/weight` | `2` |
- frontend 1
| Key | Value |
|---------------------------------------------------|-----------------------|
| `/traefik/frontends/frontend1/backend` | `backend2` |
| `/traefik/frontends/frontend1/routes/test_1/rule` | `Host:test.localhost` |
- frontend 2
| Key | Value |
|----------------------------------------------------|--------------------|
| `/traefik/frontends/frontend2/backend` | `backend1` |
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
| `/traefik/frontends/frontend2/priority` | `10` |
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
## Atomic configuration changes
The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. As a result, it may be possible for Træfɪk to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. To solve this problem, Træfɪk supports a special key called `/traefik/alias`. If set, Træfɪk use the value as an alternative key prefix.
Given the key structure below, Træfɪk will use the `http://172.17.0.2:80` as its only backend (frontend keys have been omitted for brevity).
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/1` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
When an atomic configuration change is required, you may write a new configuration at an alternative prefix. Here, although the `/traefik_configurations/2/...` keys have been set, the old configuration is still active because the `/traefik/alias` key still points to `/traefik_configurations/1`:
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/1` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` configuration becomes active atomically. Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://172.17.0.4:80` hosts while no traffic is sent to the `172.17.0.2:80` host:
| Key | Value |
|-------------------------------------------------------------------------|-----------------------------|
| `/traefik/alias` | `/traefik_configurations/2` |
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.3:80` |
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` |
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored.

View File

@@ -0,0 +1,98 @@
# Examples
You will find here some configuration examples of Træfɪk.
## 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"
```

170
docs/user-guide/swarm.md Normal file
View File

@@ -0,0 +1,170 @@
# Swarm cluster
This section explains how to create a multi-host [swarm](https://docs.docker.com/swarm) cluster using [docker-machine](https://docs.docker.com/machine/) and how to deploy Træfɪk on it.
The cluster will be made of:
- 2 servers
- 1 swarm master
- 2 swarm nodes
- 1 [overlay](https://docs.docker.com/engine/userguide/networking/dockernetworks/#an-overlay-network) network (multi-host networking)
## Prerequisites
1. You will need to install [docker-machine](https://docs.docker.com/machine/)
2. You will need the latest [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
## Cluster provisioning
We will first follow [this guide](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) to create the cluster.
### Create machine `mh-keystore`
This machine will be the service registry of our cluster.
```sh
docker-machine create -d virtualbox mh-keystore
```
Then we install the service registry [Consul](https://consul.io) on this machine:
```sh
eval "$(docker-machine env mh-keystore)"
docker run -d \
-p "8500:8500" \
-h "consul" \
progrium/consul -server -bootstrap
```
### Create machine `mhs-demo0`
This machine will have a swarm master and a swarm agent on it.
```sh
docker-machine create -d virtualbox \
--swarm --swarm-master \
--swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip mh-keystore):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
mhs-demo0
```
### Create machine `mhs-demo1`
This machine will have a swarm agent on it.
```sh
docker-machine create -d virtualbox \
--swarm \
--swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip mh-keystore):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
mhs-demo1
```
### Create the overlay Network
Create the overlay network on the swarm master:
```sh
eval $(docker-machine env --swarm mhs-demo0)
docker network create --driver overlay --subnet=10.0.9.0/24 my-net
```
## Deploy Træfɪk
Deploy Træfɪk:
```sh
docker $(docker-machine config mhs-demo0) run \
-d \
-p 80:80 -p 8080:8080 \
--net=my-net \
-v /var/lib/boot2docker/:/ssl \
traefik \
-l DEBUG \
-c /dev/null \
--docker \
--docker.domain traefik \
--docker.endpoint tcp://$(docker-machine ip mhs-demo0):3376 \
--docker.tls \
--docker.tls.ca /ssl/ca.pem \
--docker.tls.cert /ssl/server.pem \
--docker.tls.key /ssl/server-key.pem \
--docker.tls.insecureSkipVerify \
--docker.watch \
--web
```
Let's explain this command:
- `-p 80:80 -p 8080:8080`: we bind ports 80 and 8080
- `--net=my-net`: run the container on the network my-net
- `-v /var/lib/boot2docker/:/ssl`: mount the ssl keys generated by docker-machine
- `-c /dev/null`: empty config file
- `--docker`: enable docker backend
- `--docker.endpoint tcp://172.18.0.1:3376`: connect to the swarm master using the docker_gwbridge network
- `--docker.tls`: enable TLS using the docker-machine keys
- `--web`: activate the webUI on port 8080
## Deploy your apps
We can now deploy our app on the cluster, here [whoami](https://github.com/emilevauge/whoami), a simple web server in GO, on the network `my-net`:
```sh
eval $(docker-machine env --swarm mhs-demo0)
docker run -d --name=whoami0 --net=my-net --env="constraint:node==mhs-demo0" emilevauge/whoami
docker run -d --name=whoami1 --net=my-net --env="constraint:node==mhs-demo1" emilevauge/whoami
```
Check that everything is started:
```sh
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ba2c21488299 emilevauge/whoami "/whoamI" 8 seconds ago Up 9 seconds 80/tcp mhs-demo1/whoami1
8147a7746e7a emilevauge/whoami "/whoamI" 19 seconds ago Up 20 seconds 80/tcp mhs-demo0/whoami0
8fbc39271b4c traefik "/traefik -l DEBUG -c" 36 seconds ago Up 37 seconds 192.168.99.101:80->80/tcp, 192.168.99.101:8080->8080/tcp mhs-demo0/serene_bhabha
```
## Access to your apps through Træfɪk
```sh
curl -H Host:whoami0.traefik http://$(docker-machine ip mhs-demo0)
Hostname: 8147a7746e7a
IP: 127.0.0.1
IP: ::1
IP: 10.0.9.3
IP: fe80::42:aff:fe00:903
IP: 172.18.0.3
IP: fe80::42:acff:fe12:3
GET / HTTP/1.1
Host: 10.0.9.3:80
User-Agent: curl/7.35.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 192.168.99.1
X-Forwarded-Host: 10.0.9.3:80
X-Forwarded-Proto: http
X-Forwarded-Server: 8fbc39271b4c
curl -H Host:whoami1.traefik http://$(docker-machine ip mhs-demo0)
Hostname: ba2c21488299
IP: 127.0.0.1
IP: ::1
IP: 10.0.9.4
IP: fe80::42:aff:fe00:904
IP: 172.18.0.2
IP: fe80::42:acff:fe12:2
GET / HTTP/1.1
Host: 10.0.9.4:80
User-Agent: curl/7.35.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 192.168.99.1
X-Forwarded-Host: 10.0.9.4:80
X-Forwarded-Proto: http
X-Forwarded-Server: 8fbc39271b4c
```
![](http://i.giphy.com/ujUdrdpX7Ok5W.gif)

2
examples/accessLog/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
exampleHandler
exampleHandler.exe

View File

@@ -0,0 +1,46 @@
/*
Simple program to start a web server on a specified port
*/
package main
import (
"flag"
"fmt"
"net/http"
"os"
)
var (
name string
port int
help *bool
)
func init() {
flag.StringVar(&name, "n", "", "Name of handler for messages")
flag.IntVar(&port, "p", 0, "Port number to listen")
help = flag.Bool("h", false, "Displays help message")
}
func usage() {
fmt.Printf("Usage: example -n name -p port \n")
os.Exit(2)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s: Received query %s!\n", name, r.URL.Path[1:])
}
func main() {
flag.Parse()
if *help || len(name) == 0 || port <= 0 {
usage()
}
http.HandleFunc("/", handler)
fmt.Printf("%s: Listening on :%d...\n", name, port)
if er := http.ListenAndServe(fmt.Sprintf(":%d", port), nil); er != nil {
fmt.Printf("%s: Error from ListenAndServe: %s", name, er.Error())
os.Exit(1)
}
fmt.Printf("%s: How'd we get past listen and serve???\n", name)
}

122
examples/accessLog/runAb.sh Executable file
View File

@@ -0,0 +1,122 @@
#!/bin/bash
usage()
{
echo 'runAb.sh - Run Apache Benchmark to test access log'
echo ' Usage: runAb.sh [--conn nnn] [--log xxx] [--num nnn] [--time nnn] [--wait nn]'
echo ' -c|--conn - number of simultaneous connections (default 100)'
echo ' -l|--log - name of logfile (default benchmark.log)'
echo ' -n|--num - number of requests (default 50000); ignored when -t specified'
echo ' -t|--time - time in seconds for benchmark (default no limit)'
echo ' -w|--wait - number of seconds to wait for Traefik to initialize (default 15)'
echo ' '
exit
}
# Parse options
conn=100
num=50000
wait=15
time=0
logfile=""
while [[ $1 =~ ^- ]]
do
case $1 in
-c|--conn)
conn=$2
shift
;;
-h|--help)
usage
;;
-l|--log|--logfile)
logfile=$2
shift
;;
-n|--num)
num=$2
shift
;;
-t|--time)
time=$2
shift
;;
-w|--wait)
wait=$2
shift
;;
*)
echo Unknown option "$1"
usage
esac
shift
done
if [ -z "$logfile" ] ; then
logfile="benchmark.log"
fi
# Change to accessLog examples directory
[ -d examples/accessLog ] && cd examples/accessLog
if [ ! -r exampleHandler.go ] ; then
echo Please run this script either from the traefik repo root or from the examples/accessLog directory
exit
fi
# Kill traefik and any running example processes
sudo pkill -f traefik
pkill -f exampleHandler
[ ! -d log ] && mkdir log
# Start new example processes
go build exampleHandler.go
[ $? -ne 0 ] && exit $?
./exampleHandler -n Handler1 -p 8081 &
[ $? -ne 0 ] && exit $?
./exampleHandler -n Handler2 -p 8082 &
[ $? -ne 0 ] && exit $?
./exampleHandler -n Handler3 -p 8083 &
[ $? -ne 0 ] && exit $?
# Wait a couple of seconds for handlers to initialize and start Traefik
cd ../..
sleep 2s
echo Starting Traefik...
sudo ./traefik -c examples/accessLog/traefik.ab.toml &
[ $? -ne 0 ] && exit $?
# Wait for Traefik to initialize and run ab
echo Waiting $wait seconds before starting ab benchmark
sleep ${wait}s
echo
stime=`date '+%s'`
if [ $time -eq 0 ] ; then
echo Benchmark starting `date` with $conn connections until $num requests processed | tee $logfile
echo | tee -a $logfile
echo ab -k -c $conn -n $num http://127.0.0.1/test | tee -a $logfile
echo | tee -a $logfile
ab -k -c $conn -n $num http://127.0.0.1/test 2>&1 | tee -a $logfile
else
if [ $num -ne 50000 ] ; then
echo Request count ignored when --time specified
fi
echo Benchmark starting `date` with $conn connections for $time seconds | tee $logfile
echo | tee -a $logfile
echo ab -k -c $conn -t $time -n 100000000 http://127.0.0.1/test | tee -a $logfile
echo | tee -a $logfile
ab -k -c $conn -t $time -n 100000000 http://127.0.0.1/test 2>&1 | tee -a $logfile
fi
etime=`date '+%s'`
let "dt=$etime - $stime"
let "ds=$dt % 60"
let "dm=($dt / 60) % 60"
let "dh=$dt / 3600"
echo | tee -a $logfile
printf "Benchmark ended `date` after %d:%02d:%02d\n" $dh $dm $ds | tee -a $logfile
echo Results available in $logfile

View File

@@ -0,0 +1,40 @@
#!/bin/bash
# Script to run a three-server example. This script runs the three servers and restarts Traefik
# Once it is running, use the command:
#
# curl http://127.0.0.1:80/test{1,2,2}
#
# to send requests to send test requests to the servers. You should see a response like:
#
# Handler1: received query test1!
# Handler2: received query test2!
# Handler3: received query test2!
#
# and can then inspect log/access.log to see frontend, backend, and timing
# Kill traefik and any running example processes
sudo pkill -f traefik
pkill -f exampleHandler
[ ! -d log ] && mkdir log
# Start new example processes
cd examples/accessLog
go build exampleHandler.go
[ $? -ne 0 ] && exit $?
./exampleHandler -n Handler1 -p 8081 &
[ $? -ne 0 ] && exit $?
./exampleHandler -n Handler2 -p 8082 &
[ $? -ne 0 ] && exit $?
./exampleHandler -n Handler3 -p 8083 &
[ $? -ne 0 ] && exit $?
# Wait a couple of seconds for handlers to initialize and start Traefik
cd ../..
sleep 2s
echo Starting Traefik...
sudo ./traefik -c examples/accessLog/traefik.example.toml &
[ $? -ne 0 ] && exit $?
echo Sample handlers and traefik started successfully!
echo 'Use command curl http://127.0.0.1:80/test{1,2,2} to drive test'
echo Then inspect log/access.log to verify it contains frontend, backend, and timing

View File

@@ -0,0 +1,37 @@
################################################################
# Global configuration
################################################################
traefikLogsFile = "log/traefik.log"
accessLogsFile = "log/access.log"
logLevel = "DEBUG"
################################################################
# Web configuration backend
################################################################
[web]
address = ":7888"
################################################################
# File configuration backend
################################################################
[file]
################################################################
# rules
################################################################
[backends]
[backends.backend]
[backends.backend.LoadBalancer]
method = "drr"
[backends.backend.servers.server1]
url = "http://127.0.0.1:8081"
[backends.backend.servers.server2]
url = "http://127.0.0.1:8082"
[backends.backend.servers.server3]
url = "http://127.0.0.1:8083"
[frontends]
[frontends.frontend]
backend = "backend"
passHostHeader = true
[frontends.frontend.routes.test]
rule = "Path: /test"

View File

@@ -0,0 +1,42 @@
################################################################
# Global configuration
################################################################
traefikLogsFile = "log/traefik.log"
accessLogsFile = "log/access.log"
logLevel = "DEBUG"
################################################################
# Web configuration backend
################################################################
[web]
address = ":7888"
################################################################
# File configuration backend
################################################################
[file]
################################################################
# rules
################################################################
[backends]
[backends.backend1]
[backends.backend1.servers.server1]
url = "http://127.0.0.1:8081"
[backends.backend2]
[backends.backend2.LoadBalancer]
method = "drr"
[backends.backend2.servers.server1]
url = "http://127.0.0.1:8082"
[backends.backend2.servers.server2]
url = "http://127.0.0.1:8083"
[frontends]
[frontends.frontend1]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Path: /test1"
[frontends.frontend2]
backend = "backend2"
passHostHeader = true
[frontends.frontend2.routes.test_2]
rule = "Path: /test2"

View File

@@ -0,0 +1,25 @@
version: '2'
services:
consul:
image: progrium/consul
command: -server -bootstrap -advertise 12.0.0.254 -log-level debug -ui-dir /ui
ports:
- "8400:8400"
- "8500:8500"
- "8600:53/udp"
expose:
- "8300"
- "8301"
- "8301/udp"
- "8302"
- "8302/udp"
registrator:
depends_on:
- consul
image: gliderlabs/registrator:master
command: -internal consul://consul:8500
volumes:
- /var/run/docker.sock:/tmp/docker.sock
links:
- consul

View File

@@ -0,0 +1,4 @@
etcd:
image: gcr.io/google_containers/etcd:2.2.1
net: host
command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:2379', '--bind-addr=0.0.0.0:2379', '--data-dir=/var/etcd/data']

12
examples/compose-k8s.yaml Normal file
View File

@@ -0,0 +1,12 @@
kubelet:
image: gcr.io/google_containers/hyperkube-amd64:v1.2.2
privileged: true
pid: host
net : host
volumes:
- /:/rootfs:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:rw
- /var/lib/kubelet/:/var/lib/kubelet:rw
- /var/run:/var/run:rw
command: ['/hyperkube', 'kubelet', '--containerized', '--hostname-override=127.0.0.1', '--address=0.0.0.0', '--api-servers=http://localhost:8080', '--config=/etc/kubernetes/manifests', '--allow-privileged=true', '--v=2']

View File

@@ -6,7 +6,7 @@ zk:
ZK_ID: 1
master:
image: mesosphere/mesos-master:0.26.0-0.2.145.ubuntu1404
image: mesosphere/mesos-master:0.28.1-2.0.20.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.26.0-0.2.145.ubuntu1404
image: mesosphere/mesos-slave:0.28.1-2.0.20.ubuntu1404
net: host
pid: host
privileged: true
@@ -34,7 +34,7 @@ slave:
- /lib/x86_64-linux-gnu/libsystemd-journal.so.0:/lib/x86_64-linux-gnu/libsystemd-journal.so.0
marathon:
image: mesosphere/marathon:v0.13.0
image: mesosphere/marathon:v1.1.1
net: host
environment:
MARATHON_MASTER: zk://127.0.0.1:2181/mesos

View File

@@ -0,0 +1,20 @@
traefik:
image: traefik
command: -c /dev/null --web --docker --docker.domain=docker.localhost --logLevel=DEBUG
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
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

@@ -17,11 +17,9 @@ 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
curl -i -H "Accept: application/json" -X PUT -d "Host:test.localhost" http://localhost:8500/v1/kv/traefik/frontends/frontend1/routes/test_1/rule
# 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
curl -i -H "Accept: application/json" -X PUT -d "http" http://localhost:8500/v1/kv/traefik/frontends/frontend2/entrypoints
curl -i -H "Accept: application/json" -X PUT -d "Path:/test" http://localhost:8500/v1/kv/traefik/frontends/frontend2/routes/test_2/rule

25
examples/etcd-config.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/sh
# backend 1
curl -i -H "Accept: application/json" -X PUT -d value="NetworkErrorRatio() > 0.5" http://localhost:2379/v2/keys/traefik/backends/backend1/circuitbreaker/expression
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.2:80" http://localhost:2379/v2/keys/traefik/backends/backend1/servers/server1/url
curl -i -H "Accept: application/json" -X PUT -d value="10" http://localhost:2379/v2/keys/traefik/backends/backend1/servers/server1/weight
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.3:80" http://localhost:2379/v2/keys/traefik/backends/backend1/servers/server2/url
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:2379/v2/keys/traefik/backends/backend1/servers/server2/weight
# backend 2
curl -i -H "Accept: application/json" -X PUT -d value="drr" http://localhost:2379/v2/keys/traefik/backends/backend2/loadbalancer/method
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.4:80" http://localhost:2379/v2/keys/traefik/backends/backend2/servers/server1/url
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:2379/v2/keys/traefik/backends/backend2/servers/server1/weight
curl -i -H "Accept: application/json" -X PUT -d value="http://172.17.0.5:80" http://localhost:2379/v2/keys/traefik/backends/backend2/servers/server2/url
curl -i -H "Accept: application/json" -X PUT -d value="2" http://localhost:2379/v2/keys/traefik/backends/backend2/servers/server2/weight
# frontend 1
curl -i -H "Accept: application/json" -X PUT -d value="backend2" http://localhost:2379/v2/keys/traefik/frontends/frontend1/backend
curl -i -H "Accept: application/json" -X PUT -d value="http" http://localhost:2379/v2/keys/traefik/frontends/frontend1/entrypoints
curl -i -H "Accept: application/json" -X PUT -d value="Host:test.localhost" http://localhost:2379/v2/keys/traefik/frontends/frontend1/routes/test_1/rule
# frontend 2
curl -i -H "Accept: application/json" -X PUT -d value="backend1" http://localhost:2379/v2/keys/traefik/frontends/frontend2/backend
curl -i -H "Accept: application/json" -X PUT -d value="http" http://localhost:2379/v2/keys/traefik/frontends/frontend2/entrypoints
curl -i -H "Accept: application/json" -X PUT -d value="Path:/test" http://localhost:2379/v2/keys/traefik/frontends/frontend2/routes/test_2/rule

111
examples/k8s.ingress.yaml Normal file
View File

@@ -0,0 +1,111 @@
# 3 Services for the 3 endpoints of the Ingress
apiVersion: v1
kind: Service
metadata:
name: service1
labels:
app: whoami
spec:
type: NodePort
ports:
- port: 80
nodePort: 30283
targetPort: 80
protocol: TCP
name: https
selector:
app: whoami
---
apiVersion: v1
kind: Service
metadata:
name: service2
labels:
app: whoami
spec:
type: NodePort
ports:
- port: 80
nodePort: 30284
targetPort: 80
protocol: TCP
name: http
selector:
app: whoami
---
apiVersion: v1
kind: Service
metadata:
name: service3
labels:
app: whoami
spec:
type: NodePort
ports:
- port: 80
nodePort: 30285
targetPort: 80
protocol: TCP
name: http
selector:
app: whoami
---
# A single RC matching all Services
apiVersion: v1
kind: ReplicationController
metadata:
name: whoami
spec:
replicas: 1
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: emilevauge/whoami
ports:
- containerPort: 80
---
# An Ingress with 2 hosts and 3 endpoints
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: whoami-ingress
spec:
rules:
- host: foo.localhost
http:
paths:
- path: /bar
backend:
serviceName: service1
servicePort: 80
- host: bar.localhost
http:
paths:
- backend:
serviceName: service2
servicePort: 80
- backend:
serviceName: service3
servicePort: 80
---
# Another Ingress with PathPrefixStrip
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: whoami-ingress-stripped
annotations:
traefik.frontend.rule.type: "PathPrefixStrip"
spec:
rules:
- host: foo.localhost
http:
paths:
- path: /prefixWillBeStripped
backend:
serviceName: service1
servicePort: 80

10
examples/k8s.namespace.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
kubectl create -f - << EOF
kind: Namespace
apiVersion: v1
metadata:
name: kube-system
labels:
name: kube-system
EOF

31
examples/k8s.rc.yaml Normal file
View File

@@ -0,0 +1,31 @@
apiVersion: v1
kind: ReplicationController
metadata:
name: traefik-ingress-controller
labels:
k8s-app: traefik-ingress-lb
spec:
replicas: 1
selector:
k8s-app: traefik-ingress-lb
template:
metadata:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
spec:
terminationGracePeriodSeconds: 60
containers:
- image: traefik
name: traefik-ingress-lb
imagePullPolicy: Always
ports:
- containerPort: 80
hostPort: 80
- containerPort: 443
hostPort: 443
- containerPort: 8080
args:
- --web
- --kubernetes
- --logLevel=DEBUG

View File

@@ -0,0 +1,40 @@
{
"id": "/foo",
"groups": [
{
"id": "/foo/bar",
"apps": [
{
"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
}
]
}
]
}
]
}

View File

@@ -25,6 +25,8 @@
],
"labels": {
"traefik.weight": "1",
"traefik.protocole": "http"
"traefik.protocol": "http",
"traefik.frontend.rule" : "Host:test.marathon.localhost",
"traefik.frontend.priority" : "10"
}
}

View File

@@ -3,6 +3,7 @@ Copyright
*/
//go:generate rm -vf autogen/gen.go
//go:generate mkdir -p static
//go:generate go-bindata -pkg autogen -o autogen/gen.go ./static/... ./templates/...
//go:generate mkdir -p vendor/github.com/docker/docker/autogen/dockerversion

382
glide.lock generated
View File

@@ -1,270 +1,308 @@
hash: 7734b691c46b399a06cdcaa5d7feb77ea32e350cd4ff04dcbc73c06ef22468e6
updated: 2016-03-27T19:57:17.213688266+02:00
hash: c7c28fa3f095cd3e31f8531dd5badeb196256965f003f5cbadd0f509960aa647
updated: 2016-08-01T17:16:21.884990443+02:00
imports:
- name: github.com/alecthomas/template
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
- name: github.com/alecthomas/units
version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- name: github.com/boltdb/bolt
version: 51f99c862475898df9773747d3accd05a7ca33c1
version: 5cc10bbbc5c141029940133bb33c9e969512a698
- name: github.com/BurntSushi/toml
version: bd2bdf7f18f849530ef7a1c29a4290217cab32a1
version: 99064174e013895bbd9b025c31100bd1d9b590ca
- name: github.com/BurntSushi/ty
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
subpackages:
- fun
- name: github.com/cenkalti/backoff
version: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
version: cdf48bbc1eb78d1349cbda326a4a037f7ba565c6
- name: github.com/codahale/hdrhistogram
version: 954f16e8b9ef0e5d5189456aa4c1202758e04f17
version: f8ad88b59a584afeee9d334eff879b104439117b
- name: github.com/codegangsta/cli
version: bf4a526f48af7badd25d2cb02d587e1b01be3b50
version: 1efa31f08b9333f1bd4882d61f9d668a70cd902e
- name: github.com/codegangsta/negroni
version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
- name: github.com/containous/oxy
version: 0b5b371bce661385d35439204298fa6fb5db5463
version: dc6b9d037e8dab60cbfc09c61d6932537829be8b
- name: github.com/containous/flaeg
version: b98687da5c323650f4513fda6b6203fcbdec9313
- name: github.com/containous/mux
version: a819b77bba13f0c0cbe36e437bc2e948411b3996
- name: github.com/containous/staert
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
- name: github.com/coreos/etcd
version: 1c9e0a0e33051fed6c05c141e6fcbfe5c7f2a899
subpackages:
- cbreaker
- forward
- memmetrics
- roundrobin
- utils
- name: github.com/coreos/go-etcd
version: cc90c7b091275e606ad0ca7102a23fb2072f3f5e
subpackages:
- etcd
- client
- pkg/pathutil
- pkg/types
- name: github.com/davecgh/go-spew
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
subpackages:
- spew
- name: github.com/docker/distribution
version: 9038e48c3b982f8e82281ea486f078a73731ac4e
- name: github.com/docker/docker
version: f39987afe8d611407887b3094c03d6ba6a766a67
version: 857d0f15c0a4d8037175642e0ca3660829551cb5
subpackages:
- autogen
- api
- reference
- digest
- registry/api/errcode
- registry/client/auth
- registry/client/transport
- registry/client
- context
- registry/api/v2
- registry/storage/cache
- registry/storage/cache/memory
- uuid
- name: github.com/docker/docker
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
subpackages:
- namesgenerator
- pkg/namesgenerator
- pkg/random
- cliconfig
- daemon/network
- graph/tags
- cliconfig/configfile
- pkg/jsonmessage
- pkg/promise
- pkg/stdcopy
- pkg/term
- reference
- registry
- runconfig/opts
- pkg/homedir
- pkg/jsonlog
- pkg/system
- pkg/term/windows
- image
- image/v1
- pkg/ioutils
- opts
- pkg/httputils
- pkg/mflag
- pkg/stringid
- pkg/tarsum
- pkg/mount
- pkg/signal
- pkg/urlutil
- builder
- builder/dockerignore
- pkg/archive
- pkg/fileutils
- pkg/homedir
- pkg/httputils
- pkg/ioutils
- pkg/jsonmessage
- pkg/mflag
- pkg/nat
- pkg/parsers
- pkg/pools
- pkg/promise
- pkg/random
- pkg/stdcopy
- pkg/stringid
- pkg/progress
- pkg/streamformatter
- layer
- pkg/longpath
- api/types/backend
- pkg/chrootarchive
- pkg/gitutils
- 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
- pkg/idtools
- pkg/pools
- daemon/graphdriver
- pkg/reexec
- pkg/plugins
- pkg/plugins/transport
- name: github.com/docker/engine-api
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
subpackages:
- client
- types
- types/events
- types/filters
- types/container
- types/network
- client/transport
- client/transport/cancellable
- types/reference
- types/registry
- types/time
- types/versions
- types/blkiodev
- types/strslice
- name: github.com/docker/go-connections
version: 990a1a1a70b0da4c4cb70e117971a4f0babfbf1a
subpackages:
- sockets
- tlsconfig
- nat
- name: github.com/docker/go-units
version: f2d77a61e3c169b43402a0a1e84f06daf29b8190
- name: github.com/docker/libcompose
version: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
subpackages:
- docker
- project
- project/events
- project/options
- config
- docker/builder
- docker/client
- labels
- logger
- lookup
- project
- utils
- yaml
- version
- name: github.com/docker/libkv
version: 3732f7ff1b56057c3158f10bceb1e79133025373
version: 35d3e2084c650109e7bcc7282655b1bc8ba924ff
subpackages:
- store
- store/boltdb
- store/consul
- store/etcd
- store/zookeeper
- name: github.com/docker/libtrust
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
- name: github.com/donovanhide/eventsource
version: d8a3071799b98cacd30b6da92f536050ccfe6da4
version: fd1de70867126402be23c306e1ce32828455d85b
- name: github.com/elazarl/go-bindata-assetfs
version: d5cac425555ca5cf00694df246e04f05e6a55150
- name: github.com/flynn/go-shlex
version: 3f9db97f856818214da2e1057f8ad84803971cff
- name: github.com/fsouza/go-dockerclient
version: a49c8269a6899cae30da1f8a4b82e0ce945f9967
subpackages:
- external/github.com/docker/docker/opts
- external/github.com/docker/docker/pkg/archive
- external/github.com/docker/docker/pkg/fileutils
- external/github.com/docker/docker/pkg/homedir
- external/github.com/docker/docker/pkg/stdcopy
- external/github.com/hashicorp/go-cleanhttp
- external/github.com/Sirupsen/logrus
- external/github.com/docker/docker/pkg/idtools
- external/github.com/docker/docker/pkg/ioutils
- external/github.com/docker/docker/pkg/longpath
- external/github.com/docker/docker/pkg/pools
- external/github.com/docker/docker/pkg/promise
- external/github.com/docker/docker/pkg/system
- external/github.com/opencontainers/runc/libcontainer/user
- external/golang.org/x/sys/unix
- external/golang.org/x/net/context
- external/github.com/docker/go-units
version: 57eb5e1fc594ad4b0b1dbea7b286d299e0cb43c2
- name: github.com/gambol99/go-marathon
version: ade11d1dc2884ee1f387078fc28509559b6235d1
- name: github.com/golang/glog
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
version: a558128c87724cd7430060ef5aedf39f83937f55
- name: github.com/go-check/check
version: 4f90aeace3a26ad7021961c297b22c42160c7b25
- name: github.com/google/go-querystring
version: 6bb77fe6f42b85397288d4f6f67ac72f8f400ee7
version: 9235644dd9e52eeae6fa48efd539fdc351a0af53
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
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
- name: github.com/hashicorp/consul
version: de080672fee9e6104572eeea89eccdca135bb918
version: 8a8271fd81cdaa1bbc20e4ced86531b90c7eaf79
subpackages:
- api
- name: github.com/hashicorp/hcl
version: 567a5d1c4878a4ac8c198c730fd15f978b0529c7
- name: github.com/hashicorp/go-cleanhttp
version: 875fb671b3ddc66f8e2f0acc33829c8cb989a38d
- name: github.com/hashicorp/serf
version: 6c4672d66fc6312ddde18399262943e21175d831
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: e6ac2fc51e89a3249e82157fa0bb7a18ef9dd5bb
- name: github.com/kr/text
version: bb797dc4fb8320488f47bf11de07a733d7233e1f
- name: github.com/magiconair/properties
version: 497d0afefddf378f9ffb3c89db6a326985908519
- name: github.com/mailgun/log
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8
- coordinate
- serf
- name: github.com/libkermit/docker
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
subpackages:
- compose
- name: github.com/libkermit/docker-check
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
subpackages:
- compose
- name: github.com/mailgun/manners
version: fada45142db3f93097ca917da107aa3fad0ffcb5
- name: github.com/mailgun/predicate
version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
- name: github.com/mailgun/timetools
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
- name: github.com/mattn/go-shellwords
version: 525bedee691b5a8df547cb5cf9f86b7fb1883e24
- name: github.com/Microsoft/go-winio
version: ce2922f643c8fd76b46cadc7f404a06282678b34
- name: github.com/miekg/dns
version: b9171237b0642de1d8e8004f16869970e065f46b
- name: github.com/mitchellh/mapstructure
version: d2dd0262208475919e1a362f675cfc0e7c10e905
version: 5d001d020961ae1c184f9f8152fdc73810481677
- name: github.com/moul/http2curl
version: b1479103caacaa39319f75e7f57fc545287fca0d
- name: github.com/ogier/pflag
version: 45c278ab3607870051a2ea9040bb85fcb8557481
- name: github.com/opencontainers/runc
version: 4ab132458fc3e9dbeea624153e0331952dc4c8d5
version: bd1d3ac0480c5d3babac10dc32cff2886563219c
subpackages:
- libcontainer/user
- name: github.com/parnurzeal/gorequest
version: 045012d33ef41ea146c1b675df9296d0dc1a212d
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/samalba/dockerclient
version: cfb489c624b635251a93e74e1e90eb0959c5367f
- name: github.com/ryanuber/go-glob
version: 572520ed46dbddaed19ea3d9541bdd0494163693
- name: github.com/samuel/go-zookeeper
version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
version: e64db453f3512cade908163702045e0f31137843
subpackages:
- zk
- name: github.com/Sirupsen/logrus
version: 418b41d23a1bf978c06faea5313ba194650ac088
- name: github.com/spf13/cast
version: ee7b3e0353166ab1f3a605294ac8cd2b77953778
- name: github.com/spf13/cobra
version: 1bacefc9a216c93293e670067bd159a64b4d72c3
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/square/go-jose
version: 70a7e670bd0d4bb35902d31f3a75a6689843abed
subpackages:
- cipher
- json
version: a283a10442df8dc09befd873fab202bf8a253d6a
- name: github.com/streamrail/concurrent-map
version: 65a174a3a4188c0b7099acbc6cfa0c53628d3287
- name: github.com/stretchr/objx
version: cbeaeb16a013161a98496fad62933b1d21786672
- name: github.com/stretchr/testify
version: 6fe211e493929a8aac0469b93f28b1d0688a9a3a
version: d77da356e56a7428ad25149ca77381849a6a5232
subpackages:
- mock
- assert
- name: github.com/thoas/stats
version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
- name: github.com/unrolled/render
version: 26b4e3aac686940fe29521545afad9966ddfc80c
- name: github.com/vdemeester/libkermit
version: 01a5399bdbd3312916c9fa4848108fbc81fe88d8
- name: github.com/vdemeester/shakers
version: 8fe734f75f3a70b651cbfbf8a55a009da09e8dc5
- name: github.com/vulcand/oxy
version: 8aaf36279137ac04ace3792a4f86098631b27d5a
version: 152b5d051953fdb6e45f14b6826962aadc032324
- name: github.com/ugorji/go
version: b94837a2404ab90efe9289e77a70694c355739cb
subpackages:
- memmetrics
- codec
- name: github.com/unrolled/render
version: 198ad4d8b8a4612176b804ca10555b222a086b40
- name: github.com/vdemeester/docker-events
version: 20e6d2db238723e68197a9e3c6c34c99a9893a9c
- name: github.com/vdemeester/shakers
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
- name: github.com/vulcand/oxy
version: 4298f24d572dc554eb984f2ffdf6bdd54d4bd613
repo: https://github.com/containous/oxy.git
vcs: git
subpackages:
- cbreaker
- connlimit
- forward
- roundrobin
- stream
- utils
- memmetrics
- name: github.com/vulcand/predicate
version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
version: 19b9dde14240d94c804ae5736ad0e1de10bf8fe6
- name: github.com/vulcand/route
version: cb89d787ddbb1c5849a7ac9f79004c1fd12a4a32
- name: github.com/vulcand/vulcand
version: 475540bb016702d5b7cc4674e37f48ee3e144a69
version: 28a4e5c0892167589737b95ceecbcef00295be50
subpackages:
- plugin/rewrite
- plugin
- conntracker
- router
- name: github.com/wendal/errors
version: f66c77a7882b399795a8987ebf87ef64a427417e
- name: github.com/xenolf/lego
version: 118d9d5ec92bc243ea054742a03afae813ac1314
version: b2fad6198110326662e9e356a97199078a4a775c
subpackages:
- acme
- name: golang.org/x/crypto
version: 6025851c7c2bf210daf74d22300c699b16541847
version: d81fdb778bf2c40a91b24519d60cdc5767318829
subpackages:
- ocsp
- name: golang.org/x/net
version: d9558e5c97f85372afee28cf2b6059d7d3818919
version: b400c2eff1badec7022a8c8f5bea058b6315eed7
subpackages:
- context
- publicsuffix
- proxy
- name: golang.org/x/sys
version: eb2c74142fd19a79b3f237334c7384d5167b1b46
version: 62bee037599929a6e9146f29d10dd5208c43507d
subpackages:
- unix
- name: gopkg.in/alecthomas/kingpin.v2
version: 639879d6110b1b0409410c7b737ef0bb18325038
- name: gopkg.in/check.v1
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
- windows
- name: gopkg.in/fsnotify.v1
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
version: a8a77c9133d2d6fd8334f3260d06f60e8d80a5fb
- name: gopkg.in/mgo.v2
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
version: 29cc868a5ca65f401ff318143f9408d02f4799cc
subpackages:
- bson
- name: gopkg.in/yaml.v2
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40
devImports: []
- name: gopkg.in/square/go-jose.v1
version: e3f973b66b91445ec816dd7411ad1b6495a5a2fc
subpackages:
- cipher
- json
testImports:
- name: github.com/Azure/go-ansiterm
version: fa152c58bc15761d0200cb75fe958b89a9d4888e
subpackages:
- winterm
- name: github.com/cloudfoundry-incubator/candiedyaml
version: 99c3df83b51532e3615f851d8c2dbb638f5313bf
- name: github.com/flynn/go-shlex
version: 3f9db97f856818214da2e1057f8ad84803971cff
- name: github.com/gorilla/mux
version: 9fa818a44c2bf1396a17f9d5a3c0f6dd39d2ff8e
- name: github.com/vbatts/tar-split
version: 28bc4c32f9fa9725118a685c9ddd7ffdbdbfe2c8
subpackages:
- tar/asm
- tar/storage
- archive/tar
- name: github.com/xeipuuv/gojsonpointer
version: e0fe6f68307607d540ed8eac07a342c33fa1b54a
- name: github.com/xeipuuv/gojsonreference
version: e02fc20de94c78484cd5ffb007f8af96be030a45
- name: github.com/xeipuuv/gojsonschema
version: 66a3de92def23708184148ae337750915875e7c1

View File

@@ -1,169 +1,84 @@
package: main
package: github.com/containous/traefik
import:
- package: github.com/coreos/go-etcd
ref: cc90c7b091275e606ad0ca7102a23fb2072f3f5e
subpackages:
- etcd
- package: github.com/docker/distribution
ref: 9038e48c3b982f8e82281ea486f078a73731ac4e
- 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: gopkg.in/check.v1
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: 8fe734f75f3a70b651cbfbf8a55a009da09e8dc5
- package: github.com/alecthomas/units
ref: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
- package: github.com/gambol99/go-marathon
ref: ade11d1dc2884ee1f387078fc28509559b6235d1
- package: github.com/mailgun/predicate
ref: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
- package: github.com/thoas/stats
ref: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
- package: github.com/samalba/dockerclient
ref: cfb489c624b635251a93e74e1e90eb0959c5367f
- 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/fsouza/go-dockerclient
ref: a49c8269a6899cae30da1f8a4b82e0ce945f9967
- 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/docker/libcompose
ref: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
subpackages:
- docker
- logger
- lookup
- project
- utils
- 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: 01a5399bdbd3312916c9fa4848108fbc81fe88d8
- package: github.com/BurntSushi/toml
- package: github.com/BurntSushi/ty
subpackages:
- fun
- package: github.com/Sirupsen/logrus
- package: github.com/cenkalti/backoff
- package: github.com/codegangsta/negroni
- package: github.com/containous/flaeg
version: b98687da5c323650f4513fda6b6203fcbdec9313
- package: github.com/vulcand/oxy
version: 4298f24d572dc554eb984f2ffdf6bdd54d4bd613
repo: https://github.com/containous/oxy.git
vcs: git
subpackages:
- cbreaker
- connlimit
- forward
- roundrobin
- stream
- utils
- package: github.com/containous/staert
version: e2aa88e235a02dd52aa1d5d9de75f9d9139d1602
- package: github.com/docker/engine-api
version: 3d3d0b6c9d2651aac27f416a6da0224c1875b3eb
subpackages:
- client
- types
- types/events
- types/filters
- package: github.com/docker/go-connections
subpackages:
- sockets
- tlsconfig
- package: github.com/docker/libkv
subpackages:
- store
- store/boltdb
- store/consul
- store/etcd
- store/zookeeper
- package: github.com/elazarl/go-bindata-assetfs
- package: github.com/gambol99/go-marathon
version: a558128c87724cd7430060ef5aedf39f83937f55
- package: github.com/containous/mux
- package: github.com/hashicorp/consul
subpackages:
- api
- package: github.com/mailgun/manners
- package: github.com/parnurzeal/gorequest
- package: github.com/streamrail/concurrent-map
- package: github.com/stretchr/testify
subpackages:
- mock
- package: github.com/thoas/stats
- package: github.com/unrolled/render
- package: github.com/vdemeester/docker-events
version: 20e6d2db238723e68197a9e3c6c34c99a9893a9c
- package: github.com/vulcand/vulcand
subpackages:
- plugin/rewrite
- package: github.com/xenolf/lego
version: b2fad6198110326662e9e356a97199078a4a775c
subpackages:
- acme
- package: golang.org/x/net
subpackages:
- context
- package: gopkg.in/fsnotify.v1
- package: github.com/libkermit/docker-check
version: bb75a86b169c6c5d22c0ee98278124036f272d7b
- package: github.com/libkermit/docker
version: 3b5eb2973efff7af33cfb65141deaf4ed25c6d02
- package: github.com/docker/docker
version: 9837ec4da53f15f9120d53a6e1517491ba8b0261
subpackages:
- namesgenerator
- package: github.com/go-check/check
- package: github.com/docker/libcompose
version: 8ee7bcc364f7b8194581a3c6bd9fa019467c7873
- package: github.com/mattn/go-shellwords
- package: github.com/vdemeester/shakers
- package: github.com/ryanuber/go-glob

View File

@@ -0,0 +1,106 @@
package main
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"regexp"
"strings"
"time"
"github.com/go-check/check"
shellwords "github.com/mattn/go-shellwords"
checker "github.com/vdemeester/shakers"
)
// AccessLogSuite
type AccessLogSuite struct{ BaseSuite }
func (s *AccessLogSuite) TestAccessLog(c *check.C) {
// Ensure working directory is clean
os.Remove("access.log")
os.Remove("traefik.log")
// Start Traefik
cmd := exec.Command(traefikBinary, "--configFile=fixtures/access_log_config.toml")
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
defer os.Remove("access.log")
defer os.Remove("traefik.log")
time.Sleep(500 * time.Millisecond)
// Verify Traefik started OK
traefikLog, err := ioutil.ReadFile("traefik.log")
c.Assert(err, checker.IsNil)
if len(traefikLog) > 0 {
fmt.Printf("%s\n", string(traefikLog))
c.Assert(len(traefikLog), checker.Equals, 0)
}
// Start test servers
ts1 := startAccessLogServer(8081)
defer ts1.Close()
ts2 := startAccessLogServer(8082)
defer ts2.Close()
ts3 := startAccessLogServer(8083)
defer ts3.Close()
// Make some requests
_, err = http.Get("http://127.0.0.1:8000/test1")
c.Assert(err, checker.IsNil)
_, err = http.Get("http://127.0.0.1:8000/test2")
c.Assert(err, checker.IsNil)
_, err = http.Get("http://127.0.0.1:8000/test2")
c.Assert(err, checker.IsNil)
// Verify access.log output as expected
accessLog, err := ioutil.ReadFile("access.log")
c.Assert(err, checker.IsNil)
lines := strings.Split(string(accessLog), "\n")
count := 0
for i, line := range lines {
if len(line) > 0 {
count++
tokens, err := shellwords.Parse(line)
c.Assert(err, checker.IsNil)
c.Assert(len(tokens), checker.Equals, 13)
c.Assert(tokens[6], checker.Equals, "200")
c.Assert(tokens[9], checker.Equals, fmt.Sprintf("%d", i+1))
c.Assert(strings.HasPrefix(tokens[10], "frontend"), checker.True)
c.Assert(strings.HasPrefix(tokens[11], "http://127.0.0.1:808"), checker.True)
c.Assert(regexp.MustCompile("^\\d+\\.\\d+.*s$").MatchString(tokens[12]), checker.True)
}
}
c.Assert(count, checker.Equals, 3)
// Verify no other Traefik problems
traefikLog, err = ioutil.ReadFile("traefik.log")
c.Assert(err, checker.IsNil)
if len(traefikLog) > 0 {
fmt.Printf("%s\n", string(traefikLog))
c.Assert(len(traefikLog), checker.Equals, 0)
}
}
func startAccessLogServer(port int) (ts *httptest.Server) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Received query %s!\n", r.URL.Path[1:])
})
if listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)); err != nil {
panic(err)
} else {
ts = &httptest.Server{
Listener: listener,
Config: &http.Server{Handler: handler},
}
ts.Start()
}
return
}

View File

@@ -5,35 +5,28 @@ import (
"os/exec"
"time"
"fmt"
"github.com/go-check/check"
"bytes"
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 : no such file or directory")
nonExistentFile := "non/existent/file.toml"
cmd = exec.Command(traefikBinary, "--configFile="+nonExistentFile)
output, err = cmd.CombinedOutput()
c.Assert(err, checker.NotNil)
c.Assert(string(output), checker.Contains, fmt.Sprintf("Error reading file: open %s: no such file or directory", nonExistentFile))
}
func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
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: While parsing config: Near line 1")
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
cmd.Start()
time.Sleep(500 * time.Millisecond)
defer cmd.Process.Kill()
output := b.Bytes()
c.Assert(string(output), checker.Contains, "Near line 0 (last key parsed ''): Bare keys cannot contain '{'")
}
func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
@@ -64,3 +57,34 @@ func (s *SimpleSuite) TestWithWebConfig(c *check.C) {
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 200)
}
func (s *SimpleSuite) TestDefaultEntryPoints(c *check.C) {
cmd := exec.Command(traefikBinary, "--debug")
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
cmd.Start()
time.Sleep(500 * time.Millisecond)
defer cmd.Process.Kill()
output := b.Bytes()
c.Assert(string(output), checker.Contains, "\\\"DefaultEntryPoints\\\":[\\\"http\\\"]")
}
func (s *SimpleSuite) TestPrintHelp(c *check.C) {
cmd := exec.Command(traefikBinary, "--help")
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
cmd.Start()
time.Sleep(500 * time.Millisecond)
defer cmd.Process.Kill()
output := b.Bytes()
c.Assert(string(output), checker.Not(checker.Contains), "panic:")
c.Assert(string(output), checker.Contains, "Usage:")
}

View File

@@ -0,0 +1,209 @@
package main
import (
"net/http"
"os/exec"
"time"
"github.com/go-check/check"
"github.com/hashicorp/consul/api"
checker "github.com/vdemeester/shakers"
)
// Constraint test suite
type ConstraintSuite struct {
BaseSuite
consulIP string
consulClient *api.Client
}
func (s *ConstraintSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "constraints")
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 *ConstraintSuite) registerService(name string, address string, port int, tags []string) 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,
Tags: tags,
},
},
&api.WriteOptions{},
)
return err
}
func (s *ConstraintSuite) 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 *ConstraintSuite) TestMatchConstraintGlobal(c *check.C) {
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--constraints=tag==api")
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, []string{"traefik.tags=api"})
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)
}
func (s *ConstraintSuite) TestDoesNotMatchConstraintGlobal(c *check.C) {
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--constraints=tag==api")
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, []string{})
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, 404)
}
func (s *ConstraintSuite) TestMatchConstraintProvider(c *check.C) {
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api")
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, []string{"traefik.tags=api"})
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)
}
func (s *ConstraintSuite) TestDoesNotMatchConstraintProvider(c *check.C) {
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api")
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, []string{})
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, 404)
}
func (s *ConstraintSuite) TestMatchMultipleConstraint(c *check.C) {
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*")
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, []string{"traefik.tags=api", "traefik.tags=eu-1"})
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)
}
func (s *ConstraintSuite) TestDoesNotMatchMultipleConstraint(c *check.C) {
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*")
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, []string{"traefik.tags=api", "traefik.tags=us-1"})
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, 404)
}

View File

@@ -6,11 +6,10 @@ import (
"os/exec"
"time"
"github.com/go-check/check"
"github.com/hashicorp/consul/api"
docker "github.com/vdemeester/libkermit/docker"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// Consul catalog test suites
@@ -18,20 +17,14 @@ type ConsulCatalogSuite struct {
BaseSuite
consulIP string
consulClient *api.Client
project *docker.Project
}
func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
project, err := docker.NewProjectFromEnv()
c.Assert(err, checker.IsNil, check.Commentf("Error while creating docker project"))
s.project = project
s.createComposeProject(c, "consul_catalog")
err = s.composeProject.Start()
c.Assert(err, checker.IsNil, check.Commentf("Error starting project"))
s.composeProject.Start(c)
consul, err := s.project.Inspect("integration-test-consul_catalog_consul_1")
c.Assert(err, checker.IsNil, check.Commentf("Error finding consul container"))
consul := s.composeProject.Container(c, "consul")
s.consulIP = consul.NetworkSettings.IPAddress
config := api.DefaultConfig()
@@ -46,7 +39,7 @@ func (s *ConsulCatalogSuite) SetUpSuite(c *check.C) {
time.Sleep(2000 * time.Millisecond)
}
func (s *ConsulCatalogSuite) registerService(name string, address string, port int) error {
func (s *ConsulCatalogSuite) registerService(name string, address string, port int, tags []string) error {
catalog := s.consulClient.Catalog()
_, err := catalog.Register(
&api.CatalogRegistration{
@@ -57,6 +50,7 @@ func (s *ConsulCatalogSuite) registerService(name string, address string, port i
Service: name,
Address: address,
Port: port,
Tags: tags,
},
},
&api.WriteOptions{},
@@ -98,10 +92,9 @@ func (s *ConsulCatalogSuite) TestSingleService(c *check.C) {
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
nginx, err := s.project.Inspect("integration-test-consul_catalog_nginx_1")
c.Assert(err, checker.IsNil, check.Commentf("Error finding nginx container"))
nginx := s.composeProject.Container(c, "nginx")
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80)
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)

View File

@@ -5,28 +5,188 @@ import (
"os/exec"
"time"
"github.com/docker/libkv"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/consul"
"github.com/go-check/check"
"errors"
"github.com/containous/traefik/integration/utils"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
"io/ioutil"
"os"
"strings"
)
// Consul test suites (using libcompose)
type ConsulSuite struct{ BaseSuite }
type ConsulSuite struct {
BaseSuite
kv store.Store
}
func (s *ConsulSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "consul")
s.composeProject.Start(c)
consul.Register()
kv, err := libkv.NewStore(
store.CONSUL,
[]string{s.composeProject.Container(c, "consul").NetworkSettings.IPAddress + ":8500"},
&store.Config{
ConnectionTimeout: 10 * time.Second,
},
)
if err != nil {
c.Fatal("Cannot create store consul")
}
s.kv = kv
// wait for consul
err = utils.Try(60*time.Second, func() error {
_, err := kv.Exists("test")
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
}
func (s *ConsulSuite) TestSimpleConfiguration(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/consul/simple.toml")
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost})
defer os.Remove(file)
cmd := exec.Command(traefikBinary, "--configFile="+file)
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 *ConsulSuite) TestNominalConfiguration(c *check.C) {
consulHost := s.composeProject.Container(c, "consul").NetworkSettings.IPAddress
file := s.adaptFile(c, "fixtures/consul/simple.toml", struct{ ConsulHost string }{consulHost})
defer os.Remove(file)
cmd := exec.Command(traefikBinary, "--configFile="+file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
whoami1 := s.composeProject.Container(c, "whoami1")
whoami2 := s.composeProject.Container(c, "whoami2")
whoami3 := s.composeProject.Container(c, "whoami3")
whoami4 := s.composeProject.Container(c, "whoami4")
backend1 := map[string]string{
"traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5",
"traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80",
"traefik/backends/backend1/servers/server1/weight": "10",
"traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80",
"traefik/backends/backend1/servers/server2/weight": "1",
}
backend2 := map[string]string{
"traefik/backends/backend2/loadbalancer/method": "drr",
"traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80",
"traefik/backends/backend2/servers/server1/weight": "1",
"traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80",
"traefik/backends/backend2/servers/server2/weight": "2",
}
frontend1 := map[string]string{
"traefik/frontends/frontend1/backend": "backend2",
"traefik/frontends/frontend1/entrypoints": "http",
"traefik/frontends/frontend1/priority": "1",
"traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost",
}
frontend2 := map[string]string{
"traefik/frontends/frontend2/backend": "backend1",
"traefik/frontends/frontend2/entrypoints": "http",
"traefik/frontends/frontend2/priority": "10",
"traefik/frontends/frontend2/routes/test_2/rule": "Path:/test",
}
for key, value := range backend1 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range backend2 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range frontend1 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range frontend2 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
// wait for consul
err = utils.Try(60*time.Second, func() error {
_, err := s.kv.Exists("traefik/frontends/frontend2/routes/test_2/rule")
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
// wait for traefik
err = utils.TryRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, func(res *http.Response) error {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
if !strings.Contains(string(body), "Path:/test") {
return errors.New("Incorrect traefik config")
}
return nil
})
c.Assert(err, checker.IsNil)
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.localhost"
response, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(response.StatusCode, checker.Equals, 200)
body, err := ioutil.ReadAll(response.Body)
c.Assert(err, checker.IsNil)
if !strings.Contains(string(body), whoami3.NetworkSettings.IPAddress) &&
!strings.Contains(string(body), whoami4.NetworkSettings.IPAddress) {
c.Fail()
}
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/test", nil)
c.Assert(err, checker.IsNil)
response, err = client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(response.StatusCode, checker.Equals, 200)
body, err = ioutil.ReadAll(response.Body)
c.Assert(err, checker.IsNil)
if !strings.Contains(string(body), whoami1.NetworkSettings.IPAddress) &&
!strings.Contains(string(body), whoami2.NetworkSettings.IPAddress) {
c.Fail()
}
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/test2", nil)
resp, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
req.Host = "test2.localhost"
resp, err = client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
}

View File

@@ -11,10 +11,11 @@ import (
"time"
"github.com/docker/docker/pkg/namesgenerator"
"github.com/vdemeester/libkermit/docker"
"github.com/go-check/check"
d "github.com/libkermit/docker"
docker "github.com/libkermit/docker-check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
var (
@@ -36,46 +37,42 @@ type DockerSuite struct {
}
func (s *DockerSuite) startContainer(c *check.C, image string, args ...string) string {
return s.startContainerWithConfig(c, image, docker.ContainerConfig{
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, docker.ContainerConfig{
return s.startContainerWithConfig(c, image, d.ContainerConfig{
Cmd: args,
Labels: labels,
})
}
func (s *DockerSuite) startContainerWithConfig(c *check.C, image string, config docker.ContainerConfig) string {
func (s *DockerSuite) startContainerWithConfig(c *check.C, image string, config d.ContainerConfig) string {
if config.Name == "" {
config.Name = namesgenerator.GetRandomName(10)
}
container, err := s.project.StartWithConfig(image, config)
c.Assert(err, checker.IsNil, check.Commentf("Error starting a container using config %v", config))
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, err := docker.NewProjectFromEnv()
c.Assert(err, checker.IsNil, check.Commentf("Error while creating docker project"))
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(image)
c.Assert(err, checker.IsNil, check.Commentf("Error while pulling image %s", image))
s.project.Pull(c, image)
}
}
func (s *DockerSuite) TearDownTest(c *check.C) {
err := s.project.Clean(os.Getenv("CIRCLECI") != "")
c.Assert(err, checker.IsNil, check.Commentf("Error while cleaning containers"))
s.project.Clean(c, os.Getenv("CIRCLECI") != "")
}
func (s *DockerSuite) TestSimpleConfiguration(c *check.C) {
@@ -133,8 +130,7 @@ func (s *DockerSuite) TestDockerContainersWithLabels(c *check.C) {
defer os.Remove(file)
// Start a container with some labels
labels := map[string]string{
"traefik.frontend.rule": "Host",
"traefik.frontend.value": "my.super.host",
"traefik.frontend.rule": "Host:my.super.host",
}
s.startContainerWithLabels(c, "swarm:1.0.0", labels, "manage", "token://blabla")

View File

@@ -1,23 +1,64 @@
package main
import (
"github.com/go-check/check"
"net/http"
"os/exec"
"time"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
"errors"
"fmt"
"github.com/containous/traefik/integration/utils"
"github.com/docker/libkv"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/etcd"
"io/ioutil"
"os"
"strings"
)
// Etcd test suites (using libcompose)
type EtcdSuite struct{ BaseSuite }
type EtcdSuite struct {
BaseSuite
kv store.Store
}
func (s *EtcdSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "etcd")
s.composeProject.Start(c)
etcd.Register()
url := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress + ":2379"
kv, err := libkv.NewStore(
store.ETCD,
[]string{url},
&store.Config{
ConnectionTimeout: 10 * time.Second,
},
)
if err != nil {
c.Fatal("Cannot create store etcd")
}
s.kv = kv
// wait for etcd
err = utils.Try(60*time.Second, func() error {
_, err := kv.Exists("test")
if err != nil {
return fmt.Errorf("Etcd connection error to %s: %v", url, err)
}
return nil
})
c.Assert(err, checker.IsNil)
}
func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
cmd := exec.Command(traefikBinary, "--configFile=fixtures/etcd/simple.toml")
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdHost string }{etcdHost})
defer os.Remove(file)
cmd := exec.Command(traefikBinary, "--configFile="+file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
@@ -30,3 +71,125 @@ func (s *EtcdSuite) TestSimpleConfiguration(c *check.C) {
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
}
func (s *EtcdSuite) TestNominalConfiguration(c *check.C) {
etcdHost := s.composeProject.Container(c, "etcd").NetworkSettings.IPAddress
file := s.adaptFile(c, "fixtures/etcd/simple.toml", struct{ EtcdHost string }{etcdHost})
defer os.Remove(file)
cmd := exec.Command(traefikBinary, "--configFile="+file)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
whoami1 := s.composeProject.Container(c, "whoami1")
whoami2 := s.composeProject.Container(c, "whoami2")
whoami3 := s.composeProject.Container(c, "whoami3")
whoami4 := s.composeProject.Container(c, "whoami4")
backend1 := map[string]string{
"/traefik/backends/backend1/circuitbreaker/expression": "NetworkErrorRatio() > 0.5",
"/traefik/backends/backend1/servers/server1/url": "http://" + whoami1.NetworkSettings.IPAddress + ":80",
"/traefik/backends/backend1/servers/server1/weight": "10",
"/traefik/backends/backend1/servers/server2/url": "http://" + whoami2.NetworkSettings.IPAddress + ":80",
"/traefik/backends/backend1/servers/server2/weight": "1",
}
backend2 := map[string]string{
"/traefik/backends/backend2/loadbalancer/method": "drr",
"/traefik/backends/backend2/servers/server1/url": "http://" + whoami3.NetworkSettings.IPAddress + ":80",
"/traefik/backends/backend2/servers/server1/weight": "1",
"/traefik/backends/backend2/servers/server2/url": "http://" + whoami4.NetworkSettings.IPAddress + ":80",
"/traefik/backends/backend2/servers/server2/weight": "2",
}
frontend1 := map[string]string{
"/traefik/frontends/frontend1/backend": "backend2",
"/traefik/frontends/frontend1/entrypoints": "http",
"/traefik/frontends/frontend1/priority": "1",
"/traefik/frontends/frontend1/routes/test_1/rule": "Host:test.localhost",
}
frontend2 := map[string]string{
"/traefik/frontends/frontend2/backend": "backend1",
"/traefik/frontends/frontend2/entrypoints": "http",
"/traefik/frontends/frontend2/priority": "10",
"/traefik/frontends/frontend2/routes/test_2/rule": "Path:/test",
}
for key, value := range backend1 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range backend2 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range frontend1 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
for key, value := range frontend2 {
err := s.kv.Put(key, []byte(value), nil)
c.Assert(err, checker.IsNil)
}
// wait for etcd
err = utils.Try(60*time.Second, func() error {
_, err := s.kv.Exists("/traefik/frontends/frontend2/routes/test_2/rule")
if err != nil {
return err
}
return nil
})
c.Assert(err, checker.IsNil)
// wait for traefik
err = utils.TryRequest("http://127.0.0.1:8081/api/providers", 60*time.Second, func(res *http.Response) error {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
if !strings.Contains(string(body), "Path:/test") {
return errors.New("Incorrect traefik config")
}
return nil
})
c.Assert(err, checker.IsNil)
client := &http.Client{}
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.localhost"
response, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(response.StatusCode, checker.Equals, 200)
body, err := ioutil.ReadAll(response.Body)
c.Assert(err, checker.IsNil)
if !strings.Contains(string(body), whoami3.NetworkSettings.IPAddress) &&
!strings.Contains(string(body), whoami4.NetworkSettings.IPAddress) {
c.Fail()
}
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/test", nil)
c.Assert(err, checker.IsNil)
response, err = client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(response.StatusCode, checker.Equals, 200)
body, err = ioutil.ReadAll(response.Body)
c.Assert(err, checker.IsNil)
if !strings.Contains(string(body), whoami1.NetworkSettings.IPAddress) &&
!strings.Contains(string(body), whoami2.NetworkSettings.IPAddress) {
c.Fail()
}
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/test2", nil)
req.Host = "test2.localhost"
resp, err := client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
req, err = http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
resp, err = client.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(resp.StatusCode, checker.Equals, 404)
}

View File

@@ -5,8 +5,9 @@ import (
"os/exec"
"time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// File test suites
@@ -15,7 +16,7 @@ type FileSuite struct{ BaseSuite }
func (s *FileSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "file")
s.composeProject.Start()
s.composeProject.Start(c)
}
func (s *FileSuite) TestSimpleConfiguration(c *check.C) {

View File

@@ -0,0 +1,46 @@
################################################################
# Global configuration
################################################################
traefikLogsFile = "traefik.log"
accessLogsFile = "access.log"
logLevel = "ERROR"
defaultEntryPoints = ["http"]
[entryPoints]
[entryPoints.http]
address = ":8000"
################################################################
# Web configuration backend
################################################################
[web]
address = ":7888"
################################################################
# File configuration backend
################################################################
[file]
################################################################
# rules
################################################################
[backends]
[backends.backend1]
[backends.backend1.servers.server1]
url = "http://127.0.0.1:8081"
[backends.backend2]
[backends.backend2.LoadBalancer]
method = "drr"
[backends.backend2.servers.server1]
url = "http://127.0.0.1:8082"
[backends.backend2.servers.server2]
url = "http://127.0.0.1:8083"
[frontends]
[frontends.frontend1]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Path: /test1"
[frontends.frontend2]
backend = "backend2"
passHostHeader = true
[frontends.frontend2.routes.test_2]
rule = "Path: /test2"

View File

@@ -1,9 +1,16 @@
defaultEntryPoints = ["http"]
logLevel = "DEBUG"
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[consul]
endpoint = "{{.ConsulHost}}:8500"
watch = true
prefix = "traefik"
[web]
address = ":8081"

View File

@@ -1,11 +1,11 @@
defaultEntryPoints = ["http"]
logLevel = "DEBUG"
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[docker]
# It's dynamagic !

View File

@@ -1,10 +1,16 @@
defaultEntryPoints = ["http"]
logLevel = "DEBUG"
[entryPoints]
[entryPoints.http]
address = ":8000"
logLevel = "DEBUG"
[etcd]
endpoint = "127.0.0.1:4003,127.0.0.1:4002,127.0.0.1:4001"
endpoint = "{{.EtcdHost}}:2379"
prefix = "/traefik"
watch = true
[web]
address = ":8081"

View File

@@ -33,10 +33,8 @@ logLevel = "DEBUG"
[frontends.frontend1]
backend = "backend2"
[frontends.frontend1.routes.test_1]
rule = "Host"
value = "test.localhost"
rule = "Host:test.localhost"
[frontends.frontend2]
backend = "backend1"
[frontends.frontend2.routes.test_2]
rule = "Path"
value = "/test"
rule = "Path:/test"

View File

@@ -27,10 +27,8 @@ defaultEntryPoints = ["https"]
[frontends.frontend1]
backend = "backend1"
[frontends.frontend1.routes.test_1]
rule = "Host"
value = "snitest.com"
rule = "Host:snitest.com"
[frontends.frontend2]
backend = "backend2"
[frontends.frontend2.routes.test_2]
rule = "Host"
value = "snitest.org"
rule = "Host:snitest.org"

View File

@@ -8,8 +8,9 @@ import (
"os/exec"
"time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// HTTPSSuite

View File

@@ -11,10 +11,10 @@ import (
"text/template"
"github.com/containous/traefik/integration/utils"
"github.com/vdemeester/libkermit/compose"
"github.com/go-check/check"
"github.com/libkermit/docker-check/compose"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
func Test(t *testing.T) {
@@ -23,6 +23,7 @@ func Test(t *testing.T) {
func init() {
check.Suite(&SimpleSuite{})
check.Suite(&AccessLogSuite{})
check.Suite(&HTTPSSuite{})
check.Suite(&FileSuite{})
check.Suite(&DockerSuite{})
@@ -30,6 +31,7 @@ func init() {
check.Suite(&ConsulCatalogSuite{})
check.Suite(&EtcdSuite{})
check.Suite(&MarathonSuite{})
check.Suite(&ConstraintSuite{})
}
var traefikBinary = "../dist/traefik"
@@ -41,17 +43,14 @@ type BaseSuite struct {
func (s *BaseSuite) TearDownSuite(c *check.C) {
// shutdown and delete compose project
if s.composeProject != nil {
err := s.composeProject.Stop()
c.Assert(err, checker.IsNil)
s.composeProject.Stop(c)
}
}
func (s *BaseSuite) createComposeProject(c *check.C, name string) {
projectName := fmt.Sprintf("integration-test-%s", name)
composeFile := fmt.Sprintf("resources/compose/%s.yml", name)
composeProject, err := compose.CreateProject(projectName, composeFile)
c.Assert(err, checker.IsNil)
s.composeProject = composeProject
s.composeProject = compose.CreateProject(c, projectName, composeFile)
}
func (s *BaseSuite) traefikCmd(c *check.C, args ...string) (*exec.Cmd, string) {
@@ -66,7 +65,11 @@ func (s *BaseSuite) adaptFileForHost(c *check.C, path string) string {
// Default docker socket
dockerHost = "unix:///var/run/docker.sock"
}
tempObjects := struct{ DockerHost string }{dockerHost}
return s.adaptFile(c, path, tempObjects)
}
func (s *BaseSuite) adaptFile(c *check.C, path string, tempObjects interface{}) string {
// Load file
tmpl, err := template.ParseFiles(path)
c.Assert(err, checker.IsNil)
@@ -76,7 +79,7 @@ func (s *BaseSuite) adaptFileForHost(c *check.C, path string) string {
c.Assert(err, checker.IsNil)
defer tmpFile.Close()
err = tmpl.ExecuteTemplate(tmpFile, prefix, struct{ DockerHost string }{dockerHost})
err = tmpl.ExecuteTemplate(tmpFile, prefix, tempObjects)
c.Assert(err, checker.IsNil)
err = tmpFile.Sync()

View File

@@ -5,8 +5,9 @@ import (
"os/exec"
"time"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
check "gopkg.in/check.v1"
)
// Marathon test suites (using libcompose)
@@ -14,6 +15,19 @@ type MarathonSuite struct{ BaseSuite }
func (s *MarathonSuite) SetUpSuite(c *check.C) {
s.createComposeProject(c, "marathon")
s.composeProject.Start(c)
// wait for marathon
// err := utils.TryRequest("http://127.0.0.1:8080/ping", 60*time.Second, func(res *http.Response) error {
// body, err := ioutil.ReadAll(res.Body)
// if err != nil {
// return err
// }
// if !strings.Contains(string(body), "ping") {
// return errors.New("Incorrect marathon config")
// }
// return nil
// })
// c.Assert(err, checker.IsNil)
}
func (s *MarathonSuite) TestSimpleConfiguration(c *check.C) {

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

@@ -1,6 +1,6 @@
consul:
image: progrium/consul
command: -server -bootstrap -advertise 12.0.0.254 -log-level debug -ui-dir /ui
command: -server -bootstrap -log-level debug -ui-dir /ui
ports:
- "8400:8400"
- "8500:8500"
@@ -10,4 +10,16 @@ consul:
- "8301"
- "8301/udp"
- "8302"
- "8302/udp"
- "8302/udp"
whoami1:
image: emilevauge/whoami
whoami2:
image: emilevauge/whoami
whoami3:
image: emilevauge/whoami
whoami4:
image: emilevauge/whoami

View File

@@ -1,30 +1,14 @@
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
etcd:
image: containous/docker-etcd
whoami1:
image: emilevauge/whoami
whoami2:
image: emilevauge/whoami
whoami3:
image: emilevauge/whoami
whoami4:
image: emilevauge/whoami

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.28.1-2.0.20.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.28.1-2.0.20.ubuntu1404
net: host
pid: host
privileged: true
@@ -31,12 +31,13 @@ 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:v1.1.1
net: host
environment:
MARATHON_MASTER: zk://127.0.0.1:2181/mesos
MARATHON_ZK: zk://127.0.0.1:2181/marathon
MARATHON_HOSTNAME: 127.0.0.1
command: --event_subscriber http_callback
command: --event_subscriber http_callback

50
integration/utils/try.go Normal file
View File

@@ -0,0 +1,50 @@
package utils
import (
"errors"
"github.com/cenkalti/backoff"
"net/http"
"strconv"
"time"
)
// TryRequest try operation timeout, and retry backoff
func TryRequest(url string, timeout time.Duration, condition Condition) error {
exponentialBackOff := backoff.NewExponentialBackOff()
exponentialBackOff.MaxElapsedTime = timeout
var res *http.Response
err := backoff.Retry(func() error {
var err error
res, err = http.Get(url)
if err != nil {
return err
}
return condition(res)
}, exponentialBackOff)
return err
}
// Try try operation timeout, and retry backoff
func Try(timeout time.Duration, operation func() error) error {
exponentialBackOff := backoff.NewExponentialBackOff()
exponentialBackOff.MaxElapsedTime = timeout
err := backoff.Retry(operation, exponentialBackOff)
return err
}
// Condition is a retry condition function.
// It receives a response, and returns an error
// if the response failed the condition.
type Condition func(*http.Response) error
// ErrorIfStatusCodeIsNot returns a retry condition function.
// The condition returns an error
// if the given response's status code is not the given HTTP status code.
func ErrorIfStatusCodeIsNot(status int) Condition {
return func(res *http.Response) error {
if res.StatusCode != status {
return errors.New("Bad status. Got: " + res.Status + ", expected:" + strconv.Itoa(status))
}
return nil
}
}

View File

@@ -1,22 +0,0 @@
package middlewares
import (
"net/http"
"strings"
)
// StripPrefix is a middleware used to strip prefix from an URL request
type StripPrefix struct {
Handler http.Handler
Prefix string
}
func (s *StripPrefix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if p := strings.TrimPrefix(r.URL.Path, s.Prefix); len(p) < len(r.URL.Path) {
r.URL.Path = p
r.RequestURI = r.URL.RequestURI()
s.Handler.ServeHTTP(w, r)
} else {
http.NotFound(w, r)
}
}

View File

@@ -3,7 +3,7 @@ package middlewares
import (
"net/http"
"github.com/containous/oxy/cbreaker"
"github.com/vulcand/oxy/cbreaker"
)
// CircuitBreaker holds the oxy circuit breaker.
@@ -12,9 +12,12 @@ type CircuitBreaker struct {
}
// 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}
func NewCircuitBreaker(next http.Handler, expression string, options ...cbreaker.CircuitBreakerOption) (*CircuitBreaker, error) {
circuitBreaker, err := cbreaker.New(next, expression, options...)
if err != nil {
return nil, err
}
return &CircuitBreaker{circuitBreaker}, nil
}
func (cb *CircuitBreaker) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {

View File

@@ -1,40 +1,35 @@
package middlewares
import (
"github.com/gorilla/mux"
"github.com/containous/mux"
"github.com/containous/traefik/safe"
"net/http"
"sync"
)
// HandlerSwitcher allows hot switching of http.ServeMux
type HandlerSwitcher struct {
handler *mux.Router
handlerLock *sync.Mutex
handler *safe.Safe
}
// NewHandlerSwitcher builds a new instance of HandlerSwitcher
func NewHandlerSwitcher(newHandler *mux.Router) (hs *HandlerSwitcher) {
return &HandlerSwitcher{
handler: newHandler,
handlerLock: &sync.Mutex{},
handler: safe.New(newHandler),
}
}
func (hs *HandlerSwitcher) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
hs.handlerLock.Lock()
handlerBackup := hs.handler
hs.handlerLock.Unlock()
handlerBackup := hs.handler.Get().(*mux.Router)
handlerBackup.ServeHTTP(rw, r)
}
// GetHandler returns the current http.ServeMux
func (hs *HandlerSwitcher) GetHandler() (newHandler *mux.Router) {
return hs.handler
handler := hs.handler.Get().(*mux.Router)
return 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()
hs.handler.Set(newHandler)
}

View File

@@ -1,18 +1,55 @@
package middlewares
import (
"log"
"bufio"
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/streamrail/concurrent-map"
"io"
"net"
"net/http"
"os"
"github.com/gorilla/handlers"
"strconv"
"strings"
"sync/atomic"
"time"
)
// Logger is a middleware handler that logs the request as it goes in and the response as it goes out.
const (
loggerReqidHeader = "X-Traefik-Reqid"
)
/*
Logger writes each request and its response to the access log.
It gets some information from the logInfoResponseWriter set up by previous middleware.
*/
type Logger struct {
file *os.File
}
// Logging handler to log frontend name, backend name, and elapsed time
type frontendBackendLoggingHandler struct {
reqid string
writer io.Writer
handlerFunc http.HandlerFunc
}
var (
reqidCounter uint64 // Request ID
infoRwMap = cmap.New() // Map of reqid to response writer
backend2FrontendMap *map[string]string
)
// logInfoResponseWriter is a wrapper of type http.ResponseWriter
// that tracks frontend and backend names and request status and size
type logInfoResponseWriter struct {
rw http.ResponseWriter
backend string
frontend string
status int
size int
}
// NewLogger returns a new Logger instance.
func NewLogger(file string) *Logger {
if len(file) > 0 {
@@ -25,15 +62,136 @@ func NewLogger(file string) *Logger {
return &Logger{nil}
}
// SetBackend2FrontendMap is called by server.go to set up frontend translation
func SetBackend2FrontendMap(newMap *map[string]string) {
backend2FrontendMap = newMap
}
func (l *Logger) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if l.file == nil {
next(rw, r)
} else {
handlers.CombinedLoggingHandler(l.file, next).ServeHTTP(rw, r)
reqid := strconv.FormatUint(atomic.AddUint64(&reqidCounter, 1), 10)
r.Header[loggerReqidHeader] = []string{reqid}
defer deleteReqid(r, reqid)
frontendBackendLoggingHandler{reqid, l.file, next}.ServeHTTP(rw, r)
}
}
// Close closes the logger (i.e. the file).
func (l *Logger) Close() {
l.file.Close()
// Delete a reqid from the map and the request's headers
func deleteReqid(r *http.Request, reqid string) {
infoRwMap.Remove(reqid)
delete(r.Header, loggerReqidHeader)
}
// Save the backend name for the Logger
func saveBackendNameForLogger(r *http.Request, backendName string) {
if reqidHdr := r.Header[loggerReqidHeader]; len(reqidHdr) == 1 {
reqid := reqidHdr[0]
if infoRw, ok := infoRwMap.Get(reqid); ok {
infoRw.(*logInfoResponseWriter).SetBackend(backendName)
infoRw.(*logInfoResponseWriter).SetFrontend((*backend2FrontendMap)[backendName])
}
}
}
// Close closes the Logger (i.e. the file).
func (l *Logger) Close() {
if l.file != nil {
l.file.Close()
}
}
// Logging handler to log frontend name, backend name, and elapsed time
func (fblh frontendBackendLoggingHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
startTime := time.Now()
infoRw := &logInfoResponseWriter{rw: rw}
infoRwMap.Set(fblh.reqid, infoRw)
fblh.handlerFunc(infoRw, req)
username := "-"
url := *req.URL
if url.User != nil {
if name := url.User.Username(); name != "" {
username = name
}
}
host, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
host = req.RemoteAddr
}
ts := startTime.Format("02/Jan/2006:15:04:05 -0700")
method := req.Method
uri := url.RequestURI()
if qmIndex := strings.Index(uri, "?"); qmIndex > 0 {
uri = uri[0:qmIndex]
}
proto := req.Proto
referer := req.Referer()
agent := req.UserAgent()
frontend := strings.TrimPrefix(infoRw.GetFrontend(), "frontend-")
backend := infoRw.GetBackend()
status := infoRw.GetStatus()
size := infoRw.GetSize()
elapsed := time.Now().UTC().Sub(startTime.UTC())
fmt.Fprintf(fblh.writer, `%s - %s [%s] "%s %s %s" %d %d "%s" "%s" %s "%s" "%s" %s%s`,
host, username, ts, method, uri, proto, status, size, referer, agent, fblh.reqid, frontend, backend, elapsed, "\n")
}
func (lirw *logInfoResponseWriter) Header() http.Header {
return lirw.rw.Header()
}
func (lirw *logInfoResponseWriter) Write(b []byte) (int, error) {
if lirw.status == 0 {
lirw.status = http.StatusOK
}
size, err := lirw.rw.Write(b)
lirw.size += size
return size, err
}
func (lirw *logInfoResponseWriter) WriteHeader(s int) {
lirw.rw.WriteHeader(s)
lirw.status = s
}
func (lirw *logInfoResponseWriter) Flush() {
f, ok := lirw.rw.(http.Flusher)
if ok {
f.Flush()
}
}
func (lirw *logInfoResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return lirw.rw.(http.Hijacker).Hijack()
}
func (lirw *logInfoResponseWriter) GetStatus() int {
return lirw.status
}
func (lirw *logInfoResponseWriter) GetSize() int {
return lirw.size
}
func (lirw *logInfoResponseWriter) GetBackend() string {
return lirw.backend
}
func (lirw *logInfoResponseWriter) GetFrontend() string {
return lirw.frontend
}
func (lirw *logInfoResponseWriter) SetBackend(backend string) {
lirw.backend = backend
}
func (lirw *logInfoResponseWriter) SetFrontend(frontend string) {
lirw.frontend = frontend
}

116
middlewares/logger_test.go Normal file
View File

@@ -0,0 +1,116 @@
package middlewares
import (
"fmt"
shellwords "github.com/mattn/go-shellwords"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
"runtime"
"testing"
)
type logtestResponseWriter struct{}
var (
logger *Logger
logfileName = "traefikTestLogger.log"
logfilePath string
helloWorld = "Hello, World"
testBackendName = "http://127.0.0.1/testBackend"
testFrontendName = "testFrontend"
testStatus = 123
testHostname = "TestHost"
testUsername = "TestUser"
testPath = "http://testpath"
testPort = 8181
testProto = "HTTP/0.0"
testMethod = "POST"
testReferer = "testReferer"
testUserAgent = "testUserAgent"
testBackend2FrontendMap = map[string]string{
testBackendName: testFrontendName,
}
printedLogdata bool
)
func TestLogger(t *testing.T) {
if runtime.GOOS == "windows" {
logfilePath = filepath.Join(os.Getenv("TEMP"), logfileName)
} else {
logfilePath = filepath.Join("/tmp", logfileName)
}
logger = NewLogger(logfilePath)
defer cleanup()
SetBackend2FrontendMap(&testBackend2FrontendMap)
r := &http.Request{
Header: map[string][]string{
"User-Agent": {testUserAgent},
"Referer": {testReferer},
},
Proto: testProto,
Host: testHostname,
Method: testMethod,
RemoteAddr: fmt.Sprintf("%s:%d", testHostname, testPort),
URL: &url.URL{
User: url.UserPassword(testUsername, ""),
Path: testPath,
},
}
logger.ServeHTTP(&logtestResponseWriter{}, r, LogWriterTestHandlerFunc)
if logdata, err := ioutil.ReadFile(logfilePath); err != nil {
fmt.Printf("%s\n%s\n", string(logdata), err.Error())
assert.Nil(t, err)
} else if tokens, err := shellwords.Parse(string(logdata)); err != nil {
fmt.Printf("%s\n", err.Error())
assert.Nil(t, err)
} else if assert.Equal(t, 14, len(tokens), printLogdata(logdata)) {
assert.Equal(t, testHostname, tokens[0], printLogdata(logdata))
assert.Equal(t, testUsername, tokens[2], printLogdata(logdata))
assert.Equal(t, fmt.Sprintf("%s %s %s", testMethod, testPath, testProto), tokens[5], printLogdata(logdata))
assert.Equal(t, fmt.Sprintf("%d", testStatus), tokens[6], printLogdata(logdata))
assert.Equal(t, fmt.Sprintf("%d", len(helloWorld)), tokens[7], printLogdata(logdata))
assert.Equal(t, testReferer, tokens[8], printLogdata(logdata))
assert.Equal(t, testUserAgent, tokens[9], printLogdata(logdata))
assert.Equal(t, "1", tokens[10], printLogdata(logdata))
assert.Equal(t, testFrontendName, tokens[11], printLogdata(logdata))
assert.Equal(t, testBackendName, tokens[12], printLogdata(logdata))
}
}
func cleanup() {
logger.Close()
os.Remove(logfilePath)
}
func printLogdata(logdata []byte) string {
return fmt.Sprintf(
"\nExpected: %s\n"+
"Actual: %s",
"TestHost - TestUser [13/Apr/2016:07:14:19 -0700] \"POST http://testpath HTTP/0.0\" 123 12 \"testReferer\" \"testUserAgent\" 1 \"testFrontend\" \"http://127.0.0.1/testBackend\" 1ms",
string(logdata))
}
func LogWriterTestHandlerFunc(rw http.ResponseWriter, r *http.Request) {
rw.Write([]byte(helloWorld))
rw.WriteHeader(testStatus)
saveBackendNameForLogger(r, testBackendName)
}
func (lrw *logtestResponseWriter) Header() http.Header {
return map[string][]string{}
}
func (lrw *logtestResponseWriter) Write(b []byte) (int, error) {
return len(b), nil
}
func (lrw *logtestResponseWriter) WriteHeader(s int) {
}

92
middlewares/retry.go Normal file
View File

@@ -0,0 +1,92 @@
package middlewares
import (
"bufio"
"bytes"
log "github.com/Sirupsen/logrus"
"github.com/vulcand/oxy/utils"
"net"
"net/http"
)
// Retry is a middleware that retries requests
type Retry struct {
attempts int
next http.Handler
}
// NewRetry returns a new Retry instance
func NewRetry(attempts int, next http.Handler) *Retry {
return &Retry{
attempts: attempts,
next: next,
}
}
func (retry *Retry) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
attempts := 1
for {
recorder := NewRecorder()
recorder.responseWriter = rw
retry.next.ServeHTTP(recorder, r)
if !isNetworkError(recorder.Code) || attempts >= retry.attempts {
utils.CopyHeaders(rw.Header(), recorder.Header())
rw.WriteHeader(recorder.Code)
rw.Write(recorder.Body.Bytes())
break
}
attempts++
log.Debugf("New attempt %d for request: %v", attempts, r.URL)
}
}
func isNetworkError(status int) bool {
return status == http.StatusBadGateway || status == http.StatusGatewayTimeout
}
// ResponseRecorder is an implementation of http.ResponseWriter that
// records its mutations for later inspection in tests.
type ResponseRecorder struct {
Code int // the HTTP response code from WriteHeader
HeaderMap http.Header // the HTTP response headers
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
responseWriter http.ResponseWriter
}
// NewRecorder returns an initialized ResponseRecorder.
func NewRecorder() *ResponseRecorder {
return &ResponseRecorder{
HeaderMap: make(http.Header),
Body: new(bytes.Buffer),
Code: 200,
}
}
// Header returns the response headers.
func (rw *ResponseRecorder) Header() http.Header {
m := rw.HeaderMap
if m == nil {
m = make(http.Header)
rw.HeaderMap = m
}
return m
}
// Write always succeeds and writes to rw.Body, if not nil.
func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
if rw.Body != nil {
return rw.Body.Write(buf)
}
return 0, nil
}
// WriteHeader sets rw.Code.
func (rw *ResponseRecorder) WriteHeader(code int) {
rw.Code = code
}
// Hijack hijacks the connection
func (rw *ResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return rw.responseWriter.(http.Hijacker).Hijack()
}

View File

@@ -5,7 +5,7 @@ import (
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/containous/mux"
)
// Routes holds the gorilla mux routes (for the API & co).

View File

@@ -0,0 +1,20 @@
package middlewares
import (
"net/http"
)
// SaveBackend sends the backend name to the logger.
type SaveBackend struct {
next http.Handler
}
// NewSaveBackend creates a SaveBackend
func NewSaveBackend(next http.Handler) *SaveBackend {
return &SaveBackend{next}
}
func (sb *SaveBackend) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
saveBackendNameForLogger(r, (*r.URL).String())
sb.next.ServeHTTP(rw, r)
}

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
}

52
mkdocs.yml Normal file
View File

@@ -0,0 +1,52 @@
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
- User Guide:
- 'Configuration examples': 'user-guide/examples.md'
- 'Swarm cluster': 'user-guide/swarm.md'
- Benchmarks: benchmarks.md

View File

@@ -169,13 +169,13 @@ func (_m *Marathon) DeleteApplication(name string) (*marathon.DeploymentID, erro
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)
// UpdateApplication provides a mock function with given fields: application, force
func (_m *Marathon) UpdateApplication(application *marathon.Application, force bool) (*marathon.DeploymentID, error) {
ret := _m.Called(application, force)
var r0 *marathon.DeploymentID
if rf, ok := ret.Get(0).(func(*marathon.Application) *marathon.DeploymentID); ok {
r0 = rf(application)
if rf, ok := ret.Get(0).(func(*marathon.Application, bool) *marathon.DeploymentID); ok {
r0 = rf(application, force)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.DeploymentID)
@@ -183,8 +183,8 @@ func (_m *Marathon) UpdateApplication(application *marathon.Application) (*marat
}
var r1 error
if rf, ok := ret.Get(1).(func(*marathon.Application) error); ok {
r1 = rf(application)
if rf, ok := ret.Get(1).(func(*marathon.Application, bool) error); ok {
r1 = rf(application, force)
} else {
r1 = ret.Error(1)
}
@@ -307,6 +307,29 @@ func (_m *Marathon) Application(name string) (*marathon.Application, error) {
return r0, r1
}
// ApplicationByVersion provides a mock function with given fields: name, version
func (_m *Marathon) ApplicationByVersion(name string, version string) (*marathon.Application, error) {
ret := _m.Called(name, version)
var r0 *marathon.Application
if rf, ok := ret.Get(0).(func(string, string) *marathon.Application); ok {
r0 = rf(name, version)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*marathon.Application)
}
}
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
}
// 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)

View File

@@ -1,6 +1,7 @@
package provider
import (
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/boltdb"
@@ -8,13 +9,13 @@ import (
// BoltDb holds configurations of the BoltDb provider.
type BoltDb struct {
Kv `mapstructure:",squash"`
Kv
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage) error {
func (provider *BoltDb) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
provider.storeType = store.BOLTDB
boltdb.Register()
return provider.provide(configurationChan)
return provider.provide(configurationChan, pool, constraints)
}

View File

@@ -1,6 +1,7 @@
package provider
import (
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/consul"
@@ -8,13 +9,13 @@ import (
// Consul holds configurations of the Consul provider.
type Consul struct {
Kv `mapstructure:",squash"`
Kv
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage) error {
func (provider *Consul) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
provider.storeType = store.CONSUL
consul.Register()
return provider.provide(configurationChan)
return provider.provide(configurationChan, pool, constraints)
}

View File

@@ -2,12 +2,16 @@ package provider
import (
"errors"
"sort"
"strconv"
"strings"
"text/template"
"time"
"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/hashicorp/consul/api"
)
@@ -15,27 +19,64 @@ import (
const (
// DefaultWatchWaitTime is the duration to wait when polling consul
DefaultWatchWaitTime = 15 * time.Second
// DefaultConsulCatalogTagPrefix is a prefix for additional service/node configurations
DefaultConsulCatalogTagPrefix = "traefik"
)
// ConsulCatalog holds configurations of the Consul catalog provider.
type ConsulCatalog struct {
BaseProvider `mapstructure:",squash"`
Endpoint string
Domain string
client *api.Client
BaseProvider
Endpoint string `description:"Consul server endpoint"`
Domain string `description:"Default domain used"`
client *api.Client
Prefix string
}
type serviceUpdate struct {
ServiceName string
Attributes []string
}
type catalogUpdate struct {
Service string
Service *serviceUpdate
Nodes []*api.ServiceEntry
}
type nodeSorter []*api.ServiceEntry
func (a nodeSorter) Len() int {
return len(a)
}
func (a nodeSorter) Swap(i int, j int) {
a[i], a[j] = a[j], a[i]
}
func (a nodeSorter) Less(i int, j int) bool {
lentr := a[i]
rentr := a[j]
ls := strings.ToLower(lentr.Service.Service)
lr := strings.ToLower(rentr.Service.Service)
if ls != lr {
return ls < lr
}
if lentr.Service.Address != rentr.Service.Address {
return lentr.Service.Address < rentr.Service.Address
}
if lentr.Node.Address != rentr.Node.Address {
return lentr.Node.Address < rentr.Node.Address
}
return lentr.Service.Port < rentr.Service.Port
}
func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[string][]string {
watchCh := make(chan map[string][]string)
catalog := provider.client.Catalog()
go func() {
safe.Go(func() {
defer close(watchCh)
opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
@@ -64,7 +105,7 @@ func (provider *ConsulCatalog) watchServices(stopCh <-chan struct{}) <-chan map[
watchCh <- data
}
}
}()
})
return watchCh
}
@@ -78,41 +119,125 @@ func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, erro
return catalogUpdate{}, err
}
nodes := fun.Filter(func(node *api.ServiceEntry) bool {
constraintTags := provider.getContraintTags(node.Service.Tags)
ok, failingConstraint := provider.MatchConstraints(constraintTags)
if ok == false && failingConstraint != nil {
log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String())
}
return ok
}, data).([]*api.ServiceEntry)
//Merge tags of nodes matching constraints, in a single slice.
tags := fun.Foldl(func(node *api.ServiceEntry, set []string) []string {
return fun.Keys(fun.Union(
fun.Set(set),
fun.Set(node.Service.Tags),
).(map[string]bool)).([]string)
}, []string{}, nodes).([]string)
return catalogUpdate{
Service: service,
Nodes: data,
Service: &serviceUpdate{
ServiceName: service,
Attributes: tags,
},
Nodes: nodes,
}, nil
}
func (provider *ConsulCatalog) getEntryPoints(list string) []string {
return strings.Split(list, ",")
}
func (provider *ConsulCatalog) getBackend(node *api.ServiceEntry) string {
return strings.ToLower(node.Service.Service)
}
func (provider *ConsulCatalog) getFrontendValue(service string) string {
return service + "." + provider.Domain
func (provider *ConsulCatalog) getFrontendRule(service serviceUpdate) string {
customFrontendRule := provider.getAttribute("frontend.rule", service.Attributes, "")
if customFrontendRule != "" {
return customFrontendRule
}
return "Host:" + service.ServiceName + "." + provider.Domain
}
func (provider *ConsulCatalog) getBackendAddress(node *api.ServiceEntry) string {
if node.Service.Address != "" {
return node.Service.Address
}
return node.Node.Address
}
func (provider *ConsulCatalog) getBackendName(node *api.ServiceEntry, index int) string {
serviceName := strings.ToLower(node.Service.Service) + "--" + node.Service.Address + "--" + strconv.Itoa(node.Service.Port)
for _, tag := range node.Service.Tags {
serviceName += "--" + normalize(tag)
}
serviceName = strings.Replace(serviceName, ".", "-", -1)
serviceName = strings.Replace(serviceName, "=", "-", -1)
// unique int at the end
serviceName += "--" + strconv.Itoa(index)
return serviceName
}
func (provider *ConsulCatalog) getAttribute(name string, tags []string, defaultValue string) string {
for _, tag := range tags {
if strings.Index(strings.ToLower(tag), DefaultConsulCatalogTagPrefix+".") == 0 {
if kv := strings.SplitN(tag[len(DefaultConsulCatalogTagPrefix+"."):], "=", 2); len(kv) == 2 && strings.ToLower(kv[0]) == strings.ToLower(name) {
return kv[1]
}
}
}
return defaultValue
}
func (provider *ConsulCatalog) getContraintTags(tags []string) []string {
var list []string
for _, tag := range tags {
if strings.Index(strings.ToLower(tag), DefaultConsulCatalogTagPrefix+".tags=") == 0 {
splitedTags := strings.Split(tag[len(DefaultConsulCatalogTagPrefix+".tags="):], ",")
list = append(list, splitedTags...)
}
}
return list
}
func (provider *ConsulCatalog) buildConfig(catalog []catalogUpdate) *types.Configuration {
var FuncMap = template.FuncMap{
"getBackend": provider.getBackend,
"getFrontendValue": provider.getFrontendValue,
"replace": replace,
"getBackend": provider.getBackend,
"getFrontendRule": provider.getFrontendRule,
"getBackendName": provider.getBackendName,
"getBackendAddress": provider.getBackendAddress,
"getAttribute": provider.getAttribute,
"getEntryPoints": provider.getEntryPoints,
}
allNodes := []*api.ServiceEntry{}
serviceNames := []string{}
services := []*serviceUpdate{}
for _, info := range catalog {
if len(info.Nodes) > 0 {
serviceNames = append(serviceNames, info.Service)
allNodes = append(allNodes, info.Nodes...)
for _, node := range info.Nodes {
isEnabled := provider.getAttribute("enable", node.Service.Tags, "true")
if isEnabled != "false" && len(info.Nodes) > 0 {
services = append(services, info.Service)
allNodes = append(allNodes, info.Nodes...)
break
}
}
}
// Ensure a stable ordering of nodes so that identical configurations may be detected
sort.Sort(nodeSorter(allNodes))
templateObjects := struct {
Services []string
Services []*serviceUpdate
Nodes []*api.ServiceEntry
}{
Services: serviceNames,
Services: services,
Nodes: allNodes,
}
@@ -139,13 +264,16 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd
if err != nil {
return nil, err
}
nodes = append(nodes, healthy)
// healthy.Nodes can be empty if constraints do not match, without throwing error
if healthy.Service != nil && len(healthy.Nodes) > 0 {
nodes = append(nodes, healthy)
}
}
}
return nodes, nil
}
func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessage) error {
func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessage, stop chan bool) error {
stopCh := make(chan struct{})
serviceCatalog := provider.watchServices(stopCh)
@@ -153,6 +281,8 @@ func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessag
for {
select {
case <-stop:
return nil
case index, ok := <-serviceCatalog:
if !ok {
return errors.New("Consul service list nil")
@@ -173,7 +303,7 @@ func (provider *ConsulCatalog) watch(configurationChan chan<- types.ConfigMessag
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage) error {
func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
config := api.DefaultConfig()
config.Address = provider.Endpoint
client, err := api.NewClient(config)
@@ -181,19 +311,20 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess
return err
}
provider.client = client
provider.Constraints = append(provider.Constraints, constraints...)
go func() {
pool.Go(func(stop chan bool) {
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)
return provider.watch(configurationChan, stop)
}
err := backoff.RetryNotify(worker, backoff.NewExponentialBackOff(), notify)
if err != nil {
log.Fatalf("Cannot connect to consul server %+v", err)
}
}()
})
return err
}

View File

@@ -2,29 +2,174 @@ package provider
import (
"reflect"
"sort"
"testing"
"github.com/containous/traefik/types"
"github.com/hashicorp/consul/api"
)
func TestConsulCatalogGetFrontendValue(t *testing.T) {
func TestConsulCatalogGetFrontendRule(t *testing.T) {
provider := &ConsulCatalog{
Domain: "localhost",
}
services := []struct {
service string
service serviceUpdate
expected string
}{
{
service: "foo",
expected: "foo.localhost",
service: serviceUpdate{
ServiceName: "foo",
Attributes: []string{},
},
expected: "Host:foo.localhost",
},
{
service: serviceUpdate{
ServiceName: "foo",
Attributes: []string{
"traefik.frontend.rule=Host:*.example.com",
},
},
expected: "Host:*.example.com",
},
}
for _, e := range services {
actual := provider.getFrontendValue(e.service)
actual := provider.getFrontendRule(e.service)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestConsulCatalogGetAttribute(t *testing.T) {
provider := &ConsulCatalog{
Domain: "localhost",
}
services := []struct {
tags []string
key string
defaultValue string
expected string
}{
{
tags: []string{
"foo.bar=ramdom",
"traefik.backend.weight=42",
},
key: "backend.weight",
defaultValue: "",
expected: "42",
},
{
tags: []string{
"foo.bar=ramdom",
"traefik.backend.wei=42",
},
key: "backend.weight",
defaultValue: "",
expected: "",
},
}
for _, e := range services {
actual := provider.getAttribute(e.key, e.tags, e.defaultValue)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestConsulCatalogGetBackendAddress(t *testing.T) {
provider := &ConsulCatalog{
Domain: "localhost",
}
services := []struct {
node *api.ServiceEntry
expected string
}{
{
node: &api.ServiceEntry{
Node: &api.Node{
Address: "10.1.0.1",
},
Service: &api.AgentService{
Address: "10.2.0.1",
},
},
expected: "10.2.0.1",
},
{
node: &api.ServiceEntry{
Node: &api.Node{
Address: "10.1.0.1",
},
Service: &api.AgentService{
Address: "",
},
},
expected: "10.1.0.1",
},
}
for _, e := range services {
actual := provider.getBackendAddress(e.node)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
}
}
func TestConsulCatalogGetBackendName(t *testing.T) {
provider := &ConsulCatalog{
Domain: "localhost",
}
services := []struct {
node *api.ServiceEntry
expected string
}{
{
node: &api.ServiceEntry{
Service: &api.AgentService{
Service: "api",
Address: "10.0.0.1",
Port: 80,
Tags: []string{},
},
},
expected: "api--10-0-0-1--80--0",
},
{
node: &api.ServiceEntry{
Service: &api.AgentService{
Service: "api",
Address: "10.0.0.1",
Port: 80,
Tags: []string{"traefik.weight=42", "traefik.enable=true"},
},
},
expected: "api--10-0-0-1--80--traefik-weight-42--traefik-enable-true--1",
},
{
node: &api.ServiceEntry{
Service: &api.AgentService{
Service: "api",
Address: "10.0.0.1",
Port: 80,
Tags: []string{"a funny looking tag"},
},
},
expected: "api--10-0-0-1--80--a-funny-looking-tag--2",
},
}
for i, e := range services {
actual := provider.getBackendName(e.node, i)
if actual != e.expected {
t.Fatalf("expected %q, got %q", e.expected, actual)
}
@@ -49,7 +194,10 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
{
nodes: []catalogUpdate{
{
Service: "test",
Service: &serviceUpdate{
ServiceName: "test",
Attributes: []string{},
},
},
},
expectedFrontends: map[string]*types.Frontend{},
@@ -58,12 +206,26 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
{
nodes: []catalogUpdate{
{
Service: "test",
Service: &serviceUpdate{
ServiceName: "test",
Attributes: []string{
"traefik.backend.loadbalancer=drr",
"traefik.backend.circuitbreaker=NetworkErrorRatio() > 0.5",
"random.foo=bar",
},
},
Nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "test",
Address: "127.0.0.1",
Port: 80,
Tags: []string{
"traefik.backend.weight=42",
"random.foo=bar",
"traefik.backend.passHostHeader=true",
"traefik.protocol=https",
},
},
Node: &api.Node{
Node: "localhost",
@@ -75,11 +237,11 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
},
expectedFrontends: map[string]*types.Frontend{
"frontend-test": {
Backend: "backend-test",
Backend: "backend-test",
PassHostHeader: true,
Routes: map[string]types.Route{
"route-host-test": {
Rule: "Host",
Value: "test.localhost",
Rule: "Host:test.localhost",
},
},
},
@@ -87,12 +249,17 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
expectedBackends: map[string]*types.Backend{
"backend-test": {
Servers: map[string]types.Server{
"server-localhost-80": {
URL: "http://127.0.0.1:80",
"test--127-0-0-1--80--traefik-backend-weight-42--random-foo-bar--traefik-backend-passHostHeader-true--traefik-protocol-https--0": {
URL: "https://127.0.0.1:80",
Weight: 42,
},
},
CircuitBreaker: nil,
LoadBalancer: nil,
CircuitBreaker: &types.CircuitBreaker{
Expression: "NetworkErrorRatio() > 0.5",
},
LoadBalancer: &types.LoadBalancer{
Method: "drr",
},
},
},
},
@@ -108,3 +275,195 @@ func TestConsulCatalogBuildConfig(t *testing.T) {
}
}
}
func TestConsulCatalogNodeSorter(t *testing.T) {
cases := []struct {
nodes []*api.ServiceEntry
expected []*api.ServiceEntry
}{
{
nodes: []*api.ServiceEntry{},
expected: []*api.ServiceEntry{},
},
{
nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.1",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
},
expected: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.1",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
},
},
{
nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.2",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "bar",
Address: "127.0.0.2",
Port: 81,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.1",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
{
Service: &api.AgentService{
Service: "bar",
Address: "127.0.0.2",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
},
expected: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "bar",
Address: "127.0.0.2",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "bar",
Address: "127.0.0.2",
Port: 81,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.1",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "127.0.0.2",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
},
},
{
nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
},
expected: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "foo",
Address: "",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
{
Service: &api.AgentService{
Service: "foo",
Address: "",
Port: 80,
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.2",
},
},
},
},
}
for _, c := range cases {
sort.Sort(nodeSorter(c.nodes))
actual := c.nodes
if !reflect.DeepEqual(actual, c.expected) {
t.Fatalf("expected %q, got %q", c.expected, actual)
}
}
}

View File

@@ -2,87 +2,149 @@ package provider
import (
"errors"
"fmt"
"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/fsouza/go-dockerclient"
"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
BaseProvider
Endpoint string `description:"Docker server endpoint. Can be a tcp or a unix socket endpoint"`
Domain string `description:"Default domain used"`
TLS *DockerTLS `description:"Enable Docker TLS support"`
}
// DockerTLS holds TLS specific configurations
type DockerTLS struct {
CA string
Cert string
Key string
InsecureSkipVerify bool
CA string `description:"TLS CA"`
Cert string `description:"TLS cert"`
Key string `description:"TLS key"`
InsecureSkipVerify bool `description:"TLS insecure skip verify"`
}
func (provider *Docker) createClient() (client.APIClient, error) {
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 {
go func() {
func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
provider.Constraints = append(provider.Constraints, constraints...)
// TODO register this routine in pool, and watch for stop channel
safe.Go(func() {
operation := func() error {
var dockerClient *docker.Client
var err error
if provider.TLS != nil {
dockerClient, err = docker.NewTLSClient(provider.Endpoint,
provider.TLS.Cert, provider.TLS.Key, provider.TLS.CA)
if err == nil {
dockerClient.TLSConfig.InsecureSkipVerify = provider.TLS.InsecureSkipVerify
}
} else {
dockerClient, err = docker.NewClient(provider.Endpoint)
}
dockerClient, err := provider.createClient()
if err != nil {
log.Errorf("Failed to create a client for docker, error: %s", err)
return err
}
err = dockerClient.Ping()
ctx := context.Background()
version, err := dockerClient.ServerVersion(ctx)
log.Debugf("Docker connection established with docker %s (API %s)", version.Version, version.APIVersion)
containers, err := listContainers(ctx, dockerClient)
if err != nil {
log.Errorf("Docker connection error %+v", err)
log.Errorf("Failed to list containers for docker, error %s", err)
return err
}
log.Debug("Docker connection established")
configuration := provider.loadDockerConfig(listContainers(dockerClient))
configuration := provider.loadDockerConfig(containers)
configurationChan <- types.ConfigMessage{
ProviderName: "docker",
Configuration: configuration,
}
if provider.Watch {
dockerEvents := make(chan *docker.APIEvents)
dockerClient.AddEventListener(dockerEvents)
log.Debug("Docker listening")
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(listContainers(dockerClient))
if configuration != nil {
configurationChan <- types.ConfigMessage{
ProviderName: "docker",
Configuration: configuration,
}
ctx, cancel := context.WithCancel(ctx)
pool.Go(func(stop chan bool) {
for {
select {
case <-stop:
cancel()
return
}
}
})
f := filters.NewArgs()
f.Add("type", "container")
options := dockertypes.EventsOptions{
Filters: f,
}
eventHandler := events.NewHandler(events.ByAction)
startStopHandle := func(m eventtypes.Message) {
log.Debugf("Docker event received %+v", m)
containers, err := listContainers(ctx, dockerClient)
if err != nil {
log.Errorf("Failed to list containers for docker, error %s", err)
// Call cancel to get out of the monitor
cancel()
return
}
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
@@ -94,36 +156,38 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage) er
if err != nil {
log.Fatalf("Cannot connect to docker server %+v", err)
}
}()
})
return nil
}
func (provider *Docker) loadDockerConfig(containersInspected []docker.Container) *types.Configuration {
func (provider *Docker) loadDockerConfig(containersInspected []dockertypes.ContainerJSON) *types.Configuration {
var DockerFuncMap = template.FuncMap{
"getBackend": provider.getBackend,
"getIPAddress": provider.getIPAddress,
"getPort": provider.getPort,
"getWeight": provider.getWeight,
"getDomain": provider.getDomain,
"getProtocol": provider.getProtocol,
"getPassHostHeader": provider.getPassHostHeader,
"getPriority": provider.getPriority,
"getEntryPoints": provider.getEntryPoints,
"getFrontendValue": provider.getFrontendValue,
"getFrontendRule": provider.getFrontendRule,
"replace": replace,
}
// filter containers
filteredContainers := fun.Filter(containerFilter, containersInspected).([]docker.Container)
filteredContainers := fun.Filter(provider.ContainerFilter, containersInspected).([]dockertypes.ContainerJSON)
frontends := map[string][]docker.Container{}
frontends := map[string][]dockertypes.ContainerJSON{}
for _, container := range filteredContainers {
frontends[provider.getFrontendName(container)] = append(frontends[provider.getFrontendName(container)], container)
frontendName := provider.getFrontendName(container)
frontends[frontendName] = append(frontends[frontendName], container)
}
templateObjects := struct {
Containers []docker.Container
Frontends map[string][]docker.Container
Containers []dockertypes.ContainerJSON
Frontends map[string][]dockertypes.ContainerJSON
Domain string
}{
filteredContainers,
@@ -138,12 +202,13 @@ func (provider *Docker) loadDockerConfig(containersInspected []docker.Container)
return configuration
}
func containerFilter(container docker.Container) bool {
if len(container.NetworkSettings.Ports) == 0 {
log.Debugf("Filtering container without port %s", container.Name)
// ContainerFilter checks if container have to be exposed
func (provider *Docker) ContainerFilter(container dockertypes.ContainerJSON) bool {
_, err := strconv.Atoi(container.Config.Labels["traefik.port"])
if len(container.NetworkSettings.Ports) == 0 && err != nil {
log.Debugf("Filtering container without port and no traefik.port label %s", container.Name)
return false
}
_, 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
@@ -154,50 +219,71 @@ func containerFilter(container docker.Container) bool {
return false
}
labels, err := getLabels(container, []string{"traefik.frontend.rule", "traefik.frontend.value"})
if len(labels) != 0 && err != nil {
log.Debugf("Filtering bad labeled container %s", container.Name)
constraintTags := strings.Split(container.Config.Labels["traefik.tags"], ",")
if ok, failingConstraint := provider.MatchConstraints(constraintTags); !ok {
if failingConstraint != nil {
log.Debugf("Container %v pruned by '%v' constraint", container.Name, failingConstraint.String())
}
return false
}
return true
}
func (provider *Docker) getFrontendName(container docker.Container) string {
func (provider *Docker) getFrontendName(container dockertypes.ContainerJSON) string {
// Replace '.' with '-' in quoted keys because of this issue https://github.com/BurntSushi/toml/issues/78
frontendName := fmt.Sprintf("%s-%s", provider.getFrontendRule(container), provider.getFrontendValue(container))
frontendName = strings.Replace(frontendName, "[", "", -1)
frontendName = strings.Replace(frontendName, "]", "", -1)
return strings.Replace(frontendName, ".", "-", -1)
}
// GetFrontendValue returns the frontend value for the specified container, using
// it's label. It returns a default one if the label is not present.
func (provider *Docker) getFrontendValue(container docker.Container) string {
if label, err := getLabel(container, "traefik.frontend.value"); err == nil {
return label
}
return getEscapedName(container.Name) + "." + provider.Domain
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 docker.Container) string {
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"
return "Host:" + provider.getSubDomain(container.Name) + "." + provider.Domain
}
func (provider *Docker) getBackend(container docker.Container) string {
func (provider *Docker) getBackend(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.backend"); err == nil {
return label
}
return getEscapedName(container.Name)
return normalize(container.Name)
}
func (provider *Docker) getPort(container docker.Container) string {
func (provider *Docker) getIPAddress(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.docker.network"); err == nil && label != "" {
networks := container.NetworkSettings.Networks
if networks != nil {
network := networks[label]
if network != nil {
return network.IPAddress
}
}
}
// If net==host, quick n' dirty, we return 127.0.0.1
// This will work locally, but will fail with swarm.
if container.HostConfig != nil && "host" == container.HostConfig.NetworkMode {
return "127.0.0.1"
}
for _, network := range container.NetworkSettings.Networks {
return network.IPAddress
}
return ""
}
func (provider *Docker) getPort(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.port"); err == nil {
return label
}
@@ -207,42 +293,49 @@ func (provider *Docker) getPort(container docker.Container) string {
return ""
}
func (provider *Docker) getWeight(container docker.Container) string {
func (provider *Docker) getWeight(container dockertypes.ContainerJSON) string {
if label, err := getLabel(container, "traefik.weight"); err == nil {
return label
}
return "0"
return "1"
}
func (provider *Docker) getDomain(container docker.Container) string {
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 docker.Container) string {
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 docker.Container) string {
func (provider *Docker) getPassHostHeader(container dockertypes.ContainerJSON) string {
if passHostHeader, err := getLabel(container, "traefik.frontend.passHostHeader"); err == nil {
return passHostHeader
}
return "false"
return "true"
}
func (provider *Docker) getEntryPoints(container docker.Container) []string {
func (provider *Docker) getPriority(container dockertypes.ContainerJSON) string {
if priority, err := getLabel(container, "traefik.frontend.priority"); err == nil {
return priority
}
return "0"
}
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 docker.Container, label string) (string, error) {
func getLabel(container dockertypes.ContainerJSON, label string) (string, error) {
for key, value := range container.Config.Labels {
if key == label {
return value, nil
@@ -251,7 +344,7 @@ func getLabel(container docker.Container, label string) (string, error) {
return "", errors.New("Label not found:" + label)
}
func getLabels(container docker.Container, labels []string) (map[string]string, error) {
func getLabels(container dockertypes.ContainerJSON, labels []string) (map[string]string, error) {
var globalErr error
foundLabels := map[string]string{}
for _, label := range labels {
@@ -267,14 +360,26 @@ func getLabels(container docker.Container, labels []string) (map[string]string,
return foundLabels, globalErr
}
func listContainers(dockerClient *docker.Client) []docker.Container {
containerList, _ := dockerClient.ListContainers(docker.ListContainersOptions{})
containersInspected := []docker.Container{}
func listContainers(ctx context.Context, dockerClient client.APIClient) ([]dockertypes.ContainerJSON, error) {
containerList, err := dockerClient.ContainerList(ctx, dockertypes.ContainerListOptions{})
if err != nil {
return []dockertypes.ContainerJSON{}, err
}
containersInspected := []dockertypes.ContainerJSON{}
// get inspect containers
for _, container := range containerList {
containerInspected, _ := dockerClient.InspectContainer(container.ID)
containersInspected = append(containersInspected, *containerInspected)
containerInspected, err := dockerClient.ContainerInspect(ctx, container.ID)
if err != nil {
log.Warnf("Failed to inspect container %s, error: %s", container.ID, err)
} else {
containersInspected = append(containersInspected, containerInspected)
}
}
return containersInspected
return containersInspected, nil
}
// Escape beginning slash "/", convert all others to dash "-"
func (provider *Docker) getSubDomain(name string) string {
return strings.Replace(strings.TrimPrefix(name, "/"), "/", "-", -1)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
package provider
import (
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/docker/libkv/store"
"github.com/docker/libkv/store/etcd"
@@ -8,13 +9,13 @@ import (
// Etcd holds configurations of the Etcd provider.
type Etcd struct {
Kv `mapstructure:",squash"`
Kv
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage) error {
func (provider *Etcd) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
provider.storeType = store.ETCD
etcd.Register()
return provider.provide(configurationChan)
return provider.provide(configurationChan, pool, constraints)
}

View File

@@ -7,18 +7,19 @@ import (
"github.com/BurntSushi/toml"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"gopkg.in/fsnotify.v1"
)
// File holds configurations of the File provider.
type File struct {
BaseProvider `mapstructure:",squash"`
BaseProvider
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage) error {
func (provider *File) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, _ []types.Constraint) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Error("Error creating file watcher", err)
@@ -34,10 +35,12 @@ func (provider *File) Provide(configurationChan chan<- types.ConfigMessage) erro
if provider.Watch {
// Process events
go func() {
pool.Go(func(stop chan bool) {
defer watcher.Close()
for {
select {
case <-stop:
return
case event := <-watcher.Events:
if strings.Contains(event.Name, file.Name()) {
log.Debug("File event:", event)
@@ -53,7 +56,7 @@ func (provider *File) Provide(configurationChan chan<- types.ConfigMessage) erro
log.Error("Watcher event error", error)
}
}
}()
})
err = watcher.Add(filepath.Dir(file.Name()))
if err != nil {
log.Error("Error adding file watcher", err)

274
provider/k8s/client.go Normal file
View File

@@ -0,0 +1,274 @@
package k8s
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"github.com/parnurzeal/gorequest"
"net/http"
"net/url"
"strings"
)
const (
// APIEndpoint defines the base path for kubernetes API resources.
APIEndpoint = "/api/v1"
extentionsEndpoint = "/apis/extensions/v1beta1"
defaultIngress = "/ingresses"
namespaces = "/namespaces/"
)
// Client is a client for the Kubernetes master.
type Client interface {
GetIngresses(predicate func(Ingress) bool) ([]Ingress, error)
GetService(name, namespace string) (Service, error)
GetEndpoints(name, namespace string) (Endpoints, error)
WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error)
}
type clientImpl struct {
endpointURL string
tls *tls.Config
token string
caCert []byte
}
// NewClient returns a new Kubernetes client.
// The provided host is an url (scheme://hostname[:port]) of a
// Kubernetes master without any path.
// The provided client is an authorized http.Client used to perform requests to the Kubernetes API master.
func NewClient(baseURL string, caCert []byte, token string) (Client, error) {
validURL, err := url.Parse(baseURL)
if err != nil {
return nil, fmt.Errorf("failed to parse URL %q: %v", baseURL, err)
}
return &clientImpl{
endpointURL: strings.TrimSuffix(validURL.String(), "/"),
token: token,
caCert: caCert,
}, nil
}
// GetIngresses returns all ingresses in the cluster
func (c *clientImpl) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) {
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
body, err := c.do(c.request(getURL))
if err != nil {
return nil, fmt.Errorf("failed to create ingresses request: GET %q : %v", getURL, err)
}
var ingressList IngressList
if err := json.Unmarshal(body, &ingressList); err != nil {
return nil, fmt.Errorf("failed to decode list of ingress resources: %v", err)
}
ingresses := ingressList.Items[:0]
for _, ingress := range ingressList.Items {
if predicate(ingress) {
ingresses = append(ingresses, ingress)
}
}
return ingresses, nil
}
// WatchIngresses returns all ingresses in the cluster
func (c *clientImpl) WatchIngresses(stopCh <-chan bool) (chan interface{}, chan error, error) {
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
return c.watch(getURL, stopCh)
}
// GetService returns the named service from the named namespace
func (c *clientImpl) GetService(name, namespace string) (Service, error) {
getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/services/" + name
body, err := c.do(c.request(getURL))
if err != nil {
return Service{}, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err)
}
var service Service
if err := json.Unmarshal(body, &service); err != nil {
return Service{}, fmt.Errorf("failed to decode service resource: %v", err)
}
return service, nil
}
// WatchServices returns all services in the cluster
func (c *clientImpl) WatchServices(stopCh <-chan bool) (chan interface{}, chan error, error) {
getURL := c.endpointURL + APIEndpoint + "/services"
return c.watch(getURL, stopCh)
}
// GetEndpoints returns the named Endpoints
// Endpoints have the same name as the coresponding service
func (c *clientImpl) GetEndpoints(name, namespace string) (Endpoints, error) {
getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/endpoints/" + name
body, err := c.do(c.request(getURL))
if err != nil {
return Endpoints{}, fmt.Errorf("failed to create endpoints request: GET %q : %v", getURL, err)
}
var endpoints Endpoints
if err := json.Unmarshal(body, &endpoints); err != nil {
return Endpoints{}, fmt.Errorf("failed to decode endpoints resources: %v", err)
}
return endpoints, nil
}
// WatchEndpoints returns endpoints in the cluster
func (c *clientImpl) WatchEndpoints(stopCh <-chan bool) (chan interface{}, chan error, error) {
getURL := c.endpointURL + APIEndpoint + "/endpoints"
return c.watch(getURL, stopCh)
}
// WatchAll returns events in the cluster
func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) {
watchCh := make(chan interface{}, 10)
errCh := make(chan error, 10)
stopIngresses := make(chan bool)
chanIngresses, chanIngressesErr, err := c.WatchIngresses(stopIngresses)
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
}
stopServices := make(chan bool)
chanServices, chanServicesErr, err := c.WatchServices(stopServices)
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
}
stopEndpoints := make(chan bool)
chanEndpoints, chanEndpointsErr, err := c.WatchEndpoints(stopEndpoints)
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
}
go func() {
defer close(watchCh)
defer close(errCh)
defer close(stopIngresses)
defer close(stopServices)
defer close(stopEndpoints)
for {
select {
case <-stopCh:
stopIngresses <- true
stopServices <- true
stopEndpoints <- true
return
case err := <-chanIngressesErr:
errCh <- err
case err := <-chanServicesErr:
errCh <- err
case err := <-chanEndpointsErr:
errCh <- err
case event := <-chanIngresses:
watchCh <- event
case event := <-chanServices:
watchCh <- event
case event := <-chanEndpoints:
watchCh <- event
}
}
}()
return watchCh, errCh, nil
}
func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) {
res, body, errs := request.EndBytes()
if errs != nil {
return nil, fmt.Errorf("failed to create request: GET %q : %v", request.Url, errs)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, request.Url, string(body))
}
return body, nil
}
func (c *clientImpl) request(url string) *gorequest.SuperAgent {
// Make request to Kubernetes API
request := gorequest.New().Get(url)
request.Transport.DisableKeepAlives = true
if strings.HasPrefix(url, "http://") {
return request
}
if len(c.token) > 0 {
request.Header["Authorization"] = "Bearer " + c.token
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(c.caCert)
c.tls = &tls.Config{RootCAs: pool}
}
return request.TLSClientConfig(c.tls)
}
// GenericObject generic object
type GenericObject struct {
TypeMeta `json:",inline"`
ListMeta `json:"metadata,omitempty"`
}
func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, chan error, error) {
watchCh := make(chan interface{}, 10)
errCh := make(chan error, 10)
// get version
body, err := c.do(c.request(url))
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to do version request: GET %q : %v", url, err)
}
var generic GenericObject
if err := json.Unmarshal(body, &generic); err != nil {
return watchCh, errCh, fmt.Errorf("failed to decode version %v", err)
}
resourceVersion := generic.ResourceVersion
url = url + "?watch&resourceVersion=" + resourceVersion
// Make request to Kubernetes API
request := c.request(url)
req, err := request.MakeRequest()
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err)
}
request.Client.Transport = request.Transport
res, err := request.Client.Do(req)
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err)
}
go func() {
finishCh := make(chan bool)
defer close(finishCh)
defer close(watchCh)
defer close(errCh)
go func() {
defer res.Body.Close()
for {
var eventList interface{}
if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil {
if !strings.Contains(err.Error(), "net/http: request canceled") {
errCh <- fmt.Errorf("failed to decode watch event: GET %q : %v", url, err)
}
finishCh <- true
return
}
watchCh <- eventList
}
}()
select {
case <-stopCh:
go func() {
request.Transport.CancelRequest(req)
}()
<-finishCh
return
}
}()
return watchCh, errCh, nil
}

84
provider/k8s/endpoints.go Normal file
View File

@@ -0,0 +1,84 @@
package k8s
// Endpoints is a collection of endpoints that implement the actual service. Example:
// Name: "mysvc",
// Subsets: [
// {
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
// },
// {
// Addresses: [{"ip": "10.10.3.3"}],
// Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}]
// },
// ]
type Endpoints struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`
// The set of all endpoints is the union of all subsets.
Subsets []EndpointSubset
}
// EndpointSubset is a group of addresses with a common set of ports. The
// expanded set of endpoints is the Cartesian product of Addresses x Ports.
// For example, given:
// {
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
// }
// The resulting set of endpoints can be viewed as:
// a: [ 10.10.1.1:8675, 10.10.2.2:8675 ],
// b: [ 10.10.1.1:309, 10.10.2.2:309 ]
type EndpointSubset struct {
Addresses []EndpointAddress
NotReadyAddresses []EndpointAddress
Ports []EndpointPort
}
// EndpointAddress is a tuple that describes single IP address.
type EndpointAddress struct {
// The IP of this endpoint.
// IPv6 is also accepted but not fully supported on all platforms. Also, certain
// kubernetes components, like kube-proxy, are not IPv6 ready.
// TODO: This should allow hostname or IP, see #4447.
IP string
// Optional: Hostname of this endpoint
// Meant to be used by DNS servers etc.
Hostname string `json:"hostname,omitempty"`
// Optional: The kubernetes object related to the entry point.
TargetRef *ObjectReference
}
// EndpointPort is a tuple that describes a single port.
type EndpointPort struct {
// The name of this port (corresponds to ServicePort.Name). Optional
// if only one port is defined. Must be a DNS_LABEL.
Name string
// The port number.
Port int32
// The IP protocol for this port.
Protocol Protocol
}
// ObjectReference contains enough information to let you inspect or modify the referred object.
type ObjectReference struct {
Kind string `json:"kind,omitempty"`
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
UID UID `json:"uid,omitempty"`
APIVersion string `json:"apiVersion,omitempty"`
ResourceVersion string `json:"resourceVersion,omitempty"`
// Optional. If referring to a piece of an object instead of an entire object, this string
// should contain information to identify the sub-object. For example, if the object
// reference is to a container within a pod, this would take on a value like:
// "spec.containers{name}" (where "name" refers to the name of the container that triggered
// the event) or if no container name is specified "spec.containers[2]" (container with
// index 2 in this pod). This syntax is chosen only to have some well-defined way of
// referencing a part of an object.
// TODO: this design is not final and this field is subject to change in the future.
FieldPath string `json:"fieldPath,omitempty"`
}

151
provider/k8s/ingress.go Normal file
View File

@@ -0,0 +1,151 @@
package k8s
// Ingress is a collection of rules that allow inbound connections to reach the
// endpoints defined by a backend. An Ingress can be configured to give services
// externally-reachable urls, load balance traffic, terminate SSL, offer name
// based virtual hosting etc.
type Ingress struct {
TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
ObjectMeta `json:"metadata,omitempty"`
// Spec is the desired state of the Ingress.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
Spec IngressSpec `json:"spec,omitempty"`
// Status is the current state of the Ingress.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
Status IngressStatus `json:"status,omitempty"`
}
// IngressList is a collection of Ingress.
type IngressList struct {
TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
ListMeta `json:"metadata,omitempty"`
// Items is the list of Ingress.
Items []Ingress `json:"items"`
}
// IngressSpec describes the Ingress the user wishes to exist.
type IngressSpec struct {
// A default backend capable of servicing requests that don't match any
// rule. At least one of 'backend' or 'rules' must be specified. This field
// is optional to allow the loadbalancer controller or defaulting logic to
// specify a global default.
Backend *IngressBackend `json:"backend,omitempty"`
// TLS configuration. Currently the Ingress only supports a single TLS
// port, 443. If multiple members of this list specify different hosts, they
// will be multiplexed on the same port according to the hostname specified
// through the SNI TLS extension, if the ingress controller fulfilling the
// ingress supports SNI.
TLS []IngressTLS `json:"tls,omitempty"`
// A list of host rules used to configure the Ingress. If unspecified, or
// no rule matches, all traffic is sent to the default backend.
Rules []IngressRule `json:"rules,omitempty"`
// TODO: Add the ability to specify load-balancer IP through claims
}
// IngressTLS describes the transport layer security associated with an Ingress.
type IngressTLS struct {
// Hosts are a list of hosts included in the TLS certificate. The values in
// this list must match the name/s used in the tlsSecret. Defaults to the
// wildcard host setting for the loadbalancer controller fulfilling this
// Ingress, if left unspecified.
Hosts []string `json:"hosts,omitempty"`
// SecretName is the name of the secret used to terminate SSL traffic on 443.
// Field is left optional to allow SSL routing based on SNI hostname alone.
// If the SNI host in a listener conflicts with the "Host" header field used
// by an IngressRule, the SNI host is used for termination and value of the
// Host header is used for routing.
SecretName string `json:"secretName,omitempty"`
// TODO: Consider specifying different modes of termination, protocols etc.
}
// IngressStatus describe the current state of the Ingress.
type IngressStatus struct {
// LoadBalancer contains the current status of the load-balancer.
LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty"`
}
// IngressRule represents the rules mapping the paths under a specified host to
// the related backend services. Incoming requests are first evaluated for a host
// match, then routed to the backend associated with the matching IngressRuleValue.
type IngressRule struct {
// Host is the fully qualified domain name of a network host, as defined
// by RFC 3986. Note the following deviations from the "host" part of the
// URI as defined in the RFC:
// 1. IPs are not allowed. Currently an IngressRuleValue can only apply to the
// IP in the Spec of the parent Ingress.
// 2. The `:` delimiter is not respected because ports are not allowed.
// Currently the port of an Ingress is implicitly :80 for http and
// :443 for https.
// Both these may change in the future.
// Incoming requests are matched against the host before the IngressRuleValue.
// If the host is unspecified, the Ingress routes all traffic based on the
// specified IngressRuleValue.
Host string `json:"host,omitempty"`
// IngressRuleValue represents a rule to route requests for this IngressRule.
// If unspecified, the rule defaults to a http catch-all. Whether that sends
// just traffic matching the host to the default backend or all traffic to the
// default backend, is left to the controller fulfilling the Ingress. Http is
// currently the only supported IngressRuleValue.
IngressRuleValue `json:",inline,omitempty"`
}
// IngressRuleValue represents a rule to apply against incoming requests. If the
// rule is satisfied, the request is routed to the specified backend. Currently
// mixing different types of rules in a single Ingress is disallowed, so exactly
// one of the following must be set.
type IngressRuleValue struct {
//TODO:
// 1. Consider renaming this resource and the associated rules so they
// aren't tied to Ingress. They can be used to route intra-cluster traffic.
// 2. Consider adding fields for ingress-type specific global options
// usable by a loadbalancer, like http keep-alive.
HTTP *HTTPIngressRuleValue `json:"http,omitempty"`
}
// HTTPIngressRuleValue is a list of http selectors pointing to backends.
// In the example: http://<host>/<path>?<searchpart> -> backend where
// where parts of the url correspond to RFC 3986, this resource will be used
// to match against everything after the last '/' and before the first '?'
// or '#'.
type HTTPIngressRuleValue struct {
// A collection of paths that map requests to backends.
Paths []HTTPIngressPath `json:"paths"`
// TODO: Consider adding fields for ingress-type specific global
// options usable by a loadbalancer, like http keep-alive.
}
// HTTPIngressPath associates a path regex with a backend. Incoming urls matching
// the path are forwarded to the backend.
type HTTPIngressPath struct {
// Path is a extended POSIX regex as defined by IEEE Std 1003.1,
// (i.e this follows the egrep/unix syntax, not the perl syntax)
// matched against the path of an incoming request. Currently it can
// contain characters disallowed from the conventional "path"
// part of a URL as defined by RFC 3986. Paths must begin with
// a '/'. If unspecified, the path defaults to a catch all sending
// traffic to the backend.
Path string `json:"path,omitempty"`
// Backend defines the referenced service endpoint to which the traffic
// will be forwarded to.
Backend IngressBackend `json:"backend"`
}
// IngressBackend describes all endpoints for a given service and port.
type IngressBackend struct {
// Specifies the name of the referenced service.
ServiceName string `json:"serviceName"`
// Specifies the port of the referenced service.
ServicePort IntOrString `json:"servicePort"`
}

326
provider/k8s/service.go Normal file
View File

@@ -0,0 +1,326 @@
package k8s
import (
"encoding/json"
"strconv"
"time"
)
// TypeMeta describes an individual object in an API response or request
// with strings representing the type of the object and its API schema version.
// Structures that are versioned or persisted should inline TypeMeta.
type TypeMeta struct {
// Kind is a string value representing the REST resource this object represents.
// Servers may infer this from the endpoint the client submits requests to.
// Cannot be updated.
// In CamelCase.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds
Kind string `json:"kind,omitempty"`
// APIVersion defines the versioned schema of this representation of an object.
// Servers should convert recognized schemas to the latest internal value, and
// may reject unrecognized values.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources
APIVersion string `json:"apiVersion,omitempty"`
}
// ObjectMeta is metadata that all persisted resources must have, which includes all objects
// users must create.
type ObjectMeta struct {
// Name is unique within a namespace. Name is required when creating resources, although
// some resources may allow a client to request the generation of an appropriate name
// automatically. Name is primarily intended for creation idempotence and configuration
// definition.
Name string `json:"name,omitempty"`
// GenerateName indicates that the name should be made unique by the server prior to persisting
// it. A non-empty value for the field indicates the name will be made unique (and the name
// returned to the client will be different than the name passed). The value of this field will
// be combined with a unique suffix on the server if the Name field has not been provided.
// The provided value must be valid within the rules for Name, and may be truncated by the length
// of the suffix required to make the value unique on the server.
//
// If this field is specified, and Name is not present, the server will NOT return a 409 if the
// generated name exists - instead, it will either return 201 Created or 500 with Reason
// ServerTimeout indicating a unique name could not be found in the time allotted, and the client
// should retry (optionally after the time indicated in the Retry-After header).
GenerateName string `json:"generateName,omitempty"`
// Namespace defines the space within which name must be unique. An empty namespace is
// equivalent to the "default" namespace, but "default" is the canonical representation.
// Not all objects are required to be scoped to a namespace - the value of this field for
// those objects will be empty.
Namespace string `json:"namespace,omitempty"`
// SelfLink is a URL representing this object.
SelfLink string `json:"selfLink,omitempty"`
// UID is the unique in time and space value for this object. It is typically generated by
// the server on successful creation of a resource and is not allowed to change on PUT
// operations.
UID UID `json:"uid,omitempty"`
// An opaque value that represents the version of this resource. May be used for optimistic
// concurrency, change detection, and the watch operation on a resource or set of resources.
// Clients must treat these values as opaque and values may only be valid for a particular
// resource or set of resources. Only servers will generate resource versions.
ResourceVersion string `json:"resourceVersion,omitempty"`
// A sequence number representing a specific generation of the desired state.
// Populated by the system. Read-only.
Generation int64 `json:"generation,omitempty"`
// CreationTimestamp is a timestamp representing the server time when this object was
// created. It is not guaranteed to be set in happens-before order across separate operations.
// Clients may not set this value. It is represented in RFC3339 form and is in UTC.
CreationTimestamp Time `json:"creationTimestamp,omitempty"`
// DeletionTimestamp is the time after which this resource will be deleted. This
// field is set by the server when a graceful deletion is requested by the user, and is not
// directly settable by a client. The resource will be deleted (no longer visible from
// resource lists, and not reachable by name) after the time in this field. Once set, this
// value may not be unset or be set further into the future, although it may be shortened
// or the resource may be deleted prior to this time. For example, a user may request that
// a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination
// signal to the containers in the pod. Once the resource is deleted in the API, the Kubelet
// will send a hard termination signal to the container.
DeletionTimestamp *Time `json:"deletionTimestamp,omitempty"`
// DeletionGracePeriodSeconds records the graceful deletion value set when graceful deletion
// was requested. Represents the most recent grace period, and may only be shortened once set.
DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty"`
// Labels are key value pairs that may be used to scope and select individual resources.
// Label keys are of the form:
// label-key ::= prefixed-name | name
// prefixed-name ::= prefix '/' name
// prefix ::= DNS_SUBDOMAIN
// name ::= DNS_LABEL
// The prefix is optional. If the prefix is not specified, the key is assumed to be private
// to the user. Other system components that wish to use labels must specify a prefix. The
// "kubernetes.io/" prefix is reserved for use by kubernetes components.
// TODO: replace map[string]string with labels.LabelSet type
Labels map[string]string `json:"labels,omitempty"`
// Annotations are unstructured key value data stored with a resource that may be set by
// external tooling. They are not queryable and should be preserved when modifying
// objects. Annotation keys have the same formatting restrictions as Label keys. See the
// comments on Labels for details.
Annotations map[string]string `json:"annotations,omitempty"`
}
// UID is a type that holds unique ID values, including UUIDs. Because we
// don't ONLY use UUIDs, this is an alias to string. Being a type captures
// intent and helps make sure that UIDs and names do not get conflated.
type UID string
// Time is a wrapper around time.Time which supports correct
// marshaling to YAML and JSON. Wrappers are provided for many
// of the factory methods that the time package offers.
//
// +protobuf.options.marshal=false
// +protobuf.as=Timestamp
type Time struct {
time.Time `protobuf:"-"`
}
// Service is a named abstraction of software service (for example, mysql) consisting of local port
// (for example 3306) that the proxy listens on, and the selector that determines which pods
// will answer requests sent through the proxy.
type Service struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`
// Spec defines the behavior of a service.
Spec ServiceSpec `json:"spec,omitempty"`
// Status represents the current status of a service.
Status ServiceStatus `json:"status,omitempty"`
}
// ServiceSpec describes the attributes that a user creates on a service
type ServiceSpec struct {
// Type determines how the service will be exposed. Valid options: ClusterIP, NodePort, LoadBalancer
Type ServiceType `json:"type,omitempty"`
// Required: The list of ports that are exposed by this service.
Ports []ServicePort `json:"ports"`
// This service will route traffic to pods having labels matching this selector. If empty or not present,
// the service is assumed to have endpoints set by an external process and Kubernetes will not modify
// those endpoints.
Selector map[string]string `json:"selector"`
// ClusterIP is usually assigned by the master. If specified by the user
// we will try to respect it or else fail the request. This field can
// not be changed by updates.
// Valid values are None, empty string (""), or a valid IP address
// None can be specified for headless services when proxying is not required
ClusterIP string `json:"clusterIP,omitempty"`
// ExternalIPs are used by external load balancers, or can be set by
// users to handle external traffic that arrives at a node.
ExternalIPs []string `json:"externalIPs,omitempty"`
// Only applies to Service Type: LoadBalancer
// LoadBalancer will get created with the IP specified in this field.
// This feature depends on whether the underlying cloud-provider supports specifying
// the loadBalancerIP when a load balancer is created.
// This field will be ignored if the cloud-provider does not support the feature.
LoadBalancerIP string `json:"loadBalancerIP,omitempty"`
// Required: Supports "ClientIP" and "None". Used to maintain session affinity.
SessionAffinity ServiceAffinity `json:"sessionAffinity,omitempty"`
}
// ServicePort service port
type ServicePort struct {
// Optional if only one ServicePort is defined on this service: The
// name of this port within the service. This must be a DNS_LABEL.
// All ports within a ServiceSpec must have unique names. This maps to
// the 'Name' field in EndpointPort objects.
Name string `json:"name"`
// The IP protocol for this port. Supports "TCP" and "UDP".
Protocol Protocol `json:"protocol"`
// The port that will be exposed on the service.
Port int `json:"port"`
// Optional: The target port on pods selected by this service. If this
// is a string, it will be looked up as a named port in the target
// Pod's container ports. If this is not specified, the value
// of the 'port' field is used (an identity map).
// This field is ignored for services with clusterIP=None, and should be
// omitted or set equal to the 'port' field.
TargetPort IntOrString `json:"targetPort"`
// The port on each node on which this service is exposed.
// Default is to auto-allocate a port if the ServiceType of this Service requires one.
NodePort int `json:"nodePort"`
}
// ServiceStatus represents the current status of a service
type ServiceStatus struct {
// LoadBalancer contains the current status of the load-balancer,
// if one is present.
LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty"`
}
// LoadBalancerStatus represents the status of a load-balancer
type LoadBalancerStatus struct {
// Ingress is a list containing ingress points for the load-balancer;
// traffic intended for the service should be sent to these ingress points.
Ingress []LoadBalancerIngress `json:"ingress,omitempty"`
}
// LoadBalancerIngress represents the status of a load-balancer ingress point:
// traffic intended for the service should be sent to an ingress point.
type LoadBalancerIngress struct {
// IP is set for load-balancer ingress points that are IP based
// (typically GCE or OpenStack load-balancers)
IP string `json:"ip,omitempty"`
// Hostname is set for load-balancer ingress points that are DNS based
// (typically AWS load-balancers)
Hostname string `json:"hostname,omitempty"`
}
// ServiceAffinity Session Affinity Type string
type ServiceAffinity string
// ServiceType Service Type string describes ingress methods for a service
type ServiceType string
// Protocol defines network protocols supported for things like container ports.
type Protocol string
// IntOrString is a type that can hold an int32 or a string. When used in
// JSON or YAML marshalling and unmarshalling, it produces or consumes the
// inner type. This allows you to have, for example, a JSON field that can
// accept a name or number.
// TODO: Rename to Int32OrString
//
// +protobuf=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
type IntOrString struct {
Type Type
IntVal int32
StrVal string
}
// FromInt creates an IntOrString object with an int32 value. It is
// your responsibility not to call this method with a value greater
// than int32.
// TODO: convert to (val int32)
func FromInt(val int) IntOrString {
return IntOrString{Type: Int, IntVal: int32(val)}
}
// FromString creates an IntOrString object with a string value.
func FromString(val string) IntOrString {
return IntOrString{Type: String, StrVal: val}
}
// String returns the string value, or the Itoa of the int value.
func (intstr *IntOrString) String() string {
if intstr.Type == String {
return intstr.StrVal
}
return strconv.Itoa(intstr.IntValue())
}
// IntValue returns the IntVal if type Int, or if
// it is a String, will attempt a conversion to int.
func (intstr *IntOrString) IntValue() int {
if intstr.Type == String {
i, _ := strconv.Atoi(intstr.StrVal)
return i
}
return int(intstr.IntVal)
}
// UnmarshalJSON implements the json.Unmarshaller interface.
func (intstr *IntOrString) UnmarshalJSON(value []byte) error {
if value[0] == '"' {
intstr.Type = String
return json.Unmarshal(value, &intstr.StrVal)
}
intstr.Type = Int
return json.Unmarshal(value, &intstr.IntVal)
}
// Type represents the stored type of IntOrString.
type Type int
const (
// Int int
Int Type = iota // The IntOrString holds an int.
//String string
String // The IntOrString holds a string.
)
// ServiceList holds a list of services.
type ServiceList struct {
TypeMeta `json:",inline"`
ListMeta `json:"metadata,omitempty"`
Items []Service `json:"items"`
}
// ListMeta describes metadata that synthetic resources must have, including lists and
// various status objects. A resource may have only one of {ObjectMeta, ListMeta}.
type ListMeta struct {
// SelfLink is a URL representing this object.
// Populated by the system.
// Read-only.
SelfLink string `json:"selfLink,omitempty"`
// String that identifies the server's internal version of this object that
// can be used by clients to determine when objects have changed.
// Value must be treated as opaque by clients and passed unmodified back to the server.
// Populated by the system.
// Read-only.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#concurrency-control-and-consistency
ResourceVersion string `json:"resourceVersion,omitempty"`
}

315
provider/kubernetes.go Normal file
View File

@@ -0,0 +1,315 @@
package provider
import (
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/containous/traefik/provider/k8s"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"io"
"io/ioutil"
"os"
"reflect"
"strconv"
"strings"
"text/template"
"time"
)
const (
serviceAccountToken = "/var/run/secrets/kubernetes.io/serviceaccount/token"
serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
)
// Namespaces holds kubernetes namespaces
type Namespaces []string
//Set adds strings elem into the the parser
//it splits str on , and ;
func (ns *Namespaces) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
*ns = append(*ns, slice...)
return nil
}
//Get []string
func (ns *Namespaces) Get() interface{} { return Namespaces(*ns) }
//String return slice in a string
func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) }
//SetValue sets []string into the parser
func (ns *Namespaces) SetValue(val interface{}) {
*ns = Namespaces(val.(Namespaces))
}
// Kubernetes holds configurations of the Kubernetes provider.
type Kubernetes struct {
BaseProvider
Endpoint string `description:"Kubernetes server endpoint"`
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"`
Namespaces Namespaces `description:"Kubernetes namespaces"`
lastConfiguration safe.Safe
}
func (provider *Kubernetes) createClient() (k8s.Client, error) {
var token string
tokenBytes, err := ioutil.ReadFile(serviceAccountToken)
if err == nil {
token = string(tokenBytes)
log.Debugf("Kubernetes token: %s", token)
} else {
log.Errorf("Kubernetes load token error: %s", err)
}
caCert, err := ioutil.ReadFile(serviceAccountCACert)
if err == nil {
log.Debugf("Kubernetes CA cert: %s", serviceAccountCACert)
} else {
log.Errorf("Kubernetes load token error: %s", err)
}
kubernetesHost := os.Getenv("KUBERNETES_SERVICE_HOST")
kubernetesPort := os.Getenv("KUBERNETES_SERVICE_PORT_HTTPS")
if len(kubernetesPort) > 0 && len(kubernetesHost) > 0 {
provider.Endpoint = "https://" + kubernetesHost + ":" + kubernetesPort
}
log.Debugf("Kubernetes endpoint: %s", provider.Endpoint)
return k8s.NewClient(provider.Endpoint, caCert, token)
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
k8sClient, err := provider.createClient()
if err != nil {
return err
}
backOff := backoff.NewExponentialBackOff()
provider.Constraints = append(provider.Constraints, constraints...)
pool.Go(func(stop chan bool) {
operation := func() error {
for {
stopWatch := make(chan bool, 5)
defer close(stopWatch)
eventsChan, errEventsChan, err := k8sClient.WatchAll(stopWatch)
if err != nil {
log.Errorf("Error watching kubernetes events: %v", err)
timer := time.NewTimer(1 * time.Second)
select {
case <-timer.C:
return err
case <-stop:
return nil
}
}
Watch:
for {
select {
case <-stop:
stopWatch <- true
return nil
case err, ok := <-errEventsChan:
stopWatch <- true
if ok && strings.Contains(err.Error(), io.EOF.Error()) {
// edge case, kubernetes long-polling disconnection
break Watch
}
return err
case event := <-eventsChan:
log.Debugf("Received event from kubernetes %+v", event)
templateObjects, err := provider.loadIngresses(k8sClient)
if err != nil {
return err
}
if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) {
log.Debugf("Skipping event from kubernetes %+v", event)
} else {
provider.lastConfiguration.Set(templateObjects)
configurationChan <- types.ConfigMessage{
ProviderName: "kubernetes",
Configuration: provider.loadConfig(*templateObjects),
}
}
}
}
}
}
notify := func(err error, time time.Duration) {
log.Errorf("Kubernetes connection error %+v, retrying in %s", err, time)
}
err := backoff.RetryNotify(operation, backOff, notify)
if err != nil {
log.Fatalf("Cannot connect to Kubernetes server %+v", err)
}
})
templateObjects, err := provider.loadIngresses(k8sClient)
if err != nil {
return err
}
if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) {
log.Debugf("Skipping configuration from kubernetes %+v", templateObjects)
} else {
provider.lastConfiguration.Set(templateObjects)
configurationChan <- types.ConfigMessage{
ProviderName: "kubernetes",
Configuration: provider.loadConfig(*templateObjects),
}
}
return nil
}
func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configuration, error) {
ingresses, err := k8sClient.GetIngresses(func(ingress k8s.Ingress) bool {
if len(provider.Namespaces) == 0 {
return true
}
for _, n := range provider.Namespaces {
if ingress.ObjectMeta.Namespace == n {
return true
}
}
return false
})
if err != nil {
log.Errorf("Error retrieving ingresses: %+v", err)
return nil, err
}
templateObjects := types.Configuration{
map[string]*types.Backend{},
map[string]*types.Frontend{},
}
PassHostHeader := provider.getPassHostHeader()
for _, i := range ingresses {
for _, r := range i.Spec.Rules {
for _, pa := range r.HTTP.Paths {
if _, exists := templateObjects.Backends[r.Host+pa.Path]; !exists {
templateObjects.Backends[r.Host+pa.Path] = &types.Backend{
Servers: make(map[string]types.Server),
}
}
if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
templateObjects.Frontends[r.Host+pa.Path] = &types.Frontend{
Backend: r.Host + pa.Path,
PassHostHeader: PassHostHeader,
Routes: make(map[string]types.Route),
}
}
if len(r.Host) > 0 {
if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{
Rule: "Host:" + r.Host,
}
}
}
if len(pa.Path) > 0 {
ruleType := i.Annotations["traefik.frontend.rule.type"]
switch strings.ToLower(ruleType) {
case "pathprefixstrip":
ruleType = "PathPrefixStrip"
case "pathstrip":
ruleType = "PathStrip"
case "path":
ruleType = "Path"
case "pathprefix":
ruleType = "PathPrefix"
default:
log.Warnf("Unknown RuleType `%s`, falling back to `PathPrefix", ruleType)
ruleType = "PathPrefix"
}
templateObjects.Frontends[r.Host+pa.Path].Routes[pa.Path] = types.Route{
Rule: ruleType + ":" + pa.Path,
}
}
service, err := k8sClient.GetService(pa.Backend.ServiceName, i.ObjectMeta.Namespace)
if err != nil {
log.Warnf("Error retrieving services: %v", err)
delete(templateObjects.Frontends, r.Host+pa.Path)
log.Warnf("Error retrieving services %s", pa.Backend.ServiceName)
continue
}
protocol := "http"
for _, port := range service.Spec.Ports {
if equalPorts(port, pa.Backend.ServicePort) {
if port.Port == 443 {
protocol = "https"
}
endpoints, err := k8sClient.GetEndpoints(service.ObjectMeta.Name, service.ObjectMeta.Namespace)
if err != nil {
log.Errorf("Error retrieving endpoints: %v", err)
continue
}
if len(endpoints.Subsets) == 0 {
log.Warnf("Endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port),
Weight: 1,
}
} else {
for _, subset := range endpoints.Subsets {
for _, address := range subset.Addresses {
url := protocol + "://" + address.IP + ":" + strconv.Itoa(endpointPortNumber(port, subset.Ports))
templateObjects.Backends[r.Host+pa.Path].Servers[url] = types.Server{
URL: url,
Weight: 1,
}
}
}
}
break
}
}
}
}
}
return &templateObjects, nil
}
func endpointPortNumber(servicePort k8s.ServicePort, endpointPorts []k8s.EndpointPort) int {
if len(endpointPorts) > 0 {
//name is optional if there is only one port
port := endpointPorts[0]
for _, endpointPort := range endpointPorts {
if servicePort.Name == endpointPort.Name {
port = endpointPort
}
}
return int(port.Port)
}
return servicePort.Port
}
func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool {
if servicePort.Port == ingressPort.IntValue() {
return true
}
if servicePort.Name != "" && servicePort.Name == ingressPort.String() {
return true
}
return false
}
func (provider *Kubernetes) getPassHostHeader() bool {
if provider.DisablePassHostHeaders {
return false
}
return true
}
func (provider *Kubernetes) loadConfig(templateObjects types.Configuration) *types.Configuration {
var FuncMap = template.FuncMap{}
configuration, err := provider.getConfiguration("templates/kubernetes.tmpl", FuncMap, templateObjects)
if err != nil {
log.Error(err)
}
return configuration
}

1285
provider/kubernetes_test.go Normal file

File diff suppressed because it is too large Load Diff

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