mirror of
https://github.com/containous/traefik.git
synced 2025-10-23 23:33:33 +03:00
Compare commits
1258 Commits
v1.0.0-bet
...
v1.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44b82e6231 | ||
|
|
04f0bf3070 | ||
|
|
7400c39511 | ||
|
|
35ca40c3de | ||
|
|
de821fc305 | ||
|
|
e3cac7d0e5 | ||
|
|
81f7aa9df2 | ||
|
|
afbad56012 | ||
|
|
9c8df8b9ce | ||
|
|
ff4c7b82bc | ||
|
|
47ff51e640 | ||
|
|
08503655d9 | ||
|
|
3afd6024b5 | ||
|
|
aa308b7a3a | ||
|
|
9598f646f5 | ||
|
|
8af39bdaf7 | ||
|
|
8cb3f0835a | ||
|
|
cba0898e4f | ||
|
|
8d158402f3 | ||
|
|
7f2582e3b6 | ||
|
|
dbc796359f | ||
|
|
7a2ce59563 | ||
|
|
14cec7e610 | ||
|
|
6287a3dd53 | ||
|
|
93a1db77c5 | ||
|
|
a9d4b09bdb | ||
|
|
ed2eb7b5a6 | ||
|
|
18d8537d29 | ||
|
|
72f3b1ed39 | ||
|
|
fd70e6edb1 | ||
|
|
5a578c5375 | ||
|
|
9db8773055 | ||
|
|
8a67434380 | ||
|
|
c94e5f3589 | ||
|
|
adef7200f6 | ||
|
|
f8d36fda28 | ||
|
|
4fe9cc7730 | ||
|
|
758b7f875b | ||
|
|
0b97a67cfa | ||
|
|
ec5976bbc9 | ||
|
|
5cc49e2931 | ||
|
|
b6752a2c02 | ||
|
|
d41e28fc36 | ||
|
|
64c52a6921 | ||
|
|
691a678b19 | ||
|
|
1ba7fd91ff | ||
|
|
dd23ceeead | ||
|
|
058fa1367b | ||
|
|
9db12374ea | ||
|
|
a941739f8a | ||
|
|
795a346006 | ||
|
|
9d00da7285 | ||
|
|
52c1909f24 | ||
|
|
f9225c54ff | ||
|
|
cb05f36976 | ||
|
|
49e0e20ce2 | ||
|
|
7c35337999 | ||
|
|
2296aab5a8 | ||
|
|
ce3b255f1a | ||
|
|
3942f3366d | ||
|
|
df76cc33a5 | ||
|
|
cf387d5a6d | ||
|
|
0a0cf87625 | ||
|
|
1a2544610d | ||
|
|
5229b7cfba | ||
|
|
243b45881d | ||
|
|
883028d981 | ||
|
|
bdeb7bfb9f | ||
|
|
5305a16350 | ||
|
|
63b581935d | ||
|
|
c7c9349b00 | ||
|
|
6d28c52f59 | ||
|
|
f80a6ef2a6 | ||
|
|
ecf31097ea | ||
|
|
16fc3675db | ||
|
|
651d993d9c | ||
|
|
286d882f1e | ||
|
|
3b6afdf80c | ||
|
|
c19cce69fa | ||
|
|
b705e64a8a | ||
|
|
8c5514612f | ||
|
|
924e82ab0c | ||
|
|
adcb99d330 | ||
|
|
8339139400 | ||
|
|
a43cf8d2b8 | ||
|
|
5157a6ad47 | ||
|
|
cd6c58a372 | ||
|
|
03ba8396f3 | ||
|
|
b0a0e16136 | ||
|
|
732d73dd43 | ||
|
|
e075dfe911 | ||
|
|
425b53585a | ||
|
|
d5bbb103d4 | ||
|
|
5c2849ea07 | ||
|
|
723418e2cc | ||
|
|
45e2e8baec | ||
|
|
b0ae6bc049 | ||
|
|
ffb53c07b8 | ||
|
|
f329b3b51d | ||
|
|
5b27aba3e1 | ||
|
|
7c2ba62b56 | ||
|
|
24862402e5 | ||
|
|
d568d2f55a | ||
|
|
dae7e7a80a | ||
|
|
23cdb37165 | ||
|
|
2c82dfd444 | ||
|
|
c8c31aea62 | ||
|
|
89b0037ec1 | ||
|
|
b75fb23887 | ||
|
|
52b69fbcb8 | ||
|
|
f16219f90a | ||
|
|
7b0cef0fac | ||
|
|
e0af17a17a | ||
|
|
92fb86b66f | ||
|
|
919295cffc | ||
|
|
086a85d2f0 | ||
|
|
8235cd3645 | ||
|
|
f1a257abf8 | ||
|
|
98dfd2ba0e | ||
|
|
87e6285cf6 | ||
|
|
0d56a98836 | ||
|
|
8105f1c379 | ||
|
|
e6c2040ea8 | ||
|
|
c1b5b740ff | ||
|
|
1d2d0cefaa | ||
|
|
04e65958ee | ||
|
|
8765494cbd | ||
|
|
05665f4eec | ||
|
|
78544f7fa2 | ||
|
|
396449c07f | ||
|
|
eda679776e | ||
|
|
69d57d602f | ||
|
|
32b2736efd | ||
|
|
3f650bbd11 | ||
|
|
5313922bb7 | ||
|
|
ec3e2c08b8 | ||
|
|
40e18db838 | ||
|
|
0367034f93 | ||
|
|
b80ecd51a7 | ||
|
|
14a0d66410 | ||
|
|
d84ccbc52a | ||
|
|
1190768f4b | ||
|
|
ea3510d1f3 | ||
|
|
3f76f73e8c | ||
|
|
759c269dee | ||
|
|
c360395afc | ||
|
|
60a35c8aba | ||
|
|
50dd2b8cff | ||
|
|
4e5fcac9cb | ||
|
|
64b8fc52c3 | ||
|
|
19a5ba3264 | ||
|
|
7ff6c32452 | ||
|
|
ff11467022 | ||
|
|
7d3878214a | ||
|
|
984817d3a0 | ||
|
|
6b133e24b9 | ||
|
|
990ee89650 | ||
|
|
8071f31721 | ||
|
|
d456c2ce6a | ||
|
|
413ed62933 | ||
|
|
1b4dc3783c | ||
|
|
94f922cd28 | ||
|
|
29390a3c4a | ||
|
|
1db9482a8e | ||
|
|
888e6dcbc8 | ||
|
|
765c44d77f | ||
|
|
64ee68763b | ||
|
|
4122aef12e | ||
|
|
8cb44598c0 | ||
|
|
69c628b626 | ||
|
|
cd28e7b24f | ||
|
|
40d9058bb6 | ||
|
|
c36e0b3b06 | ||
|
|
3174fb8861 | ||
|
|
074b31b5e9 | ||
|
|
16609cd485 | ||
|
|
a09a8b1235 | ||
|
|
70ab34cfb8 | ||
|
|
36ee69609e | ||
|
|
c53be185f4 | ||
|
|
779eeba650 | ||
|
|
58ffea6627 | ||
|
|
a2d68ed881 | ||
|
|
d653a348b1 | ||
|
|
2e84b1e556 | ||
|
|
bbb133d94c | ||
|
|
d90fa5ab3e | ||
|
|
759a19bc4f | ||
|
|
a7ec785994 | ||
|
|
46faa7a745 | ||
|
|
54e3f08833 | ||
|
|
b365836c57 | ||
|
|
242f1b9c3c | ||
|
|
4dfbb6d489 | ||
|
|
c31b4c55c2 | ||
|
|
ca5bbab20a | ||
|
|
41dd124a4b | ||
|
|
dbf6161fa1 | ||
|
|
7aabd6e385 | ||
|
|
cb203f8e7e | ||
|
|
8f845bac74 | ||
|
|
98b52d1f54 | ||
|
|
4892b2b0da | ||
|
|
a89eb122a0 | ||
|
|
b7daa2f3a4 | ||
|
|
91ce78da46 | ||
|
|
7d178f49b4 | ||
|
|
85f4f26942 | ||
|
|
eee8ba8a53 | ||
|
|
22aceec426 | ||
|
|
121c057b90 | ||
|
|
2c976227dd | ||
|
|
81d011e57d | ||
|
|
3776e58041 | ||
|
|
f06e256934 | ||
|
|
4699d6be18 | ||
|
|
6473002021 | ||
|
|
4d89ff7e18 | ||
|
|
c5c63071ca | ||
|
|
9fbe21c534 | ||
|
|
36c88111de | ||
|
|
7a34303593 | ||
|
|
2201dcd505 | ||
|
|
7a7cafcbaa | ||
|
|
efb671401d | ||
|
|
4128c1ac8d | ||
|
|
73e10c96cc | ||
|
|
fdb24c64e4 | ||
|
|
631079a12f | ||
|
|
0055965295 | ||
|
|
f99f3b987e | ||
|
|
34e60a8404 | ||
|
|
ceec81011b | ||
|
|
927003329e | ||
|
|
01bb0a80ab | ||
|
|
db1baf80a9 | ||
|
|
9cb07d026f | ||
|
|
984ea1040f | ||
|
|
447109e868 | ||
|
|
f79317a435 | ||
|
|
131d8dd765 | ||
|
|
b452695c20 | ||
|
|
f17785c3ab | ||
|
|
fe4d0e95b3 | ||
|
|
0fb63f4488 | ||
|
|
2a578748fd | ||
|
|
d87c4d89e9 | ||
|
|
ccc429e36c | ||
|
|
0d25ba3cbc | ||
|
|
2ddae2e856 | ||
|
|
885b9f371c | ||
|
|
f275e4ad3c | ||
|
|
aea7bc0c07 | ||
|
|
a457392ec3 | ||
|
|
37ec7d0505 | ||
|
|
8f6404ab3a | ||
|
|
1538b16b21 | ||
|
|
a6477fbd95 | ||
|
|
e802dcd189 | ||
|
|
931dc02c09 | ||
|
|
7017cdcf49 | ||
|
|
5aa017d9b5 | ||
|
|
a7297b49a4 | ||
|
|
3eaeb81831 | ||
|
|
7d6c778211 | ||
|
|
9c27a98821 | ||
|
|
ad54c5a278 | ||
|
|
96939e2990 | ||
|
|
5268db47a1 | ||
|
|
3048509807 | ||
|
|
7399a83c74 | ||
|
|
18c3d8dc62 | ||
|
|
2d1ddcf28b | ||
|
|
a1a0420314 | ||
|
|
2223587fc0 | ||
|
|
63f9bccf9f | ||
|
|
18d11e02d0 | ||
|
|
a71d69cc3c | ||
|
|
e007bb7546 | ||
|
|
7874ffd506 | ||
|
|
a9216e24f5 | ||
|
|
39388a2199 | ||
|
|
71111708d4 | ||
|
|
ac5ab13a4c | ||
|
|
d5efc99876 | ||
|
|
1e84e77a67 | ||
|
|
1db22a6e63 | ||
|
|
d6b448f430 | ||
|
|
e1e07f7750 | ||
|
|
e426b27581 | ||
|
|
b6c5c14447 | ||
|
|
cbccdd51c5 | ||
|
|
4c4eba4b56 | ||
|
|
994e135368 | ||
|
|
87e5cda506 | ||
|
|
2833d68f15 | ||
|
|
dbfd2663c2 | ||
|
|
64e8b31d49 | ||
|
|
5b896bb46c | ||
|
|
bc0121808a | ||
|
|
4293446111 | ||
|
|
9967494996 | ||
|
|
b392023c37 | ||
|
|
f7d9dfafd0 | ||
|
|
2643271053 | ||
|
|
219a6372b0 | ||
|
|
5b36b274a3 | ||
|
|
8ad31d6eb4 | ||
|
|
2e762e76f3 | ||
|
|
13e8a875cf | ||
|
|
c7281df230 | ||
|
|
987ae92f53 | ||
|
|
5f0b215e90 | ||
|
|
55f610422a | ||
|
|
a04ef15bcd | ||
|
|
81754840ff | ||
|
|
2610023131 | ||
|
|
c1220b8765 | ||
|
|
bc6f764a87 | ||
|
|
0b414ed482 | ||
|
|
ff3481f06b | ||
|
|
f8ea19d29c | ||
|
|
3b8ebf7d33 | ||
|
|
5e14f20786 | ||
|
|
96b19deac5 | ||
|
|
a6aff7c85c | ||
|
|
1310347395 | ||
|
|
40c94d80d7 | ||
|
|
f521e72f15 | ||
|
|
88ea0a037b | ||
|
|
c963cee3c8 | ||
|
|
0be353d435 | ||
|
|
6afff2d403 | ||
|
|
12fa144f2f | ||
|
|
ac0e48b48c | ||
|
|
64aa37858b | ||
|
|
5348d4dccd | ||
|
|
c3c599241f | ||
|
|
c19432f95c | ||
|
|
bdf4f48d78 | ||
|
|
21aa0ea2da | ||
|
|
921a704c24 | ||
|
|
3f490f95c6 | ||
|
|
24d80b1909 | ||
|
|
f8e7b5595b | ||
|
|
f9839f7b1d | ||
|
|
2c45428c8a | ||
|
|
30aa5a82b3 | ||
|
|
3f68e382fd | ||
|
|
9e57a283d7 | ||
|
|
eaedc1b924 | ||
|
|
e3ab4e4d63 | ||
|
|
48a91d05b5 | ||
|
|
111251da05 | ||
|
|
71cec1580b | ||
|
|
78b2fba033 | ||
|
|
218b76275c | ||
|
|
cf5b6d837f | ||
|
|
0babc7bb64 | ||
|
|
8a551d91fd | ||
|
|
eeed035ef0 | ||
|
|
33404a7772 | ||
|
|
bd90745528 | ||
|
|
ede1212cb0 | ||
|
|
2dcbc01e51 | ||
|
|
61ba50fac9 | ||
|
|
b24b5e20b4 | ||
|
|
ffe1104851 | ||
|
|
3112432480 | ||
|
|
aa4ed088bb | ||
|
|
3a4ec19817 | ||
|
|
d2b204a075 | ||
|
|
94f5b0d9ff | ||
|
|
d2c8824902 | ||
|
|
fe6c35bc6b | ||
|
|
db09007dbc | ||
|
|
5b2e8990f1 | ||
|
|
2f6068decc | ||
|
|
1e591dd188 | ||
|
|
6838a81e50 | ||
|
|
ceef5e39b7 | ||
|
|
ef339af623 | ||
|
|
acc7865542 | ||
|
|
c00c240c14 | ||
|
|
3fd6da06e0 | ||
|
|
95502aeec3 | ||
|
|
58c786ca8c | ||
|
|
b6916d2f8c | ||
|
|
840c131a98 | ||
|
|
219bcec40f | ||
|
|
ccda550ab1 | ||
|
|
b5e73cfa07 | ||
|
|
ba928dd459 | ||
|
|
6fd40dbaa9 | ||
|
|
6ad273b9fa | ||
|
|
5500658f5a | ||
|
|
b4f9e3890f | ||
|
|
df6741aeeb | ||
|
|
5535318cda | ||
|
|
4e186cecf9 | ||
|
|
8ac281f9e3 | ||
|
|
e7a73d3fb3 | ||
|
|
ca9e36ebe3 | ||
|
|
138fea17ed | ||
|
|
bf3f6e2029 | ||
|
|
ec245d604a | ||
|
|
69e081f40f | ||
|
|
82651985c4 | ||
|
|
a5384bae47 | ||
|
|
1dcf8d2ea6 | ||
|
|
e86df016c3 | ||
|
|
72baf746f4 | ||
|
|
91b4b47f04 | ||
|
|
79cbe56a41 | ||
|
|
f621d7a2c4 | ||
|
|
3c33eab35e | ||
|
|
b67a27d0c7 | ||
|
|
8de107866f | ||
|
|
b5283391dd | ||
|
|
420a6db3b4 | ||
|
|
89da3b15a4 | ||
|
|
dcc4d92983 | ||
|
|
12c2d398a7 | ||
|
|
4e238280bc | ||
|
|
bd6056c269 | ||
|
|
acb0492e26 | ||
|
|
a0d6594e99 | ||
|
|
65f81990a7 | ||
|
|
1b85dd0455 | ||
|
|
bec45bc7d6 | ||
|
|
4c4b05d024 | ||
|
|
228ad9a244 | ||
|
|
2f06f339ec | ||
|
|
eefcf026d2 | ||
|
|
ccb1a4ff8c | ||
|
|
78f1b4216e | ||
|
|
44db6e9290 | ||
|
|
e2fdc27d64 | ||
|
|
25345427c3 | ||
|
|
ce492895e2 | ||
|
|
5d43b9e16a | ||
|
|
71a2c8bdcd | ||
|
|
8fd6160758 | ||
|
|
d57f83c31c | ||
|
|
441d5442a1 | ||
|
|
bf3673879f | ||
|
|
74925ba996 | ||
|
|
de6d771bc2 | ||
|
|
2f1a7cbf26 | ||
|
|
d24ba90900 | ||
|
|
9ed55e9eae | ||
|
|
a0c3d6a421 | ||
|
|
521e295349 | ||
|
|
aa8375e82b | ||
|
|
5a8215a1e4 | ||
|
|
7eb3051a57 | ||
|
|
a4355569af | ||
|
|
16c86022bb | ||
|
|
e615e833bc | ||
|
|
592a12dca2 | ||
|
|
97a3564945 | ||
|
|
f1ee471b6b | ||
|
|
750fa22cff | ||
|
|
099d605aed | ||
|
|
f1bc80ca12 | ||
|
|
49a9aeb95f | ||
|
|
25abf8b8f8 | ||
|
|
962fb908c0 | ||
|
|
b44aca64e3 | ||
|
|
34b21b9374 | ||
|
|
972579e2a0 | ||
|
|
ccff8a80f5 | ||
|
|
4f2a2d573d | ||
|
|
af1d0a7dce | ||
|
|
37e40bc776 | ||
|
|
d9fd412e0e | ||
|
|
4bc2f17b08 | ||
|
|
d1b65adfb1 | ||
|
|
19a7d22eef | ||
|
|
6012a0f3c5 | ||
|
|
4e81d41d06 | ||
|
|
f4579e5f12 | ||
|
|
a8cbe7ef5e | ||
|
|
6ba17847ab | ||
|
|
378a34c454 | ||
|
|
f38d117a31 | ||
|
|
73a1b172ed | ||
|
|
4310bdf3ca | ||
|
|
6cb8df9d1e | ||
|
|
93e123b489 | ||
|
|
8764c43eaf | ||
|
|
10e22c0b3f | ||
|
|
051f0c6855 | ||
|
|
809103f4b2 | ||
|
|
b7c2e2d3f1 | ||
|
|
d866a62b56 | ||
|
|
22ac60205a | ||
|
|
de557d031b | ||
|
|
7fcb7b86d3 | ||
|
|
9c9015a7b1 | ||
|
|
360e8e19ce | ||
|
|
dd52ee9f9b | ||
|
|
8a892b21e1 | ||
|
|
4e0f131fcd | ||
|
|
d1ee72b308 | ||
|
|
f03a9e502f | ||
|
|
542c3673e4 | ||
|
|
2d00758b2e | ||
|
|
73f09f389e | ||
|
|
29bada9ae3 | ||
|
|
4ce2c8cc34 | ||
|
|
b02b11a606 | ||
|
|
e38fa25412 | ||
|
|
38b2362a31 | ||
|
|
13754f06e3 | ||
|
|
ade223cf2e | ||
|
|
2118f6992a | ||
|
|
b04ba36682 | ||
|
|
3f293ee25b | ||
|
|
dc01094863 | ||
|
|
fa683fa7e4 | ||
|
|
1da47dfcbb | ||
|
|
fc3cc9a919 | ||
|
|
12a0026e21 | ||
|
|
aeb17182b4 | ||
|
|
a590155b0b | ||
|
|
87ce060737 | ||
|
|
f2297dd3ed | ||
|
|
2cd4c82092 | ||
|
|
6edc0926eb | ||
|
|
a456d36cc6 | ||
|
|
5c2d91ab84 | ||
|
|
a73fee50dc | ||
|
|
b02393915e | ||
|
|
b99a919bb4 | ||
|
|
51f3f6ba9c | ||
|
|
736f9b30ef | ||
|
|
b385ffaee7 | ||
|
|
b02e289734 | ||
|
|
fd1cf2484c | ||
|
|
5250c9c04d | ||
|
|
e011792a90 | ||
|
|
a507cb4835 | ||
|
|
f324983946 | ||
|
|
c876462eb0 | ||
|
|
ec7ba15955 | ||
|
|
ef83a5936d | ||
|
|
8d650da2f8 | ||
|
|
bd127168b3 | ||
|
|
1ecdadb283 | ||
|
|
d8c21639f7 | ||
|
|
d2df47d382 | ||
|
|
0cc3d05515 | ||
|
|
60ea9199e5 | ||
|
|
637c7e250c | ||
|
|
6f4c5dd4ce | ||
|
|
a3b95f798b | ||
|
|
65284441fa | ||
|
|
51e4dcbb1f | ||
|
|
e38bf0accb | ||
|
|
08c1871c98 | ||
|
|
4eb779e596 | ||
|
|
e1aa16ae70 | ||
|
|
b4dfb7223b | ||
|
|
f621a46a2e | ||
|
|
c864d80270 | ||
|
|
020a8e31ab | ||
|
|
69c31276f2 | ||
|
|
06c47134c9 | ||
|
|
c9d23494b9 | ||
|
|
7d256c9bb9 | ||
|
|
056fe9ac0a | ||
|
|
e375ba98f0 | ||
|
|
d6d93db13b | ||
|
|
3389908238 | ||
|
|
5c16860486 | ||
|
|
0a7f9b5a71 | ||
|
|
df685fa050 | ||
|
|
2c079b3d6f | ||
|
|
35973f1243 | ||
|
|
9281f4fbbc | ||
|
|
0e0a231e5a | ||
|
|
b22716c5ba | ||
|
|
240b2be1a8 | ||
|
|
c5125cee71 | ||
|
|
1cf1fbf99b | ||
|
|
1ed68b1278 | ||
|
|
84e1ec6607 | ||
|
|
1140ee6c64 | ||
|
|
8401cccff2 | ||
|
|
836f617286 | ||
|
|
1bc8c9912e | ||
|
|
b5430803b8 | ||
|
|
a7bc8c8aa4 | ||
|
|
9ab8e08d59 | ||
|
|
677899d9ff | ||
|
|
72e35af39f | ||
|
|
2a61c9049f | ||
|
|
1158eba7ac | ||
|
|
22c5bf7630 | ||
|
|
4148266ed0 | ||
|
|
6e8e597ff5 | ||
|
|
7357417f48 | ||
|
|
91bf627275 | ||
|
|
55b57c736b | ||
|
|
dd5e3fba01 | ||
|
|
49a09ab7dd | ||
|
|
dae28f7f17 | ||
|
|
9cd76f122e | ||
|
|
920b5bb15d | ||
|
|
3611818eda | ||
|
|
7d83027954 | ||
|
|
ea190b6898 | ||
|
|
aa75d5458d | ||
|
|
4172a7c62e | ||
|
|
355b4706d3 | ||
|
|
eb1ffae01b | ||
|
|
cc0733a4fa | ||
|
|
c786bbbc5b | ||
|
|
f87b1c2fcd | ||
|
|
14fd53c915 | ||
|
|
aa2edcc6e5 | ||
|
|
6b6f010851 | ||
|
|
5e8805f24d | ||
|
|
3848944d35 | ||
|
|
9d7df45b7c | ||
|
|
7a164ed401 | ||
|
|
f530284031 | ||
|
|
38c0cf7007 | ||
|
|
f3598e6b0f | ||
|
|
291ca860af | ||
|
|
7d20871f0d | ||
|
|
6942b063ee | ||
|
|
e56bd27c1e | ||
|
|
a3beec6b9c | ||
|
|
04a1ecc4f4 | ||
|
|
7707814f2e | ||
|
|
4d4f2b62aa | ||
|
|
5abffe402f | ||
|
|
38ec32a146 | ||
|
|
d77ad42326 | ||
|
|
4106f0fa9e | ||
|
|
a0a0bf0577 | ||
|
|
71c7920d0f | ||
|
|
9bb1b01742 | ||
|
|
8c824680ce | ||
|
|
60b3f74be8 | ||
|
|
dfb09bf2ab | ||
|
|
98d6a43e1e | ||
|
|
49466d0d14 | ||
|
|
66cc9a075c | ||
|
|
1e10fc2e30 | ||
|
|
c8cf5f8c44 | ||
|
|
96e6c9cef2 | ||
|
|
931ee55e1d | ||
|
|
4d3aede5d3 | ||
|
|
0b1dd69b01 | ||
|
|
0947aa901e | ||
|
|
01e3d7952a | ||
|
|
84b224b9db | ||
|
|
39f8f6868a | ||
|
|
556915cab6 | ||
|
|
bff654b843 | ||
|
|
3a875e2954 | ||
|
|
bdb63ac785 | ||
|
|
9a5dc54f85 | ||
|
|
48524a58ff | ||
|
|
38bd49b97e | ||
|
|
28054a0be3 | ||
|
|
250a0863f6 | ||
|
|
b1764a6864 | ||
|
|
41f8f0113b | ||
|
|
db63e84a9f | ||
|
|
e0a4c58081 | ||
|
|
d2b47a5681 | ||
|
|
106e5c1f92 | ||
|
|
c00a9fae0c | ||
|
|
087bbd2e3e | ||
|
|
e16f2bb23d | ||
|
|
8d0bacf146 | ||
|
|
354f69b2f6 | ||
|
|
39e6b16069 | ||
|
|
b30272d896 | ||
|
|
755822bf14 | ||
|
|
99ffc26d40 | ||
|
|
4a8f032304 | ||
|
|
a0b775a7c0 | ||
|
|
0ab0bdf818 | ||
|
|
fce32ea5c7 | ||
|
|
8d3c77a0b9 | ||
|
|
00de73bdfc | ||
|
|
96197af3f1 | ||
|
|
dacde21c27 | ||
|
|
0d3b2ed230 | ||
|
|
fa4226c742 | ||
|
|
7cb4c42772 | ||
|
|
99f251451e | ||
|
|
d5f9a80b6c | ||
|
|
d324040adc | ||
|
|
da5eba17d8 | ||
|
|
434596b103 | ||
|
|
71a185c70e | ||
|
|
cbbb5f4ccb | ||
|
|
89ec25f718 | ||
|
|
e5b688214c | ||
|
|
225dbcce0a | ||
|
|
b22dc213e8 | ||
|
|
ad12a7264e | ||
|
|
29059b77a8 | ||
|
|
cdaa64a4b2 | ||
|
|
bc4296729f | ||
|
|
3a3630f3ef | ||
|
|
93ce747205 | ||
|
|
1493a4c815 | ||
|
|
54be6beaab | ||
|
|
e9fc9fdf12 | ||
|
|
ba4670eddc | ||
|
|
5a67d0ac84 | ||
|
|
be362f0d9f | ||
|
|
a394e6a3e3 | ||
|
|
1a5f1977c4 | ||
|
|
feee8ad72e | ||
|
|
c9e78c4f4a | ||
|
|
d0e2349dfd | ||
|
|
d516cbfe6c | ||
|
|
86fd5b4c97 | ||
|
|
1131a972cd | ||
|
|
2048f77178 | ||
|
|
a70c6f25ea | ||
|
|
490427f94d | ||
|
|
7cc91a8244 | ||
|
|
4f951a242b | ||
|
|
c095fc1eab | ||
|
|
c1182377db | ||
|
|
02473328e7 | ||
|
|
2b00cdf330 | ||
|
|
18cf49755e | ||
|
|
3a7de0be5c | ||
|
|
a1b610ee03 | ||
|
|
4d99b84e5b | ||
|
|
e20d13c44e | ||
|
|
18e9064d25 | ||
|
|
fad3038df2 | ||
|
|
8e4c4f8407 | ||
|
|
68bd24d065 | ||
|
|
d15a17b634 | ||
|
|
fa1090b6eb | ||
|
|
483ef486af | ||
|
|
175659a3dd | ||
|
|
dd85cbca39 | ||
|
|
22b97b7214 | ||
|
|
db68dd3bc1 | ||
|
|
85b9c19871 | ||
|
|
2bfc237e53 | ||
|
|
d74ea22d7d | ||
|
|
8004132a3a | ||
|
|
a6f4183cde | ||
|
|
51e9f3ede2 | ||
|
|
bfc7b3d183 | ||
|
|
8a348423ae | ||
|
|
e4952cd145 | ||
|
|
5b0bf5d150 | ||
|
|
79180dc021 | ||
|
|
599c95e5f6 | ||
|
|
e1ed8b71f6 | ||
|
|
6ca142bf20 | ||
|
|
6b20d2a5f3 | ||
|
|
bef55db120 | ||
|
|
3bb3658d7d | ||
|
|
a4034ce1e2 | ||
|
|
d9fc66fdbc | ||
|
|
3ebfd729cf | ||
|
|
6adb346cee | ||
|
|
318ff52ff3 | ||
|
|
b7b0f8f68d | ||
|
|
94bb7a1435 | ||
|
|
913a297e8d | ||
|
|
d469d426f8 | ||
|
|
ec05fbcf19 | ||
|
|
686faf0556 | ||
|
|
fe2d4e0d38 | ||
|
|
c500873586 | ||
|
|
fc788eb426 | ||
|
|
87eac1dc1a | ||
|
|
91d9b9811f | ||
|
|
71beb4b08f | ||
|
|
d26f06e2d1 | ||
|
|
dca08af003 | ||
|
|
4c740e26d7 | ||
|
|
131f581f77 | ||
|
|
9236a43a4d | ||
|
|
7f4eddf6d6 | ||
|
|
d1e631a487 | ||
|
|
0b78375211 | ||
|
|
15540764a0 | ||
|
|
82234cbbb2 | ||
|
|
22392daef7 | ||
|
|
7f3ae6edb0 | ||
|
|
1a993f5dfb | ||
|
|
4e527304d0 | ||
|
|
841be8d806 | ||
|
|
055cd01bb7 | ||
|
|
e34c364d5e | ||
|
|
926eb099f1 | ||
|
|
710508dc40 | ||
|
|
b4ea68b88a | ||
|
|
2bf9acd95e | ||
|
|
a8cb905255 | ||
|
|
567387aee0 | ||
|
|
5b71e3184a | ||
|
|
e1724444ac | ||
|
|
cf8940e80e | ||
|
|
fe1b982d13 | ||
|
|
221ae2427b | ||
|
|
29f780863b | ||
|
|
8aaca8e55c | ||
|
|
2dda3d2feb | ||
|
|
22ebaedb45 | ||
|
|
7065f00443 | ||
|
|
15732269da | ||
|
|
7b06be8f5e | ||
|
|
d2dcec40e1 | ||
|
|
2af6cc4d1b | ||
|
|
56c6174d61 | ||
|
|
66e914a8ab | ||
|
|
8ae9607d9b | ||
|
|
5c0297fb61 | ||
|
|
f5bf9a2cda | ||
|
|
987ab7612d | ||
|
|
a186d5f87a | ||
|
|
801e0f9ef7 | ||
|
|
874ea62dd5 | ||
|
|
ac20ddfc6c | ||
|
|
f0b991e1a8 | ||
|
|
adf385fdf3 | ||
|
|
7af6bc093d | ||
|
|
3708fa864b | ||
|
|
28276e1b37 | ||
|
|
b0efd685a9 | ||
|
|
422aacf8e6 | ||
|
|
f6576cce27 | ||
|
|
e068ee09ca | ||
|
|
d3b48cdd22 | ||
|
|
91e3bdff48 | ||
|
|
4299d1526b | ||
|
|
c26b36cf4f | ||
|
|
3095da64d7 | ||
|
|
07f961ecba | ||
|
|
8d9caaec71 | ||
|
|
91634d5c1c | ||
|
|
f5463c3d38 | ||
|
|
73b70393d4 | ||
|
|
3db6e185e0 | ||
|
|
d174ed75c7 | ||
|
|
513d261f10 | ||
|
|
4430befe90 | ||
|
|
acf425b6cf | ||
|
|
1c4eb4322b | ||
|
|
3f3fa61a51 | ||
|
|
ddf24039e8 | ||
|
|
98b35affd5 | ||
|
|
b3cc1e1af1 | ||
|
|
5b6a5f8aa9 | ||
|
|
3e6d2391f7 | ||
|
|
664ee9d82f | ||
|
|
c9cc3c9895 | ||
|
|
00c7e5c72b | ||
|
|
2b770ae2f8 | ||
|
|
558b31f4d9 | ||
|
|
174a5e7f13 | ||
|
|
952fcf5d09 | ||
|
|
c821f191b0 | ||
|
|
3322e564fd | ||
|
|
7bf5d557c1 | ||
|
|
0c1e06199c | ||
|
|
85a20b9a39 | ||
|
|
931a124349 | ||
|
|
ab52f4d91d | ||
|
|
f3182ef29b | ||
|
|
5641af437e | ||
|
|
1c8d3ded3d | ||
|
|
c2a445370e | ||
|
|
8e5355f2d9 | ||
|
|
2492157833 | ||
|
|
7c375e8fd9 | ||
|
|
53b5d8ac33 | ||
|
|
e5a8fb390e | ||
|
|
79cbae0c73 | ||
|
|
22b0b8b750 | ||
|
|
ddbddf6edf | ||
|
|
adcf58da68 | ||
|
|
05f6b79e29 | ||
|
|
649cb548d0 | ||
|
|
14db2343c9 | ||
|
|
67eb0c8de0 | ||
|
|
870f378782 | ||
|
|
82a58010f5 | ||
|
|
f652c58367 | ||
|
|
468d138be7 | ||
|
|
f409d2f435 | ||
|
|
5780a17794 | ||
|
|
9b765d23fa | ||
|
|
4476861d9f | ||
|
|
e12ddca1a5 | ||
|
|
084d00a156 | ||
|
|
404a73a712 | ||
|
|
3b2410d904 | ||
|
|
bd5009058b | ||
|
|
d3f79c7ad3 | ||
|
|
3f65503a79 | ||
|
|
6ac1216f8c | ||
|
|
1cae35f96b | ||
|
|
0d13e91a62 | ||
|
|
b1b600e09e | ||
|
|
3692e1c4bd | ||
|
|
dcbd82ac3b | ||
|
|
d4f0541027 | ||
|
|
a30d8e7819 | ||
|
|
8ee6bf044a | ||
|
|
6632247c9c | ||
|
|
d68389dc52 | ||
|
|
4a43273ee5 | ||
|
|
66f52a6e21 | ||
|
|
640bfc4eff | ||
|
|
408ef0f5b7 | ||
|
|
b9f76394aa | ||
|
|
a96f483d56 | ||
|
|
84cb9f15a4 | ||
|
|
d4da14cf18 | ||
|
|
4ad4b8e0b8 | ||
|
|
bb29d9c8ca | ||
|
|
e72e65858f | ||
|
|
a42845502e | ||
|
|
bea5ad3f13 | ||
|
|
5a0440d6f8 | ||
|
|
38b62d4ae3 | ||
|
|
462d8b3e74 | ||
|
|
291c3b6dbc | ||
|
|
df225d9170 | ||
|
|
592e981bd2 | ||
|
|
3d7c44735a | ||
|
|
81fddb4ccf | ||
|
|
c9d4c5ae3e | ||
|
|
be5b1fd92b | ||
|
|
d78c419627 | ||
|
|
dc52abf4ce | ||
|
|
a13549cc28 | ||
|
|
baf4c474e3 | ||
|
|
a58750992d | ||
|
|
17546c3a08 | ||
|
|
067f13b61c | ||
|
|
e249983c77 | ||
|
|
454b191370 | ||
|
|
a882a9d79f | ||
|
|
89fc835bb2 | ||
|
|
364958cbaf | ||
|
|
1b6af2045e | ||
|
|
be09ff8e43 | ||
|
|
99c8bffcbf | ||
|
|
03d16d12d5 | ||
|
|
1624c51cb5 | ||
|
|
83aabefcc5 | ||
|
|
dfece708e1 | ||
|
|
5d0f82ffbd | ||
|
|
361dc94002 | ||
|
|
cc0fdf15ef | ||
|
|
928675a847 | ||
|
|
12c1131b0c | ||
|
|
bb1dde0469 | ||
|
|
ced69b8397 | ||
|
|
013808956c | ||
|
|
009057cb87 | ||
|
|
82cb21fca3 | ||
|
|
7e8937a332 | ||
|
|
e5dcfa0a2e | ||
|
|
f4520a011a | ||
|
|
98dd6ca460 | ||
|
|
c3d9312240 | ||
|
|
5ea761e19f | ||
|
|
46a7860427 | ||
|
|
af9b63eaed | ||
|
|
9a26e0db16 | ||
|
|
efe6989fd3 | ||
|
|
aa1c9b80e3 | ||
|
|
6981df3b9a | ||
|
|
0d1ed625a8 | ||
|
|
710fc56c6a | ||
|
|
d5a15d6756 | ||
|
|
b376da1829 | ||
|
|
f7f17f0057 | ||
|
|
d06b9c2992 | ||
|
|
99ca5d0a03 | ||
|
|
4783c7f70a | ||
|
|
d89bdfbd27 | ||
|
|
1e324ad3bc | ||
|
|
52737e91e5 | ||
|
|
1872e2b63d | ||
|
|
3c5605b793 | ||
|
|
9a2b7cf5be | ||
|
|
1a20e9f9b4 | ||
|
|
14d79e4eef | ||
|
|
71f48d2aef | ||
|
|
312adca226 | ||
|
|
d35c6e77d7 | ||
|
|
1de21c86ae | ||
|
|
c709a592eb | ||
|
|
a54c544eb4 | ||
|
|
7d936ec6aa | ||
|
|
f63ec1332f | ||
|
|
d340ccd601 | ||
|
|
95e8f0a31e | ||
|
|
97ddfcb17a | ||
|
|
7bb5f9a1e4 | ||
|
|
11297b38c5 | ||
|
|
fc19ab2868 | ||
|
|
5e01c0a7db | ||
|
|
f1c3d820f7 | ||
|
|
0757a75732 | ||
|
|
f0ea45a0f8 | ||
|
|
45f2335a60 | ||
|
|
d629939cf3 | ||
|
|
404f76dcb9 | ||
|
|
498ce6b00c | ||
|
|
e3a8fd116d | ||
|
|
d33e09bcf3 | ||
|
|
fb3bad3887 | ||
|
|
3a736ad4a8 | ||
|
|
c1b0c41769 | ||
|
|
c03274703e | ||
|
|
4cd08e88f6 | ||
|
|
e2c4872030 | ||
|
|
d4f190e995 | ||
|
|
039107e837 | ||
|
|
ef6c211275 | ||
|
|
1f3accc0d7 | ||
|
|
2815f80063 | ||
|
|
fa645abee3 | ||
|
|
a86649def3 | ||
|
|
1fc4c56bc4 | ||
|
|
79dd72f53d | ||
|
|
ffa060ce56 | ||
|
|
5ce9719951 | ||
|
|
914aa7d372 | ||
|
|
4a88cbde3a | ||
|
|
4882519c0f | ||
|
|
7abe68fac1 | ||
|
|
e62cca1e7c | ||
|
|
a016741918 | ||
|
|
2f95810fa3 | ||
|
|
16e2c3b1e0 | ||
|
|
bc8a92caa9 | ||
|
|
3a5b67a3e1 | ||
|
|
2a596b8162 | ||
|
|
e059239bc3 | ||
|
|
986ad9fc57 | ||
|
|
1bb3d9be73 | ||
|
|
ae31f19ef6 | ||
|
|
c170ddc7ae | ||
|
|
58b6d92ce2 | ||
|
|
87a4d73556 | ||
|
|
4c54a003fa | ||
|
|
a5f3eabf8b | ||
|
|
3bf6c59d23 | ||
|
|
ef83dea95c | ||
|
|
686c23d25b | ||
|
|
b153e90ec5 | ||
|
|
38cc36980f | ||
|
|
b83fb525a8 | ||
|
|
e26e0955b3 | ||
|
|
7ada80b619 | ||
|
|
056e0fe2d9 | ||
|
|
9be0c67d5c | ||
|
|
664bc9cae0 | ||
|
|
959c7dc783 | ||
|
|
8e333d0a03 | ||
|
|
5afcf17706 | ||
|
|
a8d05294bc | ||
|
|
1b25e492c7 | ||
|
|
61b22316d6 | ||
|
|
be8ebdba46 | ||
|
|
2d759df47a | ||
|
|
d1b5cf99d0 | ||
|
|
516608d883 | ||
|
|
bf95e6def9 | ||
|
|
3c5cb31775 | ||
|
|
d2f51fccb9 | ||
|
|
c13db04f6d | ||
|
|
d3aa056151 | ||
|
|
1c60f0b53b | ||
|
|
ca2b85f453 | ||
|
|
b80479f9ef | ||
|
|
0a9070c394 | ||
|
|
bd29bac716 | ||
|
|
d42a22f446 | ||
|
|
d1112a0feb | ||
|
|
a73baded88 | ||
|
|
24d3a698a0 | ||
|
|
1eeba34806 | ||
|
|
94fa95d747 | ||
|
|
c98a561722 | ||
|
|
9f6484a328 | ||
|
|
40c0ed092e | ||
|
|
c719aa3db8 | ||
|
|
8f8f72fa76 | ||
|
|
4ae6d42871 | ||
|
|
64243382cf | ||
|
|
c7acb2d2c4 | ||
|
|
20795cf884 | ||
|
|
6b9f64a273 | ||
|
|
9e270c951a | ||
|
|
20308dc804 | ||
|
|
b1ecb1f61f | ||
|
|
6fd8979754 | ||
|
|
050416224d | ||
|
|
6e5a221180 | ||
|
|
a1ab252303 | ||
|
|
3c89fd51ee | ||
|
|
018b8a6315 | ||
|
|
ecaa146d5b | ||
|
|
f50a4d8c2a | ||
|
|
68b0e44fbd | ||
|
|
ac9946c697 | ||
|
|
a0a8bc24e8 | ||
|
|
06ab802bc6 | ||
|
|
04ec757083 | ||
|
|
15e04bb55d | ||
|
|
e4ed7fd8f7 | ||
|
|
fd5352b0c6 | ||
|
|
606e667b88 | ||
|
|
2a209c23c4 | ||
|
|
70305266dc | ||
|
|
8e561d9f95 | ||
|
|
f446cac43c | ||
|
|
7e1ceb9a3e | ||
|
|
1b5e35461d | ||
|
|
df75700015 | ||
|
|
b586df6689 | ||
|
|
4ca2ff0495 | ||
|
|
93494c7e35 | ||
|
|
11874bc4ae | ||
|
|
dcf98d13c8 | ||
|
|
2a735e815a | ||
|
|
52de16b4c9 | ||
|
|
7133a28fdb | ||
|
|
ade2ff97e0 | ||
|
|
450d86be7d | ||
|
|
c9caf612eb | ||
|
|
56ef678c09 | ||
|
|
29e647763a | ||
|
|
357150bcab | ||
|
|
f7224ff403 | ||
|
|
01ffad2e6e | ||
|
|
223e8cafac | ||
|
|
d1ffbd8a03 | ||
|
|
f286cb9a34 | ||
|
|
5c63855cc0 | ||
|
|
2a96ae9ec2 | ||
|
|
36a2da0659 | ||
|
|
38abec520c | ||
|
|
1274d26b4c | ||
|
|
6556c79207 | ||
|
|
7e6c580130 | ||
|
|
cc4fb64b34 | ||
|
|
f4cb4bb1b8 | ||
|
|
287b3ba1f4 | ||
|
|
208998972a | ||
|
|
7cdd062432 | ||
|
|
eccb529605 | ||
|
|
78dc28cce8 | ||
|
|
84076db78e | ||
|
|
c3779f0e94 | ||
|
|
c5ac563e74 | ||
|
|
92ca220890 | ||
|
|
72f88e5c0f | ||
|
|
1a75a71ad6 | ||
|
|
3c3b179c29 | ||
|
|
3f08bb4cdf | ||
|
|
423268f485 | ||
|
|
d3f003a15f | ||
|
|
7386378cc0 | ||
|
|
d6547462e5 | ||
|
|
d297a220ce | ||
|
|
1de5434e1a | ||
|
|
f46accc74d | ||
|
|
cd2100ed84 | ||
|
|
ac087921d8 | ||
|
|
82b1f14e2b | ||
|
|
df7e1cf078 | ||
|
|
39fa8f7be4 | ||
|
|
46c2184de4 | ||
|
|
a9f9894f29 | ||
|
|
a6c360eeda | ||
|
|
01a4002169 | ||
|
|
8caaf317ae | ||
|
|
0e3c2ef10f | ||
|
|
db6c85d3d7 | ||
|
|
2bd95620a5 | ||
|
|
d8ad30f38a | ||
|
|
aad5f52968 | ||
|
|
f5d49f6657 | ||
|
|
53ae64e578 | ||
|
|
1a936b6aca | ||
|
|
4776fa1361 | ||
|
|
c5084fd025 | ||
|
|
cc2735f733 | ||
|
|
7f6b2b80f8 | ||
|
|
f64c2bc065 | ||
|
|
6752b49536 | ||
|
|
ab138e7df1 | ||
|
|
059da90a96 | ||
|
|
0821c7bdd9 | ||
|
|
89e00eb5a4 | ||
|
|
1a0f347023 | ||
|
|
1e27c2dabe | ||
|
|
629be45c4a | ||
|
|
e115e3c4e7 | ||
|
|
414fb1f406 | ||
|
|
fe0a8f3363 | ||
|
|
45589d5133 | ||
|
|
7804787e9e | ||
|
|
2e735f622f | ||
|
|
6accb90c47 | ||
|
|
e948a013cd | ||
|
|
b79535f369 | ||
|
|
ed3bcc6d9a | ||
|
|
0f23581f64 | ||
|
|
2af1e4b192 | ||
|
|
dc404b365f | ||
|
|
86f3891a2b | ||
|
|
86053ea54b | ||
|
|
938600ba95 | ||
|
|
80ab967d39 | ||
|
|
43acbaa702 | ||
|
|
5d6492e6f5 | ||
|
|
aeb9cc1732 | ||
|
|
fa25c8ef22 | ||
|
|
77a9613c3a | ||
|
|
ba62a1f630 | ||
|
|
153ab8f0fa | ||
|
|
f6c860afc0 | ||
|
|
d13b755df2 | ||
|
|
6bacbf6cac | ||
|
|
0d5baa2219 | ||
|
|
97c8a1d7ab | ||
|
|
5923d22379 | ||
|
|
70494117d1 | ||
|
|
8210743dad | ||
|
|
895f3cc109 | ||
|
|
71f160dddc | ||
|
|
92abaa0d47 | ||
|
|
47710c1385 | ||
|
|
df3abcbc9a | ||
|
|
dbb7ad41e5 | ||
|
|
9773d4e409 | ||
|
|
993165fa66 | ||
|
|
c49f5dad05 | ||
|
|
c0bdedfed3 | ||
|
|
061107b65f |
@@ -1,5 +1,3 @@
|
|||||||
dist/
|
dist/
|
||||||
vendor/
|
|
||||||
!dist/traefik
|
!dist/traefik
|
||||||
site/
|
site/
|
||||||
**/*.test
|
|
||||||
|
|||||||
24
.github/CODEOWNERS
vendored
Normal file
24
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
provider/kubernetes/** @containous/kubernetes
|
||||||
|
provider/rancher/** @containous/rancher
|
||||||
|
provider/marathon/** @containous/marathon
|
||||||
|
provider/docker/** @containous/docker
|
||||||
|
|
||||||
|
docs/user-guide/kubernetes.md @containous/kubernetes
|
||||||
|
docs/user-guide/marathon.md @containous/marathon
|
||||||
|
docs/user-guide/swarm.md @containous/docker
|
||||||
|
docs/user-guide/swarm-mode.md @containous/docker
|
||||||
|
|
||||||
|
docs/configuration/backends/docker.md @containous/docker
|
||||||
|
docs/configuration/backends/kubernetes.md @containous/kubernetes
|
||||||
|
docs/configuration/backends/marathon.md @containous/marathon
|
||||||
|
docs/configuration/backends/rancher.md @containous/rancher
|
||||||
|
|
||||||
|
examples/k8s/ @containous/kubernetes
|
||||||
|
examples/compose-k8s.yaml @containous/kubernetes
|
||||||
|
examples/k8s.namespace.yaml @containous/kubernetes
|
||||||
|
examples/compose-rancher.yml @containous/rancher
|
||||||
|
examples/compose-marathon.yml @containous/marathon
|
||||||
|
|
||||||
|
vendor/github.com/gambol99/go-marathon @containous/marathon
|
||||||
|
vendor/github.com/rancher @containous/rancher
|
||||||
|
vendor/k8s.io/ @containous/kubernetes
|
||||||
109
.github/CONTRIBUTING.md
vendored
109
.github/CONTRIBUTING.md
vendored
@@ -1,109 +0,0 @@
|
|||||||
# Contributing
|
|
||||||
|
|
||||||
### Building
|
|
||||||
|
|
||||||
You need either [Docker](https://github.com/docker/docker) and `make`, or `go` and `glide` in order to build traefik.
|
|
||||||
|
|
||||||
#### Setting up your `go` environment
|
|
||||||
|
|
||||||
- You need `go` v1.5
|
|
||||||
- You need to set `export GO15VENDOREXPERIMENT=1` environment variable
|
|
||||||
- You need `go-bindata` to be able to use `go generate` command (needed to build) : `go get github.com/jteeuwen/go-bindata/...`.
|
|
||||||
- If you clone Træfɪk into something like `~/go/src/github.com/traefik`, your `GOPATH` variable will have to be set to `~/go`: export `GOPATH=~/go`.
|
|
||||||
|
|
||||||
#### Using `Docker` and `Makefile`
|
|
||||||
|
|
||||||
You need to run the `binary` target. This will create binaries for Linux platform in the `dist` folder.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ make binary
|
|
||||||
docker build -t "traefik-dev:no-more-godep-ever" -f build.Dockerfile .
|
|
||||||
Sending build context to Docker daemon 295.3 MB
|
|
||||||
Step 0 : FROM golang:1.5
|
|
||||||
---> 8c6473912976
|
|
||||||
Step 1 : RUN go get github.com/Masterminds/glide
|
|
||||||
[...]
|
|
||||||
docker run --rm -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/emile/dev/go/src/github.com/containous/traefik/"dist":/go/src/github.com/containous/traefik/"dist"" "traefik-dev:no-more-godep-ever" ./script/make.sh generate binary
|
|
||||||
---> Making bundle: generate (in .)
|
|
||||||
removed 'gen.go'
|
|
||||||
|
|
||||||
---> Making bundle: binary (in .)
|
|
||||||
|
|
||||||
$ ls dist/
|
|
||||||
traefik*
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Using `glide`
|
|
||||||
|
|
||||||
The idea behind `glide` is the following :
|
|
||||||
|
|
||||||
- when checkout(ing) a project, **run `glide install`** to install
|
|
||||||
(`go get …`) the dependencies in the `GOPATH`.
|
|
||||||
- if you need another dependency, import and use it in
|
|
||||||
the source, and **run `glide get github.com/Masterminds/cookoo`** to save it in
|
|
||||||
`vendor` and add it to your `glide.yaml`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ glide install
|
|
||||||
# generate
|
|
||||||
$ go generate
|
|
||||||
# Simple go build
|
|
||||||
$ go build
|
|
||||||
# Using gox to build multiple platform
|
|
||||||
$ gox "linux darwin" "386 amd64 arm" \
|
|
||||||
-output="dist/traefik_{{.OS}}-{{.Arch}}"
|
|
||||||
# run other commands like tests
|
|
||||||
$ go test ./...
|
|
||||||
ok _/home/vincent/src/github/vdemeester/traefik 0.004s
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
You can run unit tests using the `test-unit` target and the
|
|
||||||
integration test using the `test-integration` target.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ make test-unit
|
|
||||||
docker build -t "traefik-dev:your-feature-branch" -f build.Dockerfile .
|
|
||||||
# […]
|
|
||||||
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/vincent/src/github/vdemeester/traefik/dist:/go/src/github.com/containous/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate test-unit
|
|
||||||
---> Making bundle: generate (in .)
|
|
||||||
removed 'gen.go'
|
|
||||||
|
|
||||||
---> Making bundle: test-unit (in .)
|
|
||||||
+ go test -cover -coverprofile=cover.out .
|
|
||||||
ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
|
|
||||||
|
|
||||||
Test success
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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
|
|
||||||
```
|
|
||||||
69
.github/ISSUE_TEMPLATE.md
vendored
Normal file
69
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<!--
|
||||||
|
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||||
|
|
||||||
|
The issue tracker is for reporting bugs and feature requests only.
|
||||||
|
For end-user related support questions, refer to one of the following:
|
||||||
|
|
||||||
|
- Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik
|
||||||
|
- the Traefik community Slack channel: https://traefik.herokuapp.com
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
### Do you want to request a *feature* or report a *bug*?
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If you intend to ask a support question: DO NOT FILE AN ISSUE.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### What did you do?
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
HOW TO WRITE A GOOD ISSUE?
|
||||||
|
|
||||||
|
- Respect the issue template as more as possible.
|
||||||
|
- If it's possible use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||||
|
- The title must be short and descriptive.
|
||||||
|
- Explain the conditions which led you to write this issue: the context.
|
||||||
|
- The context should lead to something, an idea or a problem that you’re facing.
|
||||||
|
- Remain clear and concise.
|
||||||
|
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
### What did you expect to see?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### What did you see instead?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Output of `traefik version`: (_What version of Traefik are you using?_)
|
||||||
|
|
||||||
|
<!--
|
||||||
|
For the Traefik Docker image:
|
||||||
|
docker run [IMAGE] version
|
||||||
|
ex: docker run traefik version
|
||||||
|
-->
|
||||||
|
|
||||||
|
```
|
||||||
|
(paste your output here)
|
||||||
|
```
|
||||||
|
|
||||||
|
### What is your environment & configuration (arguments, toml, provider, platform, ...)?
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# (paste your configuration here)
|
||||||
|
```
|
||||||
|
<!--
|
||||||
|
Add more configuration information here.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
### If applicable, please paste the log output in debug mode (`--debug` switch)
|
||||||
|
|
||||||
|
```
|
||||||
|
(paste your output here)
|
||||||
|
```
|
||||||
23
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
23
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<!--
|
||||||
|
PLEASE READ THIS MESSAGE.
|
||||||
|
|
||||||
|
HOW TO WRITE A GOOD PULL REQUEST?
|
||||||
|
|
||||||
|
- Make it small.
|
||||||
|
- Do only one thing.
|
||||||
|
- Avoid re-formatting.
|
||||||
|
- Make sure the code builds.
|
||||||
|
- Make sure all tests pass.
|
||||||
|
- Add tests.
|
||||||
|
- Write useful descriptions and titles.
|
||||||
|
- Address review comments in terms of additional commits.
|
||||||
|
- Do not amend/squash existing ones unless the PR is trivial.
|
||||||
|
- Read the contributing guide: https://github.com/containous/traefik/blob/master/.github/CONTRIBUTING.md.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Briefly describe the pull request in a few paragraphs.
|
||||||
|
-->
|
||||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -1,15 +1,14 @@
|
|||||||
/dist
|
/dist
|
||||||
gen.go
|
/autogen/gen.go
|
||||||
.idea
|
.idea
|
||||||
.intellij
|
.intellij
|
||||||
log
|
|
||||||
*.iml
|
*.iml
|
||||||
traefik
|
/traefik
|
||||||
traefik.toml
|
/traefik.toml
|
||||||
*.test
|
/static/
|
||||||
vendor/
|
|
||||||
static/
|
|
||||||
.vscode/
|
.vscode/
|
||||||
site/
|
/site/
|
||||||
*.log
|
*.log
|
||||||
*.exe
|
*.exe
|
||||||
|
.DS_Store
|
||||||
|
/example/acme/acme.json
|
||||||
|
|||||||
11
.semaphoreci/setup.sh
Executable file
11
.semaphoreci/setup.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
sudo -E apt-get -yq update
|
||||||
|
sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-ce=${DOCKER_VERSION}*
|
||||||
|
docker version
|
||||||
|
|
||||||
|
pip install --user -r requirements.txt
|
||||||
|
|
||||||
|
make pull-images
|
||||||
|
ci_retry make validate
|
||||||
6
.semaphoreci/tests.sh
Executable file
6
.semaphoreci/tests.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
make test-unit
|
||||||
|
ci_retry make test-integration
|
||||||
|
make -j${N_MAKE_JOBS} crossbinary-default-parallel
|
||||||
37
.semaphoreci/vars
Normal file
37
.semaphoreci/vars
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
export REPO='containous/traefik'
|
||||||
|
|
||||||
|
if VERSION=$(git describe --exact-match --abbrev=0 --tags);
|
||||||
|
then
|
||||||
|
export VERSION
|
||||||
|
else
|
||||||
|
export VERSION=''
|
||||||
|
fi
|
||||||
|
|
||||||
|
export CODENAME=roquefort
|
||||||
|
|
||||||
|
export N_MAKE_JOBS=2
|
||||||
|
|
||||||
|
|
||||||
|
function ci_retry {
|
||||||
|
|
||||||
|
local NRETRY=3
|
||||||
|
local NSLEEP=5
|
||||||
|
local n=0
|
||||||
|
|
||||||
|
until [ $n -ge $NRETRY ]
|
||||||
|
do
|
||||||
|
"$@" && break
|
||||||
|
n=$[$n+1]
|
||||||
|
echo "$@ failed, attempt ${n}/${NRETRY}"
|
||||||
|
sleep $NSLEEP
|
||||||
|
done
|
||||||
|
|
||||||
|
[ $n -lt $NRETRY ]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export -f ci_retry
|
||||||
|
|
||||||
77
.travis.yml
77
.travis.yml
@@ -1,28 +1,57 @@
|
|||||||
branches:
|
sudo: required
|
||||||
except:
|
dist: trusty
|
||||||
- /^v\d\.\d\.\d.*$/
|
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
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
|
- REPO: $TRAVIS_REPO_SLUG
|
||||||
- VERSION: v1.0.0-beta.$TRAVIS_BUILD_NUMBER
|
- VERSION: $TRAVIS_TAG
|
||||||
sudo: required
|
- CODENAME: roquefort
|
||||||
services:
|
- N_MAKE_JOBS: 2
|
||||||
- docker
|
|
||||||
install:
|
|
||||||
- sudo service docker stop
|
|
||||||
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-1.10.1 -o /usr/bin/docker
|
|
||||||
- sudo chmod +x /usr/bin/docker
|
|
||||||
- sudo service docker start
|
|
||||||
- pip install --user mkdocs
|
|
||||||
- pip install --user pymdown-extensions
|
|
||||||
before_script:
|
|
||||||
- make validate
|
|
||||||
- make binary
|
|
||||||
script:
|
script:
|
||||||
- make test-unit
|
- echo "Skipping tests... (Tests are executed on SemaphoreCI)"
|
||||||
- make test-integration
|
|
||||||
- make crossbinary
|
before_deploy:
|
||||||
- make image
|
- >
|
||||||
after_success:
|
if ! [ "$BEFORE_DEPLOY_RUN" ]; then
|
||||||
- make deploy
|
export BEFORE_DEPLOY_RUN=1;
|
||||||
|
sudo -E apt-get -yq update;
|
||||||
|
sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-ce=${DOCKER_VERSION}*;
|
||||||
|
docker version;
|
||||||
|
pip install --user -r requirements.txt;
|
||||||
|
make -j${N_MAKE_JOBS} crossbinary-parallel;
|
||||||
|
make image-dirty;
|
||||||
|
mkdocs build --clean;
|
||||||
|
tar cfz dist/traefik-${VERSION}.src.tar.gz --exclude-vcs --exclude dist .;
|
||||||
|
fi
|
||||||
|
deploy:
|
||||||
|
- provider: pages
|
||||||
|
edge: true
|
||||||
|
github_token: ${GITHUB_TOKEN}
|
||||||
|
local_dir: site
|
||||||
|
skip_cleanup: true
|
||||||
|
on:
|
||||||
|
repo: containous/traefik
|
||||||
|
tags: true
|
||||||
|
- provider: releases
|
||||||
|
api_key: ${GITHUB_TOKEN}
|
||||||
|
file: dist/traefik*
|
||||||
|
skip_cleanup: true
|
||||||
|
file_glob: true
|
||||||
|
on:
|
||||||
|
repo: containous/traefik
|
||||||
|
tags: true
|
||||||
|
- provider: script
|
||||||
|
script: sh script/deploy.sh
|
||||||
|
skip_cleanup: true
|
||||||
|
on:
|
||||||
|
repo: containous/traefik
|
||||||
|
tags: true
|
||||||
|
- provider: script
|
||||||
|
script: sh script/deploy-docker.sh
|
||||||
|
skip_cleanup: true
|
||||||
|
on:
|
||||||
|
repo: containous/traefik
|
||||||
|
|||||||
Binary file not shown.
BIN
.travis/traefiker_rsa.enc
Normal file
BIN
.travis/traefiker_rsa.enc
Normal file
Binary file not shown.
1674
CHANGELOG.md
Normal file
1674
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
74
CODE_OF_CONDUCT.md
Normal file
74
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity and
|
||||||
|
orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at contact@containo.us
|
||||||
|
All complaints will be reviewed and investigated and will result in a response that
|
||||||
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
|
available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
|
[homepage]: http://contributor-covenant.org
|
||||||
|
[version]: http://contributor-covenant.org/version/1/4/
|
||||||
228
CONTRIBUTING.md
Normal file
228
CONTRIBUTING.md
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
You need either [Docker](https://github.com/docker/docker) and `make` (Method 1), or `go` (Method 2) in order to build Traefik. For changes to its dependencies, the `glide` dependency management tool and `glide-vc` plugin are required.
|
||||||
|
|
||||||
|
### Method 1: Using `Docker` and `Makefile`
|
||||||
|
|
||||||
|
You need to run the `binary` target. This will create binaries for Linux platform in the `dist` folder.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ make binary
|
||||||
|
docker build -t "traefik-dev:no-more-godep-ever" -f build.Dockerfile .
|
||||||
|
Sending build context to Docker daemon 295.3 MB
|
||||||
|
Step 0 : FROM golang:1.9-alpine
|
||||||
|
---> 8c6473912976
|
||||||
|
Step 1 : RUN go get github.com/Masterminds/glide
|
||||||
|
[...]
|
||||||
|
docker run --rm -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/emile/dev/go/src/github.com/containous/traefik/"dist":/go/src/github.com/containous/traefik/"dist"" "traefik-dev:no-more-godep-ever" ./script/make.sh generate binary
|
||||||
|
---> Making bundle: generate (in .)
|
||||||
|
removed 'gen.go'
|
||||||
|
|
||||||
|
---> Making bundle: binary (in .)
|
||||||
|
|
||||||
|
$ ls dist/
|
||||||
|
traefik*
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 2: Using `go`
|
||||||
|
|
||||||
|
##### Setting up your `go` environment
|
||||||
|
|
||||||
|
- You need `go` v1.9+
|
||||||
|
- It is recommended you clone Træfik into a directory like `~/go/src/github.com/containous/traefik` (This is the official golang workspace hierarchy, and will allow dependencies to resolve properly)
|
||||||
|
- Set your `GOPATH` and `PATH` variable to be set to `~/go` via:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export GOPATH=~/go
|
||||||
|
export PATH=$PATH:$GOPATH/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note: You will want to add those 2 export lines to your `.bashrc` or `.bash_profile`
|
||||||
|
|
||||||
|
- Verify your environment is setup properly by running `$ go env`. Depending on your OS and environment you should see output similar to:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GOARCH="amd64"
|
||||||
|
GOBIN=""
|
||||||
|
GOEXE=""
|
||||||
|
GOHOSTARCH="amd64"
|
||||||
|
GOHOSTOS="linux"
|
||||||
|
GOOS="linux"
|
||||||
|
GOPATH="/home/<yourusername>/go"
|
||||||
|
GORACE=""
|
||||||
|
## more go env's will be listed
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Build Træfik
|
||||||
|
|
||||||
|
Once your environment is set up and the Træfik repository cloned you can build Træfik. You need get `go-bindata` once to be able to use `go generate` command as part of the build. The steps to build are:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/go/src/github.com/containous/traefik
|
||||||
|
|
||||||
|
# Get go-bindata. Please note, the ellipses are required
|
||||||
|
go get github.com/jteeuwen/go-bindata/...
|
||||||
|
|
||||||
|
# Start build
|
||||||
|
go generate
|
||||||
|
|
||||||
|
# Standard go build
|
||||||
|
go build ./cmd/traefik
|
||||||
|
# run other commands like tests
|
||||||
|
```
|
||||||
|
|
||||||
|
You will find the Træfik executable in the `~/go/src/github.com/containous/traefik` folder as `traefik`.
|
||||||
|
|
||||||
|
### Setting up `glide` and `glide-vc` for dependency management
|
||||||
|
|
||||||
|
- Glide is not required for building; however, it is necessary to modify dependencies (i.e., add, update, or remove third-party packages)
|
||||||
|
- Glide can be installed either via homebrew: `$ brew install glide` or via the official glide script: `$ curl https://glide.sh/get | sh`
|
||||||
|
- The glide plugin `glide-vc` must be installed from source: `go get github.com/sgotti/glide-vc`
|
||||||
|
|
||||||
|
If you want to add a dependency, use `$ glide get` to have glide put it into the vendor folder and update the glide manifest/lock files (`glide.yaml` and `glide.lock`, respectively). A following `glide-vc` run should be triggered to trim down the size of the vendor folder. The final result must be committed into VCS.
|
||||||
|
|
||||||
|
Care must be taken to choose the right arguments to `glide` when dealing with dependencies, or otherwise risk ending up with a broken build. For that reason, the helper script `script/glide.sh` encapsulates the gory details and conveniently calls `glide-vc` as well. Call it without parameters for basic usage instructions.
|
||||||
|
|
||||||
|
Here's a full example using glide to add a new dependency:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# install the new main dependency github.com/foo/bar and minimize vendor size
|
||||||
|
$ ./script/glide.sh get github.com/foo/bar
|
||||||
|
# generate (Only required to integrate other components such as web dashboard)
|
||||||
|
$ go generate
|
||||||
|
# Standard go build
|
||||||
|
$ go build ./cmd/traefik
|
||||||
|
# run other commands like tests
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
#### Method 1: `Docker` and `make`
|
||||||
|
|
||||||
|
You can run unit tests using the `test-unit` target and the
|
||||||
|
integration test using the `test-integration` target.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ make test-unit
|
||||||
|
docker build -t "traefik-dev:your-feature-branch" -f build.Dockerfile .
|
||||||
|
# […]
|
||||||
|
docker run --rm -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -v "/home/vincent/src/github/vdemeester/traefik/dist:/go/src/github.com/containous/traefik/dist" "traefik-dev:your-feature-branch" ./script/make.sh generate test-unit
|
||||||
|
---> Making bundle: generate (in .)
|
||||||
|
removed 'gen.go'
|
||||||
|
|
||||||
|
---> Making bundle: test-unit (in .)
|
||||||
|
+ go test -cover -coverprofile=cover.out .
|
||||||
|
ok github.com/containous/traefik 0.005s coverage: 4.1% of statements
|
||||||
|
|
||||||
|
Test success
|
||||||
|
```
|
||||||
|
|
||||||
|
For development purposes, you can specify which tests to run by using:
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
|
||||||
|
#### Method 2: `go`
|
||||||
|
|
||||||
|
- Tests can be run from the cloned directory, by `$ go test ./...` which should return `ok` similar to:
|
||||||
|
```
|
||||||
|
ok _/home/vincent/src/github/vdemeester/traefik 0.004s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
The [documentation site](http://docs.traefik.io/) is built with [mkdocs](http://mkdocs.org/)
|
||||||
|
|
||||||
|
First make sure you have python and pip installed
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ python --version
|
||||||
|
Python 2.7.2
|
||||||
|
$ pip --version
|
||||||
|
pip 1.5.2
|
||||||
|
```
|
||||||
|
|
||||||
|
Then install mkdocs with pip
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ pip install mkdocs
|
||||||
|
```
|
||||||
|
|
||||||
|
To test documentation locally run `mkdocs serve` in the root directory, this should start a server locally to preview your changes.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ 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
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## How to Write a Good Issue
|
||||||
|
|
||||||
|
Please keep in mind that the GitHub issue tracker is not intended as a general support forum, but for reporting bugs and feature requests.
|
||||||
|
|
||||||
|
For end-user related support questions, refer to one of the following:
|
||||||
|
- the Traefik community Slack channel: [](https://traefik.herokuapp.com)
|
||||||
|
- [Stack Overflow](https://stackoverflow.com/questions/tagged/traefik) (using the `traefik` tag)
|
||||||
|
|
||||||
|
### Title
|
||||||
|
|
||||||
|
The title must be short and descriptive. (~60 characters)
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
- Respect the issue template as much as possible. [template](.github/ISSUE_TEMPLATE.md)
|
||||||
|
- If it's possible use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||||
|
- Explain the conditions which led you to write this issue: the context.
|
||||||
|
- The context should lead to something, an idea or a problem that you’re facing.
|
||||||
|
- Remain clear and concise.
|
||||||
|
- Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
|
||||||
|
|
||||||
|
|
||||||
|
## How to Write a Good Pull Request
|
||||||
|
|
||||||
|
### Title
|
||||||
|
|
||||||
|
The title must be short and descriptive. (~60 characters)
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
- Respect the pull request template as much as possible. [template](.github/PULL_REQUEST_TEMPLATE.md)
|
||||||
|
- Explain the conditions which led you to write this PR: the context.
|
||||||
|
- The context should lead to something, an idea or a problem that you’re facing.
|
||||||
|
- Remain clear and concise.
|
||||||
|
- Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown)
|
||||||
|
|
||||||
|
### Content
|
||||||
|
|
||||||
|
- Make it small.
|
||||||
|
- Do only one thing.
|
||||||
|
- Write useful descriptions and titles.
|
||||||
|
- Avoid re-formatting.
|
||||||
|
- Make sure the code builds.
|
||||||
|
- Make sure all tests pass.
|
||||||
|
- Add tests.
|
||||||
|
- Address review comments in terms of additional commits.
|
||||||
|
- Do not amend/squash existing ones unless the PR is trivial.
|
||||||
|
- If a PR involves changes to third-party dependencies, the commits pertaining to the vendor folder and the manifest/lock file(s) should be committed separated.
|
||||||
|
|
||||||
|
|
||||||
|
Read [10 tips for better pull requests](http://blog.ploeh.dk/2015/01/15/10-tips-for-better-pull-requests/).
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2016 Containous SAS, Emile Vauge, emile@vauge.com
|
Copyright (c) 2016-2017 Containous SAS
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
144
MAINTAINER.md
Normal file
144
MAINTAINER.md
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# Maintainers
|
||||||
|
|
||||||
|
## The team
|
||||||
|
|
||||||
|
* Emile Vauge [@emilevauge](https://github.com/emilevauge)
|
||||||
|
* Vincent Demeester [@vdemeester](https://github.com/vdemeester)
|
||||||
|
* Ed Robinson [@errm](https://github.com/errm)
|
||||||
|
* Daniel Tomcej [@dtomcej](https://github.com/dtomcej)
|
||||||
|
* Manuel Zapf [@SantoDE](https://github.com/SantoDE)
|
||||||
|
* Timo Reimann [@timoreimann](https://github.com/timoreimann)
|
||||||
|
* Ludovic Fernandez [@ldez](https://github.com/ldez)
|
||||||
|
* Julien Salleyron [@juliens](https://github.com/juliens)
|
||||||
|
* Nicolas Mengin [@nmengin](https://github.com/nmengin)
|
||||||
|
* Marco Jantke [@marco-jantke](https://github.com/marco-jantke)
|
||||||
|
|
||||||
|
|
||||||
|
## PR review process:
|
||||||
|
|
||||||
|
* The status `needs-design-review` is only used in complex/heavy/tricky PRs.
|
||||||
|
* From `1` to `2`: 1 design LGTM in comment, by a senior maintainer, if needed.
|
||||||
|
* From `2` to `3`: 3 LGTM by any maintainer.
|
||||||
|
* If needed, a specific maintainer familiar with a particular domain can be requested for the review.
|
||||||
|
|
||||||
|
We use [PRM](https://github.com/ldez/prm) to manage locally pull requests.
|
||||||
|
|
||||||
|
|
||||||
|
## Bots
|
||||||
|
|
||||||
|
### [Myrmica Lobicornis](https://github.com/containous/lobicornis/)
|
||||||
|
|
||||||
|
**Update and Merge Pull Request**
|
||||||
|
|
||||||
|
The maintainer giving the final LGTM must add the `status/3-needs-merge` label to trigger the merge bot.
|
||||||
|
|
||||||
|
By default, a squash-rebase merge will be carried out.
|
||||||
|
If you want to preserve commits you must add `bot/merge-method-rebase` before `status/3-needs-merge`.
|
||||||
|
|
||||||
|
The status `status/4-merge-in-progress` is only for the bot.
|
||||||
|
|
||||||
|
If the bot is not able to perform the merge, the label `bot/need-human-merge` is added.
|
||||||
|
In this case you must solve conflicts/CI/... and after you only need to remove `bot/need-human-merge`.
|
||||||
|
|
||||||
|
|
||||||
|
### [Myrmica Bibikoffi](https://github.com/containous/bibikoffi/)
|
||||||
|
|
||||||
|
* closes stale issues [cron]
|
||||||
|
* use some criterion as number of days between creation, last update, labels, ...
|
||||||
|
|
||||||
|
|
||||||
|
### [Myrmica Aloba](https://github.com/containous/aloba)
|
||||||
|
|
||||||
|
**Manage GitHub labels**
|
||||||
|
|
||||||
|
* Add labels on new PR [GitHub WebHook]
|
||||||
|
* Add and remove `contributor/waiting-for-corrections` label when a review request changes [GitHub WebHook]
|
||||||
|
* Weekly report of PR status on Slack (CaptainPR) [cron]
|
||||||
|
|
||||||
|
|
||||||
|
## Labels
|
||||||
|
|
||||||
|
If we open/look an issue/PR, we must add a `kind/*` and an `area/*`.
|
||||||
|
|
||||||
|
### Contributor
|
||||||
|
|
||||||
|
* `contributor/need-more-information`: we need more information from the contributor in order to analyze a problem.
|
||||||
|
* `contributor/waiting-for-feedback`: we need the contributor to give us feedback.
|
||||||
|
* `contributor/waiting-for-corrections`: we need the contributor to take actions in order to move forward with a PR. **(only for PR)**
|
||||||
|
* `contributor/needs-resolve-conflicts`: use it only when there is some conflicts (and an automatic rebase is not possible). **(only for PR)** _[bot, humans]_
|
||||||
|
|
||||||
|
### Kind
|
||||||
|
|
||||||
|
* `kind/enhancement`: a new or improved feature.
|
||||||
|
* `kind/question`: It's a question. **(only for issue)**
|
||||||
|
* `kind/proposal`: proposal PR/issues need a public debate.
|
||||||
|
* _Proposal issues_ are design proposal that need to be refined with multiple contributors.
|
||||||
|
* _Proposal PRs_ are technical prototypes that need to be refined with multiple contributors.
|
||||||
|
|
||||||
|
* `kind/bug/possible`: if we need to analyze to understand if it's a bug or not. **(only for issues)** _[bot only]_
|
||||||
|
* `kind/bug/confirmed`: we are sure, it's a bug. **(only for issues)**
|
||||||
|
* `kind/bug/fix`: it's a bug fix. **(only for PR)**
|
||||||
|
|
||||||
|
### Resolution
|
||||||
|
|
||||||
|
* `resolution/duplicate`: it's a duplicate issue/PR.
|
||||||
|
* `resolution/declined`: Rule #1 of open-source: no is temporary, yes is forever.
|
||||||
|
* `WIP`: Work In Progress. **(only for PR)**
|
||||||
|
|
||||||
|
### Platform
|
||||||
|
|
||||||
|
* `platform/windows`: Windows related.
|
||||||
|
|
||||||
|
### Area
|
||||||
|
|
||||||
|
* `area/acme`: ACME related.
|
||||||
|
* `area/api`: Traefik API related.
|
||||||
|
* `area/authentication`: Authentication related.
|
||||||
|
* `area/cluster`: Traefik clustering related.
|
||||||
|
* `area/documentation`: regards improving/adding documentation.
|
||||||
|
* `area/infrastructure`: related to CI or Traefik building scripts.
|
||||||
|
* `area/healthcheck`: Health-check related.
|
||||||
|
* `area/logs`: Traefik logs related.
|
||||||
|
* `area/middleware`: Middleware related.
|
||||||
|
* `area/middleware/metrics`: Metrics related. (Prometheus, StatsD, ...)
|
||||||
|
* `area/oxy`: Oxy related.
|
||||||
|
* `area/provider`: related to all providers.
|
||||||
|
* `area/provider/boltdb`: Boltd DB related.
|
||||||
|
* `area/provider/consul`: Consul related.
|
||||||
|
* `area/provider/docker`: Docker and Swarm related.
|
||||||
|
* `area/provider/ecs`: ECS related.
|
||||||
|
* `area/provider/etcd`: Etcd related.
|
||||||
|
* `area/provider/eureka`: Eureka related.
|
||||||
|
* `area/provider/file`: file provider related.
|
||||||
|
* `area/provider/k8s`: Kubernetes related.
|
||||||
|
* `area/provider/marathon`: Marathon related.
|
||||||
|
* `area/provider/mesos`: Mesos related.
|
||||||
|
* `area/provider/rancher`: Rancher related.
|
||||||
|
* `area/provider/zk`: Zoo Keeper related.
|
||||||
|
* `area/sticky-session`: Sticky session related.
|
||||||
|
* `area/tls`: TLS related.
|
||||||
|
* `area/websocket`: WebSocket related.
|
||||||
|
* `area/webui`: Web UI related.
|
||||||
|
|
||||||
|
### Priority
|
||||||
|
|
||||||
|
* `priority/P0`: needs hot fix. **(only for issue)**
|
||||||
|
* `priority/P1`: need to be fixed in next release. **(only for issue)**
|
||||||
|
* `priority/P2`: need to be fixed in the future. **(only for issue)**
|
||||||
|
* `priority/P3`: maybe. **(only for issue)**
|
||||||
|
|
||||||
|
### PR size
|
||||||
|
|
||||||
|
* `size/S`: small PR. **(only for PR)** _[bot only]_
|
||||||
|
* `size/M`: medium PR. **(only for PR)** _[bot only]_
|
||||||
|
* `size/L`: Large PR. **(only for PR)** _[bot only]_
|
||||||
|
|
||||||
|
### Status - Workflow
|
||||||
|
|
||||||
|
The `status/*` labels represent the desired state in the workflow.
|
||||||
|
|
||||||
|
* `status/0-needs-triage`: all new issue or PR have this status. _[bot only]_
|
||||||
|
* `status/1-needs-design-review`: need a design review. **(only for PR)**
|
||||||
|
* `status/2-needs-review`: need a code/documentation review. **(only for PR)**
|
||||||
|
* `status/3-needs-merge`: ready to merge. **(only for PR)**
|
||||||
|
* `status/4-merge-in-progress`: merge in progress. _[bot only]_
|
||||||
58
Makefile
58
Makefile
@@ -5,20 +5,28 @@ TRAEFIK_ENVS := \
|
|||||||
-e OS_PLATFORM_ARG \
|
-e OS_PLATFORM_ARG \
|
||||||
-e TESTFLAGS \
|
-e TESTFLAGS \
|
||||||
-e VERBOSE \
|
-e VERBOSE \
|
||||||
-e VERSION
|
-e VERSION \
|
||||||
|
-e CODENAME \
|
||||||
|
-e TESTDIRS \
|
||||||
|
-e CI \
|
||||||
|
-e CONTAINER=DOCKER # Indicator for integration tests that we are running inside a container.
|
||||||
|
|
||||||
SRCS = $(shell git ls-files '*.go' | grep -v '^external/')
|
SRCS = $(shell git ls-files '*.go' | grep -v '^vendor/')
|
||||||
|
|
||||||
BIND_DIR := "dist"
|
BIND_DIR := "dist"
|
||||||
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/containous/traefik/$(BIND_DIR)"
|
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/containous/traefik/$(BIND_DIR)"
|
||||||
|
|
||||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
GIT_BRANCH := $(subst heads/,,$(shell git rev-parse --abbrev-ref HEAD 2>/dev/null))
|
||||||
TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(subst /,-,$(GIT_BRANCH)))
|
||||||
REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
|
REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
|
||||||
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
|
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
|
||||||
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock")
|
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock")
|
||||||
|
|
||||||
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
|
DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",)
|
||||||
|
DOCKER_RUN_OPTS := $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
|
||||||
|
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(DOCKER_RUN_OPTS)
|
||||||
|
DOCKER_RUN_TRAEFIK_NOTTY := docker run $(INTEGRATION_OPTS) -i $(DOCKER_RUN_OPTS)
|
||||||
|
|
||||||
|
|
||||||
print-%: ; @echo $*=$($*)
|
print-%: ; @echo $*=$($*)
|
||||||
|
|
||||||
@@ -33,6 +41,24 @@ binary: generate-webui build ## build the linux binary
|
|||||||
crossbinary: generate-webui build ## cross build the non-linux binaries
|
crossbinary: generate-webui build ## cross build the non-linux binaries
|
||||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate crossbinary
|
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate crossbinary
|
||||||
|
|
||||||
|
crossbinary-parallel:
|
||||||
|
$(MAKE) generate-webui
|
||||||
|
$(MAKE) build crossbinary-default crossbinary-others
|
||||||
|
|
||||||
|
crossbinary-default: generate-webui build
|
||||||
|
$(DOCKER_RUN_TRAEFIK_NOTTY) ./script/make.sh generate crossbinary-default
|
||||||
|
|
||||||
|
crossbinary-default-parallel:
|
||||||
|
$(MAKE) generate-webui
|
||||||
|
$(MAKE) build crossbinary-default
|
||||||
|
|
||||||
|
crossbinary-others: generate-webui build
|
||||||
|
$(DOCKER_RUN_TRAEFIK_NOTTY) ./script/make.sh generate crossbinary-others
|
||||||
|
|
||||||
|
crossbinary-others-parallel:
|
||||||
|
$(MAKE) generate-webui
|
||||||
|
$(MAKE) build crossbinary-others
|
||||||
|
|
||||||
test: build ## run the unit and integration tests
|
test: build ## run the unit and integration tests
|
||||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit binary test-integration
|
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit binary test-integration
|
||||||
|
|
||||||
@@ -40,13 +66,13 @@ test-unit: build ## run the unit tests
|
|||||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit
|
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit
|
||||||
|
|
||||||
test-integration: build ## run the integration tests
|
test-integration: build ## run the integration tests
|
||||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-integration
|
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary test-integration
|
||||||
|
|
||||||
validate: build ## validate gofmt, golint and go vet
|
validate: build ## validate gofmt, golint and go vet
|
||||||
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
|
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-glide validate-gofmt validate-govet validate-golint validate-misspell validate-vendor
|
||||||
|
|
||||||
build: dist
|
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:
|
build-webui:
|
||||||
docker build -t traefik-webui -f webui/Dockerfile webui
|
docker build -t traefik-webui -f webui/Dockerfile webui
|
||||||
@@ -57,21 +83,27 @@ build-no-cache: dist
|
|||||||
shell: build ## start a shell inside the build env
|
shell: build ## start a shell inside the build env
|
||||||
$(DOCKER_RUN_TRAEFIK) /bin/bash
|
$(DOCKER_RUN_TRAEFIK) /bin/bash
|
||||||
|
|
||||||
image: build ## build a docker traefik image
|
image-dirty: binary ## build a docker traefik image
|
||||||
docker build -t $(TRAEFIK_IMAGE) .
|
docker build -t $(TRAEFIK_IMAGE) .
|
||||||
|
|
||||||
|
image: clear-static binary ## clean up static directory and build a docker traefik image
|
||||||
|
docker build -t $(TRAEFIK_IMAGE) .
|
||||||
|
|
||||||
|
clear-static:
|
||||||
|
rm -rf static
|
||||||
|
|
||||||
dist:
|
dist:
|
||||||
mkdir dist
|
mkdir dist
|
||||||
|
|
||||||
run-dev:
|
run-dev:
|
||||||
go generate
|
go generate
|
||||||
go build
|
go build ./cmd/traefik
|
||||||
./traefik
|
./traefik
|
||||||
|
|
||||||
generate-webui: build-webui
|
generate-webui: build-webui
|
||||||
if [ ! -d "static" ]; then \
|
if [ ! -d "static" ]; then \
|
||||||
mkdir -p static; \
|
mkdir -p static; \
|
||||||
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui gulp; \
|
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui npm run build; \
|
||||||
echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md; \
|
echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -81,8 +113,8 @@ lint:
|
|||||||
fmt:
|
fmt:
|
||||||
gofmt -s -l -w $(SRCS)
|
gofmt -s -l -w $(SRCS)
|
||||||
|
|
||||||
deploy:
|
pull-images:
|
||||||
./script/deploy.sh
|
grep --no-filename -E '^\s+image:' ./integration/resources/compose/*.yml | awk '{print $$2}' | sort | uniq | xargs -P 6 -n 1 docker pull
|
||||||
|
|
||||||
help: ## this help
|
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)
|
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||||
|
|||||||
181
README.md
181
README.md
@@ -1,19 +1,38 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="docs/img/traefik.logo.png" alt="Træfɪk" title="Træfɪk" />
|
<img src="docs/img/traefik.logo.png" alt="Træfik" title="Træfik" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://travis-ci.org/containous/traefik)
|
[](https://semaphoreci.com/containous/traefik)
|
||||||
[](https://docs.traefik.io)
|
[](https://docs.traefik.io)
|
||||||
[](http://goreportcard.com/report/containous/traefik)
|
[](http://goreportcard.com/report/containous/traefik)
|
||||||
[](https://imagelayers.io/?images=traefik)
|
[](https://microbadger.com/images/traefik)
|
||||||
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
||||||
[](https://traefik.herokuapp.com)
|
[](https://traefik.herokuapp.com)
|
||||||
[](https://twitter.com/intent/follow?screen_name=traefikproxy)
|
[](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.
|
Træfik (pronounced like [traffic](https://speak-ipa.bearbin.net/speak.cgi?speak=%CB%88tr%C3%A6f%C9%AAk)) is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||||
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Kubernetes](http://kubernetes.io/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
|
It supports several backends ([Docker](https://www.docker.com/), [Swarm mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), and a lot more) to manage its configuration automatically and dynamically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
. **[Overview](#overview)** .
|
||||||
|
**[Features](#features)** .
|
||||||
|
**[Supported backends](#supported-backends)** .
|
||||||
|
**[Quickstart](#quickstart)** .
|
||||||
|
**[Web UI](#web-ui)** .
|
||||||
|
**[Test it](#test-it)** .
|
||||||
|
**[Documentation](#documentation)** .
|
||||||
|
|
||||||
|
. **[Support](#support)** .
|
||||||
|
**[Release cycle](#release-cycle)** .
|
||||||
|
**[Contributing](#contributing)** .
|
||||||
|
**[Maintainers](#maintainers)** .
|
||||||
|
**[Plumbing](#plumbing)** .
|
||||||
|
**[Credits](#credits)** .
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -28,69 +47,78 @@ But a microservices architecture is dynamic... Services are added, removed, kill
|
|||||||
|
|
||||||
Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily.
|
Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily.
|
||||||
|
|
||||||
Here enters Træfɪk.
|
Here enters Træfik.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
Træfik 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.
|
Routes to your services will be created instantly.
|
||||||
|
|
||||||
Run it and forget it!
|
Run it and forget it!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- [It's fast](http://docs.traefik.io/benchmarks)
|
- [It's fast](https://docs.traefik.io/benchmarks)
|
||||||
- No dependency hell, single binary made with go
|
- No dependency hell, single binary made with go
|
||||||
|
- [Tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image
|
||||||
- Rest API
|
- Rest API
|
||||||
- Multiple backends supported: Docker, Mesos/Marathon, Consul, Etcd, and more to come
|
|
||||||
- Watchers for backends, can listen change in backends to apply a new configuration automatically
|
|
||||||
- Hot-reloading of configuration. No need to restart the process
|
- Hot-reloading of configuration. No need to restart the process
|
||||||
- Graceful shutdown http connections
|
- Circuit breakers, retry
|
||||||
- Circuit breakers on backends
|
|
||||||
- Round Robin, rebalancer load-balancers
|
- Round Robin, rebalancer load-balancers
|
||||||
- Rest Metrics
|
- Metrics (Rest, Prometheus, Datadog, Statd)
|
||||||
- [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
|
- Clean AngularJS Web UI
|
||||||
- Websocket support
|
- Websocket, HTTP/2, GRPC ready
|
||||||
- HTTP/2 support
|
- Access Logs (JSON, CLF)
|
||||||
- Retry request if network error
|
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS with renewal)
|
||||||
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS)
|
- [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support
|
||||||
|
- High Availability with cluster mode (beta)
|
||||||
|
|
||||||
## Demo
|
## Supported backends
|
||||||
|
|
||||||
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.
|
- [Docker](https://www.docker.com/) / [Swarm mode](https://docs.docker.com/engine/swarm/)
|
||||||
|
- [Kubernetes](https://kubernetes.io)
|
||||||
|
- [Mesos](https://github.com/apache/mesos) / [Marathon](https://mesosphere.github.io/marathon/)
|
||||||
|
- [Rancher](https://rancher.com) (API, Metadata)
|
||||||
|
- [Consul](https://www.consul.io/) / [Etcd](https://coreos.com/etcd/) / [Zookeeper](https://zookeeper.apache.org) / [BoltDB](https://github.com/boltdb/bolt)
|
||||||
|
- [Eureka](https://github.com/Netflix/eureka)
|
||||||
|
- [Amazon ECS](https://aws.amazon.com/ecs)
|
||||||
|
- [Amazon DynamoDB](https://aws.amazon.com/dynamodb)
|
||||||
|
- File
|
||||||
|
- Rest API
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
You can have a quick look at Træfik in this [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers. If you are looking for a more comprehensive and real use-case example, you can also check [Play-With-Docker](http://training.play-with-docker.com/traefik-load-balancing/) to see how to load balance between multiple nodes.
|
||||||
|
|
||||||
|
Here is a talk given by [Emile Vauge](https://github.com/emilevauge) at [GopherCon 2017](https://gophercon.com/).
|
||||||
|
You will learn Træfik basics in less than 10 minutes.
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=RgudiksfL-k)
|
||||||
|
|
||||||
|
Here is a talk given by [Ed Robinson](https://github.com/errm) at [ContainerCamp UK](https://container.camp) conference.
|
||||||
|
You will learn fundamental Træfik features and see some demos with Kubernetes.
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=aFtpIShV60I)
|
||||||
|
|
||||||
[](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko)
|
|
||||||
|
|
||||||
## Web UI
|
## Web UI
|
||||||
|
|
||||||
You can access to a simple HTML frontend of Træfik.
|
You can access the simple HTML frontend of Træfik.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
## Plumbing
|
|
||||||
|
|
||||||
- [Oxy](https://github.com/vulcand/oxy): an awesome proxy library made by Mailgun guys
|
## Test it
|
||||||
- [Gorilla mux](https://github.com/gorilla/mux): famous request router
|
|
||||||
- [Negroni](https://github.com/codegangsta/negroni): web middlewares made simple
|
|
||||||
- [Manners](https://github.com/mailgun/manners): graceful shutdown of http.Handler servers
|
|
||||||
- [Lego](https://github.com/xenolf/lego): the best [Let's Encrypt](https://letsencrypt.org) library in go
|
|
||||||
|
|
||||||
## Quick start
|
|
||||||
|
|
||||||
- The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
- The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./traefik -c traefik.toml
|
./traefik --configFile=traefik.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
- Use the tiny Docker image:
|
- Use the tiny Docker image and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
|
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
|
||||||
@@ -102,42 +130,65 @@ docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.to
|
|||||||
git clone https://github.com/containous/traefik
|
git clone https://github.com/containous/traefik
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
You can find the complete documentation [here](https://docs.traefik.io).
|
You can find the complete documentation at [https://docs.traefik.io](https://docs.traefik.io).
|
||||||
|
A collection of contributions around Træfik can be found at [https://awesome.traefik.io](https://awesome.traefik.io).
|
||||||
|
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
To get basic support, you can:
|
||||||
|
- join the Træfik community Slack channel: [](https://traefik.herokuapp.com)
|
||||||
|
- use [Stack Overflow](https://stackoverflow.com/questions/tagged/traefik) (using the `traefik` tag)
|
||||||
|
|
||||||
|
If you prefer commercial support, please contact [containo.us](https://containo.us) by mail: <mailto:support@containo.us>.
|
||||||
|
|
||||||
|
|
||||||
|
## Release cycle
|
||||||
|
|
||||||
|
- Release: We try to release a new version every 2 months
|
||||||
|
- i.e.: 1.3.0, 1.4.0, 1.5.0
|
||||||
|
- Release candidate: we do RC (1.**x**.0-rc**y**) before the final release (1.**x**.0)
|
||||||
|
- i.e.: 1.1.0-rc1 -> 1.1.0-rc2 -> 1.1.0-rc3 -> 1.1.0-rc4 -> 1.1.0
|
||||||
|
- Bug-fixes: For each version we release bug fixes
|
||||||
|
- i.e.: 1.1.1, 1.1.2, 1.1.3
|
||||||
|
- those versions contain only bug-fixes
|
||||||
|
- no additional features are delivered in those versions
|
||||||
|
- Each version is supported until the next one is released
|
||||||
|
- i.e.: 1.1.x will be supported until 1.2.0 is out
|
||||||
|
- We use [Semantic Versioning](http://semver.org/)
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Please refer to [this section](.github/CONTRIBUTING.md).
|
Please refer to [contributing documentation](CONTRIBUTING.md).
|
||||||
|
|
||||||
## 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 [](https://traefik.herokuapp.com)
|
|
||||||
|
|
||||||
- Project [Mantl](https://mantl.io/) from Cisco
|
|
||||||
|
|
||||||

|
|
||||||
> Mantl is a modern platform for rapidly deploying globally distributed services. A container orchestrator, docker, a network stack, something to pool your logs, something to monitor health, a sprinkle of service discovery and some automation.
|
|
||||||
|
|
||||||
- Project [Apollo](http://capgemini.github.io/devops/apollo/) from Cap Gemini
|
|
||||||
|
|
||||||

|
|
||||||
> Apollo is an open source project to aid with building and deploying IAAS and PAAS services. It is particularly geared towards managing containerized applications across multiple hosts, and big data type workloads. Apollo leverages other open source components to provide basic mechanisms for deployment, maintenance, and scaling of infrastructure and applications.
|
|
||||||
|
|
||||||
## Partners
|
|
||||||
|
|
||||||
[](https://zenika.com)
|
|
||||||
|
|
||||||
Zenika is one of the leading providers of professional Open Source services and agile methodologies in
|
|
||||||
Europe. We provide consulting, development, training and support for the world’s leading Open Source
|
|
||||||
software products.
|
|
||||||
|
|
||||||
|
|
||||||
[](https://aster.is)
|
### Code of Conduct
|
||||||
|
|
||||||
|
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md).
|
||||||
|
By participating in this project you agree to abide by its terms.
|
||||||
|
|
||||||
|
|
||||||
|
## Maintainers
|
||||||
|
|
||||||
|
[Information about process and maintainers](MAINTAINER.md)
|
||||||
|
|
||||||
|
|
||||||
|
## Plumbing
|
||||||
|
|
||||||
|
- [Oxy](https://github.com/vulcand/oxy): an awesome proxy library made by Mailgun folks
|
||||||
|
- [Gorilla mux](https://github.com/gorilla/mux): famous request router
|
||||||
|
- [Negroni](https://github.com/urfave/negroni): web middlewares made simple
|
||||||
|
- [Lego](https://github.com/xenolf/lego): the best [Let's Encrypt](https://letsencrypt.org) library in go
|
||||||
|
|
||||||
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.
|
|
||||||
.
|
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo 
|
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo .
|
||||||
|
Traefik's logo licensed under the Creative Commons 3.0 Attributions license.
|
||||||
|
|
||||||
|
Traefik's logo was inspired by the gopher stickers made by Takuya Ueda (https://twitter.com/tenntenn).
|
||||||
|
The original Go gopher was designed by Renee French (http://reneefrench.blogspot.com/).
|
||||||
|
|||||||
245
acme/account.go
Normal file
245
acme/account.go
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Account is used to store lets encrypt registration info
|
||||||
|
type Account struct {
|
||||||
|
Email string
|
||||||
|
Registration *acme.RegistrationResource
|
||||||
|
PrivateKey []byte
|
||||||
|
DomainsCertificate DomainsCertificates
|
||||||
|
ChallengeCerts map[string]*ChallengeCert
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChallengeCert stores a challenge certificate
|
||||||
|
type ChallengeCert struct {
|
||||||
|
Certificate []byte
|
||||||
|
PrivateKey []byte
|
||||||
|
certificate *tls.Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init inits account struct
|
||||||
|
func (a *Account) Init() error {
|
||||||
|
err := a.DomainsCertificate.Init()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cert := range a.ChallengeCerts {
|
||||||
|
if cert.certificate == nil {
|
||||||
|
certificate, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert.certificate = &certificate
|
||||||
|
}
|
||||||
|
if cert.certificate.Leaf == nil {
|
||||||
|
leaf, err := x509.ParseCertificate(cert.certificate.Certificate[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert.certificate.Leaf = leaf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAccount creates an account
|
||||||
|
func NewAccount(email string) (*Account, error) {
|
||||||
|
// Create a user. New accounts need an email and private key to start
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
domainsCerts := DomainsCertificates{Certs: []*DomainsCertificate{}}
|
||||||
|
domainsCerts.Init()
|
||||||
|
return &Account{
|
||||||
|
Email: email,
|
||||||
|
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||||
|
DomainsCertificate: DomainsCertificates{Certs: domainsCerts.Certs},
|
||||||
|
ChallengeCerts: map[string]*ChallengeCert{}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmail returns email
|
||||||
|
func (a *Account) GetEmail() string {
|
||||||
|
return a.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRegistration returns lets encrypt registration resource
|
||||||
|
func (a *Account) GetRegistration() *acme.RegistrationResource {
|
||||||
|
return a.Registration
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPrivateKey returns private key
|
||||||
|
func (a *Account) GetPrivateKey() crypto.PrivateKey {
|
||||||
|
if privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey); err == nil {
|
||||||
|
return privateKey
|
||||||
|
}
|
||||||
|
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certificate is used to store certificate info
|
||||||
|
type Certificate struct {
|
||||||
|
Domain string
|
||||||
|
CertURL string
|
||||||
|
CertStableURL string
|
||||||
|
PrivateKey []byte
|
||||||
|
Certificate []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainsCertificates stores a certificate for multiple domains
|
||||||
|
type DomainsCertificates struct {
|
||||||
|
Certs []*DomainsCertificate
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificates) Len() int {
|
||||||
|
return len(dc.Certs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificates) Swap(i, j int) {
|
||||||
|
dc.Certs[i], dc.Certs[j] = dc.Certs[j], dc.Certs[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificates) Less(i, j int) bool {
|
||||||
|
if reflect.DeepEqual(dc.Certs[i].Domains, dc.Certs[j].Domains) {
|
||||||
|
return dc.Certs[i].tlsCert.Leaf.NotAfter.After(dc.Certs[j].tlsCert.Leaf.NotAfter)
|
||||||
|
}
|
||||||
|
if dc.Certs[i].Domains.Main == dc.Certs[j].Domains.Main {
|
||||||
|
return strings.Join(dc.Certs[i].Domains.SANs, ",") < strings.Join(dc.Certs[j].Domains.SANs, ",")
|
||||||
|
}
|
||||||
|
return dc.Certs[i].Domains.Main < dc.Certs[j].Domains.Main
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificates) removeDuplicates() {
|
||||||
|
sort.Sort(dc)
|
||||||
|
for i := 0; i < len(dc.Certs); i++ {
|
||||||
|
for i2 := i + 1; i2 < len(dc.Certs); i2++ {
|
||||||
|
if reflect.DeepEqual(dc.Certs[i].Domains, dc.Certs[i2].Domains) {
|
||||||
|
// delete
|
||||||
|
log.Warnf("Remove duplicate cert: %+v, expiration :%s", dc.Certs[i2].Domains, dc.Certs[i2].tlsCert.Leaf.NotAfter.String())
|
||||||
|
dc.Certs = append(dc.Certs[:i2], dc.Certs[i2+1:]...)
|
||||||
|
i2--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init inits DomainsCertificates
|
||||||
|
func (dc *DomainsCertificates) Init() error {
|
||||||
|
dc.lock.Lock()
|
||||||
|
defer dc.lock.Unlock()
|
||||||
|
for _, domainsCertificate := range dc.Certs {
|
||||||
|
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
domainsCertificate.tlsCert = &tlsCert
|
||||||
|
if domainsCertificate.tlsCert.Leaf == nil {
|
||||||
|
leaf, err := x509.ParseCertificate(domainsCertificate.tlsCert.Certificate[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
domainsCertificate.tlsCert.Leaf = leaf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dc.removeDuplicates()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain Domain) error {
|
||||||
|
dc.lock.Lock()
|
||||||
|
defer dc.lock.Unlock()
|
||||||
|
|
||||||
|
for _, domainsCertificate := range dc.Certs {
|
||||||
|
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
|
||||||
|
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
domainsCertificate.Certificate = acmeCert
|
||||||
|
domainsCertificate.tlsCert = &tlsCert
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("certificate to renew not found for domain %s", domain.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) {
|
||||||
|
dc.lock.Lock()
|
||||||
|
defer dc.lock.Unlock()
|
||||||
|
|
||||||
|
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
|
||||||
|
dc.Certs = append(dc.Certs, &cert)
|
||||||
|
return &cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*DomainsCertificate, bool) {
|
||||||
|
dc.lock.RLock()
|
||||||
|
defer dc.lock.RUnlock()
|
||||||
|
for _, domainsCertificate := range dc.Certs {
|
||||||
|
domains := []string{}
|
||||||
|
domains = append(domains, domainsCertificate.Domains.Main)
|
||||||
|
domains = append(domains, domainsCertificate.Domains.SANs...)
|
||||||
|
for _, domain := range domains {
|
||||||
|
if domain == domainToFind {
|
||||||
|
return domainsCertificate, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, bool) {
|
||||||
|
dc.lock.RLock()
|
||||||
|
defer dc.lock.RUnlock()
|
||||||
|
for _, domainsCertificate := range dc.Certs {
|
||||||
|
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
|
||||||
|
return domainsCertificate, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainsCertificate contains a certificate for multiple domains
|
||||||
|
type DomainsCertificate struct {
|
||||||
|
Domains Domain
|
||||||
|
Certificate *Certificate
|
||||||
|
tlsCert *tls.Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificate) needRenew() bool {
|
||||||
|
for _, c := range dc.tlsCert.Certificate {
|
||||||
|
crt, err := x509.ParseCertificate(c)
|
||||||
|
if err != nil {
|
||||||
|
// If there's an error, we assume the cert is broken, and needs update
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// <= 30 days left, renew certificate
|
||||||
|
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 30 * time.Hour))) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
845
acme/acme.go
845
acme/acme.go
@@ -1,173 +1,89 @@
|
|||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/containous/traefik/safe"
|
|
||||||
"github.com/xenolf/lego/acme"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
fmtlog "log"
|
fmtlog "log"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"regexp"
|
||||||
"sync"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/ty/fun"
|
||||||
|
"github.com/cenk/backoff"
|
||||||
|
"github.com/containous/staert"
|
||||||
|
"github.com/containous/traefik/cluster"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
"github.com/eapache/channels"
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"github.com/xenolf/lego/providers/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Account is used to store lets encrypt registration info
|
var (
|
||||||
type Account struct {
|
// OSCPMustStaple enables OSCP stapling as from https://github.com/xenolf/lego/issues/270
|
||||||
Email string
|
OSCPMustStaple = false
|
||||||
Registration *acme.RegistrationResource
|
)
|
||||||
PrivateKey []byte
|
|
||||||
DomainsCertificate DomainsCertificates
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEmail returns email
|
|
||||||
func (a Account) GetEmail() string {
|
|
||||||
return a.Email
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRegistration returns lets encrypt registration resource
|
|
||||||
func (a Account) GetRegistration() *acme.RegistrationResource {
|
|
||||||
return a.Registration
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPrivateKey returns private key
|
|
||||||
func (a Account) GetPrivateKey() crypto.PrivateKey {
|
|
||||||
if privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey); err == nil {
|
|
||||||
return privateKey
|
|
||||||
}
|
|
||||||
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Certificate is used to store certificate info
|
|
||||||
type Certificate struct {
|
|
||||||
Domain string
|
|
||||||
CertURL string
|
|
||||||
CertStableURL string
|
|
||||||
PrivateKey []byte
|
|
||||||
Certificate []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainsCertificates stores a certificate for multiple domains
|
|
||||||
type DomainsCertificates struct {
|
|
||||||
Certs []*DomainsCertificate
|
|
||||||
lock *sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *DomainsCertificates) init() error {
|
|
||||||
if dc.lock == nil {
|
|
||||||
dc.lock = &sync.RWMutex{}
|
|
||||||
}
|
|
||||||
dc.lock.Lock()
|
|
||||||
defer dc.lock.Unlock()
|
|
||||||
for _, domainsCertificate := range dc.Certs {
|
|
||||||
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
domainsCertificate.tlsCert = &tlsCert
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain Domain) error {
|
|
||||||
dc.lock.Lock()
|
|
||||||
defer dc.lock.Unlock()
|
|
||||||
|
|
||||||
for _, domainsCertificate := range dc.Certs {
|
|
||||||
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
|
|
||||||
domainsCertificate.Certificate = acmeCert
|
|
||||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
domainsCertificate.tlsCert = &tlsCert
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.New("Certificate to renew not found for domain " + domain.Main)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) {
|
|
||||||
dc.lock.Lock()
|
|
||||||
defer dc.lock.Unlock()
|
|
||||||
|
|
||||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
|
|
||||||
dc.Certs = append(dc.Certs, &cert)
|
|
||||||
return &cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*DomainsCertificate, bool) {
|
|
||||||
dc.lock.RLock()
|
|
||||||
defer dc.lock.RUnlock()
|
|
||||||
for _, domainsCertificate := range dc.Certs {
|
|
||||||
domains := []string{}
|
|
||||||
domains = append(domains, domainsCertificate.Domains.Main)
|
|
||||||
domains = append(domains, domainsCertificate.Domains.SANs...)
|
|
||||||
for _, domain := range domains {
|
|
||||||
if domain == domainToFind {
|
|
||||||
return domainsCertificate, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, bool) {
|
|
||||||
dc.lock.RLock()
|
|
||||||
defer dc.lock.RUnlock()
|
|
||||||
for _, domainsCertificate := range dc.Certs {
|
|
||||||
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
|
|
||||||
return domainsCertificate, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainsCertificate contains a certificate for multiple domains
|
|
||||||
type DomainsCertificate struct {
|
|
||||||
Domains Domain
|
|
||||||
Certificate *Certificate
|
|
||||||
tlsCert *tls.Certificate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *DomainsCertificate) needRenew() bool {
|
|
||||||
for _, c := range dc.tlsCert.Certificate {
|
|
||||||
crt, err := x509.ParseCertificate(c)
|
|
||||||
if err != nil {
|
|
||||||
// If there's an error, we assume the cert is broken, and needs update
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// <= 7 days left, renew certificate
|
|
||||||
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 7 * time.Hour))) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ACME allows to connect to lets encrypt and retrieve certs
|
// ACME allows to connect to lets encrypt and retrieve certs
|
||||||
type ACME struct {
|
type ACME struct {
|
||||||
Email string
|
Email string `description:"Email address used for registration"`
|
||||||
Domains []Domain
|
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
|
Storage string `description:"File or key used for certificates storage."`
|
||||||
OnDemand bool
|
StorageFile string // deprecated
|
||||||
CAServer string
|
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."`
|
||||||
EntryPoint string
|
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
|
||||||
storageLock sync.RWMutex
|
CAServer string `description:"CA server to use."`
|
||||||
|
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
||||||
|
DNSProvider string `description:"Use a DNS based challenge provider rather than HTTPS."`
|
||||||
|
DelayDontCheckDNS int `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
|
||||||
|
ACMELogging bool `description:"Enable debug logging of ACME actions."`
|
||||||
|
client *acme.Client
|
||||||
|
defaultCertificate *tls.Certificate
|
||||||
|
store cluster.Store
|
||||||
|
challengeProvider *challengeProvider
|
||||||
|
checkOnDemandDomain func(domain string) bool
|
||||||
|
jobs *channels.InfiniteChannel
|
||||||
|
TLSConfig *tls.Config `description:"TLS config in case wildcard certs are used"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//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
|
// Domain holds a domain name with SANs
|
||||||
@@ -176,60 +92,193 @@ type Domain struct {
|
|||||||
SANs []string
|
SANs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateConfig creates a tls.config from using ACME configuration
|
func (a *ACME) init() error {
|
||||||
func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(domain string) bool) error {
|
if a.ACMELogging {
|
||||||
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
acme.Logger = fmtlog.New(os.Stderr, "legolog: ", fmtlog.LstdFlags)
|
||||||
|
|
||||||
if len(a.StorageFile) == 0 {
|
|
||||||
return errors.New("Empty StorageFile, please provide a filename for certs storage")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Generating default certificate...")
|
|
||||||
if len(tlsConfig.Certificates) == 0 {
|
|
||||||
// no certificates in TLS config, so we add a default one
|
|
||||||
cert, err := generateDefaultCertificate()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tlsConfig.Certificates = append(tlsConfig.Certificates, *cert)
|
|
||||||
}
|
|
||||||
var account *Account
|
|
||||||
var needRegister bool
|
|
||||||
|
|
||||||
// if certificates in storage, load them
|
|
||||||
if fileInfo, err := os.Stat(a.StorageFile); err == nil && fileInfo.Size() != 0 {
|
|
||||||
log.Infof("Loading ACME certificates...")
|
|
||||||
// load account
|
|
||||||
account, err = a.loadAccount(a)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.Infof("Generating ACME Account...")
|
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
||||||
// Create a user. New accounts need an email and private key to start
|
|
||||||
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
account = &Account{
|
|
||||||
Email: a.Email,
|
|
||||||
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
|
|
||||||
}
|
|
||||||
account.DomainsCertificate = DomainsCertificates{Certs: []*DomainsCertificate{}, lock: &sync.RWMutex{}}
|
|
||||||
needRegister = true
|
|
||||||
}
|
}
|
||||||
|
// no certificates in TLS config, so we add a default one
|
||||||
client, err := a.buildACMEClient(account)
|
cert, err := generateDefaultCertificate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.defaultCertificate = cert
|
||||||
|
// TODO: to remove in the futurs
|
||||||
|
if len(a.StorageFile) > 0 && len(a.Storage) == 0 {
|
||||||
|
log.Warn("ACME.StorageFile is deprecated, use ACME.Storage instead")
|
||||||
|
a.Storage = a.StorageFile
|
||||||
|
}
|
||||||
|
a.jobs = channels.NewInfiniteChannel()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateClusterConfig creates a tls.config using ACME configuration in cluster mode
|
||||||
|
func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tls.Config, checkOnDemandDomain func(domain string) bool) error {
|
||||||
|
err := a.init()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(a.Storage) == 0 {
|
||||||
|
return errors.New("Empty Store, please provide a key for certs storage")
|
||||||
|
}
|
||||||
|
a.checkOnDemandDomain = checkOnDemandDomain
|
||||||
|
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
|
||||||
|
tlsConfig.GetCertificate = a.getCertificate
|
||||||
|
a.TLSConfig = tlsConfig
|
||||||
|
listener := func(object cluster.Object) error {
|
||||||
|
account := object.(*Account)
|
||||||
|
account.Init()
|
||||||
|
if !leadership.IsLeader() {
|
||||||
|
a.client, err = a.buildACMEClient(account)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error building ACME client %+v: %s", object, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
datastore, err := cluster.NewDataStore(
|
||||||
|
leadership.Pool.Ctx(),
|
||||||
|
staert.KvSource{
|
||||||
|
Store: leadership.Store,
|
||||||
|
Prefix: a.Storage,
|
||||||
|
},
|
||||||
|
&Account{},
|
||||||
|
listener)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.store = datastore
|
||||||
|
a.challengeProvider = &challengeProvider{store: a.store}
|
||||||
|
|
||||||
|
ticker := time.NewTicker(24 * time.Hour)
|
||||||
|
leadership.Pool.AddGoCtx(func(ctx context.Context) {
|
||||||
|
log.Info("Starting ACME renew job...")
|
||||||
|
defer log.Info("Stopped ACME renew job...")
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
a.renewCertificates()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
leadership.AddListener(func(elected bool) error {
|
||||||
|
if elected {
|
||||||
|
_, err := a.store.Load()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
transaction, object, err := a.store.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account := object.(*Account)
|
||||||
|
account.Init()
|
||||||
|
var needRegister bool
|
||||||
|
if account == nil || len(account.Email) == 0 {
|
||||||
|
account, err = NewAccount(a.Email)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
needRegister = true
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.client, err = a.buildACMEClient(account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if needRegister {
|
||||||
|
// New users will need to register; be sure to save it
|
||||||
|
log.Debug("Register...")
|
||||||
|
reg, err := a.client.Register()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account.Registration = reg
|
||||||
|
}
|
||||||
|
// The client has a URL to the current Let's Encrypt Subscriber
|
||||||
|
// Agreement. The user will need to agree to it.
|
||||||
|
log.Debug("AgreeToTOS...")
|
||||||
|
err = a.client.AgreeToTOS()
|
||||||
|
if err != nil {
|
||||||
|
// Let's Encrypt Subscriber Agreement renew ?
|
||||||
|
reg, err := a.client.QueryRegistration()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account.Registration = reg
|
||||||
|
err = a.client.AgreeToTOS()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = transaction.Commit(account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.retrieveCertificates()
|
||||||
|
a.renewCertificates()
|
||||||
|
a.runJobs()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateLocalConfig creates a tls.config using local ACME configuration
|
||||||
|
func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func(domain string) bool) error {
|
||||||
|
err := a.init()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(a.Storage) == 0 {
|
||||||
|
return errors.New("Empty Store, please provide a filename for certs storage")
|
||||||
|
}
|
||||||
|
a.checkOnDemandDomain = checkOnDemandDomain
|
||||||
|
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
|
||||||
|
tlsConfig.GetCertificate = a.getCertificate
|
||||||
|
a.TLSConfig = tlsConfig
|
||||||
|
localStore := NewLocalStore(a.Storage)
|
||||||
|
a.store = localStore
|
||||||
|
a.challengeProvider = &challengeProvider{store: a.store}
|
||||||
|
|
||||||
|
var needRegister bool
|
||||||
|
var account *Account
|
||||||
|
|
||||||
|
if fileInfo, fileErr := os.Stat(a.Storage); fileErr == nil && fileInfo.Size() != 0 {
|
||||||
|
log.Info("Loading ACME Account...")
|
||||||
|
// load account
|
||||||
|
object, err := localStore.Load()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account = object.(*Account)
|
||||||
|
} else {
|
||||||
|
log.Info("Generating ACME Account...")
|
||||||
|
account, err = NewAccount(a.Email)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
needRegister = true
|
||||||
|
}
|
||||||
|
|
||||||
|
a.client, err = a.buildACMEClient(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
|
|
||||||
wrapperChallengeProvider := newWrapperChallengeProvider()
|
|
||||||
client.SetChallengeProvider(acme.TLSSNI01, wrapperChallengeProvider)
|
|
||||||
|
|
||||||
if needRegister {
|
if needRegister {
|
||||||
// New users will need to register; be sure to save it
|
// New users will need to register; be sure to save it
|
||||||
reg, err := client.Register()
|
log.Info("Register...")
|
||||||
|
reg, err := a.client.Register()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -238,176 +287,329 @@ func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(doma
|
|||||||
|
|
||||||
// The client has a URL to the current Let's Encrypt Subscriber
|
// The client has a URL to the current Let's Encrypt Subscriber
|
||||||
// Agreement. The user will need to agree to it.
|
// Agreement. The user will need to agree to it.
|
||||||
err = client.AgreeToTOS()
|
log.Debug("AgreeToTOS...")
|
||||||
|
err = a.client.AgreeToTOS()
|
||||||
|
if err != nil {
|
||||||
|
// Let's Encrypt Subscriber Agreement renew ?
|
||||||
|
reg, err := a.client.QueryRegistration()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account.Registration = reg
|
||||||
|
err = a.client.AgreeToTOS()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// save account
|
||||||
|
transaction, _, err := a.store.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = transaction.Commit(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
safe.Go(func() {
|
a.retrieveCertificates()
|
||||||
a.retrieveCertificates(client, account)
|
a.renewCertificates()
|
||||||
})
|
a.runJobs()
|
||||||
|
|
||||||
tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
||||||
if challengeCert, ok := wrapperChallengeProvider.getCertificate(clientHello.ServerName); ok {
|
|
||||||
return challengeCert, nil
|
|
||||||
}
|
|
||||||
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
|
||||||
return domainCert.tlsCert, nil
|
|
||||||
}
|
|
||||||
if a.OnDemand {
|
|
||||||
if CheckOnDemandDomain != nil && !CheckOnDemandDomain(clientHello.ServerName) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return a.loadCertificateOnDemand(client, account, clientHello)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ticker := time.NewTicker(24 * time.Hour)
|
ticker := time.NewTicker(24 * time.Hour)
|
||||||
safe.Go(func() {
|
safe.Go(func() {
|
||||||
for {
|
for range ticker.C {
|
||||||
select {
|
a.renewCertificates()
|
||||||
case <-ticker.C:
|
|
||||||
|
|
||||||
if err := a.renewCertificates(client, account); err != nil {
|
|
||||||
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) retrieveCertificates(client *acme.Client, account *Account) {
|
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
log.Infof("Retrieving ACME certificates...")
|
domain := types.CanonicalDomain(clientHello.ServerName)
|
||||||
for _, domain := range a.Domains {
|
account := a.store.Get().(*Account)
|
||||||
// check if cert isn't already loaded
|
|
||||||
if _, exists := account.DomainsCertificate.exists(domain); !exists {
|
if providedCertificate := a.getProvidedCertificate([]string{domain}); providedCertificate != nil {
|
||||||
domains := []string{}
|
return providedCertificate, nil
|
||||||
domains = append(domains, domain.Main)
|
}
|
||||||
domains = append(domains, domain.SANs...)
|
|
||||||
certificateResource, err := a.getDomainsCertificates(client, domains)
|
if challengeCert, ok := a.challengeProvider.getCertificate(domain); ok {
|
||||||
if err != nil {
|
log.Debugf("ACME got challenge %s", domain)
|
||||||
log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error())
|
return challengeCert, nil
|
||||||
continue
|
}
|
||||||
|
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok {
|
||||||
|
log.Debugf("ACME got domain cert %s", domain)
|
||||||
|
return domainCert.tlsCert, nil
|
||||||
|
}
|
||||||
|
if a.OnDemand {
|
||||||
|
if a.checkOnDemandDomain != nil && !a.checkOnDemandDomain(domain) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return a.loadCertificateOnDemand(clientHello)
|
||||||
|
}
|
||||||
|
log.Debugf("ACME got nothing %s", domain)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACME) retrieveCertificates() {
|
||||||
|
a.jobs.In() <- func() {
|
||||||
|
log.Info("Retrieving ACME certificates...")
|
||||||
|
for _, domain := range a.Domains {
|
||||||
|
// check if cert isn't already loaded
|
||||||
|
account := a.store.Get().(*Account)
|
||||||
|
if _, exists := account.DomainsCertificate.exists(domain); !exists {
|
||||||
|
domains := []string{}
|
||||||
|
domains = append(domains, domain.Main)
|
||||||
|
domains = append(domains, domain.SANs...)
|
||||||
|
certificateResource, err := a.getDomainsCertificates(domains)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
transaction, object, err := a.store.Begin()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error creating ACME store transaction from domain %s: %s", domain, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
account = object.(*Account)
|
||||||
|
_, err = account.DomainsCertificate.addCertificateForDomains(certificateResource, domain)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error adding ACME certificate for domain %s: %s", domains, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = transaction.Commit(account); err != nil {
|
||||||
|
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_, err = account.DomainsCertificate.addCertificateForDomains(certificateResource, domain)
|
}
|
||||||
if err != nil {
|
log.Info("Retrieved ACME certificates")
|
||||||
log.Errorf("Error adding ACME certificate for domain %s: %s", domains, err.Error())
|
}
|
||||||
continue
|
}
|
||||||
}
|
|
||||||
if err = a.saveAccount(account); err != nil {
|
func (a *ACME) renewCertificates() {
|
||||||
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
a.jobs.In() <- func() {
|
||||||
continue
|
log.Debug("Testing certificate renew...")
|
||||||
|
account := a.store.Get().(*Account)
|
||||||
|
for _, certificateResource := range account.DomainsCertificate.Certs {
|
||||||
|
if certificateResource.needRenew() {
|
||||||
|
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
|
||||||
|
renewedCert, err := a.client.RenewCertificate(acme.CertificateResource{
|
||||||
|
Domain: certificateResource.Certificate.Domain,
|
||||||
|
CertURL: certificateResource.Certificate.CertURL,
|
||||||
|
CertStableURL: certificateResource.Certificate.CertStableURL,
|
||||||
|
PrivateKey: certificateResource.Certificate.PrivateKey,
|
||||||
|
Certificate: certificateResource.Certificate.Certificate,
|
||||||
|
}, true, OSCPMustStaple)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error renewing certificate: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Debugf("Renewed certificate %+v", certificateResource.Domains)
|
||||||
|
renewedACMECert := &Certificate{
|
||||||
|
Domain: renewedCert.Domain,
|
||||||
|
CertURL: renewedCert.CertURL,
|
||||||
|
CertStableURL: renewedCert.CertStableURL,
|
||||||
|
PrivateKey: renewedCert.PrivateKey,
|
||||||
|
Certificate: renewedCert.Certificate,
|
||||||
|
}
|
||||||
|
transaction, object, err := a.store.Begin()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error renewing certificate: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
account = object.(*Account)
|
||||||
|
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error renewing certificate: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = transaction.Commit(account); err != nil {
|
||||||
|
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Infof("Retrieved ACME certificates")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
|
func dnsOverrideDelay(delay int) error {
|
||||||
for _, certificateResource := range account.DomainsCertificate.Certs {
|
var err error
|
||||||
if certificateResource.needRenew() {
|
if delay > 0 {
|
||||||
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
|
log.Debugf("Delaying %d seconds rather than validating DNS propagation", delay)
|
||||||
renewedCert, err := client.RenewCertificate(acme.CertificateResource{
|
acme.PreCheckDNS = func(_, _ string) (bool, error) {
|
||||||
Domain: certificateResource.Certificate.Domain,
|
time.Sleep(time.Duration(delay) * time.Second)
|
||||||
CertURL: certificateResource.Certificate.CertURL,
|
return true, nil
|
||||||
CertStableURL: certificateResource.Certificate.CertStableURL,
|
|
||||||
PrivateKey: certificateResource.Certificate.PrivateKey,
|
|
||||||
Certificate: certificateResource.Certificate.Certificate,
|
|
||||||
}, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debugf("Renewed certificate %+v", certificateResource.Domains)
|
|
||||||
renewedACMECert := &Certificate{
|
|
||||||
Domain: renewedCert.Domain,
|
|
||||||
CertURL: renewedCert.CertURL,
|
|
||||||
CertStableURL: renewedCert.CertStableURL,
|
|
||||||
PrivateKey: renewedCert.PrivateKey,
|
|
||||||
Certificate: renewedCert.Certificate,
|
|
||||||
}
|
|
||||||
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = a.saveAccount(account); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else if delay < 0 {
|
||||||
|
err = fmt.Errorf("Invalid negative DelayDontCheckDNS: %d", delay)
|
||||||
}
|
}
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) buildACMEClient(Account *Account) (*acme.Client, error) {
|
func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
|
||||||
|
log.Debug("Building ACME client...")
|
||||||
caServer := "https://acme-v01.api.letsencrypt.org/directory"
|
caServer := "https://acme-v01.api.letsencrypt.org/directory"
|
||||||
if len(a.CAServer) > 0 {
|
if len(a.CAServer) > 0 {
|
||||||
caServer = a.CAServer
|
caServer = a.CAServer
|
||||||
}
|
}
|
||||||
client, err := acme.NewClient(caServer, Account, acme.RSA4096)
|
client, err := acme.NewClient(caServer, account, acme.RSA4096)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(a.DNSProvider) > 0 {
|
||||||
|
log.Debugf("Using DNS Challenge provider: %s", a.DNSProvider)
|
||||||
|
|
||||||
|
err = dnsOverrideDelay(a.DelayDontCheckDNS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var provider acme.ChallengeProvider
|
||||||
|
provider, err = dns.NewDNSChallengeProviderByName(a.DNSProvider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01})
|
||||||
|
err = client.SetChallengeProvider(acme.DNS01, provider)
|
||||||
|
} else {
|
||||||
|
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
|
||||||
|
err = client.SetChallengeProvider(acme.TLSSNI01, a.challengeProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) loadCertificateOnDemand(client *acme.Client, Account *Account, clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
if certificateResource, ok := Account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
domain := types.CanonicalDomain(clientHello.ServerName)
|
||||||
|
account := a.store.Get().(*Account)
|
||||||
|
if certificateResource, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok {
|
||||||
return certificateResource.tlsCert, nil
|
return certificateResource.tlsCert, nil
|
||||||
}
|
}
|
||||||
Certificate, err := a.getDomainsCertificates(client, []string{clientHello.ServerName})
|
certificate, err := a.getDomainsCertificates([]string{domain})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Debugf("Got certificate on demand for domain %s", clientHello.ServerName)
|
log.Debugf("Got certificate on demand for domain %s", domain)
|
||||||
cert, err := Account.DomainsCertificate.addCertificateForDomains(Certificate, Domain{Main: clientHello.ServerName})
|
|
||||||
|
transaction, object, err := a.store.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err = a.saveAccount(Account); err != nil {
|
account = object.(*Account)
|
||||||
|
cert, err := account.DomainsCertificate.addCertificateForDomains(certificate, Domain{Main: domain})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = transaction.Commit(account); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return cert.tlsCert, nil
|
return cert.tlsCert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) loadAccount(acmeConfig *ACME) (*Account, error) {
|
// LoadCertificateForDomains loads certificates from ACME for given domains
|
||||||
a.storageLock.RLock()
|
func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||||
defer a.storageLock.RUnlock()
|
a.jobs.In() <- func() {
|
||||||
Account := Account{
|
log.Debugf("LoadCertificateForDomains %v...", domains)
|
||||||
DomainsCertificate: DomainsCertificates{},
|
|
||||||
|
if len(domains) == 0 {
|
||||||
|
// no domain
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
||||||
|
|
||||||
|
// Check provided certificates
|
||||||
|
if a.getProvidedCertificate(domains) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
operation := func() error {
|
||||||
|
if a.client == nil {
|
||||||
|
return errors.New("ACME client still not built")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
notify := func(err error, time time.Duration) {
|
||||||
|
log.Errorf("Error getting ACME client: %v, retrying in %s", err, time)
|
||||||
|
}
|
||||||
|
ebo := backoff.NewExponentialBackOff()
|
||||||
|
ebo.MaxElapsedTime = 30 * time.Second
|
||||||
|
err := backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error getting ACME client: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
account := a.store.Get().(*Account)
|
||||||
|
var domain Domain
|
||||||
|
if len(domains) > 1 {
|
||||||
|
domain = Domain{Main: domains[0], SANs: domains[1:]}
|
||||||
|
} else {
|
||||||
|
domain = Domain{Main: domains[0]}
|
||||||
|
}
|
||||||
|
if _, exists := account.DomainsCertificate.exists(domain); exists {
|
||||||
|
// domain already exists
|
||||||
|
return
|
||||||
|
}
|
||||||
|
certificate, err := a.getDomainsCertificates(domains)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error getting ACME certificates %+v : %v", domains, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("Got certificate for domains %+v", domains)
|
||||||
|
transaction, object, err := a.store.Begin()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error creating transaction %+v : %v", domains, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
account = object.(*Account)
|
||||||
|
_, err = account.DomainsCertificate.addCertificateForDomains(certificate, domain)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error adding ACME certificates %+v : %v", domains, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = transaction.Commit(account); err != nil {
|
||||||
|
log.Errorf("Error Saving ACME account %+v: %v", account, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
file, err := ioutil.ReadFile(acmeConfig.StorageFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(file, &Account); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = Account.DomainsCertificate.init()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Infof("Loaded ACME config from storage %s", acmeConfig.StorageFile)
|
|
||||||
return &Account, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) saveAccount(Account *Account) error {
|
// Get provided certificate which check a domains list (Main and SANs)
|
||||||
a.storageLock.Lock()
|
func (a *ACME) getProvidedCertificate(domains []string) *tls.Certificate {
|
||||||
defer a.storageLock.Unlock()
|
// Use regex to test for provided certs that might have been added into TLSConfig
|
||||||
// write account to file
|
providedCertMatch := false
|
||||||
data, err := json.MarshalIndent(Account, "", " ")
|
log.Debugf("Look for provided certificate to validate %s...", domains)
|
||||||
if err != nil {
|
for k := range a.TLSConfig.NameToCertificate {
|
||||||
return err
|
selector := "^" + strings.Replace(k, "*.", "[^\\.]*\\.?", -1) + "$"
|
||||||
|
for _, domainToCheck := range domains {
|
||||||
|
providedCertMatch, _ = regexp.MatchString(selector, domainToCheck)
|
||||||
|
if !providedCertMatch {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if providedCertMatch {
|
||||||
|
log.Debugf("Got provided certificate for domains %s", domains)
|
||||||
|
return a.TLSConfig.NameToCertificate[k]
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ioutil.WriteFile(a.StorageFile, data, 0644)
|
log.Debugf("No provided certificate found for domains %s, get ACME certificate.", domains)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) getDomainsCertificates(client *acme.Client, domains []string) (*Certificate, error) {
|
func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
|
||||||
|
domains = fun.Map(types.CanonicalDomain, domains).([]string)
|
||||||
log.Debugf("Loading ACME certificates %s...", domains)
|
log.Debugf("Loading ACME certificates %s...", domains)
|
||||||
bundle := true
|
bundle := true
|
||||||
certificate, failures := client.ObtainCertificate(domains, bundle, nil)
|
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple)
|
||||||
if len(failures) > 0 {
|
if len(failures) > 0 {
|
||||||
log.Error(failures)
|
log.Error(failures)
|
||||||
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
|
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
|
||||||
@@ -421,3 +623,12 @@ func (a *ACME) getDomainsCertificates(client *acme.Client, domains []string) (*C
|
|||||||
Certificate: certificate.Certificate,
|
Certificate: certificate.Certificate,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ACME) runJobs() {
|
||||||
|
safe.Go(func() {
|
||||||
|
for job := range a.jobs.Out() {
|
||||||
|
function := job.(func())
|
||||||
|
function()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
296
acme/acme_test.go
Normal file
296
acme/acme_test.go
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
)
|
||||||
|
|
||||||
|
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) {
|
||||||
|
foo1Cert, foo1Key, _ := generateKeyPair("foo1.com", time.Now())
|
||||||
|
foo2Cert, foo2Key, _ := generateKeyPair("foo2.com", time.Now())
|
||||||
|
domainsCertificates := DomainsCertificates{
|
||||||
|
lock: sync.RWMutex{},
|
||||||
|
Certs: []*DomainsCertificate{
|
||||||
|
{
|
||||||
|
Domains: Domain{
|
||||||
|
Main: "foo1.com",
|
||||||
|
SANs: []string{}},
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Domain: "foo1.com",
|
||||||
|
CertURL: "url",
|
||||||
|
CertStableURL: "url",
|
||||||
|
PrivateKey: foo1Key,
|
||||||
|
Certificate: foo1Cert,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domains: Domain{
|
||||||
|
Main: "foo2.com",
|
||||||
|
SANs: []string{}},
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Domain: "foo2.com",
|
||||||
|
CertURL: "url",
|
||||||
|
CertStableURL: "url",
|
||||||
|
PrivateKey: foo2Key,
|
||||||
|
Certificate: foo2Cert,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
foo1Cert, foo1Key, _ = generateKeyPair("foo1.com", time.Now())
|
||||||
|
newCertificate := &Certificate{
|
||||||
|
Domain: "foo1.com",
|
||||||
|
CertURL: "url",
|
||||||
|
CertStableURL: "url",
|
||||||
|
PrivateKey: foo1Key,
|
||||||
|
Certificate: foo1Cert,
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveDuplicates(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
fooCert, fooKey, _ := generateKeyPair("foo.com", now)
|
||||||
|
foo24Cert, foo24Key, _ := generateKeyPair("foo.com", now.Add(24*time.Hour))
|
||||||
|
foo48Cert, foo48Key, _ := generateKeyPair("foo.com", now.Add(48*time.Hour))
|
||||||
|
barCert, barKey, _ := generateKeyPair("bar.com", now)
|
||||||
|
domainsCertificates := DomainsCertificates{
|
||||||
|
lock: sync.RWMutex{},
|
||||||
|
Certs: []*DomainsCertificate{
|
||||||
|
{
|
||||||
|
Domains: Domain{
|
||||||
|
Main: "foo.com",
|
||||||
|
SANs: []string{}},
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Domain: "foo.com",
|
||||||
|
CertURL: "url",
|
||||||
|
CertStableURL: "url",
|
||||||
|
PrivateKey: foo24Key,
|
||||||
|
Certificate: foo24Cert,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domains: Domain{
|
||||||
|
Main: "foo.com",
|
||||||
|
SANs: []string{}},
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Domain: "foo.com",
|
||||||
|
CertURL: "url",
|
||||||
|
CertStableURL: "url",
|
||||||
|
PrivateKey: foo48Key,
|
||||||
|
Certificate: foo48Cert,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domains: Domain{
|
||||||
|
Main: "foo.com",
|
||||||
|
SANs: []string{}},
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Domain: "foo.com",
|
||||||
|
CertURL: "url",
|
||||||
|
CertStableURL: "url",
|
||||||
|
PrivateKey: fooKey,
|
||||||
|
Certificate: fooCert,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domains: Domain{
|
||||||
|
Main: "bar.com",
|
||||||
|
SANs: []string{}},
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Domain: "bar.com",
|
||||||
|
CertURL: "url",
|
||||||
|
CertStableURL: "url",
|
||||||
|
PrivateKey: barKey,
|
||||||
|
Certificate: barCert,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domains: Domain{
|
||||||
|
Main: "foo.com",
|
||||||
|
SANs: []string{}},
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Domain: "foo.com",
|
||||||
|
CertURL: "url",
|
||||||
|
CertStableURL: "url",
|
||||||
|
PrivateKey: foo48Key,
|
||||||
|
Certificate: foo48Cert,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
domainsCertificates.Init()
|
||||||
|
|
||||||
|
if len(domainsCertificates.Certs) != 2 {
|
||||||
|
t.Errorf("Expected domainsCertificates length %d %+v\nGot %+v", 2, domainsCertificates.Certs, len(domainsCertificates.Certs))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cert := range domainsCertificates.Certs {
|
||||||
|
switch cert.Domains.Main {
|
||||||
|
case "bar.com":
|
||||||
|
continue
|
||||||
|
case "foo.com":
|
||||||
|
if !cert.tlsCert.Leaf.NotAfter.Equal(now.Add(48 * time.Hour).Truncate(1 * time.Second)) {
|
||||||
|
t.Errorf("Bad expiration %s date for domain %+v, now %s", cert.tlsCert.Leaf.NotAfter.String(), cert, now.Add(48*time.Hour).Truncate(1*time.Second).String())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Errorf("Unknown domain %+v", cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoPreCheckOverride(t *testing.T) {
|
||||||
|
acme.PreCheckDNS = nil // Irreversable - but not expecting real calls into this during testing process
|
||||||
|
err := dnsOverrideDelay(0)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error in dnsOverrideDelay :%v", err)
|
||||||
|
}
|
||||||
|
if acme.PreCheckDNS != nil {
|
||||||
|
t.Error("Unexpected change to acme.PreCheckDNS when leaving DNS verification as is.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSillyPreCheckOverride(t *testing.T) {
|
||||||
|
err := dnsOverrideDelay(-5)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Missing expected error in dnsOverrideDelay!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPreCheckOverride(t *testing.T) {
|
||||||
|
acme.PreCheckDNS = nil // Irreversable - but not expecting real calls into this during testing process
|
||||||
|
err := dnsOverrideDelay(5)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error in dnsOverrideDelay :%v", err)
|
||||||
|
}
|
||||||
|
if acme.PreCheckDNS == nil {
|
||||||
|
t.Error("No change to acme.PreCheckDNS when meant to be adding enforcing override function.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAcmeClientCreation(t *testing.T) {
|
||||||
|
acme.PreCheckDNS = nil // Irreversable - but not expecting real calls into this during testing process
|
||||||
|
// Lengthy setup to avoid external web requests - oh for easier golang testing!
|
||||||
|
account := &Account{Email: "f@f"}
|
||||||
|
account.PrivateKey, _ = base64.StdEncoding.DecodeString(`
|
||||||
|
MIIBPAIBAAJBAMp2Ni92FfEur+CAvFkgC12LT4l9D53ApbBpDaXaJkzzks+KsLw9zyAxvlrfAyTCQ
|
||||||
|
7tDnEnIltAXyQ0uOFUUdcMCAwEAAQJAK1FbipATZcT9cGVa5x7KD7usytftLW14heQUPXYNV80r/3
|
||||||
|
lmnpvjL06dffRpwkYeN8DATQF/QOcy3NNNGDw/4QIhAPAKmiZFxA/qmRXsuU8Zhlzf16WrNZ68K64
|
||||||
|
asn/h3qZrAiEA1+wFR3WXCPIolOvd7AHjfgcTKQNkoMPywU4FYUNQ1AkCIQDv8yk0qPjckD6HVCPJ
|
||||||
|
llJh9MC0svjevGtNlxJoE3lmEQIhAKXy1wfZ32/XtcrnENPvi6lzxI0T94X7s5pP3aCoPPoJAiEAl
|
||||||
|
cijFkALeQp/qyeXdFld2v9gUN3eCgljgcl0QweRoIc=---`)
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{
|
||||||
|
"new-authz": "https://foo/acme/new-authz",
|
||||||
|
"new-cert": "https://foo/acme/new-cert",
|
||||||
|
"new-reg": "https://foo/acme/new-reg",
|
||||||
|
"revoke-cert": "https://foo/acme/revoke-cert"
|
||||||
|
}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
a := ACME{DNSProvider: "manual", DelayDontCheckDNS: 10, CAServer: ts.URL}
|
||||||
|
|
||||||
|
client, err := a.buildACMEClient(account)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error in buildACMEClient: %v", err)
|
||||||
|
}
|
||||||
|
if client == nil {
|
||||||
|
t.Error("No client from buildACMEClient!")
|
||||||
|
}
|
||||||
|
if acme.PreCheckDNS == nil {
|
||||||
|
t.Error("No change to acme.PreCheckDNS when meant to be adding enforcing override function.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAcme_getProvidedCertificate(t *testing.T) {
|
||||||
|
mm := make(map[string]*tls.Certificate)
|
||||||
|
mm["*.containo.us"] = &tls.Certificate{}
|
||||||
|
mm["traefik.acme.io"] = &tls.Certificate{}
|
||||||
|
|
||||||
|
a := ACME{TLSConfig: &tls.Config{NameToCertificate: mm}}
|
||||||
|
|
||||||
|
domains := []string{"traefik.containo.us", "trae.containo.us"}
|
||||||
|
certificate := a.getProvidedCertificate(domains)
|
||||||
|
assert.NotNil(t, certificate)
|
||||||
|
domains = []string{"traefik.acme.io", "trae.acme.io"}
|
||||||
|
certificate = a.getProvidedCertificate(domains)
|
||||||
|
assert.Nil(t, certificate)
|
||||||
|
}
|
||||||
@@ -2,55 +2,96 @@ package acme
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"crypto/x509"
|
"github.com/cenk/backoff"
|
||||||
|
"github.com/containous/traefik/cluster"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/xenolf/lego/acme"
|
"github.com/xenolf/lego/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
type wrapperChallengeProvider struct {
|
var _ acme.ChallengeProviderTimeout = (*challengeProvider)(nil)
|
||||||
challengeCerts map[string]*tls.Certificate
|
|
||||||
lock sync.RWMutex
|
type challengeProvider struct {
|
||||||
|
store cluster.Store
|
||||||
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWrapperChallengeProvider() *wrapperChallengeProvider {
|
func (c *challengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
|
||||||
return &wrapperChallengeProvider{
|
log.Debugf("Challenge GetCertificate %s", domain)
|
||||||
challengeCerts: map[string]*tls.Certificate{},
|
if !strings.HasSuffix(domain, ".acme.invalid") {
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (c *wrapperChallengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
|
|
||||||
c.lock.RLock()
|
c.lock.RLock()
|
||||||
defer c.lock.RUnlock()
|
defer c.lock.RUnlock()
|
||||||
if cert, ok := c.challengeCerts[domain]; ok {
|
account := c.store.Get().(*Account)
|
||||||
return cert, true
|
if account.ChallengeCerts == nil {
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
return nil, false
|
account.Init()
|
||||||
|
var result *tls.Certificate
|
||||||
|
operation := func() error {
|
||||||
|
for _, cert := range account.ChallengeCerts {
|
||||||
|
for _, dns := range cert.certificate.Leaf.DNSNames {
|
||||||
|
if domain == dns {
|
||||||
|
result = cert.certificate
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cannot find challenge cert for domain %s", domain)
|
||||||
|
}
|
||||||
|
notify := func(err error, time time.Duration) {
|
||||||
|
log.Errorf("Error getting cert: %v, retrying in %s", err, time)
|
||||||
|
}
|
||||||
|
ebo := backoff.NewExponentialBackOff()
|
||||||
|
ebo.MaxElapsedTime = 60 * time.Second
|
||||||
|
err := backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error getting cert: %v", err)
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return result, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *wrapperChallengeProvider) Present(domain, token, keyAuth string) error {
|
func (c *challengeProvider) Present(domain, token, keyAuth string) error {
|
||||||
cert, err := acme.TLSSNI01ChallengeCert(keyAuth)
|
log.Debugf("Challenge Present %s", domain)
|
||||||
if err != nil {
|
cert, _, err := TLSSNI01ChallengeCert(keyAuth)
|
||||||
return err
|
|
||||||
}
|
|
||||||
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
for i := range cert.Leaf.DNSNames {
|
transaction, object, err := c.store.Begin()
|
||||||
c.challengeCerts[cert.Leaf.DNSNames[i]] = &cert
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
account := object.(*Account)
|
||||||
return nil
|
if account.ChallengeCerts == nil {
|
||||||
|
account.ChallengeCerts = map[string]*ChallengeCert{}
|
||||||
|
}
|
||||||
|
account.ChallengeCerts[domain] = &cert
|
||||||
|
return transaction.Commit(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *wrapperChallengeProvider) CleanUp(domain, token, keyAuth string) error {
|
func (c *challengeProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
|
log.Debugf("Challenge CleanUp %s", domain)
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
delete(c.challengeCerts, domain)
|
transaction, object, err := c.store.Begin()
|
||||||
return nil
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account := object.(*Account)
|
||||||
|
delete(account.ChallengeCerts, domain)
|
||||||
|
return transaction.Commit(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *challengeProvider) Timeout() (timeout, interval time.Duration) {
|
||||||
|
return 60 * time.Second, 5 * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
@@ -15,34 +17,44 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func generateDefaultCertificate() (*tls.Certificate, error) {
|
func generateDefaultCertificate() (*tls.Certificate, error) {
|
||||||
rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rsaPrivPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)})
|
|
||||||
|
|
||||||
randomBytes := make([]byte, 100)
|
randomBytes := make([]byte, 100)
|
||||||
_, err = rand.Read(randomBytes)
|
_, err := rand.Read(randomBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
zBytes := sha256.Sum256(randomBytes)
|
zBytes := sha256.Sum256(randomBytes)
|
||||||
z := hex.EncodeToString(zBytes[:sha256.Size])
|
z := hex.EncodeToString(zBytes[:sha256.Size])
|
||||||
domain := fmt.Sprintf("%s.%s.traefik.default", z[:32], z[32:])
|
domain := fmt.Sprintf("%s.%s.traefik.default", z[:32], z[32:])
|
||||||
tempCertPEM, err := generatePemCert(rsaPrivKey, domain)
|
|
||||||
|
certPEM, keyPEM, err := generateKeyPair(domain, time.Time{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
|
certificate, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &certificate, nil
|
return &certificate, nil
|
||||||
}
|
}
|
||||||
func generatePemCert(privKey *rsa.PrivateKey, domain string) ([]byte, error) {
|
|
||||||
derBytes, err := generateDerCert(privKey, time.Time{}, domain)
|
func generateKeyPair(domain string, expiration time.Time) ([]byte, []byte, error) {
|
||||||
|
rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)})
|
||||||
|
|
||||||
|
certPEM, err := generatePemCert(rsaPrivKey, domain, expiration)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return certPEM, keyPEM, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generatePemCert(privKey *rsa.PrivateKey, domain string, expiration time.Time) ([]byte, error) {
|
||||||
|
derBytes, err := generateDerCert(privKey, expiration, domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -76,3 +88,46 @@ func generateDerCert(privKey *rsa.PrivateKey, expiration time.Time, domain strin
|
|||||||
|
|
||||||
return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
|
return x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TLSSNI01ChallengeCert returns a certificate and target domain for the `tls-sni-01` challenge
|
||||||
|
func TLSSNI01ChallengeCert(keyAuth string) (ChallengeCert, string, error) {
|
||||||
|
// generate a new RSA key for the certificates
|
||||||
|
var tempPrivKey crypto.PrivateKey
|
||||||
|
tempPrivKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return ChallengeCert{}, "", err
|
||||||
|
}
|
||||||
|
rsaPrivKey := tempPrivKey.(*rsa.PrivateKey)
|
||||||
|
rsaPrivPEM := pemEncode(rsaPrivKey)
|
||||||
|
|
||||||
|
zBytes := sha256.Sum256([]byte(keyAuth))
|
||||||
|
z := hex.EncodeToString(zBytes[:sha256.Size])
|
||||||
|
domain := fmt.Sprintf("%s.%s.acme.invalid", z[:32], z[32:])
|
||||||
|
tempCertPEM, err := generatePemCert(rsaPrivKey, domain, time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
return ChallengeCert{}, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
|
||||||
|
if err != nil {
|
||||||
|
return ChallengeCert{}, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ChallengeCert{Certificate: tempCertPEM, PrivateKey: rsaPrivPEM, certificate: &certificate}, domain, nil
|
||||||
|
}
|
||||||
|
func pemEncode(data interface{}) []byte {
|
||||||
|
var pemBlock *pem.Block
|
||||||
|
switch key := data.(type) {
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
keyBytes, _ := x509.MarshalECPrivateKey(key)
|
||||||
|
pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
|
||||||
|
case *x509.CertificateRequest:
|
||||||
|
pemBlock = &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: key.Raw}
|
||||||
|
case []byte:
|
||||||
|
pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.([]byte))}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pem.EncodeToMemory(pemBlock)
|
||||||
|
}
|
||||||
|
|||||||
97
acme/localStore.go
Normal file
97
acme/localStore.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/cluster"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ cluster.Store = (*LocalStore)(nil)
|
||||||
|
|
||||||
|
// LocalStore is a store using a file as storage
|
||||||
|
type LocalStore struct {
|
||||||
|
file string
|
||||||
|
storageLock sync.RWMutex
|
||||||
|
account *Account
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLocalStore create a LocalStore
|
||||||
|
func NewLocalStore(file string) *LocalStore {
|
||||||
|
return &LocalStore{
|
||||||
|
file: file,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get atomically a struct from the file storage
|
||||||
|
func (s *LocalStore) Get() cluster.Object {
|
||||||
|
s.storageLock.RLock()
|
||||||
|
defer s.storageLock.RUnlock()
|
||||||
|
return s.account
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads file into store
|
||||||
|
func (s *LocalStore) Load() (cluster.Object, error) {
|
||||||
|
s.storageLock.Lock()
|
||||||
|
defer s.storageLock.Unlock()
|
||||||
|
account := &Account{}
|
||||||
|
|
||||||
|
err := checkPermissions(s.file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f, err := os.Open(s.file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
file, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(file, &account); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
account.Init()
|
||||||
|
s.account = account
|
||||||
|
log.Infof("Loaded ACME config from store %s", s.file)
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin creates a transaction with the KV store.
|
||||||
|
func (s *LocalStore) Begin() (cluster.Transaction, cluster.Object, error) {
|
||||||
|
s.storageLock.Lock()
|
||||||
|
return &localTransaction{LocalStore: s}, s.account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ cluster.Transaction = (*localTransaction)(nil)
|
||||||
|
|
||||||
|
type localTransaction struct {
|
||||||
|
*LocalStore
|
||||||
|
dirty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit allows to set an object in the file storage
|
||||||
|
func (t *localTransaction) Commit(object cluster.Object) error {
|
||||||
|
t.LocalStore.account = object.(*Account)
|
||||||
|
defer t.storageLock.Unlock()
|
||||||
|
if t.dirty {
|
||||||
|
return fmt.Errorf("transaction already used, please begin a new one")
|
||||||
|
}
|
||||||
|
|
||||||
|
// write account to file
|
||||||
|
data, err := json.MarshalIndent(object, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(t.file, data, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.dirty = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
25
acme/localStore_unix.go
Normal file
25
acme/localStore_unix.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check file permissions
|
||||||
|
func checkPermissions(name string) error {
|
||||||
|
f, err := os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fi.Mode().Perm()&0077 != 0 {
|
||||||
|
return fmt.Errorf("permissions %o for %s are too open, please use 600", fi.Mode().Perm(), name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
6
acme/localStore_windows.go
Normal file
6
acme/localStore_windows.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
// Do not check file permissions on Windows right now
|
||||||
|
func checkPermissions(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,28 +1,31 @@
|
|||||||
FROM golang:1.6.2
|
FROM golang:1.9-alpine
|
||||||
|
|
||||||
RUN go get github.com/Masterminds/glide \
|
RUN apk --update upgrade \
|
||||||
&& go get github.com/jteeuwen/go-bindata/... \
|
&& apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar \
|
||||||
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
RUN go get github.com/jteeuwen/go-bindata/... \
|
||||||
&& go get github.com/golang/lint/golint \
|
&& go get github.com/golang/lint/golint \
|
||||||
&& go get github.com/kisielk/errcheck
|
&& go get github.com/kisielk/errcheck \
|
||||||
|
&& go get github.com/client9/misspell/cmd/misspell \
|
||||||
|
&& go get github.com/mattfarina/glide-hash \
|
||||||
|
&& go get github.com/sgotti/glide-vc
|
||||||
|
|
||||||
# Which docker version to test on
|
# Which docker version to test on
|
||||||
ENV DOCKER_VERSION 1.10.1
|
ARG DOCKER_VERSION=17.03.2
|
||||||
|
|
||||||
# enable GO15VENDOREXPERIMENT
|
# Which glide version to test on
|
||||||
ENV GO15VENDOREXPERIMENT 1
|
ARG GLIDE_VERSION=v0.12.3
|
||||||
|
|
||||||
|
# Download glide
|
||||||
|
RUN mkdir -p /usr/local/bin \
|
||||||
|
&& curl -fL https://github.com/Masterminds/glide/releases/download/${GLIDE_VERSION}/glide-${GLIDE_VERSION}-linux-amd64.tar.gz \
|
||||||
|
| tar -xzC /usr/local/bin --transform 's#^.+/##x'
|
||||||
|
|
||||||
# Download docker
|
# Download docker
|
||||||
RUN set -ex; \
|
RUN mkdir -p /usr/local/bin \
|
||||||
curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/local/bin/docker-${DOCKER_VERSION}; \
|
&& curl -fL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}-ce.tgz \
|
||||||
chmod +x /usr/local/bin/docker-${DOCKER_VERSION}
|
| tar -xzC /usr/local/bin --transform 's#^.+/##x'
|
||||||
|
|
||||||
# Set the default Docker to be run
|
|
||||||
RUN ln -s /usr/local/bin/docker-${DOCKER_VERSION} /usr/local/bin/docker
|
|
||||||
|
|
||||||
WORKDIR /go/src/github.com/containous/traefik
|
WORKDIR /go/src/github.com/containous/traefik
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
36
circle.yml
36
circle.yml
@@ -1,36 +0,0 @@
|
|||||||
machine:
|
|
||||||
pre:
|
|
||||||
- sudo docker -d -e lxc -s btrfs -H tcp://0.0.0.0:2375:
|
|
||||||
background: true
|
|
||||||
- curl --retry 15 --retry-delay 3 -v http://172.17.42.1:2375/version
|
|
||||||
environment:
|
|
||||||
REPO: $CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
|
|
||||||
DOCKER_HOST: tcp://172.17.42.1:2375
|
|
||||||
MAKE_DOCKER_HOST: $DOCKER_HOST
|
|
||||||
VERSION: v1.0.alpha.$CIRCLE_BUILD_NUM
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
pre:
|
|
||||||
- docker version
|
|
||||||
- go get github.com/tcnksm/ghr
|
|
||||||
- make validate
|
|
||||||
override:
|
|
||||||
- make binary
|
|
||||||
|
|
||||||
test:
|
|
||||||
override:
|
|
||||||
- make test-unit
|
|
||||||
- make test-integration
|
|
||||||
post:
|
|
||||||
- make crossbinary
|
|
||||||
- make image
|
|
||||||
|
|
||||||
deployment:
|
|
||||||
hub:
|
|
||||||
branch: master
|
|
||||||
commands:
|
|
||||||
- ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME --prerelease ${VERSION} dist/
|
|
||||||
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
|
||||||
- docker push ${REPO,,}:latest
|
|
||||||
- docker tag ${REPO,,}:latest ${REPO,,}:${VERSION}
|
|
||||||
- docker push ${REPO,,}:${VERSION}
|
|
||||||
258
cluster/datastore.go
Normal file
258
cluster/datastore.go
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cenk/backoff"
|
||||||
|
"github.com/containous/staert"
|
||||||
|
"github.com/containous/traefik/job"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
|
"github.com/docker/libkv/store"
|
||||||
|
"github.com/satori/go.uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Metadata stores Object plus metadata
|
||||||
|
type Metadata struct {
|
||||||
|
object Object
|
||||||
|
Object []byte
|
||||||
|
Lock string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMetadata returns new Metadata
|
||||||
|
func NewMetadata(object Object) *Metadata {
|
||||||
|
return &Metadata{object: object}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshall marshalls object
|
||||||
|
func (m *Metadata) Marshall() error {
|
||||||
|
var err error
|
||||||
|
m.Object, err = json.Marshal(m.object)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Metadata) unmarshall() error {
|
||||||
|
if len(m.Object) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return json.Unmarshal(m.Object, m.object)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listener is called when Object has been changed in KV store
|
||||||
|
type Listener func(Object) error
|
||||||
|
|
||||||
|
var _ Store = (*Datastore)(nil)
|
||||||
|
|
||||||
|
// Datastore holds a struct synced in a KV store
|
||||||
|
type Datastore struct {
|
||||||
|
kv staert.KvSource
|
||||||
|
ctx context.Context
|
||||||
|
localLock *sync.RWMutex
|
||||||
|
meta *Metadata
|
||||||
|
lockKey string
|
||||||
|
listener Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDataStore creates a Datastore
|
||||||
|
func NewDataStore(ctx context.Context, kvSource staert.KvSource, object Object, listener Listener) (*Datastore, error) {
|
||||||
|
datastore := Datastore{
|
||||||
|
kv: kvSource,
|
||||||
|
ctx: ctx,
|
||||||
|
meta: &Metadata{object: object},
|
||||||
|
lockKey: kvSource.Prefix + "/lock",
|
||||||
|
localLock: &sync.RWMutex{},
|
||||||
|
listener: listener,
|
||||||
|
}
|
||||||
|
err := datastore.watchChanges()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &datastore, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Datastore) watchChanges() error {
|
||||||
|
stopCh := make(chan struct{})
|
||||||
|
kvCh, err := d.kv.Watch(d.lockKey, stopCh)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
safe.Go(func() {
|
||||||
|
ctx, cancel := context.WithCancel(d.ctx)
|
||||||
|
operation := func() error {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
stopCh <- struct{}{}
|
||||||
|
return nil
|
||||||
|
case _, ok := <-kvCh:
|
||||||
|
if !ok {
|
||||||
|
cancel()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = d.reload()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if d.listener != nil {
|
||||||
|
err := d.listener(d.meta.object)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error calling datastore listener: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notify := func(err error, time time.Duration) {
|
||||||
|
log.Errorf("Error in watch datastore: %+v, retrying in %s", err, time)
|
||||||
|
}
|
||||||
|
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error in watch datastore: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Datastore) reload() error {
|
||||||
|
log.Debug("Datastore reload")
|
||||||
|
d.localLock.Lock()
|
||||||
|
err := d.kv.LoadConfig(d.meta)
|
||||||
|
if err != nil {
|
||||||
|
d.localLock.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = d.meta.unmarshall()
|
||||||
|
if err != nil {
|
||||||
|
d.localLock.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.localLock.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin creates a transaction with the KV store.
|
||||||
|
func (d *Datastore) Begin() (Transaction, Object, error) {
|
||||||
|
id := uuid.NewV4().String()
|
||||||
|
log.Debugf("Transaction %s begins", id)
|
||||||
|
remoteLock, err := d.kv.NewLock(d.lockKey, &store.LockOptions{TTL: 20 * time.Second, Value: []byte(id)})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
stopCh := make(chan struct{})
|
||||||
|
ctx, cancel := context.WithCancel(d.ctx)
|
||||||
|
var errLock error
|
||||||
|
go func() {
|
||||||
|
_, errLock = remoteLock.Lock(stopCh)
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
if errLock != nil {
|
||||||
|
return nil, nil, errLock
|
||||||
|
}
|
||||||
|
case <-d.ctx.Done():
|
||||||
|
stopCh <- struct{}{}
|
||||||
|
return nil, nil, d.ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// we got the lock! Now make sure we are synced with KV store
|
||||||
|
operation := func() error {
|
||||||
|
meta := d.get()
|
||||||
|
if meta.Lock != id {
|
||||||
|
return fmt.Errorf("Object lock value: expected %s, got %s", id, meta.Lock)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
notify := func(err error, time time.Duration) {
|
||||||
|
log.Errorf("Datastore sync error: %v, retrying in %s", err, time)
|
||||||
|
err = d.reload()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error reloading: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ebo := backoff.NewExponentialBackOff()
|
||||||
|
ebo.MaxElapsedTime = 60 * time.Second
|
||||||
|
err = backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("Datastore cannot sync: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we synced with KV store, we can now return Setter
|
||||||
|
return &datastoreTransaction{
|
||||||
|
Datastore: d,
|
||||||
|
remoteLock: remoteLock,
|
||||||
|
id: id,
|
||||||
|
}, d.meta.object, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Datastore) get() *Metadata {
|
||||||
|
d.localLock.RLock()
|
||||||
|
defer d.localLock.RUnlock()
|
||||||
|
return d.meta
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load load atomically a struct from the KV store
|
||||||
|
func (d *Datastore) Load() (Object, error) {
|
||||||
|
d.localLock.Lock()
|
||||||
|
defer d.localLock.Unlock()
|
||||||
|
|
||||||
|
// clear Object first, as mapstructure's decoder doesn't have ZeroFields set to true for merging purposes
|
||||||
|
d.meta.Object = d.meta.Object[:0]
|
||||||
|
|
||||||
|
err := d.kv.LoadConfig(d.meta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = d.meta.unmarshall()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d.meta.object, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get atomically a struct from the KV store
|
||||||
|
func (d *Datastore) Get() Object {
|
||||||
|
d.localLock.RLock()
|
||||||
|
defer d.localLock.RUnlock()
|
||||||
|
return d.meta.object
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Transaction = (*datastoreTransaction)(nil)
|
||||||
|
|
||||||
|
type datastoreTransaction struct {
|
||||||
|
*Datastore
|
||||||
|
remoteLock store.Locker
|
||||||
|
dirty bool
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit allows to set an object in the KV store
|
||||||
|
func (s *datastoreTransaction) Commit(object Object) error {
|
||||||
|
s.localLock.Lock()
|
||||||
|
defer s.localLock.Unlock()
|
||||||
|
if s.dirty {
|
||||||
|
return fmt.Errorf("Transaction already used, please begin a new one")
|
||||||
|
}
|
||||||
|
s.Datastore.meta.object = object
|
||||||
|
err := s.Datastore.meta.Marshall()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Marshall error: %s", err)
|
||||||
|
}
|
||||||
|
err = s.kv.StoreConfig(s.Datastore.meta)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("StoreConfig error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.remoteLock.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unlock error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.dirty = true
|
||||||
|
log.Debugf("Transaction committed %s", s.id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
104
cluster/leadership.go
Normal file
104
cluster/leadership.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cenk/backoff"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
"github.com/docker/leadership"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Leadership allows leadership election using a KV store
|
||||||
|
type Leadership struct {
|
||||||
|
*safe.Pool
|
||||||
|
*types.Cluster
|
||||||
|
candidate *leadership.Candidate
|
||||||
|
leader *safe.Safe
|
||||||
|
listeners []LeaderListener
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLeadership creates a leadership
|
||||||
|
func NewLeadership(ctx context.Context, cluster *types.Cluster) *Leadership {
|
||||||
|
return &Leadership{
|
||||||
|
Pool: safe.NewPool(ctx),
|
||||||
|
Cluster: cluster,
|
||||||
|
candidate: leadership.NewCandidate(cluster.Store, cluster.Store.Prefix+"/leader", cluster.Node, 20*time.Second),
|
||||||
|
listeners: []LeaderListener{},
|
||||||
|
leader: safe.New(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeaderListener is called when leadership has changed
|
||||||
|
type LeaderListener func(elected bool) error
|
||||||
|
|
||||||
|
// Participate tries to be a leader
|
||||||
|
func (l *Leadership) Participate(pool *safe.Pool) {
|
||||||
|
pool.GoCtx(func(ctx context.Context) {
|
||||||
|
log.Debugf("Node %s running for election", l.Cluster.Node)
|
||||||
|
defer log.Debugf("Node %s no more running for election", l.Cluster.Node)
|
||||||
|
backOff := backoff.NewExponentialBackOff()
|
||||||
|
operation := func() error {
|
||||||
|
return l.run(ctx, l.candidate)
|
||||||
|
}
|
||||||
|
|
||||||
|
notify := func(err error, time time.Duration) {
|
||||||
|
log.Errorf("Leadership election error %+v, retrying in %s", err, time)
|
||||||
|
}
|
||||||
|
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backOff, notify)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Cannot elect leadership %+v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddListener adds a leadership listener
|
||||||
|
func (l *Leadership) AddListener(listener LeaderListener) {
|
||||||
|
l.listeners = append(l.listeners, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resign resigns from being a leader
|
||||||
|
func (l *Leadership) Resign() {
|
||||||
|
l.candidate.Resign()
|
||||||
|
log.Infof("Node %s resigned", l.Cluster.Node)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Leadership) run(ctx context.Context, candidate *leadership.Candidate) error {
|
||||||
|
electedCh, errCh := candidate.RunForElection()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case elected := <-electedCh:
|
||||||
|
l.onElection(elected)
|
||||||
|
case err := <-errCh:
|
||||||
|
return err
|
||||||
|
case <-ctx.Done():
|
||||||
|
l.candidate.Resign()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Leadership) onElection(elected bool) {
|
||||||
|
if elected {
|
||||||
|
log.Infof("Node %s elected leader ♚", l.Cluster.Node)
|
||||||
|
l.leader.Set(true)
|
||||||
|
l.Start()
|
||||||
|
} else {
|
||||||
|
log.Infof("Node %s elected worker ♝", l.Cluster.Node)
|
||||||
|
l.leader.Set(false)
|
||||||
|
l.Stop()
|
||||||
|
}
|
||||||
|
for _, listener := range l.listeners {
|
||||||
|
err := listener(elected)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error calling Leadership listener: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLeader returns true if current node is leader
|
||||||
|
func (l *Leadership) IsLeader() bool {
|
||||||
|
return l.leader.Get().(bool)
|
||||||
|
}
|
||||||
16
cluster/store.go
Normal file
16
cluster/store.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package cluster
|
||||||
|
|
||||||
|
// Object is the struct to store
|
||||||
|
type Object interface{}
|
||||||
|
|
||||||
|
// Store is a generic interface to represents a storage
|
||||||
|
type Store interface {
|
||||||
|
Load() (Object, error)
|
||||||
|
Get() Object
|
||||||
|
Begin() (Transaction, Object, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction allows to set a struct in the KV store
|
||||||
|
type Transaction interface {
|
||||||
|
Commit(object Object) error
|
||||||
|
}
|
||||||
227
cmd.go
227
cmd.go
@@ -1,227 +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
|
|
||||||
kubernetes 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{},
|
|
||||||
Kubernetes: &provider.Kubernetes{},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
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().StringVar(&arguments.ConsulCatalog.Prefix, "consulCatalog.prefix", "traefik", "Consul catalog tag prefix")
|
|
||||||
|
|
||||||
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")
|
|
||||||
|
|
||||||
traefikCmd.PersistentFlags().BoolVar(&arguments.kubernetes, "kubernetes", false, "Enable Kubernetes backend")
|
|
||||||
traefikCmd.PersistentFlags().StringVar(&arguments.Kubernetes.Endpoint, "kubernetes.endpoint", "127.0.0.1:8080", "Kubernetes server endpoint")
|
|
||||||
traefikCmd.PersistentFlags().StringSliceVar(&arguments.Kubernetes.Namespaces, "kubernetes.namespaces", []string{}, "Kubernetes namespaces")
|
|
||||||
|
|
||||||
_ = 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")
|
|
||||||
}
|
|
||||||
136
cmd/traefik/anonymize/anonymize.go
Normal file
136
cmd/traefik/anonymize/anonymize.go
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
package anonymize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/mitchellh/copystructure"
|
||||||
|
"github.com/mvdan/xurls"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maskShort = "xxxx"
|
||||||
|
maskLarge = maskShort + maskShort + maskShort + maskShort + maskShort + maskShort + maskShort + maskShort
|
||||||
|
)
|
||||||
|
|
||||||
|
// Do configuration.
|
||||||
|
func Do(baseConfig interface{}, indent bool) (string, error) {
|
||||||
|
anomConfig, err := copystructure.Copy(baseConfig)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
val := reflect.ValueOf(anomConfig)
|
||||||
|
|
||||||
|
err = doOnStruct(val)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
configJSON, err := marshal(anomConfig, indent)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return doOnJSON(string(configJSON)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func doOnJSON(input string) string {
|
||||||
|
mailExp := regexp.MustCompile(`\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3}"`)
|
||||||
|
return xurls.Relaxed.ReplaceAllString(mailExp.ReplaceAllString(input, maskLarge+"\""), maskLarge)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doOnStruct(field reflect.Value) error {
|
||||||
|
switch field.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
if !field.IsNil() {
|
||||||
|
if err := doOnStruct(field.Elem()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
for i := 0; i < field.NumField(); i++ {
|
||||||
|
fld := field.Field(i)
|
||||||
|
stField := field.Type().Field(i)
|
||||||
|
if !isExported(stField) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if stField.Tag.Get("export") == "true" {
|
||||||
|
if err := doOnStruct(fld); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := reset(fld, stField.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
for _, key := range field.MapKeys() {
|
||||||
|
if err := doOnStruct(field.MapIndex(key)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
for j := 0; j < field.Len(); j++ {
|
||||||
|
if err := doOnStruct(field.Index(j)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func reset(field reflect.Value, name string) error {
|
||||||
|
if !field.CanSet() {
|
||||||
|
return fmt.Errorf("cannot reset field %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch field.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
if !field.IsNil() {
|
||||||
|
field.Set(reflect.Zero(field.Type()))
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
if field.IsValid() {
|
||||||
|
field.Set(reflect.Zero(field.Type()))
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
if field.String() != "" {
|
||||||
|
field.Set(reflect.ValueOf(maskShort))
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
if field.Len() > 0 {
|
||||||
|
field.Set(reflect.MakeMap(field.Type()))
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
if field.Len() > 0 {
|
||||||
|
field.Set(reflect.MakeSlice(field.Type(), 0, 0))
|
||||||
|
}
|
||||||
|
case reflect.Interface:
|
||||||
|
if !field.IsNil() {
|
||||||
|
return reset(field.Elem(), "")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Primitive type
|
||||||
|
field.Set(reflect.Zero(field.Type()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isExported return true is a struct field is exported, else false
|
||||||
|
func isExported(f reflect.StructField) bool {
|
||||||
|
if f.PkgPath != "" && !f.Anonymous {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshal(anomConfig interface{}, indent bool) ([]byte, error) {
|
||||||
|
if indent {
|
||||||
|
return json.MarshalIndent(anomConfig, "", " ")
|
||||||
|
}
|
||||||
|
return json.Marshal(anomConfig)
|
||||||
|
}
|
||||||
670
cmd/traefik/anonymize/anonymize_config_test.go
Normal file
670
cmd/traefik/anonymize/anonymize_config_test.go
Normal file
@@ -0,0 +1,670 @@
|
|||||||
|
package anonymize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containous/flaeg"
|
||||||
|
"github.com/containous/traefik/acme"
|
||||||
|
"github.com/containous/traefik/configuration"
|
||||||
|
"github.com/containous/traefik/middlewares"
|
||||||
|
"github.com/containous/traefik/provider"
|
||||||
|
"github.com/containous/traefik/provider/boltdb"
|
||||||
|
"github.com/containous/traefik/provider/consul"
|
||||||
|
"github.com/containous/traefik/provider/docker"
|
||||||
|
"github.com/containous/traefik/provider/dynamodb"
|
||||||
|
"github.com/containous/traefik/provider/ecs"
|
||||||
|
"github.com/containous/traefik/provider/etcd"
|
||||||
|
"github.com/containous/traefik/provider/eureka"
|
||||||
|
"github.com/containous/traefik/provider/file"
|
||||||
|
"github.com/containous/traefik/provider/kubernetes"
|
||||||
|
"github.com/containous/traefik/provider/kv"
|
||||||
|
"github.com/containous/traefik/provider/marathon"
|
||||||
|
"github.com/containous/traefik/provider/mesos"
|
||||||
|
"github.com/containous/traefik/provider/rancher"
|
||||||
|
"github.com/containous/traefik/provider/web"
|
||||||
|
"github.com/containous/traefik/provider/zk"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
thoas_stats "github.com/thoas/stats"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDo_globalConfiguration(t *testing.T) {
|
||||||
|
|
||||||
|
config := &configuration.GlobalConfiguration{}
|
||||||
|
|
||||||
|
config.GraceTimeOut = flaeg.Duration(666 * time.Second)
|
||||||
|
config.Debug = true
|
||||||
|
config.CheckNewVersion = true
|
||||||
|
config.AccessLogsFile = "AccessLogsFile"
|
||||||
|
config.AccessLog = &types.AccessLog{
|
||||||
|
FilePath: "AccessLog FilePath",
|
||||||
|
Format: "AccessLog Format",
|
||||||
|
}
|
||||||
|
config.TraefikLogsFile = "TraefikLogsFile"
|
||||||
|
config.LogLevel = "LogLevel"
|
||||||
|
config.EntryPoints = configuration.EntryPoints{
|
||||||
|
"foo": {
|
||||||
|
Network: "foo Network",
|
||||||
|
Address: "foo Address",
|
||||||
|
TLS: &configuration.TLS{
|
||||||
|
MinVersion: "foo MinVersion",
|
||||||
|
CipherSuites: []string{"foo CipherSuites 1", "foo CipherSuites 2", "foo CipherSuites 3"},
|
||||||
|
Certificates: configuration.Certificates{
|
||||||
|
{CertFile: "CertFile 1", KeyFile: "KeyFile 1"},
|
||||||
|
{CertFile: "CertFile 2", KeyFile: "KeyFile 2"},
|
||||||
|
},
|
||||||
|
ClientCAFiles: []string{"foo ClientCAFiles 1", "foo ClientCAFiles 2", "foo ClientCAFiles 3"},
|
||||||
|
},
|
||||||
|
Redirect: &configuration.Redirect{
|
||||||
|
Replacement: "foo Replacement",
|
||||||
|
Regex: "foo Regex",
|
||||||
|
EntryPoint: "foo EntryPoint",
|
||||||
|
},
|
||||||
|
Auth: &types.Auth{
|
||||||
|
Basic: &types.Basic{
|
||||||
|
UsersFile: "foo Basic UsersFile",
|
||||||
|
Users: types.Users{"foo Basic Users 1", "foo Basic Users 2", "foo Basic Users 3"},
|
||||||
|
},
|
||||||
|
Digest: &types.Digest{
|
||||||
|
UsersFile: "foo Digest UsersFile",
|
||||||
|
Users: types.Users{"foo Digest Users 1", "foo Digest Users 2", "foo Digest Users 3"},
|
||||||
|
},
|
||||||
|
Forward: &types.Forward{
|
||||||
|
Address: "foo Address",
|
||||||
|
TLS: &types.ClientTLS{
|
||||||
|
CA: "foo CA",
|
||||||
|
Cert: "foo Cert",
|
||||||
|
Key: "foo Key",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
TrustForwardHeader: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
WhitelistSourceRange: []string{"foo WhitelistSourceRange 1", "foo WhitelistSourceRange 2", "foo WhitelistSourceRange 3"},
|
||||||
|
Compress: true,
|
||||||
|
ProxyProtocol: &configuration.ProxyProtocol{
|
||||||
|
TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"fii": {
|
||||||
|
Network: "fii Network",
|
||||||
|
Address: "fii Address",
|
||||||
|
TLS: &configuration.TLS{
|
||||||
|
MinVersion: "fii MinVersion",
|
||||||
|
CipherSuites: []string{"fii CipherSuites 1", "fii CipherSuites 2", "fii CipherSuites 3"},
|
||||||
|
Certificates: configuration.Certificates{
|
||||||
|
{CertFile: "CertFile 1", KeyFile: "KeyFile 1"},
|
||||||
|
{CertFile: "CertFile 2", KeyFile: "KeyFile 2"},
|
||||||
|
},
|
||||||
|
ClientCAFiles: []string{"fii ClientCAFiles 1", "fii ClientCAFiles 2", "fii ClientCAFiles 3"},
|
||||||
|
},
|
||||||
|
Redirect: &configuration.Redirect{
|
||||||
|
Replacement: "fii Replacement",
|
||||||
|
Regex: "fii Regex",
|
||||||
|
EntryPoint: "fii EntryPoint",
|
||||||
|
},
|
||||||
|
Auth: &types.Auth{
|
||||||
|
Basic: &types.Basic{
|
||||||
|
UsersFile: "fii Basic UsersFile",
|
||||||
|
Users: types.Users{"fii Basic Users 1", "fii Basic Users 2", "fii Basic Users 3"},
|
||||||
|
},
|
||||||
|
Digest: &types.Digest{
|
||||||
|
UsersFile: "fii Digest UsersFile",
|
||||||
|
Users: types.Users{"fii Digest Users 1", "fii Digest Users 2", "fii Digest Users 3"},
|
||||||
|
},
|
||||||
|
Forward: &types.Forward{
|
||||||
|
Address: "fii Address",
|
||||||
|
TLS: &types.ClientTLS{
|
||||||
|
CA: "fii CA",
|
||||||
|
Cert: "fii Cert",
|
||||||
|
Key: "fii Key",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
TrustForwardHeader: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
WhitelistSourceRange: []string{"fii WhitelistSourceRange 1", "fii WhitelistSourceRange 2", "fii WhitelistSourceRange 3"},
|
||||||
|
Compress: true,
|
||||||
|
ProxyProtocol: &configuration.ProxyProtocol{
|
||||||
|
TrustedIPs: []string{"127.0.0.1/32", "192.168.0.1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config.Cluster = &types.Cluster{
|
||||||
|
Node: "Cluster Node",
|
||||||
|
Store: &types.Store{
|
||||||
|
Prefix: "Cluster Store Prefix",
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config.Constraints = types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "Constraints Key 1",
|
||||||
|
Regex: "Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "Constraints Key 1",
|
||||||
|
Regex: "Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config.ACME = &acme.ACME{
|
||||||
|
Email: "acme Email",
|
||||||
|
Domains: []acme.Domain{
|
||||||
|
{
|
||||||
|
Main: "Domains Main",
|
||||||
|
SANs: []string{"Domains acme SANs 1", "Domains acme SANs 2", "Domains acme SANs 3"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Storage: "Storage",
|
||||||
|
StorageFile: "StorageFile",
|
||||||
|
OnDemand: true,
|
||||||
|
OnHostRule: true,
|
||||||
|
CAServer: "CAServer",
|
||||||
|
EntryPoint: "EntryPoint",
|
||||||
|
DNSProvider: "DNSProvider",
|
||||||
|
DelayDontCheckDNS: 666,
|
||||||
|
ACMELogging: true,
|
||||||
|
TLSConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config.DefaultEntryPoints = configuration.DefaultEntryPoints{"DefaultEntryPoints 1", "DefaultEntryPoints 2", "DefaultEntryPoints 3"}
|
||||||
|
config.ProvidersThrottleDuration = flaeg.Duration(666 * time.Second)
|
||||||
|
config.MaxIdleConnsPerHost = 666
|
||||||
|
config.IdleTimeout = flaeg.Duration(666 * time.Second)
|
||||||
|
config.InsecureSkipVerify = true
|
||||||
|
config.RootCAs = configuration.RootCAs{"RootCAs 1", "RootCAs 2", "RootCAs 3"}
|
||||||
|
config.Retry = &configuration.Retry{
|
||||||
|
Attempts: 666,
|
||||||
|
}
|
||||||
|
config.HealthCheck = &configuration.HealthCheckConfig{
|
||||||
|
Interval: flaeg.Duration(666 * time.Second),
|
||||||
|
}
|
||||||
|
config.RespondingTimeouts = &configuration.RespondingTimeouts{
|
||||||
|
ReadTimeout: flaeg.Duration(666 * time.Second),
|
||||||
|
WriteTimeout: flaeg.Duration(666 * time.Second),
|
||||||
|
IdleTimeout: flaeg.Duration(666 * time.Second),
|
||||||
|
}
|
||||||
|
config.ForwardingTimeouts = &configuration.ForwardingTimeouts{
|
||||||
|
DialTimeout: flaeg.Duration(666 * time.Second),
|
||||||
|
ResponseHeaderTimeout: flaeg.Duration(666 * time.Second),
|
||||||
|
}
|
||||||
|
config.Docker = &docker.Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{
|
||||||
|
Watch: true,
|
||||||
|
Filename: "docker Filename",
|
||||||
|
Constraints: types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "docker Constraints Key 1",
|
||||||
|
Regex: "docker Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "docker Constraints Key 1",
|
||||||
|
Regex: "docker Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trace: true,
|
||||||
|
DebugLogGeneratedTemplate: true,
|
||||||
|
},
|
||||||
|
Endpoint: "docker Endpoint",
|
||||||
|
Domain: "docker Domain",
|
||||||
|
TLS: &types.ClientTLS{
|
||||||
|
CA: "docker CA",
|
||||||
|
Cert: "docker Cert",
|
||||||
|
Key: "docker Key",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
ExposedByDefault: true,
|
||||||
|
UseBindPortIP: true,
|
||||||
|
SwarmMode: true,
|
||||||
|
}
|
||||||
|
config.File = &file.Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{
|
||||||
|
Watch: true,
|
||||||
|
Filename: "file Filename",
|
||||||
|
Constraints: types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "file Constraints Key 1",
|
||||||
|
Regex: "file Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "file Constraints Key 1",
|
||||||
|
Regex: "file Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trace: true,
|
||||||
|
DebugLogGeneratedTemplate: true,
|
||||||
|
},
|
||||||
|
Directory: "file Directory",
|
||||||
|
}
|
||||||
|
config.Web = &web.Provider{
|
||||||
|
Address: "web Address",
|
||||||
|
CertFile: "web CertFile",
|
||||||
|
KeyFile: "web KeyFile",
|
||||||
|
ReadOnly: true,
|
||||||
|
Statistics: &types.Statistics{
|
||||||
|
RecentErrors: 666,
|
||||||
|
},
|
||||||
|
Metrics: &types.Metrics{
|
||||||
|
Prometheus: &types.Prometheus{
|
||||||
|
Buckets: types.Buckets{6.5, 6.6, 6.7},
|
||||||
|
},
|
||||||
|
Datadog: &types.Datadog{
|
||||||
|
Address: "Datadog Address",
|
||||||
|
PushInterval: "Datadog PushInterval",
|
||||||
|
},
|
||||||
|
StatsD: &types.Statsd{
|
||||||
|
Address: "StatsD Address",
|
||||||
|
PushInterval: "StatsD PushInterval",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Path: "web Path",
|
||||||
|
Auth: &types.Auth{
|
||||||
|
Basic: &types.Basic{
|
||||||
|
UsersFile: "web Basic UsersFile",
|
||||||
|
Users: types.Users{"web Basic Users 1", "web Basic Users 2", "web Basic Users 3"},
|
||||||
|
},
|
||||||
|
Digest: &types.Digest{
|
||||||
|
UsersFile: "web Digest UsersFile",
|
||||||
|
Users: types.Users{"web Digest Users 1", "web Digest Users 2", "web Digest Users 3"},
|
||||||
|
},
|
||||||
|
Forward: &types.Forward{
|
||||||
|
Address: "web Address",
|
||||||
|
TLS: &types.ClientTLS{
|
||||||
|
CA: "web CA",
|
||||||
|
Cert: "web Cert",
|
||||||
|
Key: "web Key",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
TrustForwardHeader: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Debug: true,
|
||||||
|
CurrentConfigurations: &safe.Safe{},
|
||||||
|
Stats: &thoas_stats.Stats{
|
||||||
|
Uptime: time.Now(),
|
||||||
|
Pid: 666,
|
||||||
|
ResponseCounts: map[string]int{"foo": 1, "fii": 2, "fuu": 3},
|
||||||
|
TotalResponseCounts: map[string]int{"foo": 1, "fii": 2, "fuu": 3},
|
||||||
|
TotalResponseTime: time.Now(),
|
||||||
|
},
|
||||||
|
StatsRecorder: &middlewares.StatsRecorder{},
|
||||||
|
}
|
||||||
|
config.Marathon = &marathon.Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{
|
||||||
|
Watch: true,
|
||||||
|
Filename: "marathon Filename",
|
||||||
|
Constraints: types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "marathon Constraints Key 1",
|
||||||
|
Regex: "marathon Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "marathon Constraints Key 1",
|
||||||
|
Regex: "marathon Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trace: true,
|
||||||
|
DebugLogGeneratedTemplate: true,
|
||||||
|
},
|
||||||
|
Endpoint: "",
|
||||||
|
Domain: "",
|
||||||
|
ExposedByDefault: true,
|
||||||
|
GroupsAsSubDomains: true,
|
||||||
|
DCOSToken: "",
|
||||||
|
MarathonLBCompatibility: true,
|
||||||
|
TLS: &types.ClientTLS{
|
||||||
|
CA: "marathon CA",
|
||||||
|
Cert: "marathon Cert",
|
||||||
|
Key: "marathon Key",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
DialerTimeout: flaeg.Duration(666 * time.Second),
|
||||||
|
KeepAlive: flaeg.Duration(666 * time.Second),
|
||||||
|
ForceTaskHostname: true,
|
||||||
|
Basic: &marathon.Basic{
|
||||||
|
HTTPBasicAuthUser: "marathon HTTPBasicAuthUser",
|
||||||
|
HTTPBasicPassword: "marathon HTTPBasicPassword",
|
||||||
|
},
|
||||||
|
RespectReadinessChecks: true,
|
||||||
|
}
|
||||||
|
config.ConsulCatalog = &consul.CatalogProvider{
|
||||||
|
BaseProvider: provider.BaseProvider{
|
||||||
|
Watch: true,
|
||||||
|
Filename: "ConsulCatalog Filename",
|
||||||
|
Constraints: types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "ConsulCatalog Constraints Key 1",
|
||||||
|
Regex: "ConsulCatalog Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "ConsulCatalog Constraints Key 1",
|
||||||
|
Regex: "ConsulCatalog Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trace: true,
|
||||||
|
DebugLogGeneratedTemplate: true,
|
||||||
|
},
|
||||||
|
Endpoint: "ConsulCatalog Endpoint",
|
||||||
|
Domain: "ConsulCatalog Domain",
|
||||||
|
ExposedByDefault: true,
|
||||||
|
Prefix: "ConsulCatalog Prefix",
|
||||||
|
FrontEndRule: "ConsulCatalog FrontEndRule",
|
||||||
|
}
|
||||||
|
config.Kubernetes = &kubernetes.Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{
|
||||||
|
Watch: true,
|
||||||
|
Filename: "k8s Filename",
|
||||||
|
Constraints: types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "k8s Constraints Key 1",
|
||||||
|
Regex: "k8s Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "k8s Constraints Key 1",
|
||||||
|
Regex: "k8s Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trace: true,
|
||||||
|
DebugLogGeneratedTemplate: true,
|
||||||
|
},
|
||||||
|
Endpoint: "k8s Endpoint",
|
||||||
|
Token: "k8s Token",
|
||||||
|
CertAuthFilePath: "k8s CertAuthFilePath",
|
||||||
|
DisablePassHostHeaders: true,
|
||||||
|
Namespaces: kubernetes.Namespaces{"k8s Namespaces 1", "k8s Namespaces 2", "k8s Namespaces 3"},
|
||||||
|
LabelSelector: "k8s LabelSelector",
|
||||||
|
}
|
||||||
|
config.Mesos = &mesos.Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{
|
||||||
|
Watch: true,
|
||||||
|
Filename: "mesos Filename",
|
||||||
|
Constraints: types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "mesos Constraints Key 1",
|
||||||
|
Regex: "mesos Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "mesos Constraints Key 1",
|
||||||
|
Regex: "mesos Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trace: true,
|
||||||
|
DebugLogGeneratedTemplate: true,
|
||||||
|
},
|
||||||
|
Endpoint: "mesos Endpoint",
|
||||||
|
Domain: "mesos Domain",
|
||||||
|
ExposedByDefault: true,
|
||||||
|
GroupsAsSubDomains: true,
|
||||||
|
ZkDetectionTimeout: 666,
|
||||||
|
RefreshSeconds: 666,
|
||||||
|
IPSources: "mesos IPSources",
|
||||||
|
StateTimeoutSecond: 666,
|
||||||
|
Masters: []string{"mesos Masters 1", "mesos Masters 2", "mesos Masters 3"},
|
||||||
|
}
|
||||||
|
config.Eureka = &eureka.Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{
|
||||||
|
Watch: true,
|
||||||
|
Filename: "eureka Filename",
|
||||||
|
Constraints: types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "eureka Constraints Key 1",
|
||||||
|
Regex: "eureka Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "eureka Constraints Key 1",
|
||||||
|
Regex: "eureka Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trace: true,
|
||||||
|
DebugLogGeneratedTemplate: true,
|
||||||
|
},
|
||||||
|
Endpoint: "eureka Endpoint",
|
||||||
|
Delay: "eureka Delay",
|
||||||
|
}
|
||||||
|
config.ECS = &ecs.Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{
|
||||||
|
Watch: true,
|
||||||
|
Filename: "ecs Filename",
|
||||||
|
Constraints: types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "ecs Constraints Key 1",
|
||||||
|
Regex: "ecs Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "ecs Constraints Key 1",
|
||||||
|
Regex: "ecs Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trace: true,
|
||||||
|
DebugLogGeneratedTemplate: true,
|
||||||
|
},
|
||||||
|
Domain: "ecs Domain",
|
||||||
|
ExposedByDefault: true,
|
||||||
|
RefreshSeconds: 666,
|
||||||
|
Clusters: ecs.Clusters{"ecs Clusters 1", "ecs Clusters 2", "ecs Clusters 3"},
|
||||||
|
Cluster: "ecs Cluster",
|
||||||
|
AutoDiscoverClusters: true,
|
||||||
|
Region: "ecs Region",
|
||||||
|
AccessKeyID: "ecs AccessKeyID",
|
||||||
|
SecretAccessKey: "ecs SecretAccessKey",
|
||||||
|
}
|
||||||
|
config.Rancher = &rancher.Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{
|
||||||
|
Watch: true,
|
||||||
|
Filename: "rancher Filename",
|
||||||
|
Constraints: types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "rancher Constraints Key 1",
|
||||||
|
Regex: "rancher Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "rancher Constraints Key 1",
|
||||||
|
Regex: "rancher Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trace: true,
|
||||||
|
DebugLogGeneratedTemplate: true,
|
||||||
|
},
|
||||||
|
APIConfiguration: rancher.APIConfiguration{
|
||||||
|
Endpoint: "rancher Endpoint",
|
||||||
|
AccessKey: "rancher AccessKey",
|
||||||
|
SecretKey: "rancher SecretKey",
|
||||||
|
},
|
||||||
|
API: &rancher.APIConfiguration{
|
||||||
|
Endpoint: "rancher Endpoint",
|
||||||
|
AccessKey: "rancher AccessKey",
|
||||||
|
SecretKey: "rancher SecretKey",
|
||||||
|
},
|
||||||
|
Metadata: &rancher.MetadataConfiguration{
|
||||||
|
IntervalPoll: true,
|
||||||
|
Prefix: "rancher Metadata Prefix",
|
||||||
|
},
|
||||||
|
Domain: "rancher Domain",
|
||||||
|
RefreshSeconds: 666,
|
||||||
|
ExposedByDefault: true,
|
||||||
|
EnableServiceHealthFilter: true,
|
||||||
|
}
|
||||||
|
config.DynamoDB = &dynamodb.Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{
|
||||||
|
Watch: true,
|
||||||
|
Filename: "dynamodb Filename",
|
||||||
|
Constraints: types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "dynamodb Constraints Key 1",
|
||||||
|
Regex: "dynamodb Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "dynamodb Constraints Key 1",
|
||||||
|
Regex: "dynamodb Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trace: true,
|
||||||
|
DebugLogGeneratedTemplate: true,
|
||||||
|
},
|
||||||
|
AccessKeyID: "dynamodb AccessKeyID",
|
||||||
|
RefreshSeconds: 666,
|
||||||
|
Region: "dynamodb Region",
|
||||||
|
SecretAccessKey: "dynamodb SecretAccessKey",
|
||||||
|
TableName: "dynamodb TableName",
|
||||||
|
Endpoint: "dynamodb Endpoint",
|
||||||
|
}
|
||||||
|
config.Etcd = &etcd.Provider{
|
||||||
|
Provider: kv.Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{
|
||||||
|
Watch: true,
|
||||||
|
Filename: "etcd Filename",
|
||||||
|
Constraints: types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "etcd Constraints Key 1",
|
||||||
|
Regex: "etcd Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "etcd Constraints Key 1",
|
||||||
|
Regex: "etcd Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trace: true,
|
||||||
|
DebugLogGeneratedTemplate: true,
|
||||||
|
},
|
||||||
|
Endpoint: "etcd Endpoint",
|
||||||
|
Prefix: "etcd Prefix",
|
||||||
|
TLS: &types.ClientTLS{
|
||||||
|
CA: "etcd CA",
|
||||||
|
Cert: "etcd Cert",
|
||||||
|
Key: "etcd Key",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
Username: "etcd Username",
|
||||||
|
Password: "etcd Password",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config.Zookeeper = &zk.Provider{
|
||||||
|
Provider: kv.Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{
|
||||||
|
Watch: true,
|
||||||
|
Filename: "zk Filename",
|
||||||
|
Constraints: types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "zk Constraints Key 1",
|
||||||
|
Regex: "zk Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "zk Constraints Key 1",
|
||||||
|
Regex: "zk Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trace: true,
|
||||||
|
DebugLogGeneratedTemplate: true,
|
||||||
|
},
|
||||||
|
Endpoint: "zk Endpoint",
|
||||||
|
Prefix: "zk Prefix",
|
||||||
|
TLS: &types.ClientTLS{
|
||||||
|
CA: "zk CA",
|
||||||
|
Cert: "zk Cert",
|
||||||
|
Key: "zk Key",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
Username: "zk Username",
|
||||||
|
Password: "zk Password",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config.Boltdb = &boltdb.Provider{
|
||||||
|
Provider: kv.Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{
|
||||||
|
Watch: true,
|
||||||
|
Filename: "boltdb Filename",
|
||||||
|
Constraints: types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "boltdb Constraints Key 1",
|
||||||
|
Regex: "boltdb Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "boltdb Constraints Key 1",
|
||||||
|
Regex: "boltdb Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trace: true,
|
||||||
|
DebugLogGeneratedTemplate: true,
|
||||||
|
},
|
||||||
|
Endpoint: "boltdb Endpoint",
|
||||||
|
Prefix: "boltdb Prefix",
|
||||||
|
TLS: &types.ClientTLS{
|
||||||
|
CA: "boltdb CA",
|
||||||
|
Cert: "boltdb Cert",
|
||||||
|
Key: "boltdb Key",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
Username: "boltdb Username",
|
||||||
|
Password: "boltdb Password",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config.Consul = &consul.Provider{
|
||||||
|
Provider: kv.Provider{
|
||||||
|
BaseProvider: provider.BaseProvider{
|
||||||
|
Watch: true,
|
||||||
|
Filename: "consul Filename",
|
||||||
|
Constraints: types.Constraints{
|
||||||
|
{
|
||||||
|
Key: "consul Constraints Key 1",
|
||||||
|
Regex: "consul Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "consul Constraints Key 1",
|
||||||
|
Regex: "consul Constraints Regex 2",
|
||||||
|
MustMatch: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Trace: true,
|
||||||
|
DebugLogGeneratedTemplate: true,
|
||||||
|
},
|
||||||
|
Endpoint: "consul Endpoint",
|
||||||
|
Prefix: "consul Prefix",
|
||||||
|
TLS: &types.ClientTLS{
|
||||||
|
CA: "consul CA",
|
||||||
|
Cert: "consul Cert",
|
||||||
|
Key: "consul Key",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
Username: "consul Username",
|
||||||
|
Password: "consul Password",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanJSON, err := Do(config, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err, cleanJSON)
|
||||||
|
}
|
||||||
|
}
|
||||||
238
cmd/traefik/anonymize/anonymize_doOnJSON_test.go
Normal file
238
cmd/traefik/anonymize/anonymize_doOnJSON_test.go
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
package anonymize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_doOnJSON(t *testing.T) {
|
||||||
|
baseConfiguration := `
|
||||||
|
{
|
||||||
|
"GraceTimeOut": 10000000000,
|
||||||
|
"Debug": false,
|
||||||
|
"CheckNewVersion": true,
|
||||||
|
"AccessLogsFile": "",
|
||||||
|
"TraefikLogsFile": "",
|
||||||
|
"LogLevel": "ERROR",
|
||||||
|
"EntryPoints": {
|
||||||
|
"http": {
|
||||||
|
"Network": "",
|
||||||
|
"Address": ":80",
|
||||||
|
"TLS": null,
|
||||||
|
"Redirect": {
|
||||||
|
"EntryPoint": "https",
|
||||||
|
"Regex": "",
|
||||||
|
"Replacement": ""
|
||||||
|
},
|
||||||
|
"Auth": null,
|
||||||
|
"Compress": false
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"Network": "",
|
||||||
|
"Address": ":443",
|
||||||
|
"TLS": {
|
||||||
|
"MinVersion": "",
|
||||||
|
"CipherSuites": null,
|
||||||
|
"Certificates": null,
|
||||||
|
"ClientCAFiles": null
|
||||||
|
},
|
||||||
|
"Redirect": null,
|
||||||
|
"Auth": null,
|
||||||
|
"Compress": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Cluster": null,
|
||||||
|
"Constraints": [],
|
||||||
|
"ACME": {
|
||||||
|
"Email": "foo@bar.com",
|
||||||
|
"Domains": [
|
||||||
|
{
|
||||||
|
"Main": "foo@bar.com",
|
||||||
|
"SANs": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Main": "foo@bar.com",
|
||||||
|
"SANs": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Storage": "",
|
||||||
|
"StorageFile": "/acme/acme.json",
|
||||||
|
"OnDemand": true,
|
||||||
|
"OnHostRule": true,
|
||||||
|
"CAServer": "",
|
||||||
|
"EntryPoint": "https",
|
||||||
|
"DNSProvider": "",
|
||||||
|
"DelayDontCheckDNS": 0,
|
||||||
|
"ACMELogging": false,
|
||||||
|
"TLSConfig": null
|
||||||
|
},
|
||||||
|
"DefaultEntryPoints": [
|
||||||
|
"https",
|
||||||
|
"http"
|
||||||
|
],
|
||||||
|
"ProvidersThrottleDuration": 2000000000,
|
||||||
|
"MaxIdleConnsPerHost": 200,
|
||||||
|
"IdleTimeout": 180000000000,
|
||||||
|
"InsecureSkipVerify": false,
|
||||||
|
"Retry": null,
|
||||||
|
"HealthCheck": {
|
||||||
|
"Interval": 30000000000
|
||||||
|
},
|
||||||
|
"Docker": null,
|
||||||
|
"File": null,
|
||||||
|
"Web": null,
|
||||||
|
"Marathon": null,
|
||||||
|
"Consul": null,
|
||||||
|
"ConsulCatalog": null,
|
||||||
|
"Etcd": null,
|
||||||
|
"Zookeeper": null,
|
||||||
|
"Boltdb": null,
|
||||||
|
"Kubernetes": null,
|
||||||
|
"Mesos": null,
|
||||||
|
"Eureka": null,
|
||||||
|
"ECS": null,
|
||||||
|
"Rancher": null,
|
||||||
|
"DynamoDB": null,
|
||||||
|
"ConfigFile": "/etc/traefik/traefik.toml"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
expectedConfiguration := `
|
||||||
|
{
|
||||||
|
"GraceTimeOut": 10000000000,
|
||||||
|
"Debug": false,
|
||||||
|
"CheckNewVersion": true,
|
||||||
|
"AccessLogsFile": "",
|
||||||
|
"TraefikLogsFile": "",
|
||||||
|
"LogLevel": "ERROR",
|
||||||
|
"EntryPoints": {
|
||||||
|
"http": {
|
||||||
|
"Network": "",
|
||||||
|
"Address": ":80",
|
||||||
|
"TLS": null,
|
||||||
|
"Redirect": {
|
||||||
|
"EntryPoint": "https",
|
||||||
|
"Regex": "",
|
||||||
|
"Replacement": ""
|
||||||
|
},
|
||||||
|
"Auth": null,
|
||||||
|
"Compress": false
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"Network": "",
|
||||||
|
"Address": ":443",
|
||||||
|
"TLS": {
|
||||||
|
"MinVersion": "",
|
||||||
|
"CipherSuites": null,
|
||||||
|
"Certificates": null,
|
||||||
|
"ClientCAFiles": null
|
||||||
|
},
|
||||||
|
"Redirect": null,
|
||||||
|
"Auth": null,
|
||||||
|
"Compress": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Cluster": null,
|
||||||
|
"Constraints": [],
|
||||||
|
"ACME": {
|
||||||
|
"Email": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
"Domains": [
|
||||||
|
{
|
||||||
|
"Main": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
"SANs": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Main": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
"SANs": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Storage": "",
|
||||||
|
"StorageFile": "/acme/acme.json",
|
||||||
|
"OnDemand": true,
|
||||||
|
"OnHostRule": true,
|
||||||
|
"CAServer": "",
|
||||||
|
"EntryPoint": "https",
|
||||||
|
"DNSProvider": "",
|
||||||
|
"DelayDontCheckDNS": 0,
|
||||||
|
"ACMELogging": false,
|
||||||
|
"TLSConfig": null
|
||||||
|
},
|
||||||
|
"DefaultEntryPoints": [
|
||||||
|
"https",
|
||||||
|
"http"
|
||||||
|
],
|
||||||
|
"ProvidersThrottleDuration": 2000000000,
|
||||||
|
"MaxIdleConnsPerHost": 200,
|
||||||
|
"IdleTimeout": 180000000000,
|
||||||
|
"InsecureSkipVerify": false,
|
||||||
|
"Retry": null,
|
||||||
|
"HealthCheck": {
|
||||||
|
"Interval": 30000000000
|
||||||
|
},
|
||||||
|
"Docker": null,
|
||||||
|
"File": null,
|
||||||
|
"Web": null,
|
||||||
|
"Marathon": null,
|
||||||
|
"Consul": null,
|
||||||
|
"ConsulCatalog": null,
|
||||||
|
"Etcd": null,
|
||||||
|
"Zookeeper": null,
|
||||||
|
"Boltdb": null,
|
||||||
|
"Kubernetes": null,
|
||||||
|
"Mesos": null,
|
||||||
|
"Eureka": null,
|
||||||
|
"ECS": null,
|
||||||
|
"Rancher": null,
|
||||||
|
"DynamoDB": null,
|
||||||
|
"ConfigFile": "/etc/traefik/traefik.toml"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
anomConfiguration := doOnJSON(baseConfiguration)
|
||||||
|
|
||||||
|
if anomConfiguration != expectedConfiguration {
|
||||||
|
t.Errorf("Got %s, want %s.", anomConfiguration, expectedConfiguration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_doOnJSON_simple(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expectedOutput string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "email",
|
||||||
|
input: `{
|
||||||
|
"email1": "goo@example.com",
|
||||||
|
"email2": "foo.bargoo@example.com",
|
||||||
|
"email3": "foo.bargoo@example.com.us"
|
||||||
|
}`,
|
||||||
|
expectedOutput: `{
|
||||||
|
"email1": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
"email2": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
"email3": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "url",
|
||||||
|
input: `{
|
||||||
|
"URL": "foo domain.com foo",
|
||||||
|
"URL": "foo sub.domain.com foo",
|
||||||
|
"URL": "foo sub.sub.domain.com foo",
|
||||||
|
"URL": "foo sub.sub.sub.domain.com.us foo"
|
||||||
|
}`,
|
||||||
|
expectedOutput: `{
|
||||||
|
"URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo",
|
||||||
|
"URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo",
|
||||||
|
"URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo",
|
||||||
|
"URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
output := doOnJSON(test.input)
|
||||||
|
assert.Equal(t, test.expectedOutput, output)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
176
cmd/traefik/anonymize/anonymize_doOnStruct_test.go
Normal file
176
cmd/traefik/anonymize/anonymize_doOnStruct_test.go
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
package anonymize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Courgette struct {
|
||||||
|
Ji string
|
||||||
|
Ho string
|
||||||
|
}
|
||||||
|
type Tomate struct {
|
||||||
|
Ji string
|
||||||
|
Ho string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Carotte struct {
|
||||||
|
Name string
|
||||||
|
Value int
|
||||||
|
Courgette Courgette
|
||||||
|
ECourgette Courgette `export:"true"`
|
||||||
|
Pourgette *Courgette
|
||||||
|
EPourgette *Courgette `export:"true"`
|
||||||
|
Aubergine map[string]string
|
||||||
|
EAubergine map[string]string `export:"true"`
|
||||||
|
SAubergine map[string]Tomate
|
||||||
|
ESAubergine map[string]Tomate `export:"true"`
|
||||||
|
PSAubergine map[string]*Tomate
|
||||||
|
EPAubergine map[string]*Tomate `export:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_doOnStruct(t *testing.T) {
|
||||||
|
testCase := []struct {
|
||||||
|
name string
|
||||||
|
base *Carotte
|
||||||
|
expected *Carotte
|
||||||
|
hasError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "primitive",
|
||||||
|
base: &Carotte{
|
||||||
|
Name: "koko",
|
||||||
|
Value: 666,
|
||||||
|
},
|
||||||
|
expected: &Carotte{
|
||||||
|
Name: "xxxx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "struct",
|
||||||
|
base: &Carotte{
|
||||||
|
Name: "koko",
|
||||||
|
Courgette: Courgette{
|
||||||
|
Ji: "huu",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &Carotte{
|
||||||
|
Name: "xxxx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pointer",
|
||||||
|
base: &Carotte{
|
||||||
|
Name: "koko",
|
||||||
|
Pourgette: &Courgette{
|
||||||
|
Ji: "hoo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &Carotte{
|
||||||
|
Name: "xxxx",
|
||||||
|
Pourgette: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "export struct",
|
||||||
|
base: &Carotte{
|
||||||
|
Name: "koko",
|
||||||
|
ECourgette: Courgette{
|
||||||
|
Ji: "huu",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &Carotte{
|
||||||
|
Name: "xxxx",
|
||||||
|
ECourgette: Courgette{
|
||||||
|
Ji: "xxxx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "export pointer struct",
|
||||||
|
base: &Carotte{
|
||||||
|
Name: "koko",
|
||||||
|
ECourgette: Courgette{
|
||||||
|
Ji: "huu",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &Carotte{
|
||||||
|
Name: "xxxx",
|
||||||
|
ECourgette: Courgette{
|
||||||
|
Ji: "xxxx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "export map string/string",
|
||||||
|
base: &Carotte{
|
||||||
|
Name: "koko",
|
||||||
|
EAubergine: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &Carotte{
|
||||||
|
Name: "xxxx",
|
||||||
|
EAubergine: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "export map string/pointer",
|
||||||
|
base: &Carotte{
|
||||||
|
Name: "koko",
|
||||||
|
EPAubergine: map[string]*Tomate{
|
||||||
|
"foo": {
|
||||||
|
Ji: "fdskljf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &Carotte{
|
||||||
|
Name: "xxxx",
|
||||||
|
EPAubergine: map[string]*Tomate{
|
||||||
|
"foo": {
|
||||||
|
Ji: "xxxx",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "export map string/struct (UNSAFE)",
|
||||||
|
base: &Carotte{
|
||||||
|
Name: "koko",
|
||||||
|
ESAubergine: map[string]Tomate{
|
||||||
|
"foo": {
|
||||||
|
Ji: "JiJiJi",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &Carotte{
|
||||||
|
Name: "xxxx",
|
||||||
|
ESAubergine: map[string]Tomate{
|
||||||
|
"foo": {
|
||||||
|
Ji: "JiJiJi",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCase {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
val := reflect.ValueOf(test.base).Elem()
|
||||||
|
err := doOnStruct(val)
|
||||||
|
if !test.hasError && err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if test.hasError && err == nil {
|
||||||
|
t.Fatal("Got no error but want an error.")
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.EqualValues(t, test.expected, test.base)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
169
cmd/traefik/bug.go
Normal file
169
cmd/traefik/bug.go
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/containous/flaeg"
|
||||||
|
"github.com/containous/traefik/cmd/traefik/anonymize"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bugTracker = "https://github.com/containous/traefik/issues/new"
|
||||||
|
bugTemplate = `<!--
|
||||||
|
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
|
||||||
|
|
||||||
|
The issue tracker is for reporting bugs and feature requests only.
|
||||||
|
For end-user related support questions, refer to one of the following:
|
||||||
|
|
||||||
|
- Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik
|
||||||
|
- the Traefik community Slack channel: https://traefik.herokuapp.com
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Do you want to request a *feature* or report a *bug*?
|
||||||
|
|
||||||
|
(If you intend to ask a support question: **DO NOT FILE AN ISSUE**.
|
||||||
|
Use [Stack Overflow](https://stackoverflow.com/questions/tagged/traefik)
|
||||||
|
or [Slack](https://traefik.herokuapp.com) instead.)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### What did you do?
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
HOW TO WRITE A GOOD ISSUE?
|
||||||
|
|
||||||
|
- Respect the issue template as more as possible.
|
||||||
|
- If it's possible use the command ` + "`" + "traefik bug" + "`" + `. See https://www.youtube.com/watch?v=Lyz62L8m93I.
|
||||||
|
- The title must be short and descriptive.
|
||||||
|
- Explain the conditions which led you to write this issue: the context.
|
||||||
|
- The context should lead to something, an idea or a problem that you’re facing.
|
||||||
|
- Remain clear and concise.
|
||||||
|
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
### What did you expect to see?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### What did you see instead?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Output of ` + "`" + `traefik version` + "`" + `: (_What version of Traefik are you using?_)
|
||||||
|
|
||||||
|
` + "```" + `
|
||||||
|
{{.Version}}
|
||||||
|
` + "```" + `
|
||||||
|
|
||||||
|
### What is your environment & configuration (arguments, toml, provider, platform, ...)?
|
||||||
|
|
||||||
|
` + "```" + `json
|
||||||
|
{{.Configuration}}
|
||||||
|
` + "```" + `
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Add more configuration information here.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### If applicable, please paste the log output in debug mode (` + "`" + `--debug` + "`" + ` switch)
|
||||||
|
|
||||||
|
` + "```" + `
|
||||||
|
(paste your output here)
|
||||||
|
` + "```" + `
|
||||||
|
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
// newBugCmd builds a new Bug command
|
||||||
|
func newBugCmd(traefikConfiguration interface{}, traefikPointersConfiguration interface{}) *flaeg.Command {
|
||||||
|
|
||||||
|
//version Command init
|
||||||
|
return &flaeg.Command{
|
||||||
|
Name: "bug",
|
||||||
|
Description: `Report an issue on Traefik bugtracker`,
|
||||||
|
Config: traefikConfiguration,
|
||||||
|
DefaultPointersConfig: traefikPointersConfiguration,
|
||||||
|
Run: runBugCmd(traefikConfiguration),
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"parseAllSources": "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runBugCmd(traefikConfiguration interface{}) func() error {
|
||||||
|
return func() error {
|
||||||
|
|
||||||
|
body, err := createBugReport(traefikConfiguration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sendBugReport(body)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createBugReport(traefikConfiguration interface{}) (string, error) {
|
||||||
|
var version bytes.Buffer
|
||||||
|
if err := getVersionPrint(&version); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("bug").Parse(bugTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := anonymize.Do(&traefikConfiguration, true)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := struct {
|
||||||
|
Version string
|
||||||
|
Configuration string
|
||||||
|
}{
|
||||||
|
Version: version.String(),
|
||||||
|
Configuration: config,
|
||||||
|
}
|
||||||
|
|
||||||
|
var bug bytes.Buffer
|
||||||
|
if err := tmpl.Execute(&bug, v); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bug.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendBugReport(body string) {
|
||||||
|
URL := bugTracker + "?body=" + url.QueryEscape(body)
|
||||||
|
if err := openBrowser(URL); err != nil {
|
||||||
|
fmt.Printf("Please file a new issue at %s using this template:\n\n", bugTracker)
|
||||||
|
fmt.Print(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func openBrowser(URL string) error {
|
||||||
|
var err error
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
err = exec.Command("xdg-open", URL).Start()
|
||||||
|
case "windows":
|
||||||
|
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", URL).Start()
|
||||||
|
case "darwin":
|
||||||
|
err = exec.Command("open", URL).Start()
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unsupported platform")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
49
cmd/traefik/bug_test.go
Normal file
49
cmd/traefik/bug_test.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/cmd/traefik/anonymize"
|
||||||
|
"github.com/containous/traefik/configuration"
|
||||||
|
"github.com/containous/traefik/provider/file"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_createBugReport(t *testing.T) {
|
||||||
|
traefikConfiguration := TraefikConfiguration{
|
||||||
|
ConfigFile: "FOO",
|
||||||
|
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||||
|
EntryPoints: configuration.EntryPoints{
|
||||||
|
"goo": &configuration.EntryPoint{
|
||||||
|
Address: "hoo.bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
File: &file.Provider{
|
||||||
|
Directory: "BAR",
|
||||||
|
},
|
||||||
|
RootCAs: configuration.RootCAs{"fllf"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
report, err := createBugReport(traefikConfiguration)
|
||||||
|
assert.NoError(t, err, report)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_anonymize_traefikConfiguration(t *testing.T) {
|
||||||
|
traefikConfiguration := &TraefikConfiguration{
|
||||||
|
ConfigFile: "FOO",
|
||||||
|
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||||
|
EntryPoints: configuration.EntryPoints{
|
||||||
|
"goo": &configuration.EntryPoint{
|
||||||
|
Address: "hoo.bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
File: &file.Provider{
|
||||||
|
Directory: "BAR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := anonymize.Do(traefikConfiguration, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "hoo.bar", traefikConfiguration.GlobalConfiguration.EntryPoints["goo"].Address)
|
||||||
|
}
|
||||||
226
cmd/traefik/configuration.go
Normal file
226
cmd/traefik/configuration.go
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containous/flaeg"
|
||||||
|
"github.com/containous/traefik/configuration"
|
||||||
|
"github.com/containous/traefik/middlewares/accesslog"
|
||||||
|
"github.com/containous/traefik/provider/boltdb"
|
||||||
|
"github.com/containous/traefik/provider/consul"
|
||||||
|
"github.com/containous/traefik/provider/docker"
|
||||||
|
"github.com/containous/traefik/provider/dynamodb"
|
||||||
|
"github.com/containous/traefik/provider/ecs"
|
||||||
|
"github.com/containous/traefik/provider/etcd"
|
||||||
|
"github.com/containous/traefik/provider/eureka"
|
||||||
|
"github.com/containous/traefik/provider/file"
|
||||||
|
"github.com/containous/traefik/provider/kubernetes"
|
||||||
|
"github.com/containous/traefik/provider/marathon"
|
||||||
|
"github.com/containous/traefik/provider/mesos"
|
||||||
|
"github.com/containous/traefik/provider/rancher"
|
||||||
|
"github.com/containous/traefik/provider/web"
|
||||||
|
"github.com/containous/traefik/provider/zk"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TraefikConfiguration holds GlobalConfiguration and other stuff
|
||||||
|
type TraefikConfiguration struct {
|
||||||
|
configuration.GlobalConfiguration `mapstructure:",squash" export:"true"`
|
||||||
|
ConfigFile string `short:"c" description:"Configuration file to use (TOML)." export:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
|
||||||
|
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
|
//default Docker
|
||||||
|
var defaultDocker docker.Provider
|
||||||
|
defaultDocker.Watch = true
|
||||||
|
defaultDocker.ExposedByDefault = true
|
||||||
|
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
|
||||||
|
defaultDocker.SwarmMode = false
|
||||||
|
|
||||||
|
// default File
|
||||||
|
var defaultFile file.Provider
|
||||||
|
defaultFile.Watch = true
|
||||||
|
defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed()
|
||||||
|
|
||||||
|
// default Web
|
||||||
|
var defaultWeb web.Provider
|
||||||
|
defaultWeb.Address = ":8080"
|
||||||
|
defaultWeb.Statistics = &types.Statistics{
|
||||||
|
RecentErrors: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
// default Metrics
|
||||||
|
defaultWeb.Metrics = &types.Metrics{
|
||||||
|
Prometheus: &types.Prometheus{
|
||||||
|
Buckets: types.Buckets{0.1, 0.3, 1.2, 5},
|
||||||
|
},
|
||||||
|
Datadog: &types.Datadog{
|
||||||
|
Address: "localhost:8125",
|
||||||
|
PushInterval: "10s",
|
||||||
|
},
|
||||||
|
StatsD: &types.Statsd{
|
||||||
|
Address: "localhost:8125",
|
||||||
|
PushInterval: "10s",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// default Marathon
|
||||||
|
var defaultMarathon marathon.Provider
|
||||||
|
defaultMarathon.Watch = true
|
||||||
|
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
|
||||||
|
defaultMarathon.ExposedByDefault = true
|
||||||
|
defaultMarathon.Constraints = types.Constraints{}
|
||||||
|
defaultMarathon.DialerTimeout = flaeg.Duration(60 * time.Second)
|
||||||
|
defaultMarathon.KeepAlive = flaeg.Duration(10 * time.Second)
|
||||||
|
|
||||||
|
// default Consul
|
||||||
|
var defaultConsul consul.Provider
|
||||||
|
defaultConsul.Watch = true
|
||||||
|
defaultConsul.Endpoint = "127.0.0.1:8500"
|
||||||
|
defaultConsul.Prefix = "traefik"
|
||||||
|
defaultConsul.Constraints = types.Constraints{}
|
||||||
|
|
||||||
|
// default CatalogProvider
|
||||||
|
var defaultConsulCatalog consul.CatalogProvider
|
||||||
|
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
|
||||||
|
defaultConsulCatalog.ExposedByDefault = true
|
||||||
|
defaultConsulCatalog.Constraints = types.Constraints{}
|
||||||
|
defaultConsulCatalog.Prefix = "traefik"
|
||||||
|
defaultConsulCatalog.FrontEndRule = "Host:{{.ServiceName}}.{{.Domain}}"
|
||||||
|
|
||||||
|
// default Etcd
|
||||||
|
var defaultEtcd etcd.Provider
|
||||||
|
defaultEtcd.Watch = true
|
||||||
|
defaultEtcd.Endpoint = "127.0.0.1:2379"
|
||||||
|
defaultEtcd.Prefix = "/traefik"
|
||||||
|
defaultEtcd.Constraints = types.Constraints{}
|
||||||
|
|
||||||
|
//default Zookeeper
|
||||||
|
var defaultZookeeper zk.Provider
|
||||||
|
defaultZookeeper.Watch = true
|
||||||
|
defaultZookeeper.Endpoint = "127.0.0.1:2181"
|
||||||
|
defaultZookeeper.Prefix = "/traefik"
|
||||||
|
defaultZookeeper.Constraints = types.Constraints{}
|
||||||
|
|
||||||
|
//default Boltdb
|
||||||
|
var defaultBoltDb boltdb.Provider
|
||||||
|
defaultBoltDb.Watch = true
|
||||||
|
defaultBoltDb.Endpoint = "127.0.0.1:4001"
|
||||||
|
defaultBoltDb.Prefix = "/traefik"
|
||||||
|
defaultBoltDb.Constraints = types.Constraints{}
|
||||||
|
|
||||||
|
//default Kubernetes
|
||||||
|
var defaultKubernetes kubernetes.Provider
|
||||||
|
defaultKubernetes.Watch = true
|
||||||
|
defaultKubernetes.Endpoint = ""
|
||||||
|
defaultKubernetes.LabelSelector = ""
|
||||||
|
defaultKubernetes.Constraints = types.Constraints{}
|
||||||
|
|
||||||
|
// default Mesos
|
||||||
|
var defaultMesos mesos.Provider
|
||||||
|
defaultMesos.Watch = true
|
||||||
|
defaultMesos.Endpoint = "http://127.0.0.1:5050"
|
||||||
|
defaultMesos.ExposedByDefault = true
|
||||||
|
defaultMesos.Constraints = types.Constraints{}
|
||||||
|
defaultMesos.RefreshSeconds = 30
|
||||||
|
defaultMesos.ZkDetectionTimeout = 30
|
||||||
|
defaultMesos.StateTimeoutSecond = 30
|
||||||
|
|
||||||
|
//default ECS
|
||||||
|
var defaultECS ecs.Provider
|
||||||
|
defaultECS.Watch = true
|
||||||
|
defaultECS.ExposedByDefault = true
|
||||||
|
defaultECS.AutoDiscoverClusters = false
|
||||||
|
defaultECS.Clusters = ecs.Clusters{"default"}
|
||||||
|
defaultECS.RefreshSeconds = 15
|
||||||
|
defaultECS.Constraints = types.Constraints{}
|
||||||
|
|
||||||
|
//default Rancher
|
||||||
|
var defaultRancher rancher.Provider
|
||||||
|
defaultRancher.Watch = true
|
||||||
|
defaultRancher.ExposedByDefault = true
|
||||||
|
defaultRancher.RefreshSeconds = 15
|
||||||
|
|
||||||
|
// default DynamoDB
|
||||||
|
var defaultDynamoDB dynamodb.Provider
|
||||||
|
defaultDynamoDB.Constraints = types.Constraints{}
|
||||||
|
defaultDynamoDB.RefreshSeconds = 15
|
||||||
|
defaultDynamoDB.TableName = "traefik"
|
||||||
|
defaultDynamoDB.Watch = true
|
||||||
|
|
||||||
|
// default Eureka
|
||||||
|
var defaultEureka eureka.Provider
|
||||||
|
defaultEureka.Delay = "30s"
|
||||||
|
|
||||||
|
// default AccessLog
|
||||||
|
defaultAccessLog := types.AccessLog{
|
||||||
|
Format: accesslog.CommonFormat,
|
||||||
|
FilePath: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
// default HealthCheckConfig
|
||||||
|
healthCheck := configuration.HealthCheckConfig{
|
||||||
|
Interval: flaeg.Duration(configuration.DefaultHealthCheckInterval),
|
||||||
|
}
|
||||||
|
|
||||||
|
// default RespondingTimeouts
|
||||||
|
respondingTimeouts := configuration.RespondingTimeouts{
|
||||||
|
IdleTimeout: flaeg.Duration(configuration.DefaultIdleTimeout),
|
||||||
|
}
|
||||||
|
|
||||||
|
// default ForwardingTimeouts
|
||||||
|
forwardingTimeouts := configuration.ForwardingTimeouts{
|
||||||
|
DialTimeout: flaeg.Duration(configuration.DefaultDialTimeout),
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfiguration := configuration.GlobalConfiguration{
|
||||||
|
Docker: &defaultDocker,
|
||||||
|
File: &defaultFile,
|
||||||
|
Web: &defaultWeb,
|
||||||
|
Marathon: &defaultMarathon,
|
||||||
|
Consul: &defaultConsul,
|
||||||
|
ConsulCatalog: &defaultConsulCatalog,
|
||||||
|
Etcd: &defaultEtcd,
|
||||||
|
Zookeeper: &defaultZookeeper,
|
||||||
|
Boltdb: &defaultBoltDb,
|
||||||
|
Kubernetes: &defaultKubernetes,
|
||||||
|
Mesos: &defaultMesos,
|
||||||
|
ECS: &defaultECS,
|
||||||
|
Rancher: &defaultRancher,
|
||||||
|
Eureka: &defaultEureka,
|
||||||
|
DynamoDB: &defaultDynamoDB,
|
||||||
|
Retry: &configuration.Retry{},
|
||||||
|
HealthCheck: &healthCheck,
|
||||||
|
AccessLog: &defaultAccessLog,
|
||||||
|
RespondingTimeouts: &respondingTimeouts,
|
||||||
|
ForwardingTimeouts: &forwardingTimeouts,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TraefikConfiguration{
|
||||||
|
GlobalConfiguration: defaultConfiguration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTraefikConfiguration creates a TraefikConfiguration with default values
|
||||||
|
func NewTraefikConfiguration() *TraefikConfiguration {
|
||||||
|
return &TraefikConfiguration{
|
||||||
|
GlobalConfiguration: configuration.GlobalConfiguration{
|
||||||
|
GraceTimeOut: flaeg.Duration(10 * time.Second),
|
||||||
|
AccessLogsFile: "",
|
||||||
|
TraefikLogsFile: "",
|
||||||
|
LogLevel: "ERROR",
|
||||||
|
EntryPoints: map[string]*configuration.EntryPoint{},
|
||||||
|
Constraints: types.Constraints{},
|
||||||
|
DefaultEntryPoints: []string{},
|
||||||
|
ProvidersThrottleDuration: flaeg.Duration(2 * time.Second),
|
||||||
|
MaxIdleConnsPerHost: 200,
|
||||||
|
IdleTimeout: flaeg.Duration(0),
|
||||||
|
HealthCheck: &configuration.HealthCheckConfig{
|
||||||
|
Interval: flaeg.Duration(configuration.DefaultHealthCheckInterval),
|
||||||
|
},
|
||||||
|
CheckNewVersion: true,
|
||||||
|
},
|
||||||
|
ConfigFile: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
357
cmd/traefik/traefik.go
Normal file
357
cmd/traefik/traefik.go
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
fmtlog "log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/cenk/backoff"
|
||||||
|
"github.com/containous/flaeg"
|
||||||
|
"github.com/containous/staert"
|
||||||
|
"github.com/containous/traefik/acme"
|
||||||
|
"github.com/containous/traefik/cluster"
|
||||||
|
"github.com/containous/traefik/configuration"
|
||||||
|
"github.com/containous/traefik/job"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/provider/ecs"
|
||||||
|
"github.com/containous/traefik/provider/kubernetes"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
|
"github.com/containous/traefik/server"
|
||||||
|
"github.com/containous/traefik/server/uuid"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
"github.com/containous/traefik/version"
|
||||||
|
"github.com/coreos/go-systemd/daemon"
|
||||||
|
"github.com/docker/libkv/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
|
||||||
|
//traefik config inits
|
||||||
|
traefikConfiguration := NewTraefikConfiguration()
|
||||||
|
traefikPointersConfiguration := NewTraefikDefaultPointersConfiguration()
|
||||||
|
//traefik Command init
|
||||||
|
traefikCmd := &flaeg.Command{
|
||||||
|
Name: "traefik",
|
||||||
|
Description: `traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||||
|
Complete documentation is available at https://traefik.io`,
|
||||||
|
Config: traefikConfiguration,
|
||||||
|
DefaultPointersConfig: traefikPointersConfiguration,
|
||||||
|
Run: func() error {
|
||||||
|
globalConfiguration := traefikConfiguration.GlobalConfiguration
|
||||||
|
if globalConfiguration.File != nil && len(globalConfiguration.File.Filename) == 0 {
|
||||||
|
// no filename, setting to global config file
|
||||||
|
if len(traefikConfiguration.ConfigFile) != 0 {
|
||||||
|
globalConfiguration.File.Filename = traefikConfiguration.ConfigFile
|
||||||
|
} else {
|
||||||
|
log.Errorln("Error using file configuration backend, no filename defined")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(traefikConfiguration.ConfigFile) != 0 {
|
||||||
|
log.Infof("Using TOML configuration file %s", traefikConfiguration.ConfigFile)
|
||||||
|
}
|
||||||
|
run(&globalConfiguration)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
//storeconfig Command init
|
||||||
|
var kv *staert.KvSource
|
||||||
|
var err error
|
||||||
|
|
||||||
|
storeConfigCmd := &flaeg.Command{
|
||||||
|
Name: "storeconfig",
|
||||||
|
Description: `Store the static traefik configuration into a Key-value stores. Traefik will not start.`,
|
||||||
|
Config: traefikConfiguration,
|
||||||
|
DefaultPointersConfig: traefikPointersConfiguration,
|
||||||
|
Run: func() error {
|
||||||
|
if kv == nil {
|
||||||
|
return fmt.Errorf("Error using command storeconfig, no Key-value store defined")
|
||||||
|
}
|
||||||
|
jsonConf, err := json.Marshal(traefikConfiguration.GlobalConfiguration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmtlog.Printf("Storing configuration: %s\n", jsonConf)
|
||||||
|
err = kv.StoreConfig(traefikConfiguration.GlobalConfiguration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if traefikConfiguration.GlobalConfiguration.ACME != nil && len(traefikConfiguration.GlobalConfiguration.ACME.StorageFile) > 0 {
|
||||||
|
// convert ACME json file to KV store
|
||||||
|
localStore := acme.NewLocalStore(traefikConfiguration.GlobalConfiguration.ACME.StorageFile)
|
||||||
|
object, err := localStore.Load()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
meta := cluster.NewMetadata(object)
|
||||||
|
err = meta.Marshall()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
source := staert.KvSource{
|
||||||
|
Store: kv,
|
||||||
|
Prefix: traefikConfiguration.GlobalConfiguration.ACME.Storage,
|
||||||
|
}
|
||||||
|
err = source.StoreConfig(meta)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"parseAllSources": "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
healthCheckCmd := &flaeg.Command{
|
||||||
|
Name: "healthcheck",
|
||||||
|
Description: `Calls traefik /ping to check health (web provider must be enabled)`,
|
||||||
|
Config: traefikConfiguration,
|
||||||
|
DefaultPointersConfig: traefikPointersConfiguration,
|
||||||
|
Run: func() error {
|
||||||
|
traefikConfiguration.GlobalConfiguration.SetEffectiveConfiguration()
|
||||||
|
|
||||||
|
if traefikConfiguration.Web == nil {
|
||||||
|
fmt.Println("Please enable the web provider to use healtcheck.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
client := &http.Client{Timeout: 5 * time.Second}
|
||||||
|
protocol := "http"
|
||||||
|
if len(traefikConfiguration.Web.CertFile) > 0 {
|
||||||
|
protocol = "https"
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
client.Transport = tr
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Head(protocol + "://" + traefikConfiguration.Web.Address + traefikConfiguration.Web.Path + "ping")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error calling healthcheck: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
fmt.Printf("Bad healthcheck status: %s\n", resp.Status)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("OK: %s\n", resp.Request.URL)
|
||||||
|
os.Exit(0)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"parseAllSources": "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
//init flaeg source
|
||||||
|
f := flaeg.New(traefikCmd, os.Args[1:])
|
||||||
|
//add custom parsers
|
||||||
|
f.AddParser(reflect.TypeOf(configuration.EntryPoints{}), &configuration.EntryPoints{})
|
||||||
|
f.AddParser(reflect.TypeOf(configuration.DefaultEntryPoints{}), &configuration.DefaultEntryPoints{})
|
||||||
|
f.AddParser(reflect.TypeOf(configuration.RootCAs{}), &configuration.RootCAs{})
|
||||||
|
f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{})
|
||||||
|
f.AddParser(reflect.TypeOf(kubernetes.Namespaces{}), &kubernetes.Namespaces{})
|
||||||
|
f.AddParser(reflect.TypeOf(ecs.Clusters{}), &ecs.Clusters{})
|
||||||
|
f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{})
|
||||||
|
f.AddParser(reflect.TypeOf(types.Buckets{}), &types.Buckets{})
|
||||||
|
|
||||||
|
//add commands
|
||||||
|
f.AddCommand(newVersionCmd())
|
||||||
|
f.AddCommand(newBugCmd(traefikConfiguration, traefikPointersConfiguration))
|
||||||
|
f.AddCommand(storeConfigCmd)
|
||||||
|
f.AddCommand(healthCheckCmd)
|
||||||
|
|
||||||
|
usedCmd, err := f.GetCommand()
|
||||||
|
if err != nil {
|
||||||
|
fmtlog.Println(err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := f.Parse(usedCmd); err != nil {
|
||||||
|
fmtlog.Printf("Error parsing command: %s\n", err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
//staert init
|
||||||
|
s := staert.NewStaert(traefikCmd)
|
||||||
|
//init toml source
|
||||||
|
toml := staert.NewTomlSource("traefik", []string{traefikConfiguration.ConfigFile, "/etc/traefik/", "$HOME/.traefik/", "."})
|
||||||
|
|
||||||
|
//add sources to staert
|
||||||
|
s.AddSource(toml)
|
||||||
|
s.AddSource(f)
|
||||||
|
if _, err := s.LoadConfig(); err != nil {
|
||||||
|
fmtlog.Printf("Error reading TOML config file %s : %s\n", toml.ConfigFileUsed(), err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
traefikConfiguration.ConfigFile = toml.ConfigFileUsed()
|
||||||
|
|
||||||
|
kv, err = CreateKvSource(traefikConfiguration)
|
||||||
|
if err != nil {
|
||||||
|
fmtlog.Printf("Error creating kv store: %s\n", err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IF a KV Store is enable and no sub-command called in args
|
||||||
|
if kv != nil && usedCmd == traefikCmd {
|
||||||
|
if traefikConfiguration.Cluster == nil {
|
||||||
|
traefikConfiguration.Cluster = &types.Cluster{Node: uuid.Get()}
|
||||||
|
}
|
||||||
|
if traefikConfiguration.Cluster.Store == nil {
|
||||||
|
traefikConfiguration.Cluster.Store = &types.Store{Prefix: kv.Prefix, Store: kv.Store}
|
||||||
|
}
|
||||||
|
s.AddSource(kv)
|
||||||
|
operation := func() error {
|
||||||
|
_, err := s.LoadConfig()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
notify := func(err error, time time.Duration) {
|
||||||
|
log.Errorf("Load config error: %+v, retrying in %s", err, time)
|
||||||
|
}
|
||||||
|
err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify)
|
||||||
|
if err != nil {
|
||||||
|
fmtlog.Printf("Error loading configuration: %s\n", err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Run(); err != nil {
|
||||||
|
fmtlog.Printf("Error running traefik: %s\n", err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(globalConfiguration *configuration.GlobalConfiguration) {
|
||||||
|
fmtlog.SetFlags(fmtlog.Lshortfile | fmtlog.LstdFlags)
|
||||||
|
|
||||||
|
http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment
|
||||||
|
|
||||||
|
globalConfiguration.SetEffectiveConfiguration()
|
||||||
|
|
||||||
|
// logging
|
||||||
|
level, err := logrus.ParseLevel(strings.ToLower(globalConfiguration.LogLevel))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error getting level", err)
|
||||||
|
}
|
||||||
|
log.SetLevel(level)
|
||||||
|
|
||||||
|
if len(globalConfiguration.TraefikLogsFile) > 0 {
|
||||||
|
dir := filepath.Dir(globalConfiguration.TraefikLogsFile)
|
||||||
|
|
||||||
|
err := os.MkdirAll(dir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to create log path %s: %s", dir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = log.OpenFile(globalConfiguration.TraefikLogsFile)
|
||||||
|
defer func() {
|
||||||
|
if err := log.CloseFile(); err != nil {
|
||||||
|
log.Error("Error closing log", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error opening file", err)
|
||||||
|
} else {
|
||||||
|
log.SetFormatter(&logrus.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.SetFormatter(&logrus.TextFormatter{FullTimestamp: true, DisableSorting: true})
|
||||||
|
}
|
||||||
|
jsonConf, _ := json.Marshal(globalConfiguration)
|
||||||
|
log.Infof("Traefik version %s built on %s", version.Version, version.BuildDate)
|
||||||
|
|
||||||
|
if globalConfiguration.CheckNewVersion {
|
||||||
|
checkNewVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Global configuration loaded %s", string(jsonConf))
|
||||||
|
svr := server.NewServer(*globalConfiguration)
|
||||||
|
svr.Start()
|
||||||
|
defer svr.Close()
|
||||||
|
sent, err := daemon.SdNotify(false, "READY=1")
|
||||||
|
if !sent && err != nil {
|
||||||
|
log.Error("Fail to notify", err)
|
||||||
|
}
|
||||||
|
t, err := daemon.SdWatchdogEnabled(false)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Problem with watchdog", err)
|
||||||
|
} else if t != 0 {
|
||||||
|
// Send a ping each half time given
|
||||||
|
t = t / 2
|
||||||
|
log.Info("Watchdog activated with timer each ", t)
|
||||||
|
safe.Go(func() {
|
||||||
|
tick := time.Tick(t)
|
||||||
|
for range tick {
|
||||||
|
if ok, _ := daemon.SdNotify(false, "WATCHDOG=1"); !ok {
|
||||||
|
log.Error("Fail to tick watchdog")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
svr.Wait()
|
||||||
|
log.Info("Shutting down")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateKvSource creates KvSource
|
||||||
|
// TLS support is enable for Consul and Etcd backends
|
||||||
|
func CreateKvSource(traefikConfiguration *TraefikConfiguration) (*staert.KvSource, error) {
|
||||||
|
var kv *staert.KvSource
|
||||||
|
var kvStore store.Store
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case traefikConfiguration.Consul != nil:
|
||||||
|
kvStore, err = traefikConfiguration.Consul.CreateStore()
|
||||||
|
kv = &staert.KvSource{
|
||||||
|
Store: kvStore,
|
||||||
|
Prefix: traefikConfiguration.Consul.Prefix,
|
||||||
|
}
|
||||||
|
case traefikConfiguration.Etcd != nil:
|
||||||
|
kvStore, err = traefikConfiguration.Etcd.CreateStore()
|
||||||
|
kv = &staert.KvSource{
|
||||||
|
Store: kvStore,
|
||||||
|
Prefix: traefikConfiguration.Etcd.Prefix,
|
||||||
|
}
|
||||||
|
case traefikConfiguration.Zookeeper != nil:
|
||||||
|
kvStore, err = traefikConfiguration.Zookeeper.CreateStore()
|
||||||
|
kv = &staert.KvSource{
|
||||||
|
Store: kvStore,
|
||||||
|
Prefix: traefikConfiguration.Zookeeper.Prefix,
|
||||||
|
}
|
||||||
|
case traefikConfiguration.Boltdb != nil:
|
||||||
|
kvStore, err = traefikConfiguration.Boltdb.CreateStore()
|
||||||
|
kv = &staert.KvSource{
|
||||||
|
Store: kvStore,
|
||||||
|
Prefix: traefikConfiguration.Boltdb.Prefix,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return kv, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkNewVersion() {
|
||||||
|
ticker := time.NewTicker(24 * time.Hour)
|
||||||
|
safe.Go(func() {
|
||||||
|
time.Sleep(10 * time.Minute)
|
||||||
|
version.CheckNewVersion()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
version.CheckNewVersion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
63
cmd/traefik/version.go
Normal file
63
cmd/traefik/version.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/containous/flaeg"
|
||||||
|
"github.com/containous/traefik/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
var versionTemplate = `Version: {{.Version}}
|
||||||
|
Codename: {{.Codename}}
|
||||||
|
Go version: {{.GoVersion}}
|
||||||
|
Built: {{.BuildTime}}
|
||||||
|
OS/Arch: {{.Os}}/{{.Arch}}`
|
||||||
|
|
||||||
|
// newVersionCmd builds a new Version command
|
||||||
|
func newVersionCmd() *flaeg.Command {
|
||||||
|
|
||||||
|
//version Command init
|
||||||
|
return &flaeg.Command{
|
||||||
|
Name: "version",
|
||||||
|
Description: `Print version`,
|
||||||
|
Config: struct{}{},
|
||||||
|
DefaultPointersConfig: struct{}{},
|
||||||
|
Run: func() error {
|
||||||
|
if err := getVersionPrint(os.Stdout); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Print("\n")
|
||||||
|
return nil
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getVersionPrint(wr io.Writer) error {
|
||||||
|
tmpl, err := template.New("").Parse(versionTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := struct {
|
||||||
|
Version string
|
||||||
|
Codename string
|
||||||
|
GoVersion string
|
||||||
|
BuildTime string
|
||||||
|
Os string
|
||||||
|
Arch string
|
||||||
|
}{
|
||||||
|
Version: version.Version,
|
||||||
|
Codename: version.Codename,
|
||||||
|
GoVersion: runtime.Version(),
|
||||||
|
BuildTime: version.BuildDate,
|
||||||
|
Os: runtime.GOOS,
|
||||||
|
Arch: runtime.GOARCH,
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpl.Execute(wr, v)
|
||||||
|
}
|
||||||
306
configuration.go
306
configuration.go
@@ -1,306 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
fmtlog "log"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/containous/traefik/acme"
|
|
||||||
"github.com/containous/traefik/provider"
|
|
||||||
"github.com/containous/traefik/types"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GlobalConfiguration holds global configuration (with providers, etc.).
|
|
||||||
// It's populated from the traefik configuration file passed as an argument to the binary.
|
|
||||||
type GlobalConfiguration struct {
|
|
||||||
GraceTimeOut int64
|
|
||||||
AccessLogsFile string
|
|
||||||
TraefikLogsFile string
|
|
||||||
LogLevel string
|
|
||||||
EntryPoints EntryPoints
|
|
||||||
ACME *acme.ACME
|
|
||||||
DefaultEntryPoints DefaultEntryPoints
|
|
||||||
ProvidersThrottleDuration time.Duration
|
|
||||||
MaxIdleConnsPerHost int
|
|
||||||
Retry *Retry
|
|
||||||
Docker *provider.Docker
|
|
||||||
File *provider.File
|
|
||||||
Web *WebProvider
|
|
||||||
Marathon *provider.Marathon
|
|
||||||
Consul *provider.Consul
|
|
||||||
ConsulCatalog *provider.ConsulCatalog
|
|
||||||
Etcd *provider.Etcd
|
|
||||||
Zookeeper *provider.Zookepper
|
|
||||||
Boltdb *provider.BoltDb
|
|
||||||
Kubernetes *provider.Kubernetes
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultEntryPoints holds default entry points
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
|
||||||
// Set's argument is a string to be parsed to set the flag.
|
|
||||||
// It's a comma-separated list, so we split it.
|
|
||||||
func (dep *DefaultEntryPoints) Set(value string) error {
|
|
||||||
entrypoints := strings.Split(value, ",")
|
|
||||||
if len(entrypoints) == 0 {
|
|
||||||
return errors.New("Bad DefaultEntryPoints format: " + value)
|
|
||||||
}
|
|
||||||
for _, entrypoint := range entrypoints {
|
|
||||||
*dep = append(*dep, entrypoint)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type is type of the struct
|
|
||||||
func (dep *DefaultEntryPoints) Type() string {
|
|
||||||
return fmt.Sprint("defaultentrypoints²")
|
|
||||||
}
|
|
||||||
|
|
||||||
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
|
|
||||||
type EntryPoints map[string]*EntryPoint
|
|
||||||
|
|
||||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
|
||||||
// The String method's output will be used in diagnostics.
|
|
||||||
func (ep *EntryPoints) String() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
|
||||||
// Set's argument is a string to be parsed to set the flag.
|
|
||||||
// It's a comma-separated list, so we split it.
|
|
||||||
func (ep *EntryPoints) Set(value string) error {
|
|
||||||
regex := regexp.MustCompile("(?:Name:(?P<Name>\\S*))\\s*(?:Address:(?P<Address>\\S*))?\\s*(?:TLS:(?P<TLS>\\S*))?\\s*(?:Redirect.EntryPoint:(?P<RedirectEntryPoint>\\S*))?\\s*(?:Redirect.Regex:(?P<RedirectRegex>\\S*))?\\s*(?:Redirect.Replacement:(?P<RedirectReplacement>\\S*))?")
|
|
||||||
match := regex.FindAllStringSubmatch(value, -1)
|
|
||||||
if match == nil {
|
|
||||||
return errors.New("Bad EntryPoints format: " + value)
|
|
||||||
}
|
|
||||||
matchResult := match[0]
|
|
||||||
result := make(map[string]string)
|
|
||||||
for i, name := range regex.SubexpNames() {
|
|
||||||
if i != 0 {
|
|
||||||
result[name] = matchResult[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var tls *TLS
|
|
||||||
if len(result["TLS"]) > 0 {
|
|
||||||
certs := Certificates{}
|
|
||||||
if err := certs.Set(result["TLS"]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tls = &TLS{
|
|
||||||
Certificates: certs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var redirect *Redirect
|
|
||||||
if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 {
|
|
||||||
redirect = &Redirect{
|
|
||||||
EntryPoint: result["RedirectEntryPoint"],
|
|
||||||
Regex: result["RedirectRegex"],
|
|
||||||
Replacement: result["RedirectReplacement"],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(*ep)[result["Name"]] = &EntryPoint{
|
|
||||||
Address: result["Address"],
|
|
||||||
TLS: tls,
|
|
||||||
Redirect: redirect,
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type is type of the struct
|
|
||||||
func (ep *EntryPoints) Type() string {
|
|
||||||
return fmt.Sprint("entrypoints²")
|
|
||||||
}
|
|
||||||
|
|
||||||
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
|
|
||||||
type EntryPoint struct {
|
|
||||||
Network string
|
|
||||||
Address string
|
|
||||||
TLS *TLS
|
|
||||||
Redirect *Redirect
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect configures a redirection of an entry point to another, or to an URL
|
|
||||||
type Redirect struct {
|
|
||||||
EntryPoint string
|
|
||||||
Regex string
|
|
||||||
Replacement string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TLS configures TLS for an entry point
|
|
||||||
type TLS struct {
|
|
||||||
Certificates Certificates
|
|
||||||
}
|
|
||||||
|
|
||||||
// Certificates defines traefik certificates type
|
|
||||||
type Certificates []Certificate
|
|
||||||
|
|
||||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
|
||||||
// The String method's output will be used in diagnostics.
|
|
||||||
func (certs *Certificates) String() string {
|
|
||||||
if len(*certs) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return (*certs)[0].CertFile + "," + (*certs)[0].KeyFile
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
|
||||||
// Set's argument is a string to be parsed to set the flag.
|
|
||||||
// It's a comma-separated list, so we split it.
|
|
||||||
func (certs *Certificates) Set(value string) error {
|
|
||||||
files := strings.Split(value, ",")
|
|
||||||
if len(files) != 2 {
|
|
||||||
return errors.New("Bad certificates format: " + value)
|
|
||||||
}
|
|
||||||
*certs = append(*certs, Certificate{
|
|
||||||
CertFile: files[0],
|
|
||||||
KeyFile: files[1],
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type is type of the struct
|
|
||||||
func (certs *Certificates) Type() string {
|
|
||||||
return fmt.Sprint("certificates")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Certificate holds a SSL cert/key pair
|
|
||||||
type Certificate struct {
|
|
||||||
CertFile string
|
|
||||||
KeyFile string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retry contains request retry config
|
|
||||||
type Retry struct {
|
|
||||||
Attempts int
|
|
||||||
MaxMem int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGlobalConfiguration returns a GlobalConfiguration with default values.
|
|
||||||
func NewGlobalConfiguration() *GlobalConfiguration {
|
|
||||||
return new(GlobalConfiguration)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadConfiguration returns a GlobalConfiguration.
|
|
||||||
func LoadConfiguration() *GlobalConfiguration {
|
|
||||||
configuration := NewGlobalConfiguration()
|
|
||||||
viper.SetEnvPrefix("traefik")
|
|
||||||
viper.SetConfigType("toml")
|
|
||||||
viper.AutomaticEnv()
|
|
||||||
if len(viper.GetString("configFile")) > 0 {
|
|
||||||
viper.SetConfigFile(viper.GetString("configFile"))
|
|
||||||
} else {
|
|
||||||
viper.SetConfigName("traefik") // name of config file (without extension)
|
|
||||||
}
|
|
||||||
viper.AddConfigPath("/etc/traefik/") // path to look for the config file in
|
|
||||||
viper.AddConfigPath("$HOME/.traefik/") // call multiple times to add many search paths
|
|
||||||
viper.AddConfigPath(".") // optionally look for config in the working directory
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
|
||||||
if len(viper.ConfigFileUsed()) > 0 {
|
|
||||||
fmtlog.Printf("Error reading configuration file: %s", err)
|
|
||||||
} else {
|
|
||||||
fmtlog.Printf("No configuration file found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 arguments.kubernetes {
|
|
||||||
viper.Set("kubernetes", arguments.Kubernetes)
|
|
||||||
}
|
|
||||||
if err := unmarshal(&configuration); err != nil {
|
|
||||||
|
|
||||||
fmtlog.Fatalf("Error reading file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(configuration.EntryPoints) == 0 {
|
|
||||||
configuration.EntryPoints = make(map[string]*EntryPoint)
|
|
||||||
configuration.EntryPoints["http"] = &EntryPoint{
|
|
||||||
Address: ":80",
|
|
||||||
}
|
|
||||||
configuration.DefaultEntryPoints = []string{"http"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if configuration.File != nil && len(configuration.File.Filename) == 0 {
|
|
||||||
// no filename, setting to global config file
|
|
||||||
configuration.File.Filename = viper.ConfigFileUsed()
|
|
||||||
}
|
|
||||||
|
|
||||||
return configuration
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshal(rawVal interface{}) error {
|
|
||||||
config := &mapstructure.DecoderConfig{
|
|
||||||
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
|
||||||
Metadata: nil,
|
|
||||||
Result: rawVal,
|
|
||||||
WeaklyTypedInput: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder, err := mapstructure.NewDecoder(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = decoder.Decode(viper.AllSettings())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type configs map[string]*types.Configuration
|
|
||||||
537
configuration/configuration.go
Normal file
537
configuration/configuration.go
Normal file
@@ -0,0 +1,537 @@
|
|||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containous/flaeg"
|
||||||
|
"github.com/containous/traefik/acme"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/provider/boltdb"
|
||||||
|
"github.com/containous/traefik/provider/consul"
|
||||||
|
"github.com/containous/traefik/provider/docker"
|
||||||
|
"github.com/containous/traefik/provider/dynamodb"
|
||||||
|
"github.com/containous/traefik/provider/ecs"
|
||||||
|
"github.com/containous/traefik/provider/etcd"
|
||||||
|
"github.com/containous/traefik/provider/eureka"
|
||||||
|
"github.com/containous/traefik/provider/file"
|
||||||
|
"github.com/containous/traefik/provider/kubernetes"
|
||||||
|
"github.com/containous/traefik/provider/marathon"
|
||||||
|
"github.com/containous/traefik/provider/mesos"
|
||||||
|
"github.com/containous/traefik/provider/rancher"
|
||||||
|
"github.com/containous/traefik/provider/web"
|
||||||
|
"github.com/containous/traefik/provider/zk"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultHealthCheckInterval is the default health check interval.
|
||||||
|
DefaultHealthCheckInterval = 30 * time.Second
|
||||||
|
|
||||||
|
// DefaultDialTimeout when connecting to a backend server.
|
||||||
|
DefaultDialTimeout = 30 * time.Second
|
||||||
|
|
||||||
|
// DefaultIdleTimeout before closing an idle connection.
|
||||||
|
DefaultIdleTimeout = 180 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 flaeg.Duration `short:"g" description:"Duration to give active requests a chance to finish before Traefik stops" export:"true"`
|
||||||
|
Debug bool `short:"d" description:"Enable debug mode" export:"true"`
|
||||||
|
CheckNewVersion bool `description:"Periodically check if a new version has been released" export:"true"`
|
||||||
|
AccessLogsFile string `description:"(Deprecated) Access logs file" export:"true"` // Deprecated
|
||||||
|
AccessLog *types.AccessLog `description:"Access log settings" export:"true"`
|
||||||
|
TraefikLogsFile string `description:"Traefik logs file. Stdout is used when omitted or empty" export:"true"`
|
||||||
|
LogLevel string `short:"l" description:"Log level" export:"true"`
|
||||||
|
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key;prod/traefik.crt,prod/traefik.key'" export:"true"`
|
||||||
|
Cluster *types.Cluster `description:"Enable clustering" export:"true"`
|
||||||
|
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags" export:"true"`
|
||||||
|
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL" export:"true"`
|
||||||
|
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint" export:"true"`
|
||||||
|
ProvidersThrottleDuration flaeg.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." export:"true"`
|
||||||
|
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" export:"true"`
|
||||||
|
IdleTimeout flaeg.Duration `description:"(Deprecated) maximum amount of time an idle (keep-alive) connection will remain idle before closing itself." export:"true"` // Deprecated
|
||||||
|
InsecureSkipVerify bool `description:"Disable SSL certificate verification" export:"true"`
|
||||||
|
RootCAs RootCAs `description:"Add cert file for self-signed certificate"`
|
||||||
|
Retry *Retry `description:"Enable retry sending request if network error" export:"true"`
|
||||||
|
HealthCheck *HealthCheckConfig `description:"Health check parameters" export:"true"`
|
||||||
|
RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance" export:"true"`
|
||||||
|
ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers" export:"true"`
|
||||||
|
Docker *docker.Provider `description:"Enable Docker backend with default settings" export:"true"`
|
||||||
|
File *file.Provider `description:"Enable File backend with default settings" export:"true"`
|
||||||
|
Web *web.Provider `description:"Enable Web backend with default settings" export:"true"`
|
||||||
|
Marathon *marathon.Provider `description:"Enable Marathon backend with default settings" export:"true"`
|
||||||
|
Consul *consul.Provider `description:"Enable Consul backend with default settings" export:"true"`
|
||||||
|
ConsulCatalog *consul.CatalogProvider `description:"Enable Consul catalog backend with default settings" export:"true"`
|
||||||
|
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings" export:"true"`
|
||||||
|
Zookeeper *zk.Provider `description:"Enable Zookeeper backend with default settings" export:"true"`
|
||||||
|
Boltdb *boltdb.Provider `description:"Enable Boltdb backend with default settings" export:"true"`
|
||||||
|
Kubernetes *kubernetes.Provider `description:"Enable Kubernetes backend with default settings" export:"true"`
|
||||||
|
Mesos *mesos.Provider `description:"Enable Mesos backend with default settings" export:"true"`
|
||||||
|
Eureka *eureka.Provider `description:"Enable Eureka backend with default settings" export:"true"`
|
||||||
|
ECS *ecs.Provider `description:"Enable ECS backend with default settings" export:"true"`
|
||||||
|
Rancher *rancher.Provider `description:"Enable Rancher backend with default settings" export:"true"`
|
||||||
|
DynamoDB *dynamodb.Provider `description:"Enable DynamoDB backend with default settings" export:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEffectiveConfiguration adds missing configuration parameters derived from existing ones.
|
||||||
|
// It also takes care of maintaining backwards compatibility.
|
||||||
|
func (gc *GlobalConfiguration) SetEffectiveConfiguration() {
|
||||||
|
if len(gc.EntryPoints) == 0 {
|
||||||
|
gc.EntryPoints = map[string]*EntryPoint{"http": {
|
||||||
|
Address: ":80",
|
||||||
|
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||||
|
}}
|
||||||
|
gc.DefaultEntryPoints = []string{"http"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForwardedHeaders must be remove in the next breaking version
|
||||||
|
for entryPointName := range gc.EntryPoints {
|
||||||
|
entryPoint := gc.EntryPoints[entryPointName]
|
||||||
|
if entryPoint.ForwardedHeaders == nil {
|
||||||
|
entryPoint.ForwardedHeaders = &ForwardedHeaders{Insecure: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if gc.Rancher != nil {
|
||||||
|
// Ensure backwards compatibility for now
|
||||||
|
if len(gc.Rancher.AccessKey) > 0 ||
|
||||||
|
len(gc.Rancher.Endpoint) > 0 ||
|
||||||
|
len(gc.Rancher.SecretKey) > 0 {
|
||||||
|
|
||||||
|
if gc.Rancher.API == nil {
|
||||||
|
gc.Rancher.API = &rancher.APIConfiguration{
|
||||||
|
AccessKey: gc.Rancher.AccessKey,
|
||||||
|
SecretKey: gc.Rancher.SecretKey,
|
||||||
|
Endpoint: gc.Rancher.Endpoint,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Warn("Deprecated configuration found: rancher.[accesskey|secretkey|endpoint]. " +
|
||||||
|
"Please use rancher.api.[accesskey|secretkey|endpoint] instead.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if gc.Rancher.Metadata != nil && len(gc.Rancher.Metadata.Prefix) == 0 {
|
||||||
|
gc.Rancher.Metadata.Prefix = "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if gc.Debug {
|
||||||
|
gc.LogLevel = "DEBUG"
|
||||||
|
}
|
||||||
|
|
||||||
|
if gc.Web != nil && (gc.Web.Path == "" || !strings.HasSuffix(gc.Web.Path, "/")) {
|
||||||
|
gc.Web.Path += "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultEntryPoints holds default entry points
|
||||||
|
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 strings.Join(*dep, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||||
|
// Set's argument is a string to be parsed to set the flag.
|
||||||
|
// It's a comma-separated list, so we split it.
|
||||||
|
func (dep *DefaultEntryPoints) Set(value string) error {
|
||||||
|
entrypoints := strings.Split(value, ",")
|
||||||
|
if len(entrypoints) == 0 {
|
||||||
|
return fmt.Errorf("bad DefaultEntryPoints format: %s", value)
|
||||||
|
}
|
||||||
|
for _, entrypoint := range entrypoints {
|
||||||
|
*dep = append(*dep, entrypoint)
|
||||||
|
}
|
||||||
|
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 "defaultentrypoints"
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootCAs hold the CA we want to have in root
|
||||||
|
type RootCAs []FileOrContent
|
||||||
|
|
||||||
|
// FileOrContent hold a file path or content
|
||||||
|
type FileOrContent string
|
||||||
|
|
||||||
|
func (f FileOrContent) String() string {
|
||||||
|
return string(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileOrContent) Read() ([]byte, error) {
|
||||||
|
var content []byte
|
||||||
|
if _, err := os.Stat(f.String()); err == nil {
|
||||||
|
content, err = ioutil.ReadFile(f.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content = []byte(f)
|
||||||
|
}
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||||
|
// The String method's output will be used in diagnostics.
|
||||||
|
func (r *RootCAs) String() string {
|
||||||
|
sliceOfString := make([]string, len([]FileOrContent(*r)))
|
||||||
|
for key, value := range *r {
|
||||||
|
sliceOfString[key] = value.String()
|
||||||
|
}
|
||||||
|
return strings.Join(sliceOfString, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (r *RootCAs) Set(value string) error {
|
||||||
|
rootCAs := strings.Split(value, ",")
|
||||||
|
if len(rootCAs) == 0 {
|
||||||
|
return fmt.Errorf("bad RootCAs format: %s", value)
|
||||||
|
}
|
||||||
|
for _, rootCA := range rootCAs {
|
||||||
|
*r = append(*r, FileOrContent(rootCA))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get return the EntryPoints map
|
||||||
|
func (r *RootCAs) Get() interface{} {
|
||||||
|
return RootCAs(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetValue sets the EntryPoints map with val
|
||||||
|
func (r *RootCAs) SetValue(val interface{}) {
|
||||||
|
*r = RootCAs(val.(RootCAs))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type is type of the struct
|
||||||
|
func (r *RootCAs) Type() string {
|
||||||
|
return "rootcas"
|
||||||
|
}
|
||||||
|
|
||||||
|
// EntryPoints holds entry points configuration of the reverse proxy (ip, port, TLS...)
|
||||||
|
type EntryPoints map[string]*EntryPoint
|
||||||
|
|
||||||
|
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||||
|
// The String method's output will be used in diagnostics.
|
||||||
|
func (ep *EntryPoints) String() string {
|
||||||
|
return 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 {
|
||||||
|
result := parseEntryPointsConfiguration(value)
|
||||||
|
|
||||||
|
var configTLS *TLS
|
||||||
|
if len(result["tls"]) > 0 {
|
||||||
|
certs := Certificates{}
|
||||||
|
if err := certs.Set(result["tls"]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configTLS = &TLS{
|
||||||
|
Certificates: certs,
|
||||||
|
}
|
||||||
|
} else if len(result["tls_acme"]) > 0 {
|
||||||
|
configTLS = &TLS{
|
||||||
|
Certificates: Certificates{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(result["ca"]) > 0 {
|
||||||
|
files := strings.Split(result["ca"], ",")
|
||||||
|
configTLS.ClientCAFiles = files
|
||||||
|
}
|
||||||
|
var redirect *Redirect
|
||||||
|
if len(result["redirect_entrypoint"]) > 0 || len(result["redirect_regex"]) > 0 || len(result["redirect_replacement"]) > 0 {
|
||||||
|
redirect = &Redirect{
|
||||||
|
EntryPoint: result["redirect_entrypoint"],
|
||||||
|
Regex: result["redirect_regex"],
|
||||||
|
Replacement: result["redirect_replacement"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
whiteListSourceRange := []string{}
|
||||||
|
if len(result["whitelistsourcerange"]) > 0 {
|
||||||
|
whiteListSourceRange = strings.Split(result["whitelistsourcerange"], ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
compress := toBool(result, "compress")
|
||||||
|
|
||||||
|
var proxyProtocol *ProxyProtocol
|
||||||
|
ppTrustedIPs := result["proxyprotocol_trustedips"]
|
||||||
|
if len(result["proxyprotocol_insecure"]) > 0 || len(ppTrustedIPs) > 0 {
|
||||||
|
proxyProtocol = &ProxyProtocol{
|
||||||
|
Insecure: toBool(result, "proxyprotocol_insecure"),
|
||||||
|
}
|
||||||
|
if len(ppTrustedIPs) > 0 {
|
||||||
|
proxyProtocol.TrustedIPs = strings.Split(ppTrustedIPs, ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO must be changed to false by default in the next breaking version.
|
||||||
|
forwardedHeaders := &ForwardedHeaders{Insecure: true}
|
||||||
|
if _, ok := result["forwardedheaders_insecure"]; ok {
|
||||||
|
forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure")
|
||||||
|
}
|
||||||
|
|
||||||
|
fhTrustedIPs := result["forwardedheaders_trustedips"]
|
||||||
|
if len(fhTrustedIPs) > 0 {
|
||||||
|
// TODO must be removed in the next breaking version.
|
||||||
|
forwardedHeaders.Insecure = toBool(result, "forwardedheaders_insecure")
|
||||||
|
forwardedHeaders.TrustedIPs = strings.Split(fhTrustedIPs, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
if proxyProtocol != nil && proxyProtocol.Insecure {
|
||||||
|
log.Warn("ProxyProtocol.Insecure:true is dangerous. Please use 'ProxyProtocol.TrustedIPs:IPs' and remove 'ProxyProtocol.Insecure:true'")
|
||||||
|
}
|
||||||
|
|
||||||
|
(*ep)[result["name"]] = &EntryPoint{
|
||||||
|
Address: result["address"],
|
||||||
|
TLS: configTLS,
|
||||||
|
Redirect: redirect,
|
||||||
|
Compress: compress,
|
||||||
|
WhitelistSourceRange: whiteListSourceRange,
|
||||||
|
ProxyProtocol: proxyProtocol,
|
||||||
|
ForwardedHeaders: forwardedHeaders,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEntryPointsConfiguration(raw string) map[string]string {
|
||||||
|
sections := strings.Fields(raw)
|
||||||
|
|
||||||
|
config := make(map[string]string)
|
||||||
|
for _, part := range sections {
|
||||||
|
field := strings.SplitN(part, ":", 2)
|
||||||
|
name := strings.ToLower(strings.Replace(field[0], ".", "_", -1))
|
||||||
|
if len(field) > 1 {
|
||||||
|
config[name] = field[1]
|
||||||
|
} else {
|
||||||
|
if strings.EqualFold(name, "TLS") {
|
||||||
|
config["tls_acme"] = "TLS"
|
||||||
|
} else {
|
||||||
|
config[name] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func toBool(conf map[string]string, key string) bool {
|
||||||
|
if val, ok := conf[key]; ok {
|
||||||
|
return strings.EqualFold(val, "true") ||
|
||||||
|
strings.EqualFold(val, "enable") ||
|
||||||
|
strings.EqualFold(val, "on")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 "entrypoints"
|
||||||
|
}
|
||||||
|
|
||||||
|
// EntryPoint holds an entry point configuration of the reverse proxy (ip, port, TLS...)
|
||||||
|
type EntryPoint struct {
|
||||||
|
Network string
|
||||||
|
Address string
|
||||||
|
TLS *TLS `export:"true"`
|
||||||
|
Redirect *Redirect `export:"true"`
|
||||||
|
Auth *types.Auth `export:"true"`
|
||||||
|
WhitelistSourceRange []string
|
||||||
|
Compress bool `export:"true"`
|
||||||
|
ProxyProtocol *ProxyProtocol `export:"true"`
|
||||||
|
ForwardedHeaders *ForwardedHeaders `export:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect configures a redirection of an entry point to another, or to an URL
|
||||||
|
type Redirect struct {
|
||||||
|
EntryPoint string
|
||||||
|
Regex string
|
||||||
|
Replacement string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLS configures TLS for an entry point
|
||||||
|
type TLS struct {
|
||||||
|
MinVersion string `export:"true"`
|
||||||
|
CipherSuites []string
|
||||||
|
Certificates Certificates
|
||||||
|
ClientCAFiles []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinVersion Map of allowed TLS minimum versions
|
||||||
|
var MinVersion = map[string]uint16{
|
||||||
|
`VersionTLS10`: tls.VersionTLS10,
|
||||||
|
`VersionTLS11`: tls.VersionTLS11,
|
||||||
|
`VersionTLS12`: tls.VersionTLS12,
|
||||||
|
}
|
||||||
|
|
||||||
|
// CipherSuites Map of TLS CipherSuites from crypto/tls
|
||||||
|
// Available CipherSuites defined at https://golang.org/pkg/crypto/tls/#pkg-constants
|
||||||
|
var CipherSuites = map[string]uint16{
|
||||||
|
`TLS_RSA_WITH_RC4_128_SHA`: tls.TLS_RSA_WITH_RC4_128_SHA,
|
||||||
|
`TLS_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
`TLS_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
`TLS_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
`TLS_RSA_WITH_AES_128_CBC_SHA256`: tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
`TLS_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
`TLS_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
`TLS_ECDHE_ECDSA_WITH_RC4_128_SHA`: tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||||
|
`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||||
|
`TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||||
|
`TLS_ECDHE_RSA_WITH_RC4_128_SHA`: tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||||
|
`TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||||
|
`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
`TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA`: tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
`TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
`TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||||
|
`TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
`TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`: tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
`TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
`TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`: tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
`TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305`: tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||||
|
`TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305`: tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certificates defines traefik certificates type
|
||||||
|
// Certs and Keys could be either a file path, or the file content itself
|
||||||
|
type Certificates []Certificate
|
||||||
|
|
||||||
|
//CreateTLSConfig creates a TLS config from Certificate structures
|
||||||
|
func (certs *Certificates) CreateTLSConfig() (*tls.Config, error) {
|
||||||
|
config := &tls.Config{}
|
||||||
|
config.Certificates = []tls.Certificate{}
|
||||||
|
certsSlice := []Certificate(*certs)
|
||||||
|
for _, v := range certsSlice {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
certContent, err := v.CertFile.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyContent, err := v.KeyFile.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := tls.X509KeyPair(certContent, keyContent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Certificates = append(config.Certificates, cert)
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||||
|
// The String method's output will be used in diagnostics.
|
||||||
|
func (certs *Certificates) String() string {
|
||||||
|
if len(*certs) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var result []string
|
||||||
|
for _, certificate := range *certs {
|
||||||
|
result = append(result, certificate.CertFile.String()+","+certificate.KeyFile.String())
|
||||||
|
}
|
||||||
|
return strings.Join(result, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||||
|
// Set's argument is a string to be parsed to set the flag.
|
||||||
|
// It's a comma-separated list, so we split it.
|
||||||
|
func (certs *Certificates) Set(value string) error {
|
||||||
|
certificates := strings.Split(value, ";")
|
||||||
|
for _, certificate := range certificates {
|
||||||
|
files := strings.Split(certificate, ",")
|
||||||
|
if len(files) != 2 {
|
||||||
|
return fmt.Errorf("bad certificates format: %s", value)
|
||||||
|
}
|
||||||
|
*certs = append(*certs, Certificate{
|
||||||
|
CertFile: FileOrContent(files[0]),
|
||||||
|
KeyFile: FileOrContent(files[1]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type is type of the struct
|
||||||
|
func (certs *Certificates) Type() string {
|
||||||
|
return "certificates"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certificate holds a SSL cert/key pair
|
||||||
|
// Certs and Key could be either a file path, or the file content itself
|
||||||
|
type Certificate struct {
|
||||||
|
CertFile FileOrContent
|
||||||
|
KeyFile FileOrContent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry contains request retry config
|
||||||
|
type Retry struct {
|
||||||
|
Attempts int `description:"Number of attempts" export:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthCheckConfig contains health check configuration parameters.
|
||||||
|
type HealthCheckConfig struct {
|
||||||
|
Interval flaeg.Duration `description:"Default periodicity of enabled health checks" export:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RespondingTimeouts contains timeout configurations for incoming requests to the Traefik instance.
|
||||||
|
type RespondingTimeouts struct {
|
||||||
|
ReadTimeout flaeg.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set" export:"true"`
|
||||||
|
WriteTimeout flaeg.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set" export:"true"`
|
||||||
|
IdleTimeout flaeg.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. Defaults to 180 seconds. If zero, no timeout is set" export:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers.
|
||||||
|
type ForwardingTimeouts struct {
|
||||||
|
DialTimeout flaeg.Duration `description:"The amount of time to wait until a connection to a backend server can be established. Defaults to 30 seconds. If zero, no timeout exists" export:"true"`
|
||||||
|
ResponseHeaderTimeout flaeg.Duration `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists" export:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProxyProtocol contains Proxy-Protocol configuration
|
||||||
|
type ProxyProtocol struct {
|
||||||
|
Insecure bool
|
||||||
|
TrustedIPs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForwardedHeaders Trust client forwarding headers
|
||||||
|
type ForwardedHeaders struct {
|
||||||
|
Insecure bool
|
||||||
|
TrustedIPs []string
|
||||||
|
}
|
||||||
293
configuration/configuration_test.go
Normal file
293
configuration/configuration_test.go
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
package configuration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_parseEntryPointsConfiguration(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
expectedResult map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "all parameters",
|
||||||
|
value: "Name:foo TLS:goo TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:WhiteListSourceRange ProxyProtocol.TrustedIPs:192.168.0.1 ProxyProtocol.Insecure:false Address::8000",
|
||||||
|
expectedResult: map[string]string{
|
||||||
|
"name": "foo",
|
||||||
|
"address": ":8000",
|
||||||
|
"ca": "car",
|
||||||
|
"tls": "goo",
|
||||||
|
"tls_acme": "TLS",
|
||||||
|
"redirect_entrypoint": "RedirectEntryPoint",
|
||||||
|
"redirect_regex": "RedirectRegex",
|
||||||
|
"redirect_replacement": "RedirectReplacement",
|
||||||
|
"whitelistsourcerange": "WhiteListSourceRange",
|
||||||
|
"proxyprotocol_trustedips": "192.168.0.1",
|
||||||
|
"proxyprotocol_insecure": "false",
|
||||||
|
"compress": "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compress on",
|
||||||
|
value: "name:foo Compress:on",
|
||||||
|
expectedResult: map[string]string{
|
||||||
|
"name": "foo",
|
||||||
|
"compress": "on",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TLS",
|
||||||
|
value: "Name:foo TLS:goo TLS",
|
||||||
|
expectedResult: map[string]string{
|
||||||
|
"name": "foo",
|
||||||
|
"tls": "goo",
|
||||||
|
"tls_acme": "TLS",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
conf := parseEntryPointsConfiguration(test.value)
|
||||||
|
|
||||||
|
assert.Len(t, conf, len(test.expectedResult))
|
||||||
|
assert.Equal(t, test.expectedResult, conf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_toBool(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
key string
|
||||||
|
expectedBool bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "on",
|
||||||
|
value: "on",
|
||||||
|
key: "foo",
|
||||||
|
expectedBool: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "true",
|
||||||
|
value: "true",
|
||||||
|
key: "foo",
|
||||||
|
expectedBool: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "enable",
|
||||||
|
value: "enable",
|
||||||
|
key: "foo",
|
||||||
|
expectedBool: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arbitrary string",
|
||||||
|
value: "bar",
|
||||||
|
key: "foo",
|
||||||
|
expectedBool: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no existing entry",
|
||||||
|
value: "bar",
|
||||||
|
key: "fii",
|
||||||
|
expectedBool: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
conf := map[string]string{
|
||||||
|
"foo": test.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := toBool(conf, test.key)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expectedBool, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEntryPoints_Set(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
expression string
|
||||||
|
expectedEntryPointName string
|
||||||
|
expectedEntryPoint *EntryPoint
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "all parameters camelcase",
|
||||||
|
expression: "Name:foo Address::8000 TLS:goo,gii TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:Range ProxyProtocol.TrustedIPs:192.168.0.1 ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
|
||||||
|
expectedEntryPointName: "foo",
|
||||||
|
expectedEntryPoint: &EntryPoint{
|
||||||
|
Address: ":8000",
|
||||||
|
Redirect: &Redirect{
|
||||||
|
EntryPoint: "RedirectEntryPoint",
|
||||||
|
Regex: "RedirectRegex",
|
||||||
|
Replacement: "RedirectReplacement",
|
||||||
|
},
|
||||||
|
Compress: true,
|
||||||
|
ProxyProtocol: &ProxyProtocol{
|
||||||
|
TrustedIPs: []string{"192.168.0.1"},
|
||||||
|
},
|
||||||
|
ForwardedHeaders: &ForwardedHeaders{
|
||||||
|
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
|
||||||
|
},
|
||||||
|
WhitelistSourceRange: []string{"Range"},
|
||||||
|
TLS: &TLS{
|
||||||
|
ClientCAFiles: []string{"car"},
|
||||||
|
Certificates: Certificates{
|
||||||
|
{
|
||||||
|
CertFile: FileOrContent("goo"),
|
||||||
|
KeyFile: FileOrContent("gii"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all parameters lowercase",
|
||||||
|
expression: "name:foo address::8000 tls:goo,gii tls ca:car redirect.entryPoint:RedirectEntryPoint redirect.regex:RedirectRegex redirect.replacement:RedirectReplacement compress:true whiteListSourceRange:Range proxyProtocol.trustedIPs:192.168.0.1 forwardedHeaders.trustedIPs:10.0.0.3/24,20.0.0.3/24",
|
||||||
|
expectedEntryPointName: "foo",
|
||||||
|
expectedEntryPoint: &EntryPoint{
|
||||||
|
Address: ":8000",
|
||||||
|
Redirect: &Redirect{
|
||||||
|
EntryPoint: "RedirectEntryPoint",
|
||||||
|
Regex: "RedirectRegex",
|
||||||
|
Replacement: "RedirectReplacement",
|
||||||
|
},
|
||||||
|
Compress: true,
|
||||||
|
ProxyProtocol: &ProxyProtocol{
|
||||||
|
TrustedIPs: []string{"192.168.0.1"},
|
||||||
|
},
|
||||||
|
ForwardedHeaders: &ForwardedHeaders{
|
||||||
|
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
|
||||||
|
},
|
||||||
|
WhitelistSourceRange: []string{"Range"},
|
||||||
|
TLS: &TLS{
|
||||||
|
ClientCAFiles: []string{"car"},
|
||||||
|
Certificates: Certificates{
|
||||||
|
{
|
||||||
|
CertFile: FileOrContent("goo"),
|
||||||
|
KeyFile: FileOrContent("gii"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default",
|
||||||
|
expression: "Name:foo",
|
||||||
|
expectedEntryPointName: "foo",
|
||||||
|
expectedEntryPoint: &EntryPoint{
|
||||||
|
WhitelistSourceRange: []string{},
|
||||||
|
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ForwardedHeaders insecure true",
|
||||||
|
expression: "Name:foo ForwardedHeaders.Insecure:true",
|
||||||
|
expectedEntryPointName: "foo",
|
||||||
|
expectedEntryPoint: &EntryPoint{
|
||||||
|
WhitelistSourceRange: []string{},
|
||||||
|
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ForwardedHeaders insecure false",
|
||||||
|
expression: "Name:foo ForwardedHeaders.Insecure:false",
|
||||||
|
expectedEntryPointName: "foo",
|
||||||
|
expectedEntryPoint: &EntryPoint{
|
||||||
|
WhitelistSourceRange: []string{},
|
||||||
|
ForwardedHeaders: &ForwardedHeaders{Insecure: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ForwardedHeaders TrustedIPs",
|
||||||
|
expression: "Name:foo ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
|
||||||
|
expectedEntryPointName: "foo",
|
||||||
|
expectedEntryPoint: &EntryPoint{
|
||||||
|
WhitelistSourceRange: []string{},
|
||||||
|
ForwardedHeaders: &ForwardedHeaders{
|
||||||
|
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ProxyProtocol insecure true",
|
||||||
|
expression: "Name:foo ProxyProtocol.Insecure:true",
|
||||||
|
expectedEntryPointName: "foo",
|
||||||
|
expectedEntryPoint: &EntryPoint{
|
||||||
|
WhitelistSourceRange: []string{},
|
||||||
|
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||||
|
ProxyProtocol: &ProxyProtocol{Insecure: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ProxyProtocol insecure false",
|
||||||
|
expression: "Name:foo ProxyProtocol.Insecure:false",
|
||||||
|
expectedEntryPointName: "foo",
|
||||||
|
expectedEntryPoint: &EntryPoint{
|
||||||
|
WhitelistSourceRange: []string{},
|
||||||
|
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||||
|
ProxyProtocol: &ProxyProtocol{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ProxyProtocol TrustedIPs",
|
||||||
|
expression: "Name:foo ProxyProtocol.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
|
||||||
|
expectedEntryPointName: "foo",
|
||||||
|
expectedEntryPoint: &EntryPoint{
|
||||||
|
WhitelistSourceRange: []string{},
|
||||||
|
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||||
|
ProxyProtocol: &ProxyProtocol{
|
||||||
|
TrustedIPs: []string{"10.0.0.3/24", "20.0.0.3/24"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compress on",
|
||||||
|
expression: "Name:foo Compress:on",
|
||||||
|
expectedEntryPointName: "foo",
|
||||||
|
expectedEntryPoint: &EntryPoint{
|
||||||
|
Compress: true,
|
||||||
|
WhitelistSourceRange: []string{},
|
||||||
|
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "compress true",
|
||||||
|
expression: "Name:foo Compress:true",
|
||||||
|
expectedEntryPointName: "foo",
|
||||||
|
expectedEntryPoint: &EntryPoint{
|
||||||
|
Compress: true,
|
||||||
|
WhitelistSourceRange: []string{},
|
||||||
|
ForwardedHeaders: &ForwardedHeaders{Insecure: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
eps := EntryPoints{}
|
||||||
|
err := eps.Set(test.expression)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ep := eps[test.expectedEntryPointName]
|
||||||
|
assert.EqualValues(t, test.expectedEntryPoint, ep)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
151
contrib/scripts/dumpcerts.sh
Executable file
151
contrib/scripts/dumpcerts.sh
Executable file
@@ -0,0 +1,151 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copyright (c) 2017 Brian 'redbeard' Harrington <redbeard@dead-city.org>
|
||||||
|
#
|
||||||
|
# dumpcerts.sh - A simple utility to explode a Traefik acme.json file into a
|
||||||
|
# directory of certificates and a private key
|
||||||
|
#
|
||||||
|
# Usage - dumpcerts.sh /etc/traefik/acme.json /etc/ssl/
|
||||||
|
#
|
||||||
|
# Dependencies -
|
||||||
|
# util-linux
|
||||||
|
# openssl
|
||||||
|
# jq
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
# Exit codes:
|
||||||
|
# 1 - A component is missing or could not be read
|
||||||
|
# 2 - There was a problem reading acme.json
|
||||||
|
# 4 - The destination certificate directory does not exist
|
||||||
|
# 8 - Missing private key
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o pipefail
|
||||||
|
set -o nounset
|
||||||
|
|
||||||
|
USAGE="$(basename "$0") <path to acme> <destination cert directory>"
|
||||||
|
|
||||||
|
# Allow us to exit on a missing jq binary
|
||||||
|
exit_jq() {
|
||||||
|
echo "
|
||||||
|
You must have the binary 'jq' to use this.
|
||||||
|
jq is available at: https://stedolan.github.io/jq/download/
|
||||||
|
|
||||||
|
${USAGE}" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
bad_acme() {
|
||||||
|
echo "
|
||||||
|
There was a problem parsing your acme.json file.
|
||||||
|
|
||||||
|
${USAGE}" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ $# -ne 2 ]; then
|
||||||
|
echo "
|
||||||
|
Insufficient number of parameters.
|
||||||
|
|
||||||
|
${USAGE}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
readonly acmefile="${1}"
|
||||||
|
readonly certdir="${2%/}"
|
||||||
|
|
||||||
|
if [ ! -r "${acmefile}" ]; then
|
||||||
|
echo "
|
||||||
|
There was a problem reading from '${acmefile}'
|
||||||
|
We need to read this file to explode the JSON bundle... exiting.
|
||||||
|
|
||||||
|
${USAGE}" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if [ ! -d "${certdir}" ]; then
|
||||||
|
echo "
|
||||||
|
Path ${certdir} does not seem to be a directory
|
||||||
|
We need a directory in which to explode the JSON bundle... exiting.
|
||||||
|
|
||||||
|
${USAGE}" >&2
|
||||||
|
exit 4
|
||||||
|
fi
|
||||||
|
|
||||||
|
jq=$(command -v jq) || exit_jq
|
||||||
|
|
||||||
|
priv=$(${jq} -e -r '.PrivateKey' "${acmefile}") || bad_acme
|
||||||
|
|
||||||
|
if [ ! -n "${priv}" ]; then
|
||||||
|
echo "
|
||||||
|
There didn't seem to be a private key in ${acmefile}.
|
||||||
|
Please ensure that there is a key in this file and try again." >&2
|
||||||
|
exit 8
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If they do not exist, create the needed subdirectories for our assets
|
||||||
|
# and place each in a variable for later use, normalizing the path
|
||||||
|
mkdir -p "${certdir}"/{certs,private}
|
||||||
|
|
||||||
|
pdir="${certdir}/private/"
|
||||||
|
cdir="${certdir}/certs/"
|
||||||
|
|
||||||
|
# Save the existing umask, change the default mode to 600, then
|
||||||
|
# after writing the private key switch it back to the default
|
||||||
|
oldumask=$(umask)
|
||||||
|
umask 177
|
||||||
|
trap 'umask ${oldumask}' EXIT
|
||||||
|
|
||||||
|
# traefik stores the private key in stripped base64 format but the certificates
|
||||||
|
# bundled as a base64 object without stripping headers. This normalizes the
|
||||||
|
# headers and formatting.
|
||||||
|
#
|
||||||
|
# In testing this out it was a balance between the following mechanisms:
|
||||||
|
# gawk:
|
||||||
|
# echo ${priv} | awk 'BEGIN {print "-----BEGIN RSA PRIVATE KEY-----"}
|
||||||
|
# {gsub(/.{64}/,"&\n")}1
|
||||||
|
# END {print "-----END RSA PRIVATE KEY-----"}' > "${pdir}/letsencrypt.key"
|
||||||
|
#
|
||||||
|
# openssl:
|
||||||
|
# echo -e "-----BEGIN RSA PRIVATE KEY-----\n${priv}\n-----END RSA PRIVATE KEY-----" \
|
||||||
|
# | openssl rsa -inform pem -out "${pdir}/letsencrypt.key"
|
||||||
|
#
|
||||||
|
# and sed:
|
||||||
|
# echo "-----BEGIN RSA PRIVATE KEY-----" > "${pdir}/letsencrypt.key"
|
||||||
|
# echo ${priv} | sed 's/(.{64})/\1\n/g' >> "${pdir}/letsencrypt.key"
|
||||||
|
# echo "-----END RSA PRIVATE KEY-----" > "${pdir}/letsencrypt.key"
|
||||||
|
#
|
||||||
|
# In the end, openssl was chosen because most users will need this script
|
||||||
|
# *because* of openssl combined with the fact that it will refuse to write the
|
||||||
|
# key if it does not parse out correctly. The other mechanisms were left as
|
||||||
|
# comments so that the user can choose the mechanism most appropriate to them.
|
||||||
|
echo -e "-----BEGIN RSA PRIVATE KEY-----\n${priv}\n-----END RSA PRIVATE KEY-----" \
|
||||||
|
| openssl rsa -inform pem -out "${pdir}/letsencrypt.key"
|
||||||
|
|
||||||
|
# Process the certificates for each of the domains in acme.json
|
||||||
|
for domain in $(jq -r '.DomainsCertificate.Certs[].Certificate.Domain' acme.json); do
|
||||||
|
# Traefik stores a cert bundle for each domain. Within this cert
|
||||||
|
# bundle there is both proper the certificate and the Let's Encrypt CA
|
||||||
|
echo "Extracting cert bundle for ${domain}"
|
||||||
|
cert=$(jq -e -r --arg domain "$domain" '.DomainsCertificate.Certs[].Certificate |
|
||||||
|
select (.Domain == $domain )| .Certificate' ${acmefile}) || bad_acme
|
||||||
|
echo "${cert}" | base64 --decode > "${cdir}/${domain}.pem"
|
||||||
|
done
|
||||||
@@ -2,5 +2,10 @@
|
|||||||
Description=Traefik
|
Description=Traefik
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
Type=notify
|
||||||
ExecStart=/usr/bin/traefik --configFile=/etc/traefik.toml
|
ExecStart=/usr/bin/traefik --configFile=/etc/traefik.toml
|
||||||
Restart=on-failure
|
Restart=always
|
||||||
|
WatchdogSec=1s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|||||||
472
docs/basics.md
472
docs/basics.md
@@ -1,7 +1,8 @@
|
|||||||
|
# Basics
|
||||||
|
|
||||||
# Concepts
|
## Concepts
|
||||||
|
|
||||||
Let's take our example from the [overview](https://docs.traefik.io/#overview) again:
|
Let's take our example from the [overview](/#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.
|
> 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.
|
||||||
@@ -13,24 +14,24 @@ Let's take our example from the [overview](https://docs.traefik.io/#overview) ag
|
|||||||
|
|
||||||
> 
|
> 
|
||||||
|
|
||||||
Let's zoom on Træfɪk and have an overview of its internal architecture:
|
Let's zoom on Træfik and have an overview of its internal architecture:
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 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...).
|
- Incoming requests end on [entrypoints](#entrypoints), as the name suggests, they are the network entry points into Træfik (listening port, SSL, traffic redirection...).
|
||||||
- Traffic is then forwarded to a matching [frontend](#frontends). A frontend defines routes from [entrypoints](#entrypoints) to [backends](#backends).
|
- 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.
|
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.
|
- 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.
|
- Finally, the [server](#servers) will forward the request to the corresponding microservice in the private network.
|
||||||
|
|
||||||
## Entrypoints
|
### Entrypoints
|
||||||
|
|
||||||
Entrypoints are the network entry points into Træfɪk.
|
Entrypoints are the network entry points into Træfik.
|
||||||
They can be defined using:
|
They can be defined using:
|
||||||
|
|
||||||
- a port (80, 443...)
|
- a port (80, 443...)
|
||||||
- SSL (Certificates. Keys...)
|
- SSL (Certificates, Keys, authentication with a client certificate signed by a trusted CA...)
|
||||||
- redirection to another entrypoint (redirect `HTTP` to `HTTPS`)
|
- redirection to another entrypoint (redirect `HTTP` to `HTTPS`)
|
||||||
|
|
||||||
Here is an example of entrypoints definition:
|
Here is an example of entrypoints definition:
|
||||||
@@ -50,26 +51,97 @@ Here is an example of entrypoints definition:
|
|||||||
```
|
```
|
||||||
|
|
||||||
- Two entrypoints are defined `http` and `https`.
|
- Two entrypoints are defined `http` and `https`.
|
||||||
- `http` listens on port `80` et `https` on port `443`.
|
- `http` listens on port `80` and `https` on port `443`.
|
||||||
- We enable SSL en `https` by giving a certificate and a key.
|
- We enable SSL on `https` by giving a certificate and a key.
|
||||||
- We also redirect all the traffic from entrypoint `http` to `https`.
|
- We also redirect all the traffic from entrypoint `http` to `https`.
|
||||||
|
|
||||||
## Frontends
|
And here is another example with client certificate authentication:
|
||||||
|
|
||||||
A frontend is a set of rules that forwards the incoming traffic from an entrypoint to a backend.
|
```toml
|
||||||
Frontends can be defined using the following rules:
|
[entryPoints]
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
clientCAFiles = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
certFile = "tests/traefik.crt"
|
||||||
|
keyFile = "tests/traefik.key"
|
||||||
|
```
|
||||||
|
|
||||||
- `Headers: Content-Type, application/json`: Headers adds a matcher for request header values. It accepts a sequence of key/value pairs to be matched.
|
- We enable SSL on `https` by giving a certificate and a key.
|
||||||
- `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.
|
- One or several files containing Certificate Authorities in PEM format are added.
|
||||||
- `Host: traefik.io, www.traefik.io`: Match request host with given host list.
|
- It is possible to have multiple CA:s in the same file or keep them in separate files.
|
||||||
- `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.
|
### Frontends
|
||||||
- `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.
|
A frontend consists of a set of rules that determine how incoming requests are forwarded from an entrypoint to a backend.
|
||||||
- `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.
|
Rules may be classified in one of two groups: Modifiers and matchers.
|
||||||
|
|
||||||
|
#### Modifiers
|
||||||
|
|
||||||
|
Modifier rules only modify the request. They do not have any impact on routing decisions being made.
|
||||||
|
|
||||||
|
Following is the list of existing modifier rules:
|
||||||
|
|
||||||
|
- `AddPrefix: /products`: Add path prefix to the existing request path prior to forwarding the request to the backend.
|
||||||
|
- `ReplacePath: /serverless-path`: Replaces the path and adds the old path to the `X-Replaced-Path` header. Useful for mapping to AWS Lambda or Google Cloud Functions.
|
||||||
|
|
||||||
|
#### Matchers
|
||||||
|
|
||||||
|
Matcher rules determine if a particular request should be forwarded to a backend.
|
||||||
|
|
||||||
|
Separate multiple rule values by `,` (comma) in order to enable ANY semantics (i.e., forward a request if any rule matches).
|
||||||
|
Does not work for `Headers` and `HeadersRegexp`.
|
||||||
|
|
||||||
|
Separate multiple rule values by `;` (semicolon) in order to enable ALL semantics (i.e., forward a request if all rules match).
|
||||||
|
|
||||||
|
Following is the list of existing matcher rules along with examples:
|
||||||
|
|
||||||
|
| Matcher | Description |
|
||||||
|
|------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `Headers: Content-Type, application/json` | Match HTTP header. It accepts a comma-separated key/value pair where both key and value must be literals. |
|
||||||
|
| `HeadersRegexp: Content-Type, application/(text/json)` | Match HTTP header. It accepts a comma-separated key/value pair where the key must be a literal and the value may be a literal or a regular expression. |
|
||||||
|
| `Host: traefik.io, www.traefik.io` | Match request host. It accepts a sequence of literal hosts. |
|
||||||
|
| `HostRegexp: traefik.io, {subdomain:[a-z]+}.traefik.io` | Match request host. It accepts a sequence of literal and regular expression hosts. |
|
||||||
|
| `Method: GET, POST, PUT` | Match request HTTP method. It accepts a sequence of HTTP methods. |
|
||||||
|
| `Path: /products/, /articles/{category}/{id:[0-9]+}` | Match exact request path. It accepts a sequence of literal and regular expression paths. |
|
||||||
|
| `PathStrip: /products/` | Match exact path and strip off the path prior to forwarding the request to the backend. It accepts a sequence of literal paths. |
|
||||||
|
| `PathStripRegex: /articles/{category}/{id:[0-9]+}` | Match exact path and strip off the path prior to forwarding the request to the backend. It accepts a sequence of literal and regular expression paths. |
|
||||||
|
| `PathPrefix: /products/, /articles/{category}/{id:[0-9]+}` | Match request prefix path. It accepts a sequence of literal and regular expression prefix paths. |
|
||||||
|
| `PathPrefixStrip: /products/` | Match request prefix path and strip off the path prefix prior to forwarding the request to the backend. It accepts a sequence of literal prefix paths. Starting with Traefik 1.3, the stripped prefix path will be available in the `X-Forwarded-Prefix` header. |
|
||||||
|
| `PathPrefixStripRegex: /articles/{category}/{id:[0-9]+}` | Match request prefix path and strip off the path prefix prior to forwarding the request to the backend. It accepts a sequence of literal and regular expression prefix paths. Starting with Traefik 1.3, the stripped prefix path will be available in the `X-Forwarded-Prefix` header. |
|
||||||
|
| `Query: foo=bar, bar=baz` | Match Query String parameters. It accepts a sequence of key=value pairs. |
|
||||||
|
|
||||||
|
In order to use regular expressions with Host and Path matchers, you must declare an arbitrarily named variable followed by the colon-separated regular expression, all enclosed in curly braces. Any pattern supported by [Go's regexp package](https://golang.org/pkg/regexp/) may be used (example: `/posts/{id:[0-9]+}`).
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The variable has no special meaning; however, it is required by the [gorilla/mux](https://github.com/gorilla/mux) dependency which embeds the regular expression and defines the syntax.
|
||||||
|
|
||||||
You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
|
You can optionally enable `passHostHeader` to forward client `Host` header to the backend.
|
||||||
|
You can also optionally enable `passTLSCert` to forward TLS Client certificates to the backend.
|
||||||
|
|
||||||
|
##### Path Matcher Usage Guidelines
|
||||||
|
|
||||||
|
This section explains when to use the various path matchers.
|
||||||
|
|
||||||
|
Use `Path` if your backend listens on the exact path only. For instance, `Path: /products` would match `/products` but not `/products/shoes`.
|
||||||
|
|
||||||
|
Use a `*Prefix*` matcher if your backend listens on a particular base path but also serves requests on sub-paths.
|
||||||
|
For instance, `PathPrefix: /products` would match `/products` but also `/products/shoes` and `/products/shirts`.
|
||||||
|
Since the path is forwarded as-is, your backend is expected to listen on `/products`.
|
||||||
|
|
||||||
|
Use a `*Strip` matcher if your backend listens on the root path (`/`) but should be routeable on a specific prefix.
|
||||||
|
For instance, `PathPrefixStrip: /products` would match `/products` but also `/products/shoes` and `/products/shirts`.
|
||||||
|
Since the path is stripped prior to forwarding, your backend is expected to listen on `/`.
|
||||||
|
If your backend is serving assets (e.g., images or Javascript files), chances are it must return properly constructed relative URLs.
|
||||||
|
Continuing on the example, the backend should return `/products/shoes/image.png` (and not `/images.png` which Traefik would likely not be able to associate with the same backend).
|
||||||
|
The `X-Forwarded-Prefix` header (available since Traefik 1.3) can be queried to build such URLs dynamically.
|
||||||
|
|
||||||
|
Instead of distinguishing your backends by path only, you can add a Host matcher to the mix.
|
||||||
|
That way, namespacing of your backends happens on the basis of hosts in addition to paths.
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
Here is an example of frontends definition:
|
Here is an example of frontends definition:
|
||||||
|
|
||||||
@@ -78,36 +150,170 @@ Here is an example of frontends definition:
|
|||||||
[frontends.frontend1]
|
[frontends.frontend1]
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
[frontends.frontend1.routes.test_1]
|
[frontends.frontend1.routes.test_1]
|
||||||
rule = "Host: test.localhost, test2.localhost"
|
rule = "Host:test.localhost,test2.localhost"
|
||||||
[frontends.frontend2]
|
[frontends.frontend2]
|
||||||
backend = "backend1"
|
backend = "backend1"
|
||||||
passHostHeader = true
|
passHostHeader = true
|
||||||
|
passTLSCert = true
|
||||||
|
priority = 10
|
||||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||||
[frontends.frontend2.routes.test_1]
|
[frontends.frontend2.routes.test_1]
|
||||||
rule = "Host: localhost, {subdomain:[a-z]+}.localhost"
|
rule = "HostRegexp:localhost,{subdomain:[a-z]+}.localhost"
|
||||||
[frontends.frontend3]
|
[frontends.frontend3]
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
rule = "Path:/test"
|
[frontends.frontend3.routes.test_1]
|
||||||
|
rule = "Host:test3.localhost;Path:/test"
|
||||||
```
|
```
|
||||||
|
|
||||||
- Three frontends are defined: `frontend1`, `frontend2` and `frontend3`
|
- 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
|
- `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)
|
- `frontend2` will forward the traffic to the `backend1` if the rule `Host:localhost,{subdomain:[a-z]+}.localhost` is matched (forwarding client `Host` header to the backend)
|
||||||
- `frontend3` will forward the traffic to the `backend2` if the rule `Path:/test` is matched
|
- `frontend3` will forward the traffic to the `backend2` if the rules `Host:test3.localhost` **AND** `Path:/test` are matched
|
||||||
|
|
||||||
## Backends
|
#### 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 = "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"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Rules Order
|
||||||
|
|
||||||
|
When combining `Modifier` rules with `Matcher` rules, it is important to remember that `Modifier` rules **ALWAYS** apply after the `Matcher` rules.
|
||||||
|
|
||||||
|
The following rules are both `Matchers` and `Modifiers`, so the `Matcher` portion of the rule will apply first, and the `Modifier` will apply later.
|
||||||
|
|
||||||
|
- `PathStrip`
|
||||||
|
- `PathStripRegex`
|
||||||
|
- `PathPrefixStrip`
|
||||||
|
- `PathPrefixStripRegex`
|
||||||
|
|
||||||
|
`Modifiers` will be applied in a pre-determined order regardless of their order in the `rule` configuration section.
|
||||||
|
|
||||||
|
1. `PathStrip`
|
||||||
|
2. `PathPrefixStrip`
|
||||||
|
3. `PathStripRegex`
|
||||||
|
4. `PathPrefixStripRegex`
|
||||||
|
5. `AddPrefix`
|
||||||
|
6. `ReplacePath`
|
||||||
|
|
||||||
|
#### 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:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[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`).
|
||||||
|
|
||||||
|
#### Custom headers
|
||||||
|
|
||||||
|
Custom headers can be configured through the frontends, to add headers to either requests or responses that match the frontend's rules.
|
||||||
|
This allows for setting headers such as `X-Script-Name` to be added to the request, or custom headers to be added to the response.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[frontends]
|
||||||
|
[frontends.frontend1]
|
||||||
|
backend = "backend1"
|
||||||
|
[frontends.frontend1.headers.customresponseheaders]
|
||||||
|
X-Custom-Response-Header = "True"
|
||||||
|
[frontends.frontend1.headers.customrequestheaders]
|
||||||
|
X-Script-Name = "test"
|
||||||
|
[frontends.frontend1.routes.test_1]
|
||||||
|
rule = "PathPrefixStrip:/cheese"
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, all matches to the path `/cheese` will have the `X-Script-Name` header added to the proxied request, and the `X-Custom-Response-Header` added to the response.
|
||||||
|
|
||||||
|
#### Security headers
|
||||||
|
|
||||||
|
Security related headers (HSTS headers, SSL redirection, Browser XSS filter, etc) can be added and configured per frontend in a similar manner to the custom headers above.
|
||||||
|
This functionality allows for some easy security features to quickly be set.
|
||||||
|
|
||||||
|
An example of some of the security headers:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[frontends]
|
||||||
|
[frontends.frontend1]
|
||||||
|
backend = "backend1"
|
||||||
|
[frontends.frontend1.headers]
|
||||||
|
FrameDeny = true
|
||||||
|
[frontends.frontend1.routes.test_1]
|
||||||
|
rule = "PathPrefixStrip:/cheddar"
|
||||||
|
[frontends.frontend2]
|
||||||
|
backend = "backend2"
|
||||||
|
[frontends.frontend2.headers]
|
||||||
|
SSLRedirect = true
|
||||||
|
[frontends.frontend2.routes.test_1]
|
||||||
|
rule = "PathPrefixStrip:/stilton"
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, traffic routed through the first frontend will have the `X-Frame-Options` header set to `DENY`, and the second will only allow HTTPS request through, otherwise will return a 301 HTTPS redirect.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The detailed documentation for those security headers can be found in [unrolled/secure](https://github.com/unrolled/secure#available-options).
|
||||||
|
|
||||||
|
### Backends
|
||||||
|
|
||||||
A backend is responsible to load-balance the traffic coming from one or more frontends to a set of http servers.
|
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:
|
|
||||||
|
Various methods of load-balancing are supported:
|
||||||
|
|
||||||
- `wrr`: Weighted Round Robin
|
- `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.
|
- `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.
|
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.
|
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.
|
In case the condition matches, CB enters Tripped state, where it responds with predefined code or redirects to another frontend.
|
||||||
Once Tripped timer expires, CB enters Recovering state and resets all stats.
|
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.
|
In case the condition does not match and recovery timer expires, CB enters Standby state.
|
||||||
|
|
||||||
It can be configured using:
|
It can be configured using:
|
||||||
|
|
||||||
@@ -140,9 +346,73 @@ For example:
|
|||||||
- Another possible value for `extractorfunc` is `client.ip` which will categorize requests based on client source ip.
|
- 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.
|
- Lastly `extractorfunc` can take the value of `request.header.ANY_HEADER` which will categorize requests based on `ANY_HEADER` that you provide.
|
||||||
|
|
||||||
## Servers
|
### Sticky sessions
|
||||||
|
|
||||||
Servers are simply defined using a `URL`. You can also apply a custom `weight` to each server (this will be used by load-balancing).
|
Sticky sessions are supported with both load balancers.
|
||||||
|
When sticky sessions are enabled, a cookie is set on the initial request.
|
||||||
|
The default cookie name is an abbreviation of a sha1 (ex: `_1d52e`).
|
||||||
|
On subsequent requests, the client will be directed to the backend stored in the cookie if it is still healthy.
|
||||||
|
If not, a new backend will be assigned.
|
||||||
|
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[backends]
|
||||||
|
[backends.backend1]
|
||||||
|
# Enable sticky session
|
||||||
|
[backends.backend1.loadbalancer.stickiness]
|
||||||
|
|
||||||
|
# Customize the cookie name
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: a sha1 (6 chars)
|
||||||
|
#
|
||||||
|
# cookieName = "my_cookie"
|
||||||
|
```
|
||||||
|
|
||||||
|
The deprecated way:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[backends]
|
||||||
|
[backends.backend1]
|
||||||
|
[backends.backend1.loadbalancer]
|
||||||
|
sticky = true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Check
|
||||||
|
|
||||||
|
A health check can be configured in order to remove a backend from LB rotation as long as it keeps returning HTTP status codes other than `200 OK` to HTTP GET requests periodically carried out by Traefik.
|
||||||
|
The check is defined by a pathappended to the backend URL and an interval (given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration)) specifying how often the health check should be executed (the default being 30 seconds).
|
||||||
|
Each backend must respond to the health check within 5 seconds.
|
||||||
|
By default, the port of the backend server is used, however, this may be overridden.
|
||||||
|
|
||||||
|
A recovering backend returning 200 OK responses again is being returned to the
|
||||||
|
LB rotation pool.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```toml
|
||||||
|
[backends]
|
||||||
|
[backends.backend1]
|
||||||
|
[backends.backend1.healthcheck]
|
||||||
|
path = "/health"
|
||||||
|
interval = "10s"
|
||||||
|
```
|
||||||
|
|
||||||
|
To use a different port for the healthcheck:
|
||||||
|
```toml
|
||||||
|
[backends]
|
||||||
|
[backends.backend1]
|
||||||
|
[backends.backend1.healthcheck]
|
||||||
|
path = "/health"
|
||||||
|
interval = "10s"
|
||||||
|
port = 8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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).
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
Paths in `url` are ignored. Use `Modifier` to specify paths instead.
|
||||||
|
|
||||||
Here is an example of backends and servers definition:
|
Here is an example of backends and servers definition:
|
||||||
|
|
||||||
@@ -150,7 +420,7 @@ Here is an example of backends and servers definition:
|
|||||||
[backends]
|
[backends]
|
||||||
[backends.backend1]
|
[backends.backend1]
|
||||||
[backends.backend1.circuitbreaker]
|
[backends.backend1.circuitbreaker]
|
||||||
expression = "NetworkErrorRatio() > 0.5"
|
expression = "NetworkErrorRatio() > 0.5"
|
||||||
[backends.backend1.servers.server1]
|
[backends.backend1.servers.server1]
|
||||||
url = "http://172.17.0.2:80"
|
url = "http://172.17.0.2:80"
|
||||||
weight = 10
|
weight = 10
|
||||||
@@ -159,7 +429,7 @@ Here is an example of backends and servers definition:
|
|||||||
weight = 1
|
weight = 1
|
||||||
[backends.backend2]
|
[backends.backend2]
|
||||||
[backends.backend2.LoadBalancer]
|
[backends.backend2.LoadBalancer]
|
||||||
method = "drr"
|
method = "drr"
|
||||||
[backends.backend2.servers.server1]
|
[backends.backend2.servers.server1]
|
||||||
url = "http://172.17.0.4:80"
|
url = "http://172.17.0.4:80"
|
||||||
weight = 1
|
weight = 1
|
||||||
@@ -173,30 +443,136 @@ Here is an example of backends and servers definition:
|
|||||||
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2` using `drr` load-balancing strategy.
|
- `backend2` will forward the traffic to two servers: `http://172.17.0.4:80"` with weight `1` and `http://172.17.0.5:80` with weight `2` using `drr` load-balancing strategy.
|
||||||
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
|
- a circuit breaker is added on `backend1` using the expression `NetworkErrorRatio() > 0.5`: watch error ratio over 10 second sliding window
|
||||||
|
|
||||||
# Launch
|
|
||||||
|
|
||||||
Træfɪk can be configured using a TOML file configuration, arguments, or both.
|
## Configuration
|
||||||
By default, Træfɪk will try to find a `traefik.toml` in the following places:
|
|
||||||
|
Træfik's configuration has two parts:
|
||||||
|
|
||||||
|
- The [static Træfik configuration](/basics#static-trfk-configuration) which is loaded only at the beginning.
|
||||||
|
- The [dynamic Træfik configuration](/basics#dynamic-trfk-configuration) which can be hot-reloaded (no need to restart the process).
|
||||||
|
|
||||||
|
### Static Træfik configuration
|
||||||
|
|
||||||
|
The static configuration is the global configuration which is setting up connections to configuration backends and entrypoints.
|
||||||
|
|
||||||
|
Træfik can be configured using many configuration sources with the following precedence order.
|
||||||
|
Each item takes precedence over the item below it:
|
||||||
|
|
||||||
|
- [Key-value store](/basics/#key-value-stores)
|
||||||
|
- [Arguments](/basics/#arguments)
|
||||||
|
- [Configuration file](/basics/#configuration-file)
|
||||||
|
- Default
|
||||||
|
|
||||||
|
It means that arguments override configuration file, and key-value store overrides arguments.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
the provider-enabling argument parameters (e.g., `--docker`) set all default values for the specific provider.
|
||||||
|
It must not be used if a configuration source with less precedence wants to set a non-default provider value.
|
||||||
|
|
||||||
|
#### Configuration file
|
||||||
|
|
||||||
|
By default, Træfik will try to find a `traefik.toml` in the following places:
|
||||||
|
|
||||||
- `/etc/traefik/`
|
- `/etc/traefik/`
|
||||||
- `$HOME/.traefik/`
|
- `$HOME/.traefik/`
|
||||||
- `.` *the working directory*
|
- `.` _the working directory_
|
||||||
|
|
||||||
You can override this by setting a `configFile` argument:
|
You can override this by setting a `configFile` argument:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ traefik --configFile=foo/bar/myconfigfile.toml
|
traefik --configFile=foo/bar/myconfigfile.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
Træfɪk uses the following precedence order. Each item takes precedence over the item below it:
|
Please refer to the [global configuration](/configuration/commons) section to get documentation on it.
|
||||||
|
|
||||||
- arguments
|
#### Arguments
|
||||||
- configuration file
|
|
||||||
- default
|
|
||||||
|
|
||||||
It means that arguments overrides configuration file.
|
Each argument (and command) is described in the help section:
|
||||||
Each argument is described in the help section:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ traefik --help
|
traefik --help
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that all default values will be displayed as well.
|
||||||
|
|
||||||
|
#### Key-value stores
|
||||||
|
|
||||||
|
Træfik supports several Key-value stores:
|
||||||
|
|
||||||
|
- [Consul](https://consul.io)
|
||||||
|
- [etcd](https://coreos.com/etcd/)
|
||||||
|
- [ZooKeeper](https://zookeeper.apache.org/)
|
||||||
|
- [boltdb](https://github.com/boltdb/bolt)
|
||||||
|
|
||||||
|
Please refer to the [User Guide Key-value store configuration](/user-guide/kv-config/) section to get documentation on it.
|
||||||
|
|
||||||
|
### Dynamic Træfik configuration
|
||||||
|
|
||||||
|
The dynamic configuration concerns :
|
||||||
|
|
||||||
|
- [Frontends](/basics/#frontends)
|
||||||
|
- [Backends](/basics/#backends)
|
||||||
|
- [Servers](/basics/#servers)
|
||||||
|
|
||||||
|
Træfik can hot-reload those rules which could be provided by [multiple configuration backends](/configuration/commons).
|
||||||
|
|
||||||
|
We only need to enable `watch` option to make Træfik watch configuration backend changes and generate its configuration automatically.
|
||||||
|
Routes to services will be created and updated instantly at any changes.
|
||||||
|
|
||||||
|
Please refer to the [configuration backends](/configuration/commons) section to get documentation on it.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### traefik
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
```bash
|
||||||
|
traefik [command] [--flag=flag_argument]
|
||||||
|
```
|
||||||
|
|
||||||
|
List of Træfik available commands with description :
|
||||||
|
|
||||||
|
- `version` : Print version
|
||||||
|
- `storeconfig` : Store the static Traefik configuration into a Key-value stores. Please refer to the [Store Træfik configuration](/user-guide/kv-config/#store-trfk-configuration) section to get documentation on it.
|
||||||
|
- `bug`: The easiest way to submit a pre-filled issue.
|
||||||
|
- `healthcheck`: Calls Traefik `/ping` to check health.
|
||||||
|
|
||||||
|
Each command may have related flags.
|
||||||
|
|
||||||
|
All those related flags will be displayed with :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
traefik [command] --help
|
||||||
|
```
|
||||||
|
|
||||||
|
Each command is described at the beginning of the help section:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
traefik --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command: bug
|
||||||
|
|
||||||
|
Here is the easiest way to submit a pre-filled issue on [Træfik GitHub](https://github.com/containous/traefik).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
traefik bug
|
||||||
|
```
|
||||||
|
|
||||||
|
Watch [this demo](https://www.youtube.com/watch?v=Lyz62L8m93I).
|
||||||
|
|
||||||
|
### Command: healthcheck
|
||||||
|
|
||||||
|
This command allows to check the health of Traefik. Its exit status is `0` if Traefik is healthy and `1` if it is unhealthy.
|
||||||
|
|
||||||
|
This can be used with Docker [HEALTHCHECK](https://docs.docker.com/engine/reference/builder/#healthcheck) instruction or any other health check orchestration mechanism.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The [`web` provider](/configuration/backends/web) must be enabled to allow `/ping` calls by the `healthcheck` command.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
traefik healthcheck
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
OK: http://:8082/ping
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ I used 4 VMs for the tests with the following configuration:
|
|||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1. One VM used to launch the benchmarking tool [wrk](https://github.com/wg/wrk)
|
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)
|
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/)
|
3. Two VMs for 2 backend servers in go [whoami](https://github.com/emilevauge/whoamI/)
|
||||||
|
|
||||||
Each VM has been tuned using the following limits:
|
Each VM has been tuned using the following limits:
|
||||||
@@ -117,7 +117,7 @@ server {
|
|||||||
|
|
||||||
Here is the `traefik.toml` file used:
|
Here is the `traefik.toml` file used:
|
||||||
|
|
||||||
```
|
```toml
|
||||||
MaxIdleConnsPerHost = 100000
|
MaxIdleConnsPerHost = 100000
|
||||||
defaultEntryPoints = ["http"]
|
defaultEntryPoints = ["http"]
|
||||||
|
|
||||||
@@ -145,8 +145,8 @@ defaultEntryPoints = ["http"]
|
|||||||
## Results
|
## Results
|
||||||
|
|
||||||
### whoami:
|
### whoami:
|
||||||
```
|
```shell
|
||||||
wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench
|
wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-whoami:80/bench
|
||||||
Running 1m test @ http://IP-whoami:80/bench
|
Running 1m test @ http://IP-whoami:80/bench
|
||||||
20 threads and 1000 connections
|
20 threads and 1000 connections
|
||||||
Thread Stats Avg Stdev Max +/- Stdev
|
Thread Stats Avg Stdev Max +/- Stdev
|
||||||
@@ -164,7 +164,7 @@ Transfer/sec: 6.40MB
|
|||||||
```
|
```
|
||||||
|
|
||||||
### nginx:
|
### nginx:
|
||||||
```
|
```shell
|
||||||
wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-nginx:8001/bench
|
wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-nginx:8001/bench
|
||||||
Running 1m test @ http://IP-nginx:8001/bench
|
Running 1m test @ http://IP-nginx:8001/bench
|
||||||
20 threads and 1000 connections
|
20 threads and 1000 connections
|
||||||
@@ -182,9 +182,10 @@ Requests/sec: 33591.67
|
|||||||
Transfer/sec: 4.97MB
|
Transfer/sec: 4.97MB
|
||||||
```
|
```
|
||||||
|
|
||||||
### traefik:
|
### Traefik:
|
||||||
```
|
|
||||||
wrk -t8 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench
|
```shell
|
||||||
|
wrk -t20 -c1000 -d60s -H "Host: test.traefik" --latency http://IP-traefik:8000/bench
|
||||||
Running 1m test @ http://IP-traefik:8000/bench
|
Running 1m test @ http://IP-traefik:8000/bench
|
||||||
20 threads and 1000 connections
|
20 threads and 1000 connections
|
||||||
Thread Stats Avg Stdev Max +/- Stdev
|
Thread Stats Avg Stdev Max +/- Stdev
|
||||||
@@ -209,5 +210,5 @@ Not bad for young project :) !
|
|||||||
Some areas of possible improvements:
|
Some areas of possible improvements:
|
||||||
|
|
||||||
- Use [GO_REUSEPORT](https://github.com/kavu/go_reuseport) listener
|
- 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)
|
- 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)
|
||||||
|
|
||||||
|
|||||||
238
docs/configuration/acme.md
Normal file
238
docs/configuration/acme.md
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
# ACME (Let's Encrypt) configuration
|
||||||
|
|
||||||
|
See also [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) and [Docker & Let's Encrypt user guide](/user-guide/docker-and-lets-encrypt).
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Sample entrypoint configuration when using ACME.
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
|
||||||
|
# Enable ACME (Let's Encrypt): automatic SSL.
|
||||||
|
[acme]
|
||||||
|
|
||||||
|
# Email address used for registration.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
#
|
||||||
|
email = "test@traefik.io"
|
||||||
|
|
||||||
|
# File or key used for certificates storage.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
#
|
||||||
|
storage = "acme.json"
|
||||||
|
# or `storage = "traefik/acme/account"` if using KV store.
|
||||||
|
|
||||||
|
# Entrypoint to proxy acme challenge/apply certificates to.
|
||||||
|
# WARNING, must point to an entrypoint on port 443
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
#
|
||||||
|
entryPoint = "https"
|
||||||
|
|
||||||
|
# Use a DNS based acme challenge rather than external HTTPS access
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# dnsProvider = "digitalocean"
|
||||||
|
|
||||||
|
# By default, the dnsProvider will verify the TXT DNS challenge record before letting ACME verify.
|
||||||
|
# If delayDontCheckDNS is greater than zero, avoid this & instead just wait so many seconds.
|
||||||
|
# Useful if internal networks block external DNS queries.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# delayDontCheckDNS = 0
|
||||||
|
|
||||||
|
# If true, display debug log messages from the acme client library.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# acmeLogging = true
|
||||||
|
|
||||||
|
# Enable on demand certificate.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# onDemand = true
|
||||||
|
|
||||||
|
# Enable certificate generation on frontends Host rules.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# onHostRule = true
|
||||||
|
|
||||||
|
# CA server to use.
|
||||||
|
# - Uncomment the line to run on the staging let's encrypt server.
|
||||||
|
# - Leave comment to go to prod.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# caServer = "https://acme-staging.api.letsencrypt.org/directory"
|
||||||
|
|
||||||
|
# Domains list.
|
||||||
|
#
|
||||||
|
# [[acme.domains]]
|
||||||
|
# main = "local1.com"
|
||||||
|
# sans = ["test1.local1.com", "test2.local1.com"]
|
||||||
|
# [[acme.domains]]
|
||||||
|
# main = "local2.com"
|
||||||
|
# sans = ["test1.local2.com", "test2.local2.com"]
|
||||||
|
# [[acme.domains]]
|
||||||
|
# main = "local3.com"
|
||||||
|
# [[acme.domains]]
|
||||||
|
# main = "local4.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `storage`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[acme]
|
||||||
|
# ...
|
||||||
|
storage = "acme.json"
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
File or key used for certificates storage.
|
||||||
|
|
||||||
|
**WARNING** If you use Traefik in Docker, you have 2 options:
|
||||||
|
|
||||||
|
- create a file on your host and mount it as a volume:
|
||||||
|
```toml
|
||||||
|
storage = "acme.json"
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
docker run -v "/my/host/acme.json:acme.json" traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
- mount the folder containing the file as a volume
|
||||||
|
```toml
|
||||||
|
storage = "/etc/traefik/acme/acme.json"
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
docker run -v "/my/host/acme:/etc/traefik/acme" traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
### `dnsProvider`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[acme]
|
||||||
|
# ...
|
||||||
|
dnsProvider = "digitalocean"
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Use a DNS based acme challenge rather than external HTTPS access, e.g. for a firewalled server.
|
||||||
|
|
||||||
|
Select the provider that matches the DNS domain that will host the challenge TXT record, and provide environment variables with access keys to enable setting it:
|
||||||
|
|
||||||
|
| Provider | Configuration |
|
||||||
|
|----------------------------------------------|-----------------------------------------------------------------------------------------------------------|
|
||||||
|
| [Cloudflare](https://www.cloudflare.com) | `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY` |
|
||||||
|
| [DigitalOcean](https://www.digitalocean.com) | `DO_AUTH_TOKEN` |
|
||||||
|
| [DNSimple](https://dnsimple.com) | `DNSIMPLE_EMAIL`, `DNSIMPLE_OAUTH_TOKEN` |
|
||||||
|
| [DNS Made Easy](https://dnsmadeeasy.com) | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET` |
|
||||||
|
| [Exoscale](https://www.exoscale.ch) | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET` |
|
||||||
|
| [Gandi](https://www.gandi.net) | `GANDI_API_KEY` |
|
||||||
|
| [Linode](https://www.linode.com) | `LINODE_API_KEY` |
|
||||||
|
| manual | none, but run Traefik interactively & turn on `acmeLogging` to see instructions & press <kbd>Enter</kbd>. |
|
||||||
|
| [Namecheap](https://www.namecheap.com) | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` |
|
||||||
|
| RFC2136 | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` |
|
||||||
|
| [Route 53](https://aws.amazon.com/route53/) | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, or configured user/instance IAM profile. |
|
||||||
|
| [dyn](https://dyn.com) | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` |
|
||||||
|
| [VULTR](https://www.vultr.com) | `VULTR_API_KEY` |
|
||||||
|
| [OVH](https://www.ovh.com) | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` |
|
||||||
|
| [pdns](https://www.powerdns.com) | `PDNS_API_KEY`, `PDNS_API_URL` |
|
||||||
|
|
||||||
|
### `delayDontCheckDNS`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[acme]
|
||||||
|
# ...
|
||||||
|
delayDontCheckDNS = 0
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the dnsProvider will verify the TXT DNS challenge record before letting ACME verify.
|
||||||
|
If `delayDontCheckDNS` is greater than zero, avoid this & instead just wait so many seconds.
|
||||||
|
|
||||||
|
Useful if internal networks block external DNS queries.
|
||||||
|
|
||||||
|
### `onDemand`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[acme]
|
||||||
|
# ...
|
||||||
|
onDemand = true
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
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 lead to DoS attacks.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits)
|
||||||
|
|
||||||
|
### `onHostRule`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[acme]
|
||||||
|
# ...
|
||||||
|
onHostRule = true
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable certificate generation on frontends Host rules.
|
||||||
|
|
||||||
|
This will request a certificate from Let's Encrypt for each frontend with a Host rule.
|
||||||
|
|
||||||
|
For example, a rule `Host:test1.traefik.io,test2.traefik.io` will request a certificate with main domain `test1.traefik.io` and SAN `test2.traefik.io`.
|
||||||
|
|
||||||
|
### `caServer`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[acme]
|
||||||
|
# ...
|
||||||
|
caServer = "https://acme-staging.api.letsencrypt.org/directory"
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
CA server to use.
|
||||||
|
|
||||||
|
- Uncomment the line to run on the staging Let's Encrypt server.
|
||||||
|
- Leave comment to go to prod.
|
||||||
|
|
||||||
|
### `domains`
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[acme]
|
||||||
|
# ...
|
||||||
|
[[acme.domains]]
|
||||||
|
main = "local1.com"
|
||||||
|
sans = ["test1.local1.com", "test2.local1.com"]
|
||||||
|
[[acme.domains]]
|
||||||
|
main = "local2.com"
|
||||||
|
sans = ["test1.local2.com", "test2.local2.com"]
|
||||||
|
[[acme.domains]]
|
||||||
|
main = "local3.com"
|
||||||
|
[[acme.domains]]
|
||||||
|
main = "local4.com"
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
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://letsencrypt.org/docs/rate-limits).
|
||||||
|
|
||||||
|
Each domain & SANs will lead to a certificate request.
|
||||||
59
docs/configuration/backends/boltdb.md
Normal file
59
docs/configuration/backends/boltdb.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# BoltDB Backend
|
||||||
|
|
||||||
|
Træfik can be configured to use BoltDB as a backend configuration.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# BoltDB configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable BoltDB configuration backend.
|
||||||
|
[boltdb]
|
||||||
|
|
||||||
|
# BoltDB file.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: "127.0.0.1:4001"
|
||||||
|
#
|
||||||
|
endpoint = "/my.db"
|
||||||
|
|
||||||
|
# Enable watch BoltDB changes.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
watch = true
|
||||||
|
|
||||||
|
# Prefix used for KV store.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "/traefik"
|
||||||
|
#
|
||||||
|
prefix = "/traefik"
|
||||||
|
|
||||||
|
# Override default configuration template.
|
||||||
|
# For advanced users :)
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
filename = "boltdb.tmpl"
|
||||||
|
|
||||||
|
# Use BoltDB user/pass authentication.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# username = foo
|
||||||
|
# password = bar
|
||||||
|
|
||||||
|
# Enable BoltDB TLS connection.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# [boltdb.tls]
|
||||||
|
# ca = "/etc/ssl/ca.crt"
|
||||||
|
# cert = "/etc/ssl/boltdb.crt"
|
||||||
|
# key = "/etc/ssl/boltdb.key"
|
||||||
|
# insecureskipverify = true
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||||
136
docs/configuration/backends/consul.md
Normal file
136
docs/configuration/backends/consul.md
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# Consul Backend
|
||||||
|
|
||||||
|
## Consul Key-Value backend
|
||||||
|
|
||||||
|
Træfik can be configured to use Consul as a backend configuration.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# Consul KV configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable Consul KV configuration backend.
|
||||||
|
[consul]
|
||||||
|
|
||||||
|
# Consul server endpoint.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: "127.0.0.1:8500"
|
||||||
|
#
|
||||||
|
endpoint = "127.0.0.1:8500"
|
||||||
|
|
||||||
|
# Enable watch Consul changes.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
watch = true
|
||||||
|
|
||||||
|
# Prefix used for KV store.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: traefik
|
||||||
|
#
|
||||||
|
prefix = "traefik"
|
||||||
|
|
||||||
|
# Override default configuration template.
|
||||||
|
# For advanced users :)
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# filename = "consul.tmpl"
|
||||||
|
|
||||||
|
# Use Consul user/pass authentication.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# username = foo
|
||||||
|
# password = bar
|
||||||
|
|
||||||
|
# Enable Consul TLS connection.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# [consul.tls]
|
||||||
|
# ca = "/etc/ssl/ca.crt"
|
||||||
|
# cert = "/etc/ssl/consul.crt"
|
||||||
|
# key = "/etc/ssl/consul.key"
|
||||||
|
# insecureskipverify = true
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||||
|
|
||||||
|
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
|
||||||
|
|
||||||
|
|
||||||
|
## Consul Catalog backend
|
||||||
|
|
||||||
|
Træfik can be configured to use service discovery catalog of Consul as a backend configuration.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# Consul Catalog configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable Consul Catalog configuration backend.
|
||||||
|
[consulCatalog]
|
||||||
|
|
||||||
|
# Consul server endpoint.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: "127.0.0.1:8500"
|
||||||
|
#
|
||||||
|
endpoint = "127.0.0.1:8500"
|
||||||
|
|
||||||
|
# Expose Consul catalog services by default in Traefik.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
exposedByDefault = false
|
||||||
|
|
||||||
|
# Prefix for Consul catalog tags.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "traefik"
|
||||||
|
#
|
||||||
|
prefix = "traefik"
|
||||||
|
|
||||||
|
# Default frontEnd Rule for Consul services.
|
||||||
|
#
|
||||||
|
# The format is a Go Template with:
|
||||||
|
# - ".ServiceName", ".Domain" and ".Attributes" available
|
||||||
|
# - "getTag(name, tags, defaultValue)", "hasTag(name, tags)" and "getAttribute(name, tags, defaultValue)" functions are available
|
||||||
|
# - "getAttribute(...)" function uses prefixed tag names based on "prefix" value
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "Host:{{.ServiceName}}.{{.Domain}}"
|
||||||
|
#
|
||||||
|
#frontEndRule = "Host:{{.ServiceName}}.{{Domain}}"
|
||||||
|
```
|
||||||
|
|
||||||
|
This backend will create routes matching on hostname based on the service name used in Consul.
|
||||||
|
|
||||||
|
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||||
|
|
||||||
|
### Tags
|
||||||
|
|
||||||
|
Additional settings can be defined using Consul Catalog tags.
|
||||||
|
|
||||||
|
| Tag | Description |
|
||||||
|
|-----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `traefik.enable=false` | Disable this container in Træfik |
|
||||||
|
| `traefik.protocol=https` | Override the default `http` protocol |
|
||||||
|
| `traefik.backend.weight=10` | Assign this weight to the container |
|
||||||
|
| `traefik.backend.circuitbreaker=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend, ex: `NetworkErrorRatio() > 0.` |
|
||||||
|
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. |
|
||||||
|
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. |
|
||||||
|
| `traefik.frontend.rule=Host:test.traefik.io` | Override the default frontend rule (Default: `Host:{{.ServiceName}}.{{.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.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||||
|
| `traefik.backend.loadbalancer=drr` | override the default `wrr` load balancer algorithm |
|
||||||
|
| `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions |
|
||||||
|
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||||
|
| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions (DEPRECATED) |
|
||||||
193
docs/configuration/backends/docker.md
Normal file
193
docs/configuration/backends/docker.md
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
# Docker Backend
|
||||||
|
|
||||||
|
Træfik can be configured to use Docker as a backend configuration.
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# Docker configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable Docker configuration backend.
|
||||||
|
[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"
|
||||||
|
|
||||||
|
# Expose containers by default in Traefik.
|
||||||
|
# If set to false, containers that don't have `traefik.enable=true` will be ignored.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
exposedbydefault = true
|
||||||
|
|
||||||
|
# Use the IP address from the binded port instead of the inner network one.
|
||||||
|
# For specific use-case :)
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
usebindportip = true
|
||||||
|
|
||||||
|
# Use Swarm Mode services as data provider.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
swarmmode = false
|
||||||
|
|
||||||
|
# Enable docker TLS connection.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# [docker.tls]
|
||||||
|
# ca = "/etc/ssl/ca.crt"
|
||||||
|
# cert = "/etc/ssl/docker.crt"
|
||||||
|
# key = "/etc/ssl/docker.key"
|
||||||
|
# insecureskipverify = true
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||||
|
|
||||||
|
|
||||||
|
## Docker Swarm Mode
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# Docker Swarmmode configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable Docker configuration backend.
|
||||||
|
[docker]
|
||||||
|
|
||||||
|
# Docker server endpoint.
|
||||||
|
# Can be a tcp or a unix socket endpoint.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: "unix:///var/run/docker.sock"
|
||||||
|
#
|
||||||
|
endpoint = "tcp://127.0.0.1:2375"
|
||||||
|
|
||||||
|
# Default domain used.
|
||||||
|
# Can be overridden by setting the "traefik.domain" label on a services.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: ""
|
||||||
|
#
|
||||||
|
domain = "docker.localhost"
|
||||||
|
|
||||||
|
# Enable watch docker changes.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
watch = true
|
||||||
|
|
||||||
|
# Use Docker Swarm Mode as data provider.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
swarmmode = true
|
||||||
|
|
||||||
|
# Override default configuration template.
|
||||||
|
# For advanced users :)
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# filename = "docker.tmpl"
|
||||||
|
|
||||||
|
# Expose services by default in Traefik.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
exposedbydefault = false
|
||||||
|
|
||||||
|
# Enable docker TLS connection.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# [docker.tls]
|
||||||
|
# ca = "/etc/ssl/ca.crt"
|
||||||
|
# cert = "/etc/ssl/docker.crt"
|
||||||
|
# key = "/etc/ssl/docker.key"
|
||||||
|
# insecureskipverify = true
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||||
|
|
||||||
|
## Labels: overriding default behaviour
|
||||||
|
|
||||||
|
### On Containers
|
||||||
|
|
||||||
|
Labels can be used on containers to override default behaviour.
|
||||||
|
|
||||||
|
| Label | Description |
|
||||||
|
|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `traefik.backend=foo` | Give the name `foo` to the generated backend for this container. |
|
||||||
|
| `traefik.backend.maxconn.amount=10` | Set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. |
|
||||||
|
| `traefik.backend.maxconn.extractorfunc=client.ip` | Set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. |
|
||||||
|
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
|
||||||
|
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |
|
||||||
|
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||||
|
| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) |
|
||||||
|
| `traefik.backend.loadbalancer.swarm=true` | Use Swarm's inbuilt load balancer (only relevant under Swarm Mode). |
|
||||||
|
| `traefik.backend.circuitbreaker.expression=EXPR` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||||
|
| `traefik.port=80` | Register this port. Useful when the container exposes multiples ports. |
|
||||||
|
| `traefik.protocol=https` | Override the default `http` protocol |
|
||||||
|
| `traefik.weight=10` | Assign this weight to the container |
|
||||||
|
| `traefik.enable=false` | Disable this container in Træfik |
|
||||||
|
| `traefik.frontend.rule=EXPR` | Override the default frontend rule. Default: `Host:{containerName}.{domain}` or `Host:{service}.{project_name}.{domain}` if you are using `docker-compose`. |
|
||||||
|
| `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.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||||
|
| `traefik.frontend.whitelistSourceRange:RANGE` | List of IP-Ranges which are allowed to access. An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
|
||||||
|
| `traefik.docker.network` | Set the docker network to use for connections to this container. If a container is linked to several networks, be sure to set the proper network name (you can check with `docker inspect <container_id>`) otherwise it will randomly pick one (depending on how docker is returning them). For instance when deploying docker `stack` from compose files, the compose defined networks will be prefixed with the `stack` name. |
|
||||||
|
|
||||||
|
### On Service
|
||||||
|
|
||||||
|
Services labels can be used for overriding default behaviour
|
||||||
|
|
||||||
|
| Label | Description |
|
||||||
|
|---------------------------------------------------|--------------------------------------------------------------------------------------------------|
|
||||||
|
| `traefik.<service-name>.port=PORT` | Overrides `traefik.port`. If several ports need to be exposed, the service labels could be used. |
|
||||||
|
| `traefik.<service-name>.protocol` | Overrides `traefik.protocol`. |
|
||||||
|
| `traefik.<service-name>.weight` | Assign this service weight. Overrides `traefik.weight`. |
|
||||||
|
| `traefik.<service-name>.frontend.backend=BACKEND` | Assign this service frontend to `BACKEND`. Default is to assign to the service backend. |
|
||||||
|
| `traefik.<service-name>.frontend.entryPoints` | Overrides `traefik.frontend.entrypoints` |
|
||||||
|
| `traefik.<service-name>.frontend.auth.basic` | Sets a Basic Auth for that frontend |
|
||||||
|
| `traefik.<service-name>.frontend.passHostHeader` | Overrides `traefik.frontend.passHostHeader`. |
|
||||||
|
| `traefik.<service-name>.frontend.priority` | Overrides `traefik.frontend.priority`. |
|
||||||
|
| `traefik.<service-name>.frontend.rule` | Overrides `traefik.frontend.rule`. |
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
when running inside a container, Træfik will need network access through:
|
||||||
|
|
||||||
|
`docker network connect <network> <traefik-container>`
|
||||||
71
docs/configuration/backends/dynamodb.md
Normal file
71
docs/configuration/backends/dynamodb.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# DynamoDB Backend
|
||||||
|
|
||||||
|
Træfik can be configured to use Amazon DynamoDB as a backend configuration.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# DynamoDB configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable DynamoDB configuration backend.
|
||||||
|
[dynamodb]
|
||||||
|
|
||||||
|
# Region to use when connecting to AWS.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
#
|
||||||
|
region = "us-west-1"
|
||||||
|
|
||||||
|
# DyanmoDB Table Name.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "traefik"
|
||||||
|
#
|
||||||
|
tableName = "traefik"
|
||||||
|
|
||||||
|
# Enable watch DynamoDB changes.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
watch = true
|
||||||
|
|
||||||
|
# Polling interval (in seconds).
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 15
|
||||||
|
#
|
||||||
|
refreshSeconds = 15
|
||||||
|
|
||||||
|
# AccessKeyID to use when connecting to AWS.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
accessKeyID = "abc"
|
||||||
|
|
||||||
|
# SecretAccessKey to use when connecting to AWS.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
secretAccessKey = "123"
|
||||||
|
|
||||||
|
# Endpoint of local dynamodb instance for testing?
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
endpoint = "http://localhost:8080"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Table Items
|
||||||
|
|
||||||
|
Items in the `dynamodb` table must have three attributes:
|
||||||
|
|
||||||
|
- `id` (string): The id is the primary key.
|
||||||
|
- `name`(string): The name is used as the name of the frontend or backend.
|
||||||
|
- `frontend` or `backend` (map): This attribute's structure matches exactly the structure of a Frontend or Backend type in Traefik.
|
||||||
|
See `types/types.go` for details.
|
||||||
|
The presence or absence of this attribute determines its type.
|
||||||
|
So an item should never have both a `frontend` and a `backend` attribute.
|
||||||
|
|
||||||
140
docs/configuration/backends/ecs.md
Normal file
140
docs/configuration/backends/ecs.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# ECS Backend
|
||||||
|
|
||||||
|
Træfik can be configured to use Amazon ECS as a backend configuration.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# ECS configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable ECS configuration backend.
|
||||||
|
[ecs]
|
||||||
|
|
||||||
|
# ECS Cluster Name.
|
||||||
|
#
|
||||||
|
# DEPRECATED - Please use `clusters`.
|
||||||
|
#
|
||||||
|
cluster = "default"
|
||||||
|
|
||||||
|
# ECS Clusters Name.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: ["default"]
|
||||||
|
#
|
||||||
|
clusters = ["default"]
|
||||||
|
|
||||||
|
# Enable watch ECS changes.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
watch = true
|
||||||
|
|
||||||
|
# Default domain used.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: ""
|
||||||
|
#
|
||||||
|
domain = "ecs.localhost"
|
||||||
|
|
||||||
|
# Enable auto discover ECS clusters.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
autoDiscoverClusters = false
|
||||||
|
|
||||||
|
# Polling interval (in seconds).
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 15
|
||||||
|
#
|
||||||
|
refreshSeconds = 15
|
||||||
|
|
||||||
|
# Expose ECS services by default in Traefik.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
exposedByDefault = false
|
||||||
|
|
||||||
|
# Region to use when connecting to AWS.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
region = "us-east-1"
|
||||||
|
|
||||||
|
# AccessKeyID to use when connecting to AWS.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
accessKeyID = "abc"
|
||||||
|
|
||||||
|
# SecretAccessKey to use when connecting to AWS.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
secretAccessKey = "123"
|
||||||
|
|
||||||
|
# Override default configuration template.
|
||||||
|
# For advanced users :)
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# filename = "ecs.tmpl"
|
||||||
|
```
|
||||||
|
|
||||||
|
If `AccessKeyID`/`SecretAccessKey` is not given credentials will be resolved in the following order:
|
||||||
|
|
||||||
|
- From environment variables; `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN`.
|
||||||
|
- Shared credentials, determined by `AWS_PROFILE` and `AWS_SHARED_CREDENTIALS_FILE`, defaults to `default` and `~/.aws/credentials`.
|
||||||
|
- EC2 instance role or ECS task role
|
||||||
|
|
||||||
|
## Policy
|
||||||
|
|
||||||
|
Træfik needs the following policy to read ECS information:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Sid": "TraefikECSReadAccess",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"ecs:ListClusters",
|
||||||
|
"ecs:DescribeClusters",
|
||||||
|
"ecs:ListTasks",
|
||||||
|
"ecs:DescribeTasks",
|
||||||
|
"ecs:DescribeContainerInstances",
|
||||||
|
"ecs:DescribeTaskDefinition",
|
||||||
|
"ec2:DescribeInstances"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Labels: overriding default behaviour
|
||||||
|
|
||||||
|
Labels can be used on task containers to override default behaviour:
|
||||||
|
|
||||||
|
| Label | Description |
|
||||||
|
|-----------------------------------------------------------|------------------------------------------------------------------------------------------|
|
||||||
|
| `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æfik |
|
||||||
|
| `traefik.backend.loadbalancer.method=drr` | override the default `wrr` load balancer algorithm |
|
||||||
|
| `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions |
|
||||||
|
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||||
|
| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions (DEPRECATED) |
|
||||||
|
| `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.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||||
61
docs/configuration/backends/etcd.md
Normal file
61
docs/configuration/backends/etcd.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Etcd Backend
|
||||||
|
|
||||||
|
Træfik can be configured to use Etcd as a backend configuration.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# Etcd configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable Etcd configuration backend.
|
||||||
|
[etcd]
|
||||||
|
|
||||||
|
# Etcd server endpoint.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: "127.0.0.1:2379"
|
||||||
|
#
|
||||||
|
endpoint = "127.0.0.1:2379"
|
||||||
|
|
||||||
|
# Enable watch Etcd changes.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
watch = true
|
||||||
|
|
||||||
|
# Prefix used for KV store.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "/traefik"
|
||||||
|
#
|
||||||
|
prefix = "/traefik"
|
||||||
|
|
||||||
|
# Override default configuration template.
|
||||||
|
# For advanced users :)
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# filename = "etcd.tmpl"
|
||||||
|
|
||||||
|
# Use etcd user/pass authentication.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# username = foo
|
||||||
|
# password = bar
|
||||||
|
|
||||||
|
# Enable etcd TLS connection.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# [etcd.tls]
|
||||||
|
# ca = "/etc/ssl/ca.crt"
|
||||||
|
# cert = "/etc/ssl/etcd.crt"
|
||||||
|
# key = "/etc/ssl/etcd.key"
|
||||||
|
# insecureskipverify = true
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||||
|
|
||||||
|
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
|
||||||
32
docs/configuration/backends/eureka.md
Normal file
32
docs/configuration/backends/eureka.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Eureka Backend
|
||||||
|
|
||||||
|
Træfik can be configured to use Eureka as a backend configuration.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# Eureka configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable Eureka configuration backend.
|
||||||
|
[eureka]
|
||||||
|
|
||||||
|
# Eureka server endpoint.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
#
|
||||||
|
endpoint = "http://my.eureka.server/eureka"
|
||||||
|
|
||||||
|
# Override default configuration time between refresh.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 30s
|
||||||
|
#
|
||||||
|
delay = "1m"
|
||||||
|
|
||||||
|
# Override default configuration template.
|
||||||
|
# For advanced users :)
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# filename = "eureka.tmpl"
|
||||||
|
```
|
||||||
168
docs/configuration/backends/file.md
Normal file
168
docs/configuration/backends/file.md
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# File Backends
|
||||||
|
|
||||||
|
Like any other reverse proxy, Træfik can be configured with a file.
|
||||||
|
|
||||||
|
You have three choices:
|
||||||
|
|
||||||
|
- [Simple](/configuration/backends/file/#simple)
|
||||||
|
- [Rules in a Separate File](/configuration/backends/file/#rules-in-a-separate-file)
|
||||||
|
- [Multiple `.toml` Files](/configuration/backends/file/#multiple-toml-files)
|
||||||
|
|
||||||
|
## Simple
|
||||||
|
|
||||||
|
Add your configuration at the end of the global configuration file `traefik.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
defaultEntryPoints = ["http", "https"]
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.http.redirect]
|
||||||
|
entryPoint = "https"
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||||
|
KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||||
|
KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||||
|
|
||||||
|
[file]
|
||||||
|
|
||||||
|
# rules
|
||||||
|
[backends]
|
||||||
|
[backends.backend1]
|
||||||
|
[backends.backend1.circuitbreaker]
|
||||||
|
expression = "NetworkErrorRatio() > 0.5"
|
||||||
|
[backends.backend1.servers.server1]
|
||||||
|
url = "http://172.17.0.2:80"
|
||||||
|
weight = 10
|
||||||
|
[backends.backend1.servers.server2]
|
||||||
|
url = "http://172.17.0.3:80"
|
||||||
|
weight = 1
|
||||||
|
[backends.backend2]
|
||||||
|
[backends.backend2.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
|
||||||
|
|
||||||
|
# restrict access to this frontend to the specified list of IPv4/IPv6 CIDR Nets
|
||||||
|
# an unset or empty list allows all Source-IPs to access
|
||||||
|
# if one of the Net-Specifications are invalid, the whole list is invalid
|
||||||
|
# and allows all Source-IPs to access.
|
||||||
|
whitelistSourceRange = ["10.42.0.0/16", "152.89.1.33/32", "afed:be44::/16"]
|
||||||
|
|
||||||
|
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"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules in a Separate File
|
||||||
|
|
||||||
|
Put your rules in a separate file, for example `rules.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# traefik.toml
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.http.redirect]
|
||||||
|
entryPoint = "https"
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||||
|
KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||||
|
KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||||
|
|
||||||
|
[file]
|
||||||
|
filename = "rules.toml"
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# rules.toml
|
||||||
|
[backends]
|
||||||
|
[backends.backend1]
|
||||||
|
[backends.backend1.circuitbreaker]
|
||||||
|
expression = "NetworkErrorRatio() > 0.5"
|
||||||
|
[backends.backend1.servers.server1]
|
||||||
|
url = "http://172.17.0.2:80"
|
||||||
|
weight = 10
|
||||||
|
[backends.backend1.servers.server2]
|
||||||
|
url = "http://172.17.0.3:80"
|
||||||
|
weight = 1
|
||||||
|
[backends.backend2]
|
||||||
|
[backends.backend2.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"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multiple `.toml` Files
|
||||||
|
|
||||||
|
You could have multiple `.toml` files in a directory:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[file]
|
||||||
|
directory = "/path/to/config/"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want Træfik to watch file changes automatically, just add:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[file]
|
||||||
|
watch = true
|
||||||
|
```
|
||||||
133
docs/configuration/backends/kubernetes.md
Normal file
133
docs/configuration/backends/kubernetes.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# Kubernetes Ingress Backend
|
||||||
|
|
||||||
|
Træfik can be configured to use Kubernetes Ingress as a backend configuration.
|
||||||
|
|
||||||
|
See also [Kubernetes user guide](/user-guide/kubernetes).
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# Kubernetes Ingress configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable Kubernetes Ingress configuration backend.
|
||||||
|
[kubernetes]
|
||||||
|
|
||||||
|
# Kubernetes server endpoint.
|
||||||
|
#
|
||||||
|
# Optional for in-cluster configuration, required otherwise.
|
||||||
|
# Default: empty
|
||||||
|
#
|
||||||
|
# endpoint = "http://localhost:8080"
|
||||||
|
|
||||||
|
# Bearer token used for the Kubernetes client configuration.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: empty
|
||||||
|
#
|
||||||
|
# token = "my token"
|
||||||
|
|
||||||
|
# Path to the certificate authority file.
|
||||||
|
# Used for the Kubernetes client configuration.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: empty
|
||||||
|
#
|
||||||
|
# certAuthFilePath = "/my/ca.crt"
|
||||||
|
|
||||||
|
# Array of namespaces to watch.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: all namespaces (empty array).
|
||||||
|
#
|
||||||
|
# namespaces = ["default", "production"]
|
||||||
|
|
||||||
|
# Ingress label selector to identify Ingress objects that should be processed.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: empty (process all Ingresses)
|
||||||
|
#
|
||||||
|
# labelselector = "A and not B"
|
||||||
|
|
||||||
|
# Disable PassHost Headers.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
# disablePassHostHeaders = true
|
||||||
|
```
|
||||||
|
|
||||||
|
### `endpoint`
|
||||||
|
|
||||||
|
The Kubernetes server endpoint.
|
||||||
|
|
||||||
|
When deployed as a replication controller in Kubernetes, Traefik will use the environment variables `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` to construct the 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`
|
||||||
|
|
||||||
|
The endpoint may be given to override the environment variable values.
|
||||||
|
|
||||||
|
When the environment variables are not found, Traefik will try to connect to the Kubernetes API server with an external-cluster client.
|
||||||
|
In this case, the endpoint is required.
|
||||||
|
Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Kubernetes cluster from localhost.
|
||||||
|
|
||||||
|
### `labelselector`
|
||||||
|
|
||||||
|
Ingress label selector to identify Ingress objects that should be processed.
|
||||||
|
|
||||||
|
See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details.
|
||||||
|
|
||||||
|
|
||||||
|
## Annotations
|
||||||
|
|
||||||
|
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`.
|
||||||
|
- `traefik.frontend.priority: "3"`
|
||||||
|
Override the default frontend rule priority.
|
||||||
|
|
||||||
|
Annotations can be used on the Kubernetes service to override default behaviour:
|
||||||
|
|
||||||
|
- `traefik.backend.loadbalancer.method=drr`
|
||||||
|
Override the default `wrr` load balancer algorithm
|
||||||
|
- `traefik.backend.loadbalancer.stickiness=true`
|
||||||
|
Enable backend sticky sessions
|
||||||
|
- `traefik.backend.loadbalancer.stickiness.cookieName=NAME`
|
||||||
|
Manually set the cookie name for sticky sessions
|
||||||
|
- `traefik.backend.loadbalancer.sticky=true`
|
||||||
|
Enable backend sticky sessions (DEPRECATED)
|
||||||
|
|
||||||
|
You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik.yaml).
|
||||||
|
|
||||||
|
Additionally, an annotation can be used on Kubernetes services to set the [circuit breaker expression](/basics/#backends) for a backend.
|
||||||
|
|
||||||
|
- `traefik.backend.circuitbreaker: <expression>`
|
||||||
|
Set the circuit breaker expression for the backend. Default: `nil`.
|
||||||
|
|
||||||
|
As known from nginx when used as Kubernetes Ingress Controller, a list of IP-Ranges which are allowed to access can be configured by using an ingress annotation:
|
||||||
|
|
||||||
|
- `ingress.kubernetes.io/whitelist-source-range: "1.2.3.0/24, fe80::/16"`
|
||||||
|
|
||||||
|
An unset or empty list allows all Source-IPs to access.
|
||||||
|
If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access.
|
||||||
|
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
Is possible to add additional authentication annotations in the Ingress rule.
|
||||||
|
The source of the authentication is a secret that contains usernames and passwords inside the the key auth.
|
||||||
|
|
||||||
|
- `ingress.kubernetes.io/auth-type`: `basic`
|
||||||
|
- `ingress.kubernetes.io/auth-secret`
|
||||||
|
Contains the usernames and passwords with access to the paths defined in the Ingress Rule.
|
||||||
|
|
||||||
|
The secret must be created in the same namespace as the Ingress rule.
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
|
||||||
|
- Basic authentication only.
|
||||||
|
- Realm not configurable; only `traefik` default.
|
||||||
|
- Secret must contain only single file.
|
||||||
188
docs/configuration/backends/marathon.md
Normal file
188
docs/configuration/backends/marathon.md
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# Marathon Backend
|
||||||
|
|
||||||
|
Træfik can be configured to use Marathon as a backend configuration.
|
||||||
|
|
||||||
|
See also [Marathon user guide](/user-guide/marathon).
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# Mesos/Marathon configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable Marathon configuration backend.
|
||||||
|
[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
|
||||||
|
# Default: "http://127.0.0.1:8080"
|
||||||
|
#
|
||||||
|
endpoint = "http://127.0.0.1:8080"
|
||||||
|
|
||||||
|
# Enable watch Marathon changes.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
watch = true
|
||||||
|
|
||||||
|
# Default domain used.
|
||||||
|
# Can be overridden by setting the "traefik.domain" label on an application.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
#
|
||||||
|
domain = "marathon.localhost"
|
||||||
|
|
||||||
|
# Override default configuration template.
|
||||||
|
# For advanced users :)
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# filename = "marathon.tmpl"
|
||||||
|
|
||||||
|
# Expose Marathon apps by default in Traefik.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
# exposedByDefault = false
|
||||||
|
|
||||||
|
# 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 compatibility with marathon-lb labels.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
# marathonLBCompatibility = 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]
|
||||||
|
# CA = "/etc/ssl/ca.crt"
|
||||||
|
# Cert = "/etc/ssl/marathon.cert"
|
||||||
|
# Key = "/etc/ssl/marathon.key"
|
||||||
|
# InsecureSkipVerify = true
|
||||||
|
|
||||||
|
# DCOSToken for DCOS environment.
|
||||||
|
# This will override the Authorization header.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# dcosToken = "xxxxxx"
|
||||||
|
|
||||||
|
# Override DialerTimeout.
|
||||||
|
# Amount of time to allow the Marathon provider to wait to open a TCP connection
|
||||||
|
# to a Marathon master.
|
||||||
|
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||||
|
# values (digits).
|
||||||
|
# If no units are provided, the value is parsed assuming seconds.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "60s"
|
||||||
|
#
|
||||||
|
# dialerTimeout = "60s"
|
||||||
|
|
||||||
|
# Set the TCP Keep Alive interval for the Marathon HTTP Client.
|
||||||
|
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||||
|
# values (digits).
|
||||||
|
# If no units are provided, the value is parsed assuming seconds.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "10s"
|
||||||
|
#
|
||||||
|
# keepAlive = "10s"
|
||||||
|
|
||||||
|
# By default, a task's IP address (as returned by the Marathon API) is used as
|
||||||
|
# backend server if an IP-per-task configuration can be found; otherwise, the
|
||||||
|
# name of the host running the task is used.
|
||||||
|
# The latter behavior can be enforced by enabling this switch.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
# forceTaskHostname = true
|
||||||
|
|
||||||
|
# Applications may define readiness checks which are probed by Marathon during
|
||||||
|
# deployments periodically and the results exposed via the API.
|
||||||
|
# Enabling the following parameter causes Traefik to filter out tasks
|
||||||
|
# whose readiness checks have not succeeded.
|
||||||
|
# Note that the checks are only valid at deployment times.
|
||||||
|
# See the Marathon guide for details.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
# respectReadinessChecks = true
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||||
|
|
||||||
|
|
||||||
|
## Labels: overriding default behaviour
|
||||||
|
|
||||||
|
### On Containers
|
||||||
|
|
||||||
|
Labels can be used on containers to override default behaviour:
|
||||||
|
|
||||||
|
| Label | Description |
|
||||||
|
|-----------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `traefik.backend=foo` | assign the application to `foo` backend |
|
||||||
|
| `traefik.backend.maxconn.amount=10` | set a maximum number of connections to the backend. Must be used in conjunction with the below label to take effect. |
|
||||||
|
| `traefik.backend.maxconn.extractorfunc=client.ip` | set the function to be used against the request to determine what to limit maximum connections to the backend by. Must be used in conjunction with the above label to take effect. |
|
||||||
|
| `traefik.backend.loadbalancer.method=drr` | override the default `wrr` load balancer algorithm |
|
||||||
|
| `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions (DEPRECATED) |
|
||||||
|
| `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions |
|
||||||
|
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||||
|
| `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5` | create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||||
|
| `traefik.backend.healthcheck.path=/health` | set the Traefik health check path [default: no health checks] |
|
||||||
|
| `traefik.backend.healthcheck.interval=5s` | sets a custom health check interval in Go-parseable (`time.ParseDuration`) format [default: 30s] |
|
||||||
|
| `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æfik |
|
||||||
|
| `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.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash`. |
|
||||||
|
|
||||||
|
### On Services
|
||||||
|
|
||||||
|
If several ports need to be exposed from a container, the services labels can be used:
|
||||||
|
|
||||||
|
| Label | Description |
|
||||||
|
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------|
|
||||||
|
| `traefik.<service-name>.port=443` | create a service binding with frontend/backend using this port. Overrides `traefik.port`. |
|
||||||
|
| `traefik.<service-name>.portIndex=1` | create a service binding with frontend/backend using this port index. Overrides `traefik.portIndex`. |
|
||||||
|
| `traefik.<service-name>.protocol=https` | assign `https` protocol. Overrides `traefik.protocol`. |
|
||||||
|
| `traefik.<service-name>.weight=10` | assign this service weight. Overrides `traefik.weight`. |
|
||||||
|
| `traefik.<service-name>.frontend.backend=fooBackend` | assign this service frontend to `foobackend`. Default is to assign to the service backend. |
|
||||||
|
| `traefik.<service-name>.frontend.entryPoints=http` | assign this service entrypoints. Overrides `traefik.frontend.entrypoints`. |
|
||||||
|
| `traefik.<service-name>.frontend.auth.basic=test:EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash` |
|
||||||
|
| `traefik.<service-name>.frontend.passHostHeader=true` | Forward client `Host` header to the backend. Overrides `traefik.frontend.passHostHeader`. |
|
||||||
|
| `traefik.<service-name>.frontend.priority=10` | assign the service frontend priority. Overrides `traefik.frontend.priority`. |
|
||||||
|
| `traefik.<service-name>.frontend.rule=Path:/foo` | assign the service frontend rule. Overrides `traefik.frontend.rule`. |
|
||||||
93
docs/configuration/backends/mesos.md
Normal file
93
docs/configuration/backends/mesos.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# Mesos Generic Backend
|
||||||
|
|
||||||
|
Træfik can be configured to use Mesos as a backend configuration.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# Mesos configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable Mesos configuration backend.
|
||||||
|
[mesos]
|
||||||
|
|
||||||
|
# Mesos server endpoint.
|
||||||
|
# You can also specify multiple endpoint for Mesos:
|
||||||
|
# endpoint = "192.168.35.40:5050,192.168.35.41:5050,192.168.35.42:5050"
|
||||||
|
# endpoint = "zk://192.168.35.20:2181,192.168.35.21:2181,192.168.35.22:2181/mesos"
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: "http://127.0.0.1:5050"
|
||||||
|
#
|
||||||
|
endpoint = "http://127.0.0.1:8080"
|
||||||
|
|
||||||
|
# Enable watch Mesos changes.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
watch = true
|
||||||
|
|
||||||
|
# Default domain used.
|
||||||
|
# Can be overridden by setting the "traefik.domain" label on an application.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
#
|
||||||
|
domain = "mesos.localhost"
|
||||||
|
|
||||||
|
# Override default configuration template.
|
||||||
|
# For advanced users :)
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# filename = "mesos.tmpl"
|
||||||
|
|
||||||
|
# Expose Mesos apps by default in Traefik.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
# ExposedByDefault = false
|
||||||
|
|
||||||
|
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# [mesos.TLS]
|
||||||
|
# InsecureSkipVerify = true
|
||||||
|
|
||||||
|
# Zookeeper timeout (in seconds).
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 30
|
||||||
|
#
|
||||||
|
# ZkDetectionTimeout = 30
|
||||||
|
|
||||||
|
# Polling interval (in seconds).
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 30
|
||||||
|
#
|
||||||
|
# RefreshSeconds = 30
|
||||||
|
|
||||||
|
# IP sources (e.g. host, docker, mesos, rkt).
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# IPSources = "host"
|
||||||
|
|
||||||
|
# HTTP Timeout (in seconds).
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 30
|
||||||
|
#
|
||||||
|
# StateTimeoutSecond = "30"
|
||||||
|
|
||||||
|
# Convert 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
|
||||||
|
```
|
||||||
131
docs/configuration/backends/rancher.md
Normal file
131
docs/configuration/backends/rancher.md
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# Rancher Backend
|
||||||
|
|
||||||
|
Træfik can be configured to use Rancher as a backend configuration.
|
||||||
|
|
||||||
|
## Global Configuration
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# Rancher configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable Rancher configuration backend.
|
||||||
|
[rancher]
|
||||||
|
|
||||||
|
# Default domain used.
|
||||||
|
# Can be overridden by setting the "traefik.domain" label on an service.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
#
|
||||||
|
domain = "rancher.localhost"
|
||||||
|
|
||||||
|
# Enable watch Rancher changes.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
watch = true
|
||||||
|
|
||||||
|
# Polling interval (in seconds).
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 15
|
||||||
|
#
|
||||||
|
refreshSeconds = 15
|
||||||
|
|
||||||
|
# Expose Rancher services by default in Traefik.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
exposedByDefault = false
|
||||||
|
|
||||||
|
# Filter services with unhealthy states and inactive states.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
enableServiceHealthFilter = true
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||||
|
|
||||||
|
## Rancher Metadata Service
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Enable Rancher metadata service configuration backend instead of the API
|
||||||
|
# configuration backend.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
[rancher.metadata]
|
||||||
|
|
||||||
|
# Poll the Rancher metadata service for changes every `rancher.RefreshSeconds`.
|
||||||
|
# NOTE: this is less accurate than the default long polling technique which
|
||||||
|
# will provide near instantaneous updates to Traefik
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
intervalPoll = true
|
||||||
|
|
||||||
|
# Prefix used for accessing the Rancher metadata service.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "/latest"
|
||||||
|
#
|
||||||
|
prefix = "/2016-07-29"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rancher API
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Enable Rancher API configuration backend.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
[rancher.api]
|
||||||
|
|
||||||
|
# Endpoint to use when connecting to the Rancher API.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
endpoint = "http://rancherserver.example.com/v1"
|
||||||
|
|
||||||
|
# AccessKey to use when connecting to the Rancher API.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
accessKey = "XXXXXXXXXXXXXXXXXXXX"
|
||||||
|
|
||||||
|
# SecretKey to use when connecting to the Rancher API.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
If Traefik needs access to the Rancher API, you need to set the `endpoint`, `accesskey` and `secretkey` parameters.
|
||||||
|
|
||||||
|
To enable Traefik to fetch information about the Environment it's deployed in only, you need to create an `Environment API Key`.
|
||||||
|
This can be found within the API Key advanced options.
|
||||||
|
|
||||||
|
## Labels: overriding default behaviour
|
||||||
|
|
||||||
|
Labels can be used on task containers to override default behaviour:
|
||||||
|
|
||||||
|
| Label | Description |
|
||||||
|
|-----------------------------------------------------------------------|------------------------------------------------------------------------------------------|
|
||||||
|
| `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æfik |
|
||||||
|
| `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.frontend.auth.basic=EXPR` | Sets basic authentication for that frontend in CSV format: `User:Hash,User:Hash`. |
|
||||||
|
| `traefik.backend.circuitbreaker.expression=NetworkErrorRatio() > 0.5` | Create a [circuit breaker](/basics/#backends) to be used against the backend |
|
||||||
|
| `traefik.backend.loadbalancer.method=drr` | Override the default `wrr` load balancer algorithm |
|
||||||
|
| `traefik.backend.loadbalancer.stickiness=true` | Enable backend sticky sessions |
|
||||||
|
| `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions |
|
||||||
|
| `traefik.backend.loadbalancer.sticky=true` | Enable backend sticky sessions (DEPRECATED) |
|
||||||
349
docs/configuration/backends/web.md
Normal file
349
docs/configuration/backends/web.md
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
# Web Backend
|
||||||
|
|
||||||
|
Træfik can be configured:
|
||||||
|
|
||||||
|
- using a RESTful api.
|
||||||
|
- to use a monitoring system (like Prometheus, DataDog or StatD, ...).
|
||||||
|
- to expose a Web Dashboard.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Enable web backend.
|
||||||
|
[web]
|
||||||
|
|
||||||
|
# Web administration port.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: ":8080"
|
||||||
|
#
|
||||||
|
address = ":8080"
|
||||||
|
|
||||||
|
# SSL certificate and key used.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# certFile = "traefik.crt"
|
||||||
|
# keyFile = "traefik.key"
|
||||||
|
|
||||||
|
# Set REST API to read-only mode.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
readOnly = true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Web UI
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The `/ping` path of the api is excluded from authentication (since 1.4).
|
||||||
|
|
||||||
|
#### Basic Authentication
|
||||||
|
|
||||||
|
Passwords can be encoded in MD5, SHA1 and BCrypt: you can use `htpasswd` to generate those ones.
|
||||||
|
|
||||||
|
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
||||||
|
if both are provided, the two are merged, with external file contents having precedence.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[web]
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# To enable basic auth on the webui with 2 user/pass: test:test and test2:test2
|
||||||
|
[web.auth.basic]
|
||||||
|
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||||
|
usersFile = "/path/to/.htpasswd"
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Digest Authentication
|
||||||
|
|
||||||
|
You can use `htdigest` to generate those ones.
|
||||||
|
|
||||||
|
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
||||||
|
if both are provided, the two are merged, with external file contents having precedence
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[web]
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# To enable digest auth on the webui with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
|
||||||
|
[web.auth.digest]
|
||||||
|
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||||
|
usersFile = "/path/to/.htdigest"
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Metrics
|
||||||
|
|
||||||
|
You can enable Traefik to export internal metrics to different monitoring systems.
|
||||||
|
|
||||||
|
### Prometheus
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[web]
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# To enable Traefik to export internal metrics to Prometheus
|
||||||
|
[web.metrics.prometheus]
|
||||||
|
|
||||||
|
# Buckets for latency metrics
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: [0.1, 0.3, 1.2, 5]
|
||||||
|
buckets=[0.1,0.3,1.2,5.0]
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### DataDog
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[web]
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# DataDog metrics exporter type
|
||||||
|
[web.metrics.datadog]
|
||||||
|
|
||||||
|
# DataDog's address.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: "localhost:8125"
|
||||||
|
#
|
||||||
|
address = "localhost:8125"
|
||||||
|
|
||||||
|
# DataDog push interval
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "10s"
|
||||||
|
#
|
||||||
|
pushinterval = "10s"
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### StatsD
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[web]
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# StatsD metrics exporter type
|
||||||
|
[web.metrics.statsd]
|
||||||
|
|
||||||
|
# StatD's address.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: "localhost:8125"
|
||||||
|
#
|
||||||
|
address = "localhost:8125"
|
||||||
|
|
||||||
|
# StatD push interval
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "10s"
|
||||||
|
#
|
||||||
|
pushinterval = "10s"
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Statistics
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[web]
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# Enable more detailed statistics.
|
||||||
|
[web.statistics]
|
||||||
|
|
||||||
|
# Number of recent errors logged.
|
||||||
|
#
|
||||||
|
# Default: 10
|
||||||
|
#
|
||||||
|
recentErrors = 10
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
| Path | Method | Description |
|
||||||
|
|-----------------------------------------------------------------|:-------------:|----------------------------------------------------------------------------------------------------|
|
||||||
|
| `/` | `GET` | Provides a simple HTML frontend of Træfik |
|
||||||
|
| `/ping` | `GET`, `HEAD` | A simple endpoint to check for Træfik process liveness. Return a code `200` with the content: `OK` |
|
||||||
|
| `/health` | `GET` | json health metrics |
|
||||||
|
| `/api` | `GET` | Configuration for all providers |
|
||||||
|
| `/api/providers` | `GET` | Providers |
|
||||||
|
| `/api/providers/{provider}` | `GET`, `PUT` | Get or update provider |
|
||||||
|
| `/api/providers/{provider}/backends` | `GET` | List backends |
|
||||||
|
| `/api/providers/{provider}/backends/{backend}` | `GET` | Get backend |
|
||||||
|
| `/api/providers/{provider}/backends/{backend}/servers` | `GET` | List servers in backend |
|
||||||
|
| `/api/providers/{provider}/backends/{backend}/servers/{server}` | `GET` | Get a server in a backend |
|
||||||
|
| `/api/providers/{provider}/frontends` | `GET` | List frontends |
|
||||||
|
| `/api/providers/{provider}/frontends/{frontend}` | `GET` | Get a frontend |
|
||||||
|
| `/api/providers/{provider}/frontends/{frontend}/routes` | `GET` | List routes in a frontend |
|
||||||
|
| `/api/providers/{provider}/frontends/{frontend}/routes/{route}` | `GET` | Get a route in a frontend |
|
||||||
|
| `/metrics` | `GET` | Export internal metrics |
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
#### Ping
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -sv "http://localhost:8080/ping"
|
||||||
|
```
|
||||||
|
```shell
|
||||||
|
* Trying ::1...
|
||||||
|
* Connected to localhost (::1) port 8080 (#0)
|
||||||
|
> GET /ping HTTP/1.1
|
||||||
|
> Host: localhost:8080
|
||||||
|
> User-Agent: curl/7.43.0
|
||||||
|
> Accept: */*
|
||||||
|
>
|
||||||
|
< HTTP/1.1 200 OK
|
||||||
|
< Date: Thu, 25 Aug 2016 01:35:36 GMT
|
||||||
|
< Content-Length: 2
|
||||||
|
< Content-Type: text/plain; charset=utf-8
|
||||||
|
<
|
||||||
|
* Connection #0 to host localhost left intact
|
||||||
|
OK
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Health
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -s "http://localhost:8080/health" | jq .
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
// Træfik PID
|
||||||
|
"pid": 2458,
|
||||||
|
// Træfik server uptime (formated time)
|
||||||
|
"uptime": "39m6.885931127s",
|
||||||
|
// Træfik 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æfik 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,
|
||||||
|
|
||||||
|
// request statistics [requires --web.statistics to be set]
|
||||||
|
// ten most recent requests with 4xx and 5xx status codes
|
||||||
|
"recent_errors": [
|
||||||
|
{
|
||||||
|
// status code
|
||||||
|
"status_code": 500,
|
||||||
|
// description of status code
|
||||||
|
"status": "Internal Server Error",
|
||||||
|
// request HTTP method
|
||||||
|
"method": "GET",
|
||||||
|
// request hostname
|
||||||
|
"host": "localhost",
|
||||||
|
// request path
|
||||||
|
"path": "/path",
|
||||||
|
// RFC 3339 formatted date/time
|
||||||
|
"time": "2016-10-21T16:59:15.418495872-07:00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Provider configurations
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -s "http://localhost:8080/api" | jq .
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
61
docs/configuration/backends/zookeeper.md
Normal file
61
docs/configuration/backends/zookeeper.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Zookeeper Backend
|
||||||
|
|
||||||
|
Træfik can be configured to use Zookeeper as a backend configuration.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# Zookeeper configuration backend
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# Enable Zookeeperconfiguration backend.
|
||||||
|
[zookeeper]
|
||||||
|
|
||||||
|
# Zookeeper server endpoint.
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: "127.0.0.1:2181"
|
||||||
|
#
|
||||||
|
endpoint = "127.0.0.1:2181"
|
||||||
|
|
||||||
|
# Enable watch Zookeeper changes.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
watch = true
|
||||||
|
|
||||||
|
# Prefix used for KV store.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "/traefik"
|
||||||
|
#
|
||||||
|
prefix = "/traefik"
|
||||||
|
|
||||||
|
# Override default configuration template.
|
||||||
|
# For advanced users :)
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# filename = "zookeeper.tmpl"
|
||||||
|
|
||||||
|
# Use Zookeeper user/pass authentication.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# username = foo
|
||||||
|
# password = bar
|
||||||
|
|
||||||
|
# Enable Zookeeper TLS connection.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# [zookeeper.tls]
|
||||||
|
# ca = "/etc/ssl/ca.crt"
|
||||||
|
# cert = "/etc/ssl/zookeeper.crt"
|
||||||
|
# key = "/etc/ssl/zookeeper.key"
|
||||||
|
# insecureskipverify = true
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable constraints see [backend-specific constraints section](/configuration/commons/#backend-specific).
|
||||||
|
|
||||||
|
Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure.
|
||||||
439
docs/configuration/commons.md
Normal file
439
docs/configuration/commons.md
Normal file
@@ -0,0 +1,439 @@
|
|||||||
|
# Global Configuration
|
||||||
|
|
||||||
|
## Main Section
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Duration to give active requests a chance to finish before Traefik stops.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "10s"
|
||||||
|
#
|
||||||
|
# graceTimeOut = "10s"
|
||||||
|
|
||||||
|
# Enable debug mode.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
# debug = true
|
||||||
|
|
||||||
|
# Periodically check if a new version has been released.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: true
|
||||||
|
#
|
||||||
|
# checkNewVersion = false
|
||||||
|
|
||||||
|
# Backends throttle duration.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "2s"
|
||||||
|
#
|
||||||
|
# ProvidersThrottleDuration = "2s"
|
||||||
|
|
||||||
|
# Controls the maximum idle (keep-alive) connections to keep per-host.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 200
|
||||||
|
#
|
||||||
|
# MaxIdleConnsPerHost = 200
|
||||||
|
|
||||||
|
# If set to true invalid SSL certificates are accepted for backends.
|
||||||
|
# This disables detection of man-in-the-middle attacks so should only be used on secure backend networks.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
# InsecureSkipVerify = true
|
||||||
|
|
||||||
|
# Register Certificates in the RootCA.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: []
|
||||||
|
#
|
||||||
|
# RootCAs = [ "/mycert.cert" ]
|
||||||
|
|
||||||
|
# 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"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `graceTimeOut`: Duration to give active requests a chance to finish before Traefik stops.
|
||||||
|
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||||
|
If no units are provided, the value is parsed assuming seconds.
|
||||||
|
**Note:** in this time frame no new requests are accepted.
|
||||||
|
|
||||||
|
- `ProvidersThrottleDuration`: Backends throttle duration: minimum duration in seconds between 2 events from providers before applying a new configuration.
|
||||||
|
It avoids unnecessary reloads if multiples events are sent in a short amount of time.
|
||||||
|
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||||
|
If no units are provided, the value is parsed assuming seconds.
|
||||||
|
|
||||||
|
- `MaxIdleConnsPerHost`: Controls the maximum idle (keep-alive) connections to keep per-host.
|
||||||
|
If zero, `DefaultMaxIdleConnsPerHost` from the Go standard library net/http module is used.
|
||||||
|
If you encounter 'too many open files' errors, you can either increase this value or change the `ulimit`.
|
||||||
|
|
||||||
|
- `InsecureSkipVerify` : If set to true invalid SSL certificates are accepted for backends.
|
||||||
|
**Note:** This disables detection of man-in-the-middle attacks so should only be used on secure backend networks.
|
||||||
|
|
||||||
|
- `RootCAs`: Register Certificates in the RootCA. This certificates will be use for backends calls.
|
||||||
|
**Note** You can use file path or cert content directly
|
||||||
|
|
||||||
|
- `defaultEntryPoints`: Entrypoints to be used by frontends that do not specify any entrypoint.
|
||||||
|
Each frontend can specify its own entrypoints.
|
||||||
|
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
In a micro-service architecture, with a central service discovery, setting constraints limits Træfik scope to a smaller number of routes.
|
||||||
|
|
||||||
|
Træfik filters services according to service attributes/tags set in your configuration backends.
|
||||||
|
|
||||||
|
Supported filters:
|
||||||
|
|
||||||
|
- `tag`
|
||||||
|
|
||||||
|
### Simple
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Simple matching constraint
|
||||||
|
constraints = ["tag==api"]
|
||||||
|
|
||||||
|
# Simple mismatching constraint
|
||||||
|
constraints = ["tag!=api"]
|
||||||
|
|
||||||
|
# Globbing
|
||||||
|
constraints = ["tag==us-*"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Multiple constraints
|
||||||
|
# - "tag==" must match with at least one tag
|
||||||
|
# - "tag!=" must match with none of tags
|
||||||
|
constraints = ["tag!=us-*", "tag!=asia-*"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend-specific
|
||||||
|
|
||||||
|
Supported backends:
|
||||||
|
|
||||||
|
- Docker
|
||||||
|
- Consul K/V
|
||||||
|
- BoltDB
|
||||||
|
- Zookeeper
|
||||||
|
- Etcd
|
||||||
|
- Consul Catalog
|
||||||
|
- Rancher
|
||||||
|
- Marathon
|
||||||
|
- Kubernetes (using a provider-specific mechanism based on label selectors)
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Backend-specific constraint
|
||||||
|
[consulCatalog]
|
||||||
|
# ...
|
||||||
|
constraints = ["tag==api"]
|
||||||
|
|
||||||
|
# Backend-specific constraint
|
||||||
|
[marathon]
|
||||||
|
# ...
|
||||||
|
constraints = ["tag==api", "tag!=v*-beta"]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Logs Definition
|
||||||
|
|
||||||
|
### Traefik logs
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Traefik logs file
|
||||||
|
# If not defined, logs to stdout
|
||||||
|
traefikLogsFile = "log/traefik.log"
|
||||||
|
|
||||||
|
# Log level
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "ERROR"
|
||||||
|
#
|
||||||
|
# Accepted values, in order of severity: "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "PANIC"
|
||||||
|
# Messages at and above the selected level will be logged.
|
||||||
|
#
|
||||||
|
logLevel = "ERROR"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Access Logs
|
||||||
|
|
||||||
|
Access logs are written when `[accessLog]` is defined.
|
||||||
|
By default it will write to stdout and produce logs in the textual Common Log Format (CLF), extended with additional fields.
|
||||||
|
|
||||||
|
To enable access logs using the default settings just add the `[accessLog]` entry.
|
||||||
|
```toml
|
||||||
|
[accessLog]
|
||||||
|
```
|
||||||
|
|
||||||
|
To write the logs into a logfile specify the `filePath`.
|
||||||
|
```toml
|
||||||
|
[accessLog]
|
||||||
|
filePath = "/path/to/access.log"
|
||||||
|
```
|
||||||
|
|
||||||
|
To write JSON format logs, specify `json` as the format:
|
||||||
|
```toml
|
||||||
|
[accessLog]
|
||||||
|
filePath = "/path/to/access.log"
|
||||||
|
format = "json"
|
||||||
|
```
|
||||||
|
|
||||||
|
Deprecated way (before 1.4):
|
||||||
|
```toml
|
||||||
|
# Access logs file
|
||||||
|
#
|
||||||
|
# DEPRECATED - see [accessLog] lower down
|
||||||
|
#
|
||||||
|
accessLogsFile = "log/access.log"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Log Rotation
|
||||||
|
|
||||||
|
Traefik will close and reopen its log files, assuming they're configured, on receipt of a USR1 signal.
|
||||||
|
This allows the logs to be rotated and processed by an external program, such as `logrotate`.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
This does not work on Windows due to the lack of USR signals.
|
||||||
|
|
||||||
|
|
||||||
|
## Custom Error pages
|
||||||
|
|
||||||
|
Custom error pages can be returned, in lieu of the default, according to frontend-configured ranges of HTTP Status codes.
|
||||||
|
|
||||||
|
In the example below, if a 503 status is returned from the frontend "website", the custom error page at http://2.3.4.5/503.html is returned with the actual status code set in the HTTP header.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The `503.html` page itself is not hosted on Traefik, but some other infrastructure.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[frontends]
|
||||||
|
[frontends.website]
|
||||||
|
backend = "website"
|
||||||
|
[frontends.website.errors]
|
||||||
|
[frontends.website.errors.network]
|
||||||
|
status = ["500-599"]
|
||||||
|
backend = "error"
|
||||||
|
query = "/{status}.html"
|
||||||
|
[frontends.website.routes.website]
|
||||||
|
rule = "Host: website.mydomain.com"
|
||||||
|
|
||||||
|
[backends]
|
||||||
|
[backends.website]
|
||||||
|
[backends.website.servers.website]
|
||||||
|
url = "https://1.2.3.4"
|
||||||
|
[backends.error]
|
||||||
|
[backends.error.servers.error]
|
||||||
|
url = "http://2.3.4.5"
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above example, the error page rendered was based on the status code.
|
||||||
|
Instead, the query parameter can also be set to some generic error page like so: `query = "/500s.html"`
|
||||||
|
|
||||||
|
Now the `500s.html` error page is returned for the configured code range.
|
||||||
|
The configured status code ranges are inclusive; that is, in the above example, the `500s.html` page will be returned for status codes `500` through, and including, `599`.
|
||||||
|
|
||||||
|
Custom error pages are easiest to implement using the file provider.
|
||||||
|
For dynamic providers, the corresponding template file needs to be customized accordingly and referenced in the Traefik configuration.
|
||||||
|
|
||||||
|
|
||||||
|
## Retry Configuration
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Enable retry sending request if network error
|
||||||
|
[retry]
|
||||||
|
|
||||||
|
# Number of attempts
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: (number servers in backend) -1
|
||||||
|
#
|
||||||
|
# attempts = 3
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Health Check Configuration
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Enable custom health check options.
|
||||||
|
[healthcheck]
|
||||||
|
|
||||||
|
# Set the default health check interval.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "30s"
|
||||||
|
#
|
||||||
|
# interval = "30s"
|
||||||
|
```
|
||||||
|
|
||||||
|
- `interval` set the default health check interval.
|
||||||
|
Will only be effective if health check paths are defined.
|
||||||
|
Given provider-specific support, the value may be overridden on a per-backend basis.
|
||||||
|
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||||
|
If no units are provided, the value is parsed assuming seconds.
|
||||||
|
|
||||||
|
## Timeouts
|
||||||
|
|
||||||
|
### Responding Timeouts
|
||||||
|
|
||||||
|
`respondingTimeouts` are timeouts for incoming requests to the Traefik instance.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[respondingTimeouts]
|
||||||
|
|
||||||
|
# readTimeout is the maximum duration for reading the entire request, including the body.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "0s"
|
||||||
|
#
|
||||||
|
# readTimeout = "5s"
|
||||||
|
|
||||||
|
# writeTimeout is the maximum duration before timing out writes of the response.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "0s"
|
||||||
|
#
|
||||||
|
# writeTimeout = "5s"
|
||||||
|
|
||||||
|
# idleTimeout is the maximum duration an idle (keep-alive) connection will remain idle before closing itself.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "180s"
|
||||||
|
#
|
||||||
|
# idleTimeout = "360s"
|
||||||
|
```
|
||||||
|
|
||||||
|
- `readTimeout` is the maximum duration for reading the entire request, including the body.
|
||||||
|
If zero, no timeout exists.
|
||||||
|
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||||
|
If no units are provided, the value is parsed assuming seconds.
|
||||||
|
|
||||||
|
- `writeTimeout` is the maximum duration before timing out writes of the response.
|
||||||
|
It covers the time from the end of the request header read to the end of the response write.
|
||||||
|
If zero, no timeout exists.
|
||||||
|
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||||
|
If no units are provided, the value is parsed assuming seconds.
|
||||||
|
|
||||||
|
- `idleTimeout` is the maximum duration an idle (keep-alive) connection will remain idle before closing itself.
|
||||||
|
If zero, no timeout exists.
|
||||||
|
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||||
|
If no units are provided, the value is parsed assuming seconds.
|
||||||
|
|
||||||
|
### Forwarding Timeouts
|
||||||
|
|
||||||
|
`forwardingTimeouts` are timeouts for requests forwarded to the backend servers.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[forwardingTimeouts]
|
||||||
|
|
||||||
|
# dialTimeout is the amount of time to wait until a connection to a backend server can be established.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "30s"
|
||||||
|
#
|
||||||
|
# dialTimeout = "30s"
|
||||||
|
|
||||||
|
# responseHeaderTimeout is the amount of time to wait for a server's response headers after fully writing the request (including its body, if any).
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "0s"
|
||||||
|
#
|
||||||
|
# responseHeaderTimeout = "0s"
|
||||||
|
```
|
||||||
|
|
||||||
|
- `dialTimeout` is the amount of time to wait until a connection to a backend server can be established.
|
||||||
|
If zero, no timeout exists.
|
||||||
|
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||||
|
If no units are provided, the value is parsed assuming seconds.
|
||||||
|
|
||||||
|
- `responseHeaderTimeout` is the amount of time to wait for a server's response headers after fully writing the request (including its body, if any).
|
||||||
|
If zero, no timeout exists.
|
||||||
|
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||||
|
If no units are provided, the value is parsed assuming seconds.
|
||||||
|
|
||||||
|
|
||||||
|
### Idle Timeout (deprecated)
|
||||||
|
|
||||||
|
Use [respondingTimeouts](/configuration/commons/#responding-timeouts) instead of `IdleTimeout`.
|
||||||
|
In the case both settings are configured, the deprecated option will be overwritten.
|
||||||
|
|
||||||
|
`IdleTimeout` is the maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
|
||||||
|
This is set to enforce closing of stale client connections.
|
||||||
|
|
||||||
|
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
|
||||||
|
If no units are provided, the value is parsed assuming seconds.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# IdleTimeout
|
||||||
|
#
|
||||||
|
# DEPRECATED - see [respondingTimeouts] section.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: "180s"
|
||||||
|
#
|
||||||
|
IdleTimeout = "360s"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Override Default Configuration Template
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
For advanced users only.
|
||||||
|
|
||||||
|
Supported by all backends except: File backend, Web backend and DynamoDB backend.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[backend_name]
|
||||||
|
|
||||||
|
# Override default configuration template. For advanced users :)
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: ""
|
||||||
|
#
|
||||||
|
filename = "custom_config_template.tpml"
|
||||||
|
|
||||||
|
# Enable debug logging of generated configuration template.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
debugLogGeneratedTemplate = true
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[marathon]
|
||||||
|
filename = "my_custom_config_template.tpml"
|
||||||
|
```
|
||||||
|
|
||||||
|
The template files can be written using functions provided by:
|
||||||
|
|
||||||
|
- [go template](https://golang.org/pkg/text/template/)
|
||||||
|
- [sprig library](https://masterminds.github.io/sprig/)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```tmpl
|
||||||
|
[backends]
|
||||||
|
[backends.backend1]
|
||||||
|
url = "http://firstserver"
|
||||||
|
[backends.backend2]
|
||||||
|
url = "http://secondserver"
|
||||||
|
|
||||||
|
{{$frontends := dict "frontend1" "backend1" "frontend2" "backend2"}}
|
||||||
|
[frontends]
|
||||||
|
{{range $frontend, $backend := $frontends}}
|
||||||
|
[frontends.{{$frontend}}]
|
||||||
|
backend = "{{$backend}}"
|
||||||
|
{{end}}
|
||||||
|
```
|
||||||
239
docs/configuration/entrypoints.md
Normal file
239
docs/configuration/entrypoints.md
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
# Entry Points Definition
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Entrypoints definition
|
||||||
|
#
|
||||||
|
# Default:
|
||||||
|
# [entryPoints]
|
||||||
|
# [entryPoints.http]
|
||||||
|
# address = ":80"
|
||||||
|
#
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Redirect HTTP to HTTPS
|
||||||
|
|
||||||
|
To redirect an http entrypoint to an https entrypoint (with SNI support).
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[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"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rewriting URL
|
||||||
|
|
||||||
|
To redirect an entrypoint rewriting the URL.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.http.redirect]
|
||||||
|
regex = "^http://localhost/(.*)"
|
||||||
|
replacement = "http://mydomain/$1"
|
||||||
|
```
|
||||||
|
|
||||||
|
## TLS Mutual Authentication
|
||||||
|
|
||||||
|
Only accept clients that present a certificate signed by a specified Certificate Authority (CA).
|
||||||
|
`ClientCAFiles` can be configured with multiple `CA:s` in the same file or use multiple files containing one or several `CA:s`.
|
||||||
|
The `CA:s` has to be in PEM format.
|
||||||
|
|
||||||
|
All clients will be required to present a valid cert.
|
||||||
|
The requirement will apply to all server certs in the entrypoint.
|
||||||
|
|
||||||
|
In the example below both `snitest.com` and `snitest.org` will require client certs
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
ClientCAFiles = ["tests/clientca1.crt", "tests/clientca2.crt"]
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||||
|
KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "integration/fixtures/https/snitest.org.cert"
|
||||||
|
KeyFile = "integration/fixtures/https/snitest.org.key"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
### Basic Authentication
|
||||||
|
|
||||||
|
Passwords can be encoded in MD5, SHA1 and BCrypt: you can use `htpasswd` to generate those ones.
|
||||||
|
|
||||||
|
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
||||||
|
if both are provided, the two are merged, with external file contents having precedence.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# To enable basic auth on an entrypoint with 2 user/pass: test:test and test2:test2
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.http.auth.basic]
|
||||||
|
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||||
|
usersFile = "/path/to/.htpasswd"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Digest Authentication
|
||||||
|
|
||||||
|
You can use `htdigest` to generate those ones.
|
||||||
|
|
||||||
|
Users can be specified directly in the toml file, or indirectly by referencing an external file;
|
||||||
|
if both are provided, the two are merged, with external file contents having precedence
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# To enable digest auth on an entrypoint with 2 user/realm/pass: test:traefik:test and test2:traefik:test2
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.http.auth.basic]
|
||||||
|
users = ["test:traefik:a2688e031edb4be6a3797f3882655c05 ", "test2:traefik:518845800f9e2bfb1f1f740ec24f074e"]
|
||||||
|
usersFile = "/path/to/.htdigest"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Forward Authentication
|
||||||
|
|
||||||
|
This configuration will first forward the request to `http://authserver.com/auth`.
|
||||||
|
|
||||||
|
If the response code is 2XX, access is granted and the original request is performed.
|
||||||
|
Otherwise, the response from the auth server is returned.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entryPoints]
|
||||||
|
[entrypoints.http]
|
||||||
|
# ...
|
||||||
|
# To enable forward auth on an entrypoint
|
||||||
|
[entrypoints.http.auth.forward]
|
||||||
|
address = "https://authserver.com/auth"
|
||||||
|
|
||||||
|
# Trust existing X-Forwarded-* headers.
|
||||||
|
# Useful with another reverse proxy in front of Traefik.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
trustForwardHeader = true
|
||||||
|
|
||||||
|
# Enable forward auth TLS connection.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
[entrypoints.http.auth.forward.tls]
|
||||||
|
cert = "authserver.crt"
|
||||||
|
key = "authserver.key"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Specify Minimum TLS Version
|
||||||
|
|
||||||
|
To specify an https entry point with a minimum TLS version, and specifying an array of cipher suites (from crypto/tls).
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
minVersion = "VersionTLS12"
|
||||||
|
cipherSuites = ["TLS_RSA_WITH_AES_256_GCM_SHA384"]
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
certFile = "integration/fixtures/https/snitest.com.cert"
|
||||||
|
keyFile = "integration/fixtures/https/snitest.com.key"
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
certFile = "integration/fixtures/https/snitest.org.cert"
|
||||||
|
keyFile = "integration/fixtures/https/snitest.org.key"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compression
|
||||||
|
|
||||||
|
To enable compression support using gzip format.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
compress = true
|
||||||
|
```
|
||||||
|
|
||||||
|
Responses are compressed when:
|
||||||
|
|
||||||
|
* The response body is larger than `512` bytes
|
||||||
|
* And the `Accept-Encoding` request header contains `gzip`
|
||||||
|
* And the response is not already compressed, i.e. the `Content-Encoding` response header is not already set.
|
||||||
|
|
||||||
|
## Whitelisting
|
||||||
|
|
||||||
|
To enable IP whitelisting at the entrypoint level.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
whiteListSourceRange = ["127.0.0.1/32", "192.168.1.7"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## ProxyProtocol
|
||||||
|
|
||||||
|
To enable [ProxyProtocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) support.
|
||||||
|
Only IPs in `trustedIPs` will lead to remote client address replacement: you should declare your load-balancer IP or CIDR range here (in testing environment, you can trust everyone using `insecure = true`).
|
||||||
|
|
||||||
|
!!! danger
|
||||||
|
When queuing Træfik behind another load-balancer, be sure to carefully configure Proxy Protocol on both sides.
|
||||||
|
Otherwise, it could introduce a security risk in your system by forging requests.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
|
||||||
|
# Enable ProxyProtocol
|
||||||
|
[entryPoints.http.proxyProtocol]
|
||||||
|
# List of trusted IPs
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: []
|
||||||
|
#
|
||||||
|
trustedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||||
|
|
||||||
|
# Insecure mode FOR TESTING ENVIRONNEMENT ONLY
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: false
|
||||||
|
#
|
||||||
|
# insecure = true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Forwarded Header
|
||||||
|
|
||||||
|
Only IPs in `trustedIPs` will be authorize to trust the client forwarded headers (`X-Forwarded-*`).
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
|
||||||
|
# Enable Forwarded Headers
|
||||||
|
[entryPoints.http.forwardedHeaders]
|
||||||
|
# List of trusted IPs
|
||||||
|
#
|
||||||
|
# Required
|
||||||
|
# Default: []
|
||||||
|
#
|
||||||
|
trustedIPs = ["127.0.0.1/32", "192.168.1.7"]
|
||||||
|
```
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
4
docs/img/grpc.svg
Normal file
4
docs/img/grpc.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 186 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 255 KiB |
144
docs/index.md
144
docs/index.md
@@ -1,17 +1,17 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="img/traefik.logo.png" alt="Træfɪk" title="Træfɪk" />
|
<img src="img/traefik.logo.png" alt="Træfik" title="Træfik" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://travis-ci.org/containous/traefik)
|
[](https://semaphoreci.com/containous/traefik)
|
||||||
[](https://docs.traefik.io)
|
[](https://docs.traefik.io)
|
||||||
[](http://goreportcard.com/report/containous/traefik)
|
[](https://goreportcard.com/report/github.com/containous/traefik)
|
||||||
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
||||||
[](https://traefik.herokuapp.com)
|
[](https://traefik.herokuapp.com)
|
||||||
[](https://twitter.com/intent/follow?screen_name=traefikproxy)
|
[](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.
|
Træfik (pronounced like [traffic](https://speak-ipa.bearbin.net/speak.cgi?speak=%CB%88tr%C3%A6f%C9%AAk)) is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
|
||||||
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [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 mode](https://docs.docker.com/engine/swarm/), [Kubernetes](https://kubernetes.io), [Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Rancher](https://rancher.com), [Amazon ECS](https://aws.amazon.com/ecs), and a lot more) to manage its configuration automatically and dynamically.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -26,21 +26,59 @@ But a microservices architecture is dynamic... Services are added, removed, kill
|
|||||||
|
|
||||||
Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily.
|
Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily.
|
||||||
|
|
||||||
Here enters Træfɪk.
|
Here enters Træfik.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
Træfik 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.
|
Routes to your services will be created instantly.
|
||||||
|
|
||||||
Run it and forget it!
|
Run it and forget it!
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
## Demo
|
- [It's fast](/benchmarks)
|
||||||
|
- No dependency hell, single binary made with go
|
||||||
|
- [Tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image
|
||||||
|
- Rest API
|
||||||
|
- Hot-reloading of configuration. No need to restart the process
|
||||||
|
- Circuit breakers, retry
|
||||||
|
- Round Robin, rebalancer load-balancers
|
||||||
|
- Metrics (Rest, Prometheus, Datadog, Statd)
|
||||||
|
- Clean AngularJS Web UI
|
||||||
|
- Websocket, HTTP/2, GRPC ready
|
||||||
|
- Access Logs (JSON, CLF)
|
||||||
|
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS with renewal)
|
||||||
|
- High Availability with cluster mode
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
[](https://asciinema.org/a/4tcyde7riou5vxulo6my3mtko)
|
## Supported backends
|
||||||
|
|
||||||
|
- [Docker](https://www.docker.com/) / [Swarm mode](https://docs.docker.com/engine/swarm/)
|
||||||
|
- [Kubernetes](https://kubernetes.io)
|
||||||
|
- [Mesos](https://github.com/apache/mesos) / [Marathon](https://mesosphere.github.io/marathon/)
|
||||||
|
- [Rancher](https://rancher.com) (API, Metadata)
|
||||||
|
- [Consul](https://www.consul.io/) / [Etcd](https://coreos.com/etcd/) / [Zookeeper](https://zookeeper.apache.org) / [BoltDB](https://github.com/boltdb/bolt)
|
||||||
|
- [Eureka](https://github.com/Netflix/eureka)
|
||||||
|
- [Amazon ECS](https://aws.amazon.com/ecs)
|
||||||
|
- [Amazon DynamoDB](https://aws.amazon.com/dynamodb)
|
||||||
|
- File
|
||||||
|
- Rest API
|
||||||
|
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
You can have a quick look at Træfik in this [Katacoda tutorial](https://www.katacoda.com/courses/traefik/deploy-load-balancer) that shows how to load balance requests between multiple Docker containers.
|
||||||
|
|
||||||
|
Here is a talk given by [Emile Vauge](https://github.com/emilevauge) at [GopherCon 2017](https://gophercon.com).
|
||||||
|
You will learn Træfik basics in less than 10 minutes.
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=RgudiksfL-k)
|
||||||
|
|
||||||
|
Here is a talk given by [Ed Robinson](https://github.com/errm) at [ContainerCamp UK](https://container.camp) conference.
|
||||||
|
You will learn fundamental Træfik features and see some demos with Kubernetes.
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=aFtpIShV60I)
|
||||||
|
|
||||||
## Get it
|
## Get it
|
||||||
|
|
||||||
@@ -62,42 +100,71 @@ docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.to
|
|||||||
|
|
||||||
## Test it
|
## Test it
|
||||||
|
|
||||||
You can test Træfɪk easily using [Docker compose](https://docs.docker.com/compose), with this `docker-compose.yml` file:
|
You can test Træfik easily using [Docker compose](https://docs.docker.com/compose), with this `docker-compose.yml` file in a folder named `traefik`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
traefik:
|
version: '2'
|
||||||
image: traefik
|
|
||||||
command: --web --docker --docker.domain=docker.localhost --logLevel=DEBUG
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "8080:8080"
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
- /dev/null:/traefik.toml
|
|
||||||
|
|
||||||
whoami1:
|
services:
|
||||||
image: emilevauge/whoami
|
proxy:
|
||||||
labels:
|
image: traefik
|
||||||
- "traefik.backend=whoami"
|
command: --web --docker --docker.domain=docker.localhost --logLevel=DEBUG
|
||||||
- "traefik.frontend.rule=Host:whoami.docker.localhost"
|
networks:
|
||||||
|
- webgateway
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- /dev/null:/traefik.toml
|
||||||
|
|
||||||
whoami2:
|
networks:
|
||||||
image: emilevauge/whoami
|
webgateway:
|
||||||
labels:
|
driver: bridge
|
||||||
- "traefik.backend=whoami"
|
|
||||||
- "traefik.frontend.rule=Host:whoami.docker.localhost"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, start it:
|
Start it from within the `traefik` folder:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, test load-balancing between the two servers `whoami1` and `whoami2`:
|
In a browser you may open [http://localhost:8080](http://localhost:8080) to access Træfik's dashboard and observe the following magic.
|
||||||
|
|
||||||
```bash
|
Now, create a folder named `test` and create a `docker-compose.yml` in it with this content:
|
||||||
$ curl -H Host:whoami.docker.localhost http://127.0.0.1
|
|
||||||
|
```yaml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
services:
|
||||||
|
whoami:
|
||||||
|
image: emilevauge/whoami
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
labels:
|
||||||
|
- "traefik.backend=whoami"
|
||||||
|
- "traefik.frontend.rule=Host:whoami.docker.localhost"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
external:
|
||||||
|
name: traefik_webgateway
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, start and scale it in the `test` folder:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker-compose up -d
|
||||||
|
docker-compose scale whoami=2
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, test load-balancing between the two services `test_whoami_1` and `test_whoami_2`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -H Host:whoami.docker.localhost http://127.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
Hostname: ef194d07634a
|
Hostname: ef194d07634a
|
||||||
IP: 127.0.0.1
|
IP: 127.0.0.1
|
||||||
IP: ::1
|
IP: ::1
|
||||||
@@ -112,8 +179,13 @@ X-Forwarded-For: 172.17.0.1
|
|||||||
X-Forwarded-Host: 172.17.0.4:80
|
X-Forwarded-Host: 172.17.0.4:80
|
||||||
X-Forwarded-Proto: http
|
X-Forwarded-Proto: http
|
||||||
X-Forwarded-Server: dbb60406010d
|
X-Forwarded-Server: dbb60406010d
|
||||||
|
```
|
||||||
|
|
||||||
$ curl -H Host:whoami.docker.localhost http://127.0.0.1
|
```shell
|
||||||
|
curl -H Host:whoami.docker.localhost http://127.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
Hostname: 6c3c5df0c79a
|
Hostname: 6c3c5df0c79a
|
||||||
IP: 127.0.0.1
|
IP: 127.0.0.1
|
||||||
IP: ::1
|
IP: ::1
|
||||||
|
|||||||
4
docs/theme/js/extra.js
vendored
Normal file
4
docs/theme/js/extra.js
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/* Highlight */
|
||||||
|
(function(hljs) {
|
||||||
|
hljs.initHighlightingOnLoad();
|
||||||
|
})(hljs);
|
||||||
24
docs/theme/js/hljs/LICENSE
vendored
Normal file
24
docs/theme/js/hljs/LICENSE
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
Copyright (c) 2006, Ivan Sagalaev
|
||||||
|
All rights reserved.
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of highlight.js nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||||
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
2
docs/theme/js/hljs/highlight.pack.js
vendored
Normal file
2
docs/theme/js/hljs/highlight.pack.js
vendored
Normal file
File diff suppressed because one or more lines are too long
104
docs/theme/partials/footer.html
vendored
Normal file
104
docs/theme/partials/footer.html
vendored
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<!--
|
||||||
|
Copyright (c) 2016-2017 Martin Donath <martin.donath@squidfunk.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to
|
||||||
|
deal in the Software without restriction, including without limitation the
|
||||||
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
IN THE SOFTWARE.
|
||||||
|
-->
|
||||||
|
|
||||||
|
{% import "partials/language.html" as lang %}
|
||||||
|
|
||||||
|
<!-- Application footer -->
|
||||||
|
<footer class="md-footer">
|
||||||
|
|
||||||
|
<!-- Link to previous and/or next page -->
|
||||||
|
{% if page.previous_page or page.next_page %}
|
||||||
|
<!--<div class="md-footer-nav">-->
|
||||||
|
<!--<nav class="md-footer-nav__inner md-grid">-->
|
||||||
|
<!-- -->
|
||||||
|
<!-- Link to previous page -->
|
||||||
|
<!--{% if page.previous_page %}-->
|
||||||
|
<!--<a href="{{ page.previous_page.url }}"-->
|
||||||
|
<!--title="{{ page.previous_page.title }}"-->
|
||||||
|
<!--class="md-flex md-footer-nav__link md-footer-nav__link--prev"-->
|
||||||
|
<!--rel="prev">-->
|
||||||
|
<!--<div class="md-flex__cell md-flex__cell--shrink">-->
|
||||||
|
<!--<i class="md-icon md-icon--arrow-back-->
|
||||||
|
<!--md-footer-nav__button"></i>-->
|
||||||
|
<!--</div>-->
|
||||||
|
<!--<div class="md-flex__cell md-flex__cell--stretch-->
|
||||||
|
<!--md-footer-nav__title">-->
|
||||||
|
<!--<span class="md-flex__ellipsis">-->
|
||||||
|
<!--<span class="md-footer-nav__direction">-->
|
||||||
|
<!--{{ lang.t("footer.previous") }} -->
|
||||||
|
<!--</span>-->
|
||||||
|
<!--{{ page.previous_page.title }}-->
|
||||||
|
<!--</span>-->
|
||||||
|
<!--</div>-->
|
||||||
|
<!--</a>-->
|
||||||
|
<!--{% endif %}-->
|
||||||
|
<!-- -->
|
||||||
|
<!-- Link to next page -->
|
||||||
|
<!--{% if page.next_page %}-->
|
||||||
|
<!--<a href="{{ page.next_page.url }}" title="{{ page.next_page.title }}"-->
|
||||||
|
<!--class="md-flex md-footer-nav__link md-footer-nav__link--next"-->
|
||||||
|
<!--rel="next">-->
|
||||||
|
<!--<div class="md-flex__cell md-flex__cell--stretch-->
|
||||||
|
<!--md-footer-nav__title">-->
|
||||||
|
<!--<span class="md-flex__ellipsis">-->
|
||||||
|
<!--<span class="md-footer-nav__direction">-->
|
||||||
|
<!--{{ lang.t("footer.next") }}-->
|
||||||
|
<!--</span>-->
|
||||||
|
<!--{{ page.next_page.title }}-->
|
||||||
|
<!--</span>-->
|
||||||
|
<!--</div>-->
|
||||||
|
<!--<div class="md-flex__cell md-flex__cell--shrink">-->
|
||||||
|
<!--<i class="md-icon md-icon--arrow-forward-->
|
||||||
|
<!--md-footer-nav__button"></i>-->
|
||||||
|
<!--</div>-->
|
||||||
|
<!--</a>-->
|
||||||
|
<!--{% endif %}-->
|
||||||
|
<!--</nav>-->
|
||||||
|
<!--</div>-->
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Further information -->
|
||||||
|
<div class="md-footer-meta md-typeset">
|
||||||
|
<div class="md-footer-meta__inner md-grid">
|
||||||
|
|
||||||
|
<!-- Copyright and theme information -->
|
||||||
|
<div class="md-footer-copyright">
|
||||||
|
{% if config.copyright %}
|
||||||
|
<div class="md-footer-copyright__highlight">
|
||||||
|
{{ config.copyright }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
powered by
|
||||||
|
<a href="http://www.mkdocs.org" title="MkDocs">MkDocs</a>
|
||||||
|
and
|
||||||
|
<a href="http://squidfunk.github.io/mkdocs-material/"
|
||||||
|
title="Material for MkDocs">
|
||||||
|
Material for MkDocs</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Social links -->
|
||||||
|
{% block social %}
|
||||||
|
{% include "partials/social.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
96
docs/theme/styles/atom-one-light.css
vendored
Normal file
96
docs/theme/styles/atom-one-light.css
vendored
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Atom One Light by Daniel Gamage
|
||||||
|
Original One Light Syntax theme from https://github.com/atom/one-light-syntax
|
||||||
|
|
||||||
|
base: #fafafa
|
||||||
|
mono-1: #383a42
|
||||||
|
mono-2: #686b77
|
||||||
|
mono-3: #a0a1a7
|
||||||
|
hue-1: #0184bb
|
||||||
|
hue-2: #4078f2
|
||||||
|
hue-3: #a626a4
|
||||||
|
hue-4: #50a14f
|
||||||
|
hue-5: #e45649
|
||||||
|
hue-5-2: #c91243
|
||||||
|
hue-6: #986801
|
||||||
|
hue-6-2: #c18401
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 0.5em;
|
||||||
|
color: #383a42;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-quote {
|
||||||
|
color: #a0a1a7;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-doctag,
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-formula {
|
||||||
|
color: #a626a4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-section,
|
||||||
|
.hljs-name,
|
||||||
|
.hljs-selector-tag,
|
||||||
|
.hljs-deletion,
|
||||||
|
.hljs-subst {
|
||||||
|
color: #e45649;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-literal {
|
||||||
|
color: #0184bb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-regexp,
|
||||||
|
.hljs-addition,
|
||||||
|
.hljs-attribute,
|
||||||
|
.hljs-meta-string {
|
||||||
|
color: #50a14f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-built_in,
|
||||||
|
.hljs-class .hljs-title {
|
||||||
|
color: #c18401;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-attr,
|
||||||
|
.hljs-variable,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-type,
|
||||||
|
.hljs-selector-class,
|
||||||
|
.hljs-selector-attr,
|
||||||
|
.hljs-selector-pseudo,
|
||||||
|
.hljs-number {
|
||||||
|
color: #986801;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-link,
|
||||||
|
.hljs-meta,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-title {
|
||||||
|
color: #4078f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-emphasis {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-link {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
20
docs/theme/styles/extra.css
vendored
Normal file
20
docs/theme/styles/extra.css
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.md-logo img {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix for Chrome */
|
||||||
|
.md-typeset__table td code {
|
||||||
|
word-break: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-typeset__table tr :nth-child(1) {
|
||||||
|
word-wrap: break-word;
|
||||||
|
max-width: 30em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
972
docs/toml.md
972
docs/toml.md
@@ -1,972 +0,0 @@
|
|||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Sets the maximum request body to be stored in memory in Mo
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
# Default: 2
|
|
||||||
#
|
|
||||||
# maxMem = 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, don't forget to mount this file as a volume.
|
|
||||||
#
|
|
||||||
# Required
|
|
||||||
#
|
|
||||||
storageFile = "acme.json"
|
|
||||||
|
|
||||||
# Entrypoint to proxy acme challenge to.
|
|
||||||
# WARNING, must point to an entrypoint on port 443
|
|
||||||
#
|
|
||||||
# Required
|
|
||||||
#
|
|
||||||
entryPoint = "https"
|
|
||||||
|
|
||||||
# Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate.
|
|
||||||
# WARNING, TLS handshakes will be slow when requesting a hostname certificate for the first time, this can leads to DoS attacks.
|
|
||||||
# WARNING, Take note that Let's Encrypt have rate limiting: https://community.letsencrypt.org/t/quick-start-guide/1631
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
# onDemand = true
|
|
||||||
|
|
||||||
# CA server to use
|
|
||||||
# Uncomment the line to run on the staging let's encrypt server
|
|
||||||
# Leave comment to go to prod
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
# caServer = "https://acme-staging.api.letsencrypt.org/directory"
|
|
||||||
|
|
||||||
# Domains list
|
|
||||||
# You can provide SANs (alternative domains) to each main domain
|
|
||||||
# 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
|
|
||||||
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
|
|
||||||
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
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
- `/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.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
|
||||||
* `traefik.domain=traefik.localhost`: override the default domain
|
|
||||||
|
|
||||||
|
|
||||||
## Marathon backend
|
|
||||||
|
|
||||||
Træfɪk can be configured to use Marathon as a backend configuration:
|
|
||||||
|
|
||||||
|
|
||||||
```toml
|
|
||||||
################################################################
|
|
||||||
# Mesos/Marathon configuration backend
|
|
||||||
################################################################
|
|
||||||
|
|
||||||
# Enable Marathon configuration backend
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
[marathon]
|
|
||||||
|
|
||||||
# Marathon server endpoint.
|
|
||||||
# You can also specify multiple endpoint for Marathon:
|
|
||||||
# endpoint := "http://10.241.1.71:8080,10.241.1.72:8080,10.241.1.73:8080"
|
|
||||||
#
|
|
||||||
# Required
|
|
||||||
#
|
|
||||||
endpoint = "http://127.0.0.1:8080"
|
|
||||||
|
|
||||||
# Enable watch Marathon changes
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
watch = true
|
|
||||||
|
|
||||||
# Default domain used.
|
|
||||||
# Can be overridden by setting the "traefik.domain" label on an application.
|
|
||||||
#
|
|
||||||
# Required
|
|
||||||
#
|
|
||||||
domain = "marathon.localhost"
|
|
||||||
|
|
||||||
# Override default configuration template. For advanced users :)
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
# filename = "marathon.tmpl"
|
|
||||||
|
|
||||||
# Expose Marathon apps by default in traefik
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
# Default: false
|
|
||||||
#
|
|
||||||
# ExposedByDefault = true
|
|
||||||
|
|
||||||
# Enable Marathon basic authentication
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
# [marathon.basic]
|
|
||||||
# httpBasicAuthUser = "foo"
|
|
||||||
# httpBasicPassword = "bar"
|
|
||||||
|
|
||||||
# TLS client configuration. https://golang.org/pkg/crypto/tls/#Config
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
# [marathon.TLS]
|
|
||||||
# InsecureSkipVerify = true
|
|
||||||
```
|
|
||||||
|
|
||||||
Labels can be used on containers to override default behaviour:
|
|
||||||
|
|
||||||
- `traefik.backend=foo`: assign the application to `foo` backend
|
|
||||||
- `traefik.portIndex=1`: register port by index in the application's ports array. Useful when the application exposes multiple ports.
|
|
||||||
- `traefik.port=80`: register the explicit application port value. Cannot be used alongside `traefik.portIndex`.
|
|
||||||
- `traefik.protocol=https`: override the default `http` protocol
|
|
||||||
- `traefik.weight=10`: assign this weight to the application
|
|
||||||
- `traefik.enable=false`: disable this application in Træfɪk
|
|
||||||
- `traefik.frontend.rule=Host:test.traefik.io`: override the default frontend rule (Default: `Host:{containerName}.{domain}`).
|
|
||||||
- `traefik.frontend.passHostHeader=true`: forward client `Host` header to the backend.
|
|
||||||
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
|
||||||
* `traefik.domain=traefik.localhost`: override the default domain
|
|
||||||
|
|
||||||
|
|
||||||
## 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"]
|
|
||||||
```
|
|
||||||
|
|
||||||
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.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:4001"
|
|
||||||
|
|
||||||
# Enable watch Etcd changes
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
watch = true
|
|
||||||
|
|
||||||
# Prefix used for KV store.
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
prefix = "/traefik"
|
|
||||||
|
|
||||||
# Override default configuration template. For advanced users :)
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
# filename = "etcd.tmpl"
|
|
||||||
|
|
||||||
# Enable etcd TLS connection
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
# [etcd.tls]
|
|
||||||
# ca = "/etc/ssl/ca.crt"
|
|
||||||
# cert = "/etc/ssl/etcd.crt"
|
|
||||||
# key = "/etc/ssl/etcd.key"
|
|
||||||
# insecureskipverify = true
|
|
||||||
```
|
|
||||||
|
|
||||||
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
|
|
||||||
|
|
||||||
|
|
||||||
## Zookeeper backend
|
|
||||||
|
|
||||||
Træfɪk can be configured to use Zookeeper as a backend configuration:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
################################################################
|
|
||||||
# Zookeeper configuration backend
|
|
||||||
################################################################
|
|
||||||
|
|
||||||
# Enable Zookeeperconfiguration backend
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
[zookeeper]
|
|
||||||
|
|
||||||
# Zookeeper server endpoint
|
|
||||||
#
|
|
||||||
# Required
|
|
||||||
#
|
|
||||||
endpoint = "127.0.0.1:2181"
|
|
||||||
|
|
||||||
# Enable watch Zookeeper changes
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
watch = true
|
|
||||||
|
|
||||||
# Prefix used for KV store.
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
prefix = "/traefik"
|
|
||||||
|
|
||||||
# Override default configuration template. For advanced users :)
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
# filename = "zookeeper.tmpl"
|
|
||||||
```
|
|
||||||
|
|
||||||
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
|
|
||||||
|
|
||||||
## BoltDB backend
|
|
||||||
|
|
||||||
Træfɪk can be configured to use BoltDB as a backend configuration:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
################################################################
|
|
||||||
# BoltDB configuration backend
|
|
||||||
################################################################
|
|
||||||
|
|
||||||
# Enable BoltDB configuration backend
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
[boltdb]
|
|
||||||
|
|
||||||
# BoltDB file
|
|
||||||
#
|
|
||||||
# Required
|
|
||||||
#
|
|
||||||
endpoint = "/my.db"
|
|
||||||
|
|
||||||
# Enable watch BoltDB changes
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
watch = true
|
|
||||||
|
|
||||||
# Prefix used for KV store.
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
prefix = "/traefik"
|
|
||||||
|
|
||||||
# Override default configuration template. For advanced users :)
|
|
||||||
#
|
|
||||||
# Optional
|
|
||||||
#
|
|
||||||
# filename = "boltdb.tmpl"
|
|
||||||
```
|
|
||||||
|
|
||||||
Please refer to the [Key Value storage structure](#key-value-storage-structure) section to get documentation en traefik KV structure.
|
|
||||||
|
|
||||||
## Key-value storage structure
|
|
||||||
|
|
||||||
The Keys-Values structure should look (using `prefix = "/traefik"`):
|
|
||||||
|
|
||||||
- backend 1
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|--------------------------------------------------------|-----------------------------|
|
|
||||||
| `/traefik/backends/backend1/circuitbreaker/expression` | `NetworkErrorRatio() > 0.5` |
|
|
||||||
| `/traefik/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
|
||||||
| `/traefik/backends/backend1/servers/server1/weight` | `10` |
|
|
||||||
| `/traefik/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
|
|
||||||
| `/traefik/backends/backend1/servers/server2/weight` | `1` |
|
|
||||||
|
|
||||||
- backend 2
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|-----------------------------------------------------|------------------------|
|
|
||||||
| `/traefik/backends/backend2/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/entrypoints` | `http,https` |
|
|
||||||
| `/traefik/frontends/frontend2/routes/test_2/rule` | `Path:/test` |
|
|
||||||
|
|
||||||
## Atomic configuration changes
|
|
||||||
|
|
||||||
The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically. As a result, it may be possible for Træfɪk to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag. To solve this problem, Træfɪk supports a special key called `/traefik/alias`. If set, Træfɪk use the value as an alternative key prefix.
|
|
||||||
|
|
||||||
Given the key structure below, Træfɪk will use the `http://172.17.0.2:80` as its only backend (frontend keys have been omitted for brevity).
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|-------------------------------------------------------------------------|-----------------------------|
|
|
||||||
| `/traefik/alias` | `/traefik_configurations/1` |
|
|
||||||
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
|
||||||
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
|
|
||||||
|
|
||||||
When an atomic configuration change is required, you may write a new configuration at an alternative prefix. Here, although the `/traefik_configurations/2/...` keys have been set, the old configuration is still active because the `/traefik/alias` key still points to `/traefik_configurations/1`:
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|-------------------------------------------------------------------------|-----------------------------|
|
|
||||||
| `/traefik/alias` | `/traefik_configurations/1` |
|
|
||||||
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
|
||||||
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
|
|
||||||
|
|
||||||
Once the `/traefik/alias` key is updated, the new `/traefik_configurations/2` configuration becomes active atomically. Here, we have a 50% balance between the `http://172.17.0.3:80` and the `http://172.17.0.4:80` hosts while no traffic is sent to the `172.17.0.2:80` host:
|
|
||||||
|
|
||||||
| Key | Value |
|
|
||||||
|-------------------------------------------------------------------------|-----------------------------|
|
|
||||||
| `/traefik/alias` | `/traefik_configurations/2` |
|
|
||||||
| `/traefik_configurations/1/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
|
||||||
| `/traefik_configurations/1/backends/backend1/servers/server1/weight` | `10` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server1/url` | `http://172.17.0.3:80` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server1/weight` | `5` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server2/url` | `http://172.17.0.4:80` |
|
|
||||||
| `/traefik_configurations/2/backends/backend1/servers/server2/weight` | `5` |
|
|
||||||
|
|
||||||
Note that Træfɪk *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik` prefix. Further, if the `/traefik/alias` key is set, all other sibling keys with the `/traefik` prefix are ignored.
|
|
||||||
|
|
||||||
25
docs/user-guide/cluster.md
Normal file
25
docs/user-guide/cluster.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Clustering / High Availability (beta)
|
||||||
|
|
||||||
|
This guide explains how to use Træfik in high availability mode.
|
||||||
|
|
||||||
|
In order to deploy and configure multiple Træfik instances, without copying the same configuration file on each instance, we will use a distributed Key-Value store.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
You will need a working KV store cluster.
|
||||||
|
_(Currently, we recommend [Consul](https://consul.io) .)_
|
||||||
|
|
||||||
|
## File configuration to KV store migration
|
||||||
|
|
||||||
|
We created a special Træfik command to help configuring your Key Value store from a Træfik TOML configuration file.
|
||||||
|
|
||||||
|
Please refer to [this section](/user-guide/kv-config/#store-configuration-in-key-value-store) to get more details.
|
||||||
|
|
||||||
|
## Deploy a Træfik cluster
|
||||||
|
|
||||||
|
Once your Træfik configuration is uploaded on your KV store, you can start each Træfik instance.
|
||||||
|
|
||||||
|
A Træfik cluster is based on a manager/worker model.
|
||||||
|
|
||||||
|
When starting, Træfik will elect a manager.
|
||||||
|
If this instance fails, another manager will be automatically elected.
|
||||||
233
docs/user-guide/docker-and-lets-encrypt.md
Normal file
233
docs/user-guide/docker-and-lets-encrypt.md
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
# Docker & Traefik
|
||||||
|
|
||||||
|
In this use case, we want to use Traefik as a _layer-7_ load balancer with SSL termination for a set of micro-services used to run a web application.
|
||||||
|
|
||||||
|
We also want to automatically _discover any services_ on the Docker host and let Traefik reconfigure itself automatically when containers get created (or shut down) so HTTP traffic can be routed accordingly.
|
||||||
|
|
||||||
|
In addition, we want to use Let's Encrypt to automatically generate and renew SSL certificates per hostname.
|
||||||
|
|
||||||
|
## Setting Up
|
||||||
|
|
||||||
|
In order for this to work, you'll need a server with a public IP address, with Docker installed on it.
|
||||||
|
|
||||||
|
In this example, we're using the fictitious domain _my-awesome-app.org_.
|
||||||
|
|
||||||
|
In real-life, you'll want to use your own domain and have the DNS configured accordingly so the hostname records you'll want to use point to the aforementioned public IP address.
|
||||||
|
|
||||||
|
## Networking
|
||||||
|
|
||||||
|
Docker containers can only communicate with each other over TCP when they share at least one network.
|
||||||
|
This makes sense from a topological point of view in the context of networking, since Docker under the hood creates IPTable rules so containers can't reach other containers _unless you'd want to_.
|
||||||
|
|
||||||
|
In this example, we're going to use a single network called `web` where all containers that are handling HTTP traffic (including Traefik) will reside in.
|
||||||
|
|
||||||
|
On the Docker host, run the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker network create web
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, let's create a directory on the server where we will configure the rest of Traefik:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mkdir -p /opt/traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
Within this directory, we're going to create 3 empty files:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
touch /opt/traefik/docker-compose.yml
|
||||||
|
touch /opt/traefik/acme.json && chmod 600 /opt/traefik/acme.json
|
||||||
|
touch /opt/traefik/traefik.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
The `docker-compose.yml` file will provide us with a simple, consistent and more importantly, a deterministic way to create Traefik.
|
||||||
|
|
||||||
|
The contents of the file is as follows:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
services:
|
||||||
|
traefik:
|
||||||
|
image: traefik:1.3.5
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
- 443:443
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- /srv/traefik/traefik.toml:/traefik.toml
|
||||||
|
- /srv/traefik/acme.json:/acme.json
|
||||||
|
container_name: traefik
|
||||||
|
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
external: true
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see, we're mounting the `traefik.toml` file as well as the (empty) `acme.json` file in the container.
|
||||||
|
Also, we're mounting the `/var/run/docker.sock` Docker socket in the container as well, so Traefik can listen to Docker events and reconfigure it's own internal configuration when containers are created (or shut down).
|
||||||
|
Also, we're making sure the container is automatically restarted by the Docker engine in case of problems (or: if the server is rebooted).
|
||||||
|
We're publishing the default HTTP ports `80` and `443` on the host, and making sure the container is placed within the `web` network we've created earlier on.
|
||||||
|
Finally, we're giving this container a static name called `traefik`.
|
||||||
|
|
||||||
|
Let's take a look at a simple `traefik.toml` configuration as well before we'll create the Traefik container:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
debug = false
|
||||||
|
checkNewVersion = true
|
||||||
|
logLevel = "ERROR"
|
||||||
|
defaultEntryPoints = ["https","http"]
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.http.redirect]
|
||||||
|
entryPoint = "https"
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
|
||||||
|
[retry]
|
||||||
|
|
||||||
|
[docker]
|
||||||
|
endpoint = "unix:///var/run/docker.sock"
|
||||||
|
domain = "my-awesome-app.org"
|
||||||
|
watch = true
|
||||||
|
exposedbydefault = false
|
||||||
|
|
||||||
|
[acme]
|
||||||
|
email = "your-email-here@my-awesome-app.org"
|
||||||
|
storage = "acme.json"
|
||||||
|
entryPoint = "https"
|
||||||
|
OnHostRule = true
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the minimum configuration required to do the following:
|
||||||
|
|
||||||
|
- Log `ERROR`-level messages (or more severe) to the console, but silence `DEBUG`-level messagse
|
||||||
|
- Check for new versions of Traefik periodically
|
||||||
|
- Create two entry points, namely an `HTTP` endpoint on port `80`, and an `HTTPS` endpoint on port `443` where all incoming traffic on port `80` will immediately get redirected to `HTTPS`.
|
||||||
|
- Enable the Docker configuration backend and listen for container events on the Docker unix socket we've mounted earlier. However, **new containers will not be exposed by Traefik by default, we'll get into this in a bit!**
|
||||||
|
- Enable automatic request and configuration of SSL certificates using Let's Encrypt.
|
||||||
|
These certificates will be stored in the `acme.json` file, which you can back-up yourself and store off-premises.
|
||||||
|
|
||||||
|
Alright, let's boot the container. From the `/opt/traefik` directory, run `docker-compose up -d` which will create and start the Traefik container.
|
||||||
|
|
||||||
|
## Exposing Web Services to the Outside World
|
||||||
|
|
||||||
|
Now that we've fully configured and started Traefik, it's time to get our applications running!
|
||||||
|
|
||||||
|
Let's take a simple example of a micro-service project consisting of various services, where some will be exposed to the outside world and some will not.
|
||||||
|
|
||||||
|
The `docker-compose.yml` of our project looks like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: "2.1"
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: my-docker-registry.com/my-awesome-app/app:latest
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
- default
|
||||||
|
expose:
|
||||||
|
- "9000"
|
||||||
|
labels:
|
||||||
|
- "traefik.backend=my-awesome-app-app"
|
||||||
|
- "traefik.docker.network=web"
|
||||||
|
- "traefik.frontend.rule=Host:app.my-awesome-app.org"
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.port=9000"
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: my-docker-registry.com/back-end/5.7
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: my-docker-registry.com/back-end/redis:4-alpine
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
events:
|
||||||
|
image: my-docker-registry.com/my-awesome-app/events:latest
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
- default
|
||||||
|
expose:
|
||||||
|
- "3000"
|
||||||
|
labels:
|
||||||
|
- "traefik.backend=my-awesome-app-events"
|
||||||
|
- "traefik.docker.network=web"
|
||||||
|
- "traefik.frontend.rule=Host:events.my-awesome-app.org"
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.port=3000"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
external: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, we can see a set of services with two applications that we're actually exposing to the outside world.
|
||||||
|
Notice how there isn't a single container that has any published ports to the host -- everything is routed through Docker networks.
|
||||||
|
Also, only the containers that we want traffic to get routed to are attached to the `web` network we created at the start of this document.
|
||||||
|
|
||||||
|
Since the `traefik` container we've created and started earlier is also attached to this network, HTTP requests can now get routed to these containers.
|
||||||
|
|
||||||
|
### Labels
|
||||||
|
|
||||||
|
As mentioned earlier, we don't want containers exposed automatically by Traefik.
|
||||||
|
|
||||||
|
The reason behind this is simple: we want to have control over this process ourselves.
|
||||||
|
Thanks to Docker labels, we can tell Traefik how to create it's internal routing configuration.
|
||||||
|
|
||||||
|
Let's take a look at the labels themselves for the `app` service, which is a HTTP webservice listing on port 9000:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- "traefik.backend=my-awesome-app-app"
|
||||||
|
- "traefik.docker.network=web"
|
||||||
|
- "traefik.frontend.rule=Host:app.my-awesome-app.org"
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.port=9000"
|
||||||
|
```
|
||||||
|
|
||||||
|
First, we specify the `backend` name which corresponds to the actual service we're routing **to**.
|
||||||
|
|
||||||
|
We also tell Traefik to use the `web` network to route HTTP traffic to this container.
|
||||||
|
With the `frontend.rule` label, we tell Traefik that we want to route to this container if the incoming HTTP request contains the `Host` `app.my-awesome-app.org`.
|
||||||
|
Essentially, this is the actual rule used for Layer-7 load balancing.
|
||||||
|
With the `traefik.enable` label, we tell Traefik to include this container in it's internal configuration.
|
||||||
|
|
||||||
|
Finally but not unimportantly, we tell Traefik to route **to** port `9000`, since that is the actual TCP/IP port the container actually listens on.
|
||||||
|
|
||||||
|
#### Gotchas and tips
|
||||||
|
|
||||||
|
- Always specify the correct port where the container expects HTTP traffic using `traefik.port` label.
|
||||||
|
If a container exposes multiple ports, Traefik may forward traffic to the wrong port.
|
||||||
|
Even if a container only exposes one port, you should always write configuration defensively and explicitly.
|
||||||
|
- Should you choose to enable the `exposedbydefault` flag in the `traefik.toml` configuration, be aware that all containers that are placed in the same network as Traefik will automatically be reachable from the outside world, for everyone and everyone to see.
|
||||||
|
Usually, this is a bad idea.
|
||||||
|
- With the `traefik.frontend.auth.basic` label, it's possible for Traefik to provide a HTTP basic-auth challenge for the endpoints you provide the label for.
|
||||||
|
- Traefik has built-in support to automatically export [Prometheus](https://prometheus.io) metrics
|
||||||
|
- Traefik supports websockets out of the box. In the example above, the `events`-service could be a NodeJS-based application which allows clients to connect using websocket protocol.
|
||||||
|
Thanks to the fact that HTTPS in our example is enforced, these websockets are automatically secure as well (WSS)
|
||||||
|
|
||||||
|
### Final thoughts
|
||||||
|
|
||||||
|
Using Traefik as a Layer-7 load balancer in combination with both Docker and Let's Encrypt provides you with an extremely flexible, powerful and self-configuring solution for your projects.
|
||||||
|
|
||||||
|
With Let's Encrypt, your endpoints are automatically secured with production-ready SSL certificates that are renewed automatically as well.
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
|
|
||||||
You will find here some configuration examples of Træfɪk.
|
You will find here some configuration examples of Træfik.
|
||||||
|
|
||||||
## HTTP only
|
## HTTP only
|
||||||
|
|
||||||
```
|
```toml
|
||||||
defaultEntryPoints = ["http"]
|
defaultEntryPoints = ["http"]
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
@@ -14,7 +13,7 @@ defaultEntryPoints = ["http"]
|
|||||||
|
|
||||||
## HTTP + HTTPS (with SNI)
|
## HTTP + HTTPS (with SNI)
|
||||||
|
|
||||||
```
|
```toml
|
||||||
defaultEntryPoints = ["http", "https"]
|
defaultEntryPoints = ["http", "https"]
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
@@ -23,16 +22,17 @@ defaultEntryPoints = ["http", "https"]
|
|||||||
address = ":443"
|
address = ":443"
|
||||||
[entryPoints.https.tls]
|
[entryPoints.https.tls]
|
||||||
[[entryPoints.https.tls.certificates]]
|
[[entryPoints.https.tls.certificates]]
|
||||||
CertFile = "integration/fixtures/https/snitest.com.cert"
|
certFile = "integration/fixtures/https/snitest.com.cert"
|
||||||
KeyFile = "integration/fixtures/https/snitest.com.key"
|
keyFile = "integration/fixtures/https/snitest.com.key"
|
||||||
[[entryPoints.https.tls.certificates]]
|
[[entryPoints.https.tls.certificates]]
|
||||||
CertFile = "integration/fixtures/https/snitest.org.cert"
|
certFile = "integration/fixtures/https/snitest.org.cert"
|
||||||
KeyFile = "integration/fixtures/https/snitest.org.key"
|
keyFile = "integration/fixtures/https/snitest.org.key"
|
||||||
```
|
```
|
||||||
|
Note that we can either give path to certificate file or directly the file content itself ([like in this TOML example](/user-guide/kv-config/#upload-the-configuration-in-the-key-value-store)).
|
||||||
|
|
||||||
## HTTP redirect on HTTPS
|
## HTTP redirect on HTTPS
|
||||||
|
|
||||||
```
|
```toml
|
||||||
defaultEntryPoints = ["http", "https"]
|
defaultEntryPoints = ["http", "https"]
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.http]
|
[entryPoints.http]
|
||||||
@@ -43,25 +43,23 @@ defaultEntryPoints = ["http", "https"]
|
|||||||
address = ":443"
|
address = ":443"
|
||||||
[entryPoints.https.tls]
|
[entryPoints.https.tls]
|
||||||
[[entryPoints.https.tls.certificates]]
|
[[entryPoints.https.tls.certificates]]
|
||||||
certFile = "tests/traefik.crt"
|
certFile = "examples/traefik.crt"
|
||||||
keyFile = "tests/traefik.key"
|
keyFile = "examples/traefik.key"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Let's Encrypt support
|
## Let's Encrypt support
|
||||||
|
|
||||||
```
|
### Basic example
|
||||||
|
|
||||||
|
```toml
|
||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.https]
|
[entryPoints.https]
|
||||||
address = ":443"
|
address = ":443"
|
||||||
[entryPoints.https.tls]
|
[entryPoints.https.tls]
|
||||||
# certs used as default certs
|
|
||||||
[[entryPoints.https.tls.certificates]]
|
|
||||||
certFile = "tests/traefik.crt"
|
|
||||||
keyFile = "tests/traefik.key"
|
|
||||||
[acme]
|
[acme]
|
||||||
email = "test@traefik.io"
|
email = "test@traefik.io"
|
||||||
storageFile = "acme.json"
|
storage = "acme.json"
|
||||||
onDemand = true
|
|
||||||
caServer = "http://172.18.0.1:4000/directory"
|
caServer = "http://172.18.0.1:4000/directory"
|
||||||
entryPoint = "https"
|
entryPoint = "https"
|
||||||
|
|
||||||
@@ -77,9 +75,171 @@ entryPoint = "https"
|
|||||||
main = "local4.com"
|
main = "local4.com"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Override entrypoints in frontends
|
This configuration allows generating Let's Encrypt certificates for the four domains `local[1-4].com` with described SANs.
|
||||||
|
|
||||||
|
Traefik generates these certificates when it starts and it needs to be restart if new domains are added.
|
||||||
|
|
||||||
|
### OnHostRule option
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
|
||||||
|
[acme]
|
||||||
|
email = "test@traefik.io"
|
||||||
|
storage = "acme.json"
|
||||||
|
onHostRule = 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"
|
||||||
|
```
|
||||||
|
|
||||||
|
This configuration allows generating Let's Encrypt certificates for the four domains `local[1-4].com`.
|
||||||
|
|
||||||
|
Traefik generates these certificates when it starts.
|
||||||
|
|
||||||
|
If a backend is added with a `onHost` rule, Traefik will automatically generate the Let's Encrypt certificate for the new domain.
|
||||||
|
|
||||||
|
### OnDemand option
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
|
||||||
|
[acme]
|
||||||
|
email = "test@traefik.io"
|
||||||
|
storage = "acme.json"
|
||||||
|
onDemand = true
|
||||||
|
caServer = "http://172.18.0.1:4000/directory"
|
||||||
|
entryPoint = "https"
|
||||||
|
```
|
||||||
|
|
||||||
|
This configuration allows generating a Let's Encrypt certificate during the first HTTPS request on a new domain.
|
||||||
|
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
This option simplifies the configuration but :
|
||||||
|
|
||||||
|
* TLS handshakes will be slow when requesting a hostname certificate for the first time, this can leads to DDoS attacks.
|
||||||
|
* Let's Encrypt have rate limiting: https://letsencrypt.org/docs/rate-limits
|
||||||
|
|
||||||
|
That's why, it's better to use the `onHostRule` optin if possible.
|
||||||
|
|
||||||
|
### DNS challenge
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
|
||||||
|
[acme]
|
||||||
|
email = "test@traefik.io"
|
||||||
|
storage = "acme.json"
|
||||||
|
dnsProvider = "digitalocean" # DNS Provider name (cloudflare, OVH, gandi...)
|
||||||
|
delayDontCheckDNS = 0
|
||||||
|
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"
|
||||||
|
```
|
||||||
|
|
||||||
|
DNS challenge needs environment variables to be executed.
|
||||||
|
This variables have to be set on the machine/container which host Traefik.
|
||||||
|
|
||||||
|
These variables has described [in this section](/configuration/acme/#dnsprovider).
|
||||||
|
|
||||||
|
### OnHostRule option and provided certificates
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
certFile = "examples/traefik.crt"
|
||||||
|
keyFile = "examples/traefik.key"
|
||||||
|
|
||||||
|
[acme]
|
||||||
|
email = "test@traefik.io"
|
||||||
|
storage = "acme.json"
|
||||||
|
onHostRule = true
|
||||||
|
caServer = "http://172.18.0.1:4000/directory"
|
||||||
|
entryPoint = "https"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Traefik will only try to generate a Let's encrypt certificate if the domain cannot be checked by the provided certificates.
|
||||||
|
|
||||||
|
### Cluster mode
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
Before to use Let's Encrypt in a Traefik cluster, take a look to [the key-value store explanations](/user-guide/kv-config) and more precisely to [this section](/user-guide/kv-config/#store-configuration-in-key-value-store) in the way to know how to migrate from a acme local storage *(acme.json file)* to a key-value store configuration.
|
||||||
|
|
||||||
|
#### Configuration
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
|
||||||
|
[acme]
|
||||||
|
email = "test@traefik.io"
|
||||||
|
storage = "traefik/acme/account"
|
||||||
|
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"
|
||||||
|
|
||||||
|
[consul]
|
||||||
|
endpoint = "127.0.0.1:8500"
|
||||||
|
watch = true
|
||||||
|
prefix = "traefik"
|
||||||
|
```
|
||||||
|
|
||||||
|
This configuration allows to use the key `traefik/acme/account` to get/set Let's Encrypt certificates content.
|
||||||
|
The `consul` provider contains the configuration.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
It's possible to use others key-value store providers as described [here](/user-guide/kv-config/#key-value-store-configuration).
|
||||||
|
|
||||||
|
## Override entrypoints in frontends
|
||||||
|
|
||||||
|
```toml
|
||||||
[frontends]
|
[frontends]
|
||||||
[frontends.frontend1]
|
[frontends.frontend1]
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
@@ -88,6 +248,7 @@ entryPoint = "https"
|
|||||||
[frontends.frontend2]
|
[frontends.frontend2]
|
||||||
backend = "backend1"
|
backend = "backend1"
|
||||||
passHostHeader = true
|
passHostHeader = true
|
||||||
|
passTLSCert = true
|
||||||
entrypoints = ["https"] # overrides defaultEntryPoints
|
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||||
[frontends.frontend2.routes.test_1]
|
[frontends.frontend2.routes.test_1]
|
||||||
rule = "Host:{subdomain:[a-z]+}.localhost"
|
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||||
@@ -96,3 +257,129 @@ entryPoint = "https"
|
|||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
rule = "Path:/test"
|
rule = "Path:/test"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Enable Basic authentication in an entrypoint
|
||||||
|
|
||||||
|
With two user/pass:
|
||||||
|
|
||||||
|
- `test`:`test`
|
||||||
|
- `test2`:`test2`
|
||||||
|
|
||||||
|
Passwords are encoded in MD5: you can use htpasswd to generate those ones.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
defaultEntryPoints = ["http"]
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.http.auth.basic]
|
||||||
|
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pass Authenticated user to application via headers
|
||||||
|
|
||||||
|
Providing an authentication method as described above, it is possible to pass the user to the application
|
||||||
|
via a configurable header value.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
defaultEntryPoints = ["http"]
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.http.auth]
|
||||||
|
headerField = "X-WebAuth-User"
|
||||||
|
[entryPoints.http.auth.basic]
|
||||||
|
users = ["test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Override the Traefik HTTP server IdleTimeout and/or throttle configurations from re-loading too quickly
|
||||||
|
|
||||||
|
```toml
|
||||||
|
providersThrottleDuration = "5s"
|
||||||
|
|
||||||
|
[respondingTimeouts]
|
||||||
|
idleTimeout = "360s"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Securing Ping Health Check
|
||||||
|
|
||||||
|
The `/ping` health-check URL is enabled together with the web admin panel, enabled with the command-line `--web` or config file option `[web]`.
|
||||||
|
Thus, if you have a regular path for `/foo` and an entrypoint on `:80`, you would access them as follows:
|
||||||
|
|
||||||
|
* Regular path: `http://hostname:80/foo`
|
||||||
|
* Admin panel: `http://hostname:8080/`
|
||||||
|
* Ping URL: `http://hostname:8080/ping`
|
||||||
|
|
||||||
|
However, for security reasons, you may want to be able to expose the `/ping` health-check URL to outside health-checkers, e.g. an Internet service or cloud load-balancer, _without_ exposing your admin panel's port.
|
||||||
|
In many environments, the security staff may not _allow_ you to expose it.
|
||||||
|
|
||||||
|
You have two options:
|
||||||
|
|
||||||
|
* Enable `/ping` on a regular entrypoint
|
||||||
|
* Enable `/ping` on a dedicated port
|
||||||
|
|
||||||
|
### Enable ping health check on a regular entrypoint
|
||||||
|
|
||||||
|
To proxy `/ping` from a regular entrypoint to the admin one without exposing the panel, do the following:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[backends]
|
||||||
|
[backends.traefik]
|
||||||
|
[backends.traefik.servers.server1]
|
||||||
|
url = "http://localhost:8080"
|
||||||
|
weight = 10
|
||||||
|
|
||||||
|
[frontends]
|
||||||
|
[frontends.traefikadmin]
|
||||||
|
backend = "traefik"
|
||||||
|
[frontends.traefikadmin.routes.ping]
|
||||||
|
rule = "Path:/ping"
|
||||||
|
```
|
||||||
|
|
||||||
|
The above creates a new backend called `traefik`, listening on `http://localhost:8080`, i.e. the local admin port.
|
||||||
|
We only expose the admin panel via the `frontend` named `traefikadmin`, and only expose the `/ping` Path.
|
||||||
|
Be careful with the `traefikadmin` frontend. If you do _not_ specify a `Path:` rule, you would expose the entire dashboard.
|
||||||
|
|
||||||
|
### Enable ping health check on dedicated port
|
||||||
|
|
||||||
|
If you do not want to or cannot expose the health-check on a regular entrypoint - e.g. your security rules do not allow it, or you have a conflicting path - then you can enable health-check on its own entrypoint.
|
||||||
|
Use the following config:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
defaultEntryPoints = ["http"]
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.ping]
|
||||||
|
address = ":8082"
|
||||||
|
|
||||||
|
[backends]
|
||||||
|
[backends.traefik]
|
||||||
|
[backends.traefik.servers.server1]
|
||||||
|
url = "http://localhost:8080"
|
||||||
|
weight = 10
|
||||||
|
|
||||||
|
[frontends]
|
||||||
|
[frontends.traefikadmin]
|
||||||
|
backend = "traefik"
|
||||||
|
entrypoints = ["ping"]
|
||||||
|
[frontends.traefikadmin.routes.ping]
|
||||||
|
rule = "Path:/ping"
|
||||||
|
```
|
||||||
|
|
||||||
|
The above is similar to the previous example, but instead of enabling `/ping` on the _default_ entrypoint, we enable it on a _dedicated_ entrypoint.
|
||||||
|
|
||||||
|
In the above example, you would access a regular path, admin panel and health-check as follows:
|
||||||
|
|
||||||
|
* Regular path: `http://hostname:80/foo`
|
||||||
|
* Admin panel: `http://hostname:8080/`
|
||||||
|
* Ping URL: `http://hostname:8082/ping`
|
||||||
|
|
||||||
|
Note the dedicated port `:8082` for `/ping`.
|
||||||
|
|
||||||
|
In the above example, it is _very_ important to create a named dedicated entrypoint, and do **not** include it in `defaultEntryPoints`.
|
||||||
|
Otherwise, you are likely to expose _all_ services via that entrypoint.
|
||||||
|
|
||||||
|
In the above example, we have two entrypoints, `http` and `ping`, but we only included `http` in `defaultEntryPoints`, while explicitly tying `frontend.traefikadmin` to the `ping` entrypoint.
|
||||||
|
This ensures that all the "normal" frontends will be exposed via entrypoint `http` and _not_ via entrypoint `ping`.
|
||||||
|
|||||||
151
docs/user-guide/grpc.md
Normal file
151
docs/user-guide/grpc.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# gRPC example
|
||||||
|
|
||||||
|
This section explains how to use Traefik as reverse proxy for gRPC application with self-signed certificates.
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
As gRPC needs HTTP2, we need HTTPS certificates on both gRPC Server and Træfik.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="/img/grpc.svg" alt="gRPC architecture" title="gRPC architecture" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## gRPC Server certificate
|
||||||
|
|
||||||
|
In order to secure the gRPC server, we generate a self-signed certificate for backend url:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./backend.key -out ./backend.cert
|
||||||
|
```
|
||||||
|
|
||||||
|
That will prompt for information, the important answer is:
|
||||||
|
|
||||||
|
```
|
||||||
|
Common Name (e.g. server FQDN or YOUR name) []: backend.local
|
||||||
|
```
|
||||||
|
|
||||||
|
## gRPC Client certificate
|
||||||
|
|
||||||
|
Generate your self-signed certificate for frontend url:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./frontend.key -out ./frontend.cert
|
||||||
|
```
|
||||||
|
|
||||||
|
with
|
||||||
|
|
||||||
|
```
|
||||||
|
Common Name (e.g. server FQDN or YOUR name) []: frontend.local
|
||||||
|
```
|
||||||
|
|
||||||
|
## Træfik configuration
|
||||||
|
|
||||||
|
At last, we configure our Træfik instance to use both self-signed certificates.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
defaultEntryPoints = ["https"]
|
||||||
|
|
||||||
|
# For secure connection on backend.local
|
||||||
|
RootCAs = [ "./backend.cert" ]
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":4443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
# For secure connection on frontend.local
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
certFile = "./frontend.cert"
|
||||||
|
keyFile = "./frontend.key"
|
||||||
|
|
||||||
|
|
||||||
|
[web]
|
||||||
|
address = ":8080"
|
||||||
|
|
||||||
|
[file]
|
||||||
|
|
||||||
|
[backends]
|
||||||
|
[backends.backend1]
|
||||||
|
[backends.backend1.servers.server1]
|
||||||
|
# Access on backend with HTTPS
|
||||||
|
url = "https://backend.local:8080"
|
||||||
|
|
||||||
|
|
||||||
|
[frontends]
|
||||||
|
[frontends.frontend1]
|
||||||
|
backend = "backend1"
|
||||||
|
[frontends.frontend1.routes.test_1]
|
||||||
|
rule = "Host:frontend.local"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
With some backends, the server URLs use the IP, so you may need to configure `InsecureSkipVerify` instead of the `RootCAS` to activate HTTPS without hostname verification.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
We don't need specific configuration to use gRPC in Træfik, we just need to be careful that all the exchanges (between client and Træfik, and between Træfik and backend) are HTTPS communications because gRPC uses HTTP2.
|
||||||
|
|
||||||
|
## A gRPC example in go
|
||||||
|
|
||||||
|
We will use the gRPC greeter example in [grpc-go](https://github.com/grpc/grpc-go/tree/master/examples/helloworld)
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
In order to use this gRPC example, we need to modify it to use HTTPS
|
||||||
|
|
||||||
|
So we modify the "gRPC server example" to use our own self-signed certificate:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Read cert and key file
|
||||||
|
BackendCert, _ := ioutil.ReadFile("./backend.cert")
|
||||||
|
BackendKey, _ := ioutil.ReadFile("./backend.key")
|
||||||
|
|
||||||
|
// Generate Certificate struct
|
||||||
|
cert, err := tls.X509KeyPair(BackendCert, BackendKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to parse certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create credentials
|
||||||
|
creds := credentials.NewServerTLSFromCert(&cert)
|
||||||
|
|
||||||
|
// Use Credentials in gRPC server options
|
||||||
|
serverOption := grpc.Creds(creds)
|
||||||
|
var s *grpc.Server = grpc.NewServer(serverOption)
|
||||||
|
defer s.Stop()
|
||||||
|
|
||||||
|
pb.RegisterGreeterServer(s, &server{})
|
||||||
|
err := s.Serve(lis)
|
||||||
|
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Next we will modify gRPC Client to use our Træfik self-signed certificate:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Read cert file
|
||||||
|
FrontendCert, _ := ioutil.ReadFile("./frontend.cert")
|
||||||
|
|
||||||
|
// Create CertPool
|
||||||
|
roots := x509.NewCertPool()
|
||||||
|
roots.AppendCertsFromPEM(FrontendCert)
|
||||||
|
|
||||||
|
// Create credentials
|
||||||
|
credsClient := credentials.NewClientTLSFromCert(roots, "")
|
||||||
|
|
||||||
|
// Dial with specific Transport (with credentials)
|
||||||
|
conn, err := grpc.Dial("frontend.local:4443", grpc.WithTransportCredentials(credsClient))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("did not connect: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer conn.Close()
|
||||||
|
client := pb.NewGreeterClient(conn)
|
||||||
|
|
||||||
|
name := "World"
|
||||||
|
r, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: name})
|
||||||
|
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
719
docs/user-guide/kubernetes.md
Normal file
719
docs/user-guide/kubernetes.md
Normal file
@@ -0,0 +1,719 @@
|
|||||||
|
# Kubernetes Ingress Controller
|
||||||
|
|
||||||
|
This guide explains how to use Træfik as an Ingress controller in a Kubernetes cluster.
|
||||||
|
|
||||||
|
If you are not familiar with Ingresses in Kubernetes you might want to read the [Kubernetes user guide](https://kubernetes.io/docs/concepts/services-networking/ingress/)
|
||||||
|
|
||||||
|
The config files used in this guide can be found in the [examples directory](https://github.com/containous/traefik/tree/master/examples/k8s)
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. A working Kubernetes cluster. If you want to follow along with this guide, you should setup [minikube](https://kubernetes.io/docs/getting-started-guides/minikube/)
|
||||||
|
on your machine, as it is the quickest way to get a local Kubernetes cluster setup for experimentation and development.
|
||||||
|
|
||||||
|
2. The `kubectl` binary should be [installed on your workstation](https://kubernetes.io/docs/getting-started-guides/minikube/#download-kubectl).
|
||||||
|
|
||||||
|
### Role Based Access Control configuration (Kubernetes 1.6+ only)
|
||||||
|
|
||||||
|
Kubernetes introduces [Role Based Access Control (RBAC)](https://kubernetes.io/docs/admin/authorization/rbac/) in 1.6+ to allow fine-grained control of Kubernetes resources and api.
|
||||||
|
|
||||||
|
If your cluster is configured with RBAC, you may need to authorize Træfik to use the Kubernetes API using ClusterRole and ClusterRoleBinding resources:
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
your cluster may have suitable ClusterRoles already setup, but the following should work everywhere
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
kind: ClusterRole
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
- endpoints
|
||||||
|
- secrets
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- extensions
|
||||||
|
resources:
|
||||||
|
- ingresses
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
---
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
namespace: kube-system
|
||||||
|
```
|
||||||
|
|
||||||
|
[examples/k8s/traefik-rbac.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik-rbac.yaml)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-rbac.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deploy Træfik using a Deployment or DaemonSet
|
||||||
|
|
||||||
|
It is possible to use Træfik with a [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or a [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) object,
|
||||||
|
whereas both options have their own pros and cons:
|
||||||
|
|
||||||
|
- The scalability is much better when using a Deployment, because you will have a Single-Pod-per-Node model when using the DeaemonSet.
|
||||||
|
- It is possible to exclusively run a Service on a dedicated set of machines using taints and tolerations with a DaemonSet.
|
||||||
|
- On the other hand the DaemonSet allows you to access any Node directly on Port 80 and 443, where you have to setup a [Service](https://kubernetes.io/docs/concepts/services-networking/service/) object with a Deployment.
|
||||||
|
|
||||||
|
The Deployment objects looks like this:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
kind: Deployment
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
k8s-app: traefik-ingress-lb
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
k8s-app: traefik-ingress-lb
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
k8s-app: traefik-ingress-lb
|
||||||
|
name: traefik-ingress-lb
|
||||||
|
spec:
|
||||||
|
serviceAccountName: traefik-ingress-controller
|
||||||
|
terminationGracePeriodSeconds: 60
|
||||||
|
containers:
|
||||||
|
- image: traefik
|
||||||
|
name: traefik-ingress-lb
|
||||||
|
args:
|
||||||
|
- --web
|
||||||
|
- --kubernetes
|
||||||
|
---
|
||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-service
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
k8s-app: traefik-ingress-lb
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 80
|
||||||
|
name: web
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8080
|
||||||
|
name: admin
|
||||||
|
type: NodePort
|
||||||
|
```
|
||||||
|
[examples/k8s/traefik-deployment.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik-deployment.yaml)
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The Service will expose two NodePorts which allow access to the ingress and the web interface.
|
||||||
|
|
||||||
|
The DaemonSet objects looks not much different:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
kind: DaemonSet
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
k8s-app: traefik-ingress-lb
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
k8s-app: traefik-ingress-lb
|
||||||
|
name: traefik-ingress-lb
|
||||||
|
spec:
|
||||||
|
serviceAccountName: traefik-ingress-controller
|
||||||
|
terminationGracePeriodSeconds: 60
|
||||||
|
hostNetwork: true
|
||||||
|
containers:
|
||||||
|
- image: traefik
|
||||||
|
name: traefik-ingress-lb
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
hostPort: 80
|
||||||
|
- name: admin
|
||||||
|
containerPort: 8080
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
args:
|
||||||
|
- -d
|
||||||
|
- --web
|
||||||
|
- --kubernetes
|
||||||
|
---
|
||||||
|
kind: Service
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-service
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
k8s-app: traefik-ingress-lb
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 80
|
||||||
|
name: web
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8080
|
||||||
|
name: admin
|
||||||
|
type: NodePort
|
||||||
|
```
|
||||||
|
|
||||||
|
[examples/k8s/traefik-ds.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik-ds.yaml)
|
||||||
|
|
||||||
|
To deploy Træfik to your cluster start by submitting one of the YAML files to the cluster with `kubectl`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-deployment.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-ds.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
There are some significant differences between using Deployments and DaemonSets:
|
||||||
|
|
||||||
|
- The Deployment has easier up and down scaling possibilities.
|
||||||
|
It can implement full pod lifecycle and supports rolling updates from Kubernetes 1.2.
|
||||||
|
At least one Pod is needed to run the Deployment.
|
||||||
|
- The DaemonSet automatically scales to all nodes that meets a specific selector and guarantees to fill nodes one at a time.
|
||||||
|
Rolling updates are fully supported from Kubernetes 1.7 for DaemonSets as well.
|
||||||
|
|
||||||
|
### Check the Pods
|
||||||
|
|
||||||
|
Now lets check if our command was successful.
|
||||||
|
|
||||||
|
Start by listing the pods in the `kube-system` namespace:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kubectl --namespace=kube-system get pods
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
NAME READY STATUS RESTARTS AGE
|
||||||
|
kube-addon-manager-minikubevm 1/1 Running 0 4h
|
||||||
|
kubernetes-dashboard-s8krj 1/1 Running 0 4h
|
||||||
|
traefik-ingress-controller-678226159-eqseo 1/1 Running 0 7m
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see that after submitting the Deployment or DaemonSet to Kubernetes it has launched a Pod, and it is now running.
|
||||||
|
_It might take a few moments for kubernetes to pull the Træfik image and start the container._
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
You could also check the deployment with the Kubernetes dashboard, run
|
||||||
|
`minikube dashboard` to open it in your browser, then choose the `kube-system`
|
||||||
|
namespace from the menu at the top right of the screen.
|
||||||
|
|
||||||
|
You should now be able to access Træfik on port 80 of your Minikube instance when using the DaemonSet:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl $(minikube ip)
|
||||||
|
```
|
||||||
|
```
|
||||||
|
404 page not found
|
||||||
|
```
|
||||||
|
|
||||||
|
If you decided to use the deployment, then you need to target the correct NodePort, which can be seen then you execute `kubectl get services --namespace=kube-system`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl $(minikube ip):<NODEPORT>
|
||||||
|
```
|
||||||
|
```
|
||||||
|
404 page not found
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
We expect to see a 404 response here as we haven't yet given Træfik any configuration.
|
||||||
|
|
||||||
|
## Deploy Træfik using Helm Chart
|
||||||
|
|
||||||
|
Instead of installing Træfik via an own object, you can also use the Træfik Helm chart.
|
||||||
|
|
||||||
|
This allows more complex configuration via Kubernetes [ConfigMap](https://kubernetes.io/docs/tasks/configure-pod-container/configmap/) and enabled TLS certificates.
|
||||||
|
|
||||||
|
Install Træfik chart by:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
helm install stable/traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information, check out [the doc](https://github.com/kubernetes/charts/tree/master/stable/traefik).
|
||||||
|
|
||||||
|
## Submitting An Ingress to the cluster.
|
||||||
|
|
||||||
|
Lets start by creating a Service and an Ingress that will expose the [Træfik Web UI](https://github.com/containous/traefik#web-ui).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: traefik-web-ui
|
||||||
|
namespace: kube-system
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
k8s-app: traefik-ingress-lb
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: traefik-web-ui
|
||||||
|
namespace: kube-system
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: traefik
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: traefik-ui.minikube
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
serviceName: traefik-web-ui
|
||||||
|
servicePort: 80
|
||||||
|
```
|
||||||
|
[examples/k8s/ui.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/ui.yaml)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/ui.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Now lets setup an entry in our /etc/hosts file to route `traefik-ui.minikube` to our cluster.
|
||||||
|
|
||||||
|
In production you would want to set up real dns entries.
|
||||||
|
You can get the ip address of your minikube instance by running `minikube ip`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
echo "$(minikube ip) traefik-ui.minikube" | sudo tee -a /etc/hosts
|
||||||
|
```
|
||||||
|
|
||||||
|
We should now be able to visit [traefik-ui.minikube](http://traefik-ui.minikube) in the browser and view the Træfik Web UI.
|
||||||
|
|
||||||
|
## Name based routing
|
||||||
|
|
||||||
|
In this example we are going to setup websites for 3 of the United Kingdoms best loved cheeses, Cheddar, Stilton and Wensleydale.
|
||||||
|
|
||||||
|
First lets start by launching the 3 pods for the cheese websites.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
kind: Deployment
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: stilton
|
||||||
|
labels:
|
||||||
|
app: cheese
|
||||||
|
cheese: stilton
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: cheese
|
||||||
|
task: stilton
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: cheese
|
||||||
|
task: stilton
|
||||||
|
version: v0.0.1
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: cheese
|
||||||
|
image: errm/cheese:stilton
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
---
|
||||||
|
kind: Deployment
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: cheddar
|
||||||
|
labels:
|
||||||
|
app: cheese
|
||||||
|
cheese: cheddar
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: cheese
|
||||||
|
task: cheddar
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: cheese
|
||||||
|
task: cheddar
|
||||||
|
version: v0.0.1
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: cheese
|
||||||
|
image: errm/cheese:cheddar
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
---
|
||||||
|
kind: Deployment
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: wensleydale
|
||||||
|
labels:
|
||||||
|
app: cheese
|
||||||
|
cheese: wensleydale
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: cheese
|
||||||
|
task: wensleydale
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: cheese
|
||||||
|
task: wensleydale
|
||||||
|
version: v0.0.1
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: cheese
|
||||||
|
image: errm/cheese:wensleydale
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
```
|
||||||
|
[examples/k8s/cheese-deployments.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-deployments.yaml)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-deployments.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Next we need to setup a service for each of the cheese pods.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: stilton
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
targetPort: 80
|
||||||
|
port: 80
|
||||||
|
selector:
|
||||||
|
app: cheese
|
||||||
|
task: stilton
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: cheddar
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
targetPort: 80
|
||||||
|
port: 80
|
||||||
|
selector:
|
||||||
|
app: cheese
|
||||||
|
task: cheddar
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: wensleydale
|
||||||
|
annotations:
|
||||||
|
traefik.backend.circuitbreaker: "NetworkErrorRatio() > 0.5"
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
targetPort: 80
|
||||||
|
port: 80
|
||||||
|
selector:
|
||||||
|
app: cheese
|
||||||
|
task: wensleydale
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
We also set a [circuit breaker expression](/basics/#backends) for one of the backends by setting the `traefik.backend.circuitbreaker` annotation on the service.
|
||||||
|
|
||||||
|
|
||||||
|
[examples/k8s/cheese-services.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-services.yaml)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-services.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we can submit an ingress for the cheese websites.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: cheese
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: traefik
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: stilton.minikube
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
backend:
|
||||||
|
serviceName: stilton
|
||||||
|
servicePort: http
|
||||||
|
- host: cheddar.minikube
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
backend:
|
||||||
|
serviceName: cheddar
|
||||||
|
servicePort: http
|
||||||
|
- host: wensleydale.minikube
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
backend:
|
||||||
|
serviceName: wensleydale
|
||||||
|
servicePort: http
|
||||||
|
```
|
||||||
|
[examples/k8s/cheese-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheese-ingress.yaml)
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
we list each hostname, and add a backend service.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-ingress.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Now visit the [Træfik dashboard](http://traefik-ui.minikube/) and you should see a frontend for each host.
|
||||||
|
Along with a backend listing for each service with a Server set up for each pod.
|
||||||
|
|
||||||
|
If you edit your `/etc/hosts` again you should be able to access the cheese websites in your browser.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
echo "$(minikube ip) stilton.minikube cheddar.minikube wensleydale.minikube" | sudo tee -a /etc/hosts
|
||||||
|
```
|
||||||
|
|
||||||
|
* [Stilton](http://stilton.minikube/)
|
||||||
|
* [Cheddar](http://cheddar.minikube/)
|
||||||
|
* [Wensleydale](http://wensleydale.minikube/)
|
||||||
|
|
||||||
|
## Path based routing
|
||||||
|
|
||||||
|
Now lets suppose that our fictional client has decided that while they are super happy about our cheesy web design, when they asked for 3 websites they had not really bargained on having to buy 3 domain names.
|
||||||
|
|
||||||
|
No problem, we say, why don't we reconfigure the sites to host all 3 under one domain.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: cheeses
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: traefik
|
||||||
|
traefik.frontend.rule.type: PathPrefixStrip
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: cheeses.minikube
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /stilton
|
||||||
|
backend:
|
||||||
|
serviceName: stilton
|
||||||
|
servicePort: http
|
||||||
|
- path: /cheddar
|
||||||
|
backend:
|
||||||
|
serviceName: cheddar
|
||||||
|
servicePort: http
|
||||||
|
- path: /wensleydale
|
||||||
|
backend:
|
||||||
|
serviceName: wensleydale
|
||||||
|
servicePort: http
|
||||||
|
```
|
||||||
|
[examples/k8s/cheeses-ingress.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/cheeses-ingress.yaml)
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
we are configuring Træfik to strip the prefix from the url path with the `traefik.frontend.rule.type` annotation so that we can use the containers from the previous example without modification.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheeses-ingress.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
echo "$(minikube ip) cheeses.minikube" | sudo tee -a /etc/hosts
|
||||||
|
```
|
||||||
|
|
||||||
|
You should now be able to visit the websites in your browser.
|
||||||
|
|
||||||
|
* [cheeses.minikube/stilton](http://cheeses.minikube/stilton/)
|
||||||
|
* [cheeses.minikube/cheddar](http://cheeses.minikube/cheddar/)
|
||||||
|
* [cheeses.minikube/wensleydale](http://cheeses.minikube/wensleydale/)
|
||||||
|
|
||||||
|
## Specifying priority for routing
|
||||||
|
|
||||||
|
Sometimes you need to specify priority for ingress route, especially when handling wildcard routes.
|
||||||
|
This can be done by adding annotation `traefik.frontend.priority`, i.e.:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: wildcard-cheeses
|
||||||
|
annotations:
|
||||||
|
traefik.frontend.priority: "1"
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: *.minikube
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
backend:
|
||||||
|
serviceName: stilton
|
||||||
|
servicePort: http
|
||||||
|
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: specific-cheeses
|
||||||
|
annotations:
|
||||||
|
traefik.frontend.priority: "2"
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: specific.minikube
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
backend:
|
||||||
|
serviceName: stilton
|
||||||
|
servicePort: http
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that priority values must be quoted to avoid them being interpreted as numbers (which are illegal for annotations).
|
||||||
|
|
||||||
|
## Forwarding to ExternalNames
|
||||||
|
|
||||||
|
When specifying an [ExternalName](https://kubernetes.io/docs/concepts/services-networking/service/#services-without-selectors),
|
||||||
|
Træfik will forward requests to the given host accordingly and use HTTPS when the Service port matches 443.
|
||||||
|
This still requires setting up a proper port mapping on the Service from the Ingress port to the (external) Service port.
|
||||||
|
|
||||||
|
## Disable passing the Host header
|
||||||
|
|
||||||
|
By default Træfik will pass the incoming Host header on to the upstream resource.
|
||||||
|
|
||||||
|
There are times however where you may not want this to be the case.
|
||||||
|
For example if your service is of the ExternalName type.
|
||||||
|
|
||||||
|
### Disable entirely
|
||||||
|
|
||||||
|
Add the following to your toml config:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
disablePassHostHeaders = true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disable per ingress
|
||||||
|
|
||||||
|
To disable passing the Host header per ingress resource set the `traefik.frontend.passHostHeader` annotation on your ingress to `false`.
|
||||||
|
|
||||||
|
Here is an example ingress definition:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: example
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: traefik
|
||||||
|
traefik.frontend.passHostHeader: "false"
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: example.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /static
|
||||||
|
backend:
|
||||||
|
serviceName: static
|
||||||
|
servicePort: https
|
||||||
|
```
|
||||||
|
|
||||||
|
And an example service definition:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: static
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: https
|
||||||
|
port: 443
|
||||||
|
type: ExternalName
|
||||||
|
externalName: static.otherdomain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
If you were to visit `example.com/static` the request would then be passed onto `static.otherdomain.com/static` and s`tatic.otherdomain.com` would receive the request with the Host header being `static.otherdomain.com`.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The per ingress annotation overides whatever the global value is set to.
|
||||||
|
So you could set `disablePassHostHeaders` to `true` in your toml file and then enable passing
|
||||||
|
the host header per ingress if you wanted.
|
||||||
|
|
||||||
|
## Partitioning the Ingress object space
|
||||||
|
|
||||||
|
By default, Træfik processes every Ingress objects it observes. At times, however, it may be desirable to ignore certain objects. The following sub-sections describe common use cases and how they can be handled with Træfik.
|
||||||
|
|
||||||
|
### Between Træfik and other Ingress controller implementations
|
||||||
|
|
||||||
|
Sometimes Træfik runs along other Ingress controller implementations. One such example is when both Træfik and a cloud provider Ingress controller are active.
|
||||||
|
|
||||||
|
The `kubernetes.io/ingress.class` annotation can be attached to any Ingress object in order to control whether Træfik should handle it.
|
||||||
|
|
||||||
|
If the annotation is missing, contains an empty value, or the value `traefik`, then the Træfik controller will take responsibility and process the associated Ingress object. If the annotation contains any other value (usually the name of a different Ingress controller), Træfik will ignore the object.
|
||||||
|
|
||||||
|
### Between multiple Træfik Deployments
|
||||||
|
|
||||||
|
Sometimes multiple Træfik Deployments are supposed to run concurrently. For instance, it is conceivable to have one Deployment deal with internal and another one with external traffic.
|
||||||
|
|
||||||
|
For such cases, it is advisable to classify Ingress objects through a label and configure the `labelSelector` option per each Træfik Deployment accordingly. To stick with the internal/external example above, all Ingress objects meant for internal traffic could receive a `traffic-type: internal` label while objects designated for external traffic receive a `traffic-type: external` label. The label selectors on the Træfik Deployments would then be `traffic-type=internal` and `traffic-type=external`, respectively.
|
||||||
|
|
||||||
|
## Production advice
|
||||||
|
|
||||||
|
### Resource limitations
|
||||||
|
|
||||||
|
The examples shown deliberately do not specify any [resource limitations](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/) as there is no one size fits all.
|
||||||
|
|
||||||
|
In a production environment, however, it is important to set proper bounds, especially with regards to CPU:
|
||||||
|
|
||||||
|
- too strict and Traefik will be throttled while serving requests (as Kubernetes imposes hard quotas)
|
||||||
|
- too loose and Traefik may waste resources not available for other containers
|
||||||
|
|
||||||
|
When in doubt, you should measure your resource needs, and adjust requests and limits accordingly.
|
||||||
362
docs/user-guide/kv-config.md
Normal file
362
docs/user-guide/kv-config.md
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
# Key-value store configuration
|
||||||
|
|
||||||
|
Both [static global configuration](/user-guide/kv-config/#static-configuration-in-key-value-store) and [dynamic](/user-guide/kv-config/#dynamic-configuration-in-key-value-store) configuration can be sorted in a Key-value store.
|
||||||
|
|
||||||
|
This section explains how to launch Træfik using a configuration loaded from a Key-value store.
|
||||||
|
|
||||||
|
Træfik supports several Key-value stores:
|
||||||
|
|
||||||
|
- [Consul](https://consul.io)
|
||||||
|
- [etcd](https://coreos.com/etcd/)
|
||||||
|
- [ZooKeeper](https://zookeeper.apache.org/)
|
||||||
|
- [boltdb](https://github.com/boltdb/bolt)
|
||||||
|
|
||||||
|
## Static configuration in Key-value store
|
||||||
|
|
||||||
|
We will see the steps to set it up with an easy example.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
We could do the same with any other Key-value Store.
|
||||||
|
|
||||||
|
### docker-compose file for Consul
|
||||||
|
|
||||||
|
The Træfik global configuration will be retrieved from a [Consul](https://consul.io) store.
|
||||||
|
|
||||||
|
First we have to launch Consul in a container.
|
||||||
|
|
||||||
|
The [docker-compose file](https://docs.docker.com/compose/compose-file/) allows us to launch Consul and four instances of the trivial app [emilevauge/whoamI](https://github.com/emilevauge/whoamI) :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
consul:
|
||||||
|
image: progrium/consul
|
||||||
|
command: -server -bootstrap -log-level debug -ui-dir /ui
|
||||||
|
ports:
|
||||||
|
- "8400:8400"
|
||||||
|
- "8500:8500"
|
||||||
|
- "8600:53/udp"
|
||||||
|
expose:
|
||||||
|
- "8300"
|
||||||
|
- "8301"
|
||||||
|
- "8301/udp"
|
||||||
|
- "8302"
|
||||||
|
- "8302/udp"
|
||||||
|
|
||||||
|
whoami1:
|
||||||
|
image: emilevauge/whoami
|
||||||
|
|
||||||
|
whoami2:
|
||||||
|
image: emilevauge/whoami
|
||||||
|
|
||||||
|
whoami3:
|
||||||
|
image: emilevauge/whoami
|
||||||
|
|
||||||
|
whoami4:
|
||||||
|
image: emilevauge/whoami
|
||||||
|
```
|
||||||
|
|
||||||
|
### Upload the configuration in the Key-value store
|
||||||
|
|
||||||
|
We should now fill the store with the Træfik global configuration, as we do with a [TOML file configuration](/toml).
|
||||||
|
To do that, we can send the Key-value pairs via [curl commands](https://www.consul.io/intro/getting-started/kv.html) or via the [Web UI](https://www.consul.io/intro/getting-started/ui.html).
|
||||||
|
|
||||||
|
Fortunately, Træfik allows automation of this process using the `storeconfig` subcommand.
|
||||||
|
Please refer to the [store Træfik configuration](/user-guide/kv-config/#store-configuration-in-key-value-store) section to get documentation on it.
|
||||||
|
|
||||||
|
Here is the toml configuration we would like to store in the Key-value Store :
|
||||||
|
|
||||||
|
```toml
|
||||||
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
|
defaultEntryPoints = ["http", "https"]
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = "integration/fixtures/https/snitest.com.cert"
|
||||||
|
KeyFile = "integration/fixtures/https/snitest.com.key"
|
||||||
|
[[entryPoints.https.tls.certificates]]
|
||||||
|
CertFile = """-----BEGIN CERTIFICATE-----
|
||||||
|
<cert file content>
|
||||||
|
-----END CERTIFICATE-----"""
|
||||||
|
KeyFile = """-----BEGIN CERTIFICATE-----
|
||||||
|
<key file content>
|
||||||
|
-----END CERTIFICATE-----"""
|
||||||
|
|
||||||
|
[consul]
|
||||||
|
endpoint = "127.0.0.1:8500"
|
||||||
|
watch = true
|
||||||
|
prefix = "traefik"
|
||||||
|
|
||||||
|
[web]
|
||||||
|
address = ":8081"
|
||||||
|
```
|
||||||
|
|
||||||
|
And there, the same global configuration in the Key-value Store (using `prefix = "traefik"`):
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|-----------------------------------------------------------|---------------------------------------------------------------|
|
||||||
|
| `/traefik/loglevel` | `DEBUG` |
|
||||||
|
| `/traefik/defaultentrypoints/0` | `http` |
|
||||||
|
| `/traefik/defaultentrypoints/1` | `https` |
|
||||||
|
| `/traefik/entrypoints/http/address` | `:80` |
|
||||||
|
| `/traefik/entrypoints/https/address` | `:443` |
|
||||||
|
| `/traefik/entrypoints/https/tls/certificates/0/certfile` | `integration/fixtures/https/snitest.com.cert` |
|
||||||
|
| `/traefik/entrypoints/https/tls/certificates/0/keyfile` | `integration/fixtures/https/snitest.com.key` |
|
||||||
|
| `/traefik/entrypoints/https/tls/certificates/1/certfile` | `--BEGIN CERTIFICATE--<cert file content>--END CERTIFICATE--` |
|
||||||
|
| `/traefik/entrypoints/https/tls/certificates/1/keyfile` | `--BEGIN CERTIFICATE--<key file content>--END CERTIFICATE--` |
|
||||||
|
| `/traefik/consul/endpoint` | `127.0.0.1:8500` |
|
||||||
|
| `/traefik/consul/watch` | `true` |
|
||||||
|
| `/traefik/consul/prefix` | `traefik` |
|
||||||
|
| `/traefik/web/address` | `:8081` |
|
||||||
|
|
||||||
|
In case you are setting key values manually:
|
||||||
|
|
||||||
|
- Remember to specify the indexes (`0`,`1`, `2`, ... ) under prefixes `/traefik/defaultentrypoints/` and `/traefik/entrypoints/https/tls/certificates/` in order to match the global configuration structure.
|
||||||
|
- Be careful to give the correct IP address and port on the key `/traefik/consul/endpoint`.
|
||||||
|
|
||||||
|
Note that we can either give path to certificate file or directly the file content itself.
|
||||||
|
|
||||||
|
### Launch Træfik
|
||||||
|
|
||||||
|
We will now launch Træfik in a container.
|
||||||
|
|
||||||
|
We use CLI flags to setup the connection between Træfik and Consul.
|
||||||
|
All the rest of the global configuration is stored in Consul.
|
||||||
|
|
||||||
|
Here is the [docker-compose file](https://docs.docker.com/compose/compose-file/) :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
traefik:
|
||||||
|
image: traefik
|
||||||
|
command: --consul --consul.endpoint=127.0.0.1:8500
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "8080:8080"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
Be careful to give the correct IP address and port in the flag `--consul.endpoint`.
|
||||||
|
|
||||||
|
### Consul ACL Token support
|
||||||
|
|
||||||
|
To specify a Consul ACL token for Traefik, we have to set a System Environment variable named `CONSUL_HTTP_TOKEN` prior to starting Traefik.
|
||||||
|
This variable must be initialized with the ACL token value.
|
||||||
|
|
||||||
|
If Traefik is launched into a Docker container, the variable `CONSUL_HTTP_TOKEN` can be initialized with the `-e` Docker option : `-e "CONSUL_HTTP_TOKEN=[consul-acl-token-value]"`
|
||||||
|
|
||||||
|
### TLS support
|
||||||
|
|
||||||
|
To connect to a Consul endpoint using SSL, simply specify `https://` in the `consul.endpoint` property
|
||||||
|
|
||||||
|
- `--consul.endpoint=https://[consul-host]:[consul-ssl-port]`
|
||||||
|
|
||||||
|
### TLS support with client certificates
|
||||||
|
|
||||||
|
So far, only [Consul](https://consul.io) and [etcd](https://coreos.com/etcd/) support TLS connections with client certificates.
|
||||||
|
|
||||||
|
To set it up, we should enable [consul security](https://www.consul.io/docs/internals/security.html) (or [etcd security](https://coreos.com/etcd/docs/latest/security.html)).
|
||||||
|
|
||||||
|
Then, we have to provide CA, Cert and Key to Træfik using `consul` flags :
|
||||||
|
|
||||||
|
- `--consul.tls`
|
||||||
|
- `--consul.tls.ca=path/to/the/file`
|
||||||
|
- `--consul.tls.cert=path/to/the/file`
|
||||||
|
- `--consul.tls.key=path/to/the/file`
|
||||||
|
|
||||||
|
Or etcd flags :
|
||||||
|
|
||||||
|
- `--etcd.tls`
|
||||||
|
- `--etcd.tls.ca=path/to/the/file`
|
||||||
|
- `--etcd.tls.cert=path/to/the/file`
|
||||||
|
- `--etcd.tls.key=path/to/the/file`
|
||||||
|
|
||||||
|
!! note
|
||||||
|
We can either give directly directly the file content itself (instead of the path to certificate) in a TOML file configuration.
|
||||||
|
|
||||||
|
Remember the command `traefik --help` to display the updated list of flags.
|
||||||
|
|
||||||
|
## Dynamic configuration in Key-value store
|
||||||
|
|
||||||
|
Following our example, we will provide backends/frontends rules to Træfik.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
This section is independent of the way Træfik got its static configuration.
|
||||||
|
It means that the static configuration can either come from the same Key-value store or from any other sources.
|
||||||
|
|
||||||
|
### Key-value storage structure
|
||||||
|
|
||||||
|
Here is the toml configuration we would like to store in the store :
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[file]
|
||||||
|
|
||||||
|
# rules
|
||||||
|
[backends]
|
||||||
|
[backends.backend1]
|
||||||
|
[backends.backend1.circuitbreaker]
|
||||||
|
expression = "NetworkErrorRatio() > 0.5"
|
||||||
|
[backends.backend1.servers.server1]
|
||||||
|
url = "http://172.17.0.2:80"
|
||||||
|
weight = 10
|
||||||
|
[backends.backend1.servers.server2]
|
||||||
|
url = "http://172.17.0.3:80"
|
||||||
|
weight = 1
|
||||||
|
[backends.backend2]
|
||||||
|
[backends.backend1.maxconn]
|
||||||
|
amount = 10
|
||||||
|
extractorfunc = "request.host"
|
||||||
|
[backends.backend2.LoadBalancer]
|
||||||
|
method = "drr"
|
||||||
|
[backends.backend2.servers.server1]
|
||||||
|
url = "http://172.17.0.4:80"
|
||||||
|
weight = 1
|
||||||
|
[backends.backend2.servers.server2]
|
||||||
|
url = "http://172.17.0.5:80"
|
||||||
|
weight = 2
|
||||||
|
|
||||||
|
[frontends]
|
||||||
|
[frontends.frontend1]
|
||||||
|
backend = "backend2"
|
||||||
|
[frontends.frontend1.routes.test_1]
|
||||||
|
rule = "Host:test.localhost"
|
||||||
|
[frontends.frontend2]
|
||||||
|
backend = "backend1"
|
||||||
|
passHostHeader = true
|
||||||
|
priority = 10
|
||||||
|
entrypoints = ["https"] # overrides defaultEntryPoints
|
||||||
|
[frontends.frontend2.routes.test_1]
|
||||||
|
rule = "Host:{subdomain:[a-z]+}.localhost"
|
||||||
|
[frontends.frontend3]
|
||||||
|
entrypoints = ["http", "https"] # overrides defaultEntryPoints
|
||||||
|
backend = "backend2"
|
||||||
|
rule = "Path:/test"
|
||||||
|
```
|
||||||
|
|
||||||
|
And there, the same dynamic configuration in a KV Store (using `prefix = "traefik"`):
|
||||||
|
|
||||||
|
- backend 1
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|--------------------------------------------------------|-----------------------------|
|
||||||
|
| `/traefik/backends/backend1/circuitbreaker/expression` | `NetworkErrorRatio() > 0.5` |
|
||||||
|
| `/traefik/backends/backend1/servers/server1/url` | `http://172.17.0.2:80` |
|
||||||
|
| `/traefik/backends/backend1/servers/server1/weight` | `10` |
|
||||||
|
| `/traefik/backends/backend1/servers/server2/url` | `http://172.17.0.3:80` |
|
||||||
|
| `/traefik/backends/backend1/servers/server2/weight` | `1` |
|
||||||
|
| `/traefik/backends/backend1/servers/server2/tags` | `api,helloworld` |
|
||||||
|
|
||||||
|
- backend 2
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|-----------------------------------------------------|------------------------|
|
||||||
|
| `/traefik/backends/backend2/maxconn/amount` | `10` |
|
||||||
|
| `/traefik/backends/backend2/maxconn/extractorfunc` | `request.host` |
|
||||||
|
| `/traefik/backends/backend2/loadbalancer/method` | `drr` |
|
||||||
|
| `/traefik/backends/backend2/servers/server1/url` | `http://172.17.0.4:80` |
|
||||||
|
| `/traefik/backends/backend2/servers/server1/weight` | `1` |
|
||||||
|
| `/traefik/backends/backend2/servers/server2/url` | `http://172.17.0.5:80` |
|
||||||
|
| `/traefik/backends/backend2/servers/server2/weight` | `2` |
|
||||||
|
| `/traefik/backends/backend2/servers/server2/tags` | `web` |
|
||||||
|
|
||||||
|
- frontend 1
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|---------------------------------------------------|-----------------------|
|
||||||
|
| `/traefik/frontends/frontend1/backend` | `backend2` |
|
||||||
|
| `/traefik/frontends/frontend1/routes/test_1/rule` | `Host:test.localhost` |
|
||||||
|
|
||||||
|
- frontend 2
|
||||||
|
|
||||||
|
| Key | Value |
|
||||||
|
|----------------------------------------------------|--------------------|
|
||||||
|
| `/traefik/frontends/frontend2/backend` | `backend1` |
|
||||||
|
| `/traefik/frontends/frontend2/passHostHeader` | `true` |
|
||||||
|
| `/traefik/frontends/frontend2/priority` | `10` |
|
||||||
|
| `/traefik/frontends/frontend2/entrypoints` | `http,https` |
|
||||||
|
| `/traefik/frontends/frontend2/routes/test_2/rule` | `PathPrefix:/test` |
|
||||||
|
|
||||||
|
### Atomic configuration changes
|
||||||
|
|
||||||
|
Træfik can watch the backends/frontends configuration changes and generate its configuration automatically.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
Only backends/frontends rules are dynamic, the rest of the Træfik configuration stay static.
|
||||||
|
|
||||||
|
The [Etcd](https://github.com/coreos/etcd/issues/860) and [Consul](https://github.com/hashicorp/consul/issues/886) backends do not support updating multiple keys atomically.
|
||||||
|
As a result, it may be possible for Træfik to read an intermediate configuration state despite judicious use of the `--providersThrottleDuration` flag.
|
||||||
|
To solve this problem, Træfik supports a special key called `/traefik/alias`.
|
||||||
|
If set, Træfik use the value as an alternative key prefix.
|
||||||
|
|
||||||
|
Given the key structure below, Træfik 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
|
||||||
|
Træfik *will not watch for key changes in the `/traefik_configurations` prefix*. It will only watch for changes in the `/traefik/alias`.
|
||||||
|
Further, if the `/traefik/alias` key is set, all other configuration with `/traefik/backends` or `/traefik/frontends` prefix are ignored.
|
||||||
|
|
||||||
|
## Store configuration in Key-value store
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
Don't forget to [setup the connection between Træfik and Key-value store](/user-guide/kv-config/#launch-trfk).
|
||||||
|
|
||||||
|
The static Træfik configuration in a key-value store can be automatically created and updated, using the [`storeconfig` subcommand](/basics/#commands).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
traefik storeconfig [flags] ...
|
||||||
|
```
|
||||||
|
This command is here only to automate the [process which upload the configuration into the Key-value store](/user-guide/kv-config/#upload-the-configuration-in-the-key-value-store).
|
||||||
|
Træfik will not start but the [static configuration](/basics/#static-trfk-configuration) will be uploaded into the Key-value store.
|
||||||
|
If you configured ACME (Let's Encrypt), your registration account and your certificates will also be uploaded.
|
||||||
|
|
||||||
|
To upload your ACME certificates to the KV store, get your Traefik TOML file and add the new `storage` option in the `acme` section:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[acme]
|
||||||
|
email = "test@traefik.io"
|
||||||
|
storage = "traefik/acme/account" # the key where to store your certificates in the KV store
|
||||||
|
storageFile = "acme.json" # your old certificates store
|
||||||
|
```
|
||||||
|
|
||||||
|
Call `traefik storeconfig` to upload your config in the KV store.
|
||||||
|
Then remove the line `storageFile = "acme.json"` from your TOML config file.
|
||||||
|
|
||||||
|
That's it!
|
||||||
|
|
||||||
|

|
||||||
127
docs/user-guide/marathon.md
Normal file
127
docs/user-guide/marathon.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# Marathon
|
||||||
|
|
||||||
|
This guide explains how to integrate Marathon and operate the cluster in a reliable way from Traefik's standpoint.
|
||||||
|
|
||||||
|
## Host detection
|
||||||
|
|
||||||
|
Marathon offers multiple ways to run (Docker-containerized) applications, the most popular ones being
|
||||||
|
|
||||||
|
- BRIDGE-networked containers with dynamic high ports exposed
|
||||||
|
- HOST-networked containers with host machine ports
|
||||||
|
- containers with dedicated IP addresses ([IP-per-task](https://mesosphere.github.io/marathon/docs/ip-per-task.html)).
|
||||||
|
|
||||||
|
Traefik tries to detect the configured mode and route traffic to the right IP addresses. It is possible to force using task hosts with the `forceTaskHostname` option.
|
||||||
|
|
||||||
|
Given the complexity of the subject, it is possible that the heuristic fails.
|
||||||
|
Apart from filing an issue and waiting for the feature request / bug report to get addressed, one workaround for such situations is to customize the Marathon template file to the individual needs.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
This does _not_ require rebuilding Traefik but only to point the `filename` configuration parameter to a customized version of the `marathon.tmpl` file on Traefik startup.
|
||||||
|
|
||||||
|
## Port detection
|
||||||
|
|
||||||
|
Traefik also attempts to determine the right port (which is a [non-trivial matter in Marathon](https://mesosphere.github.io/marathon/docs/ports.html)).
|
||||||
|
Following is the order by which Traefik tries to identify the port (the first one that yields a positive result will be used):
|
||||||
|
|
||||||
|
1. A arbitrary port specified through the `traefik.port` label.
|
||||||
|
1. The task port (possibly indexed through the `traefik.portIndex` label, otherwise the first one).
|
||||||
|
1. The port from the application's `portDefinitions` field (possibly indexed through the `traefik.portIndex` label, otherwise the first one).
|
||||||
|
1. The port from the application's `ipAddressPerTask` field (possibly indexed through the `traefik.portIndex` label, otherwise the first one).
|
||||||
|
|
||||||
|
## Achieving high availability
|
||||||
|
|
||||||
|
### Scenarios
|
||||||
|
|
||||||
|
There are three scenarios where the availability of a Marathon application could be impaired along with the risk of losing or failing requests:
|
||||||
|
|
||||||
|
- During the startup phase when Traefik already routes requests to the backend even though it has not completed its bootstrapping process yet.
|
||||||
|
- During the shutdown phase when Traefik still routes requests to the backend while the backend is already terminating.
|
||||||
|
- During a failure of the application when Traefik has not yet identified the backend as being erroneous.
|
||||||
|
|
||||||
|
The first two scenarios are common with every rolling upgrade of an application (i.e. a new version release or configuration update).
|
||||||
|
|
||||||
|
The following sub-sections describe how to resolve or mitigate each scenario.
|
||||||
|
|
||||||
|
#### Startup
|
||||||
|
|
||||||
|
It is possible to define [readiness checks](https://mesosphere.github.io/marathon/docs/readiness-checks.html) (available since Marathon version 1.1) per application and have Marathon take these into account during the startup phase.
|
||||||
|
|
||||||
|
The idea is that each application provides an HTTP endpoint that Marathon queries periodically during an ongoing deployment in order to mark the associated readiness check result as successful if and only if the endpoint returns a response within the configured HTTP code range.
|
||||||
|
As long as the check keeps failing, Marathon will not proceed with the deployment (within the configured upgrade strategy bounds).
|
||||||
|
|
||||||
|
Beginning with version 1.4, Traefik respects readiness check results if the Traefik option is set and checks are configured on the applications accordingly.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
Due to the way readiness check results are currently exposed by the Marathon API, ready tasks may be taken into rotation with a small delay.
|
||||||
|
It is on the order of one readiness check timeout interval (as configured on the application specifiation) and guarantees that non-ready tasks do not receive traffic prematurely.
|
||||||
|
|
||||||
|
If readiness checks are not possible, a current mitigation strategy is to enable [retries](/configuration/commons#retry-configuration) and make sure that a sufficient number of healthy application tasks exist so that one retry will likely hit one of those.
|
||||||
|
Apart from its probabilistic nature, the workaround comes at the price of increased latency.
|
||||||
|
|
||||||
|
#### Shutdown
|
||||||
|
|
||||||
|
It is possible to install a [termination handler](https://mesosphere.github.io/marathon/docs/health-checks.html) (available since Marathon version 1.3) with each application whose responsibility it is to delay the shutdown process long enough until the backend has been taken out of load-balancing rotation with reasonable confidence (i.e., Traefik has received an update from the Marathon event bus, recomputes the available Marathon backends, and applies the new configuration).
|
||||||
|
Specifically, each termination handler should install a signal handler listening for a SIGTERM signal and implement the following steps on signal reception:
|
||||||
|
|
||||||
|
1. Disable Keep-Alive HTTP connections.
|
||||||
|
1. Keep accepting HTTP requests for a certain period of time.
|
||||||
|
1. Stop accepting new connections.
|
||||||
|
1. Finish serving any in-flight requests.
|
||||||
|
1. Shut down.
|
||||||
|
|
||||||
|
Traefik already ignores Marathon tasks whose state does not match `TASK_RUNNING`; since terminating tasks transition into the `TASK_KILLING` and eventually `TASK_KILLED` state, there is nothing further that needs to be done on Traefik's end.
|
||||||
|
|
||||||
|
How long HTTP requests should continue to be accepted in step 2 depends on how long Traefik needs to receive and process the Marathon configuration update.
|
||||||
|
Under regular operational conditions, it should be on the order of seconds, with 10 seconds possibly being a good default value.
|
||||||
|
|
||||||
|
Again, configuring Traefik to do retries (as discussed in the previous section) can serve as a decent workaround strategy.
|
||||||
|
Paired with termination handlers, they would cover for those cases where either the termination sequence or Traefik cannot complete their part of the orchestration process in time.
|
||||||
|
|
||||||
|
#### Failure
|
||||||
|
|
||||||
|
A failing application always happens unexpectedly, and hence, it is very difficult or even impossible to rule out the adversal effects categorically.
|
||||||
|
|
||||||
|
Failure reasons vary broadly and could stretch from unacceptable slowness, a task crash, or a network split.
|
||||||
|
|
||||||
|
There are two mitigaton efforts:
|
||||||
|
|
||||||
|
1. Configure [Marathon health checks](https://mesosphere.github.io/marathon/docs/health-checks.html) on each application.
|
||||||
|
1. Configure Traefik health checks (possibly via the `traefik.backend.healthcheck.*` labels) and make sure they probe with proper frequency.
|
||||||
|
|
||||||
|
The Marathon health check makes sure that applications once deemed dysfunctional are being rescheduled to different slaves.
|
||||||
|
However, they might take a while to get triggered and the follow-up processes to complete.
|
||||||
|
|
||||||
|
For that reason, the Treafik health check provides an additional check that responds more rapidly and does not require a configuration reload to happen.
|
||||||
|
Additionally, it protects from cases that the Marathon health check may not be able to cover, such as a network split.
|
||||||
|
|
||||||
|
### (Non-)Alternatives
|
||||||
|
|
||||||
|
There are a few alternatives of varying quality that are frequently asked for.
|
||||||
|
|
||||||
|
The remaining section is going to explore them along with a benefit/cost trade-off.
|
||||||
|
|
||||||
|
#### Reusing Marathon health checks
|
||||||
|
|
||||||
|
It may seem obvious to reuse the Marathon health checks as a signal to Traefik whether an application should be taken into load-balancing rotation or not.
|
||||||
|
|
||||||
|
Apart from the increased latency a failing health check may have, a major problem with this is is that Marathon does not persist the health check results.
|
||||||
|
Consequently, if a master re-election occurs in the Marathon clusters, all health check results will revert to the _unknown_ state, effectively causing all applications inside the cluster to become unavailable and leading to a complete cluster failure.
|
||||||
|
Re-elections do not only happen during regular maintenance work (often requiring rolling upgrades of the Marathon nodes) but also when the Marathon leader fails spontaneously.
|
||||||
|
As such, there is no way to handle this situation deterministically.
|
||||||
|
|
||||||
|
Finally, Marathon health checks are not mandatory (the default is to use the task state as reported by Mesos), so requiring them for Traefik would raise the entry barrier for Marathon users.
|
||||||
|
|
||||||
|
Traefik used to use the health check results as a strict requirement but moved away from it as [users reported the dramatic consequences](https://github.com/containous/traefik/issues/653).
|
||||||
|
If health check results are known to exist, however, they will be used to signal task availability.
|
||||||
|
|
||||||
|
#### Draining
|
||||||
|
|
||||||
|
Another common approach is to let a proxy drain backends that are supposed to shut down.
|
||||||
|
That is, once a backend is supposed to shut down, Traefik would stop forwarding requests.
|
||||||
|
|
||||||
|
On the plus side, this would not require any modifications to the application in question.
|
||||||
|
However, implementing this fully within Traefik seems like a non-trivial undertaking.
|
||||||
|
|
||||||
|
Additionally, the approach is less flexible compared to a custom termination handler since only the latter allows for the implementation of custom termination sequences that go beyond simple request draining (e.g., persisting a snapshot state to disk prior to terminating).
|
||||||
|
|
||||||
|
The feature is currently not implemented; a request for draining in general is at [issue 41](https://github.com/containous/traefik/issues/41).
|
||||||
333
docs/user-guide/swarm-mode.md
Normal file
333
docs/user-guide/swarm-mode.md
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
# Docker Swarm (mode) cluster
|
||||||
|
|
||||||
|
This section explains how to create a multi-host docker cluster with swarm mode using [docker-machine](https://docs.docker.com/machine) and how to deploy Træfik on it.
|
||||||
|
|
||||||
|
The cluster consists of:
|
||||||
|
|
||||||
|
- 3 servers
|
||||||
|
- 1 manager
|
||||||
|
- 2 workers
|
||||||
|
- 1 [overlay](https://docs.docker.com/engine/userguide/networking/dockernetworks/#an-overlay-network) network (multi-host networking)
|
||||||
|
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. You will need to install [docker-machine](https://docs.docker.com/machine/)
|
||||||
|
2. You will need the latest [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
|
||||||
|
|
||||||
|
|
||||||
|
## Cluster provisioning
|
||||||
|
|
||||||
|
First, let's create all the required nodes.
|
||||||
|
It's a shorter version of the [swarm tutorial](https://docs.docker.com/engine/swarm/swarm-tutorial/).
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker-machine create -d virtualbox manager
|
||||||
|
docker-machine create -d virtualbox worker1
|
||||||
|
docker-machine create -d virtualbox worker2
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, let's setup the cluster, in order:
|
||||||
|
|
||||||
|
1. initialize the cluster
|
||||||
|
1. get the token for other host to join
|
||||||
|
1. on both workers, join the cluster with the token
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker-machine ssh manager "docker swarm init \
|
||||||
|
--listen-addr $(docker-machine ip manager) \
|
||||||
|
--advertise-addr $(docker-machine ip manager)"
|
||||||
|
|
||||||
|
export worker_token=$(docker-machine ssh manager "docker swarm \
|
||||||
|
join-token worker -q")
|
||||||
|
|
||||||
|
docker-machine ssh worker1 "docker swarm join \
|
||||||
|
--token=${worker_token} \
|
||||||
|
--listen-addr $(docker-machine ip worker1) \
|
||||||
|
--advertise-addr $(docker-machine ip worker1) \
|
||||||
|
$(docker-machine ip manager)"
|
||||||
|
|
||||||
|
docker-machine ssh worker2 "docker swarm join \
|
||||||
|
--token=${worker_token} \
|
||||||
|
--listen-addr $(docker-machine ip worker2) \
|
||||||
|
--advertise-addr $(docker-machine ip worker2) \
|
||||||
|
$(docker-machine ip manager)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's validate the cluster is up and running.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker-machine ssh manager docker node ls
|
||||||
|
```
|
||||||
|
```
|
||||||
|
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
|
||||||
|
013v16l1sbuwjqcn7ucbu4jwt worker1 Ready Active
|
||||||
|
8buzkquycd17jqjber0mo2gn8 worker2 Ready Active
|
||||||
|
fnpj8ozfc85zvahx2r540xfcf * manager Ready Active Leader
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, let's create a network for Træfik to use.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker-machine ssh manager "docker network create --driver=overlay traefik-net"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Deploy Træfik
|
||||||
|
|
||||||
|
Let's deploy Træfik as a docker service in our cluster.
|
||||||
|
The only requirement for Træfik to work with swarm mode is that it needs to run on a manager node - we are going to use a [constraint](https://docs.docker.com/engine/reference/commandline/service_create/#/specify-service-constraints-constraint) for that.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker-machine ssh manager "docker service create \
|
||||||
|
--name traefik \
|
||||||
|
--constraint=node.role==manager \
|
||||||
|
--publish 80:80 --publish 8080:8080 \
|
||||||
|
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
|
||||||
|
--network traefik-net \
|
||||||
|
traefik \
|
||||||
|
--docker \
|
||||||
|
--docker.swarmmode \
|
||||||
|
--docker.domain=traefik \
|
||||||
|
--docker.watch \
|
||||||
|
--web"
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's explain this command:
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|-----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------|
|
||||||
|
| `--publish 80:80 --publish 8080:8080` | we publish port `80` and `8080` on the cluster. |
|
||||||
|
| `--constraint=node.role==manager` | we ask docker to schedule Træfik on a manager node. |
|
||||||
|
| `--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock` | we bind mount the docker socket where Træfik is scheduled to be able to speak to the daemon. |
|
||||||
|
| `--network traefik-net` | we attach the Træfik service (and thus the underlying container) to the `traefik-net` network. |
|
||||||
|
| `--docker` | enable docker backend, and `--docker.swarmmode` to enable the swarm mode on Træfik. |
|
||||||
|
| `--web` | activate the webUI on port 8080 |
|
||||||
|
|
||||||
|
|
||||||
|
## Deploy your apps
|
||||||
|
|
||||||
|
We can now deploy our app on the cluster, here [whoami](https://github.com/emilevauge/whoami), a simple web server in Go.
|
||||||
|
We start 2 services, on the `traefik-net` network.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker-machine ssh manager "docker service create \
|
||||||
|
--name whoami0 \
|
||||||
|
--label traefik.port=80 \
|
||||||
|
--network traefik-net \
|
||||||
|
emilevauge/whoami"
|
||||||
|
|
||||||
|
docker-machine ssh manager "docker service create \
|
||||||
|
--name whoami1 \
|
||||||
|
--label traefik.port=80 \
|
||||||
|
--network traefik-net \
|
||||||
|
--label traefik.backend.loadbalancer.sticky=true \
|
||||||
|
emilevauge/whoami"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
We set `whoami1` to use sticky sessions (`--label traefik.backend.loadbalancer.stickiness=true`).
|
||||||
|
We'll demonstrate that later.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
If using `docker stack deploy`, there is [a specific way that the labels must be defined in the docker-compose file](https://github.com/containous/traefik/issues/994#issuecomment-269095109).
|
||||||
|
|
||||||
|
Check that everything is scheduled and started:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker-machine ssh manager "docker service ls"
|
||||||
|
```
|
||||||
|
```
|
||||||
|
ID NAME MODE REPLICAS IMAGE PORTS
|
||||||
|
moq3dq4xqv6t traefik replicated 1/1 traefik:latest *:80->80/tcp,*:8080->8080/tcp
|
||||||
|
ysil6oto1wim whoami0 replicated 1/1 emilevauge/whoami:latest
|
||||||
|
z9re2mnl34k4 whoami1 replicated 1/1 emilevauge/whoami:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Access to your apps through Træfik
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -H Host:whoami0.traefik http://$(docker-machine ip manager)
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
|
Hostname: 5b0b3d148359
|
||||||
|
IP: 127.0.0.1
|
||||||
|
IP: 10.0.0.8
|
||||||
|
IP: 10.0.0.4
|
||||||
|
IP: 172.18.0.5
|
||||||
|
GET / HTTP/1.1
|
||||||
|
Host: whoami0.traefik
|
||||||
|
User-Agent: curl/7.55.1
|
||||||
|
Accept: */*
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
X-Forwarded-For: 10.255.0.2
|
||||||
|
X-Forwarded-Host: whoami0.traefik
|
||||||
|
X-Forwarded-Proto: http
|
||||||
|
X-Forwarded-Server: 77fc29c69fe4
|
||||||
|
```
|
||||||
|
```shell
|
||||||
|
curl -H Host:whoami1.traefik http://$(docker-machine ip manager)
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
|
Hostname: 3633163970f6
|
||||||
|
IP: 127.0.0.1
|
||||||
|
IP: 10.0.0.14
|
||||||
|
IP: 10.0.0.6
|
||||||
|
IP: 172.18.0.5
|
||||||
|
GET / HTTP/1.1
|
||||||
|
Host: whoami1.traefik
|
||||||
|
User-Agent: curl/7.55.1
|
||||||
|
Accept: */*
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
X-Forwarded-For: 10.255.0.2
|
||||||
|
X-Forwarded-Host: whoami1.traefik
|
||||||
|
X-Forwarded-Proto: http
|
||||||
|
X-Forwarded-Server: 77fc29c69fe4
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
As Træfik is published, you can access it from any machine and not only the manager.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -H Host:whoami0.traefik http://$(docker-machine ip worker1)
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
|
Hostname: 5b0b3d148359
|
||||||
|
IP: 127.0.0.1
|
||||||
|
IP: 10.0.0.8
|
||||||
|
IP: 10.0.0.4
|
||||||
|
IP: 172.18.0.5
|
||||||
|
GET / HTTP/1.1
|
||||||
|
Host: whoami0.traefik
|
||||||
|
User-Agent: curl/7.55.1
|
||||||
|
Accept: */*
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
X-Forwarded-For: 10.255.0.3
|
||||||
|
X-Forwarded-Host: whoami0.traefik
|
||||||
|
X-Forwarded-Proto: http
|
||||||
|
X-Forwarded-Server: 77fc29c69fe4
|
||||||
|
```
|
||||||
|
```shell
|
||||||
|
curl -H Host:whoami1.traefik http://$(docker-machine ip worker2)
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
|
Hostname: 3633163970f6
|
||||||
|
IP: 127.0.0.1
|
||||||
|
IP: 10.0.0.14
|
||||||
|
IP: 10.0.0.6
|
||||||
|
IP: 172.18.0.5
|
||||||
|
GET / HTTP/1.1
|
||||||
|
Host: whoami1.traefik
|
||||||
|
User-Agent: curl/7.55.1
|
||||||
|
Accept: */*
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
X-Forwarded-For: 10.255.0.4
|
||||||
|
X-Forwarded-Host: whoami1.traefik
|
||||||
|
X-Forwarded-Proto: http
|
||||||
|
X-Forwarded-Server: 77fc29c69fe4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scale both services
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker-machine ssh manager "docker service scale whoami0=5"
|
||||||
|
docker-machine ssh manager "docker service scale whoami1=5"
|
||||||
|
```
|
||||||
|
|
||||||
|
Check that we now have 5 replicas of each `whoami` service:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker-machine ssh manager "docker service ls"
|
||||||
|
```
|
||||||
|
```
|
||||||
|
ID NAME MODE REPLICAS IMAGE PORTS
|
||||||
|
moq3dq4xqv6t traefik replicated 1/1 traefik:latest *:80->80/tcp,*:8080->8080/tcp
|
||||||
|
ysil6oto1wim whoami0 replicated 5/5 emilevauge/whoami:latest
|
||||||
|
z9re2mnl34k4 whoami1 replicated 5/5 emilevauge/whoami:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Access to your `whoami0` through Træfik multiple times.
|
||||||
|
|
||||||
|
Repeat the following command multiple times and note that the Hostname changes each time as Traefik load balances each request against the 5 tasks:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -H Host:whoami0.traefik http://$(docker-machine ip manager)
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
|
Hostname: f3138d15b567
|
||||||
|
IP: 127.0.0.1
|
||||||
|
IP: 10.0.0.5
|
||||||
|
IP: 10.0.0.4
|
||||||
|
IP: 172.18.0.3
|
||||||
|
GET / HTTP/1.1
|
||||||
|
Host: whoami0.traefik
|
||||||
|
User-Agent: curl/7.55.1
|
||||||
|
Accept: */*
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
X-Forwarded-For: 10.255.0.2
|
||||||
|
X-Forwarded-Host: whoami0.traefik
|
||||||
|
X-Forwarded-Proto: http
|
||||||
|
X-Forwarded-Server: 77fc29c69fe4
|
||||||
|
```
|
||||||
|
|
||||||
|
Do the same against `whoami1`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -c cookies.txt -H Host:whoami1.traefik http://$(docker-machine ip manager)
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
|
Hostname: 348e2f7bf432
|
||||||
|
IP: 127.0.0.1
|
||||||
|
IP: 10.0.0.15
|
||||||
|
IP: 10.0.0.6
|
||||||
|
IP: 172.18.0.6
|
||||||
|
GET / HTTP/1.1
|
||||||
|
Host: whoami1.traefik
|
||||||
|
User-Agent: curl/7.55.1
|
||||||
|
Accept: */*
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
X-Forwarded-For: 10.255.0.2
|
||||||
|
X-Forwarded-Host: whoami1.traefik
|
||||||
|
X-Forwarded-Proto: http
|
||||||
|
X-Forwarded-Server: 77fc29c69fe4
|
||||||
|
```
|
||||||
|
|
||||||
|
Because the sticky sessions require cookies to work, we used the `-c cookies.txt` option to store the cookie into a file.
|
||||||
|
The cookie contains the IP of the container to which the session sticks:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cat ./cookies.txt
|
||||||
|
```
|
||||||
|
```
|
||||||
|
# Netscape HTTP Cookie File
|
||||||
|
# https://curl.haxx.se/docs/http-cookies.html
|
||||||
|
# This file was generated by libcurl! Edit at your own risk.
|
||||||
|
|
||||||
|
whoami1.traefik FALSE / FALSE 0 _TRAEFIK_BACKEND http://10.0.0.15:80
|
||||||
|
```
|
||||||
|
|
||||||
|
If you load the cookies file (`-b cookies.txt`) for the next request, you will see that stickiness is maintained:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -b cookies.txt -H Host:whoami1.traefik http://$(docker-machine ip manager)
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
|
Hostname: 348e2f7bf432
|
||||||
|
IP: 127.0.0.1
|
||||||
|
IP: 10.0.0.15
|
||||||
|
IP: 10.0.0.6
|
||||||
|
IP: 172.18.0.6
|
||||||
|
GET / HTTP/1.1
|
||||||
|
Host: whoami1.traefik
|
||||||
|
User-Agent: curl/7.55.1
|
||||||
|
Accept: */*
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
Cookie: _TRAEFIK_BACKEND=http://10.0.0.15:80
|
||||||
|
X-Forwarded-For: 10.255.0.2
|
||||||
|
X-Forwarded-Host: whoami1.traefik
|
||||||
|
X-Forwarded-Proto: http
|
||||||
|
X-Forwarded-Server: 77fc29c69fe4
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
@@ -1,7 +1,8 @@
|
|||||||
# Swarm cluster
|
# 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.
|
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æfik on it.
|
||||||
The cluster will be made of:
|
|
||||||
|
The cluster consists of:
|
||||||
|
|
||||||
- 2 servers
|
- 2 servers
|
||||||
- 1 swarm master
|
- 1 swarm master
|
||||||
@@ -10,24 +11,24 @@ The cluster will be made of:
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
1. You will need to install [docker-machine](https://docs.docker.com/machine/)
|
1. You need to install [docker-machine](https://docs.docker.com/machine/)
|
||||||
2. You will need the latest [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
|
2. You need the latest [VirtualBox](https://www.virtualbox.org/wiki/Downloads)
|
||||||
|
|
||||||
## Cluster provisioning
|
## Cluster provisioning
|
||||||
|
|
||||||
We will first follow [this guide](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) to create the cluster.
|
We first follow [this guide](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) to create the cluster.
|
||||||
|
|
||||||
### Create machine `mh-keystore`
|
### Create machine `mh-keystore`
|
||||||
|
|
||||||
This machine will be the service registry of our cluster.
|
This machine is the service registry of our cluster.
|
||||||
|
|
||||||
```sh
|
```shell
|
||||||
docker-machine create -d virtualbox mh-keystore
|
docker-machine create -d virtualbox mh-keystore
|
||||||
```
|
```
|
||||||
|
|
||||||
Then we install the service registry [Consul](https://consul.io) on this machine:
|
Then we install the service registry [Consul](https://consul.io) on this machine:
|
||||||
|
|
||||||
```sh
|
```shell
|
||||||
eval "$(docker-machine env mh-keystore)"
|
eval "$(docker-machine env mh-keystore)"
|
||||||
docker run -d \
|
docker run -d \
|
||||||
-p "8500:8500" \
|
-p "8500:8500" \
|
||||||
@@ -37,9 +38,9 @@ docker run -d \
|
|||||||
|
|
||||||
### Create machine `mhs-demo0`
|
### Create machine `mhs-demo0`
|
||||||
|
|
||||||
This machine will have a swarm master and a swarm agent on it.
|
This machine is a swarm master and a swarm agent on it.
|
||||||
|
|
||||||
```sh
|
```shell
|
||||||
docker-machine create -d virtualbox \
|
docker-machine create -d virtualbox \
|
||||||
--swarm --swarm-master \
|
--swarm --swarm-master \
|
||||||
--swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \
|
--swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \
|
||||||
@@ -50,9 +51,9 @@ docker-machine create -d virtualbox \
|
|||||||
|
|
||||||
### Create machine `mhs-demo1`
|
### Create machine `mhs-demo1`
|
||||||
|
|
||||||
This machine will have a swarm agent on it.
|
This machine have a swarm agent on it.
|
||||||
|
|
||||||
```sh
|
```shell
|
||||||
docker-machine create -d virtualbox \
|
docker-machine create -d virtualbox \
|
||||||
--swarm \
|
--swarm \
|
||||||
--swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \
|
--swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \
|
||||||
@@ -65,16 +66,16 @@ docker-machine create -d virtualbox \
|
|||||||
|
|
||||||
Create the overlay network on the swarm master:
|
Create the overlay network on the swarm master:
|
||||||
|
|
||||||
```sh
|
```shell
|
||||||
eval $(docker-machine env --swarm mhs-demo0)
|
eval $(docker-machine env --swarm mhs-demo0)
|
||||||
docker network create --driver overlay --subnet=10.0.9.0/24 my-net
|
docker network create --driver overlay --subnet=10.0.9.0/24 my-net
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deploy Træfɪk
|
## Deploy Træfik
|
||||||
|
|
||||||
Deploy Træfɪk:
|
Deploy Træfik:
|
||||||
|
|
||||||
```sh
|
```shell
|
||||||
docker $(docker-machine config mhs-demo0) run \
|
docker $(docker-machine config mhs-demo0) run \
|
||||||
-d \
|
-d \
|
||||||
-p 80:80 -p 8080:8080 \
|
-p 80:80 -p 8080:8080 \
|
||||||
@@ -84,33 +85,36 @@ docker $(docker-machine config mhs-demo0) run \
|
|||||||
-l DEBUG \
|
-l DEBUG \
|
||||||
-c /dev/null \
|
-c /dev/null \
|
||||||
--docker \
|
--docker \
|
||||||
--docker.domain traefik \
|
--docker.domain=traefik \
|
||||||
--docker.endpoint tcp://$(docker-machine ip mhs-demo0):3376 \
|
--docker.endpoint=tcp://$(docker-machine ip mhs-demo0):3376 \
|
||||||
--docker.tls \
|
--docker.tls \
|
||||||
--docker.tls.ca /ssl/ca.pem \
|
--docker.tls.ca=/ssl/ca.pem \
|
||||||
--docker.tls.cert /ssl/server.pem \
|
--docker.tls.cert=/ssl/server.pem \
|
||||||
--docker.tls.key /ssl/server-key.pem \
|
--docker.tls.key=/ssl/server-key.pem \
|
||||||
--docker.tls.insecureSkipVerify \
|
--docker.tls.insecureSkipVerify \
|
||||||
--docker.watch \
|
--docker.watch \
|
||||||
--web
|
--web
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's explain this command:
|
Let's explain this command:
|
||||||
|
|
||||||
- `-p 80:80 -p 8080:8080`: we bind ports 80 and 8080
|
| Option | Description |
|
||||||
- `--net=my-net`: run the container on the network my-net
|
|-------------------------------------------|---------------------------------------------------------------|
|
||||||
- `-v /var/lib/boot2docker/:/ssl`: mount the ssl keys generated by docker-machine
|
| `-p 80:80 -p 8080:8080` | we bind ports 80 and 8080 |
|
||||||
- `-c /dev/null`: empty config file
|
| `--net=my-net` | run the container on the network my-net |
|
||||||
- `--docker`: enable docker backend
|
| `-v /var/lib/boot2docker/:/ssl` | mount the ssl keys generated by docker-machine |
|
||||||
- `--docker.endpoint tcp://172.18.0.1:3376`: connect to the swarm master using the docker_gwbridge network
|
| `-c /dev/null` | empty config file |
|
||||||
- `--docker.tls`: enable TLS using the docker-machine keys
|
| `--docker` | enable docker backend |
|
||||||
- `--web`: activate the webUI on port 8080
|
| `--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
|
## 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`:
|
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
|
```shell
|
||||||
eval $(docker-machine env --swarm mhs-demo0)
|
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=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
|
docker run -d --name=whoami1 --net=my-net --env="constraint:node==mhs-demo1" emilevauge/whoami
|
||||||
@@ -118,18 +122,22 @@ docker run -d --name=whoami1 --net=my-net --env="constraint:node==mhs-demo1" emi
|
|||||||
|
|
||||||
Check that everything is started:
|
Check that everything is started:
|
||||||
|
|
||||||
```sh
|
```shell
|
||||||
docker ps
|
docker ps
|
||||||
|
```
|
||||||
|
```
|
||||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||||
ba2c21488299 emilevauge/whoami "/whoamI" 8 seconds ago Up 9 seconds 80/tcp mhs-demo1/whoami1
|
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
|
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
|
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
|
## Access to your apps through Træfik
|
||||||
|
|
||||||
```sh
|
```shell
|
||||||
curl -H Host:whoami0.traefik http://$(docker-machine ip mhs-demo0)
|
curl -H Host:whoami0.traefik http://$(docker-machine ip mhs-demo0)
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
Hostname: 8147a7746e7a
|
Hostname: 8147a7746e7a
|
||||||
IP: 127.0.0.1
|
IP: 127.0.0.1
|
||||||
IP: ::1
|
IP: ::1
|
||||||
@@ -146,8 +154,12 @@ X-Forwarded-For: 192.168.99.1
|
|||||||
X-Forwarded-Host: 10.0.9.3:80
|
X-Forwarded-Host: 10.0.9.3:80
|
||||||
X-Forwarded-Proto: http
|
X-Forwarded-Proto: http
|
||||||
X-Forwarded-Server: 8fbc39271b4c
|
X-Forwarded-Server: 8fbc39271b4c
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
curl -H Host:whoami1.traefik http://$(docker-machine ip mhs-demo0)
|
curl -H Host:whoami1.traefik http://$(docker-machine ip mhs-demo0)
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
Hostname: ba2c21488299
|
Hostname: ba2c21488299
|
||||||
IP: 127.0.0.1
|
IP: 127.0.0.1
|
||||||
IP: ::1
|
IP: ::1
|
||||||
@@ -166,5 +178,4 @@ X-Forwarded-Proto: http
|
|||||||
X-Forwarded-Server: 8fbc39271b4c
|
X-Forwarded-Server: 8fbc39271b4c
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -19,19 +19,19 @@ address = ":7888"
|
|||||||
################################################################
|
################################################################
|
||||||
# rules
|
# rules
|
||||||
################################################################
|
################################################################
|
||||||
[backends]
|
[backends]
|
||||||
[backends.backend]
|
[backends.backend]
|
||||||
[backends.backend.LoadBalancer]
|
[backends.backend.LoadBalancer]
|
||||||
method = "drr"
|
method = "drr"
|
||||||
[backends.backend.servers.server1]
|
[backends.backend.servers.server1]
|
||||||
url = "http://127.0.0.1:8081"
|
url = "http://127.0.0.1:8081"
|
||||||
[backends.backend.servers.server2]
|
[backends.backend.servers.server2]
|
||||||
url = "http://127.0.0.1:8082"
|
url = "http://127.0.0.1:8082"
|
||||||
[backends.backend.servers.server3]
|
[backends.backend.servers.server3]
|
||||||
url = "http://127.0.0.1:8083"
|
url = "http://127.0.0.1:8083"
|
||||||
[frontends]
|
[frontends]
|
||||||
[frontends.frontend]
|
[frontends.frontend]
|
||||||
backend = "backend"
|
backend = "backend"
|
||||||
passHostHeader = true
|
passHostHeader = true
|
||||||
[frontends.frontend.routes.test]
|
[frontends.frontend.routes.test]
|
||||||
rule = "Path: /test"
|
rule = "Path: /test"
|
||||||
|
|||||||
@@ -19,24 +19,24 @@ address = ":7888"
|
|||||||
################################################################
|
################################################################
|
||||||
# rules
|
# rules
|
||||||
################################################################
|
################################################################
|
||||||
[backends]
|
[backends]
|
||||||
[backends.backend1]
|
[backends.backend1]
|
||||||
[backends.backend1.servers.server1]
|
[backends.backend1.servers.server1]
|
||||||
url = "http://127.0.0.1:8081"
|
url = "http://127.0.0.1:8081"
|
||||||
[backends.backend2]
|
[backends.backend2]
|
||||||
[backends.backend2.LoadBalancer]
|
[backends.backend2.LoadBalancer]
|
||||||
method = "drr"
|
method = "drr"
|
||||||
[backends.backend2.servers.server1]
|
[backends.backend2.servers.server1]
|
||||||
url = "http://127.0.0.1:8082"
|
url = "http://127.0.0.1:8082"
|
||||||
[backends.backend2.servers.server2]
|
[backends.backend2.servers.server2]
|
||||||
url = "http://127.0.0.1:8083"
|
url = "http://127.0.0.1:8083"
|
||||||
[frontends]
|
[frontends]
|
||||||
[frontends.frontend1]
|
[frontends.frontend1]
|
||||||
backend = "backend1"
|
backend = "backend1"
|
||||||
[frontends.frontend1.routes.test_1]
|
[frontends.frontend1.routes.test_1]
|
||||||
rule = "Path: /test1"
|
rule = "Path: /test1"
|
||||||
[frontends.frontend2]
|
[frontends.frontend2]
|
||||||
backend = "backend2"
|
backend = "backend2"
|
||||||
passHostHeader = true
|
passHostHeader = true
|
||||||
[frontends.frontend2.routes.test_2]
|
[frontends.frontend2.routes.test_2]
|
||||||
rule = "Path: /test2"
|
rule = "Path: /test2"
|
||||||
30
examples/acme/Docker_Acme.md
Normal file
30
examples/acme/Docker_Acme.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# ACME Testing environment
|
||||||
|
|
||||||
|
## Objectives
|
||||||
|
|
||||||
|
In our integration ACME tests, we use a simulated Let's Encrypt container based stack named boulder.
|
||||||
|
|
||||||
|
The goal of this directory is to provide to developers a Traefik-boulder full stack environment.
|
||||||
|
This environment may be used in order to quickly test developments on ACME certificates management.
|
||||||
|
|
||||||
|
The provided Boulder stack is based on the environment used during integration tests.
|
||||||
|
|
||||||
|
## Directory content
|
||||||
|
|
||||||
|
* **compose-acme.yml** : Docker-Compose file which contains the description of Traefik and all the boulder stack containers to get,
|
||||||
|
* **acme.toml** : Traefik configuration file used by the Traefik container described above,
|
||||||
|
* **manage_acme_docker_environment.sh** Shell script which does all needed checks and manages the docker-compose environment.
|
||||||
|
|
||||||
|
## Shell script
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
To work fine, boulder needs a domain name, with a related IP and storage file. The shell script allows to check the environment before launching the Docker environment with the rights parameters and to managing this environment.
|
||||||
|
|
||||||
|
### Use
|
||||||
|
|
||||||
|
The script **manage_acme_docker_environment.sh** requires one argument. This argument can have 3 values :
|
||||||
|
|
||||||
|
* **--start** : Check environment and launch a new Docker environment.
|
||||||
|
* **--stop** : Stop and delete the current Docker environment.
|
||||||
|
* **--restart--** : Concatenate **--stop** and **--start** actions.
|
||||||
33
examples/acme/acme.toml
Normal file
33
examples/acme/acme.toml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
logLevel = "DEBUG"
|
||||||
|
|
||||||
|
defaultEntryPoints = ["http", "https"]
|
||||||
|
|
||||||
|
[entryPoints]
|
||||||
|
[entryPoints.http]
|
||||||
|
address = ":80"
|
||||||
|
[entryPoints.http.redirect]
|
||||||
|
entryPoint = "https"
|
||||||
|
[entryPoints.https]
|
||||||
|
address = ":443"
|
||||||
|
[entryPoints.https.tls]
|
||||||
|
|
||||||
|
|
||||||
|
[acme]
|
||||||
|
email = "test@traefik.io"
|
||||||
|
storage = "/etc/traefik/conf/acme.json"
|
||||||
|
entryPoint = "https"
|
||||||
|
onDemand = false
|
||||||
|
OnHostRule = true
|
||||||
|
caServer = "http://traefik.localhost.com:4000/directory"
|
||||||
|
|
||||||
|
|
||||||
|
[web]
|
||||||
|
address = ":8080"
|
||||||
|
|
||||||
|
[docker]
|
||||||
|
endpoint = "unix:///var/run/docker.sock"
|
||||||
|
domain = "traefik.localhost.com"
|
||||||
|
watch = true
|
||||||
|
exposedbydefault = false
|
||||||
|
|
||||||
|
|
||||||
89
examples/acme/compose-acme.yml
Normal file
89
examples/acme/compose-acme.yml
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
version: "2"
|
||||||
|
|
||||||
|
# IP_HOST : Docker host IP (not 127.0.0.1)
|
||||||
|
|
||||||
|
services :
|
||||||
|
boulder:
|
||||||
|
image: containous/boulder:release
|
||||||
|
environment:
|
||||||
|
FAKE_DNS: $IP_HOST
|
||||||
|
PKCS11_PROXY_SOCKET: tcp://boulder-hsm:5657
|
||||||
|
extra_hosts:
|
||||||
|
- le.wtf:127.0.0.1
|
||||||
|
- boulder:127.0.0.1
|
||||||
|
ports:
|
||||||
|
- 4000:4000 # ACME
|
||||||
|
- 4002:4002 # OCSP
|
||||||
|
- 4003:4003 # OCSP
|
||||||
|
- 4500:4500 # ct-test-srv
|
||||||
|
- 8000:8000 # debug ports
|
||||||
|
- 8001:8001
|
||||||
|
- 8002:8002
|
||||||
|
- 8003:8003
|
||||||
|
- 8004:8004
|
||||||
|
- 8055:8055 # dns-test-srv updates
|
||||||
|
- 9380:9380 # mail-test-srv
|
||||||
|
- 9381:9381 # mail-test-srv
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- bhsm
|
||||||
|
- bmysql
|
||||||
|
- brabbitmq
|
||||||
|
|
||||||
|
bhsm:
|
||||||
|
image: letsencrypt/boulder-tools:2016-11-02
|
||||||
|
hostname: boulder-hsm
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases:
|
||||||
|
- boulder-hsm
|
||||||
|
environment:
|
||||||
|
PKCS11_DAEMON_SOCKET: tcp://0.0.0.0:5657
|
||||||
|
command: /usr/local/bin/pkcs11-daemon /usr/lib/softhsm/libsofthsm.so
|
||||||
|
expose:
|
||||||
|
- 5657
|
||||||
|
bmysql:
|
||||||
|
image: mariadb:10.1
|
||||||
|
hostname: boulder-mysql
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases:
|
||||||
|
- boulder-mysql
|
||||||
|
environment:
|
||||||
|
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
|
||||||
|
|
||||||
|
brabbitmq:
|
||||||
|
image: rabbitmq:3-alpine
|
||||||
|
hostname: boulder-rabbitmq
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases:
|
||||||
|
- boulder-rabbitmq
|
||||||
|
environment:
|
||||||
|
RABBITMQ_NODE_IP_ADDRESS: "0.0.0.0"
|
||||||
|
|
||||||
|
traefik:
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
image: containous/traefik:latest
|
||||||
|
command: --configFile=/etc/traefik/conf/acme.toml
|
||||||
|
restart: unless-stopped
|
||||||
|
extra_hosts:
|
||||||
|
- traefik.localhost.com:$IP_HOST
|
||||||
|
volumes:
|
||||||
|
- "./acme.toml:/etc/traefik/conf/acme.toml:ro"
|
||||||
|
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||||
|
- "./acme.json:/etc/traefik/conf/acme.json:rw"
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
- "5001:443" # Needed for SNI challenge
|
||||||
|
expose:
|
||||||
|
- "8080"
|
||||||
|
labels:
|
||||||
|
- "traefik.port=8080"
|
||||||
|
- "traefik.backend=traefikception"
|
||||||
|
- "traefik.frontend.rule=Host:traefik.localhost.com"
|
||||||
|
- "traefik.enable=true"
|
||||||
|
depends_on:
|
||||||
|
- boulder
|
||||||
101
examples/acme/manage_acme_docker_environment.sh
Executable file
101
examples/acme/manage_acme_docker_environment.sh
Executable file
@@ -0,0 +1,101 @@
|
|||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
# Initialize variables
|
||||||
|
readonly traefik_url="traefik.localhost.com"
|
||||||
|
readonly basedir=$(dirname $0)
|
||||||
|
readonly doc_file=$basedir"/compose-acme.yml"
|
||||||
|
|
||||||
|
# Stop and remove Docker environment
|
||||||
|
down_environment() {
|
||||||
|
echo "STOP Docker environment"
|
||||||
|
! docker-compose -f $doc_file down -v &>/dev/null && \
|
||||||
|
echo "[ERROR] Impossible to stop the Docker environment" && exit 11
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create and start Docker-compose environment or subpart of its services (if services are listed)
|
||||||
|
# $@ : List of services to start (optional)
|
||||||
|
up_environment() {
|
||||||
|
echo "START Docker environment"
|
||||||
|
! docker-compose -f $doc_file up -d $@ &>/dev/null && \
|
||||||
|
echo "[ERROR] Impossible to start Docker environment" && exit 21
|
||||||
|
}
|
||||||
|
|
||||||
|
# Init the environment : get IP address and create needed files
|
||||||
|
init_environment() {
|
||||||
|
for netw in $(ip addr show | grep -v "LOOPBACK" | grep -v docker | grep -oE "^[0-9]{1}: .*:" | cut -d ':' -f2); do
|
||||||
|
ip_addr=$(ip addr show $netw | grep -E "inet " | grep -Eo "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | head -n 1)
|
||||||
|
[[ ! -z $ip_addr ]] && break
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -z $ip_addr ]] && \
|
||||||
|
echo "[ERROR] Impossible to find an IP address for the Docker host" && exit 31
|
||||||
|
|
||||||
|
# The $traefik_url entry must exist into /etc/hosts file
|
||||||
|
# It has to refer to the $ip_addr IP address
|
||||||
|
[[ $(cat /etc/hosts | grep $traefik_url | grep -vE "^#" | grep -oE "([0-9]+(\.)?){4}") != $ip_addr ]] && \
|
||||||
|
echo "[ERROR] Domain ${traefik_url} has to refer to ${ip_addr} into /etc/hosts file." && exit 32
|
||||||
|
# Export IP_HOST to use it in the DOcker COmpose file
|
||||||
|
export IP_HOST=$ip_addr
|
||||||
|
|
||||||
|
echo "CREATE empty acme.json file"
|
||||||
|
rm -f $basedir/acme.json && \
|
||||||
|
touch $basedir/acme.json && \
|
||||||
|
chmod 600 $basedir/acme.json # Needed for ACME
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start all the environement
|
||||||
|
start() {
|
||||||
|
init_environment
|
||||||
|
echo "Start boulder environment"
|
||||||
|
up_environment bmysql brabbitmq bhsm boulder
|
||||||
|
waiting_counter=12
|
||||||
|
# Not start Traefik if boulder is not started
|
||||||
|
echo "WAIT for boulder..."
|
||||||
|
while [[ -z $(curl -s http://$traefik_url:4000/directory) ]]; do
|
||||||
|
sleep 5
|
||||||
|
let waiting_counter-=1
|
||||||
|
if [[ $waiting_counter -eq 0 ]]; then
|
||||||
|
echo "[ERROR] Impossible to start boulder container in the allowed time, the Docker environment will be stopped"
|
||||||
|
down_environment
|
||||||
|
exit 41
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "START Traefik container"
|
||||||
|
up_environment traefik
|
||||||
|
}
|
||||||
|
|
||||||
|
# Script usage
|
||||||
|
show_usage() {
|
||||||
|
echo
|
||||||
|
echo "USAGE : manage_acme_docker_environment.sh [--start|--stop|--restart]"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main method
|
||||||
|
# $@ All parameters given
|
||||||
|
main() {
|
||||||
|
|
||||||
|
[[ $# -ne 1 ]] && show_usage && exit 1
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
"--start")
|
||||||
|
# Start boulder environment
|
||||||
|
start
|
||||||
|
echo "ENVIRONMENT SUCCESSFULLY STARTED"
|
||||||
|
;;
|
||||||
|
"--stop")
|
||||||
|
! down_environment
|
||||||
|
echo "ENVIRONMENT SUCCESSFULLY STOPPED"
|
||||||
|
;;
|
||||||
|
"--restart")
|
||||||
|
down_environment
|
||||||
|
start
|
||||||
|
echo "ENVIRONMENT SUCCESSFULLY STARTED"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
show_usage && exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main $@
|
||||||
@@ -1,21 +1,25 @@
|
|||||||
consul:
|
version: '2'
|
||||||
image: progrium/consul
|
services:
|
||||||
command: -server -bootstrap -advertise 12.0.0.254 -log-level debug -ui-dir /ui
|
consul:
|
||||||
ports:
|
image: progrium/consul
|
||||||
- "8400:8400"
|
command: -server -bootstrap -advertise 12.0.0.254 -log-level debug -ui-dir /ui
|
||||||
- "8500:8500"
|
ports:
|
||||||
- "8600:53/udp"
|
- "8400:8400"
|
||||||
expose:
|
- "8500:8500"
|
||||||
- "8300"
|
- "8600:53/udp"
|
||||||
- "8301"
|
expose:
|
||||||
- "8301/udp"
|
- "8300"
|
||||||
- "8302"
|
- "8301"
|
||||||
- "8302/udp"
|
- "8301/udp"
|
||||||
|
- "8302"
|
||||||
|
- "8302/udp"
|
||||||
|
|
||||||
registrator:
|
registrator:
|
||||||
image: gliderlabs/registrator:master
|
depends_on:
|
||||||
command: -internal consulkv://consul:8500/traefik
|
- consul
|
||||||
volumes:
|
image: gliderlabs/registrator:master
|
||||||
- /var/run/docker.sock:/tmp/docker.sock
|
command: -internal consul://consul:8500
|
||||||
links:
|
volumes:
|
||||||
- consul
|
- /var/run/docker.sock:/tmp/docker.sock
|
||||||
|
links:
|
||||||
|
- consul
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
etcd:
|
etcd:
|
||||||
image: gcr.io/google_containers/etcd:2.2.1
|
image: gcr.io/google_containers/etcd:2.2.1
|
||||||
net: host
|
net: host
|
||||||
command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:4001', '--bind-addr=0.0.0.0:4001', '--data-dir=/var/etcd/data']
|
command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:2379', '--bind-addr=0.0.0.0:2379', '--data-dir=/var/etcd/data']
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
# etcd:
|
|
||||||
# image: gcr.io/google_containers/etcd:2.2.1
|
|
||||||
# net: host
|
|
||||||
# command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:4001', '--bind-addr=0.0.0.0:4001', '--data-dir=/var/etcd/data']
|
|
||||||
|
|
||||||
kubelet:
|
kubelet:
|
||||||
image: gcr.io/google_containers/hyperkube-amd64:v1.2.2
|
image: gcr.io/google_containers/hyperkube-amd64:v1.5.2
|
||||||
privileged: true
|
privileged: true
|
||||||
pid: host
|
pid: host
|
||||||
net : host
|
net : host
|
||||||
volumes:
|
volumes:
|
||||||
- /:/rootfs:ro
|
- /sys:/sys:rw
|
||||||
- /sys:/sys:ro
|
|
||||||
- /var/lib/docker/:/var/lib/docker:rw
|
- /var/lib/docker/:/var/lib/docker:rw
|
||||||
- /var/lib/kubelet/:/var/lib/kubelet:rw
|
- /var/lib/kubelet/:/var/lib/kubelet:rw,shared
|
||||||
- /var/run:/var/run: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']
|
command: ['/hyperkube', 'kubelet', '--hostname-override=127.0.0.1', '--api-servers=http://localhost:8080', '--config=/etc/kubernetes/manifests', '--allow-privileged=true', '--v=2', '--cluster-dns=10.0.0.10', '--cluster-domain=cluster.local']
|
||||||
|
|||||||
@@ -1,52 +1,59 @@
|
|||||||
zk:
|
|
||||||
image: bobrik/zookeeper
|
|
||||||
net: host
|
|
||||||
environment:
|
|
||||||
ZK_CONFIG: tickTime=2000,initLimit=10,syncLimit=5,maxClientCnxns=128,forceSync=no,clientPort=2181
|
|
||||||
ZK_ID: 1
|
|
||||||
|
|
||||||
master:
|
version: '2'
|
||||||
image: mesosphere/mesos-master:0.28.1-2.0.20.ubuntu1404
|
services:
|
||||||
net: host
|
zookeeper:
|
||||||
environment:
|
image: netflixoss/exhibitor:1.5.2
|
||||||
MESOS_ZK: zk://127.0.0.1:2181/mesos
|
hostname: zookeeper
|
||||||
MESOS_HOSTNAME: 127.0.0.1
|
ports:
|
||||||
MESOS_IP: 127.0.0.1
|
- "2181:2181"
|
||||||
MESOS_QUORUM: 1
|
mesos-master:
|
||||||
MESOS_CLUSTER: docker-compose
|
image: mesosphere/marathon:v1.2.0-RC6
|
||||||
MESOS_WORK_DIR: /var/lib/mesos
|
hostname: mesos-master
|
||||||
|
entrypoint: [ "mesos-master" ]
|
||||||
slave:
|
ports:
|
||||||
image: mesosphere/mesos-slave:0.28.1-2.0.20.ubuntu1404
|
- "5050:5050"
|
||||||
net: host
|
links:
|
||||||
pid: host
|
- zookeeper
|
||||||
privileged: true
|
environment:
|
||||||
environment:
|
- MESOS_CLUSTER=local
|
||||||
MESOS_MASTER: zk://127.0.0.1:2181/mesos
|
- MESOS_HOSTNAME=mesos-master.docker
|
||||||
MESOS_HOSTNAME: 127.0.0.1
|
- MESOS_LOG_DIR=/var/log
|
||||||
MESOS_IP: 127.0.0.1
|
- MESOS_WORK_DIR=/var/lib/mesos
|
||||||
MESOS_CONTAINERIZERS: docker,mesos
|
- MESOS_QUORUM=1
|
||||||
volumes:
|
- MESOS_ZK=zk://zookeeper:2181/mesos
|
||||||
- /sys/fs/cgroup:/sys/fs/cgroup
|
mesos-slave:
|
||||||
- /usr/bin/docker:/usr/bin/docker:ro
|
image: mesosphere/mesos-slave-dind:0.2.4_mesos-0.27.2_docker-1.8.2_ubuntu-14.04.4
|
||||||
- /usr/lib/x86_64-linux-gnu/libapparmor.so.1:/usr/lib/x86_64-linux-gnu/libapparmor.so.1:ro
|
entrypoint:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- mesos-slave
|
||||||
- /lib/x86_64-linux-gnu/libsystemd-journal.so.0:/lib/x86_64-linux-gnu/libsystemd-journal.so.0
|
privileged: true
|
||||||
|
hostname: mesos-slave
|
||||||
marathon:
|
ports:
|
||||||
image: mesosphere/marathon:v1.1.1
|
- "5051:5051"
|
||||||
net: host
|
links:
|
||||||
environment:
|
- zookeeper
|
||||||
MARATHON_MASTER: zk://127.0.0.1:2181/mesos
|
- mesos-master
|
||||||
MARATHON_ZK: zk://127.0.0.1:2181/marathon
|
environment:
|
||||||
MARATHON_HOSTNAME: 127.0.0.1
|
- MESOS_CONTAINERIZERS=docker,mesos
|
||||||
command: --event_subscriber http_callback
|
- MESOS_ISOLATOR=cgroups/cpu,cgroups/mem
|
||||||
|
- MESOS_LOG_DIR=/var/log
|
||||||
traefik:
|
- MESOS_MASTER=zk://zookeeper:2181/mesos
|
||||||
image: containous/traefik
|
- MESOS_PORT=5051
|
||||||
command: -c /dev/null --web --logLevel=DEBUG --marathon --marathon.domain marathon.localhost --marathon.endpoint http://172.17.0.1:8080 --marathon.watch
|
- MESOS_WORK_DIR=/var/lib/mesos
|
||||||
ports:
|
- MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins
|
||||||
- "8000:80"
|
- MESOS_EXECUTOR_SHUTDOWN_GRACE_PERIOD=90secs
|
||||||
- "8081:8080"
|
- MESOS_DOCKER_STOP_TIMEOUT=60secs
|
||||||
volumes:
|
- MESOS_RESOURCES=cpus:2;mem:2048;disk:20480;ports(*):[12000-12999]
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
volumes:
|
||||||
|
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||||
|
marathon:
|
||||||
|
image: mesosphere/marathon:v1.2.0-RC6
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
links:
|
||||||
|
- zookeeper
|
||||||
|
- mesos-master
|
||||||
|
extra_hosts:
|
||||||
|
- "mesos-slave:172.17.0.1"
|
||||||
|
environment:
|
||||||
|
- MARATHON_ZK=zk://zookeeper:2181/marathon
|
||||||
|
- MARATHON_MASTER=zk://zookeeper:2181/mesos
|
||||||
|
|||||||
7
examples/compose-rancher.yml
Normal file
7
examples/compose-rancher.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
traefik:
|
||||||
|
image: traefik
|
||||||
|
command: --web --rancher --rancher.domain=rancher.localhost --rancher.endpoint=http://example.com --rancher.accesskey=XXXXXXX --rancher.secretkey=YYYYYY --logLevel=DEBUG
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
- "8080:8080"
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# backend 1
|
# backend 1
|
||||||
curl -i -H "Accept: application/json" -X PUT -d value="NetworkErrorRatio() > 0.5" http://localhost:4001/v2/keys/traefik/backends/backend1/circuitbreaker/expression
|
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:4001/v2/keys/traefik/backends/backend1/servers/server1/url
|
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:4001/v2/keys/traefik/backends/backend1/servers/server1/weight
|
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:4001/v2/keys/traefik/backends/backend1/servers/server2/url
|
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:4001/v2/keys/traefik/backends/backend1/servers/server2/weight
|
curl -i -H "Accept: application/json" -X PUT -d value="1" http://localhost:2379/v2/keys/traefik/backends/backend1/servers/server2/weight
|
||||||
|
|
||||||
# backend 2
|
# backend 2
|
||||||
curl -i -H "Accept: application/json" -X PUT -d value="drr" http://localhost:4001/v2/keys/traefik/backends/backend2/loadbalancer/method
|
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:4001/v2/keys/traefik/backends/backend2/servers/server1/url
|
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:4001/v2/keys/traefik/backends/backend2/servers/server1/weight
|
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:4001/v2/keys/traefik/backends/backend2/servers/server2/url
|
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:4001/v2/keys/traefik/backends/backend2/servers/server2/weight
|
curl -i -H "Accept: application/json" -X PUT -d value="2" http://localhost:2379/v2/keys/traefik/backends/backend2/servers/server2/weight
|
||||||
|
|
||||||
# frontend 1
|
# frontend 1
|
||||||
curl -i -H "Accept: application/json" -X PUT -d value="backend2" http://localhost:4001/v2/keys/traefik/frontends/frontend1/backend
|
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:4001/v2/keys/traefik/frontends/frontend1/entrypoints
|
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:4001/v2/keys/traefik/frontends/frontend1/routes/test_1/rule
|
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
|
# frontend 2
|
||||||
curl -i -H "Accept: application/json" -X PUT -d value="backend1" http://localhost:4001/v2/keys/traefik/frontends/frontend2/backend
|
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:4001/v2/keys/traefik/frontends/frontend2/entrypoints
|
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:4001/v2/keys/traefik/frontends/frontend2/routes/test_2/rule
|
curl -i -H "Accept: application/json" -X PUT -d value="Path:/test" http://localhost:2379/v2/keys/traefik/frontends/frontend2/routes/test_2/rule
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
# 3 Services for the 3 endpoints of the Ingress
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: service1
|
|
||||||
labels:
|
|
||||||
app: whoami
|
|
||||||
spec:
|
|
||||||
type: NodePort
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
nodePort: 30283
|
|
||||||
targetPort: 80
|
|
||||||
protocol: TCP
|
|
||||||
name: https
|
|
||||||
selector:
|
|
||||||
app: whoami
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: service2
|
|
||||||
labels:
|
|
||||||
app: whoami
|
|
||||||
spec:
|
|
||||||
type: NodePort
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
nodePort: 30284
|
|
||||||
targetPort: 80
|
|
||||||
protocol: TCP
|
|
||||||
name: http
|
|
||||||
selector:
|
|
||||||
app: whoami
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: service3
|
|
||||||
labels:
|
|
||||||
app: whoami
|
|
||||||
spec:
|
|
||||||
type: NodePort
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
nodePort: 30285
|
|
||||||
targetPort: 80
|
|
||||||
protocol: TCP
|
|
||||||
name: http
|
|
||||||
selector:
|
|
||||||
app: whoami
|
|
||||||
---
|
|
||||||
# A single RC matching all Services
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ReplicationController
|
|
||||||
metadata:
|
|
||||||
name: whoami
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: whoami
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: whoami
|
|
||||||
image: emilevauge/whoami
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
---
|
|
||||||
# An Ingress with 2 hosts and 3 endpoints
|
|
||||||
apiVersion: extensions/v1beta1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: whoami-ingress
|
|
||||||
spec:
|
|
||||||
rules:
|
|
||||||
- host: foo.localhost
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /bar
|
|
||||||
backend:
|
|
||||||
serviceName: service1
|
|
||||||
servicePort: 80
|
|
||||||
- host: bar.localhost
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- backend:
|
|
||||||
serviceName: service2
|
|
||||||
servicePort: 80
|
|
||||||
- backend:
|
|
||||||
serviceName: service3
|
|
||||||
servicePort: 80
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user