Skip to content

Commit

Permalink
feat(canary): support use cookie value and pattern (#8)
Browse files Browse the repository at this point in the history
* feat(canary): support use cookie value

* feat: support use cookie pattern

Co-authored-by: Lisheng Zheng <[email protected]>
  • Loading branch information
whalecold and Lisheng Zheng authored Mar 25, 2020
1 parent dacd3c3 commit 6c9f81e
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 4 deletions.
6 changes: 6 additions & 0 deletions docs/user-guide/nginx-configuration/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
|[nginx.ingress.kubernetes.io/canary-by-header-value](#canary)|string|
|[nginx.ingress.kubernetes.io/canary-by-header-pattern](#canary)|string|
|[nginx.ingress.kubernetes.io/canary-by-cookie](#canary)|string|
|[nginx.ingress.kubernetes.io/canary-by-cookie-value](#canary)|string|
|[nginx.ingress.kubernetes.io/canary-by-cookie-pattern](#canary)|string|
|[nginx.ingress.kubernetes.io/canary-weight](#canary)|number|
|[nginx.ingress.kubernetes.io/client-body-buffer-size](#client-body-buffer-size)|string|
|[nginx.ingress.kubernetes.io/configuration-snippet](#configuration-snippet)|string|
Expand Down Expand Up @@ -116,6 +118,10 @@ In some cases, you may want to "canary" a new set of changes by sending a small

* `nginx.ingress.kubernetes.io/canary-by-cookie`: The cookie to use for notifying the Ingress to route the request to the service specified in the Canary Ingress. When the cookie value is set to `always`, it will be routed to the canary. When the cookie is set to `never`, it will never be routed to the canary. For any other value, the cookie will be ignored and the request compared against the other canary rules by precedence.

* `nginx.ingress.kubernetes.io/canary-by-cookie-value`: The cookie value to match for notifying the Ingress to route the request to the service specified in the Canary Ingress. When the request cookie is set to this value, it will be routed to the canary. For any other header value, the header will be ignored and the request compared against the other canary rules by precedence. This annotation has to be used together with . The annotation is an extension of the `nginx.ingress.kubernetes.io/canary-by-cookie` to allow customizing the header value instead of using hardcoded values. It doesn't have any effect if the `nginx.ingress.kubernetes.io/canary-by-cookie` annotation is not defined.

* `nginx.ingress.kubernetes.io/canary-by-cookie-pattern`: This works the same way as `canary-by-cookie-value` except it does PCRE Regex matching. Note that when `canary-by-cookie-value` is set this annotation will be ignored. When the given Regex causes error during request processing, the request will be considered as not matching.

* `nginx.ingress.kubernetes.io/canary-weight`: The integer based (0 - 100) percent of random requests that should be routed to the service specified in the canary Ingress. A weight of 0 implies that no requests will be sent to the service in the Canary ingress by this canary rule. A weight of 100 means implies all requests will be sent to the alternative service specified in the Ingress.

Canary rules are evaluated in order of precedence. Precedence is as follows:
Expand Down
14 changes: 12 additions & 2 deletions internal/ingress/annotations/canary/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package canary

import (
extensions "k8s.io/api/extensions/v1beta1"

"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver"
Expand All @@ -36,6 +35,8 @@ type Config struct {
HeaderValue string
HeaderPattern string
Cookie string
CookieValue string
CookiePattern string
}

// NewParser parses the ingress for canary related annotations
Expand Down Expand Up @@ -79,8 +80,17 @@ func (c canary) Parse(ing *extensions.Ingress) (interface{}, error) {
config.Cookie = ""
}

config.CookieValue, err = parser.GetStringAnnotation("canary-by-cookie-value", ing)
if err != nil {
config.CookieValue = ""
}

config.CookiePattern, err = parser.GetStringAnnotation("canary-by-cookie-pattern", ing)
if err != nil {
config.CookiePattern = ""
}
if !config.Enabled && (config.Weight > 0 || len(config.Header) > 0 || len(config.HeaderValue) > 0 || len(config.Cookie) > 0 ||
len(config.HeaderPattern) > 0) {
len(config.HeaderPattern) > 0 || len(config.CookieValue) > 0 || len(config.CookiePattern) > 0) {
return nil, errors.NewInvalidAnnotationConfiguration("canary", "configured but not enabled")
}

Expand Down
7 changes: 6 additions & 1 deletion internal/ingress/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ package controller

import (
"fmt"
"k8s.io/ingress-nginx/internal/ingress/annotations/log"
"sort"
"strconv"
"strings"
"time"

"k8s.io/ingress-nginx/internal/ingress/annotations/log"

"github.com/mitchellh/hashstructure"
"k8s.io/klog"

Expand Down Expand Up @@ -697,6 +698,8 @@ func (n *NGINXController) createUpstreams(data []*ingress.Ingress, du *ingress.B
HeaderValue: anns.Canary.HeaderValue,
HeaderPattern: anns.Canary.HeaderPattern,
Cookie: anns.Canary.Cookie,
CookieValue: anns.Canary.CookieValue,
CookiePattern: anns.Canary.CookiePattern,
}
}

Expand Down Expand Up @@ -766,6 +769,8 @@ func (n *NGINXController) createUpstreams(data []*ingress.Ingress, du *ingress.B
HeaderValue: anns.Canary.HeaderValue,
HeaderPattern: anns.Canary.HeaderPattern,
Cookie: anns.Canary.Cookie,
CookieValue: anns.Canary.CookieValue,
CookiePattern: anns.Canary.CookiePattern,
}
}

Expand Down
4 changes: 4 additions & 0 deletions internal/ingress/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ type TrafficShapingPolicy struct {
HeaderPattern string `json:"headerPattern"`
// Cookie on which to redirect requests to this backend
Cookie string `json:"cookie"`
// CookieValue on which to redirect requests to this backend
CookieValue string `json:"cookieValue"`
// CookiePattern the cookie value match pattern, support exact, regex.
CookiePattern string `json:"cookiePattern"`
}

// HashInclude defines if a field should be used or not to calculate the hash
Expand Down
6 changes: 6 additions & 0 deletions internal/ingress/types_equals.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,12 @@ func (tsp1 TrafficShapingPolicy) Equal(tsp2 TrafficShapingPolicy) bool {
if tsp1.Cookie != tsp2.Cookie {
return false
}
if tsp1.CookieValue != tsp2.CookieValue {
return false
}
if tsp1.CookiePattern != tsp2.CookiePattern {
return false
}

return true
}
Expand Down
17 changes: 16 additions & 1 deletion rootfs/etc/nginx/lua/balancer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,22 @@ local function route_to_alternative_balancer(balancer)
local target_cookie = traffic_shaping_policy.cookie
local cookie = ngx.var["cookie_" .. target_cookie]
if cookie then
if cookie == "always" then
if traffic_shaping_policy.cookieValue
and #traffic_shaping_policy.cookieValue > 0 then
if traffic_shaping_policy.cookieValue == cookie then
return true
end
elseif traffic_shaping_policy.cookiePattern
and #traffic_shaping_policy.cookiePattern > 0 then
local m, err = ngx.re.match(cookie, traffic_shaping_policy.cookiePattern)
if m then
return true
elseif err then
ngx.log(ngx.ERR, "error when matching canary-by-cookie-pattern: '",
traffic_shaping_policy.cookiePattern, "', error: ", err)
return false
end
elseif cookie == "always" then
return true
elseif cookie == "never" then
return false
Expand Down

0 comments on commit 6c9f81e

Please sign in to comment.