Skip to content

Commit

Permalink
Add support for AWS ALB TargetGroupStickinessConfig
Browse files Browse the repository at this point in the history
Adds support for AWS ALB [TargetGroupStickinessConfig](https://aws.amazon.com/blogs/aws/new-application-load-balancer-simplifies-deployment-with-weighted-target-groups/)

This is required to support sticky session on the listener level while Argo is using ALB's weighting
  • Loading branch information
derjust committed Oct 12, 2021
1 parent 7c77744 commit 62ef776
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 10 deletions.
8 changes: 8 additions & 0 deletions ingress/alb.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ func getResetALBActionStr(ingress *extensionsv1beta1.Ingress, action string) (st
},
},
}

if previousAction.ForwardConfig.TargetGroupStickinessConfig != nil {
albAction.ForwardConfig.TargetGroupStickinessConfig = &ingressutil.ALBTargetGroupStickinessConfig{
Enabled: previousAction.ForwardConfig.TargetGroupStickinessConfig.Enabled,
DurationSeconds: previousAction.ForwardConfig.TargetGroupStickinessConfig.DurationSeconds,
}
}

bytes := jsonutil.MustMarshal(albAction)
return string(bytes), nil
}
70 changes: 62 additions & 8 deletions ingress/alb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,43 @@ const actionTemplate = `{
}
}`


const actionTemplateWithStickyConfig = `{
"Type":"forward",
"ForwardConfig":{
"TargetGroups":[
{
"ServiceName":"%s",
"ServicePort":"%d",
"Weight": 85
},{
"ServiceName":"%s",
"ServicePort":"%d",
"Weight": 15
}
],
"TargetGroupStickinessConfig":{
"DurationSeconds" : 300,
"Enabled" : true
}
}
}`

func albActionAnnotation(stable string) string {
return fmt.Sprintf("%s%s%s", ingressutil.ALBIngressAnnotation, ingressutil.ALBActionPrefix, stable)
}

func newALBIngress(name string, port int, serviceName string, rollout string) *extensionsv1beta1.Ingress {
func newALBIngress(name string, port int, serviceName string, rollout string, includeStickyConfig bool) *extensionsv1beta1.Ingress {
canaryService := fmt.Sprintf("%s-canary", serviceName)
albActionKey := albActionAnnotation(serviceName)
managedBy := fmt.Sprintf("%s:%s", rollout, albActionKey)
action := fmt.Sprintf(actionTemplate, serviceName, port, canaryService, port)
var template string
if includeStickyConfig {
template = actionTemplateWithStickyConfig
}else{
template = actionTemplate
}
action := fmt.Sprintf(template, serviceName, port, canaryService, port)
return &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Expand Down Expand Up @@ -98,7 +126,7 @@ func rollout(name, service, ingress string) *v1alpha1.Rollout {

func TestInvalidManagedALBActions(t *testing.T) {
rollout := rollout("rollout", "stable-service", "test-ingress")
ing := newALBIngress("test-ingress", 80, "stable-service", rollout.Name)
ing := newALBIngress("test-ingress", 80, "stable-service", rollout.Name, false)
ing.Annotations[ingressutil.ManagedActionsAnnotation] = "invalid-managed-by"

ctrl, kubeclient, enqueuedObjects := newFakeIngressController(ing, rollout)
Expand All @@ -110,7 +138,7 @@ func TestInvalidManagedALBActions(t *testing.T) {
}

func TestInvalidPreviousALBActionAnnotationValue(t *testing.T) {
ing := newALBIngress("test-ingress", 80, "stable-service", "not-existing-rollout")
ing := newALBIngress("test-ingress", 80, "stable-service", "not-existing-rollout", false)
ing.Annotations[albActionAnnotation("stable-service")] = "{"

ctrl, kubeclient, enqueuedObjects := newFakeIngressController(ing, nil)
Expand All @@ -122,7 +150,7 @@ func TestInvalidPreviousALBActionAnnotationValue(t *testing.T) {
}

func TestInvalidPreviousALBActionAnnotationKey(t *testing.T) {
ing := newALBIngress("test-ingress", 80, "stable-service", "not-existing-rollout")
ing := newALBIngress("test-ingress", 80, "stable-service", "not-existing-rollout", false)
ing.Annotations[ingressutil.ManagedActionsAnnotation] = "invalid-action-key"
ctrl, kubeclient, enqueuedObjects := newFakeIngressController(ing, nil)

Expand All @@ -133,7 +161,7 @@ func TestInvalidPreviousALBActionAnnotationKey(t *testing.T) {
}

func TestResetActionFailureFindNoPort(t *testing.T) {
ing := newALBIngress("test-ingress", 80, "stable-service", "not-existing-rollout")
ing := newALBIngress("test-ingress", 80, "stable-service", "not-existing-rollout", false)
ing.Annotations[albActionAnnotation("stable-service")] = "{}"

ctrl, kubeclient, enqueuedObjects := newFakeIngressController(ing, nil)
Expand All @@ -146,7 +174,7 @@ func TestResetActionFailureFindNoPort(t *testing.T) {

func TestALBIngressNoModifications(t *testing.T) {
rollout := rollout("rollout", "stable-service", "test-ingress")
ing := newALBIngress("test-ingress", 80, "stable-service", rollout.Name)
ing := newALBIngress("test-ingress", 80, "stable-service", rollout.Name, false)

ctrl, kubeclient, enqueuedObjects := newFakeIngressController(ing, rollout)

Expand All @@ -157,7 +185,7 @@ func TestALBIngressNoModifications(t *testing.T) {
}

func TestALBIngressResetAction(t *testing.T) {
ing := newALBIngress("test-ingress", 80, "stable-service", "non-existing-rollout")
ing := newALBIngress("test-ingress", 80, "stable-service", "non-existing-rollout", false)

ctrl, kubeclient, enqueuedObjects := newFakeIngressController(ing, nil)
err := ctrl.syncIngress("default/test-ingress")
Expand All @@ -179,3 +207,29 @@ func TestALBIngressResetAction(t *testing.T) {
expectedAction := `{"Type":"forward","ForwardConfig":{"TargetGroups":[{"ServiceName":"stable-service","ServicePort":"80","Weight":100}]}}`
assert.Equal(t, expectedAction, annotations[albActionAnnotation("stable-service")])
}


func TestALBIngressResetActionWithStickyConfig(t *testing.T) {
ing := newALBIngress("test-ingress", 80, "stable-service", "non-existing-rollout", true)

ctrl, kubeclient, enqueuedObjects := newFakeIngressController(ing, nil)
err := ctrl.syncIngress("default/test-ingress")
assert.Nil(t, err)
assert.Len(t, enqueuedObjects, 0)
actions := kubeclient.Actions()
assert.Len(t, actions, 1)
updateAction, ok := actions[0].(k8stesting.UpdateAction)
if !ok {
assert.Fail(t, "Client call was not an update")
updateAction.GetObject()
}
acc, err := meta.Accessor(updateAction.GetObject())
if err != nil {
panic(err)
}
annotations := acc.GetAnnotations()
assert.NotContains(t, annotations, ingressutil.ManagedActionsAnnotation)
expectedAction := `{"Type":"forward","ForwardConfig":{"TargetGroups":[{"ServiceName":"stable-service","ServicePort":"80","Weight":100}],"TargetGroupStickinessConfig":{"Enabled":true,"DurationSeconds":300}}}`
assert.Equal(t, expectedAction, annotations[albActionAnnotation("stable-service")])
}

10 changes: 10 additions & 0 deletions manifests/crds/rollout-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,16 @@ spec:
properties:
alb:
properties:
stickinessConfig:
properties:
enabled:
type: boolean
durationSeconds:
type: integer
required:
- enabled
- durationSeconds
type: object
annotationPrefix:
type: string
ingress:
Expand Down
10 changes: 10 additions & 0 deletions manifests/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10387,6 +10387,16 @@ spec:
servicePort:
format: int32
type: integer
stickinessConfig:
properties:
durationSeconds:
type: integer
enabled:
type: boolean
required:
- enabled
- durationSeconds
type: object
required:
- ingress
- servicePort
Expand Down
10 changes: 10 additions & 0 deletions manifests/namespace-install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10387,6 +10387,16 @@ spec:
servicePort:
format: int32
type: integer
stickinessConfig:
properties:
durationSeconds:
type: integer
enabled:
type: boolean
required:
- enabled
- durationSeconds
type: object
required:
- ingress
- servicePort
Expand Down
8 changes: 8 additions & 0 deletions pkg/apis/rollouts/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,11 +323,19 @@ type ALBTrafficRouting struct {
ServicePort int32 `json:"servicePort" protobuf:"varint,2,opt,name=servicePort"`
// RootService references the service in the ingress to the controller should add the action to
RootService string `json:"rootService,omitempty" protobuf:"bytes,3,opt,name=rootService"`
// AdditionalForwardConfig allows to specify further settings on the ForwaredConfig
// +optional
StickinessConfig StickinessConfig `json:"stickinessConfig,omitempty" protobuf:"bytes,5,opt,name=stickinessConfig"`
// AnnotationPrefix has to match the configured annotation prefix on the alb ingress controller
// +optional
AnnotationPrefix string `json:"annotationPrefix,omitempty" protobuf:"bytes,4,opt,name=annotationPrefix"`
}

type StickinessConfig struct {
Enabled bool `json:"enabled" protobuf:"varint,1,opt,name=enabled"`
DurationSeconds int `json:"durationSeconds" protobuf:"varint,2,opt,name=durationSeconds"`
}

// RolloutTrafficRouting hosts all the different configuration for supported service meshes to enable more fine-grained traffic routing
type RolloutTrafficRouting struct {
// Istio holds Istio specific configuration to route traffic
Expand Down
7 changes: 7 additions & 0 deletions rollout/trafficrouting/alb/alb.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,13 @@ func getForwardActionString(r *v1alpha1.Rollout, port int32, desiredWeight int32
TargetGroups: targetGroups,
},
}

var stickinessConfig = r.Spec.Strategy.Canary.TrafficRouting.ALB.StickinessConfig;
if stickinessConfig.Enabled {
action.ForwardConfig.TargetGroupStickinessConfig.Enabled = true
action.ForwardConfig.TargetGroupStickinessConfig.DurationSeconds = stickinessConfig.DurationSeconds
}

bytes := jsonutil.MustMarshal(action)
return string(bytes)
}
Expand Down
21 changes: 21 additions & 0 deletions rollout/trafficrouting/alb/alb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,27 @@ const actionTemplate = `{
}
}`

const actionTemplateWithStickyConfig = `{
"Type":"forward",
"ForwardConfig":{
"TargetGroups":[
{
"ServiceName":"%s",
"ServicePort":"%d",
"Weight":%d
},{
"ServiceName":"%s",
"ServicePort":"%d",
"Weight":%d
}
],
"TargetGroupStickinessConfig":{
"DurationSeconds" : 300,
"Enabled" : true
}
}
}`

const actionTemplateWithExperiments = `{"Type":"forward","ForwardConfig":{"TargetGroups":[{"ServiceName":"%s","ServicePort":"%d","Weight":%d},{"ServiceName":"%s","ServicePort":"%d","Weight":%d},{"ServiceName":"%s","ServicePort":"%d","Weight":%d},{"ServiceName":"%s","ServicePort":"%d","Weight":%d}]}}`

func albActionAnnotation(stable string) string {
Expand Down
11 changes: 9 additions & 2 deletions utils/ingress/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,20 @@ const (
// ALBAction describes an ALB action that configure the behavior of an ALB. This struct is marshaled into a string
// that is added to the Ingress's annotations.
type ALBAction struct {
Type string `json:"Type"`
ForwardConfig ALBForwardConfig `json:"ForwardConfig"`
Type string `json:"Type"`
ForwardConfig ALBForwardConfig `json:"ForwardConfig"`
}

// ALBForwardConfig describes a list of target groups that the ALB should route traffic towards
type ALBForwardConfig struct {
TargetGroups []ALBTargetGroup `json:"TargetGroups"`
TargetGroupStickinessConfig *ALBTargetGroupStickinessConfig `json:"TargetGroupStickinessConfig,omitempty"`
}

// ALBTargetGroupStickinessConfig describes settings for the listener to apply to all forwards
type ALBTargetGroupStickinessConfig struct {
Enabled bool `json:"Enabled"`
DurationSeconds int `json:"DurationSeconds"`
}

// ALBTargetGroup holds the weight to send to a specific destination consisting of a K8s service and port or ARN
Expand Down

0 comments on commit 62ef776

Please sign in to comment.