diff --git a/api/v1alpha1/trafficrouting_types.go b/api/v1alpha1/trafficrouting_types.go index 88e918d5..753f58a7 100644 --- a/api/v1alpha1/trafficrouting_types.go +++ b/api/v1alpha1/trafficrouting_types.go @@ -44,7 +44,7 @@ type TrafficRoutingRef struct { // IngressTrafficRouting configuration for ingress controller to control traffic routing type IngressTrafficRouting struct { // ClassType refers to the type of `Ingress`. - // current support nginx, aliyun-alb. default is nginx. + // current support nginx, aliyun-alb, aws-alb. default is nginx. // +optional ClassType string `json:"classType,omitempty"` // Name refers to the name of an `Ingress` resource in the same namespace as the `Rollout` diff --git a/config/crd/bases/rollouts.kruise.io_rollouts.yaml b/config/crd/bases/rollouts.kruise.io_rollouts.yaml index d0dd7d03..d939fc08 100644 --- a/config/crd/bases/rollouts.kruise.io_rollouts.yaml +++ b/config/crd/bases/rollouts.kruise.io_rollouts.yaml @@ -447,9 +447,9 @@ spec: to route traffic, e.g. Nginx, Alb. properties: classType: - description: |- - ClassType refers to the type of `Ingress`. - current support nginx, aliyun-alb. default is nginx. + description: ClassType refers to the type of `Ingress`. + current support nginx, aliyun-alb, aws-alb. default + is nginx. type: string name: description: Name refers to the name of an `Ingress` diff --git a/config/crd/bases/rollouts.kruise.io_trafficroutings.yaml b/config/crd/bases/rollouts.kruise.io_trafficroutings.yaml index 2ea47300..45c09ae9 100644 --- a/config/crd/bases/rollouts.kruise.io_trafficroutings.yaml +++ b/config/crd/bases/rollouts.kruise.io_trafficroutings.yaml @@ -96,9 +96,9 @@ spec: route traffic, e.g. Nginx, Alb. properties: classType: - description: |- - ClassType refers to the type of `Ingress`. - current support nginx, aliyun-alb. default is nginx. + description: ClassType refers to the type of `Ingress`. + current support nginx, aliyun-alb, aws-alb. default is + nginx. type: string name: description: Name refers to the name of an `Ingress` resource diff --git a/lua_configuration/trafficrouting_ingress/aws-alb.lua b/lua_configuration/trafficrouting_ingress/aws-alb.lua new file mode 100644 index 00000000..4d38d3c3 --- /dev/null +++ b/lua_configuration/trafficrouting_ingress/aws-alb.lua @@ -0,0 +1,54 @@ +local annotations = obj.annotations or {} + +-- Remove any prior ALB canary annotations +annotations["alb.ingress.kubernetes.io/actions.canary"] = nil +annotations["alb.ingress.kubernetes.io/conditions.canary"] = nil + +-- Compute weights +local cw = tonumber(obj.weight) or 0 +if cw < 0 then cw = 0 end +if cw > 100 then cw = 100 end +local sw = 100 - cw + +-- Build the forward action with *both* target‐groups +local action = { + Type = "forward", + ForwardConfig = { + TargetGroups = { + { ServiceName = obj.stableService, ServicePort = "80", Weight = sw }, + { ServiceName = obj.canaryService, ServicePort = "80", Weight = cw }, + } + } +} +annotations["alb.ingress.kubernetes.io/actions.canary"] = action + +-- Build conditions if any +if obj.matches then + local conds = {} + for _, match in ipairs(obj.matches) do + for _, hdr in ipairs(match.headers or {}) do + if hdr.name == "canary-by-cookie" then + table.insert(conds, { + Field = "http-cookie", + HttpCookieConfig = { CookieName = hdr.value, Values = { hdr.value } }, + }) + elseif hdr.name == "SourceIp" then + table.insert(conds, { + Field = "source-ip", + SourceIpConfig = { Values = { hdr.value } }, + }) + else + table.insert(conds, { + Field = "http-header", + HttpHeaderConfig = { + HttpHeaderName = hdr.name, + Values = { hdr.value }, + }, + }) + end + end + end + annotations["alb.ingress.kubernetes.io/conditions.canary"] = conds +end + +return annotations diff --git a/pkg/trafficrouting/network/ingress/ingress.go b/pkg/trafficrouting/network/ingress/ingress.go index b8be88dd..0ae0aebc 100644 --- a/pkg/trafficrouting/network/ingress/ingress.go +++ b/pkg/trafficrouting/network/ingress/ingress.go @@ -236,6 +236,7 @@ func (r *ingressController) executeLuaForCanary(annotations map[string]string, w Annotations map[string]string Weight string Matches []v1beta1.HttpRouteMatch + StableService string CanaryService string RequestHeaderModifier *gatewayv1beta1.HTTPHeaderFilter } @@ -243,6 +244,7 @@ func (r *ingressController) executeLuaForCanary(annotations map[string]string, w Annotations: annotations, Weight: fmt.Sprintf("%d", *weight), Matches: matches, + StableService: r.conf.StableService, CanaryService: r.conf.CanaryService, RequestHeaderModifier: headerModifier, } diff --git a/pkg/trafficrouting/network/ingress/ingress_test.go b/pkg/trafficrouting/network/ingress/ingress_test.go index 64ffb3e8..f86b32b9 100644 --- a/pkg/trafficrouting/network/ingress/ingress_test.go +++ b/pkg/trafficrouting/network/ingress/ingress_test.go @@ -189,6 +189,58 @@ var ( annotations[conditionKey] = json.encode(conditions) return annotations `, + fmt.Sprintf("%s.aws-alb", configuration.LuaTrafficRoutingIngressTypePrefix): ` + -- aws-alb.lua + local annotations = obj.annotations or {} + annotations["alb.ingress.kubernetes.io/actions.canary"] = nil + annotations["alb.ingress.kubernetes.io/conditions.canary"] = nil + + local cw = tonumber(obj.weight) or 0 + if cw < 0 then cw = 0 end + if cw > 100 then cw = 100 end + local sw = 100 - cw + + local action = { + Type = "forward", + ForwardConfig = { + TargetGroups = { + { ServiceName = obj.stableService, ServicePort = "80", Weight = sw }, + { ServiceName = obj.canaryService, ServicePort = "80", Weight = cw }, + } + } + } + annotations["alb.ingress.kubernetes.io/actions.canary"] = json.encode(action) + + if obj.matches then + local conds = {} + for _, m in ipairs(obj.matches) do + for _, hdr in ipairs(m.headers or {}) do + if hdr.name == "canary-by-cookie" then + table.insert(conds, { + Field = "http-cookie", + HttpCookieConfig = { CookieName = hdr.value, Values = { hdr.value } } + }) + elseif hdr.name == "SourceIp" then + table.insert(conds, { + Field = "source-ip", + SourceIpConfig = { Values = { hdr.value } } + }) + else + table.insert(conds, { + Field = "http-header", + HttpHeaderConfig = { + HttpHeaderName = hdr.name, + Values = { hdr.value } + } + }) + end + end + end + annotations["alb.ingress.kubernetes.io/conditions.canary"] = json.encode(conds) + end + + return annotations + `, }, } @@ -658,6 +710,52 @@ func TestEnsureRoutes(t *testing.T) { return expect }, }, + { + name: "ensure routes test aws-alb forward+conditions", + ingressType: "aws-alb", + getConfigmap: func() *corev1.ConfigMap { + return demoConf.DeepCopy() + }, + getIngress: func() []*netv1.Ingress { + canary := demoIngress.DeepCopy() + canary.Name = "echoserver-canary" + + // Expect both stable(80%) and canary(0%) in JSON + canary.Annotations["alb.ingress.kubernetes.io/actions.canary"] = + `{"Type":"forward","ForwardConfig":{"TargetGroups":[{"ServiceName":"echoserver","ServicePort":"80","Weight":100},{"ServiceName":"echoserver-canary","ServicePort":"80","Weight":0}]}}` + // And any header‐match condition + canary.Annotations["alb.ingress.kubernetes.io/conditions.canary"] = + `[{"Field":"http-header","HttpHeaderConfig":{"HttpHeaderName":"user_id","Values":["123456"]}}]` + + canary.Spec.Rules[0].HTTP.Paths = canary.Spec.Rules[0].HTTP.Paths[:1] + canary.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary" + return []*netv1.Ingress{demoIngress.DeepCopy(), canary} + }, + getRoutes: func() *v1beta1.CanaryStep { + return &v1beta1.CanaryStep{ + TrafficRoutingStrategy: v1beta1.TrafficRoutingStrategy{ + Traffic: utilpointer.String("0"), + Matches: []v1beta1.HttpRouteMatch{{ + Headers: []gatewayv1beta1.HTTPHeaderMatch{{ + Name: "user_id", + Value: "123456", + }}, + }}, + }, + } + }, + expectIngress: func() *netv1.Ingress { + e := demoIngress.DeepCopy() + e.Name = "echoserver-canary" + e.Annotations = map[string]string{ + "alb.ingress.kubernetes.io/actions.canary": `{"Type":"forward","ForwardConfig":{"TargetGroups":[{"ServiceName":"echoserver","ServicePort":"80","Weight":100},{"ServiceName":"echoserver-canary","ServicePort":"80","Weight":0}]}}`, + "alb.ingress.kubernetes.io/conditions.canary": `[{"Field":"http-header","HttpHeaderConfig":{"HttpHeaderName":"user_id","Values":["123456"]}}]`, + } + e.Spec.Rules[0].HTTP.Paths = e.Spec.Rules[0].HTTP.Paths[:1] + e.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Name = "echoserver-canary" + return e + }, + }, } config := Config{