From 890c57f2ca1c85d84e5bad821d9deae28959c069 Mon Sep 17 00:00:00 2001 From: zhengjiajin <393926893@qq.com> Date: Sun, 13 Aug 2017 14:52:20 +0800 Subject: [PATCH] feat(#733)Support nginx bandwidth control --- controllers/nginx/pkg/config/config.go | 2 + controllers/nginx/pkg/template/template.go | 12 ++++++ .../pkg/ingress/annotations/ratelimit/main.go | 43 +++++++++++++++---- .../annotations/ratelimit/main_test.go | 26 +++++++++-- core/pkg/ingress/controller/annotations.go | 2 +- core/pkg/ingress/defaults/main.go | 11 +++++ 6 files changed, 84 insertions(+), 12 deletions(-) diff --git a/controllers/nginx/pkg/config/config.go b/controllers/nginx/pkg/config/config.go index 1a2133953..190fdb9d6 100644 --- a/controllers/nginx/pkg/config/config.go +++ b/controllers/nginx/pkg/config/config.go @@ -387,6 +387,8 @@ func NewDefault() Configuration { CustomHTTPErrors: []int{}, WhitelistSourceRange: []string{}, SkipAccessLogURLs: []string{}, + LimitRate: 0, + LimitRateAfter: 0, }, UpstreamKeepaliveConnections: 0, LimitConnZoneVariable: defaultLimitConnZoneVariable, diff --git a/controllers/nginx/pkg/template/template.go b/controllers/nginx/pkg/template/template.go index 607fb6abc..5fe735723 100644 --- a/controllers/nginx/pkg/template/template.go +++ b/controllers/nginx/pkg/template/template.go @@ -404,6 +404,18 @@ func buildRateLimit(input interface{}) []string { limits = append(limits, limit) } + if loc.RateLimit.LimitRateAfter > 0 { + limit := fmt.Sprintf("limit_rate_after %vk;", + loc.RateLimit.LimitRateAfter) + limits = append(limits, limit) + } + + if loc.RateLimit.LimitRate > 0 { + limit := fmt.Sprintf("limit_rate %vk;", + loc.RateLimit.LimitRate) + limits = append(limits, limit) + } + return limits } diff --git a/core/pkg/ingress/annotations/ratelimit/main.go b/core/pkg/ingress/annotations/ratelimit/main.go index fab90860b..acf6a32b0 100644 --- a/core/pkg/ingress/annotations/ratelimit/main.go +++ b/core/pkg/ingress/annotations/ratelimit/main.go @@ -22,12 +22,15 @@ import ( extensions "k8s.io/api/extensions/v1beta1" "k8s.io/ingress/core/pkg/ingress/annotations/parser" + "k8s.io/ingress/core/pkg/ingress/resolver" ) const ( - limitIP = "ingress.kubernetes.io/limit-connections" - limitRPS = "ingress.kubernetes.io/limit-rps" - limitRPM = "ingress.kubernetes.io/limit-rpm" + limitIP = "ingress.kubernetes.io/limit-connections" + limitRPS = "ingress.kubernetes.io/limit-rps" + limitRPM = "ingress.kubernetes.io/limit-rpm" + limitRATE = "ingress.kubernetes.io/limit-rate" + limitRATEAFTER = "ingress.kubernetes.io/limit-rate-after" // allow 5 times the specified limit as burst defBurst = 5 @@ -48,6 +51,10 @@ type RateLimit struct { RPS Zone `json:"rps"` RPM Zone `json:"rpm"` + + LimitRate int `json:"limit-rate"` + + LimitRateAfter int `json:"limit-rate-after"` } // Equal tests for equality between two RateLimit types @@ -67,6 +74,12 @@ func (rt1 *RateLimit) Equal(rt2 *RateLimit) bool { if !(&rt1.RPS).Equal(&rt2.RPS) { return false } + if rt1.LimitRate != rt2.LimitRate { + return false + } + if rt1.LimitRateAfter != rt2.LimitRateAfter { + return false + } return true } @@ -106,16 +119,26 @@ func (z1 *Zone) Equal(z2 *Zone) bool { } type ratelimit struct { + backendResolver resolver.DefaultBackend } // NewParser creates a new ratelimit annotation parser -func NewParser() parser.IngressAnnotation { - return ratelimit{} +func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation { + return ratelimit{br} } // ParseAnnotations parses the annotations contained in the ingress // rule used to rewrite the defined paths func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) { + defBackend := a.backendResolver.GetDefaultBackend() + lr, err := parser.GetIntAnnotation(limitRATE, ing) + if err != nil { + lr = defBackend.LimitRate + } + lra, err := parser.GetIntAnnotation(limitRATEAFTER, ing) + if err != nil { + lra = defBackend.LimitRateAfter + } rpm, _ := parser.GetIntAnnotation(limitRPM, ing) rps, _ := parser.GetIntAnnotation(limitRPS, ing) @@ -123,9 +146,11 @@ func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) { if rpm == 0 && rps == 0 && conn == 0 { return &RateLimit{ - Connections: Zone{}, - RPS: Zone{}, - RPM: Zone{}, + Connections: Zone{}, + RPS: Zone{}, + RPM: Zone{}, + LimitRate: lr, + LimitRateAfter: lra, }, nil } @@ -150,5 +175,7 @@ func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) { Burst: rpm * defBurst, SharedSize: defSharedSize, }, + LimitRate: lr, + LimitRateAfter: lra, }, nil } diff --git a/core/pkg/ingress/annotations/ratelimit/main_test.go b/core/pkg/ingress/annotations/ratelimit/main_test.go index 1dab15bfa..e7d405c03 100644 --- a/core/pkg/ingress/annotations/ratelimit/main_test.go +++ b/core/pkg/ingress/annotations/ratelimit/main_test.go @@ -24,6 +24,7 @@ import ( meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/ingress/core/pkg/ingress/defaults" ) func buildIngress() *extensions.Ingress { @@ -61,9 +62,19 @@ func buildIngress() *extensions.Ingress { } } +type mockBackend struct { +} + +func (m mockBackend) GetDefaultBackend() defaults.Backend { + return defaults.Backend{ + LimitRateAfter: 0, + LimitRate: 0, + } +} + func TestWithoutAnnotations(t *testing.T) { ing := buildIngress() - _, err := NewParser().Parse(ing) + _, err := NewParser(mockBackend{}).Parse(ing) if err != nil { t.Error("unexpected error with ingress without annotations") } @@ -78,7 +89,7 @@ func TestBadRateLimiting(t *testing.T) { data[limitRPM] = "0" ing.SetAnnotations(data) - _, err := NewParser().Parse(ing) + _, err := NewParser(mockBackend{}).Parse(ing) if err != nil { t.Errorf("unexpected error with invalid limits (0)") } @@ -87,9 +98,12 @@ func TestBadRateLimiting(t *testing.T) { data[limitIP] = "5" data[limitRPS] = "100" data[limitRPM] = "10" + data[limitRATEAFTER] = "100" + data[limitRATE] = "10" + ing.SetAnnotations(data) - i, err := NewParser().Parse(ing) + i, err := NewParser(mockBackend{}).Parse(ing) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -106,4 +120,10 @@ func TestBadRateLimiting(t *testing.T) { if rateLimit.RPM.Limit != 10 { t.Errorf("expected 10 in limit by rpm but %v was returend", rateLimit.RPM) } + if rateLimit.LimitRateAfter != 100 { + t.Errorf("expected 100 in limit by limitrateafter but %v was returend", rateLimit.LimitRateAfter) + } + if rateLimit.LimitRate != 10 { + t.Errorf("expected 10 in limit by limitrate but %v was returend", rateLimit.LimitRate) + } } diff --git a/core/pkg/ingress/controller/annotations.go b/core/pkg/ingress/controller/annotations.go index 7aadb9ddd..ee7a08757 100644 --- a/core/pkg/ingress/controller/annotations.go +++ b/core/pkg/ingress/controller/annotations.go @@ -62,7 +62,7 @@ func newAnnotationExtractor(cfg extractorConfig) annotationExtractor { "Whitelist": ipwhitelist.NewParser(cfg), "UsePortInRedirects": portinredirect.NewParser(cfg), "Proxy": proxy.NewParser(cfg), - "RateLimit": ratelimit.NewParser(), + "RateLimit": ratelimit.NewParser(cfg), "Redirect": rewrite.NewParser(cfg), "SecureUpstream": secureupstream.NewParser(cfg), "ServiceUpstream": serviceupstream.NewParser(), diff --git a/core/pkg/ingress/defaults/main.go b/core/pkg/ingress/defaults/main.go index 401c32d7c..adc499616 100644 --- a/core/pkg/ingress/defaults/main.go +++ b/core/pkg/ingress/defaults/main.go @@ -88,4 +88,15 @@ type Backend struct { // WhitelistSourceRange allows limiting access to certain client addresses // http://nginx.org/en/docs/http/ngx_http_access_module.html WhitelistSourceRange []string `json:"whitelist-source-range,-"` + + // Limits the rate of response transmission to a client. + // The rate is specified in bytes per second. The zero value disables rate limiting. + // The limit is set per a request, and so if a client simultaneously opens two connections, + // the overall rate will be twice as much as the specified limit. + // http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate + LimitRate int `json:"limit-rate"` + + // Sets the initial amount after which the further transmission of a response to a client will be rate limited. + // http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate_after + LimitRateAfter int `json:"limit-rate-after"` }