From 1c495d7ea45e709eba8fa4e62b7ae2ce1b1852f0 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Thu, 21 Dec 2017 22:07:37 +0100 Subject: [PATCH] feat(k8s): add rate limit annotations. --- .../kubernetes/builder_configuration_test.go | 53 +++++++++++++++++++ provider/kubernetes/kubernetes.go | 27 +++++++--- provider/kubernetes/kubernetes_test.go | 33 ++++++++++++ templates/kubernetes.tmpl | 12 +++++ 4 files changed, 117 insertions(+), 8 deletions(-) diff --git a/provider/kubernetes/builder_configuration_test.go b/provider/kubernetes/builder_configuration_test.go index edf9ab017..1ddea79e5 100644 --- a/provider/kubernetes/builder_configuration_test.go +++ b/provider/kubernetes/builder_configuration_test.go @@ -2,7 +2,9 @@ package kubernetes import ( "testing" + "time" + "github.com/containous/flaeg" "github.com/containous/traefik/tls" "github.com/containous/traefik/types" "github.com/stretchr/testify/assert" @@ -252,6 +254,57 @@ func errorBackend(backend string) func(*types.ErrorPage) { } } +func rateLimit(opts ...func(*types.RateLimit)) func(*types.Frontend) { + return func(f *types.Frontend) { + if f.RateLimit == nil { + f.RateLimit = &types.RateLimit{} + } + + for _, opt := range opts { + opt(f.RateLimit) + } + } +} + +func rateExtractorFunc(exp string) func(*types.RateLimit) { + return func(limit *types.RateLimit) { + limit.ExtractorFunc = exp + } +} + +func rateSet(name string, opts ...func(*types.Rate)) func(*types.RateLimit) { + return func(limit *types.RateLimit) { + if limit.RateSet == nil { + limit.RateSet = make(map[string]*types.Rate) + } + + if len(name) > 0 { + limit.RateSet[name] = &types.Rate{} + for _, opt := range opts { + opt(limit.RateSet[name]) + } + } + } +} + +func limitAverage(avg int64) func(*types.Rate) { + return func(rate *types.Rate) { + rate.Average = avg + } +} + +func limitBurst(burst int64) func(*types.Rate) { + return func(rate *types.Rate) { + rate.Burst = burst + } +} + +func limitPeriod(period time.Duration) func(*types.Rate) { + return func(rate *types.Rate) { + rate.Period = flaeg.Duration(period) + } +} + func passTLSCert() func(*types.Frontend) { return func(f *types.Frontend) { f.PassTLSCert = true diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index 479eee0ca..30562cfe0 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -234,6 +234,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error) EntryPoints: entryPoints, Headers: getHeader(i), Errors: errorPages, + RateLimit: getRateLimit(i), } } @@ -507,6 +508,19 @@ func getFrontendRedirect(i *v1beta1.Ingress) *types.Redirect { return nil } +func getBuffering(service *v1.Service) *types.Buffering { + if label.HasPrefix(service.Annotations, label.TraefikBackendBuffering) { + return &types.Buffering{ + MaxRequestBodyBytes: label.GetInt64Value(service.Annotations, label.TraefikBackendBufferingMaxRequestBodyBytes, 0), + MemRequestBodyBytes: label.GetInt64Value(service.Annotations, label.TraefikBackendBufferingMemRequestBodyBytes, 0), + MaxResponseBodyBytes: label.GetInt64Value(service.Annotations, label.TraefikBackendBufferingMaxResponseBodyBytes, 0), + MemResponseBodyBytes: label.GetInt64Value(service.Annotations, label.TraefikBackendBufferingMemResponseBodyBytes, 0), + RetryExpression: label.GetStringValue(service.Annotations, label.TraefikBackendBufferingRetryExpression, ""), + } + } + return nil +} + func getLoadBalancer(service *v1.Service) *types.LoadBalancer { loadBalancer := &types.LoadBalancer{ Method: "wrr", @@ -564,14 +578,11 @@ func getHeader(i *v1beta1.Ingress) *types.Headers { } } -func getBuffering(service *v1.Service) *types.Buffering { - if label.HasPrefix(service.Annotations, label.TraefikBackendBuffering) { - return &types.Buffering{ - MaxRequestBodyBytes: label.GetInt64Value(service.Annotations, label.TraefikBackendBufferingMaxRequestBodyBytes, 0), - MemRequestBodyBytes: label.GetInt64Value(service.Annotations, label.TraefikBackendBufferingMemRequestBodyBytes, 0), - MaxResponseBodyBytes: label.GetInt64Value(service.Annotations, label.TraefikBackendBufferingMaxResponseBodyBytes, 0), - MemResponseBodyBytes: label.GetInt64Value(service.Annotations, label.TraefikBackendBufferingMemResponseBodyBytes, 0), - RetryExpression: label.GetStringValue(service.Annotations, label.TraefikBackendBufferingRetryExpression, ""), +func getRateLimit(i *v1beta1.Ingress) *types.RateLimit { + if rlExtractFunc := i.Annotations[label.TraefikFrontendRateLimitExtractorFunc]; len(rlExtractFunc) > 0 { + return &types.RateLimit{ + ExtractorFunc: rlExtractFunc, + RateSet: label.ParseRateSets(i.Annotations, label.Prefix+label.BaseFrontendRateLimit, label.RegexpFrontendRateLimit), } } return nil diff --git a/provider/kubernetes/kubernetes_test.go b/provider/kubernetes/kubernetes_test.go index 207344d20..d0de4e88f 100644 --- a/provider/kubernetes/kubernetes_test.go +++ b/provider/kubernetes/kubernetes_test.go @@ -5,6 +5,7 @@ import ( "fmt" "reflect" "testing" + "time" "github.com/containous/traefik/provider/label" "github.com/containous/traefik/tls" @@ -685,6 +686,22 @@ func TestIngressAnnotations(t *testing.T) { iPaths(onePath(iPath("/errorpages"), iBackend("service1", intstr.FromInt(80))))), ), ), + buildIngress( + iNamespace("testing"), + iAnnotation(annotationKubernetesIngressClass, "traefik"), + iAnnotation(label.TraefikFrontendRateLimitExtractorFunc, "client.ip"), + iAnnotation(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitPeriod, "6"), + iAnnotation(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitAverage, "12"), + iAnnotation(label.Prefix+label.BaseFrontendRateLimit+"foo."+label.SuffixRateLimitBurst, "18"), + iAnnotation(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitPeriod, "3"), + iAnnotation(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitAverage, "6"), + iAnnotation(label.Prefix+label.BaseFrontendRateLimit+"bar."+label.SuffixRateLimitBurst, "9"), + iRules( + iRule( + iHost("rate-limit"), + iPaths(onePath(iPath("/ratelimit"), iBackend("service1", intstr.FromInt(80))))), + ), + ), } services := []*v1.Service{ @@ -785,6 +802,12 @@ func TestIngressAnnotations(t *testing.T) { server("http://example.com", weight(1))), lbMethod("wrr"), ), + backend("rate-limit/ratelimit", + servers( + server("http://example.com", weight(1)), + server("http://example.com", weight(1))), + lbMethod("wrr"), + ), ), frontends( frontend("foo/bar", @@ -863,6 +886,16 @@ func TestIngressAnnotations(t *testing.T) { route("/errorpages", "PathPrefix:/errorpages"), route("error-pages", "Host:error-pages")), ), + frontend("rate-limit/ratelimit", + headers(), + passHostHeader(), + rateLimit(rateExtractorFunc("client.ip"), + rateSet("foo", limitPeriod(6*time.Second), limitAverage(12), limitBurst(18)), + rateSet("bar", limitPeriod(3*time.Second), limitAverage(6), limitBurst(9))), + routes( + route("/ratelimit", "PathPrefix:/ratelimit"), + route("rate-limit", "Host:rate-limit")), + ), ), ) diff --git a/templates/kubernetes.tmpl b/templates/kubernetes.tmpl index 769df0a4c..97d79d139 100644 --- a/templates/kubernetes.tmpl +++ b/templates/kubernetes.tmpl @@ -75,6 +75,18 @@ {{end}} {{end}} + {{ if $frontend.RateLimit }} + [frontends."frontend-{{$frontendName}}".rateLimit] + extractorFunc = "{{ $frontend.RateLimit.ExtractorFunc }}" + [frontends."frontend-{{$frontendName}}".rateLimit.rateSet] + {{ range $limitName, $limit := $frontend.RateLimit.RateSet }} + [frontends."frontend-{{$frontendName}}".rateLimit.rateSet.{{ $limitName }}] + period = "{{ $limit.Period }}" + average = {{ $limit.Average }} + burst = {{ $limit.Burst }} + {{end}} + {{end}} + {{if $frontend.Headers }} [frontends."{{$frontendName}}".headers] SSLRedirect = {{$frontend.Headers.SSLRedirect}}