Add feature to allow sticky sessions per location
This commit is contained in:
parent
955897a431
commit
83d03a19a6
6 changed files with 77 additions and 14 deletions
|
@ -135,6 +135,7 @@ var (
|
||||||
"buildRateLimitZones": buildRateLimitZones,
|
"buildRateLimitZones": buildRateLimitZones,
|
||||||
"buildRateLimit": buildRateLimit,
|
"buildRateLimit": buildRateLimit,
|
||||||
"buildResolvers": buildResolvers,
|
"buildResolvers": buildResolvers,
|
||||||
|
"buildUpstreamName": buildUpstreamName,
|
||||||
"isLocationAllowed": isLocationAllowed,
|
"isLocationAllowed": isLocationAllowed,
|
||||||
"buildLogFormatUpstream": buildLogFormatUpstream,
|
"buildLogFormatUpstream": buildLogFormatUpstream,
|
||||||
"buildDenyVariable": buildDenyVariable,
|
"buildDenyVariable": buildDenyVariable,
|
||||||
|
@ -257,7 +258,7 @@ func buildLogFormatUpstream(input interface{}) string {
|
||||||
// (specified through the ingress.kubernetes.io/rewrite-to annotation)
|
// (specified through the ingress.kubernetes.io/rewrite-to annotation)
|
||||||
// If the annotation ingress.kubernetes.io/add-base-url:"true" is specified it will
|
// 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
|
// 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)
|
backends := b.([]*ingress.Backend)
|
||||||
location, ok := loc.(*ingress.Location)
|
location, ok := loc.(*ingress.Location)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -267,17 +268,23 @@ func buildProxyPass(b interface{}, loc interface{}) string {
|
||||||
path := location.Path
|
path := location.Path
|
||||||
proto := "http"
|
proto := "http"
|
||||||
|
|
||||||
|
upstreamName := location.Backend
|
||||||
for _, backend := range backends {
|
for _, backend := range backends {
|
||||||
if backend.Name == location.Backend {
|
if backend.Name == location.Backend {
|
||||||
if backend.Secure || backend.SSLPassthrough {
|
if backend.Secure || backend.SSLPassthrough {
|
||||||
proto = "https"
|
proto = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isSticky(host, location, backend.SessionAffinity.CookieSessionAffinity.Locations) {
|
||||||
|
upstreamName = fmt.Sprintf("sticky-%v", upstreamName)
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// defProxyPass returns the default proxy_pass, just the name of the upstream
|
// 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 the path in the ingress rule is equals to the target: no special rewrite
|
||||||
if path == location.Redirect.Target {
|
if path == location.Redirect.Target {
|
||||||
return defProxyPass
|
return defProxyPass
|
||||||
|
@ -408,3 +415,38 @@ func buildDenyVariable(a interface{}) string {
|
||||||
|
|
||||||
return fmt.Sprintf("$deny_%v", denyPathSlugMap[l])
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@ func TestBuildProxyPass(t *testing.T) {
|
||||||
Backend: "upstream-name",
|
Backend: "upstream-name",
|
||||||
}
|
}
|
||||||
|
|
||||||
pp := buildProxyPass([]*ingress.Backend{}, loc)
|
pp := buildProxyPass("", []*ingress.Backend{}, loc)
|
||||||
if !strings.EqualFold(tc.ProxyPass, pp) {
|
if !strings.EqualFold(tc.ProxyPass, pp) {
|
||||||
t.Errorf("%s: expected \n'%v'\nbut returned \n'%v'", k, tc.ProxyPass, pp)
|
t.Errorf("%s: expected \n'%v'\nbut returned \n'%v'", k, tc.ProxyPass, pp)
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,16 +225,25 @@ http {
|
||||||
proxy_pass_header Server;
|
proxy_pass_header Server;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{range $name, $upstream := $backends}}
|
{{ range $name, $upstream := $backends }}
|
||||||
upstream {{$upstream.Name}} {
|
|
||||||
{{ if eq $upstream.SessionAffinity.AffinityType "cookie" }}
|
{{ if eq $upstream.SessionAffinity.AffinityType "cookie" }}
|
||||||
sticky hash={{$upstream.SessionAffinity.CookieSessionAffinity.Hash}} name={{$upstream.SessionAffinity.CookieSessionAffinity.Name}} httponly;
|
upstream sticky-{{ $upstream.Name }} {
|
||||||
{{ else }}
|
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
|
# Load balance algorithm; empty for round robin, which is the default
|
||||||
{{ if ne $cfg.LoadBalanceAlgorithm "round_robin" }}
|
{{ if ne $cfg.LoadBalanceAlgorithm "round_robin" }}
|
||||||
{{ $cfg.LoadBalanceAlgorithm }};
|
{{ $cfg.LoadBalanceAlgorithm }};
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ if (gt $cfg.UpstreamKeepaliveConnections 0) }}
|
{{ if (gt $cfg.UpstreamKeepaliveConnections 0) }}
|
||||||
keepalive {{ $cfg.UpstreamKeepaliveConnections }};
|
keepalive {{ $cfg.UpstreamKeepaliveConnections }};
|
||||||
|
@ -343,8 +352,7 @@ http {
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
location {{ $path }} {
|
location {{ $path }} {
|
||||||
set $proxy_upstream_name "{{ $location.Backend }}";
|
set $proxy_upstream_name "{{ buildUpstreamName $server.Hostname $backends $location }}";
|
||||||
|
|
||||||
{{ if isLocationAllowed $location }}
|
{{ if isLocationAllowed $location }}
|
||||||
{{ if gt (len $location.Whitelist.CIDR) 0 }}
|
{{ if gt (len $location.Whitelist.CIDR) 0 }}
|
||||||
if ({{ buildDenyVariable (print $server.Hostname "_" $path) }}) {
|
if ({{ buildDenyVariable (print $server.Hostname "_" $path) }}) {
|
||||||
|
@ -439,7 +447,7 @@ http {
|
||||||
{{/* Add any additional configuration defined */}}
|
{{/* Add any additional configuration defined */}}
|
||||||
{{ $location.ConfigurationSnippet }}
|
{{ $location.ConfigurationSnippet }}
|
||||||
|
|
||||||
{{ buildProxyPass $backends $location }}
|
{{ buildProxyPass $server.Hostname $backends $location }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
#{{ $location.Denied }}
|
#{{ $location.Denied }}
|
||||||
return 503;
|
return 503;
|
||||||
|
|
|
@ -791,14 +791,21 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
|
||||||
if !upstreams[name].Secure {
|
if !upstreams[name].Secure {
|
||||||
upstreams[name].Secure = secUpstream.Secure
|
upstreams[name].Secure = secUpstream.Secure
|
||||||
}
|
}
|
||||||
|
|
||||||
if upstreams[name].SecureCACert.Secret == "" {
|
if upstreams[name].SecureCACert.Secret == "" {
|
||||||
upstreams[name].SecureCACert = secUpstream.CACert
|
upstreams[name].SecureCACert = secUpstream.CACert
|
||||||
}
|
}
|
||||||
|
|
||||||
if upstreams[name].SessionAffinity.AffinityType == "" {
|
if upstreams[name].SessionAffinity.AffinityType == "" {
|
||||||
upstreams[name].SessionAffinity.AffinityType = affinity.AffinityType
|
upstreams[name].SessionAffinity.AffinityType = affinity.AffinityType
|
||||||
if affinity.AffinityType == "cookie" {
|
if affinity.AffinityType == "cookie" {
|
||||||
upstreams[name].SessionAffinity.CookieSessionAffinity.Name = affinity.CookieConfig.Name
|
upstreams[name].SessionAffinity.CookieSessionAffinity.Name = affinity.CookieConfig.Name
|
||||||
upstreams[name].SessionAffinity.CookieSessionAffinity.Hash = affinity.CookieConfig.Hash
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,11 @@ func newUpstream(name string) *ingress.Backend {
|
||||||
return &ingress.Backend{
|
return &ingress.Backend{
|
||||||
Name: name,
|
Name: name,
|
||||||
Endpoints: []ingress.Endpoint{},
|
Endpoints: []ingress.Endpoint{},
|
||||||
|
SessionAffinity: ingress.SessionAffinityConfig{
|
||||||
|
CookieSessionAffinity: ingress.CookieSessionAffinity{
|
||||||
|
Locations: make(map[string][]string),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -176,6 +176,7 @@ type SessionAffinityConfig struct {
|
||||||
type CookieSessionAffinity struct {
|
type CookieSessionAffinity struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Hash string `json:"hash"`
|
Hash string `json:"hash"`
|
||||||
|
Locations map[string][]string `json:"locations,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Endpoint describes a kubernetes endpoint in a backend
|
// Endpoint describes a kubernetes endpoint in a backend
|
||||||
|
|
Loading…
Reference in a new issue