mirror of
https://github.com/containous/traefik.git
synced 2025-03-19 18:50:12 +03:00
feat(docker): add rate limit labels.
This commit is contained in:
parent
c30ebe5f90
commit
942614dd23
@ -37,20 +37,23 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
|
||||
"getStickinessCookieName": getFuncStringLabel(label.TraefikBackendLoadBalancerStickinessCookieName, label.DefaultBackendLoadbalancerStickinessCookieName),
|
||||
|
||||
// Frontend functions
|
||||
"getBackend": getBackend,
|
||||
"getPriority": getFuncStringLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
|
||||
"getPassHostHeader": getFuncStringLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader),
|
||||
"getPassTLSCert": getFuncBoolLabel(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
|
||||
"getEntryPoints": getFuncSliceStringLabel(label.TraefikFrontendEntryPoints),
|
||||
"getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic),
|
||||
"getWhitelistSourceRange": getFuncSliceStringLabel(label.TraefikFrontendWhitelistSourceRange),
|
||||
"getFrontendRule": p.getFrontendRule,
|
||||
"hasRedirect": hasRedirect,
|
||||
"getRedirectEntryPoint": getFuncStringLabel(label.TraefikFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint),
|
||||
"getRedirectRegex": getFuncStringLabel(label.TraefikFrontendRedirectRegex, ""),
|
||||
"getRedirectReplacement": getFuncStringLabel(label.TraefikFrontendRedirectReplacement, ""),
|
||||
"hasErrorPages": hasErrorPages,
|
||||
"getErrorPages": getErrorPages,
|
||||
"getBackend": getBackend,
|
||||
"getPriority": getFuncStringLabel(label.TraefikFrontendPriority, label.DefaultFrontendPriority),
|
||||
"getPassHostHeader": getFuncStringLabel(label.TraefikFrontendPassHostHeader, label.DefaultPassHostHeader),
|
||||
"getPassTLSCert": getFuncBoolLabel(label.TraefikFrontendPassTLSCert, label.DefaultPassTLSCert),
|
||||
"getEntryPoints": getFuncSliceStringLabel(label.TraefikFrontendEntryPoints),
|
||||
"getBasicAuth": getFuncSliceStringLabel(label.TraefikFrontendAuthBasic),
|
||||
"getWhitelistSourceRange": getFuncSliceStringLabel(label.TraefikFrontendWhitelistSourceRange),
|
||||
"getFrontendRule": p.getFrontendRule,
|
||||
"hasRedirect": hasRedirect,
|
||||
"getRedirectEntryPoint": getFuncStringLabel(label.TraefikFrontendRedirectEntryPoint, label.DefaultFrontendRedirectEntryPoint),
|
||||
"getRedirectRegex": getFuncStringLabel(label.TraefikFrontendRedirectRegex, ""),
|
||||
"getRedirectReplacement": getFuncStringLabel(label.TraefikFrontendRedirectReplacement, ""),
|
||||
"hasErrorPages": hasErrorPages,
|
||||
"getErrorPages": getErrorPages,
|
||||
"hasRateLimits": hasFunc(label.TraefikFrontendRateLimitExtractorFunc),
|
||||
"getRateLimitsExtractorFunc": getFuncStringLabel(label.TraefikFrontendRateLimitExtractorFunc, ""),
|
||||
"getRateLimits": getRateLimits,
|
||||
// Headers
|
||||
"hasRequestHeaders": hasFunc(label.TraefikFrontendRequestHeaders),
|
||||
"getRequestHeaders": getFuncMapLabel(label.TraefikFrontendRequestHeaders),
|
||||
@ -118,6 +121,8 @@ func (p *Provider) buildConfiguration(containersInspected []dockerData) *types.C
|
||||
"getServiceResponseHeaders": getFuncServiceMapLabel(label.SuffixFrontendResponseHeaders),
|
||||
"hasServiceErrorPages": hasServiceErrorPages,
|
||||
"getServiceErrorPages": getServiceErrorPages,
|
||||
"hasServiceRateLimits": hasFuncServiceLabel(label.SuffixFrontendRateLimitExtractorFunc),
|
||||
"getServiceRateLimits": getServiceRateLimits,
|
||||
}
|
||||
// filter containers
|
||||
filteredContainers := fun.Filter(func(container dockerData) bool {
|
||||
|
@ -182,6 +182,11 @@ func getErrorPages(container dockerData) map[string]*types.ErrorPage {
|
||||
return label.ParseErrorPages(container.Labels, prefix, label.RegexpFrontendErrorPage)
|
||||
}
|
||||
|
||||
func getRateLimits(container dockerData) map[string]*types.Rate {
|
||||
prefix := label.Prefix + label.BaseFrontendRateLimit
|
||||
return label.ParseRateSets(container.Labels, prefix, label.RegexpFrontendRateLimit)
|
||||
}
|
||||
|
||||
// Label functions
|
||||
|
||||
func getFuncInt64Label(labelName string, defaultValue int64) func(container dockerData) int64 {
|
||||
|
@ -4,7 +4,9 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/provider/label"
|
||||
"github.com/containous/traefik/types"
|
||||
docker "github.com/docker/docker/api/types"
|
||||
@ -236,6 +238,65 @@ func TestDockerBuildConfiguration(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
containers: []docker.ContainerJSON{
|
||||
containerJSON(
|
||||
name("test1"),
|
||||
labels(map[string]string{
|
||||
label.TraefikBackend: "foobar",
|
||||
label.TraefikFrontendRateLimitExtractorFunc: "client.ip",
|
||||
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitPeriod: "6",
|
||||
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitAverage: "12",
|
||||
label.Prefix + label.BaseFrontendRateLimit + "foo." + label.SuffixRateLimitBurst: "18",
|
||||
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitPeriod: "3",
|
||||
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitAverage: "6",
|
||||
label.Prefix + label.BaseFrontendRateLimit + "bar." + label.SuffixRateLimitBurst: "9",
|
||||
}),
|
||||
ports(nat.PortMap{
|
||||
"80/tcp": {},
|
||||
}),
|
||||
withNetwork("bridge", ipv4("127.0.0.1")),
|
||||
),
|
||||
},
|
||||
expectedFrontends: map[string]*types.Frontend{
|
||||
"frontend-Host-test1-docker-localhost-0": {
|
||||
EntryPoints: []string{},
|
||||
BasicAuth: []string{},
|
||||
PassHostHeader: true,
|
||||
Backend: "backend-foobar",
|
||||
Routes: map[string]types.Route{
|
||||
"route-frontend-Host-test1-docker-localhost-0": {
|
||||
Rule: "Host:test1.docker.localhost",
|
||||
},
|
||||
},
|
||||
RateLimit: &types.RateLimit{
|
||||
ExtractorFunc: "client.ip",
|
||||
RateSet: map[string]*types.Rate{
|
||||
"foo": {
|
||||
Period: flaeg.Duration(6 * time.Second),
|
||||
Average: 12,
|
||||
Burst: 18,
|
||||
},
|
||||
"bar": {
|
||||
Period: flaeg.Duration(3 * time.Second),
|
||||
Average: 6,
|
||||
Burst: 9,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedBackends: map[string]*types.Backend{
|
||||
"backend-foobar": {
|
||||
Servers: map[string]types.Server{
|
||||
"server-test1": {
|
||||
URL: "http://127.0.0.1:80",
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for caseID, test := range testCases {
|
||||
|
@ -108,6 +108,11 @@ func getServiceErrorPages(container dockerData, serviceName string) map[string]*
|
||||
return label.ParseErrorPages(serviceLabels, label.BaseFrontendErrorPage, label.RegexpBaseFrontendErrorPage)
|
||||
}
|
||||
|
||||
func getServiceRateLimits(container dockerData, serviceName string) map[string]*types.Rate {
|
||||
serviceLabels := getServiceLabels(container, serviceName)
|
||||
return label.ParseRateSets(serviceLabels, label.BaseFrontendRateLimit, label.RegexpBaseFrontendRateLimit)
|
||||
}
|
||||
|
||||
// Service label functions
|
||||
|
||||
func getFuncServiceMapLabel(labelSuffix string) func(container dockerData, serviceName string) map[string]string {
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/log"
|
||||
"github.com/containous/traefik/types"
|
||||
)
|
||||
@ -44,6 +45,12 @@ var (
|
||||
|
||||
// RegexpFrontendErrorPage used to extract error pages from label
|
||||
RegexpFrontendErrorPage = regexp.MustCompile(`^traefik\.frontend\.errors\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`)
|
||||
|
||||
// RegexpBaseFrontendRateLimit used to extract rate limits from service's label
|
||||
RegexpBaseFrontendRateLimit = regexp.MustCompile(`^frontend\.rateLimit\.rateSet\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`)
|
||||
|
||||
// RegexpFrontendRateLimit used to extract rate limits from label
|
||||
RegexpFrontendRateLimit = regexp.MustCompile(`^traefik\.frontend\.rateLimit\.rateSet\.(?P<name>[^ .]+)\.(?P<field>[^ .]+)$`)
|
||||
)
|
||||
|
||||
// ServicePropertyValues is a map of services properties
|
||||
@ -295,6 +302,58 @@ func ParseErrorPages(labels map[string]string, labelPrefix string, labelRegex *r
|
||||
return errorPages
|
||||
}
|
||||
|
||||
// ParseRateSets parse rate limits to create Rate struct
|
||||
func ParseRateSets(labels map[string]string, labelPrefix string, labelRegex *regexp.Regexp) map[string]*types.Rate {
|
||||
rateSets := make(map[string]*types.Rate)
|
||||
|
||||
for lblName, rawValue := range labels {
|
||||
if strings.HasPrefix(lblName, labelPrefix) && len(rawValue) > 0 {
|
||||
submatch := labelRegex.FindStringSubmatch(lblName)
|
||||
if len(submatch) != 3 {
|
||||
log.Errorf("Invalid rate limit label: %s, sub-match: %v", lblName, submatch)
|
||||
continue
|
||||
}
|
||||
|
||||
limitName := submatch[1]
|
||||
|
||||
ep, ok := rateSets[limitName]
|
||||
if !ok {
|
||||
ep = &types.Rate{}
|
||||
rateSets[limitName] = ep
|
||||
}
|
||||
|
||||
switch submatch[2] {
|
||||
case "period":
|
||||
var d flaeg.Duration
|
||||
err := d.Set(rawValue)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
|
||||
continue
|
||||
}
|
||||
ep.Period = d
|
||||
case "average":
|
||||
value, err := strconv.ParseInt(rawValue, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
|
||||
continue
|
||||
}
|
||||
ep.Average = value
|
||||
case "burst":
|
||||
value, err := strconv.ParseInt(rawValue, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to parse %q: %q. %v", lblName, rawValue, err)
|
||||
continue
|
||||
}
|
||||
ep.Burst = value
|
||||
default:
|
||||
log.Errorf("Invalid rate limit label: %s", lblName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return rateSets
|
||||
}
|
||||
|
||||
// IsEnabled Check if a container is enabled in Træfik
|
||||
func IsEnabled(labels map[string]string, exposedByDefault bool) bool {
|
||||
return GetBoolValue(labels, TraefikEnable, exposedByDefault)
|
||||
|
@ -2,7 +2,9 @@ package label
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containous/flaeg"
|
||||
"github.com/containous/traefik/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -1081,3 +1083,46 @@ func TestParseErrorPages(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRateSets(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
labels map[string]string
|
||||
expected map[string]*types.Rate
|
||||
}{
|
||||
{
|
||||
desc: "2 rate limits",
|
||||
labels: map[string]string{
|
||||
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitPeriod: "6",
|
||||
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitAverage: "12",
|
||||
Prefix + BaseFrontendRateLimit + "foo." + SuffixRateLimitBurst: "18",
|
||||
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitPeriod: "3",
|
||||
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitAverage: "6",
|
||||
Prefix + BaseFrontendRateLimit + "bar." + SuffixRateLimitBurst: "9",
|
||||
},
|
||||
expected: map[string]*types.Rate{
|
||||
"foo": {
|
||||
Period: flaeg.Duration(6 * time.Second),
|
||||
Average: 12,
|
||||
Burst: 18,
|
||||
},
|
||||
"bar": {
|
||||
Period: flaeg.Duration(3 * time.Second),
|
||||
Average: 6,
|
||||
Burst: 9,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rateSets := ParseRateSets(test.labels, Prefix+BaseFrontendRateLimit, RegexpFrontendRateLimit)
|
||||
|
||||
assert.EqualValues(t, test.expected, rateSets)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ const (
|
||||
SuffixFrontendPassHostHeader = "frontend.passHostHeader"
|
||||
SuffixFrontendPassTLSCert = "frontend.passTLSCert"
|
||||
SuffixFrontendPriority = "frontend.priority"
|
||||
SuffixFrontendRateLimitExtractorFunc = "frontend.rateLimit.extractorFunc"
|
||||
SuffixFrontendRedirectEntryPoint = "frontend.redirect.entryPoint"
|
||||
SuffixFrontendRedirectRegex = "frontend.redirect.regex"
|
||||
SuffixFrontendRedirectReplacement = "frontend.redirect.replacement"
|
||||
@ -83,11 +84,12 @@ const (
|
||||
TraefikFrontendPassHostHeader = Prefix + SuffixFrontendPassHostHeader
|
||||
TraefikFrontendPassTLSCert = Prefix + SuffixFrontendPassTLSCert
|
||||
TraefikFrontendPriority = Prefix + SuffixFrontendPriority
|
||||
TraefikFrontendRule = Prefix + SuffixFrontendRule
|
||||
TraefikFrontendRuleType = Prefix + SuffixFrontendRuleType
|
||||
TraefikFrontendRateLimitExtractorFunc = Prefix + SuffixFrontendRateLimitExtractorFunc
|
||||
TraefikFrontendRedirectEntryPoint = Prefix + SuffixFrontendRedirectEntryPoint
|
||||
TraefikFrontendRedirectRegex = Prefix + SuffixFrontendRedirectRegex
|
||||
TraefikFrontendRedirectReplacement = Prefix + SuffixFrontendRedirectReplacement
|
||||
TraefikFrontendRule = Prefix + SuffixFrontendRule
|
||||
TraefikFrontendRuleType = Prefix + SuffixFrontendRuleType
|
||||
TraefikFrontendValue = Prefix + SuffixFrontendValue
|
||||
TraefikFrontendWhitelistSourceRange = Prefix + SuffixFrontendWhitelistSourceRange
|
||||
TraefikFrontendRequestHeaders = Prefix + SuffixFrontendRequestHeaders
|
||||
@ -114,4 +116,8 @@ const (
|
||||
SuffixErrorPageBackend = "backend"
|
||||
SuffixErrorPageQuery = "query"
|
||||
SuffixErrorPageStatus = "status"
|
||||
BaseFrontendRateLimit = "frontend.rateLimit.rateSet."
|
||||
SuffixRateLimitPeriod = "period"
|
||||
SuffixRateLimitAverage = "average"
|
||||
SuffixRateLimitBurst = "burst"
|
||||
)
|
||||
|
@ -95,6 +95,18 @@
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ if hasServiceRateLimits $container $serviceName }}
|
||||
[frontends."frontend-{{getServiceBackend $container $serviceName}}".rateLimit]
|
||||
extractorFunc = "{{ getRateLimitsExtractorFunc $container $serviceName }}"
|
||||
[frontends."frontend-{{getServiceBackend $container $serviceName}}".rateLimit.rateSet]
|
||||
{{ range $limitName, $rateLimit := getServiceRateLimits $container $serviceName }}
|
||||
[frontends."frontend-{{getServiceBackend $container $serviceName}}".rateLimit.rateSet.{{ $limitName }}]
|
||||
period = "{{ $rateLimit.Period }}"
|
||||
average = {{ $rateLimit.Average }}
|
||||
burst = {{ $rateLimit.Burst }}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
[frontends."frontend-{{getServiceBackend $container $serviceName}}".routes."service-{{$serviceName | replace "/" "" | replace "." "-"}}"]
|
||||
rule = "{{getServiceFrontendRule $container $serviceName}}"
|
||||
|
||||
@ -155,6 +167,18 @@
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ if hasRateLimits $container }}
|
||||
[frontends."frontend-{{$frontend}}".rateLimit]
|
||||
extractorFunc = "{{ getRateLimitsExtractorFunc $container }}"
|
||||
[frontends."frontend-{{$frontend}}".rateLimit.rateSet]
|
||||
{{ range $limitName, $rateLimit := getRateLimits $container }}
|
||||
[frontends."frontend-{{$frontend}}".rateLimit.rateSet.{{ $limitName }}]
|
||||
period = "{{ $rateLimit.Period }}"
|
||||
average = {{ $rateLimit.Average }}
|
||||
burst = {{ $rateLimit.Burst }}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
[frontends."frontend-{{$frontend}}".headers]
|
||||
{{if hasSSLRedirectHeaders $container}}
|
||||
SSLRedirect = {{getSSLRedirectHeaders $container}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user