Add feature to allow sticky sessions per location

This commit is contained in:
Manuel de Brito Fontes 2017-06-15 20:43:17 -04:00
parent 955897a431
commit 83d03a19a6
6 changed files with 77 additions and 14 deletions

View file

@ -135,6 +135,7 @@ var (
"buildRateLimitZones": buildRateLimitZones,
"buildRateLimit": buildRateLimit,
"buildResolvers": buildResolvers,
"buildUpstreamName": buildUpstreamName,
"isLocationAllowed": isLocationAllowed,
"buildLogFormatUpstream": buildLogFormatUpstream,
"buildDenyVariable": buildDenyVariable,
@ -257,7 +258,7 @@ func buildLogFormatUpstream(input interface{}) string {
// (specified through the ingress.kubernetes.io/rewrite-to annotation)
// If the annotation ingress.kubernetes.io/add-base-url:"true" is specified it will
// add a base tag in the head of the response from the service
func buildProxyPass(b interface{}, loc interface{}) string {
func buildProxyPass(host string, b interface{}, loc interface{}) string {
backends := b.([]*ingress.Backend)
location, ok := loc.(*ingress.Location)
if !ok {
@ -267,17 +268,23 @@ func buildProxyPass(b interface{}, loc interface{}) string {
path := location.Path
proto := "http"
upstreamName := location.Backend
for _, backend := range backends {
if backend.Name == location.Backend {
if backend.Secure || backend.SSLPassthrough {
proto = "https"
}
if isSticky(host, location, backend.SessionAffinity.CookieSessionAffinity.Locations) {
upstreamName = fmt.Sprintf("sticky-%v", upstreamName)
}
break
}
}
// defProxyPass returns the default proxy_pass, just the name of the upstream
defProxyPass := fmt.Sprintf("proxy_pass %s://%s;", proto, location.Backend)
defProxyPass := fmt.Sprintf("proxy_pass %s://%s;", proto, upstreamName)
// if the path in the ingress rule is equals to the target: no special rewrite
if path == location.Redirect.Target {
return defProxyPass
@ -408,3 +415,38 @@ func buildDenyVariable(a interface{}) string {
return fmt.Sprintf("$deny_%v", denyPathSlugMap[l])
}
func buildUpstreamName(host string, b interface{}, loc interface{}) string {
backends := b.([]*ingress.Backend)
location, ok := loc.(*ingress.Location)
if !ok {
return ""
}
upstreamName := location.Backend
for _, backend := range backends {
if backend.Name == location.Backend {
if backend.SessionAffinity.AffinityType == "cookie" &&
isSticky(host, location, backend.SessionAffinity.CookieSessionAffinity.Locations) {
upstreamName = fmt.Sprintf("sticky-%v", upstreamName)
}
break
}
}
return upstreamName
}
func isSticky(host string, loc *ingress.Location, stickyLocations map[string][]string) bool {
if _, ok := stickyLocations[host]; ok {
for _, sl := range stickyLocations[host] {
if sl == loc.Path {
return true
}
}
}
return false
}

View file

@ -129,7 +129,7 @@ func TestBuildProxyPass(t *testing.T) {
Backend: "upstream-name",
}
pp := buildProxyPass([]*ingress.Backend{}, loc)
pp := buildProxyPass("", []*ingress.Backend{}, loc)
if !strings.EqualFold(tc.ProxyPass, pp) {
t.Errorf("%s: expected \n'%v'\nbut returned \n'%v'", k, tc.ProxyPass, pp)
}

View file

@ -225,16 +225,25 @@ http {
proxy_pass_header Server;
{{ end }}
{{range $name, $upstream := $backends}}
upstream {{$upstream.Name}} {
{{ if eq $upstream.SessionAffinity.AffinityType "cookie" }}
sticky hash={{$upstream.SessionAffinity.CookieSessionAffinity.Hash}} name={{$upstream.SessionAffinity.CookieSessionAffinity.Name}} httponly;
{{ else }}
{{ range $name, $upstream := $backends }}
{{ if eq $upstream.SessionAffinity.AffinityType "cookie" }}
upstream sticky-{{ $upstream.Name }} {
sticky hash={{ $upstream.SessionAffinity.CookieSessionAffinity.Hash }} name={{ $upstream.SessionAffinity.CookieSessionAffinity.Name }} httponly;
{{ if (gt $cfg.UpstreamKeepaliveConnections 0) }}
keepalive {{ $cfg.UpstreamKeepaliveConnections }};
{{ end }}
{{ range $server := $upstream.Endpoints }}server {{ $server.Address | formatIP }}:{{ $server.Port }} max_fails={{ $server.MaxFails }} fail_timeout={{ $server.FailTimeout }};
{{ end }}
}
{{ end }}
upstream {{ $upstream.Name }} {
# Load balance algorithm; empty for round robin, which is the default
{{ if ne $cfg.LoadBalanceAlgorithm "round_robin" }}
{{ $cfg.LoadBalanceAlgorithm }};
{{ end }}
{{ end }}
{{ if (gt $cfg.UpstreamKeepaliveConnections 0) }}
keepalive {{ $cfg.UpstreamKeepaliveConnections }};
@ -343,8 +352,7 @@ http {
{{ end }}
location {{ $path }} {
set $proxy_upstream_name "{{ $location.Backend }}";
set $proxy_upstream_name "{{ buildUpstreamName $server.Hostname $backends $location }}";
{{ if isLocationAllowed $location }}
{{ if gt (len $location.Whitelist.CIDR) 0 }}
if ({{ buildDenyVariable (print $server.Hostname "_" $path) }}) {
@ -439,7 +447,7 @@ http {
{{/* Add any additional configuration defined */}}
{{ $location.ConfigurationSnippet }}
{{ buildProxyPass $backends $location }}
{{ buildProxyPass $server.Hostname $backends $location }}
{{ else }}
#{{ $location.Denied }}
return 503;

View file

@ -791,14 +791,21 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
if !upstreams[name].Secure {
upstreams[name].Secure = secUpstream.Secure
}
if upstreams[name].SecureCACert.Secret == "" {
upstreams[name].SecureCACert = secUpstream.CACert
}
if upstreams[name].SessionAffinity.AffinityType == "" {
upstreams[name].SessionAffinity.AffinityType = affinity.AffinityType
if affinity.AffinityType == "cookie" {
upstreams[name].SessionAffinity.CookieSessionAffinity.Name = affinity.CookieConfig.Name
upstreams[name].SessionAffinity.CookieSessionAffinity.Hash = affinity.CookieConfig.Hash
if _, ok := upstreams[name].SessionAffinity.CookieSessionAffinity.Locations[rule.Host]; !ok {
upstreams[name].SessionAffinity.CookieSessionAffinity.Locations[rule.Host] = []string{}
}
upstreams[name].SessionAffinity.CookieSessionAffinity.Locations[rule.Host] = append(upstreams[name].SessionAffinity.CookieSessionAffinity.Locations[rule.Host], path.Path)
}
}

View file

@ -39,6 +39,11 @@ func newUpstream(name string) *ingress.Backend {
return &ingress.Backend{
Name: name,
Endpoints: []ingress.Endpoint{},
SessionAffinity: ingress.SessionAffinityConfig{
CookieSessionAffinity: ingress.CookieSessionAffinity{
Locations: make(map[string][]string),
},
},
}
}

View file

@ -174,8 +174,9 @@ type SessionAffinityConfig struct {
// CookieSessionAffinity defines the structure used in Affinity configured by Cookies.
type CookieSessionAffinity struct {
Name string `json:"name"`
Hash string `json:"hash"`
Name string `json:"name"`
Hash string `json:"hash"`
Locations map[string][]string `json:"locations,omitempty"`
}
// Endpoint describes a kubernetes endpoint in a backend