Skip to content

Commit

Permalink
Add feature to allow sticky sessions per location
Browse files Browse the repository at this point in the history
  • Loading branch information
aledbf committed Jun 22, 2017
1 parent 955897a commit 83d03a1
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 14 deletions.
46 changes: 44 additions & 2 deletions controllers/nginx/pkg/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ var (
"buildRateLimitZones": buildRateLimitZones,
"buildRateLimit": buildRateLimit,
"buildResolvers": buildResolvers,
"buildUpstreamName": buildUpstreamName,
"isLocationAllowed": isLocationAllowed,
"buildLogFormatUpstream": buildLogFormatUpstream,
"buildDenyVariable": buildDenyVariable,
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion controllers/nginx/pkg/template/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
26 changes: 17 additions & 9 deletions controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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 }};
Expand Down Expand Up @@ -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) }}) {
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions core/pkg/ingress/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
5 changes: 5 additions & 0 deletions core/pkg/ingress/controller/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
},
},
}
}

Expand Down
5 changes: 3 additions & 2 deletions core/pkg/ingress/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 83d03a1

Please sign in to comment.