Skip to content

Commit 23385dd

Browse files
authored
feat(elasticloadbalancingv2): Add support for application cookies (#13142)
https://aws.amazon.com/about-aws/whats-new/2021/02/application-load-balancer-supports-application-cookie-stickiness/ Maybe I shouldn't have deprecated `stickinessCookieDuration` ... which is essentially a rename to `loadBalancerStickinessCookieDuration`.... but that can be easily reverted based on the feedback I recieve. As always... feedback is much appreciated. Thanks! 👍 edit: lb_cookie <img width="1377" alt="Screen Shot 2021-02-18 at 5 14 31 PM" src="https://user-images.githubusercontent.com/31543/108447101-858a0d80-721c-11eb-83e7-d017bb3f69e5.png"> app_cookie <img width="1397" alt="Screen Shot 2021-02-18 at 5 14 16 PM" src="https://user-images.githubusercontent.com/31543/108447114-8ae75800-721c-11eb-8d74-fa17a2d5ec8c.png"> ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 4553d4b commit 23385dd

File tree

7 files changed

+263
-14
lines changed

7 files changed

+263
-14
lines changed

packages/@aws-cdk/aws-elasticloadbalancingv2/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,33 @@ const group = listener.addTargets('AppFleet', {
243243
group.addTarget(asg2);
244244
```
245245

246+
### Sticky sessions for your Application Load Balancer
247+
248+
By default, an Application Load Balancer routes each request independently to a registered target based on the chosen load-balancing algorithm. However, you can use the sticky session feature (also known as session affinity) to enable the load balancer to bind a user's session to a specific target. This ensures that all requests from the user during the session are sent to the same target. This feature is useful for servers that maintain state information in order to provide a continuous experience to clients. To use sticky sessions, the client must support cookies.
249+
250+
Application Load Balancers support both duration-based cookies (`lb_cookie`) and application-based cookies (`app_cookie`). The key to managing sticky sessions is determining how long your load balancer should consistently route the user's request to the same target. Sticky sessions are enabled at the target group level. You can use a combination of duration-based stickiness, application-based stickiness, and no stickiness across all of your target groups.
251+
252+
```ts
253+
// Target group with duration-based stickiness with load-balancer generated cookie
254+
const tg1 = new elbv2.ApplicationTargetGroup(stack, 'TG1', {
255+
targetType: elbv2.TargetType.INSTANCE,
256+
port: 80,
257+
stickinessCookieDuration: cdk.Duration.minutes(5),
258+
vpc,
259+
});
260+
261+
// Target group with application-based stickiness
262+
const tg2 = new elbv2.ApplicationTargetGroup(stack, 'TG2', {
263+
targetType: elbv2.TargetType.INSTANCE,
264+
port: 80,
265+
stickinessCookieDuration: cdk.Duration.minutes(5),
266+
stickinessCookieName: 'MyDeliciousCookie',
267+
vpc,
268+
});
269+
```
270+
271+
For more information see: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/sticky-sessions.html#application-based-stickiness
272+
246273
## Using Lambda Targets
247274

248275
To use a Lambda Function as a target, use the integration class in the

packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
363363
protocol: props.protocol,
364364
slowStart: props.slowStart,
365365
stickinessCookieDuration: props.stickinessCookieDuration,
366+
stickinessCookieName: props.stickinessCookieName,
366367
targetGroupName: props.targetGroupName,
367368
targets: props.targets,
368369
vpc: this.loadBalancer.vpc,
@@ -813,6 +814,20 @@ export interface AddApplicationTargetsProps extends AddRuleProps {
813814
*/
814815
readonly stickinessCookieDuration?: Duration;
815816

817+
/**
818+
* The name of an application-based stickiness cookie.
819+
*
820+
* Names that start with the following prefixes are not allowed: AWSALB, AWSALBAPP,
821+
* and AWSALBTG; they're reserved for use by the load balancer.
822+
*
823+
* Note: `stickinessCookieName` parameter depends on the presence of `stickinessCookieDuration` parameter.
824+
* If `stickinessCookieDuration` is not set, `stickinessCookieName` will be omitted.
825+
*
826+
* @default - If `stickinessCookieDuration` is set, a load-balancer generated cookie is used. Otherwise, no stickiness is defined.
827+
* @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/sticky-sessions.html
828+
*/
829+
readonly stickinessCookieName?: string;
830+
816831
/**
817832
* The targets to add to this target group.
818833
*

packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
22
import * as ec2 from '@aws-cdk/aws-ec2';
3-
import { Annotations, Duration } from '@aws-cdk/core';
3+
import { Annotations, Duration, Token } from '@aws-cdk/core';
44
import { IConstruct, Construct } from 'constructs';
55
import { ApplicationELBMetrics } from '../elasticloadbalancingv2-canned-metrics.generated';
66
import {
@@ -57,6 +57,20 @@ export interface ApplicationTargetGroupProps extends BaseTargetGroupProps {
5757
*/
5858
readonly stickinessCookieDuration?: Duration;
5959

60+
/**
61+
* The name of an application-based stickiness cookie.
62+
*
63+
* Names that start with the following prefixes are not allowed: AWSALB, AWSALBAPP,
64+
* and AWSALBTG; they're reserved for use by the load balancer.
65+
*
66+
* Note: `stickinessCookieName` parameter depends on the presence of `stickinessCookieDuration` parameter.
67+
* If `stickinessCookieDuration` is not set, `stickinessCookieName` will be omitted.
68+
*
69+
* @default - If `stickinessCookieDuration` is set, a load-balancer generated cookie is used. Otherwise, no stickiness is defined.
70+
* @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/sticky-sessions.html
71+
*/
72+
readonly stickinessCookieName?: string;
73+
6074
/**
6175
* The targets to add to this target group.
6276
*
@@ -111,8 +125,8 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat
111125
if (props.slowStart !== undefined) {
112126
this.setAttribute('slow_start.duration_seconds', props.slowStart.toSeconds().toString());
113127
}
114-
if (props.stickinessCookieDuration !== undefined) {
115-
this.enableCookieStickiness(props.stickinessCookieDuration);
128+
if (props.stickinessCookieDuration) {
129+
this.enableCookieStickiness(props.stickinessCookieDuration, props.stickinessCookieName);
116130
}
117131
this.addTarget(...(props.targets || []));
118132
}
@@ -129,12 +143,31 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat
129143
}
130144

131145
/**
132-
* Enable sticky routing via a cookie to members of this target group
146+
* Enable sticky routing via a cookie to members of this target group.
147+
*
148+
* Note: If the `cookieName` parameter is set, application-based stickiness will be applied,
149+
* otherwise it defaults to duration-based stickiness attributes (`lb_cookie`).
150+
*
151+
* @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/sticky-sessions.html
133152
*/
134-
public enableCookieStickiness(duration: Duration) {
153+
public enableCookieStickiness(duration: Duration, cookieName?: string) {
154+
if (cookieName !== undefined) {
155+
if (!Token.isUnresolved(cookieName) && (cookieName.startsWith('AWSALB') || cookieName.startsWith('AWSALBAPP') || cookieName.startsWith('AWSALBTG'))) {
156+
throw new Error('App cookie names that start with the following prefixes are not allowed: AWSALB, AWSALBAPP, and AWSALBTG; they\'re reserved for use by the load balancer.');
157+
}
158+
if (cookieName === '') {
159+
throw new Error('App cookie name cannot be an empty string.');
160+
}
161+
}
135162
this.setAttribute('stickiness.enabled', 'true');
136-
this.setAttribute('stickiness.type', 'lb_cookie');
137-
this.setAttribute('stickiness.lb_cookie.duration_seconds', duration.toSeconds().toString());
163+
if (cookieName) {
164+
this.setAttribute('stickiness.type', 'app_cookie');
165+
this.setAttribute('stickiness.app_cookie.cookie_name', cookieName);
166+
this.setAttribute('stickiness.app_cookie.duration_seconds', duration.toSeconds().toString());
167+
} else {
168+
this.setAttribute('stickiness.type', 'lb_cookie');
169+
this.setAttribute('stickiness.lb_cookie.duration_seconds', duration.toSeconds().toString());
170+
}
138171
}
139172

140173
/**

packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/listener.test.ts

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe('tests', () => {
7171
});
7272
});
7373

74-
test('Listener default to open - IPv4 and IPv6 (dualstack)', () => {
74+
test('Listener default to open - IPv4 and IPv6 (dual stack)', () => {
7575
// GIVEN
7676
const stack = new cdk.Stack();
7777
const vpc = new ec2.Vpc(stack, 'Stack');
@@ -316,7 +316,7 @@ describe('tests', () => {
316316
});
317317
});
318318

319-
test('Enable stickiness for targets', () => {
319+
test('Enable alb stickiness for targets', () => {
320320
// GIVEN
321321
const stack = new cdk.Stack();
322322
const vpc = new ec2.Vpc(stack, 'Stack');
@@ -349,6 +349,43 @@ describe('tests', () => {
349349
});
350350
});
351351

352+
test('Enable app stickiness for targets', () => {
353+
// GIVEN
354+
const stack = new cdk.Stack();
355+
const vpc = new ec2.Vpc(stack, 'Stack');
356+
const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc });
357+
const listener = lb.addListener('Listener', { port: 80 });
358+
359+
// WHEN
360+
const group = listener.addTargets('Group', {
361+
port: 80,
362+
targets: [new FakeSelfRegisteringTarget(stack, 'Target', vpc)],
363+
});
364+
group.enableCookieStickiness(cdk.Duration.hours(1), 'MyDeliciousCookie');
365+
366+
// THEN
367+
expect(stack).toHaveResource('AWS::ElasticLoadBalancingV2::TargetGroup', {
368+
TargetGroupAttributes: [
369+
{
370+
Key: 'stickiness.enabled',
371+
Value: 'true',
372+
},
373+
{
374+
Key: 'stickiness.type',
375+
Value: 'app_cookie',
376+
},
377+
{
378+
Key: 'stickiness.app_cookie.cookie_name',
379+
Value: 'MyDeliciousCookie',
380+
},
381+
{
382+
Key: 'stickiness.app_cookie.duration_seconds',
383+
Value: '3600',
384+
},
385+
],
386+
});
387+
});
388+
352389
test('Enable health check for targets', () => {
353390
// GIVEN
354391
const stack = new cdk.Stack();
@@ -823,7 +860,7 @@ describe('tests', () => {
823860
});
824861
});
825862

826-
test('Throws when specifying both target groups and fixed reponse', () => {
863+
test('Throws when specifying both target groups and fixed response', () => {
827864
// GIVEN
828865
const stack = new cdk.Stack();
829866
const vpc = new ec2.Vpc(stack, 'VPC');
@@ -868,7 +905,7 @@ describe('tests', () => {
868905
})).toThrowError('Priority must have value greater than or equal to 1');
869906
});
870907

871-
test('Throws when specifying both target groups and redirect reponse', () => {
908+
test('Throws when specifying both target groups and redirect response', () => {
872909
// GIVEN
873910
const stack = new cdk.Stack();
874911
const vpc = new ec2.Vpc(stack, 'VPC');
@@ -970,7 +1007,7 @@ describe('tests', () => {
9701007
});
9711008
});
9721009

973-
test('Can add additional certificates via addCertficateArns to application listener', () => {
1010+
test('Can add additional certificates via addCertificateArns to application listener', () => {
9741011
// GIVEN
9751012
const stack = new cdk.Stack();
9761013
const vpc = new ec2.Vpc(stack, 'Stack');
@@ -1050,7 +1087,7 @@ describe('tests', () => {
10501087
})).toThrowError('Both `pathPatterns` and `pathPattern` are specified, specify only one');
10511088
});
10521089

1053-
test('Add additonal condition to listener rule', () => {
1090+
test('Add additional condition to listener rule', () => {
10541091
// GIVEN
10551092
const stack = new cdk.Stack();
10561093
const vpc = new ec2.Vpc(stack, 'Stack');
@@ -1244,7 +1281,7 @@ describe('tests', () => {
12441281
});
12451282
});
12461283

1247-
test('Can exist together legacy style conditions and modan style conditions', () => {
1284+
test('Can exist together legacy style conditions and modern style conditions', () => {
12481285
// GIVEN
12491286
const stack = new cdk.Stack();
12501287
const vpc = new ec2.Vpc(stack, 'Stack');

packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-group.test.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,106 @@ describe('tests', () => {
8888
UnhealthyThresholdCount: 27,
8989
});
9090
});
91+
92+
test('Load balancer duration cookie stickiness', () => {
93+
// GIVEN
94+
const app = new cdk.App();
95+
const stack = new cdk.Stack(app, 'Stack');
96+
const vpc = new ec2.Vpc(stack, 'VPC', {});
97+
98+
// WHEN
99+
new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', {
100+
stickinessCookieDuration: cdk.Duration.minutes(5),
101+
vpc,
102+
});
103+
104+
// THEN
105+
expect(stack).toHaveResource('AWS::ElasticLoadBalancingV2::TargetGroup', {
106+
TargetGroupAttributes: [
107+
{
108+
Key: 'stickiness.enabled',
109+
Value: 'true',
110+
},
111+
{
112+
Key: 'stickiness.type',
113+
Value: 'lb_cookie',
114+
},
115+
{
116+
Key: 'stickiness.lb_cookie.duration_seconds',
117+
Value: '300',
118+
},
119+
],
120+
});
121+
});
122+
123+
test('Load balancer app cookie stickiness', () => {
124+
// GIVEN
125+
const app = new cdk.App();
126+
const stack = new cdk.Stack(app, 'Stack');
127+
const vpc = new ec2.Vpc(stack, 'VPC', {});
128+
129+
// WHEN
130+
new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', {
131+
stickinessCookieDuration: cdk.Duration.minutes(5),
132+
stickinessCookieName: 'MyDeliciousCookie',
133+
vpc,
134+
});
135+
136+
// THEN
137+
expect(stack).toHaveResource('AWS::ElasticLoadBalancingV2::TargetGroup', {
138+
TargetGroupAttributes: [
139+
{
140+
Key: 'stickiness.enabled',
141+
Value: 'true',
142+
},
143+
{
144+
Key: 'stickiness.type',
145+
Value: 'app_cookie',
146+
},
147+
{
148+
Key: 'stickiness.app_cookie.cookie_name',
149+
Value: 'MyDeliciousCookie',
150+
},
151+
{
152+
Key: 'stickiness.app_cookie.duration_seconds',
153+
Value: '300',
154+
},
155+
],
156+
});
157+
});
158+
159+
test('Bad stickiness cookie names', () => {
160+
// GIVEN
161+
const app = new cdk.App();
162+
const stack = new cdk.Stack(app, 'Stack');
163+
const vpc = new ec2.Vpc(stack, 'VPC', {});
164+
const errMessage = 'App cookie names that start with the following prefixes are not allowed: AWSALB, AWSALBAPP, and AWSALBTG; they\'re reserved for use by the load balancer';
165+
166+
// THEN
167+
['AWSALBCookieName', 'AWSALBstickinessCookieName', 'AWSALBTGCookieName'].forEach((badCookieName, i) => {
168+
expect(() => {
169+
new elbv2.ApplicationTargetGroup(stack, `TargetGroup${i}`, {
170+
stickinessCookieDuration: cdk.Duration.minutes(5),
171+
stickinessCookieName: badCookieName,
172+
vpc,
173+
});
174+
}).toThrow(errMessage);
175+
});
176+
});
177+
178+
test('Empty stickiness cookie name', () => {
179+
// GIVEN
180+
const app = new cdk.App();
181+
const stack = new cdk.Stack(app, 'Stack');
182+
const vpc = new ec2.Vpc(stack, 'VPC', {});
183+
184+
// THEN
185+
expect(() => {
186+
new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', {
187+
stickinessCookieDuration: cdk.Duration.minutes(5),
188+
stickinessCookieName: '',
189+
vpc,
190+
});
191+
}).toThrow(/App cookie name cannot be an empty string./);
192+
});
91193
});

packages/@aws-cdk/aws-elasticloadbalancingv2/test/integ.alb.expected.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,20 @@
438438
"Properties": {
439439
"Port": 80,
440440
"Protocol": "HTTP",
441+
"TargetGroupAttributes": [
442+
{
443+
"Key": "stickiness.enabled",
444+
"Value": "true"
445+
},
446+
{
447+
"Key": "stickiness.type",
448+
"Value": "lb_cookie"
449+
},
450+
{
451+
"Key": "stickiness.lb_cookie.duration_seconds",
452+
"Value": "300"
453+
}
454+
],
441455
"Targets": [
442456
{
443457
"Id": "10.0.128.4"
@@ -454,6 +468,24 @@
454468
"Properties": {
455469
"Port": 80,
456470
"Protocol": "HTTP",
471+
"TargetGroupAttributes": [
472+
{
473+
"Key": "stickiness.enabled",
474+
"Value": "true"
475+
},
476+
{
477+
"Key": "stickiness.type",
478+
"Value": "app_cookie"
479+
},
480+
{
481+
"Key": "stickiness.app_cookie.cookie_name",
482+
"Value": "MyDeliciousCookie"
483+
},
484+
{
485+
"Key": "stickiness.app_cookie.duration_seconds",
486+
"Value": "300"
487+
}
488+
],
457489
"Targets": [
458490
{
459491
"Id": "10.0.128.5"

0 commit comments

Comments
 (0)