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

Compare commits

..

10 Commits

Author SHA1 Message Date
Emile Vauge
51f2433ba5 Merge pull request #695 from containous/prepare-release-v1.0.3
Prepare release v1.0.3
2016-09-22 15:00:43 +02:00
Emile Vauge
df710fc89e Prepare release v1.0.3
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-22 14:15:53 +02:00
Emile Vauge
cb8a8b5cbb Merge pull request #693 from containous/fix-health-race
Fix health race
2016-09-22 14:09:20 +02:00
Emile Vauge
7dfcdcec10 Fix health race
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-09-22 13:47:06 +02:00
Emile Vauge
35c74ba56c Merge pull request #585 from containous/prepare-release-1.0.2
Prepare release v1.0.2
2016-08-02 19:20:43 +02:00
Emile Vauge
89fc0d2d0e Prepare release v1.0.2
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-08-02 13:47:03 +02:00
Emile Vauge
5306981923 Merge pull request #582 from containous/fix-acme-tos
Fix ACME TOS
2016-08-02 13:41:05 +02:00
Emile Vauge
b466413b11 Fix ACME TOS
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-08-02 12:30:05 +02:00
Emile Vauge
2c411767de Merge pull request #584 from containous/bump-oxy-version
Bump oxy version, fix streaming
2016-08-02 12:23:37 +02:00
Emile Vauge
54e80492bd Bump oxy version, fix streaming
Signed-off-by: Emile Vauge <emile@vauge.com>
2016-08-02 10:17:18 +02:00
1519 changed files with 28442 additions and 181682 deletions

View File

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

1
.gitattributes vendored
View File

@@ -1 +0,0 @@
# vendor/github.com/go-acme/lego/providers/dns/cloudxns/cloudxns.go eol=crlf

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

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

View File

@@ -1,77 +0,0 @@
<!-- PLEASE FOLLOW THE ISSUE TEMPLATE TO HELP TRIAGE AND SUPPORT! -->
### Do you want to request a *feature* or report a *bug*?
<!--
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, please refer to one of the following:
- the Traefik community forum: https://community.containo.us/
-->
Bug
<!--
The configurations between 1.X and 2.X are NOT compatible.
Please have a look here https://doc.traefik.io/traefik/getting-started/configuration-overview/.
-->
### What did you do?
<!--
HOW TO WRITE A GOOD BUG REPORT?
- Respect the issue template as much as possible.
- The title should be short and descriptive.
- Explain the conditions which led you to report this issue: the context.
- The context should lead to something, an idea or a problem that youre 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?_)
<!--
`latest` is not considered as a valid version.
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 level (`--log.level=DEBUG` switch)
```
(paste your output here)
```

View File

@@ -1,82 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
---
<!-- PLEASE FOLLOW THE ISSUE TEMPLATE TO HELP TRIAGE AND SUPPORT! -->
### Do you want to request a *feature* or report a *bug*?
<!--
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, please refer to one of the following:
- the Traefik community forum: https://community.containo.us/
-->
Bug
<!--
The configurations between 1.X and 2.X are NOT compatible.
Please have a look here https://doc.traefik.io/traefik/getting-started/configuration-overview/.
-->
### What did you do?
<!--
HOW TO WRITE A GOOD BUG REPORT?
- Respect the issue template as much as possible.
- The title should be short and descriptive.
- Explain the conditions which led you to report this issue: the context.
- The context should lead to something, an idea or a problem that youre 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?_)
<!--
`latest` is not considered as a valid version.
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 level (`--log.level=DEBUG` switch)
```
(paste your output here)
```

View File

@@ -1,35 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
---
<!-- PLEASE FOLLOW THE ISSUE TEMPLATE TO HELP TRIAGE AND SUPPORT! -->
### Do you want to request a *feature* or report a *bug*?
<!--
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, please refer to one of the following:
- the Traefik community forum: https://community.containo.us/
-->
Feature
### What did you expect to see?
<!--
HOW TO WRITE A GOOD ISSUE?
- Respect the issue template as much as possible.
- The title should be short and descriptive.
- Explain the conditions which led you to report this issue: the context.
- The context should lead to something, an idea or a problem that youre 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
-->

View File

@@ -1,37 +0,0 @@
<!--
PLEASE READ THIS MESSAGE.
Documentation fixes or enhancements:
- for Traefik v1: use branch v1.7
- for Traefik v2: use branch v2.3
Bug fixes:
- for Traefik v1: use branch v1.7
- for Traefik v2: use branch v2.3
Enhancements:
- for Traefik v1: we only accept bug fixes
- for Traefik v2: use branch master
HOW TO WRITE A GOOD PULL REQUEST? https://doc.traefik.io/traefik/contributing/submitting-pull-requests/
-->
### What does this PR do?
<!-- A brief description of the change being made with this pull request. -->
### Motivation
<!-- What inspired you to submit this pull request? -->
### More
- [ ] Added/updated tests
- [ ] Added/updated documentation
### Additional Notes
<!-- Anything else we should know when reviewing? -->

View File

@@ -1,7 +0,0 @@
### What does this PR do?
Merge v{{.Version}} into master
### Motivation
Be sync.

View File

@@ -1,7 +0,0 @@
### What does this PR do?
Prepare release v{{.Version}}.
### Motivation
Create a new release.

29
.gitignore vendored
View File

@@ -1,20 +1,15 @@
.idea/
.intellij/
*.iml
.vscode/
.DS_Store
/dist
/webui/.tmp/
/site/
/docs/site/
/static/
/autogen/
/traefik
/traefik.toml
/traefik.yml
gen.go
.idea
.intellij
log
*.iml
traefik
traefik.toml
*.test
vendor/
static/
.vscode/
site/
*.log
*.exe
cover.out
vendor/
plugins-storage/
traefik_changelog.md

View File

@@ -1,116 +0,0 @@
[run]
timeout = "10m"
skip-files = []
skip-dirs = [
"pkg/provider/kubernetes/crd/generated/",
]
[linters-settings]
[linters-settings.govet]
check-shadowing = false
[linters-settings.golint]
min-confidence = 0.0
[linters-settings.gocyclo]
min-complexity = 14.0
[linters-settings.maligned]
suggest-new = true
[linters-settings.goconst]
min-len = 3.0
min-occurrences = 4.0
[linters-settings.misspell]
locale = "US"
[linters-settings.funlen]
lines = 230 # default 60
statements = 120 # default 40
[linters]
enable-all = true
disable = [
"gocyclo", # FIXME must be fixed
"gosec",
"dupl",
"maligned",
"lll",
"unparam",
"prealloc",
"scopelint",
"gochecknoinits",
"gochecknoglobals",
"godox",
"gocognit",
"bodyclose", # Too many false-positive and panics.
"wsl", # Too strict
"gomnd", # Too strict
"stylecheck", # skip because report issues related to some generated files.
"testpackage", # Too strict
"goerr113", # Too strict
"nestif", # Too many false-positive.
"noctx", # Too strict
"exhaustive", # Too strict
"nlreturn", # Too strict
]
[issues]
exclude-use-default = false
max-per-linter = 0
max-same-issues = 0
exclude = [
"SA1019: http.CloseNotifier is deprecated: the CloseNotifier interface predates Go's context package. New code should use Request.Context instead.", # FIXME must be fixed
"Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*printf?|os\\.(Un)?Setenv). is not checked",
"should have a package comment, unless it's in another file for this package",
]
[[issues.exclude-rules]]
path = "(.+)_test.go"
linters = ["goconst", "funlen", "godot"]
[[issues.exclude-rules]]
path = "integration/.+_test.go"
text = "Error return value of `cmd\\.Process\\.Kill` is not checked"
[[issues.exclude-rules]]
path = "integration/(consul_catalog_test|constraint_test).go"
text = "Error return value of `(s.deregisterService|s.deregisterAgentService)` is not checked"
[[issues.exclude-rules]]
path = "integration/grpc_test.go"
text = "Error return value of `closer` is not checked"
[[issues.exclude-rules]]
path = "pkg/h2c/h2c.go"
text = "Error return value of `rw.Write` is not checked"
[[issues.exclude-rules]]
path = "pkg/middlewares/recovery/recovery.go"
text = "`logger` can be `github.com/stretchr/testify/assert.TestingT`"
[[issues.exclude-rules]]
path = "pkg/provider/docker/builder_test.go"
text = "(U1000: func )?`(.+)` is unused"
[[issues.exclude-rules]]
path = "pkg/provider/kubernetes/builder_(endpoint|service)_test.go"
text = "(U1000: func )?`(.+)` is unused"
[[issues.exclude-rules]]
path = "pkg/config/parser/.+_test.go"
text = "U1000: field `(foo|fuu)` is unused"
[[issues.exclude-rules]]
path = "pkg/server/service/bufferpool.go"
text = "SA6002: argument should be pointer-like to avoid allocations"
[[issues.exclude-rules]]
path = "cmd/configuration.go"
text = "string `traefik` has (\\d) occurrences, make it a constant"
[[issues.exclude-rules]]
path = "pkg/server/middleware/middlewares.go"
text = "Function 'buildConstructor' has too many statements"
[[issues.exclude-rules]] # FIXME must be fixed
path = "cmd/context.go"
text = "S1000: should use a simple channel send/receive instead of `select` with a single case"
[[issues.exclude-rules]]
path = "pkg/tracing/haystack/logger.go"
linters = ["goprintffuncname"]
[[issues.exclude-rules]]
path = "pkg/tracing/tracing.go"
text = "printf-like formatting function 'SetErrorWithEvent' should be named 'SetErrorWithEventf'"
[[issues.exclude-rules]]
path = "pkg/log/deprecated.go"
linters = ["godot"]

View File

@@ -1,60 +0,0 @@
project_name: traefik
before:
hooks:
- go generate
builds:
- binary: traefik
main: ./cmd/traefik/
env:
- CGO_ENABLED=0
ldflags:
- -s -w -X github.com/traefik/traefik/v2/pkg/version.Version={{.Version}} -X github.com/traefik/traefik/v2/pkg/version.Codename={{.Env.CODENAME}} -X github.com/traefik/traefik/v2/pkg/version.BuildDate={{.Date}}
goos:
- linux
- darwin
- windows
- freebsd
- openbsd
goarch:
- amd64
- 386
- arm
- arm64
- ppc64le
goarm:
- 7
- 6
- 5
ignore:
- goos: darwin
goarch: 386
- goos: openbsd
goarch: arm
- goos: openbsd
goarch: arm64
- goos: freebsd
goarch: arm64
changelog:
skip: true
archives:
- id: traefik
name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
format: tar.gz
format_overrides:
- goos: windows
format: zip
files:
- LICENSE.md
- CHANGELOG.md
checksum:
name_template: "{{ .ProjectName }}_v{{ .Version }}_checksums.txt"
release:
disable: true

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

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

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env bash
set -e
sudo rm -rf static

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env bash
set -e
curl -O https://dl.google.com/go/go"${GO_VERSION}".linux-amd64.tar.gz
tar -xvf go"${GO_VERSION}".linux-amd64.tar.gz
rm -rf go"${GO_VERSION}".linux-amd64.tar.gz
sudo mkdir -p /usr/local/golang/"${GO_VERSION}"/go
sudo mv go /usr/local/golang/"${GO_VERSION}"/
sudo rm /usr/local/bin/go
sudo chmod +x /usr/local/golang/"${GO_VERSION}"/go/bin/go
sudo ln -s /usr/local/golang/"${GO_VERSION}"/go/bin/go /usr/local/bin/go
export GOROOT="/usr/local/golang/${GO_VERSION}/go"
export GOTOOLDIR="/usr/local/golang/${GO_VERSION}/go/pkg/tool/linux_amd64"
go version

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
set -e
if [ -n "$SHOULD_TEST" ]; then ci_retry make pull-images; fi
if [ -n "$SHOULD_TEST" ]; then ci_retry make test-integration; fi

View File

@@ -1,8 +0,0 @@
#!/usr/bin/env bash
set -e
ci_retry make validate
if [ -n "$SHOULD_TEST" ]; then ci_retry make test-unit; fi
if [ -n "$SHOULD_TEST" ]; then make -j"${N_MAKE_JOBS}" crossbinary-default-parallel; fi

View File

@@ -1,35 +0,0 @@
# For personnal CI
# mv /home/runner/workspace/src/github.com/<username>/ /home/runner/workspace/src/github.com/traefik/
# cd /home/runner/workspace/src/github.com/traefik/traefik/
for s in apache2 cassandra elasticsearch memcached mysql mongod postgresql sphinxsearch rethinkdb rabbitmq-server redis-server; do sudo service $s stop; done
sudo swapoff -a
sudo dd if=/dev/zero of=/swapfile bs=1M count=3072
sudo mkswap /swapfile
sudo swapon /swapfile
sudo rm -rf /home/runner/.rbenv
sudo rm -rf /usr/local/golang/{1.4.3,1.5.4,1.6.4,1.7.6,1.8.6,1.9.7,1.10.3,1.11}
#export DOCKER_VERSION=18.06.3
source .semaphoreci/vars
if [ -z "${PULL_REQUEST_NUMBER}" ]; then SHOULD_TEST="-*-"; else TEMP_STORAGE=$(curl --silent https://patch-diff.githubusercontent.com/raw/traefik/traefik/pull/${PULL_REQUEST_NUMBER}.diff | patch --dry-run -p1 -R || true); fi
echo ${SHOULD_TEST}
if [ -n "$TEMP_STORAGE" ]; then SHOULD_TEST=$(echo "$TEMP_STORAGE" | grep -Ev '(.md|.yaml|.yml)' || :); fi
echo ${TEMP_STORAGE}
echo ${SHOULD_TEST}
#if [ -n "$SHOULD_TEST" ]; then sudo -E apt-get -yq update; fi
#if [ -n "$SHOULD_TEST" ]; then sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install docker-ce=${DOCKER_VERSION}*; fi
if [ -n "$SHOULD_TEST" ]; then docker version; fi
export GO_VERSION=1.13
if [ -f "./go.mod" ]; then GO_VERSION="$(grep '^go .*' go.mod | awk '{print $2}')"; export GO_VERSION; fi
#if [ "${GO_VERSION}" == '1.15' ]; then export GO_VERSION=1.15rc2; fi
echo "Selected Go version: ${GO_VERSION}"
if [ -f "./.semaphoreci/golang.sh" ]; then ./.semaphoreci/golang.sh; fi
if [ -f "./.semaphoreci/golang.sh" ]; then export GOROOT="/usr/local/golang/${GO_VERSION}/go"; fi
if [ -f "./.semaphoreci/golang.sh" ]; then export GOTOOLDIR="/usr/local/golang/${GO_VERSION}/go/pkg/tool/linux_amd64"; fi
go version
if [ -f "./go.mod" ]; then export GO111MODULE=on; fi
if [ -f "./go.mod" ]; then export GOPROXY=https://proxy.golang.org; fi
if [ -f "./go.mod" ]; then go mod download; fi
df

View File

@@ -1,36 +0,0 @@
#!/usr/bin/env bash
set -e
export REPO='traefik/traefik'
if VERSION=$(git describe --exact-match --abbrev=0 --tags);
then
export VERSION
else
export VERSION=''
fi
export CODENAME=picodon
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

View File

@@ -1,57 +1,33 @@
sudo: required
dist: trusty
git:
depth: false
services:
- docker
branches:
env:
global:
- REPO=$TRAVIS_REPO_SLUG
- VERSION=$TRAVIS_TAG
- CODENAME=picodon
- GO111MODULE=on
- secure: btt4r13t09gQlHb6gYrvGC2yGCMMHfnp1Mz1RQedc4Mpf/FfT8aE6xmK2a2i9CCvskjrP0t/BFaS4yxIURjnFRn+ugQIEa0pLspB9UJArW/vgOSpIWM9/OQ/fg8z5XuMxN6Md4DL1/iLypMNSageA1x0TRdt89+D1N1dALpg5XRCXLFbC84TLi0gjlFuib9ibPKzEhLT+anCRJ6iZMzeupDSoaCVbAtJMoDvXw4+4AcRZ1+k4MybBLyCib5boaEOt4pTT88mz4Kk0YaMwPVJyg9Qv36VqyUcPS09Yd95LuyVQ4+tZt8Y1ccbIzULsK+sLM3hLCzxlmlpN3dQBlZJiiRtQde0mgGAKyC0P0A1XjuDTywcsa5edB+fTk1Dsewz9xZ9V0NmMz8t+UNZnaSsAPga9i86jULbXUUwMVSzVRc+Xgx02liB/8qI1xYC9FM6ilStt7rn7mF0k3KbiWhcptgeXjO6Lah9FjEKd5w4MXsdUSTi/86rQaLo+kj+XdaTrXCTulKHyRyQEUj+8V1w0oVz7pcGjePHd7y5oU9ByifVQy6sytuFBfRZvugM5bKHo+i0pcWvixrZS42DrzwxZJsspANOvqSe5ifVbvOkfUppQdCBIwptxV5N1b49XPKU3W/w34QJ8xGmKp3TFA7WwVCztriFHjPgiRpB3EG99Bg=
- REPO: $TRAVIS_REPO_SLUG
- VERSION: $TRAVIS_TAG
- CODENAME: reblochon
matrix:
- DOCKER_VERSION=1.9.1
- DOCKER_VERSION=1.10.1
sudo: required
services:
- docker
install:
- sudo service docker stop
- sudo curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/bin/docker
- sudo chmod +x /usr/bin/docker
- sudo service docker start
- sleep 5
- docker version
- pip install --user mkdocs
- pip install --user pymdown-extensions
before_script:
- make validate
- make binary
script:
- echo "Skipping tests... (Tests are executed on SemaphoreCI)"
- if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then make docs; fi
before_deploy:
- >
if ! [ "$BEFORE_DEPLOY_RUN" ]; then
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;
make build-image;
if [ "$TRAVIS_TAG" ]; then
make release-packages;
fi;
curl -sfL https://raw.githubusercontent.com/traefik/structor/master/godownloader.sh | bash -s -- -b "${GOPATH}/bin" ${STRUCTOR_VERSION}
curl -sSfL https://raw.githubusercontent.com/traefik/mixtus/master/godownloader.sh | sh -s -- -b "${GOPATH}/bin" ${MIXTUS_VERSION}
structor -o traefik -r traefik --dockerfile-url="https://raw.githubusercontent.com/traefik/traefik/v1.7/docs.Dockerfile" --menu.js-url="https://raw.githubusercontent.com/traefik/structor/master/traefik-menu.js.gotmpl" --rqts-url="https://raw.githubusercontent.com/traefik/structor/master/requirements-override.txt" --force-edit-url --exp-branch=master --debug;
fi
deploy:
- provider: releases
api_key: ${GITHUB_TOKEN}
file: dist/traefik*
skip_cleanup: true
file_glob: true
on:
repo: traefik/traefik
tags: true
- provider: script
script: sh script/deploy.sh
skip_cleanup: true
on:
repo: traefik/traefik
tags: true
- provider: script
script: mixtus --dst-doc-path="./traefik" --dst-owner=traefik --dst-repo-name=doc --git-user-email="30906710+traefiker@users.noreply.github.com" --git-user-name=traefiker --src-doc-path="./site" --src-owner=containous --src-repo-name=traefik
skip_cleanup: true
on:
repo: traefik/traefik
all_branches: true
- make test-unit
- make test-integration
- make crossbinary
- make image
after_success:
- make deploy
- make deploy-pr

BIN
.travis/traefik.id_rsa.enc Normal file

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,51 +0,0 @@
# 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@traefik.io
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/

View File

@@ -1,4 +0,0 @@
# Contributing
- https://doc.traefik.io/traefik/contributing/submitting-pull-requests/
- https://doc.traefik.io/traefik/contributing/submitting-issues/

View File

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

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2016-2020 Containous SAS; 2020 Traefik Labs
Copyright (c) 2016 Containous SAS, Emile Vauge, emile@vauge.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

187
Makefile
View File

@@ -1,22 +1,4 @@
.PHONY: all docs docs-serve
SRCS = $(shell git ls-files '*.go' | grep -v '^vendor/')
TAG_NAME := $(shell git tag -l --contains HEAD)
SHA := $(shell git rev-parse HEAD)
VERSION_GIT := $(if $(TAG_NAME),$(TAG_NAME),$(SHA))
VERSION := $(if $(VERSION),$(VERSION),$(VERSION_GIT))
BIND_DIR := "dist"
GIT_BRANCH := $(subst heads/,,$(shell git rev-parse --abbrev-ref HEAD 2>/dev/null))
TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(subst /,-,$(GIT_BRANCH)))
REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"traefik/traefik")
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -e "TEST_CONTAINER=1" -v "/var/run/docker.sock:/var/run/docker.sock")
DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",)
.PHONY: all
TRAEFIK_ENVS := \
-e OS_ARCH_ARG \
@@ -24,131 +6,88 @@ TRAEFIK_ENVS := \
-e TESTFLAGS \
-e VERBOSE \
-e VERSION \
-e CODENAME \
-e TESTDIRS \
-e CI \
-e CONTAINER=DOCKER # Indicator for integration tests that we are running inside a container.
-e CODENAME
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/traefik/traefik/$(BIND_DIR)"
DOCKER_RUN_OPTS := $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
DOCKER_NON_INTERACTIVE ?= false
DOCKER_RUN_TRAEFIK := docker run --add-host=host.docker.internal:127.0.0.1 $(INTEGRATION_OPTS) $(if $(DOCKER_NON_INTERACTIVE), , -it) $(DOCKER_RUN_OPTS)
DOCKER_RUN_TRAEFIK_NOTTY := docker run $(INTEGRATION_OPTS) $(if $(DOCKER_NON_INTERACTIVE), , -i) $(DOCKER_RUN_OPTS)
SRCS = $(shell git ls-files '*.go' | grep -v '^external/')
PRE_TARGET ?= build-dev-image
BIND_DIR := "dist"
TRAEFIK_MOUNT := -v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/containous/traefik/$(BIND_DIR)"
PLATFORM_URL := $(if $(PLATFORM_URL),$(PLATFORM_URL),"https://pilot.traefik.io")
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
TRAEFIK_DEV_IMAGE := traefik-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
REPONAME := $(shell echo $(REPO) | tr '[:upper:]' '[:lower:]')
TRAEFIK_IMAGE := $(if $(REPONAME),$(REPONAME),"containous/traefik")
INTEGRATION_OPTS := $(if $(MAKE_DOCKER_HOST),-e "DOCKER_HOST=$(MAKE_DOCKER_HOST)", -v "/var/run/docker.sock:/var/run/docker.sock")
DOCKER_BUILD_ARGS := $(if $(DOCKER_VERSION), "--build-arg=DOCKER_VERSION=$(DOCKER_VERSION)",)
DOCKER_RUN_TRAEFIK := docker run $(INTEGRATION_OPTS) -it $(TRAEFIK_ENVS) $(TRAEFIK_MOUNT) "$(TRAEFIK_DEV_IMAGE)"
print-%: ; @echo $*=$($*)
default: binary
## Build Dev Docker image
build-dev-image: dist
all: generate-webui build ## validate all checks, build linux binary, run all tests\ncross non-linux binaries
$(DOCKER_RUN_TRAEFIK) ./script/make.sh
binary: generate-webui build ## build the linux binary
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate binary
crossbinary: generate-webui build ## cross build the non-linux binaries
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate crossbinary
test: build ## run the unit and integration tests
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit binary test-integration
test-unit: build ## run the unit tests
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit
test-integration: build ## run the integration tests
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-integration
validate: build ## validate gofmt, golint and go vet
$(DOCKER_RUN_TRAEFIK) ./script/make.sh validate-gofmt validate-govet validate-golint
build: dist
docker build $(DOCKER_BUILD_ARGS) -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
## Build Dev Docker image without cache
build-dev-image-no-cache: dist
build-webui:
docker build -t traefik-webui -f webui/Dockerfile webui
build-no-cache: dist
docker build --no-cache -t "$(TRAEFIK_DEV_IMAGE)" -f build.Dockerfile .
## Create the "dist" directory
shell: build ## start a shell inside the build env
$(DOCKER_RUN_TRAEFIK) /bin/bash
image: build ## build a docker traefik image
docker build -t $(TRAEFIK_IMAGE) .
dist:
mkdir dist
## Build WebUI Docker image
build-webui-image:
docker build -t traefik-webui --build-arg ARG_PLATFORM_URL=$(PLATFORM_URL) -f webui/Dockerfile webui
run-dev:
go generate
go build
./traefik
## Generate WebUI
generate-webui: build-webui-image
generate-webui: build-webui
if [ ! -d "static" ]; then \
mkdir -p static; \
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui npm run build:nc; \
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui chown -R $(shell id -u):$(shell id -g) ../static; \
docker run --rm -v "$$PWD/static":'/src/static' traefik-webui gulp; \
echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md; \
fi
## Build the linux binary
binary: generate-webui $(PRE_TARGET)
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate binary
lint:
script/validate-golint
## Build the binary for the standard plaforms (linux, darwin, windows)
crossbinary-default: generate-webui build-dev-image
$(DOCKER_RUN_TRAEFIK_NOTTY) ./script/make.sh generate crossbinary-default
## Build the binary for the standard plaforms (linux, darwin, windows) in parallel
crossbinary-default-parallel:
$(MAKE) generate-webui
$(MAKE) build-dev-image crossbinary-default
## Run the unit and integration tests
test: build-dev-image
$(DOCKER_RUN_TRAEFIK) ./script/make.sh generate test-unit binary test-integration
## Run the unit tests
test-unit: $(PRE_TARGET)
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate test-unit
## Pull all images for integration tests
pull-images:
grep --no-filename -E '^\s+image:' ./integration/resources/compose/*.yml | awk '{print $$2}' | sort | uniq | xargs -P 6 -n 1 docker pull
## Run the integration tests
test-integration: $(PRE_TARGET)
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK),TEST_CONTAINER=1) ./script/make.sh generate binary test-integration
TEST_HOST=1 ./script/make.sh test-integration
## Validate code and docs
validate-files: $(PRE_TARGET)
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate validate-lint validate-misspell
bash $(CURDIR)/script/validate-shell-script.sh
## Validate code, docs, and vendor
validate: $(PRE_TARGET)
$(if $(PRE_TARGET),$(DOCKER_RUN_TRAEFIK)) ./script/make.sh generate validate-lint validate-misspell validate-vendor
bash $(CURDIR)/script/validate-shell-script.sh
## Clean up static directory and build a Docker Traefik image
build-image: binary
rm -rf static
docker build -t $(TRAEFIK_IMAGE) .
## Build a Docker Traefik image
build-image-dirty: binary
docker build -t $(TRAEFIK_IMAGE) .
## Start a shell inside the build env
shell: build-dev-image
$(DOCKER_RUN_TRAEFIK) /bin/bash
## Build documentation site
docs:
make -C ./docs docs
## Serve the documentation site localy
docs-serve:
make -C ./docs docs-serve
## Generate CRD clientset
generate-crd:
./script/update-generated-crd-code.sh
## Create packages for the release
release-packages: generate-webui build-dev-image
rm -rf dist
$(DOCKER_RUN_TRAEFIK_NOTTY) goreleaser release --skip-publish --timeout="60m"
$(DOCKER_RUN_TRAEFIK_NOTTY) tar cfz dist/traefik-${VERSION}.src.tar.gz \
--exclude-vcs \
--exclude .idea \
--exclude .travis \
--exclude .semaphoreci \
--exclude .github \
--exclude dist .
$(DOCKER_RUN_TRAEFIK_NOTTY) chown -R $(shell id -u):$(shell id -g) dist/
## Format the Code
fmt:
gofmt -s -l -w $(SRCS)
run-dev:
go generate
GO111MODULE=on go build ./cmd/traefik
./traefik
deploy:
./script/deploy.sh
deploy-pr:
./script/deploy-pr.sh
help: ## this help
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

205
README.md
View File

@@ -1,160 +1,157 @@
<p align="center">
<img src="docs/content/assets/img/traefik.logo.png" alt="Traefik" title="Traefik" />
<img src="docs/img/traefik.logo.png" alt="Træfɪk" title="Træfɪk" />
</p>
[![Build Status SemaphoreCI](https://semaphoreci.com/api/v1/containous/traefik/branches/master/shields_badge.svg)](https://semaphoreci.com/containous/traefik)
[![Docs](https://img.shields.io/badge/docs-current-brightgreen.svg)](https://doc.traefik.io/traefik)
[![Go Report Card](https://goreportcard.com/badge/traefik/traefik)](https://goreportcard.com/report/traefik/traefik)
[![](https://images.microbadger.com/badges/image/traefik.svg)](https://microbadger.com/images/traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/traefik/traefik/blob/master/LICENSE.md)
[![Join the community support forum at https://community.traefik.io/](https://img.shields.io/badge/style-register-green.svg?style=social&label=Discourse)](https://community.traefik.io/)
[![Twitter](https://img.shields.io/twitter/follow/traefik.svg?style=social)](https://twitter.com/intent/follow?screen_name=traefik)
[![Build Status](https://travis-ci.org/containous/traefik.svg?branch=master)](https://travis-ci.org/containous/traefik)
[![Docs](https://img.shields.io/badge/docs-current-brightgreen.svg)](https://docs.traefik.io)
[![Go Report Card](https://goreportcard.com/badge/kubernetes/helm)](http://goreportcard.com/report/containous/traefik)
[![Image Layer](https://badge.imagelayers.io/traefik:latest.svg)](https://imagelayers.io/?images=traefik)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/containous/traefik/blob/master/LICENSE.md)
[![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com)
[![Twitter](https://img.shields.io/twitter/follow/traefikproxy.svg?style=social)](https://twitter.com/intent/follow?screen_name=traefikproxy)
Traefik (pronounced _traffic_) is a modern HTTP reverse proxy and load balancer that makes deploying microservices easy.
Traefik integrates with your existing infrastructure components ([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 configures itself automatically and dynamically.
Pointing Traefik at your orchestrator should be the _only_ configuration step you need.
---
. **[Overview](#overview)** .
**[Features](#features)** .
**[Supported backends](#supported-backends)** .
**[Quickstart](#quickstart)** .
**[Web UI](#web-ui)** .
**[Documentation](#documentation)** .
. **[Support](#support)** .
**[Release cycle](#release-cycle)** .
**[Contributing](#contributing)** .
**[Maintainers](#maintainers)** .
**[Credits](#credits)** .
---
:warning: Please be aware that the old configurations for Traefik v1.x are NOT compatible with the v2.x config as of now. If you're running v2, please ensure you are using a [v2 configuration](https://doc.traefik.io/traefik/).
Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Kubernetes](http://kubernetes.io/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically.
## Overview
Imagine that you have deployed a bunch of microservices with the help of an orchestrator (like Swarm or Kubernetes) or a service registry (like etcd or consul).
Now you want users to access these microservices, and you need a reverse proxy.
Imagine that you have deployed a bunch of microservices on your infrastructure. You probably used a service registry (like etcd or consul) and/or an orchestrator (swarm, Mesos/Marathon) to manage all these services.
If you want your users to access some of your microservices from the Internet, you will have to use a reverse proxy and configure it using virtual hosts or prefix paths:
Traditional reverse-proxies require that you configure _each_ route that will connect paths and subdomains to _each_ microservice.
In an environment where you add, remove, kill, upgrade, or scale your services _many_ times a day, the task of keeping the routes up to date becomes tedious.
- domain `api.domain.com` will point the microservice `api` in your private network
- path `domain.com/web` will point the microservice `web` in your private network
- domain `backoffice.domain.com` will point the microservices `backoffice` in your private network, load-balancing between your multiple instances
**This is when Traefik can help you!**
But a microservices architecture is dynamic... Services are added, removed, killed or upgraded often, eventually several times a day.
Traefik listens to your service registry/orchestrator API and instantly generates the routes so your microservices are connected to the outside world -- without further intervention from your part.
Traditional reverse-proxies are not natively dynamic. You can't change their configuration and hot-reload easily.
**Run Traefik and let it do the work for you!**
_(But if you'd rather configure some of your routes manually, Traefik supports that too!)_
Here enters Træfɪk.
![Architecture](docs/img/architecture.png)
Træfɪk can listen to your service registry/orchestrator API, and knows each time a microservice is added, removed, killed or upgraded, and can generate its configuration automatically.
Routes to your services will be created instantly.
Run it and forget it!
![Architecture](docs/content/assets/img/traefik-architecture.png)
## Features
- Continuously updates its configuration (No restarts!)
- Supports multiple load balancing algorithms
- Provides HTTPS to your microservices by leveraging [Let's Encrypt](https://letsencrypt.org) (wildcard certificates support)
- Circuit breakers, retry
- See the magic through its clean web UI
- Websocket, HTTP/2, GRPC ready
- Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB)
- Keeps access logs (JSON, CLF)
- Fast
- Exposes a Rest API
- Packaged as a single binary file (made with :heart: with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image
- [It's fast](http://docs.traefik.io/benchmarks)
- No dependency hell, single binary made with go
- Rest API
- Multiple backends supported: Docker, Mesos/Marathon, Consul, Etcd, and more to come
- Watchers for backends, can listen change in backends to apply a new configuration automatically
- Hot-reloading of configuration. No need to restart the process
- Graceful shutdown http connections
- Circuit breakers on backends
- Round Robin, rebalancer load-balancers
- Rest Metrics
- [Tiny](https://imagelayers.io/?images=traefik) [official](https://hub.docker.com/r/_/traefik/) docker image included
- SSL backends support
- SSL frontend support (with SNI)
- Clean AngularJS Web UI
- Websocket support
- HTTP/2 support
- Retry request if network error
- [Let's Encrypt](https://letsencrypt.org) support (Automatic HTTPS with renewal)
## Demo
## Supported Backends
Here is a talk (in french) given by [Emile Vauge](https://github.com/emilevauge) at the [Devoxx France 2016](http://www.devoxx.fr) conference.
You will learn fundamental Træfɪk features and see some demos with Docker, Mesos/Marathon and Lets'Encrypt.
- [Docker](https://doc.traefik.io/traefik/providers/docker/) / [Swarm mode](https://doc.traefik.io/traefik/providers/docker/)
- [Kubernetes](https://doc.traefik.io/traefik/providers/kubernetes-crd/)
- [Marathon](https://doc.traefik.io/traefik/providers/marathon/)
- [Rancher](https://doc.traefik.io/traefik/providers/rancher/) (Metadata)
- [File](https://doc.traefik.io/traefik/providers/file/)
## Quickstart
To get your hands on Traefik, you can use the [5-Minute Quickstart](https://doc.traefik.io/traefik/getting-started/quick-start/) in our documentation (you will need Docker).
[![Traefik Devoxx France](http://img.youtube.com/vi/QvAz9mVx5TI/0.jpg)](http://www.youtube.com/watch?v=QvAz9mVx5TI)
## Web UI
You can access the simple HTML frontend of Traefik.
You can access to a simple HTML frontend of Træfik.
![Web UI Providers](docs/content/assets/img/webui-dashboard.png)
![Web UI Providers](docs/img/web.frontend.png)
![Web UI Health](docs/img/traefik-health.png)
## Documentation
## Plumbing
You can find the complete documentation of Traefik v2 at [https://doc.traefik.io/traefik/](https://doc.traefik.io/traefik/).
- [Oxy](https://github.com/vulcand/oxy): an awesome proxy library made by Mailgun guys
- [Gorilla mux](https://github.com/gorilla/mux): famous request router
- [Negroni](https://github.com/codegangsta/negroni): web middlewares made simple
- [Manners](https://github.com/mailgun/manners): graceful shutdown of http.Handler servers
- [Lego](https://github.com/xenolf/lego): the best [Let's Encrypt](https://letsencrypt.org) library in go
If you are using Traefik v1, you can find the complete documentation at [https://doc.traefik.io/traefik/v1.7/](https://doc.traefik.io/traefik/v1.7/).
## Quick start
A collection of contributions around Traefik can be found at [https://awesome.traefik.io](https://awesome.traefik.io).
## Support
To get community support, you can:
- join the Traefik community forum: [![Join the chat at https://community.traefik.io/](https://img.shields.io/badge/style-register-green.svg?style=social&label=Discourse)](https://community.traefik.io/)
If you need commercial support, please contact [Traefik.io](https://traefik.io) by mail: <mailto:support@traefik.io>.
## Download
- Grab the latest binary from the [releases](https://github.com/traefik/traefik/releases) page and run it with the [sample configuration file](https://raw.githubusercontent.com/traefik/traefik/master/traefik.sample.toml):
- The simple way: grab the latest binary from the [releases](https://github.com/containous/traefik/releases) page and just run it with the [sample configuration file](https://raw.githubusercontent.com/containous/traefik/master/traefik.sample.toml):
```shell
./traefik --configFile=traefik.toml
```
- Or use the official tiny Docker image and run it with the [sample configuration file](https://raw.githubusercontent.com/traefik/traefik/master/traefik.sample.toml):
- Use the tiny Docker image:
```shell
docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik
```
- Or get the sources:
- From sources:
```shell
git clone https://github.com/traefik/traefik
git clone https://github.com/containous/traefik
```
## Introductory Videos
## Documentation
You can find high level and deep dive videos on [videos.traefik.io](https://videos.traefik.io).
## Maintainers
[Information about process and maintainers](docs/content/contributing/maintainers.md)
You can find the complete documentation [here](https://docs.traefik.io).
## Contributing
If you'd like to contribute to the project, refer to the [contributing documentation](CONTRIBUTING.md).
Please refer to [this section](.github/CONTRIBUTING.md).
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md).
By participating in this project, you agree to abide by its terms.
## Support
## Release Cycle
You can join [![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com) to get basic support.
If you prefer a commercial support, please contact [containo.us](https://containo.us) by mail: <mailto:support@containo.us>.
- We release a new version (e.g. 1.1.0, 1.2.0, 1.3.0) every other month.
- Release Candidates are available before the release (e.g. 1.1.0-rc1, 1.1.0-rc2, 1.1.0-rc3, 1.1.0-rc4, before 1.1.0).
- Bug-fixes (e.g. 1.1.1, 1.1.2, 1.2.1, 1.2.3) are released as needed (no additional features are delivered in those versions, bug-fixes only).
## Træfɪk here and there
Each version is supported until the next one is released (e.g. 1.1.x will be supported until 1.2.0 is out).
These projects use Træfɪk internally. If your company uses Træfɪk, we would be glad to get your feedback :) Contact us on [![Join the chat at https://traefik.herokuapp.com](https://img.shields.io/badge/style-register-green.svg?style=social&label=Slack)](https://traefik.herokuapp.com)
We use [Semantic Versioning](https://semver.org/).
- Project [Mantl](https://mantl.io/) from Cisco
## Mailing Lists
![Web UI Providers](docs/img/mantl-logo.png)
> Mantl is a modern platform for rapidly deploying globally distributed services. A container orchestrator, docker, a network stack, something to pool your logs, something to monitor health, a sprinkle of service discovery and some automation.
- General announcements, new releases: mail at news+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/news).
- Security announcements: mail at security+subscribe@traefik.io or on [the online viewer](https://groups.google.com/a/traefik.io/forum/#!forum/security).
- Project [Apollo](http://capgemini.github.io/devops/apollo/) from Cap Gemini
![Web UI Providers](docs/img/apollo-logo.png)
> Apollo is an open source project to aid with building and deploying IAAS and PAAS services. It is particularly geared towards managing containerized applications across multiple hosts, and big data type workloads. Apollo leverages other open source components to provide basic mechanisms for deployment, maintenance, and scaling of infrastructure and applications.
## Partners
[![Zenika](docs/img/zenika.logo.png)](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 worlds leading Open Source
software products.
[![Asteris](docs/img/asteris.logo.png)](https://aster.is)
Founded in 2014, Asteris creates next-generation infrastructure software for the modern datacenter. Asteris writes software that makes it easy for companies to implement continuous delivery and realtime data pipelines. We support the HashiCorp stack, along with Kubernetes, Apache Mesos, Spark and Kafka. We're core committers on mantl.io, consul-cli and mesos-consul.
## Maintainers
- Emile Vauge [@emilevauge](https://github.com/emilevauge)
- Vincent Demeester [@vdemeester](https://github.com/vdemeester)
- Samuel Berthe [@samber](https://github.com/samber)
- Russell Clare [@Russell-IO](https://github.com/Russell-IO)
- Ed Robinson [@errm](https://github.com/errm)
## Credits
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the gopher's logo!.
The gopher's logo of Traefik is licensed under the Creative Commons 3.0 Attributions license.
The gopher's logo of Traefik was inspired by the gopher stickers made by [Takuya Ueda](https://twitter.com/tenntenn).
The original Go gopher was designed by [Renee French](https://reneefrench.blogspot.com/).
Kudos to [Peka](http://peka.byethost11.com/photoblog/) for his awesome work on the logo ![logo](docs/img/traefik.icon.png)

479
acme/acme.go Normal file
View File

@@ -0,0 +1,479 @@
package acme
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/containous/traefik/safe"
"github.com/xenolf/lego/acme"
"io/ioutil"
fmtlog "log"
"os"
"reflect"
"strings"
"sync"
"time"
)
// Account is used to store lets encrypt registration info
type Account struct {
Email string
Registration *acme.RegistrationResource
PrivateKey []byte
DomainsCertificate DomainsCertificates
}
// GetEmail returns email
func (a Account) GetEmail() string {
return a.Email
}
// GetRegistration returns lets encrypt registration resource
func (a Account) GetRegistration() *acme.RegistrationResource {
return a.Registration
}
// GetPrivateKey returns private key
func (a Account) GetPrivateKey() crypto.PrivateKey {
if privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey); err == nil {
return privateKey
}
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
return nil
}
// Certificate is used to store certificate info
type Certificate struct {
Domain string
CertURL string
CertStableURL string
PrivateKey []byte
Certificate []byte
}
// DomainsCertificates stores a certificate for multiple domains
type DomainsCertificates struct {
Certs []*DomainsCertificate
lock *sync.RWMutex
}
func (dc *DomainsCertificates) init() error {
if dc.lock == nil {
dc.lock = &sync.RWMutex{}
}
dc.lock.Lock()
defer dc.lock.Unlock()
for _, domainsCertificate := range dc.Certs {
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
if err != nil {
return err
}
domainsCertificate.tlsCert = &tlsCert
}
return nil
}
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain Domain) error {
dc.lock.Lock()
defer dc.lock.Unlock()
for _, domainsCertificate := range dc.Certs {
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
if err != nil {
return err
}
domainsCertificate.Certificate = acmeCert
domainsCertificate.tlsCert = &tlsCert
return nil
}
}
return errors.New("Certificate to renew not found for domain " + domain.Main)
}
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) {
dc.lock.Lock()
defer dc.lock.Unlock()
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
if err != nil {
return nil, err
}
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
dc.Certs = append(dc.Certs, &cert)
return &cert, nil
}
func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*DomainsCertificate, bool) {
dc.lock.RLock()
defer dc.lock.RUnlock()
for _, domainsCertificate := range dc.Certs {
domains := []string{}
domains = append(domains, domainsCertificate.Domains.Main)
domains = append(domains, domainsCertificate.Domains.SANs...)
for _, domain := range domains {
if domain == domainToFind {
return domainsCertificate, true
}
}
}
return nil, false
}
func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, bool) {
dc.lock.RLock()
defer dc.lock.RUnlock()
for _, domainsCertificate := range dc.Certs {
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
return domainsCertificate, true
}
}
return nil, false
}
// DomainsCertificate contains a certificate for multiple domains
type DomainsCertificate struct {
Domains Domain
Certificate *Certificate
tlsCert *tls.Certificate
}
func (dc *DomainsCertificate) needRenew() bool {
for _, c := range dc.tlsCert.Certificate {
crt, err := x509.ParseCertificate(c)
if err != nil {
// If there's an error, we assume the cert is broken, and needs update
return true
}
// <= 7 days left, renew certificate
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 7 * time.Hour))) {
return true
}
}
return false
}
// ACME allows to connect to lets encrypt and retrieve certs
type ACME struct {
Email string `description:"Email address used for registration"`
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
StorageFile string `description:"File used for certificates storage."`
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
CAServer string `description:"CA server to use."`
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
storageLock sync.RWMutex
}
//Domains parse []Domain
type Domains []Domain
//Set []Domain
func (ds *Domains) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
if len(slice) < 1 {
return fmt.Errorf("Parse error ACME.Domain. Imposible to parse %s", str)
}
d := Domain{
Main: slice[0],
SANs: []string{},
}
if len(slice) > 1 {
d.SANs = slice[1:]
}
*ds = append(*ds, d)
return nil
}
//Get []Domain
func (ds *Domains) Get() interface{} { return []Domain(*ds) }
//String returns []Domain in string
func (ds *Domains) String() string { return fmt.Sprintf("%+v", *ds) }
//SetValue sets []Domain into the parser
func (ds *Domains) SetValue(val interface{}) {
*ds = Domains(val.([]Domain))
}
// Domain holds a domain name with SANs
type Domain struct {
Main string
SANs []string
}
// CreateConfig creates a tls.config from using ACME configuration
func (a *ACME) CreateConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(domain string) bool) error {
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
if len(a.StorageFile) == 0 {
return errors.New("Empty StorageFile, please provide a filename for certs storage")
}
log.Debugf("Generating default certificate...")
if len(tlsConfig.Certificates) == 0 {
// no certificates in TLS config, so we add a default one
cert, err := generateDefaultCertificate()
if err != nil {
return err
}
tlsConfig.Certificates = append(tlsConfig.Certificates, *cert)
}
var account *Account
var needRegister bool
// if certificates in storage, load them
if fileInfo, err := os.Stat(a.StorageFile); err == nil && fileInfo.Size() != 0 {
log.Infof("Loading ACME certificates...")
// load account
account, err = a.loadAccount(a)
if err != nil {
return err
}
} else {
log.Infof("Generating ACME Account...")
// Create a user. New accounts need an email and private key to start
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return err
}
account = &Account{
Email: a.Email,
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
}
account.DomainsCertificate = DomainsCertificates{Certs: []*DomainsCertificate{}, lock: &sync.RWMutex{}}
needRegister = true
}
client, err := a.buildACMEClient(account)
if err != nil {
return err
}
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
wrapperChallengeProvider := newWrapperChallengeProvider()
client.SetChallengeProvider(acme.TLSSNI01, wrapperChallengeProvider)
if needRegister {
// New users will need to register; be sure to save it
reg, err := client.Register()
if err != nil {
return err
}
account.Registration = reg
}
// The client has a URL to the current Let's Encrypt Subscriber
// Agreement. The user will need to agree to it.
err = client.AgreeToTOS()
if err != nil {
// Let's Encrypt Subscriber Agreement renew ?
reg, err := client.QueryRegistration()
if err != nil {
return err
}
account.Registration = reg
err = client.AgreeToTOS()
if err != nil {
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
}
}
// save account
err = a.saveAccount(account)
if err != nil {
return err
}
safe.Go(func() {
a.retrieveCertificates(client, account)
if err := a.renewCertificates(client, account); err != nil {
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
}
})
tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
if challengeCert, ok := wrapperChallengeProvider.getCertificate(clientHello.ServerName); ok {
return challengeCert, nil
}
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
return domainCert.tlsCert, nil
}
if a.OnDemand {
if CheckOnDemandDomain != nil && !CheckOnDemandDomain(clientHello.ServerName) {
return nil, nil
}
return a.loadCertificateOnDemand(client, account, clientHello)
}
return nil, nil
}
ticker := time.NewTicker(24 * time.Hour)
safe.Go(func() {
for {
select {
case <-ticker.C:
if err := a.renewCertificates(client, account); err != nil {
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
}
}
}
})
return nil
}
func (a *ACME) retrieveCertificates(client *acme.Client, account *Account) {
log.Infof("Retrieving ACME certificates...")
for _, domain := range a.Domains {
// check if cert isn't already loaded
if _, exists := account.DomainsCertificate.exists(domain); !exists {
domains := []string{}
domains = append(domains, domain.Main)
domains = append(domains, domain.SANs...)
certificateResource, err := a.getDomainsCertificates(client, domains)
if err != nil {
log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error())
continue
}
_, err = account.DomainsCertificate.addCertificateForDomains(certificateResource, domain)
if err != nil {
log.Errorf("Error adding ACME certificate for domain %s: %s", domains, err.Error())
continue
}
if err = a.saveAccount(account); err != nil {
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
continue
}
}
}
log.Infof("Retrieved ACME certificates")
}
func (a *ACME) renewCertificates(client *acme.Client, account *Account) error {
log.Debugf("Testing certificate renew...")
for _, certificateResource := range account.DomainsCertificate.Certs {
if certificateResource.needRenew() {
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
renewedCert, err := client.RenewCertificate(acme.CertificateResource{
Domain: certificateResource.Certificate.Domain,
CertURL: certificateResource.Certificate.CertURL,
CertStableURL: certificateResource.Certificate.CertStableURL,
PrivateKey: certificateResource.Certificate.PrivateKey,
Certificate: certificateResource.Certificate.Certificate,
}, true)
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,
}
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
if err != nil {
log.Errorf("Error renewing certificate: %v", err)
continue
}
if err = a.saveAccount(account); err != nil {
log.Errorf("Error saving ACME account: %v", err)
continue
}
}
}
return nil
}
func (a *ACME) buildACMEClient(Account *Account) (*acme.Client, error) {
caServer := "https://acme-v01.api.letsencrypt.org/directory"
if len(a.CAServer) > 0 {
caServer = a.CAServer
}
client, err := acme.NewClient(caServer, Account, acme.RSA4096)
if err != nil {
return nil, err
}
return client, nil
}
func (a *ACME) loadCertificateOnDemand(client *acme.Client, Account *Account, clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
if certificateResource, ok := Account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
return certificateResource.tlsCert, nil
}
Certificate, err := a.getDomainsCertificates(client, []string{clientHello.ServerName})
if err != nil {
return nil, err
}
log.Debugf("Got certificate on demand for domain %s", clientHello.ServerName)
cert, err := Account.DomainsCertificate.addCertificateForDomains(Certificate, Domain{Main: clientHello.ServerName})
if err != nil {
return nil, err
}
if err = a.saveAccount(Account); err != nil {
return nil, err
}
return cert.tlsCert, nil
}
func (a *ACME) loadAccount(acmeConfig *ACME) (*Account, error) {
a.storageLock.RLock()
defer a.storageLock.RUnlock()
Account := Account{
DomainsCertificate: DomainsCertificates{},
}
file, err := ioutil.ReadFile(acmeConfig.StorageFile)
if err != nil {
return nil, err
}
if err := json.Unmarshal(file, &Account); err != nil {
return nil, err
}
err = Account.DomainsCertificate.init()
if err != nil {
return nil, err
}
log.Infof("Loaded ACME config from storage %s", acmeConfig.StorageFile)
return &Account, nil
}
func (a *ACME) saveAccount(Account *Account) error {
a.storageLock.Lock()
defer a.storageLock.Unlock()
// write account to file
data, err := json.MarshalIndent(Account, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(a.StorageFile, data, 0644)
}
func (a *ACME) getDomainsCertificates(client *acme.Client, domains []string) (*Certificate, error) {
log.Debugf("Loading ACME certificates %s...", domains)
bundle := true
certificate, failures := client.ObtainCertificate(domains, bundle, nil)
if len(failures) > 0 {
log.Error(failures)
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
}
log.Debugf("Loaded ACME certificates %s", domains)
return &Certificate{
Domain: certificate.Domain,
CertURL: certificate.CertURL,
CertStableURL: certificate.CertStableURL,
PrivateKey: certificate.PrivateKey,
Certificate: certificate.Certificate,
}, nil
}

258
acme/acme_test.go Normal file
View File

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

56
acme/challengeProvider.go Normal file
View File

@@ -0,0 +1,56 @@
package acme
import (
"crypto/tls"
"sync"
"crypto/x509"
"github.com/xenolf/lego/acme"
)
type wrapperChallengeProvider struct {
challengeCerts map[string]*tls.Certificate
lock sync.RWMutex
}
func newWrapperChallengeProvider() *wrapperChallengeProvider {
return &wrapperChallengeProvider{
challengeCerts: map[string]*tls.Certificate{},
}
}
func (c *wrapperChallengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
c.lock.RLock()
defer c.lock.RUnlock()
if cert, ok := c.challengeCerts[domain]; ok {
return cert, true
}
return nil, false
}
func (c *wrapperChallengeProvider) Present(domain, token, keyAuth string) error {
cert, _, err := acme.TLSSNI01ChallengeCert(keyAuth)
if err != nil {
return err
}
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return err
}
c.lock.Lock()
defer c.lock.Unlock()
for i := range cert.Leaf.DNSNames {
c.challengeCerts[cert.Leaf.DNSNames[i]] = &cert
}
return nil
}
func (c *wrapperChallengeProvider) CleanUp(domain, token, keyAuth string) error {
c.lock.Lock()
defer c.lock.Unlock()
delete(c.challengeCerts, domain)
return nil
}

78
acme/crypto.go Normal file
View File

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

34
adapters.go Normal file
View File

@@ -0,0 +1,34 @@
/*
Copyright
*/
package main
import (
"net/http"
log "github.com/Sirupsen/logrus"
)
// OxyLogger implements oxy Logger interface with logrus.
type OxyLogger struct {
}
// Infof logs specified string as Debug level in logrus.
func (oxylogger *OxyLogger) Infof(format string, args ...interface{}) {
log.Debugf(format, args...)
}
// Warningf logs specified string as Warning level in logrus.
func (oxylogger *OxyLogger) Warningf(format string, args ...interface{}) {
log.Warningf(format, args...)
}
// Errorf logs specified string as Warningf level in logrus.
func (oxylogger *OxyLogger) Errorf(format string, args ...interface{}) {
log.Warningf(format, args...)
}
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
//templatesRenderer.HTML(w, http.StatusNotFound, "notFound", nil)
}

View File

@@ -1,37 +1,25 @@
FROM golang:1.15-alpine
FROM golang:1.6.2
RUN apk --update upgrade \
&& apk --no-cache --no-progress add git mercurial bash gcc musl-dev curl tar ca-certificates tzdata \
&& update-ca-certificates \
&& rm -rf /var/cache/apk/*
RUN go get github.com/Masterminds/glide \
&& go get github.com/jteeuwen/go-bindata/... \
&& go get github.com/golang/lint/golint \
&& go get github.com/kisielk/errcheck
# Which docker version to test on
ARG DOCKER_VERSION=18.09.7
ARG DOCKER_VERSION=1.10.1
# Download docker
RUN mkdir -p /usr/local/bin \
&& curl -fL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz \
| tar -xzC /usr/local/bin --transform 's#^.+/##x'
RUN set -ex; \
curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION} -o /usr/local/bin/docker-${DOCKER_VERSION}; \
chmod +x /usr/local/bin/docker-${DOCKER_VERSION}
# Download go-bindata binary to bin folder in $GOPATH
RUN mkdir -p /usr/local/bin \
&& curl -fsSL -o /usr/local/bin/go-bindata https://github.com/containous/go-bindata/releases/download/v1.0.0/go-bindata \
&& chmod +x /usr/local/bin/go-bindata
# Set the default Docker to be run
RUN ln -s /usr/local/bin/docker-${DOCKER_VERSION} /usr/local/bin/docker
# Download golangci-lint binary to bin folder in $GOPATH
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.31.0
WORKDIR /go/src/github.com/containous/traefik
# Download misspell binary to bin folder in $GOPATH
RUN curl -sfL https://raw.githubusercontent.com/client9/misspell/master/install-misspell.sh | bash -s -- -b $GOPATH/bin v0.3.4
COPY glide.yaml glide.yaml
COPY glide.lock glide.lock
RUN glide install
# Download goreleaser binary to bin folder in $GOPATH
RUN curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | sh
WORKDIR /go/src/github.com/traefik/traefik
# Download go modules
COPY go.mod .
COPY go.sum .
RUN GO111MODULE=on GOPROXY=https://proxy.golang.org go mod download
COPY . /go/src/github.com/traefik/traefik
COPY . /go/src/github.com/containous/traefik

36
circle.yml Normal file
View File

@@ -0,0 +1,36 @@
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}

View File

@@ -1,34 +0,0 @@
package cmd
import (
"time"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v2/pkg/config/static"
)
// TraefikCmdConfiguration wraps the static configuration and extra parameters.
type TraefikCmdConfiguration struct {
static.Configuration `export:"true"`
// ConfigFile is the path to the configuration file.
ConfigFile string `description:"Configuration file to use. If specified all other flags are ignored." export:"true"`
}
// NewTraefikConfiguration creates a TraefikCmdConfiguration with default values.
func NewTraefikConfiguration() *TraefikCmdConfiguration {
return &TraefikCmdConfiguration{
Configuration: static.Configuration{
Global: &static.Global{
CheckNewVersion: true,
},
EntryPoints: make(static.EntryPoints),
Providers: &static.Providers{
ProvidersThrottleDuration: ptypes.Duration(2 * time.Second),
},
ServersTransport: &static.ServersTransport{
MaxIdleConnsPerHost: 200,
},
},
ConfigFile: "",
}
}

View File

@@ -1,22 +0,0 @@
package cmd
import (
"context"
"os"
"os/signal"
"syscall"
)
// ContextWithSignal creates a context canceled when SIGINT or SIGTERM are notified.
func ContextWithSignal(ctx context.Context) context.Context {
newCtx, cancel := context.WithCancel(ctx)
signals := make(chan os.Signal)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
go func() {
select {
case <-signals:
cancel()
}
}()
return newCtx
}

View File

@@ -1,79 +0,0 @@
package healthcheck
import (
"errors"
"fmt"
"net/http"
"os"
"time"
"github.com/traefik/paerser/cli"
"github.com/traefik/traefik/v2/pkg/config/static"
)
// NewCmd builds a new HealthCheck command.
func NewCmd(traefikConfiguration *static.Configuration, loaders []cli.ResourceLoader) *cli.Command {
return &cli.Command{
Name: "healthcheck",
Description: `Calls Traefik /ping endpoint (disabled by default) to check the health of Traefik.`,
Configuration: traefikConfiguration,
Run: runCmd(traefikConfiguration),
Resources: loaders,
}
}
func runCmd(traefikConfiguration *static.Configuration) func(_ []string) error {
return func(_ []string) error {
traefikConfiguration.SetEffectiveConfiguration()
resp, errPing := Do(*traefikConfiguration)
if resp != nil {
resp.Body.Close()
}
if errPing != nil {
fmt.Printf("Error calling healthcheck: %s\n", errPing)
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
}
}
// Do try to do a healthcheck.
func Do(staticConfiguration static.Configuration) (*http.Response, error) {
if staticConfiguration.Ping == nil {
return nil, errors.New("please enable `ping` to use health check")
}
ep := staticConfiguration.Ping.EntryPoint
if ep == "" {
ep = "traefik"
}
pingEntryPoint, ok := staticConfiguration.EntryPoints[ep]
if !ok {
return nil, fmt.Errorf("ping: missing %s entry point", ep)
}
client := &http.Client{Timeout: 5 * time.Second}
protocol := "http"
// FIXME Handle TLS on ping etc...
// if pingEntryPoint.TLS != nil {
// protocol = "https"
// tr := &http.Transport{
// TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
// }
// client.Transport = tr
// }
path := "/"
return client.Head(protocol + "://" + pingEntryPoint.GetAddress() + path + "ping")
}

View File

@@ -1,40 +0,0 @@
package main
import (
"github.com/traefik/traefik/v2/pkg/config/static"
"github.com/traefik/traefik/v2/pkg/plugins"
)
const outputDir = "./plugins-storage/"
func initPlugins(staticCfg *static.Configuration) (*plugins.Client, map[string]plugins.Descriptor, *plugins.DevPlugin, error) {
if !isPilotEnabled(staticCfg) || !hasPlugins(staticCfg) {
return nil, map[string]plugins.Descriptor{}, nil, nil
}
opts := plugins.ClientOptions{
Output: outputDir,
Token: staticCfg.Pilot.Token,
}
client, err := plugins.NewClient(opts)
if err != nil {
return nil, nil, nil, err
}
err = plugins.Setup(client, staticCfg.Experimental.Plugins, staticCfg.Experimental.DevPlugin)
if err != nil {
return nil, nil, nil, err
}
return client, staticCfg.Experimental.Plugins, staticCfg.Experimental.DevPlugin, nil
}
func isPilotEnabled(staticCfg *static.Configuration) bool {
return staticCfg.Pilot != nil && staticCfg.Pilot.Token != ""
}
func hasPlugins(staticCfg *static.Configuration) bool {
return staticCfg.Experimental != nil &&
(len(staticCfg.Experimental.Plugins) > 0 || staticCfg.Experimental.DevPlugin != nil)
}

View File

@@ -1,501 +0,0 @@
package main
import (
"context"
"encoding/json"
stdlog "log"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/coreos/go-systemd/daemon"
assetfs "github.com/elazarl/go-bindata-assetfs"
"github.com/sirupsen/logrus"
"github.com/traefik/paerser/cli"
"github.com/traefik/traefik/v2/autogen/genstatic"
"github.com/traefik/traefik/v2/cmd"
"github.com/traefik/traefik/v2/cmd/healthcheck"
cmdVersion "github.com/traefik/traefik/v2/cmd/version"
tcli "github.com/traefik/traefik/v2/pkg/cli"
"github.com/traefik/traefik/v2/pkg/collector"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/config/runtime"
"github.com/traefik/traefik/v2/pkg/config/static"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/metrics"
"github.com/traefik/traefik/v2/pkg/middlewares/accesslog"
"github.com/traefik/traefik/v2/pkg/pilot"
"github.com/traefik/traefik/v2/pkg/plugins"
"github.com/traefik/traefik/v2/pkg/provider/acme"
"github.com/traefik/traefik/v2/pkg/provider/aggregator"
"github.com/traefik/traefik/v2/pkg/provider/traefik"
"github.com/traefik/traefik/v2/pkg/safe"
"github.com/traefik/traefik/v2/pkg/server"
"github.com/traefik/traefik/v2/pkg/server/middleware"
"github.com/traefik/traefik/v2/pkg/server/service"
traefiktls "github.com/traefik/traefik/v2/pkg/tls"
"github.com/traefik/traefik/v2/pkg/types"
"github.com/traefik/traefik/v2/pkg/version"
"github.com/vulcand/oxy/roundrobin"
)
func main() {
// traefik config inits
tConfig := cmd.NewTraefikConfiguration()
loaders := []cli.ResourceLoader{&tcli.FileLoader{}, &tcli.FlagLoader{}, &tcli.EnvLoader{}}
cmdTraefik := &cli.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`,
Configuration: tConfig,
Resources: loaders,
Run: func(_ []string) error {
return runCmd(&tConfig.Configuration)
},
}
err := cmdTraefik.AddCommand(healthcheck.NewCmd(&tConfig.Configuration, loaders))
if err != nil {
stdlog.Println(err)
os.Exit(1)
}
err = cmdTraefik.AddCommand(cmdVersion.NewCmd())
if err != nil {
stdlog.Println(err)
os.Exit(1)
}
err = cli.Execute(cmdTraefik)
if err != nil {
stdlog.Println(err)
logrus.Exit(1)
}
logrus.Exit(0)
}
func runCmd(staticConfiguration *static.Configuration) error {
configureLogging(staticConfiguration)
http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment
if err := roundrobin.SetDefaultWeight(0); err != nil {
log.WithoutContext().Errorf("Could not set round robin default weight: %v", err)
}
staticConfiguration.SetEffectiveConfiguration()
if err := staticConfiguration.ValidateConfiguration(); err != nil {
return err
}
log.WithoutContext().Infof("Traefik version %s built on %s", version.Version, version.BuildDate)
jsonConf, err := json.Marshal(staticConfiguration)
if err != nil {
log.WithoutContext().Errorf("Could not marshal static configuration: %v", err)
log.WithoutContext().Debugf("Static configuration loaded [struct] %#v", staticConfiguration)
} else {
log.WithoutContext().Debugf("Static configuration loaded %s", string(jsonConf))
}
if staticConfiguration.API != nil && staticConfiguration.API.Dashboard {
staticConfiguration.API.DashboardAssets = &assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"}
}
if staticConfiguration.Global.CheckNewVersion {
checkNewVersion()
}
stats(staticConfiguration)
svr, err := setupServer(staticConfiguration)
if err != nil {
return err
}
ctx := cmd.ContextWithSignal(context.Background())
if staticConfiguration.Experimental != nil && staticConfiguration.Experimental.DevPlugin != nil {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, 30*time.Minute)
defer cancel()
}
if staticConfiguration.Ping != nil {
staticConfiguration.Ping.WithContext(ctx)
}
svr.Start(ctx)
defer svr.Close()
sent, err := daemon.SdNotify(false, "READY=1")
if !sent && err != nil {
log.WithoutContext().Errorf("Failed to notify: %v", err)
}
t, err := daemon.SdWatchdogEnabled(false)
if err != nil {
log.WithoutContext().Errorf("Could not enable Watchdog: %v", err)
} else if t != 0 {
// Send a ping each half time given
t /= 2
log.WithoutContext().Infof("Watchdog activated with timer duration %s", t)
safe.Go(func() {
tick := time.Tick(t)
for range tick {
resp, errHealthCheck := healthcheck.Do(*staticConfiguration)
if resp != nil {
_ = resp.Body.Close()
}
if staticConfiguration.Ping == nil || errHealthCheck == nil {
if ok, _ := daemon.SdNotify(false, "WATCHDOG=1"); !ok {
log.WithoutContext().Error("Fail to tick watchdog")
}
} else {
log.WithoutContext().Error(errHealthCheck)
}
}
})
}
svr.Wait()
log.WithoutContext().Info("Shutting down")
return nil
}
func setupServer(staticConfiguration *static.Configuration) (*server.Server, error) {
providerAggregator := aggregator.NewProviderAggregator(*staticConfiguration.Providers)
// adds internal provider
err := providerAggregator.AddProvider(traefik.New(*staticConfiguration))
if err != nil {
return nil, err
}
tlsManager := traefiktls.NewManager()
acmeProviders := initACMEProvider(staticConfiguration, &providerAggregator, tlsManager)
serverEntryPointsTCP, err := server.NewTCPEntryPoints(staticConfiguration.EntryPoints)
if err != nil {
return nil, err
}
serverEntryPointsUDP, err := server.NewUDPEntryPoints(staticConfiguration.EntryPoints)
if err != nil {
return nil, err
}
ctx := context.Background()
routinesPool := safe.NewPool(ctx)
metricRegistries := registerMetricClients(staticConfiguration.Metrics)
var aviator *pilot.Pilot
if isPilotEnabled(staticConfiguration) {
pilotRegistry := metrics.RegisterPilot()
aviator = pilot.New(staticConfiguration.Pilot.Token, pilotRegistry, routinesPool)
routinesPool.GoCtx(func(ctx context.Context) {
aviator.Tick(ctx)
})
metricRegistries = append(metricRegistries, pilotRegistry)
}
metricsRegistry := metrics.NewMultiRegistry(metricRegistries)
accessLog := setupAccessLog(staticConfiguration.AccessLog)
chainBuilder := middleware.NewChainBuilder(*staticConfiguration, metricsRegistry, accessLog)
managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry)
client, plgs, devPlugin, err := initPlugins(staticConfiguration)
if err != nil {
return nil, err
}
pluginBuilder, err := plugins.NewBuilder(client, plgs, devPlugin)
if err != nil {
return nil, err
}
routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder, pluginBuilder)
var defaultEntryPoints []string
for name, cfg := range staticConfiguration.EntryPoints {
protocol, err := cfg.GetProtocol()
if err != nil {
// Should never happen because Traefik should not start if protocol is invalid.
log.WithoutContext().Errorf("Invalid protocol: %v", err)
}
if protocol != "udp" && name != static.DefaultInternalEntryPointName {
defaultEntryPoints = append(defaultEntryPoints, name)
}
}
sort.Strings(defaultEntryPoints)
watcher := server.NewConfigurationWatcher(
routinesPool,
providerAggregator,
time.Duration(staticConfiguration.Providers.ProvidersThrottleDuration),
defaultEntryPoints,
)
watcher.AddListener(func(conf dynamic.Configuration) {
ctx := context.Background()
tlsManager.UpdateConfigs(ctx, conf.TLS.Stores, conf.TLS.Options, conf.TLS.Certificates)
})
watcher.AddListener(func(_ dynamic.Configuration) {
metricsRegistry.ConfigReloadsCounter().Add(1)
metricsRegistry.LastConfigReloadSuccessGauge().Set(float64(time.Now().Unix()))
})
watcher.AddListener(switchRouter(routerFactory, acmeProviders, serverEntryPointsTCP, serverEntryPointsUDP, aviator))
watcher.AddListener(func(conf dynamic.Configuration) {
if metricsRegistry.IsEpEnabled() || metricsRegistry.IsSvcEnabled() {
var eps []string
for key := range serverEntryPointsTCP {
eps = append(eps, key)
}
metrics.OnConfigurationUpdate(conf, eps)
}
})
resolverNames := map[string]struct{}{}
for _, p := range acmeProviders {
resolverNames[p.ResolverName] = struct{}{}
watcher.AddListener(p.ListenConfiguration)
}
watcher.AddListener(func(config dynamic.Configuration) {
for rtName, rt := range config.HTTP.Routers {
if rt.TLS == nil || rt.TLS.CertResolver == "" {
continue
}
if _, ok := resolverNames[rt.TLS.CertResolver]; !ok {
log.WithoutContext().Errorf("the router %s uses a non-existent resolver: %s", rtName, rt.TLS.CertResolver)
}
}
})
return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, chainBuilder, accessLog), nil
}
func switchRouter(routerFactory *server.RouterFactory, acmeProviders []*acme.Provider, serverEntryPointsTCP server.TCPEntryPoints, serverEntryPointsUDP server.UDPEntryPoints, aviator *pilot.Pilot) func(conf dynamic.Configuration) {
return func(conf dynamic.Configuration) {
rtConf := runtime.NewConfig(conf)
routers, udpRouters := routerFactory.CreateRouters(rtConf)
for entryPointName, rt := range routers {
for _, p := range acmeProviders {
if p != nil && p.HTTPChallenge != nil && p.HTTPChallenge.EntryPoint == entryPointName {
rt.HTTPHandler(p.CreateHandler(rt.GetHTTPHandler()))
break
}
}
}
if aviator != nil {
aviator.SetRuntimeConfiguration(rtConf)
}
serverEntryPointsTCP.Switch(routers)
serverEntryPointsUDP.Switch(udpRouters)
}
}
// initACMEProvider creates an acme provider from the ACME part of globalConfiguration.
func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.ProviderAggregator, tlsManager *traefiktls.Manager) []*acme.Provider {
challengeStore := acme.NewLocalChallengeStore()
localStores := map[string]*acme.LocalStore{}
var resolvers []*acme.Provider
for name, resolver := range c.CertificatesResolvers {
if resolver.ACME != nil {
if localStores[resolver.ACME.Storage] == nil {
localStores[resolver.ACME.Storage] = acme.NewLocalStore(resolver.ACME.Storage)
}
p := &acme.Provider{
Configuration: resolver.ACME,
Store: localStores[resolver.ACME.Storage],
ChallengeStore: challengeStore,
ResolverName: name,
}
if err := providerAggregator.AddProvider(p); err != nil {
log.WithoutContext().Errorf("The ACME resolver %q is skipped from the resolvers list because: %v", name, err)
continue
}
p.SetTLSManager(tlsManager)
if p.TLSChallenge != nil {
tlsManager.TLSAlpnGetter = p.GetTLSALPNCertificate
}
p.SetConfigListenerChan(make(chan dynamic.Configuration))
resolvers = append(resolvers, p)
}
}
return resolvers
}
func registerMetricClients(metricsConfig *types.Metrics) []metrics.Registry {
if metricsConfig == nil {
return nil
}
var registries []metrics.Registry
if metricsConfig.Prometheus != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "prometheus"))
prometheusRegister := metrics.RegisterPrometheus(ctx, metricsConfig.Prometheus)
if prometheusRegister != nil {
registries = append(registries, prometheusRegister)
log.FromContext(ctx).Debug("Configured Prometheus metrics")
}
}
if metricsConfig.Datadog != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "datadog"))
registries = append(registries, metrics.RegisterDatadog(ctx, metricsConfig.Datadog))
log.FromContext(ctx).Debugf("Configured Datadog metrics: pushing to %s once every %s",
metricsConfig.Datadog.Address, metricsConfig.Datadog.PushInterval)
}
if metricsConfig.StatsD != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "statsd"))
registries = append(registries, metrics.RegisterStatsd(ctx, metricsConfig.StatsD))
log.FromContext(ctx).Debugf("Configured StatsD metrics: pushing to %s once every %s",
metricsConfig.StatsD.Address, metricsConfig.StatsD.PushInterval)
}
if metricsConfig.InfluxDB != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "influxdb"))
registries = append(registries, metrics.RegisterInfluxDB(ctx, metricsConfig.InfluxDB))
log.FromContext(ctx).Debugf("Configured InfluxDB metrics: pushing to %s once every %s",
metricsConfig.InfluxDB.Address, metricsConfig.InfluxDB.PushInterval)
}
return registries
}
func setupAccessLog(conf *types.AccessLog) *accesslog.Handler {
if conf == nil {
return nil
}
accessLoggerMiddleware, err := accesslog.NewHandler(conf)
if err != nil {
log.WithoutContext().Warnf("Unable to create access logger : %v", err)
return nil
}
return accessLoggerMiddleware
}
func configureLogging(staticConfiguration *static.Configuration) {
// configure default log flags
stdlog.SetFlags(stdlog.Lshortfile | stdlog.LstdFlags)
// configure log level
// an explicitly defined log level always has precedence. if none is
// given and debug mode is disabled, the default is ERROR, and DEBUG
// otherwise.
levelStr := "error"
if staticConfiguration.Log != nil && staticConfiguration.Log.Level != "" {
levelStr = strings.ToLower(staticConfiguration.Log.Level)
}
level, err := logrus.ParseLevel(levelStr)
if err != nil {
log.WithoutContext().Errorf("Error getting level: %v", err)
}
log.SetLevel(level)
var logFile string
if staticConfiguration.Log != nil && len(staticConfiguration.Log.FilePath) > 0 {
logFile = staticConfiguration.Log.FilePath
}
// configure log format
var formatter logrus.Formatter
if staticConfiguration.Log != nil && staticConfiguration.Log.Format == "json" {
formatter = &logrus.JSONFormatter{}
} else {
disableColors := len(logFile) > 0
formatter = &logrus.TextFormatter{DisableColors: disableColors, FullTimestamp: true, DisableSorting: true}
}
log.SetFormatter(formatter)
if len(logFile) > 0 {
dir := filepath.Dir(logFile)
if err := os.MkdirAll(dir, 0o755); err != nil {
log.WithoutContext().Errorf("Failed to create log path %s: %s", dir, err)
}
err = log.OpenFile(logFile)
logrus.RegisterExitHandler(func() {
if err := log.CloseFile(); err != nil {
log.WithoutContext().Errorf("Error while closing log: %v", err)
}
})
if err != nil {
log.WithoutContext().Errorf("Error while opening log file %s: %v", logFile, err)
}
}
}
func checkNewVersion() {
ticker := time.Tick(24 * time.Hour)
safe.Go(func() {
for time.Sleep(10 * time.Minute); ; <-ticker {
version.CheckNewVersion()
}
})
}
func stats(staticConfiguration *static.Configuration) {
logger := log.WithoutContext()
if staticConfiguration.Global.SendAnonymousUsage {
logger.Info(`Stats collection is enabled.`)
logger.Info(`Many thanks for contributing to Traefik's improvement by allowing us to receive anonymous information from your configuration.`)
logger.Info(`Help us improve Traefik by leaving this feature on :)`)
logger.Info(`More details on: https://doc.traefik.io/traefik/contributing/data-collection/`)
collect(staticConfiguration)
} else {
logger.Info(`
Stats collection is disabled.
Help us improve Traefik by turning this feature on :)
More details on: https://doc.traefik.io/traefik/contributing/data-collection/
`)
}
}
func collect(staticConfiguration *static.Configuration) {
ticker := time.Tick(24 * time.Hour)
safe.Go(func() {
for time.Sleep(10 * time.Minute); ; <-ticker {
if err := collector.Collect(staticConfiguration); err != nil {
log.WithoutContext().Debug(err)
}
}
})
}

View File

@@ -1,60 +0,0 @@
package version
import (
"fmt"
"io"
"os"
"runtime"
"text/template"
"github.com/traefik/paerser/cli"
"github.com/traefik/traefik/v2/pkg/version"
)
var versionTemplate = `Version: {{.Version}}
Codename: {{.Codename}}
Go version: {{.GoVersion}}
Built: {{.BuildTime}}
OS/Arch: {{.Os}}/{{.Arch}}`
// NewCmd builds a new Version command.
func NewCmd() *cli.Command {
return &cli.Command{
Name: "version",
Description: `Shows the current Traefik version.`,
Configuration: nil,
Run: func(_ []string) error {
if err := GetPrint(os.Stdout); err != nil {
return err
}
fmt.Print("\n")
return nil
},
}
}
// GetPrint write Printable version.
func GetPrint(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)
}

314
configuration.go Normal file
View File

@@ -0,0 +1,314 @@
package main
import (
"errors"
"fmt"
"github.com/containous/traefik/acme"
"github.com/containous/traefik/provider"
"github.com/containous/traefik/types"
"regexp"
"strings"
"time"
)
// TraefikConfiguration holds GlobalConfiguration and other stuff
type TraefikConfiguration struct {
GlobalConfiguration
ConfigFile string `short:"c" description:"Configuration file to use (TOML)."`
}
// GlobalConfiguration holds global configuration (with providers, etc.).
// It's populated from the traefik configuration file passed as an argument to the binary.
type GlobalConfiguration struct {
GraceTimeOut int64 `short:"g" description:"Duration to give active requests a chance to finish during hot-reload"`
Debug bool `short:"d" description:"Enable debug mode"`
AccessLogsFile string `description:"Access logs file"`
TraefikLogsFile string `description:"Traefik logs file"`
LogLevel string `short:"l" description:"Log level"`
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"`
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags."`
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
ProvidersThrottleDuration time.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time."`
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"`
Retry *Retry `description:"Enable retry sending request if network error"`
Docker *provider.Docker `description:"Enable Docker backend"`
File *provider.File `description:"Enable File backend"`
Web *WebProvider `description:"Enable Web backend"`
Marathon *provider.Marathon `description:"Enable Marathon backend"`
Consul *provider.Consul `description:"Enable Consul backend"`
ConsulCatalog *provider.ConsulCatalog `description:"Enable Consul catalog backend"`
Etcd *provider.Etcd `description:"Enable Etcd backend"`
Zookeeper *provider.Zookepper `description:"Enable Zookeeper backend"`
Boltdb *provider.BoltDb `description:"Enable Boltdb backend"`
Kubernetes *provider.Kubernetes `description:"Enable Kubernetes backend"`
}
// DefaultEntryPoints holds default entry points
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 errors.New("Bad DefaultEntryPoints format: " + 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 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 fmt.Sprintf("%+v", *ep)
}
// Set is the method to set the flag value, part of the flag.Value interface.
// Set's argument is a string to be parsed to set the flag.
// It's a comma-separated list, so we split it.
func (ep *EntryPoints) Set(value string) error {
regex := regexp.MustCompile("(?:Name:(?P<Name>\\S*))\\s*(?:Address:(?P<Address>\\S*))?\\s*(?:TLS:(?P<TLS>\\S*))?\\s*((?P<TLSACME>TLS))?\\s*(?:Redirect.EntryPoint:(?P<RedirectEntryPoint>\\S*))?\\s*(?:Redirect.Regex:(?P<RedirectRegex>\\S*))?\\s*(?:Redirect.Replacement:(?P<RedirectReplacement>\\S*))?")
match := regex.FindAllStringSubmatch(value, -1)
if match == nil {
return errors.New("Bad EntryPoints format: " + value)
}
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,
}
} else if len(result["TLSACME"]) > 0 {
tls = &TLS{
Certificates: Certificates{},
}
}
var redirect *Redirect
if len(result["RedirectEntryPoint"]) > 0 || len(result["RedirectRegex"]) > 0 || len(result["RedirectReplacement"]) > 0 {
redirect = &Redirect{
EntryPoint: result["RedirectEntryPoint"],
Regex: result["RedirectRegex"],
Replacement: result["RedirectReplacement"],
}
}
(*ep)[result["Name"]] = &EntryPoint{
Address: result["Address"],
TLS: tls,
Redirect: redirect,
}
return nil
}
// Get return the EntryPoints map
func (ep *EntryPoints) Get() interface{} { return EntryPoints(*ep) }
// SetValue sets the EntryPoints map with val
func (ep *EntryPoints) SetValue(val interface{}) {
*ep = EntryPoints(val.(EntryPoints))
}
// Type is type of the struct
func (ep *EntryPoints) Type() string {
return fmt.Sprint("entrypoints")
}
// 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 `description:"Number of attempts"`
}
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
//default Docker
var defaultDocker provider.Docker
defaultDocker.Watch = true
defaultDocker.Endpoint = "unix:///var/run/docker.sock"
// default File
var defaultFile provider.File
defaultFile.Watch = true
defaultFile.Filename = "" //needs equivalent to viper.ConfigFileUsed()
// default Web
var defaultWeb WebProvider
defaultWeb.Address = ":8080"
// default Marathon
var defaultMarathon provider.Marathon
defaultMarathon.Watch = true
defaultMarathon.Endpoint = "http://127.0.0.1:8080"
defaultMarathon.ExposedByDefault = true
defaultMarathon.Constraints = []types.Constraint{}
// default Consul
var defaultConsul provider.Consul
defaultConsul.Watch = true
defaultConsul.Endpoint = "127.0.0.1:8500"
defaultConsul.Prefix = "traefik"
defaultConsul.Constraints = []types.Constraint{}
// default ConsulCatalog
var defaultConsulCatalog provider.ConsulCatalog
defaultConsulCatalog.Endpoint = "127.0.0.1:8500"
defaultConsulCatalog.Constraints = []types.Constraint{}
// default Etcd
var defaultEtcd provider.Etcd
defaultEtcd.Watch = true
defaultEtcd.Endpoint = "127.0.0.1:2379"
defaultEtcd.Prefix = "/traefik"
defaultEtcd.Constraints = []types.Constraint{}
//default Zookeeper
var defaultZookeeper provider.Zookepper
defaultZookeeper.Watch = true
defaultZookeeper.Endpoint = "127.0.0.1:2181"
defaultZookeeper.Prefix = "/traefik"
defaultZookeeper.Constraints = []types.Constraint{}
//default Boltdb
var defaultBoltDb provider.BoltDb
defaultBoltDb.Watch = true
defaultBoltDb.Endpoint = "127.0.0.1:4001"
defaultBoltDb.Prefix = "/traefik"
defaultBoltDb.Constraints = []types.Constraint{}
//default Kubernetes
var defaultKubernetes provider.Kubernetes
defaultKubernetes.Watch = true
defaultKubernetes.Endpoint = "http://127.0.0.1:8080"
defaultKubernetes.Constraints = []types.Constraint{}
defaultConfiguration := GlobalConfiguration{
Docker: &defaultDocker,
File: &defaultFile,
Web: &defaultWeb,
Marathon: &defaultMarathon,
Consul: &defaultConsul,
ConsulCatalog: &defaultConsulCatalog,
Etcd: &defaultEtcd,
Zookeeper: &defaultZookeeper,
Boltdb: &defaultBoltDb,
Kubernetes: &defaultKubernetes,
Retry: &Retry{},
}
return &TraefikConfiguration{
GlobalConfiguration: defaultConfiguration,
}
}
// NewTraefikConfiguration creates a TraefikConfiguration with default values
func NewTraefikConfiguration() *TraefikConfiguration {
return &TraefikConfiguration{
GlobalConfiguration: GlobalConfiguration{
GraceTimeOut: 10,
AccessLogsFile: "",
TraefikLogsFile: "",
LogLevel: "ERROR",
EntryPoints: map[string]*EntryPoint{},
Constraints: []types.Constraint{},
DefaultEntryPoints: []string{},
ProvidersThrottleDuration: time.Duration(2 * time.Second),
MaxIdleConnsPerHost: 200,
},
ConfigFile: "",
}
}
type configs map[string]*types.Configuration

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,41 +1,6 @@
[Unit]
Description=Traefik
Documentation=https://doc.traefik.io/traefik/
#After=network-online.target
#AssertFileIsExecutable=/usr/bin/traefik
#AssertPathExists=/etc/traefik/traefik.toml
[Service]
# Run traefik as its own user (create new user with: useradd -r -s /bin/false -U -M traefik)
#User=traefik
#AmbientCapabilities=CAP_NET_BIND_SERVICE
# configure service behavior
Type=notify
#ExecStart=/usr/bin/traefik --configFile=/etc/traefik/traefik.toml
Restart=always
WatchdogSec=1s
# lock down system access
# prohibit any operating system and configuration modification
#ProtectSystem=strict
# create separate, new (and empty) /tmp and /var/tmp filesystems
#PrivateTmp=true
# make /home directories inaccessible
#ProtectHome=true
# turns off access to physical devices (/dev/...)
#PrivateDevices=true
# make kernel settings (procfs and sysfs) read-only
#ProtectKernelTunables=true
# make cgroups /sys/fs/cgroup read-only
#ProtectControlGroups=true
# allow writing of acme.json
#ReadWritePaths=/etc/traefik/acme.json
# depending on log and entrypoint configuration, you may need to allow writing to other paths, too
# limit number of processes in this unit
#LimitNPROC=1
[Install]
WantedBy=multi-user.target
ExecStart=/usr/bin/traefik --configFile=/etc/traefik.toml
Restart=on-failure

View File

@@ -1 +0,0 @@
site/

View File

@@ -1,12 +0,0 @@
{
"no-hard-tabs": false,
"MD007": { "indent": 4 },
"MD009": false,
"MD013": false,
"MD024": false,
"MD026": false,
"MD033": false,
"MD034": false,
"MD036": false,
"MD046": false
}

1
docs/CNAME Normal file
View File

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

View File

@@ -1,52 +0,0 @@
#######
# This Makefile contains all targets related to the documentation
#######
DOCS_VERIFY_SKIP ?= false
DOCS_LINT_SKIP ?= false
TRAEFIK_DOCS_BUILD_IMAGE ?= traefik-docs
TRAEFIK_DOCS_CHECK_IMAGE ?= $(TRAEFIK_DOCS_BUILD_IMAGE)-check
SITE_DIR := $(CURDIR)/site
DOCKER_RUN_DOC_PORT := 8000
DOCKER_RUN_DOC_MOUNTS := -v $(CURDIR):/mkdocs
DOCKER_RUN_DOC_OPTS := --rm $(DOCKER_RUN_DOC_MOUNTS) -p $(DOCKER_RUN_DOC_PORT):8000
# Default: generates the documentation into $(SITE_DIR)
docs: docs-clean docs-image docs-lint docs-build docs-verify
# Writer Mode: build and serve docs on http://localhost:8000 with livereload
docs-serve: docs-image
docker run $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOCS_BUILD_IMAGE) mkdocs serve
# Utilities Targets for each step
docs-image:
docker build -t $(TRAEFIK_DOCS_BUILD_IMAGE) -f docs.Dockerfile ./
docs-build: docs-image
docker run $(DOCKER_RUN_DOC_OPTS) $(TRAEFIK_DOCS_BUILD_IMAGE) sh -c "mkdocs build \
&& chown -R $(shell id -u):$(shell id -g) ./site"
docs-verify: docs-build
@if [ "$(DOCS_VERIFY_SKIP)" != "true" ]; then \
docker build -t $(TRAEFIK_DOCS_CHECK_IMAGE) -f check.Dockerfile ./; \
docker run --rm -v $(CURDIR):/app $(TRAEFIK_DOCS_CHECK_IMAGE) /verify.sh; \
else \
echo "DOCS_VERIFY_SKIP is true: no verification done."; \
fi
docs-lint:
@if [ "$(DOCS_LINT_SKIP)" != "true" ]; then \
docker build -t $(TRAEFIK_DOCS_CHECK_IMAGE) -f check.Dockerfile ./ && \
docker run --rm -v $(CURDIR):/app $(TRAEFIK_DOCS_CHECK_IMAGE) /lint.sh; \
else \
echo "DOCS_LINT_SKIP is true: no linting done."; \
fi
docs-clean:
rm -rf $(SITE_DIR)
.PHONY: all docs-verify docs docs-clean docs-build docs-lint

267
docs/basics.md Normal file
View File

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

213
docs/benchmarks.md Normal file
View File

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

View File

@@ -1,40 +0,0 @@
FROM alpine:3.10 as alpine
RUN apk --no-cache --no-progress add \
libcurl \
ruby \
ruby-bigdecimal \
ruby-etc \
ruby-ffi \
ruby-json \
ruby-nokogiri
RUN gem install html-proofer --version 3.13.0 --no-document -- --use-system-libraries
# After Ruby, some NodeJS YAY!
RUN apk --no-cache --no-progress add \
git \
nodejs \
npm
# To handle 'not get uid/gid'
RUN npm config set unsafe-perm true
RUN npm install --global \
markdownlint@0.17.2 \
markdownlint-cli@0.19.0
# Finally the shell tools we need for later
# tini helps to terminate properly all the parallelized tasks when sending CTRL-C
RUN apk --no-cache --no-progress add \
ca-certificates \
curl \
tini
COPY ./scripts/verify.sh /verify.sh
COPY ./scripts/lint.sh /lint.sh
WORKDIR /app
VOLUME ["/tmp","/app"]
ENTRYPOINT ["/sbin/tini","-g","sh"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

View File

@@ -1,4 +0,0 @@
/* Highlight */
(function(hljs) {
hljs.initHighlightingOnLoad();
})(hljs);

View File

@@ -1,24 +0,0 @@
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.

File diff suppressed because one or more lines are too long

View File

@@ -1,96 +0,0 @@
/*
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;
}

View File

@@ -1,70 +0,0 @@
.md-container {
padding-top: 0;
}
.md-content h1 {
color: var(--dark) !important;
font-weight: bold !important;
}
.md-content a {
color: var(--blue) !important;
}
.md-content a:hover {
font-weight: bold !important;
}
.md-typeset p code,
.md-typeset .codehilite,
.md-typeset .highlight {
background-color: var(--light-blue) !important;
}
.md-typeset table:not([class]) th {
background: var(--dark) !important;
color: white !important;
}
/* Front page image size */
img[src$='#small'] {
width: 150px;
}
img[src$='#medium'] {
width: 300px;
}
/* Center table and objects */
.center,
img,
.md-typeset__table {
display: block !important;
margin: 0 auto;
}
.md-typeset table:not([class]) tr td:first-child {
text-align: left;
}
.md-typeset table:not([class]) th:not([align]),
.md-typeset table:not([class]) td:not([align]) {
text-align: center;
}
article p:not([class]),
article ul:not([class]),
article ol:not([class]) {
padding-left: 0.8em !important;
}
/* 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;
}

View File

@@ -1,10 +0,0 @@
.md-footer-meta {
background-color: var(--dark);
}
.md-footer-privacy-policy {
margin: 0 .6rem;
padding: .4rem 0;
color: hsla(0,0%,100%,.3);
font-size: .64rem;
}

View File

@@ -1,484 +0,0 @@
@import url('https://fonts.googleapis.com/css?family=Rubik:300i,400,400i,500,500i,700&amp;display=swap');
.wrapper-1200 {
width: 100%;
max-width: 61rem;
margin: 0 auto;
padding: 0 .6rem;
}
@media (max-width: 700px) {
.wrapper-1200 {
padding: 0 20px;
}
}
.btn-type-1 {
outline: none;
border: none;
background-color: #1e54d5;
line-height: 1em;
border-radius: 8px;
padding: 12px 15px;
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 1.25rem;
background-image: linear-gradient(to top, rgba(0, 0, 0, 0.28) 1%, #1e54d5 99%);
font-weight: 500;
text-align: center;
color: white;
transition: all 0.2s;
}
.button--secondary {
outline: none;
border: 2px solid #1e54d5 !important;
background: transparent;
line-height: 1em;
border-radius: 8px;
padding: 9px 13px;
letter-spacing: 0;
font-size: 1.3rem;
font-weight: 500;
text-align: center;
color: #1e54d5;
transition: all 0.2s;
display: inline-block;
}
.button--secondary:hover {
color: white !important;
background: #1e54d5;
}
.button--secondary:focus {
color: white !important;
background: #1e54d5;
}
.site-header-and-placeholder-wrapper {
position: relative;
height: 64px;
}
.site-header {
position: fixed;
width: 100%;
top: 0;
left: 0;
transition: height 0.1s;
z-index: 100;
background: white;
box-shadow: 0 0 7px 0 #00000021;
border-bottom: 1px solid #e2e2e2;
height: 64px;
display: flex;
align-items: center;
font-size: 10px;
font-family: 'Rubik', -apple-system, 'BlinkMacSystemFont', 'Segoe UI',
'Helvetica Neue', sans-serif;
color: #06102a;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.site-header.scrolled {
box-shadow: 0 0 5px 0 #00000028;
position: fixed;
top: 0;
height: 52px;
}
.site-header.scrolled .site-header__title a {
font-size: 2.2em;
}
.header-placeholder {
background: none;
width: 100%;
height: 64px;
position: absolute;
}
.header-placeholder.active {
display: block;
}
.site-header .wrapper-1200 {
display: flex;
justify-content: space-between;
align-items: center;
}
.site-header .wrapper-1200 .left {
display: flex;
align-items: center;
justify-content: flex-start;
}
.site-header__logo {
max-width: 145px;
}
.site-header__title a {
color: #06102a;
font-size: 2.2em;
font-weight: 500;
transition: all 0.2s;
text-transform: uppercase;
letter-spacing: 0.02em;
}
/* Navigation */
.site-header__nav .menu-item-wrapper {
display: inline-block;
padding-left: 30px;
}
.site-header__nav .menu-item {
color: #06102a;
transition: all 0.05s;
font-size: 1.45em;
line-height: 1em;
font-weight: 500;
}
.site-header__nav .menu-item:hover {
color: #8a959e;
}
.site-header__nav .menu-item--with-icon {
display: flex;
align-items: center;
justify-content: flex-start;
}
.site-header__nav .menu-item--with-icon .title {
margin-right: 3px;
}
.site-header__nav .menu-item--with-icon .icon {
width: 20px;
height: 20px;
transition: all 0.1s;
}
.site-header__nav .menu-item--with-icon .icon svg {
stroke-width: 2.5 !important;
width: 100%;
height: 100%;
}
.site-header__nav .menu-item-wrapper--dropdown {
position: relative;
}
.site-header__nav .menu-item-wrapper--dropdown:hover .nav-dropdown-menu {
display: block;
}
.site-header__nav .nav-dropdown-menu {
display: none;
}
.nav-dropdown-menu {
position: absolute;
z-index: 500;
background: transparent;
}
.nav-dropdown-menu-wrapper {
border-radius: 8px;
box-shadow: 0 12px 40px 0 rgba(1, 10, 32, 0.24);
background: white;
margin: 8px 0;
overflow: hidden;
}
/* Products, Solutions dropdown menu */
.nav-dropdown-menu--products,
.nav-dropdown-menu--solutions {
width: 500px;
}
.nav-dropdown-menu--products .nav-dropdown-menu-wrapper,
.nav-dropdown-menu--solutions .nav-dropdown-menu-wrapper {
padding: 20px;
}
.nav-dropdown-menu--products .nav-dropdown-menu-wrapper {
height: 430px;
}
.nav-dropdown-menu--products .dm-header,
.nav-dropdown-menu--solutions .dm-header {
font-size: 1.1em;
font-weight: 500;
font-stretch: normal;
font-style: normal;
line-height: normal;
letter-spacing: 3.67px;
color: #505769;
margin-bottom: 20px;
text-transform: uppercase;
}
.nav-dropdown-menu--products .dm-item,
.nav-dropdown-menu--solutions .dm-item {
border: none;
margin: 0 0 24px;
color: #06102a;
transition: all 0.1s;
position: relative;
width: 100%;
}
.nav-dropdown-menu--products .dm-item:last-child,
.nav-dropdown-menu--solutions .dm-item:last-child {
margin-bottom: 0;
}
.nav-dropdown-menu--products .dm-item .dmi-image {
width: 104px;
height: 72px;
position: absolute;
/*background: #f4f4f4;*/
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
border-radius: 4px;
z-index: 0;
}
.nav-dropdown-menu--solutions .dm-item .dmi-image {
width: 65px;
padding: 10px;
background: white;
height: auto;
position: absolute;
z-index: 0;
}
.nav-dropdown-menu--solutions .dm-item .dmi-image img {
width: 100%;
}
.nav-dropdown-menu--products .dm-item .dmi-details,
.nav-dropdown-menu--solutions .dm-item .dmi-details {
padding: 6px 0 0 127px;
width: 100%;
background: transparent;
display: block;
color: #06102a;
position: relative;
z-index: 1;
}
.nav-dropdown-menu--products .dm-item .dmi-details:hover,
.nav-dropdown-menu--solutions .dm-item .dmi-details:hover {
color: #1e54d5;
}
.nav-dropdown-menu--products .dm-item .dmi-title,
.nav-dropdown-menu--solutions .dm-item .dmi-title {
font-size: 1.6em;
font-weight: 500;
margin: 0 0 2px;
}
.nav-dropdown-menu--products .dm-item .dmi-description,
.nav-dropdown-menu--solutions .dm-item .dmi-description {
font-size: 1.4em;
opacity: 0.7;
line-height: 1.6em;
}
.nav-dropdown-menu--products .dm-item--traefikee .dmi-image img,
.nav-dropdown-menu--solutions .dm-item--traefikee .dmi-image img {
transform: scale(1.1);
}
.nav-dropdown-menu--solutions .dm-item .dmi-details {
padding: 5px 0 0 80px;
}
.nav-dropdown-menu--solutions .dm-item:last-child {
margin-bottom: 10px;
}
/* Dropdown menu: Learn and Company */
.nav-dropdown-menu--learn {
width: 250px;
}
.nav-dropdown-menu--company {
width: 500px;
}
.nav-dropdown-menu--company .nav-dropdown-menu-wrapper {
display: grid;
grid-template-columns: 50% 50%;
}
.nav-dropdown-menu--learn .dm-left,
.nav-dropdown-menu--company .dm-left {
padding: 25px;
}
.nav-dropdown-menu--learn .dm-header,
.nav-dropdown-menu--company .dm-header {
font-size: 1.1em;
font-weight: 500;
font-stretch: normal;
font-style: normal;
line-height: normal;
letter-spacing: 3.67px;
color: #505769;
margin-bottom: 20px;
text-transform: uppercase;
}
.nav-dropdown-menu--learn .dm-item,
.nav-dropdown-menu--company .dm-item {
display: block;
font-size: 1.6em;
font-weight: 500;
color: #06102a;
margin-bottom: 15px;
}
.nav-dropdown-menu--learn .dm-item:last-child,
.nav-dropdown-menu--company .dm-item:last-child {
margin-bottom: 0;
}
.nav-dropdown-menu--learn .dm-item:hover,
.nav-dropdown-menu--company .dm-item:hover {
color: #1e54d5;
}
.dm-preview {
background: #edeff4;
overflow: hidden;
height: 100%;
display: flex;
flex-direction: column;
}
.dm-preview__feature-image {
overflow: hidden;
display: block;
}
.dm-preview__feature-image img {
width: 100%;
height: 145px;
background: #ffffff no-repeat 50%;
object-fit: cover;
vertical-align: middle;
}
.dm-preview__content {
padding: 15px;
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-direction: column;
flex: 1;
position: relative;
}
.dm-preview__tag {
display: block;
font-size: 1.2em;
color: #db7d11;
letter-spacing: 2.5px;
font-weight: 500;
margin: 0 0;
text-transform: uppercase;
}
.dm-preview__title {
font-size: 1.6em;
font-weight: 500;
line-height: 1.6em;
margin: 0;
color: #06102a;
display: block;
flex: 1;
position: relative;
z-index: 1;
padding-bottom: 20px;
}
.dm-preview .arrow-link {
justify-content: flex-start;
font-size: 1.4em;
position: absolute;
bottom: 12px;
z-index: 0;
}
/* Dropdown menu: Company */
.nav-dropdown-menu--company {
width: 450px;
}
.nav-dropdown-menu--company .dm-right {
background: #06102a;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
color: white;
padding: 20px;
}
.nav-dropdown-menu--company .dm-right p {
font-size: 1.6em;
font-weight: 500;
margin: 0 0 15px;
text-align: center;
}
.nav-dropdown-menu--company .dm-right a {
text-transform: uppercase;
line-height: 1.5em;
padding: 9px 12px;
font-size: 1.2em;
}
/* Demo */
.site-header__demo-button .button--secondary {
font-size: 1.4em;
padding: 8px 12px;
border-radius: 6px;
}
/* Drawer */
.site-header .drawer {
display: none;
}
@media (max-width: 1219px) {
.site-header__nav .menu-item-wrapper {
padding-left: 20px;
}
}
@media (max-width: 980px) {
.site-header__nav {
display: none;
}
.site-header .drawer {
display: block;
}
.site-header .right .site-header__demo-button {
display: none;
}
html [data-md-color-primary=indigo] .md-nav--primary .md-nav__title--site {
background-color: #06102a;
}
html .md-nav--primary .md-nav__title {
padding: 64px .8rem .2rem;
}
.md-search__inner {
top: 64px;
right: 0;
}
}
.md-header .md-search {
margin-right: 12.1rem;
}
.site-header__main {
display: flex;
align-items: center;
}
.dmi-image.proxy {
background-color: #24a1c1;
}
.dmi-image.mesh {
background-color: #9d0fb0;
}
.dmi-image.enterprise {
background-color: #0060e0;
}
.dmi-image.pilot {
background-color: #db7d11;
}

View File

@@ -1,101 +0,0 @@
.md-nav__link {
margin-left: -0.4rem;
padding: 0 0.4rem;
line-height: 32px;
color: var(--dark) !important;
}
.md-nav__link::after {
font-size: 16px;
vertical-align: -.25em;
}
.md-nav__toggle:checked + .md-nav__link,
.md-nav__link--active,
.md-nav__link:hover {
border-radius: 8px;
background-color: var(--light-blue) !important;
color: var(--dark) !important;
transition: background-color 0.3s ease;
}
.md-nav__link--active {
color: var(--blue) !important;
font-weight: bold;
}
.md-sidebar--primary {
background-color: white;
}
.md-sidebar--secondary .md-nav__title {
font-size: 12px;
text-transform: uppercase;
margin-bottom: 0.4rem;
padding: 0;
}
.md-sidebar--secondary .md-sidebar__scrollwrap {
border-radius: 8px;
background-color: var(--light-blue) !important;
}
.md-sidebar--secondary .md-nav__title {
padding: 0.8rem 0.4rem 0.8rem;
}
.md-sidebar--secondary .md-nav__list {
padding: 0 0.4rem 0.8rem 1.2rem;
}
.md-sidebar--secondary .md-sidebar__scrollwrap .md-nav__link {
font-weight: 300;
}
.md-sidebar--secondary
.md-sidebar__scrollwrap
.md-nav__link[data-md-state='blur'],
.md-sidebar--secondary .md-sidebar__scrollwrap .md-nav__link:hover {
color: var(--blue) !important;
font-weight: bold;
}
.md-sidebar--secondary .md-nav__item {
padding: 0 0 0 0.4rem;
}
.md-sidebar--secondary .md-nav__link {
margin-top: 0.225em;
padding: 0.1rem 0.2rem;
}
.md-sidebar--secondary li {
list-style-type: disc;
}
.md-sidebar--secondary .repo_url {
padding: 10px 0 14px 0;
}
.md-search__inner {
width: inherit;
float: inherit;
}
.md-search__input {
margin-bottom: 10px;
border-radius: 4px;
background-color: inherit;
border: 1px solid rgba(0,0,0,.07);
}
.md-search__input::placeholder {
color: rgba(0,0,0,.07);
}
@media only screen and (min-width: 60em) {
[data-md-toggle=search]:checked~.md-header .md-search__inner {
margin-top: 100px;
}
}

View File

@@ -1,24 +0,0 @@
.product-switcher {
font-size: 10px;
font-family: 'Rubik', -apple-system, 'BlinkMacSystemFont', 'Segoe UI',
'Helvetica Neue', sans-serif;
color: #06102a;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.product-switcher .menu-item-wrapper {
padding-left: 15px;
}
.product-switcher img {
margin-right: 10px;
}
.product-switcher img.gopher {
margin: 0 2px 0 16px;
}
.product-switcher .nav-dropdown-menu--products .nav-dropdown-menu-wrapper {
width: auto;
height: 335px;
}

View File

@@ -1,10 +0,0 @@
:root {
--dark: #06102a;
--blue: #04B5D1;
--light-blue: #E4F7FA;
--input-bg-color: white;
--input-color: black;
--input-placeholder-color: #bbb;
--input-border-color: #dcdcdc;
}

View File

@@ -1,10 +0,0 @@
# Advocating
Spread the Love & Tell Us about It
{: .subtitle }
There are many ways to contribute to the project, and there is one that always spark joy: when we see/read about users talking about how Traefik helps them solve their problems.
If you're talking about Traefik, [let us know](https://blog.traefik.io/spread-the-love-ba5a40aa72e7) and we'll promote your enthusiasm!
Also, if you've written about Traefik or shared useful information you'd like to promote, feel free to add links in the [dedicated wiki page on Github](https://github.com/traefik/traefik/wiki/Awesome-Traefik).

View File

@@ -1,178 +0,0 @@
# Building and Testing
Compile and Test Your Own Traefik!
{: .subtitle }
So you want to build your own Traefik binary from the sources?
Let's see how.
## 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 `dep` dependency management tool is required.
### Method 1: Using `Docker` and `Makefile`
Run make with the `binary` target.
This will create binaries for the Linux platform in the `dist` folder.
In case when you run build on CI, you may probably want to run docker in non-interactive mode. To achieve that define `DOCKER_NON_INTERACTIVE=true` environment variable.
```bash
$ make binary
docker build -t traefik-webui -f webui/Dockerfile webui
Sending build context to Docker daemon 2.686MB
Step 1/11 : FROM node:8.15.0
---> 1f6c34f7921c
[...]
Successfully built ce4ff439c06a
Successfully tagged traefik-webui:latest
[...]
docker build -t "traefik-dev:4475--feature-documentation" -f build.Dockerfile .
Sending build context to Docker daemon 279MB
Step 1/10 : FROM golang:1.14-alpine
---> f4bfb3d22bda
[...]
Successfully built 5c3c1a911277
Successfully tagged traefik-dev:4475--feature-documentation
docker run -e "TEST_CONTAINER=1" -v "/var/run/docker.sock:/var/run/docker.sock" -it -e OS_ARCH_ARG -e OS_PLATFORM_ARG -e TESTFLAGS -e VERBOSE -e VERSION -e CODENAME -e TESTDIRS -e CI -e CONTAINER=DOCKER -v "/home/ldez/sources/go/src/github.com/traefik/traefik/"dist":/go/src/github.com/traefik/traefik/"dist"" "traefik-dev:4475--feature-documentation" ./script/make.sh generate binary
---> Making bundle: generate (in .)
removed 'autogen/genstatic/gen.go'
---> Making bundle: binary (in .)
$ ls dist/
traefik*
```
The following targets can be executed outside Docker by setting the variable `PRE_TARGET` to an empty string (we don't recommend that):
- `test-unit`
- `test-integration`
- `validate`
- `binary` (the webUI is still generated by using Docker)
ex:
```bash
PRE_TARGET= make test-unit
```
### Method 2: Using `go`
Requirements:
- `go` v1.14+
- environment variable `GO111MODULE=on`
- [go-bindata](https://github.com/containous/go-bindata) `GO111MODULE=off go get -u github.com/containous/go-bindata/...`
!!! tip "Source Directory"
It is recommended that you clone Traefik into the `~/go/src/github.com/traefik/traefik` directory.
This is the official golang workspace hierarchy that will allow dependencies to be properly resolved.
!!! note "Environment"
Set your `GOPATH` and `PATH` variable to be set to `~/go` via:
```bash
export GOPATH=~/go
export PATH=$PATH:$GOPATH/bin
```
For convenience, add `GOPATH` and `PATH` 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 an output similar to:
```bash
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/<yourusername>/go"
GORACE=""
## ... and the list goes on
```
#### Build Traefik
Once you've set up your go environment and cloned the source repository, you can build Traefik.
Beforehand, you need to get [go-bindata](https://github.com/containous/go-bindata) (the first time) in order to be able to use the `go generate` command (which is part of the build process).
```bash
cd ~/go/src/github.com/traefik/traefik
# Get go-bindata. (Important: the ellipses are required.)
GO111MODULE=off go get github.com/containous/go-bindata/...
```
```bash
# Generate UI static files
rm -rf static/ autogen/; make generate-webui
# required to merge non-code components into the final binary,
# such as the web dashboard/UI
go generate
```
```bash
# Standard go build
go build ./cmd/traefik
```
You will find the Traefik executable (`traefik`) in the `~/go/src/github.com/traefik/traefik` directory.
## Testing
### Method 1: `Docker` and `make`
Run unit tests using the `test-unit` target.
Run integration tests using the `test-integration` target.
Run all tests (unit and integration) using the `test` 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/user/go/src/github/traefik/traefik/dist:/go/src/github.com/traefik/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/traefik/traefik 0.005s coverage: 4.1% of statements
Test success
```
For development purposes, you can specify which tests to run by using (only works the `test-integration` target):
```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`
Unit tests can be run from the cloned directory using `$ go test ./...` which should return `ok`, similar to:
```test
ok _/home/user/go/src/github/traefik/traefik 0.004s
```
Integration tests must be run from the `integration/` directory and require the `-integration` switch: `$ cd integration && go test -integration ./...`.

View File

@@ -1,95 +0,0 @@
# Data Collection
Understanding How Traefik is Being Used
{: .subtitle }
## Configuration Example
Understanding how you use Traefik is very important to us: it helps us improve the solution in many different ways.
For this very reason, the sendAnonymousUsage option is mandatory: we want you to take time to consider whether or not you wish to share anonymous data with us so we can benefit from your experience and use cases.
!!! example "Enabling Data Collection"
```toml tab="File (TOML)"
[global]
# Send anonymous usage data
sendAnonymousUsage = true
```
```yaml tab="File (YAML)"
global:
# Send anonymous usage data
sendAnonymousUsage: true
```
```bash tab="CLI"
# Send anonymous usage data
--global.sendAnonymousUsage
```
## Collected Data
This feature comes from the public proposal [here](https://github.com/traefik/traefik/issues/2369).
In order to help us learn more about how Traefik is being used and improve it, we collect anonymous usage statistics from running instances.
Those data help us prioritize our developments and focus on what's important for our users (for example, which provider is popular, and which is not).
### What's collected / when ?
Once a day (the first call begins 10 minutes after the start of Traefik), we collect:
- the Traefik version number
- a hash of the configuration
- an **anonymized version** of the static configuration (token, user name, password, URL, IP, domain, email, etc, are removed).
!!! info
- We do not collect the dynamic configuration information (routers & services).
- We do not collect this data to run advertising programs.
- We do not sell this data to third-parties.
### Example of Collected Data
```toml tab="Original configuration"
[entryPoints]
[entryPoints.web]
address = ":80"
[api]
[providers.docker]
endpoint = "tcp://10.10.10.10:2375"
exposedByDefault = true
swarmMode = true
[providers.docker.TLS]
ca = "dockerCA"
cert = "dockerCert"
key = "dockerKey"
insecureSkipVerify = true
```
```toml tab="Resulting Obfuscated Configuration"
[entryPoints]
[entryPoints.web]
address = ":80"
[api]
[providers.docker]
endpoint = "xxxx"
exposedByDefault = true
swarmMode = true
[providers.docker.TLS]
ca = "xxxx"
cert = "xxxx"
key = "xxxx"
insecureSkipVerify = true
```
## The Code for Data Collection
If you want to dig into more details, here is the source code of the collecting system: [collector.go](https://github.com/traefik/traefik/blob/master/pkg/collector/collector.go)
By default we anonymize all configuration fields, except fields tagged with `export=true`.

View File

@@ -1,100 +0,0 @@
# Documentation
Features Are Better When You Know How to Use Them
{: .subtitle }
You've found something unclear in the documentation and want to give a try at explaining it better?
Let's see how.
## Building Documentation
### General
This [documentation](https://doc.traefik.io/traefik/) is built with [mkdocs](https://mkdocs.org/).
### Method 1: `Docker` and `make`
You can build the documentation and test it locally (with live reloading), using the `docs` target:
```bash
$ make docs
docker build -t traefik-docs -f docs.Dockerfile .
# […]
docker run --rm -v /home/user/go/github/traefik/traefik:/mkdocs -p 8000:8000 traefik-docs mkdocs serve
# […]
[I 170828 20:47:48 server:283] Serving on http://0.0.0.0:8000
[I 170828 20:47:48 handlers:60] Start watching changes
[I 170828 20:47:48 handlers:62] Start detecting changes
```
!!! tip "Default URL"
Your local documentation server will run by default on [http://127.0.0.1:8000](http://127.0.0.1:8000).
If you only want to build the documentation without serving it locally, you can use the following command:
```bash
$ make docs-build
...
```
### Method 2: `mkdocs`
First, make sure you have `python` and `pip` installed.
```bash
$ python --version
Python 2.7.2
$ pip --version
pip 1.5.2
```
Then, install mkdocs with `pip`.
```bash
pip install --user -r requirements.txt
```
To build the documentation locally and serve it locally, run `mkdocs serve` from the root directory.
This will start a local server.
```bash
$ mkdocs serve
INFO - Building documentation...
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
```
### Check the Documentation
To check that the documentation meets standard expectations (no dead links, html markup validity, ...), use the `docs-verify` target.
```bash
$ make docs-verify
docker build -t traefik-docs-verify ./script/docs-verify-docker-image ## Build Validator image
...
docker run --rm -v /home/travis/build/traefik/traefik:/app traefik-docs-verify ## Check for dead links and w3c compliance
=== Checking HTML content...
Running ["HtmlCheck", "ImageCheck", "ScriptCheck", "LinkCheck"] on /app/site/basics/index.html on *.html...
```
!!! note "Clean & Verify"
If you've made changes to the documentation, it's safter to clean it before verifying it.
```bash
$ make docs-clean docs-verify
...
```
!!! note "Disabling Documentation Verification"
Verification can be disabled by setting the environment variable `DOCS_VERIFY_SKIP` to `true`:
```shell
DOCS_VERIFY_SKIP=true make docs-verify
...
DOCS_LINT_SKIP is true: no linting done.
```

View File

@@ -1,176 +0,0 @@
# 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)
* Michaël Matur [@mmatur](https://github.com/mmatur)
* Gérald Croës [@geraldcroes](https://github.com/geraldcroes)
* Jean-Baptiste Doumenjou [@jbdoumenjou](https://github.com/jbdoumenjou)
* Mathieu Lonjaret [@mpl](https://github.com/mpl)
* Romain Tribotté [@rtribotte](https://github.com/rtribotte)
## Contributions Daily Meeting
* 3 Maintainers should attend to a Contributions Daily Meeting where we sort and label new issues ([is:issue label:status/0-needs-triage](https://github.com/traefik/traefik/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Astatus%2F0-needs-triage+)), and review every Pull Requests
* Every pull request should be checked during the Contributions Daily Meeting
* Even if its already assigned
* Even PR labelled with `contributor/waiting-for-corrections` or `contributor/waiting-for-feedback`
* Issues labeled with `priority/P0` and `priority/P1` should be assigned.
* Modifying an issue or a pull request (labels, assignees, milestone) is only possible:
* During the Contributions Daily Meeting
* By an assigned maintainer
* In case of emergency, if a change proposal is approved by 2 other maintainers (on Slack, Discord, Discourse, etc)
## PR review process:
* The status `needs-design-review` is only used in complex/heavy/tricky PRs.
* From `1` to `2`: 1 comment that says “design LGTM” (by a senior maintainer).
* From `2` to `3`: 3 LGTM approvals by any maintainer.
* If needed, a specific maintainer familiar with a particular domain can be requested for the review.
* If a PR has been implemented in pair programming, one peer's LGTM goes into the review for free
* Amending someone else's pull request is authorized only in emergency, if a rebase is needed, or if the initial contributor is silent
We use [PRM](https://github.com/ldez/prm) to manage locally pull requests.
## Bots
### [Myrmica Lobicornis](https://github.com/traefik/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.
To preserve commits, add `bot/merge-method-rebase` before `status/3-needs-merge`.
The status `status/4-merge-in-progress` is only used by the bot.
If the bot is not able to perform the merge, the label `bot/need-human-merge` is added.
In such a situation, solve the conflicts/CI/... and then remove the label `bot/need-human-merge`.
To prevent the bot from automatically merging a PR, add the label `bot/no-merge`.
The label `bot/light-review` decreases the number of required LGTM from 3 to 1.
This label is used when:
* Updating the vendors from previously reviewed PRs
* Merging branches into the master
* Preparing the release
### [Myrmica Bibikoffi](https://github.com/traefik/bibikoffi/)
* closes stale issues [cron]
* use some criterion as number of days between creation, last update, labels, ...
### [Myrmica Aloba](https://github.com/traefik/aloba)
Manage GitHub labels.
* Add labels on new PR [GitHub WebHook]
* Add milestone to a new PR based on a branch version (1.4, 1.3, ...) [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
A maintainer that looks at an issue/PR must define its `kind/*`, `area/*`, and `status/*`.
### Status - Workflow
The `status/*` labels represent the desired state in the workflow.
* `status/0-needs-triage`: all the new issues and PRs have this status. _[bot only]_
* `status/1-needs-design-review`: needs a design review. **(only for PR)**
* `status/2-needs-review`: needs a code/documentation review. **(only for PR)**
* `status/3-needs-merge`: ready to merge. **(only for PR)**
* `status/4-merge-in-progress`: merge is in progress. _[bot only]_
### 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)** _[bot, humans]_
* `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`: a question. **(only for issue)**
* `kind/proposal`: a proposal that needs to be discussed.
* _Proposal issues_ are design proposals
* _Proposal PRs_ are technical prototypes that need to be refined with multiple contributors.
* `kind/bug/possible`: a possible bug that needs analysis before it is confirmed or fixed. **(only for issues)**
* `kind/bug/confirmed`: a confirmed bug (reproducible). **(only for issues)**
* `kind/bug/fix`: a bug fix. **(only for PR)**
### Resolution
* `resolution/duplicate`: a duplicate issue/PR.
* `resolution/declined`: 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`: Documentation related.
* `area/infrastructure`: CI or Traefik building scripts related.
* `area/healthcheck`: Health-check related.
* `area/logs`: Logs related.
* `area/middleware`: Middleware related.
* `area/middleware/metrics`: Metrics related. (Prometheus, StatsD, ...)
* `area/middleware/tracing`: Tracing related. (Jaeger, Zipkin, ...)
* `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/kv`: KV related.
* `area/provider/marathon`: Marathon related.
* `area/provider/mesos`: Mesos related.
* `area/provider/rancher`: Rancher related.
* `area/provider/servicefabric`: Azure service fabric related.
* `area/provider/zk`: Zoo Keeper related.
* `area/rules`: Rules related.
* `area/server`: Server related.
* `area/sticky-session`: Sticky session related.
* `area/tls`: TLS related.
* `area/websocket`: WebSocket related.
* `area/webui`: Web UI related.
### Issues Priority
* `priority/P0`: needs hot fix.
* `priority/P1`: need to be fixed in next release.
* `priority/P2`: need to be fixed in the future.
* `priority/P3`: maybe.
### PR size
Automatically set by a bot.
* `size/S`: small PR.
* `size/M`: medium PR.
* `size/L`: Large PR.

View File

@@ -1,44 +0,0 @@
# Submitting Issues
Help Us Help You!
{: .subtitle }
We use the [GitHub issue tracker](https://github.com/traefik/traefik/issues) to keep track of issues in Traefik.
The process of sorting and checking the issues is a daunting task, and requires a lot of work (more than an hour a day ... just for sorting).
To save us some time and get quicker feedback, be sure to follow the guide lines below.
!!! important "Getting Help Vs Reporting an Issue"
The issue tracker is not a general support forum, but a place to report bugs and asks for new features.
For end-user related support questions, try using first:
- the Traefik community forum: [![Join the chat at https://community.traefik.io/](https://img.shields.io/badge/style-register-green.svg?style=social&label=Discourse)](https://community.traefik.io/)
## Issue Title
The title must be short and descriptive. (~60 characters)
## Description
Follow the [issue template](https://github.com/traefik/traefik/blob/master/.github/ISSUE_TEMPLATE.md) as much as possible.
Explain us in which conditions you encountered the issue, what is your context.
Remain as clear and concise as possible
Take time to polish the format of your message so we'll enjoy reading it and working on it.
Help the readers focus on what matters, and help them understand the structure of your message (see the [Github Markdown Syntax](https://help.github.com/articles/github-flavored-markdown)).
## Feature Request
Traefik is an open-source project and aims to be the best edge router possible.
Remember when asking for new features that these must be useful to the majority (and not only useful in edge case scenarios, or hack-like setups).
Do you best to explain what you're looking for, and why it would improve Traefik for everyone.
## International English
Every maintainer / Traefik user is not a native English speaker, so if you feel sometimes that some messages sound rude, remember that it probably is a language barrier problem from someone willing to help you.

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