From 68093193184cc7d18d539dfa6876e2e98d445ec8 Mon Sep 17 00:00:00 2001 From: Ricardo Pchevuzinske Katz Date: Fri, 10 Feb 2017 12:24:16 -0200 Subject: [PATCH] Adds support for configuring stickness per Ingress --- controllers/nginx/configuration.md | 17 ++++++ .../rootfs/etc/nginx/template/nginx.tmpl | 2 +- .../ingress/annotations/stickysession/main.go | 27 ++++----- .../ingress/controller/annotations_test.go | 2 + core/pkg/ingress/types.go | 2 +- examples/stickysession/nginx/README.md | 58 +++++++++++++++++++ .../stickysession/nginx/sticky-ingress.yaml | 19 ++++++ 7 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 examples/stickysession/nginx/README.md create mode 100644 examples/stickysession/nginx/sticky-ingress.yaml diff --git a/controllers/nginx/configuration.md b/controllers/nginx/configuration.md index 77c21ca73..e9e5fb8ce 100644 --- a/controllers/nginx/configuration.md +++ b/controllers/nginx/configuration.md @@ -51,6 +51,9 @@ The following annotations are supported: |[ingress.kubernetes.io/upstream-max-fails](#custom-nginx-upstream-checks)|number| |[ingress.kubernetes.io/upstream-fail-timeout](#custom-nginx-upstream-checks)|number| |[ingress.kubernetes.io/whitelist-source-range](#whitelist-source-range)|CIDR| +|[ingress.kubernetes.io/sticky-enabled](#sticky-session)|true or false| +|[ingress.kubernetes.io/sticky-name](#sticky-session)|string| +|[ingress.kubernetes.io/sticky-hash](#sticky-session)|string| @@ -177,6 +180,20 @@ To configure this setting globally for all Ingress rules, the `whitelist-source- Please check the [whitelist](examples/whitelist/README.md) example. +### Sticky Session + +The annotation `ingress.kubernetes.io/sticky-enabled` enables stickness in all Upstreams of an Ingress. This way, a request will always be directed to the same upstream server. + +You can also specify the name of the cookie that will be used to route the requests with the annotation `ingress.kubernetes.io/sticky-name`. The default is to create a cookie named 'route'. + +The annotation `ingress.kubernetes.io/sticky-hash` defines which algorithm will be used to 'hash' the used upstream. Default value is `md5` and possible values are `md5`, `sha1` and `index`. + +This feature is implemented by the third party module *nginx-sticky-module-ng* (https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng). + +The workflow used to define which upstream server will be used is explained here: https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/raw/08a395c66e425540982c00482f55034e1fee67b6/docs/sticky.pdf + + + ### **Allowed parameters in configuration ConfigMap** **proxy-body-size:** Sets the maximum allowed size of the client request body. See NGINX [client_max_body_size](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size). diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index bb3b04506..34bfea851 100644 --- a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl +++ b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl @@ -186,7 +186,7 @@ http { {{range $name, $upstream := $backends}} upstream {{$upstream.Name}} { {{ if $upstream.StickySession.Enabled }} - sticky hash={{$upstream.StickySession.Hash}} route={{$upstream.StickySession.Hash}} httponly; + sticky hash={{$upstream.StickySession.Hash}} name={{$upstream.StickySession.Name}} httponly; {{ else }} least_conn; {{ end }} diff --git a/core/pkg/ingress/annotations/stickysession/main.go b/core/pkg/ingress/annotations/stickysession/main.go index ce9609896..20bae2c8e 100644 --- a/core/pkg/ingress/annotations/stickysession/main.go +++ b/core/pkg/ingress/annotations/stickysession/main.go @@ -19,10 +19,11 @@ package stickysession import ( "regexp" + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/ingress/core/pkg/ingress/annotations/parser" - ing_errors "k8s.io/ingress/core/pkg/ingress/errors" ) const ( @@ -59,28 +60,28 @@ func NewParser() parser.IngressAnnotation { // rule used to configure the sticky directives func (a sticky) Parse(ing *extensions.Ingress) (interface{}, error) { // Check if the sticky is enabled - se, _ := parser.GetBoolAnnotation(stickyEnabled, ing) + se, err := parser.GetBoolAnnotation(stickyEnabled, ing) + if err != nil { + se = false + } + + glog.V(3).Infof("Ingress %v: Setting stickness to %v", ing.Name, se) // Get the Sticky Cookie Name - sn, _ := parser.GetStringAnnotation(stickyName, ing) + sn, err := parser.GetStringAnnotation(stickyName, ing) - if sn == "" { + if err != nil || sn == "" { + glog.V(3).Infof("Ingress %v: No value found in annotation %v. Using the default %v", ing.Name, stickyName, defaultStickyName) sn = defaultStickyName } - sh, _ := parser.GetStringAnnotation(stickyHash, ing) + sh, err := parser.GetStringAnnotation(stickyHash, ing) - if sh == "" { + if err != nil || !stickyHashRegex.MatchString(sh) { + glog.V(3).Infof("Invalid or no annotation value found in Ingress %v: %v: %v. Setting it to default %v", ing.Name, stickyHash, sh, defaultStickyHash) sh = defaultStickyHash } - if !stickyHashRegex.MatchString(sh) { - return &StickyConfig{ - Name: "", - Enabled: false, - Hash: "", - }, ing_errors.NewInvalidAnnotationContent(stickyHash, sh) - return &StickyConfig{ Name: sn, Enabled: se, diff --git a/core/pkg/ingress/controller/annotations_test.go b/core/pkg/ingress/controller/annotations_test.go index 942508bab..80f9c99ca 100644 --- a/core/pkg/ingress/controller/annotations_test.go +++ b/core/pkg/ingress/controller/annotations_test.go @@ -196,6 +196,8 @@ func TestStickySession(t *testing.T) { {map[string]string{annotationStickyEnabled: "true", annotationStickyHash: "md5", annotationStickyName: "route"}, true, "md5", "route"}, {map[string]string{annotationStickyEnabled: "true", annotationStickyHash: "", annotationStickyName: "xpto"}, true, "md5", "xpto"}, {map[string]string{annotationStickyEnabled: "true", annotationStickyHash: "", annotationStickyName: ""}, true, "md5", "route"}, + {map[string]string{}, false, "md5", "route"}, + {nil, false, "md5", "route"}, } for _, foo := range fooAnns { diff --git a/core/pkg/ingress/types.go b/core/pkg/ingress/types.go index 89f426a89..a43e1d7a3 100644 --- a/core/pkg/ingress/types.go +++ b/core/pkg/ingress/types.go @@ -136,7 +136,7 @@ type Backend struct { // Endpoints contains the list of endpoints currently running Endpoints []Endpoint `json:"endpoints"` // StickySession contains the StickyConfig object with stickness configuration - StickySession *stickysession.StickyConfig `json:"stickysession"` + StickySession stickysession.StickyConfig `json:"stickysession,omitempty"` } // Endpoint describes a kubernetes endpoint in an backend diff --git a/examples/stickysession/nginx/README.md b/examples/stickysession/nginx/README.md new file mode 100644 index 000000000..ba5bf9abc --- /dev/null +++ b/examples/stickysession/nginx/README.md @@ -0,0 +1,58 @@ +# Sticky Session + +This example demonstrates how to Stickness in a Ingress. + +## Prerequisites + +You will need to make sure you Ingress targets exactly one Ingress +controller by specifying the [ingress.class annotation](/examples/PREREQUISITES.md#ingress-class), +and that you have an ingress controller [running](/examples/deployment) in your cluster. + +Also, you need to have a deployment with replica > 1. Using a deployment with only one replica doesn't set the 'sticky' cookie. + +## Deployment + +The following command instructs the controller to set Stickness in all Upstreams of an Ingress + +```console +$ kubectl create -f sticky-ingress.yaml +``` + +## Validation + +You can confirm that the Ingress works. + +```console +$ kubectl describe ing nginx-test +Name: nginx-test +Namespace: default +Address: +Default backend: default-http-backend:80 (10.180.0.4:8080,10.240.0.2:8080) +Rules: + Host Path Backends + ---- ---- -------- + stickyingress.example.com + / nginx-service:80 () +Annotations: + sticky-enabled: true + sticky-hash: sha1 + sticky-name: route +Events: + FirstSeen LastSeen Count From SubObjectPath Type Reason Message + --------- -------- ----- ---- ------------- -------- ------ ------- + 7s 7s 1 {nginx-ingress-controller } Normal CREATE default/nginx-test + + +$ curl -I http://stickyingress.example.com +HTTP/1.1 200 OK +Server: nginx/1.11.9 +Date: Fri, 10 Feb 2017 14:11:12 GMT +Content-Type: text/html +Content-Length: 612 +Connection: keep-alive +Set-Cookie: route=a9907b79b248140b56bb13723f72b67697baac3d; Path=/; HttpOnly +Last-Modified: Tue, 24 Jan 2017 14:02:19 GMT +ETag: "58875e6b-264" +Accept-Ranges: bytes +``` +In the example avove, you can see a line containing the 'Set-Cookie: route' setting the right defined stickness cookie. \ No newline at end of file diff --git a/examples/stickysession/nginx/sticky-ingress.yaml b/examples/stickysession/nginx/sticky-ingress.yaml new file mode 100644 index 000000000..fe7dd42b3 --- /dev/null +++ b/examples/stickysession/nginx/sticky-ingress.yaml @@ -0,0 +1,19 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: nginx-test + annotations: + kubernetes.io/ingress.class: "nginx" + ingress.kubernetes.io/sticky-enabled: "true" + ingress.kubernetes.io/sticky-name: "route" + ingress.kubernetes.io/sticky-hash: "sha1" + +spec: + rules: + - host: stickyingress.example.com + http: + paths: + - backend: + serviceName: nginx-service + servicePort: 80 + path: /