Improve the session affinity feature

This commit is contained in:
Ricardo Pchevuzinske Katz 2017-02-12 21:13:39 -02:00
parent 6809319318
commit a158e5fc5a
12 changed files with 276 additions and 213 deletions

View file

@ -177,20 +177,21 @@ To configure this setting globally for all Ingress rules, the `whitelist-source-
*Note:* Adding an annotation to an Ingress rule overrides any global restriction. *Note:* Adding an annotation to an Ingress rule overrides any global restriction.
Please check the [whitelist](examples/whitelist/README.md) example. Please check the [whitelist](examples/affinity/cookie/nginx/README.md) example.
### Sticky Session ### 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. The annotation `ingress.kubernetes.io/affinity` enables and sets the affinity type 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`. #### Cookie affinity
If you use the ``cookie`` type you can also specify the name of the cookie that will be used to route the requests with the annotation `ingress.kubernetes.io/session-cookie-name`. The default is to create a cookie named 'route'.
This feature is implemented by the third party module *nginx-sticky-module-ng* (https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng). In case of NGINX the annotation `ingress.kubernetes.io/session-cookie-hash` defines which algorithm will be used to 'hash' the used upstream. Default value is `md5` and possible values are `md5`, `sha1` and `index`.
The `index` option is not hashed, an in-memory index is used instead, it's quicker and the overhead is shorter Warning: the matching against upstream servers list is inconsistent. So, at reload, if upstreams servers has changed, index values are not guaranted to correspond to the same server as before! USE IT WITH CAUTION and only if you need to!
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 In NGINX 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

View file

@ -185,8 +185,8 @@ http {
{{range $name, $upstream := $backends}} {{range $name, $upstream := $backends}}
upstream {{$upstream.Name}} { upstream {{$upstream.Name}} {
{{ if $upstream.StickySession.Enabled }} {{ if eq $upstream.SessionAffinity.AffinityType "cookie" }}
sticky hash={{$upstream.StickySession.Hash}} name={{$upstream.StickySession.Name}} httponly; sticky hash={{$upstream.SessionAffinity.CookieSessionAffinity.Hash}} name={{$upstream.SessionAffinity.CookieSessionAffinity.Name}} httponly;
{{ else }} {{ else }}
least_conn; least_conn;
{{ end }} {{ end }}

View file

@ -0,0 +1,118 @@
/*
Copyright 2016 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 sessionaffinity
import (
"regexp"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
)
const (
annotationAffinityType = "ingress.kubernetes.io/affinity"
// If a cookie with this name exists,
// its value is used as an index into the list of available backends.
annotationAffinityCookieName = "ingress.kubernetes.io/session-cookie-name"
defaultAffinityCookieName = "route"
// This is the algorithm used by nginx to generate a value for the session cookie, if
// one isn't supplied and affintiy is set to "cookie".
annotationAffinityCookieHash = "ingress.kubernetes.io/session-cookie-hash"
defaultAffinityCookieHash = "md5"
)
var (
affinityCookieHashRegex = regexp.MustCompile(`^(index|md5|sha1)$`)
)
// AffinityConfig describes the per ingress session affinity config
type AffinityConfig struct {
// The type of affinity that will be used
AffinityType string `json:"type"`
CookieAffinityConfig CookieAffinityConfig `json:"cookieconfig"`
}
// CookieAffinityConfig describes the Config of cookie type affinity
type CookieAffinityConfig struct {
// The name of the cookie that will be used in case of cookie affinity type.
Name string `json:"name"`
// The hash that will be used to encode the cookie in case of cookie affinity type
Hash string `json:"hash"`
}
type affinity struct {
}
// CookieAffinityParse gets the annotation values related to Cookie Affinity
// It also sets default values when no value or incorrect value is found
func CookieAffinityParse(ing *extensions.Ingress) *CookieAffinityConfig {
sn, err := parser.GetStringAnnotation(annotationAffinityCookieName, ing)
if err != nil || sn == "" {
glog.V(3).Infof("Ingress %v: No value found in annotation %v. Using the default %v", ing.Name, annotationAffinityCookieName, defaultAffinityCookieName)
sn = defaultAffinityCookieName
}
sh, err := parser.GetStringAnnotation(annotationAffinityCookieHash, ing)
if err != nil || !affinityCookieHashRegex.MatchString(sh) {
glog.V(3).Infof("Invalid or no annotation value found in Ingress %v: %v. Setting it to default %v", ing.Name, annotationAffinityCookieHash, defaultAffinityCookieHash)
sh = defaultAffinityCookieHash
}
return &CookieAffinityConfig{
Name: sn,
Hash: sh,
}
}
// NewParser creates a new Affinity annotation parser
func NewParser() parser.IngressAnnotation {
return affinity{}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure the affinity directives
func (a affinity) Parse(ing *extensions.Ingress) (interface{}, error) {
var cookieAffinityConfig *CookieAffinityConfig
cookieAffinityConfig = &CookieAffinityConfig{}
// Check the type of affinity that will be used
at, err := parser.GetStringAnnotation(annotationAffinityType, ing)
if err != nil {
at = ""
}
//cookieAffinityConfig = CookieAffinityParse(ing)
switch at {
case "cookie":
cookieAffinityConfig = CookieAffinityParse(ing)
default:
glog.V(3).Infof("No default affinity was found for Ingress %v", ing.Name)
}
return &AffinityConfig{
AffinityType: at,
CookieAffinityConfig: *cookieAffinityConfig,
}, nil
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package stickysession package sessionaffinity
import ( import (
"testing" "testing"
@ -59,30 +59,30 @@ func buildIngress() *extensions.Ingress {
} }
} }
func TestIngressStickySession(t *testing.T) { func TestIngressAffinityCookieConfig(t *testing.T) {
ing := buildIngress() ing := buildIngress()
data := map[string]string{} data := map[string]string{}
data[stickyEnabled] = "true" data[annotationAffinityType] = "cookie"
data[stickyHash] = "md5" data[annotationAffinityCookieHash] = "sha123"
data[stickyName] = "route1" data[annotationAffinityCookieName] = "route"
ing.SetAnnotations(data) ing.SetAnnotations(data)
sti, _ := NewParser().Parse(ing) affin, _ := NewParser().Parse(ing)
nginxSti, ok := sti.(*StickyConfig) nginxAffinity, ok := affin.(*AffinityConfig)
if !ok { if !ok {
t.Errorf("expected a StickyConfig type") t.Errorf("expected a Config type")
} }
if nginxSti.Hash != "md5" { if nginxAffinity.AffinityType != "cookie" {
t.Errorf("expected md5 as sticky-hash but returned %v", nginxSti.Hash) t.Errorf("expected cookie as sticky-type but returned %v", nginxAffinity.AffinityType)
} }
if nginxSti.Hash != "md5" { if nginxAffinity.CookieAffinityConfig.Hash != "md5" {
t.Errorf("expected md5 as sticky-hash but returned %v", nginxSti.Hash) t.Errorf("expected md5 as sticky-hash but returned %v", nginxAffinity.CookieAffinityConfig.Hash)
} }
if !nginxSti.Enabled { if nginxAffinity.CookieAffinityConfig.Name != "route" {
t.Errorf("expected sticky-enabled but returned %v", nginxSti.Enabled) t.Errorf("expected route as sticky-name but returned %v", nginxAffinity.CookieAffinityConfig.Name)
} }
} }

View file

@ -1,90 +0,0 @@
/*
Copyright 2016 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 stickysession
import (
"regexp"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
)
const (
stickyEnabled = "ingress.kubernetes.io/sticky-enabled"
stickyName = "ingress.kubernetes.io/sticky-name"
stickyHash = "ingress.kubernetes.io/sticky-hash"
defaultStickyHash = "md5"
defaultStickyName = "route"
)
var (
stickyHashRegex = regexp.MustCompile(`index|md5|sha1`)
)
// StickyConfig describes the per ingress sticky session config
type StickyConfig struct {
// The name of the cookie that will be used as stickness router.
Name string `json:"name"`
// If sticky must or must not be enabled
Enabled bool `json:"enabled"`
// The hash that will be used to encode the cookie
Hash string `json:"hash"`
}
type sticky struct {
}
// NewParser creates a new Sticky annotation parser
func NewParser() parser.IngressAnnotation {
return sticky{}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure the sticky directives
func (a sticky) Parse(ing *extensions.Ingress) (interface{}, error) {
// Check if the sticky is enabled
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, err := parser.GetStringAnnotation(stickyName, ing)
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, err := parser.GetStringAnnotation(stickyHash, ing)
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
}
return &StickyConfig{
Name: sn,
Enabled: se,
Hash: sh,
}, nil
}

View file

@ -33,8 +33,8 @@ import (
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit" "k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite" "k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
"k8s.io/ingress/core/pkg/ingress/annotations/secureupstream" "k8s.io/ingress/core/pkg/ingress/annotations/secureupstream"
"k8s.io/ingress/core/pkg/ingress/annotations/sessionaffinity"
"k8s.io/ingress/core/pkg/ingress/annotations/sslpassthrough" "k8s.io/ingress/core/pkg/ingress/annotations/sslpassthrough"
"k8s.io/ingress/core/pkg/ingress/annotations/stickysession"
"k8s.io/ingress/core/pkg/ingress/errors" "k8s.io/ingress/core/pkg/ingress/errors"
"k8s.io/ingress/core/pkg/ingress/resolver" "k8s.io/ingress/core/pkg/ingress/resolver"
) )
@ -63,8 +63,8 @@ func newAnnotationExtractor(cfg extractorConfig) annotationExtractor {
"RateLimit": ratelimit.NewParser(), "RateLimit": ratelimit.NewParser(),
"Redirect": rewrite.NewParser(cfg), "Redirect": rewrite.NewParser(cfg),
"SecureUpstream": secureupstream.NewParser(), "SecureUpstream": secureupstream.NewParser(),
"SessionAffinity": sessionaffinity.NewParser(),
"SSLPassthrough": sslpassthrough.NewParser(), "SSLPassthrough": sslpassthrough.NewParser(),
"StickySession": stickysession.NewParser(),
}, },
} }
} }
@ -101,7 +101,7 @@ const (
secureUpstream = "SecureUpstream" secureUpstream = "SecureUpstream"
healthCheck = "HealthCheck" healthCheck = "HealthCheck"
sslPassthrough = "SSLPassthrough" sslPassthrough = "SSLPassthrough"
stickySession = "StickySession" sessionAffinity = "SessionAffinity"
) )
func (e *annotationExtractor) SecureUpstream(ing *extensions.Ingress) bool { func (e *annotationExtractor) SecureUpstream(ing *extensions.Ingress) bool {
@ -119,7 +119,7 @@ func (e *annotationExtractor) SSLPassthrough(ing *extensions.Ingress) bool {
return val.(bool) return val.(bool)
} }
func (e *annotationExtractor) StickySession(ing *extensions.Ingress) *stickysession.StickyConfig { func (e *annotationExtractor) SessionAffinity(ing *extensions.Ingress) *sessionaffinity.AffinityConfig {
val, _ := e.annotations[stickySession].Parse(ing) val, _ := e.annotations[sessionAffinity].Parse(ing)
return val.(*stickysession.StickyConfig) return val.(*sessionaffinity.AffinityConfig)
} }

View file

@ -32,9 +32,9 @@ const (
annotationUpsMaxFails = "ingress.kubernetes.io/upstream-max-fails" annotationUpsMaxFails = "ingress.kubernetes.io/upstream-max-fails"
annotationUpsFailTimeout = "ingress.kubernetes.io/upstream-fail-timeout" annotationUpsFailTimeout = "ingress.kubernetes.io/upstream-fail-timeout"
annotationPassthrough = "ingress.kubernetes.io/ssl-passthrough" annotationPassthrough = "ingress.kubernetes.io/ssl-passthrough"
annotationStickyEnabled = "ingress.kubernetes.io/sticky-enabled" annotationAffinityType = "ingress.kubernetes.io/affinity"
annotationStickyName = "ingress.kubernetes.io/sticky-name" annotationAffinityCookieName = "ingress.kubernetes.io/session-cookie-name"
annotationStickyHash = "ingress.kubernetes.io/sticky-hash" annotationAffinityCookieHash = "ingress.kubernetes.io/session-cookie-hash"
) )
type mockCfg struct { type mockCfg struct {
@ -183,38 +183,38 @@ func TestSSLPassthrough(t *testing.T) {
} }
} }
func TestStickySession(t *testing.T) { func TestAffinitySession(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{}) ec := newAnnotationExtractor(mockCfg{})
ing := buildIngress() ing := buildIngress()
fooAnns := []struct { fooAnns := []struct {
annotations map[string]string annotations map[string]string
enabled bool affinitytype string
hash string hash string
name string name string
}{ }{
{map[string]string{annotationStickyEnabled: "true", annotationStickyHash: "md5", annotationStickyName: "route"}, true, "md5", "route"}, {map[string]string{annotationAffinityType: "cookie", annotationAffinityCookieHash: "md5", annotationAffinityCookieName: "route"}, "cookie", "md5", "route"},
{map[string]string{annotationStickyEnabled: "true", annotationStickyHash: "", annotationStickyName: "xpto"}, true, "md5", "xpto"}, {map[string]string{annotationAffinityType: "cookie", annotationAffinityCookieHash: "xpto", annotationAffinityCookieName: "route1"}, "cookie", "md5", "route1"},
{map[string]string{annotationStickyEnabled: "true", annotationStickyHash: "", annotationStickyName: ""}, true, "md5", "route"}, {map[string]string{annotationAffinityType: "cookie", annotationAffinityCookieHash: "", annotationAffinityCookieName: ""}, "cookie", "md5", "route"},
{map[string]string{}, false, "md5", "route"}, {map[string]string{}, "", "", ""},
{nil, false, "md5", "route"}, {nil, "", "", ""},
} }
for _, foo := range fooAnns { for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations) ing.SetAnnotations(foo.annotations)
r := ec.StickySession(ing) r := ec.SessionAffinity(ing)
t.Logf("Testing pass %v %v %v", foo.affinitytype, foo.hash, foo.name)
if r == nil { if r == nil {
t.Errorf("Returned nil but expected a StickySesion.StickyConfig") t.Errorf("Returned nil but expected a SessionAffinity.AffinityConfig")
continue continue
} }
if r.Hash != foo.hash { if r.CookieAffinityConfig.Hash != foo.hash {
t.Errorf("Returned %v but expected %v for Hash", r.Hash, foo.hash) t.Errorf("Returned %v but expected %v for Hash", r.CookieAffinityConfig.Hash, foo.hash)
} }
if r.Name != foo.name { if r.CookieAffinityConfig.Name != foo.name {
t.Errorf("Returned %v but expected %v for Name", r.Name, foo.name) t.Errorf("Returned %v but expected %v for Name", r.CookieAffinityConfig.Name, foo.name)
} }
} }
} }

View file

@ -700,7 +700,7 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
secUpstream := ic.annotations.SecureUpstream(ing) secUpstream := ic.annotations.SecureUpstream(ing)
hz := ic.annotations.HealthCheck(ing) hz := ic.annotations.HealthCheck(ing)
sticky := ic.annotations.StickySession(ing) affinity := ic.annotations.SessionAffinity(ing)
var defBackend string var defBackend string
if ing.Spec.Backend != nil { if ing.Spec.Backend != nil {
@ -740,10 +740,12 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
if !upstreams[name].Secure { if !upstreams[name].Secure {
upstreams[name].Secure = secUpstream upstreams[name].Secure = secUpstream
} }
if !upstreams[name].StickySession.Enabled || upstreams[name].StickySession.Name == "" || upstreams[name].StickySession.Hash == "" { if upstreams[name].SessionAffinity.AffinityType == "" {
upstreams[name].StickySession.Enabled = sticky.Enabled upstreams[name].SessionAffinity.AffinityType = affinity.AffinityType
upstreams[name].StickySession.Name = sticky.Name if affinity.AffinityType == "cookie" {
upstreams[name].StickySession.Hash = sticky.Hash upstreams[name].SessionAffinity.CookieSessionAffinity.Name = affinity.CookieAffinityConfig.Name
upstreams[name].SessionAffinity.CookieSessionAffinity.Hash = affinity.CookieAffinityConfig.Hash
}
} }
svcKey := fmt.Sprintf("%v/%v", ing.GetNamespace(), path.Backend.ServiceName) svcKey := fmt.Sprintf("%v/%v", ing.GetNamespace(), path.Backend.ServiceName)

View file

@ -26,7 +26,6 @@ import (
"k8s.io/ingress/core/pkg/ingress/annotations/proxy" "k8s.io/ingress/core/pkg/ingress/annotations/proxy"
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit" "k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite" "k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
"k8s.io/ingress/core/pkg/ingress/annotations/stickysession"
"k8s.io/ingress/core/pkg/ingress/defaults" "k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/ingress/core/pkg/ingress/resolver" "k8s.io/ingress/core/pkg/ingress/resolver"
) )
@ -136,7 +135,25 @@ type Backend struct {
// Endpoints contains the list of endpoints currently running // Endpoints contains the list of endpoints currently running
Endpoints []Endpoint `json:"endpoints"` Endpoints []Endpoint `json:"endpoints"`
// StickySession contains the StickyConfig object with stickness configuration // StickySession contains the StickyConfig object with stickness configuration
StickySession stickysession.StickyConfig `json:"stickysession,omitempty"`
SessionAffinity SessionAffinityConfig
}
// SessionAffinityConfig describes different affinity configurations for new sessions.
// Once a session is mapped to a backend based on some affinity setting, it
// retains that mapping till the backend goes down, or the ingress controller
// restarts. Exactly one of these values will be set on the upstream, since multiple
// affinity values are incompatible. Once set, the backend makes no guarantees
// about honoring updates.
type SessionAffinityConfig struct {
AffinityType string `json:"name"`
CookieSessionAffinity CookieSessionAffinity
}
// CookieSessionAffinity defines the structure used in Affinity configured by Cookies.
type CookieSessionAffinity struct {
Name string `json:"name"`
Hash string `json:"hash"`
} }
// Endpoint describes a kubernetes endpoint in an backend // Endpoint describes a kubernetes endpoint in an backend

View file

@ -0,0 +1,73 @@
# Sticky Session
This example demonstrates how to achieve session affinity using cookies
## 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.
You will also need to deploy multiple replicas of your application that show up as endpoints for the Service referenced in the Ingress object, to test session stickyness.
Using a deployment with only one replica doesn't set the 'sticky' cookie.
## Deployment
Session stickyness is achieved through 3 annotations on the Ingress, as shown in the [example](sticky-ingress.yaml).
|Name|Description|Values|
|ingress.kubernetes.io/affinity|Sets the affinity type|string (in NGINX only ``cookie`` is possible|
|ingress.kubernetes.io/session-cookie-name|Name of the cookie that will be used|string (default to route)|
|ingress.kubernetes.io/session-cookie-hash|Type of hash that will be used in cookie value|sha1/md5/index|
You can create the ingress to test this
```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 (<none>)
Annotations:
affinity: cookie
session-cookie-hash: sha1
session-cookie-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 above, you can see a line containing the 'Set-Cookie: route' setting the right defined stickness cookie.
This cookie is created by NGINX containing the hash of the used upstream in that request.
If the user changes this cookie, NGINX creates a new one and redirect the user to another upstream.
If the backend pool grows up NGINX will keep sending the requests through the same server of the first request, even if it's overloaded.
When the backend server is removed, the requests are then re-routed to another upstream server and NGINX creates a new cookie, as the previous hash became invalid.

View file

@ -4,9 +4,9 @@ metadata:
name: nginx-test name: nginx-test
annotations: annotations:
kubernetes.io/ingress.class: "nginx" kubernetes.io/ingress.class: "nginx"
ingress.kubernetes.io/sticky-enabled: "true" ingress.kubernetes.io/affinity: "cookie"
ingress.kubernetes.io/sticky-name: "route" ingress.kubernetes.io/session-cookie-name: "route"
ingress.kubernetes.io/sticky-hash: "sha1" ingress.kubernetes.io/session-cookie-hash: "sha1"
spec: spec:
rules: rules:

View file

@ -1,58 +0,0 @@
# 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 (<none>)
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.