diff --git a/controllers/nginx/configuration.md b/controllers/nginx/configuration.md index b018cecb2b..6a35a61a6e 100644 --- a/controllers/nginx/configuration.md +++ b/controllers/nginx/configuration.md @@ -64,6 +64,7 @@ The following annotations are supported: |[ingress.kubernetes.io/upstream-fail-timeout](#custom-nginx-upstream-checks)|number| |[ingress.kubernetes.io/whitelist-source-range](#whitelist-source-range)|CIDR| |[ingress.kubernetes.io/server-alias](#server-alias)|string| +|[ingress.kubernetes.io/client-body-buffer-size](#client-body-buffer-size)|string| #### Custom NGINX template @@ -172,6 +173,23 @@ the new server configuration will take place over the alias configuration. For more information please see http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name +### Client Body Buffer Size + +Sets buffer size for reading client request body per location. In case the request body is larger than the buffer, +the whole body or only its part is written to a temporary file. By default, buffer size is equal to two memory pages. +This is 8K on x86, other 32-bit platforms, and x86-64. It is usually 16K on other 64-bit platforms. This annotation is +applied to each location provided in the ingress rule. + +*Note:* The annotation value must be given in a valid format otherwise the +For example to set the client-body-buffer-size the following can be done: +* `ingress.kubernetes.io/client-body-buffer-size: "1000"` # 1000 bytes +* `ingress.kubernetes.io/client-body-buffer-size: 1k` # 1 kilobyte +* `ingress.kubernetes.io/client-body-buffer-size: 1K` # 1 kilobyte +* `ingress.kubernetes.io/client-body-buffer-size: 1m` # 1 megabyte +* `ingress.kubernetes.io/client-body-buffer-size: 1M` # 1 megabyte + +For more information please see http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size + ### External Authentication To use an existing service that provides authentication the Ingress rule can be annotated with `ingress.kubernetes.io/auth-url` to indicate the URL where the HTTP request should be sent. diff --git a/controllers/nginx/pkg/template/template.go b/controllers/nginx/pkg/template/template.go index a3cc83840e..679d8c21f6 100644 --- a/controllers/nginx/pkg/template/template.go +++ b/controllers/nginx/pkg/template/template.go @@ -251,7 +251,7 @@ func buildAuthResponseHeaders(input interface{}) []string { func buildLogFormatUpstream(input interface{}) string { cfg, ok := input.(config.Configuration) if !ok { - glog.Errorf("error an ingress.buildLogFormatUpstream type but %T was returned", input) + glog.Errorf("error an ingress.buildLogFormatUpstream type but %T was returned", input) } return cfg.BuildLogFormatUpstream() diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index 1ee7bc9bf0..c1c06292f7 100644 --- a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl +++ b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl @@ -549,6 +549,9 @@ stream { proxy_ssl_server_name on; client_max_body_size "{{ $location.Proxy.BodySize }}"; + {{ if $location.ClientBodyBufferSize }} + client_body_buffer_size {{ $location.ClientBodyBufferSize }}; + {{ end }} set $target {{ $location.ExternalAuth.URL }}; proxy_pass $target; @@ -615,6 +618,9 @@ stream { {{ end }} client_max_body_size "{{ $location.Proxy.BodySize }}"; + {{ if $location.ClientBodyBufferSize }} + client_body_buffer_size {{ $location.ClientBodyBufferSize }}; + {{ end }} proxy_set_header Host $best_http_host; diff --git a/core/pkg/ingress/annotations/clientbodybuffersize/main.go b/core/pkg/ingress/annotations/clientbodybuffersize/main.go new file mode 100644 index 0000000000..b722c484d6 --- /dev/null +++ b/core/pkg/ingress/annotations/clientbodybuffersize/main.go @@ -0,0 +1,41 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package clientbodybuffersize + +import ( + extensions "k8s.io/api/extensions/v1beta1" + + "k8s.io/ingress/core/pkg/ingress/annotations/parser" +) + +const ( + annotation = "ingress.kubernetes.io/client-body-buffer-size" +) + +type clientBodyBufferSize struct { +} + +// NewParser creates a new clientBodyBufferSize annotation parser +func NewParser() parser.IngressAnnotation { + return clientBodyBufferSize{} +} + +// Parse parses the annotations contained in the ingress rule +// used to add an client-body-buffer-size to the provided locations +func (a clientBodyBufferSize) Parse(ing *extensions.Ingress) (interface{}, error) { + return parser.GetStringAnnotation(annotation, ing) +} \ No newline at end of file diff --git a/core/pkg/ingress/annotations/clientbodybuffersize/main_test.go b/core/pkg/ingress/annotations/clientbodybuffersize/main_test.go new file mode 100644 index 0000000000..8ed6e0c386 --- /dev/null +++ b/core/pkg/ingress/annotations/clientbodybuffersize/main_test.go @@ -0,0 +1,59 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package clientbodybuffersize + +import ( + "testing" + + api "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestParse(t *testing.T) { + ap := NewParser() + if ap == nil { + t.Fatalf("expected a parser.IngressAnnotation but returned nil") + } + + testCases := []struct { + annotations map[string]string + expected string + }{ + {map[string]string{annotation: "8k"}, "8k"}, + {map[string]string{annotation: "16k"}, "16k"}, + {map[string]string{annotation: ""}, ""}, + {map[string]string{}, ""}, + {nil, ""}, + } + + ing := &extensions.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: extensions.IngressSpec{}, + } + + for _, testCase := range testCases { + ing.SetAnnotations(testCase.annotations) + result, _ := ap.Parse(ing) + if result != testCase.expected { + t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations) + } + } +} diff --git a/core/pkg/ingress/controller/annotations.go b/core/pkg/ingress/controller/annotations.go index 105e919a08..ba9cccb14c 100644 --- a/core/pkg/ingress/controller/annotations.go +++ b/core/pkg/ingress/controller/annotations.go @@ -39,6 +39,7 @@ import ( "k8s.io/ingress/core/pkg/ingress/annotations/sslpassthrough" "k8s.io/ingress/core/pkg/ingress/errors" "k8s.io/ingress/core/pkg/ingress/resolver" + "k8s.io/ingress/core/pkg/ingress/annotations/clientbodybuffersize" ) type extractorConfig interface { @@ -73,6 +74,7 @@ func newAnnotationExtractor(cfg extractorConfig) annotationExtractor { "SSLPassthrough": sslpassthrough.NewParser(), "ConfigurationSnippet": snippet.NewParser(), "Alias": alias.NewParser(), + "ClientBodyBufferSize": clientbodybuffersize.NewParser(), }, } } @@ -106,12 +108,13 @@ func (e *annotationExtractor) Extract(ing *extensions.Ingress) map[string]interf } const ( - secureUpstream = "SecureUpstream" - healthCheck = "HealthCheck" - sslPassthrough = "SSLPassthrough" - sessionAffinity = "SessionAffinity" - serviceUpstream = "ServiceUpstream" - serverAlias = "Alias" + secureUpstream = "SecureUpstream" + healthCheck = "HealthCheck" + sslPassthrough = "SSLPassthrough" + sessionAffinity = "SessionAffinity" + serviceUpstream = "ServiceUpstream" + serverAlias = "Alias" + clientBodyBufferSize = "ClientBodyBufferSize" ) func (e *annotationExtractor) ServiceUpstream(ing *extensions.Ingress) bool { @@ -143,6 +146,11 @@ func (e *annotationExtractor) Alias(ing *extensions.Ingress) string { return val.(string) } +func (e *annotationExtractor) ClientBodyBufferSize(ing *extensions.Ingress) string { + val, _ := e.annotations[clientBodyBufferSize].Parse(ing) + return val.(string) +} + func (e *annotationExtractor) SessionAffinity(ing *extensions.Ingress) *sessionaffinity.AffinityConfig { val, _ := e.annotations[sessionAffinity].Parse(ing) return val.(*sessionaffinity.AffinityConfig) diff --git a/core/pkg/ingress/controller/controller.go b/core/pkg/ingress/controller/controller.go index a6b2d0b54c..682ec6a3f7 100644 --- a/core/pkg/ingress/controller/controller.go +++ b/core/pkg/ingress/controller/controller.go @@ -633,6 +633,9 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress anns := ic.annotations.Extract(ing) + // setup client-buffer-body-size based on annotations + clientBufferBodySizeAnnotation := ic.annotations.ClientBodyBufferSize(ing) + for _, rule := range ing.Spec.Rules { host := rule.Host if host == "" { @@ -685,16 +688,18 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress } break } + loc.ClientBodyBufferSize = clientBufferBodySizeAnnotation } // is a new location if addLoc { glog.V(3).Infof("adding location %v in ingress rule %v/%v upstream %v", nginxPath, ing.Namespace, ing.Name, ups.Name) loc := &ingress.Location{ - Path: nginxPath, - Backend: ups.Name, - IsDefBackend: false, - Service: ups.Service, - Port: ups.Port, + Path: nginxPath, + Backend: ups.Name, + IsDefBackend: false, + Service: ups.Service, + Port: ups.Port, + ClientBodyBufferSize: clientBufferBodySizeAnnotation, } mergeLocationAnnotations(loc, anns) if loc.Redirect.FromToWWW { @@ -1060,6 +1065,9 @@ func (ic *GenericController) createServers(data []interface{}, } } + // setup client-buffer-body-size based on annotations + clientBufferBodySizeAnnotation := ic.annotations.ClientBodyBufferSize(ing) + for _, rule := range ing.Spec.Rules { host := rule.Host if host == "" { @@ -1074,11 +1082,12 @@ func (ic *GenericController) createServers(data []interface{}, Hostname: host, Locations: []*ingress.Location{ { - Path: rootLocation, - IsDefBackend: true, - Backend: un, - Proxy: ngxProxy, - Service: &api.Service{}, + Path: rootLocation, + IsDefBackend: true, + Backend: un, + Proxy: ngxProxy, + Service: &api.Service{}, + ClientBodyBufferSize: clientBufferBodySizeAnnotation, }, }, SSLPassthrough: sslpt} } diff --git a/core/pkg/ingress/types.go b/core/pkg/ingress/types.go index ba316532a3..f8a966f220 100644 --- a/core/pkg/ingress/types.go +++ b/core/pkg/ingress/types.go @@ -303,6 +303,10 @@ type Location struct { // ConfigurationSnippet contains additional configuration for the backend // to be considered in the configuration of the location ConfigurationSnippet string `json:"configuration-snippet"` + // ClientBodyBufferSize allows for the configuration of the client body + // buffer size for a specific location. + // +optional + ClientBodyBufferSize string `json:"client-body-buffer-size,omitempty"` } // SSLPassthroughBackend describes a SSL upstream server configured diff --git a/core/pkg/ingress/types_equals.go b/core/pkg/ingress/types_equals.go index 5244e6f86b..cab085f07c 100644 --- a/core/pkg/ingress/types_equals.go +++ b/core/pkg/ingress/types_equals.go @@ -379,6 +379,9 @@ func (l1 *Location) Equal(l2 *Location) bool { if l1.ConfigurationSnippet != l2.ConfigurationSnippet { return false } + if l1.ClientBodyBufferSize != l2.ClientBodyBufferSize { + return false + } return true }