Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
51f2433ba5 | ||
|
df710fc89e | ||
|
cb8a8b5cbb | ||
|
7dfcdcec10 | ||
|
35c74ba56c | ||
|
89fc0d2d0e | ||
|
5306981923 | ||
|
b466413b11 | ||
|
2c411767de | ||
|
54e80492bd |
@@ -1,5 +1,5 @@
|
||||
dist/
|
||||
vendor/
|
||||
!dist/traefik
|
||||
site/
|
||||
vendor/
|
||||
.idea/
|
||||
**/*.test
|
||||
|
1
.gitattributes
vendored
@@ -1 +0,0 @@
|
||||
# vendor/github.com/go-acme/lego/providers/dns/cloudxns/cloudxns.go eol=crlf
|
126
.github/CONTRIBUTING.md
vendored
Normal 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
|
||||
```
|
77
.github/ISSUE_TEMPLATE.md
vendored
@@ -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 you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||
|
||||
-->
|
||||
|
||||
### What did you expect to see?
|
||||
|
||||
|
||||
|
||||
### What did you see instead?
|
||||
|
||||
|
||||
|
||||
### Output of `traefik version`: (_What version of Traefik are you using?_)
|
||||
|
||||
<!--
|
||||
`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)
|
||||
```
|
82
.github/ISSUE_TEMPLATE/Bug_report.md
vendored
@@ -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 you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||
|
||||
-->
|
||||
|
||||
### What did you expect to see?
|
||||
|
||||
|
||||
|
||||
### What did you see instead?
|
||||
|
||||
|
||||
|
||||
### Output of `traefik version`: (_What version of Traefik are you using?_)
|
||||
|
||||
<!--
|
||||
`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)
|
||||
```
|
35
.github/ISSUE_TEMPLATE/Feature_request.md
vendored
@@ -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 you’re facing.
|
||||
- Remain clear and concise.
|
||||
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown
|
||||
|
||||
-->
|
37
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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? -->
|
7
.github/PULL_REQUEST_TEMPLATE/mergeback.md
vendored
@@ -1,7 +0,0 @@
|
||||
### What does this PR do?
|
||||
|
||||
Merge v{{.Version}} into master
|
||||
|
||||
### Motivation
|
||||
|
||||
Be sync.
|
7
.github/PULL_REQUEST_TEMPLATE/release.md
vendored
@@ -1,7 +0,0 @@
|
||||
### What does this PR do?
|
||||
|
||||
Prepare release v{{.Version}}.
|
||||
|
||||
### Motivation
|
||||
|
||||
Create a new release.
|
29
.gitignore
vendored
@@ -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
|
||||
|
116
.golangci.toml
@@ -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"]
|
@@ -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
@@ -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
|
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
sudo rm -rf static
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
84
.travis.yml
@@ -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
5108
CHANGELOG.md
@@ -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/
|
@@ -1,4 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
- https://doc.traefik.io/traefik/contributing/submitting-pull-requests/
|
||||
- https://doc.traefik.io/traefik/contributing/submitting-issues/
|
@@ -2,5 +2,4 @@ FROM scratch
|
||||
COPY script/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY dist/traefik /
|
||||
EXPOSE 80
|
||||
VOLUME ["/tmp"]
|
||||
ENTRYPOINT ["/traefik"]
|
||||
|
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
[](https://semaphoreci.com/containous/traefik)
|
||||
[](https://doc.traefik.io/traefik)
|
||||
[](https://goreportcard.com/report/traefik/traefik)
|
||||
[](https://microbadger.com/images/traefik)
|
||||
[](https://github.com/traefik/traefik/blob/master/LICENSE.md)
|
||||
[](https://community.traefik.io/)
|
||||
[](https://twitter.com/intent/follow?screen_name=traefik)
|
||||
[](https://travis-ci.org/containous/traefik)
|
||||
[](https://docs.traefik.io)
|
||||
[](http://goreportcard.com/report/containous/traefik)
|
||||
[](https://imagelayers.io/?images=traefik)
|
||||
[](https://github.com/containous/traefik/blob/master/LICENSE.md)
|
||||
[](https://traefik.herokuapp.com)
|
||||
[](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.
|
||||
|
||||

|
||||
|
||||
Træfɪk can listen to your service registry/orchestrator API, and knows each time a microservice is added, removed, killed or upgraded, and can generate its configuration automatically.
|
||||
Routes to your services will be created instantly.
|
||||
|
||||
Run it and forget it!
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- 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).
|
||||
[](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.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## 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: [](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 [](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 [](https://traefik.herokuapp.com)
|
||||
|
||||
We use [Semantic Versioning](https://semver.org/).
|
||||
- Project [Mantl](https://mantl.io/) from Cisco
|
||||
|
||||
## Mailing Lists
|
||||

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

|
||||
> Apollo is an open source project to aid with building and deploying IAAS and PAAS services. It is particularly geared towards managing containerized applications across multiple hosts, and big data type workloads. Apollo leverages other open source components to provide basic mechanisms for deployment, maintenance, and scaling of infrastructure and applications.
|
||||
|
||||
## Partners
|
||||
|
||||
[](https://zenika.com)
|
||||
|
||||
Zenika is one of the leading providers of professional Open Source services and agile methodologies in
|
||||
Europe. We provide consulting, development, training and support for the world’s leading Open Source
|
||||
software products.
|
||||
|
||||
|
||||
[](https://aster.is)
|
||||
|
||||
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 
|
||||
|
479
acme/acme.go
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
@@ -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
@@ -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}
|
@@ -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: "",
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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")
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@@ -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
@@ -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
|
@@ -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
|
||||
|
@@ -1 +0,0 @@
|
||||
site/
|
@@ -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
@@ -0,0 +1 @@
|
||||
docs.traefik.io
|
@@ -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
@@ -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
|
||||
|
||||
> 
|
||||
|
||||
Let's zoom on Træfɪk and have an overview of its internal architecture:
|
||||
|
||||
|
||||

|
||||
|
||||
- Incoming requests end on [entrypoints](#entrypoints), as the name suggests, they are the network entry points into Træfɪk (listening port, SSL, traffic redirection...).
|
||||
- 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
@@ -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)
|
||||
|
@@ -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"]
|
Before Width: | Height: | Size: 361 KiB |
Before Width: | Height: | Size: 376 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 307 KiB |
Before Width: | Height: | Size: 377 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 228 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 284 KiB |
Before Width: | Height: | Size: 354 KiB |
Before Width: | Height: | Size: 339 KiB |
Before Width: | Height: | Size: 378 KiB |
Before Width: | Height: | Size: 452 KiB |
Before Width: | Height: | Size: 182 KiB |
Before Width: | Height: | Size: 209 KiB |
Before Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 186 KiB |
Before Width: | Height: | Size: 125 KiB |
@@ -1,4 +0,0 @@
|
||||
/* Highlight */
|
||||
(function(hljs) {
|
||||
hljs.initHighlightingOnLoad();
|
||||
})(hljs);
|
@@ -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.
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -1,484 +0,0 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Rubik:300i,400,400i,500,500i,700&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;
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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).
|
@@ -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 ./...`.
|
@@ -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`.
|
@@ -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.
|
||||
```
|
@@ -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 it’s 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.
|
@@ -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: [](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.
|