add canary-weight-total annotation (#6338)
This commit is contained in:
parent
6163231ef6
commit
5cff197bc5
7 changed files with 71 additions and 5 deletions
|
@ -41,6 +41,7 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
|
||||||
|[nginx.ingress.kubernetes.io/canary-by-header-pattern](#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](#canary)|string|
|
||||||
|[nginx.ingress.kubernetes.io/canary-weight](#canary)|number|
|
|[nginx.ingress.kubernetes.io/canary-weight](#canary)|number|
|
||||||
|
|[nginx.ingress.kubernetes.io/canary-weight-total](#canary)|number|
|
||||||
|[nginx.ingress.kubernetes.io/client-body-buffer-size](#client-body-buffer-size)|string|
|
|[nginx.ingress.kubernetes.io/client-body-buffer-size](#client-body-buffer-size)|string|
|
||||||
|[nginx.ingress.kubernetes.io/configuration-snippet](#configuration-snippet)|string|
|
|[nginx.ingress.kubernetes.io/configuration-snippet](#configuration-snippet)|string|
|
||||||
|[nginx.ingress.kubernetes.io/custom-http-errors](#custom-http-errors)|[]int|
|
|[nginx.ingress.kubernetes.io/custom-http-errors](#custom-http-errors)|[]int|
|
||||||
|
@ -138,7 +139,9 @@ 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`: 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-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.
|
* `nginx.ingress.kubernetes.io/canary-weight`: The integer based (0 - <weight-total>) 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 <weight-total> means implies all requests will be sent to the alternative service specified in the Ingress. `<weight-total>` defaults to 100, and can be increased via `nginx.ingress.kubernetes.io/canary-weight-total`.
|
||||||
|
|
||||||
|
* `nginx.ingress.kubernetes.io/canary-weight-total`: The total weight of traffic. If unspecified, it defaults to 100.
|
||||||
|
|
||||||
Canary rules are evaluated in order of precedence. Precedence is as follows:
|
Canary rules are evaluated in order of precedence. Precedence is as follows:
|
||||||
`canary-by-header -> canary-by-cookie -> canary-weight`
|
`canary-by-header -> canary-by-cookie -> canary-weight`
|
||||||
|
|
|
@ -32,6 +32,7 @@ type canary struct {
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Weight int
|
Weight int
|
||||||
|
WeightTotal int
|
||||||
Header string
|
Header string
|
||||||
HeaderValue string
|
HeaderValue string
|
||||||
HeaderPattern string
|
HeaderPattern string
|
||||||
|
@ -59,6 +60,11 @@ func (c canary) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||||
config.Weight = 0
|
config.Weight = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.WeightTotal, err = parser.GetIntAnnotation("canary-weight-total", ing)
|
||||||
|
if err != nil {
|
||||||
|
config.WeightTotal = 100
|
||||||
|
}
|
||||||
|
|
||||||
config.Header, err = parser.GetStringAnnotation("canary-by-header", ing)
|
config.Header, err = parser.GetStringAnnotation("canary-by-header", ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
config.Header = ""
|
config.Header = ""
|
||||||
|
|
|
@ -896,6 +896,7 @@ func (n *NGINXController) createUpstreams(data []*ingress.Ingress, du *ingress.B
|
||||||
upstreams[defBackend].NoServer = true
|
upstreams[defBackend].NoServer = true
|
||||||
upstreams[defBackend].TrafficShapingPolicy = ingress.TrafficShapingPolicy{
|
upstreams[defBackend].TrafficShapingPolicy = ingress.TrafficShapingPolicy{
|
||||||
Weight: anns.Canary.Weight,
|
Weight: anns.Canary.Weight,
|
||||||
|
WeightTotal: anns.Canary.WeightTotal,
|
||||||
Header: anns.Canary.Header,
|
Header: anns.Canary.Header,
|
||||||
HeaderValue: anns.Canary.HeaderValue,
|
HeaderValue: anns.Canary.HeaderValue,
|
||||||
HeaderPattern: anns.Canary.HeaderPattern,
|
HeaderPattern: anns.Canary.HeaderPattern,
|
||||||
|
|
|
@ -111,10 +111,15 @@ type Backend struct {
|
||||||
// alternative backend
|
// alternative backend
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
type TrafficShapingPolicy struct {
|
type TrafficShapingPolicy struct {
|
||||||
// Weight (0-100) of traffic to redirect to the backend.
|
// Weight (0-<WeightTotal>) of traffic to redirect to the backend.
|
||||||
// e.g. Weight 20 means 20% of traffic will be redirected to the backend and 80% will remain
|
// e.g. <WeightTotal> defaults to 100, weight 20 means 20% of traffic will be
|
||||||
// with the other backend. 0 weight will not send any traffic to this backend
|
// redirected to the backend and 80% will remain with the other backend. If
|
||||||
|
// <WeightTotal> is set to 1000, weight 2 means 0.2% of traffic will be
|
||||||
|
// redirected to the backend and 99.8% will remain with the other backend.
|
||||||
|
// 0 weight will not send any traffic to this backend
|
||||||
Weight int `json:"weight"`
|
Weight int `json:"weight"`
|
||||||
|
// The total weight of traffic (>= 100). If unspecified, it defaults to 100.
|
||||||
|
WeightTotal int `json:"weightTotal"`
|
||||||
// Header on which to redirect requests to this backend
|
// Header on which to redirect requests to this backend
|
||||||
Header string `json:"header"`
|
Header string `json:"header"`
|
||||||
// HeaderValue on which to redirect requests to this backend
|
// HeaderValue on which to redirect requests to this backend
|
||||||
|
|
|
@ -259,7 +259,11 @@ local function route_to_alternative_balancer(balancer)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if math.random(100) <= traffic_shaping_policy.weight then
|
local weightTotal = 100
|
||||||
|
if traffic_shaping_policy.weightTotal ~= nil and traffic_shaping_policy.weightTotal > 100 then
|
||||||
|
weightTotal = traffic_shaping_policy.weightTotal
|
||||||
|
end
|
||||||
|
if math.random(weightTotal) <= traffic_shaping_policy.weight then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -203,6 +203,20 @@ describe("Balancer", function()
|
||||||
balancer.sync_backend(backend)
|
balancer.sync_backend(backend)
|
||||||
assert.equal(false, balancer.route_to_alternative_balancer(_primaryBalancer))
|
assert.equal(false, balancer.route_to_alternative_balancer(_primaryBalancer))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("returns true when weight is 1000 and weight total is 1000", function()
|
||||||
|
backend.trafficShapingPolicy.weight = 1000
|
||||||
|
backend.trafficShapingPolicy.weightTotal = 1000
|
||||||
|
balancer.sync_backend(backend)
|
||||||
|
assert.equal(true, balancer.route_to_alternative_balancer(_primaryBalancer))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("returns false when weight is 0 and weight total is 1000", function()
|
||||||
|
backend.trafficShapingPolicy.weight = 1000
|
||||||
|
backend.trafficShapingPolicy.weightTotal = 1000
|
||||||
|
balancer.sync_backend(backend)
|
||||||
|
assert.equal(true, balancer.route_to_alternative_balancer(_primaryBalancer))
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("canary by cookie", function()
|
describe("canary by cookie", function()
|
||||||
|
|
|
@ -773,6 +773,39 @@ var _ = framework.DescribeAnnotation("canary-*", func() {
|
||||||
Contains(canaryService)
|
Contains(canaryService)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ginkgo.It("should route requests only to canary if canary weight is equal to canary weight total", func() {
|
||||||
|
host := "foo"
|
||||||
|
annotations := map[string]string{}
|
||||||
|
|
||||||
|
ing := framework.NewSingleIngress(host, "/", host,
|
||||||
|
f.Namespace, framework.EchoService, 80, annotations)
|
||||||
|
f.EnsureIngress(ing)
|
||||||
|
|
||||||
|
f.WaitForNginxServer(host,
|
||||||
|
func(server string) bool {
|
||||||
|
return strings.Contains(server, "server_name foo")
|
||||||
|
})
|
||||||
|
|
||||||
|
canaryIngName := fmt.Sprintf("%v-canary", host)
|
||||||
|
canaryAnnotations := map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/canary": "true",
|
||||||
|
"nginx.ingress.kubernetes.io/canary-weight": "1000",
|
||||||
|
"nginx.ingress.kubernetes.io/canary-weight-total": "1000",
|
||||||
|
}
|
||||||
|
|
||||||
|
canaryIng := framework.NewSingleIngress(canaryIngName, "/", host,
|
||||||
|
f.Namespace, canaryService, 80, canaryAnnotations)
|
||||||
|
f.EnsureIngress(canaryIng)
|
||||||
|
|
||||||
|
f.HTTPTestClient().
|
||||||
|
GET("/").
|
||||||
|
WithHeader("Host", host).
|
||||||
|
Expect().
|
||||||
|
Status(http.StatusOK).
|
||||||
|
Body().
|
||||||
|
Contains(canaryService)
|
||||||
|
})
|
||||||
|
|
||||||
ginkgo.It("should route requests evenly split between mainline and canary if canary weight is 50", func() {
|
ginkgo.It("should route requests evenly split between mainline and canary if canary weight is 50", func() {
|
||||||
host := "foo"
|
host := "foo"
|
||||||
annotations := map[string]string{}
|
annotations := map[string]string{}
|
||||||
|
|
Loading…
Reference in a new issue