Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(elasticloadbalancingv2): Add support for application cookies #13142

Merged
merged 23 commits into from
Feb 24, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9066dc9
feat(elasticloadbalancingv2): Add support for application cookies
robertd Feb 19, 2021
5333556
Merge branch 'master' into alb-app-cookie-stickiness
robertd Feb 19, 2021
ba46b1c
Merge branch 'master' into alb-app-cookie-stickiness
robertd Feb 22, 2021
6e3959e
Merge branch 'master' into alb-app-cookie-stickiness
robertd Feb 23, 2021
71d7d25
Merge branch 'master' into alb-app-cookie-stickiness
robertd Feb 23, 2021
d26334c
refactor to use overloaded method
robertd Feb 23, 2021
db848f7
update readme
robertd Feb 23, 2021
f323123
update readme
robertd Feb 23, 2021
2e79f1e
update documentation
robertd Feb 23, 2021
1d5d6e2
Merge branch 'master' into alb-app-cookie-stickiness
robertd Feb 23, 2021
cebf77c
Merge branch 'master' into alb-app-cookie-stickiness
robertd Feb 24, 2021
4c9a91d
move app cookie checks
robertd Feb 24, 2021
390d21a
Merge branch 'master' into alb-app-cookie-stickiness
robertd Feb 24, 2021
5445db0
Merge branch 'master' into alb-app-cookie-stickiness
robertd Feb 24, 2021
7442bed
Update packages/@aws-cdk/aws-elasticloadbalancingv2/README.md
robertd Feb 24, 2021
f922c6b
Update packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/applicati…
robertd Feb 24, 2021
ed8c0ca
Update packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/applicati…
robertd Feb 24, 2021
0d1ea05
Update packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/applicati…
robertd Feb 24, 2021
ca19300
Update packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/target-g…
robertd Feb 24, 2021
a733310
fix tests
robertd Feb 24, 2021
459097f
consolidate alb integration tests
robertd Feb 24, 2021
8b40c6e
check if token is defined
robertd Feb 24, 2021
ab07116
fix docstring for stickinessCookieName
robertd Feb 24, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/@aws-cdk/aws-elasticloadbalancingv2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,33 @@ const group = listener.addTargets('AppFleet', {
group.addTarget(asg2);
```

### Sticky sessions for your Application Load Balancer

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.

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.

```ts
// Target group with duration-based stickiness
robertd marked this conversation as resolved.
Show resolved Hide resolved
const tg1 = new elbv2.ApplicationTargetGroup(stack, 'TG1', {
targetType: elbv2.TargetType.INSTANCE,
port: 80,
stickinessCookieDuration: cdk.Duration.minutes(5),
vpc,
});

// Target group with application-based stickiness
const tg2 = new elbv2.ApplicationTargetGroup(stack, 'TG2', {
targetType: elbv2.TargetType.INSTANCE,
port: 80,
stickinessCookieDuration: cdk.Duration.minutes(5),
stickinessCookieName: 'MyDeliciousCookie',
vpc,
});
```

For more information see: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/sticky-sessions.html#application-based-stickiness

## Using Lambda Targets

To use a Lambda Function as a target, use the integration class in the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
protocol: props.protocol,
slowStart: props.slowStart,
stickinessCookieDuration: props.stickinessCookieDuration,
stickinessCookieName: props.stickinessCookieName,
targetGroupName: props.targetGroupName,
targets: props.targets,
vpc: this.loadBalancer.vpc,
Expand Down Expand Up @@ -813,6 +814,22 @@ export interface AddApplicationTargetsProps extends AddRuleProps {
*/
readonly stickinessCookieDuration?: Duration;

/**
* The app cookie name.
*
* Indicates the name of the application-based stickiness cookie.
robertd marked this conversation as resolved.
Show resolved Hide resolved
*
* Names that start with the following prefixes are not allowed: AWSALB, AWSALBAPP,
* and AWSALBTG; they're reserved for use by the load balancer.
*
* Note: `stickinessCookieName` parameter depends on the presence of `stickinessCookieDuration` parameter.
* If `stickinessCookieDuration` is not set, `stickinessCookieName` will be omitted.
*
* @default No app cookie name
robertd marked this conversation as resolved.
Show resolved Hide resolved
* @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/sticky-sessions.html
*/
readonly stickinessCookieName?: string;

/**
* The targets to add to this target group.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@ export interface ApplicationTargetGroupProps extends BaseTargetGroupProps {
*/
readonly stickinessCookieDuration?: Duration;

/**
* The app cookie name.
robertd marked this conversation as resolved.
Show resolved Hide resolved
*
* Indicates the name of the application-based stickiness cookie.
*
* Names that start with the following prefixes are not allowed: AWSALB, AWSALBAPP,
* and AWSALBTG; they're reserved for use by the load balancer.
*
* Note: `stickinessCookieName` parameter depends on the presence of `stickinessCookieDuration` parameter.
* If `stickinessCookieDuration` is not set, `stickinessCookieName` will be omitted.
*
* @default No app cookie name
* @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/sticky-sessions.html
*/
readonly stickinessCookieName?: string;

/**
* The targets to add to this target group.
*
Expand Down Expand Up @@ -111,8 +127,8 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat
if (props.slowStart !== undefined) {
this.setAttribute('slow_start.duration_seconds', props.slowStart.toSeconds().toString());
}
if (props.stickinessCookieDuration !== undefined) {
this.enableCookieStickiness(props.stickinessCookieDuration);
if (props.stickinessCookieDuration) {
this.enableCookieStickiness(props.stickinessCookieDuration, props.stickinessCookieName);
}
this.addTarget(...(props.targets || []));
}
Expand All @@ -129,12 +145,31 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat
}

/**
* Enable sticky routing via a cookie to members of this target group
* Enable sticky routing via a cookie to members of this target group.
*
* Note: If you provide cookieName parameter application-based stickiness attributes (`app_cookie`) will be applied,
robertd marked this conversation as resolved.
Show resolved Hide resolved
* otherwise it defaults to duration-based stickiness attributes (`lb_cookie`).
*
* @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/sticky-sessions.html
*/
public enableCookieStickiness(duration: Duration) {
public enableCookieStickiness(duration: Duration, cookieName?: string) {
robertd marked this conversation as resolved.
Show resolved Hide resolved
if (cookieName !== undefined) {
if (cookieName.startsWith('AWSALB') || cookieName.startsWith('AWSALBAPP') || cookieName.startsWith('AWSALBTG')) {
robertd marked this conversation as resolved.
Show resolved Hide resolved
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.');
}
if (cookieName === '') {
throw new Error('App cookie name cannot be an empty string.');
}
}
this.setAttribute('stickiness.enabled', 'true');
this.setAttribute('stickiness.type', 'lb_cookie');
this.setAttribute('stickiness.lb_cookie.duration_seconds', duration.toSeconds().toString());
if (cookieName) {
this.setAttribute('stickiness.type', 'app_cookie');
this.setAttribute('stickiness.app_cookie.cookie_name', cookieName);
this.setAttribute('stickiness.app_cookie.duration_seconds', duration.toSeconds().toString());
njlynch marked this conversation as resolved.
Show resolved Hide resolved
} else {
this.setAttribute('stickiness.type', 'lb_cookie');
this.setAttribute('stickiness.lb_cookie.duration_seconds', duration.toSeconds().toString());
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('tests', () => {
});
});

test('Listener default to open - IPv4 and IPv6 (dualstack)', () => {
test('Listener default to open - IPv4 and IPv6 (dual stack)', () => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'Stack');
Expand Down Expand Up @@ -316,7 +316,7 @@ describe('tests', () => {
});
});

test('Enable stickiness for targets', () => {
test('Enable alb stickiness for targets', () => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'Stack');
Expand Down Expand Up @@ -349,6 +349,43 @@ describe('tests', () => {
});
});

test('Enable app stickiness for targets', () => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'Stack');
const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc });
const listener = lb.addListener('Listener', { port: 80 });

// WHEN
const group = listener.addTargets('Group', {
port: 80,
targets: [new FakeSelfRegisteringTarget(stack, 'Target', vpc)],
});
group.enableCookieStickiness(cdk.Duration.hours(1), 'MyDeliciousCookie');

// THEN
expect(stack).toHaveResource('AWS::ElasticLoadBalancingV2::TargetGroup', {
TargetGroupAttributes: [
{
Key: 'stickiness.enabled',
Value: 'true',
},
{
Key: 'stickiness.type',
Value: 'app_cookie',
},
{
Key: 'stickiness.app_cookie.cookie_name',
Value: 'MyDeliciousCookie',
},
{
Key: 'stickiness.app_cookie.duration_seconds',
Value: '3600',
},
],
});
});

test('Enable health check for targets', () => {
// GIVEN
const stack = new cdk.Stack();
Expand Down Expand Up @@ -823,7 +860,7 @@ describe('tests', () => {
});
});

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

test('Throws when specifying both target groups and redirect reponse', () => {
test('Throws when specifying both target groups and redirect response', () => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
Expand Down Expand Up @@ -970,7 +1007,7 @@ describe('tests', () => {
});
});

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

test('Add additonal condition to listener rule', () => {
test('Add additional condition to listener rule', () => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'Stack');
Expand Down Expand Up @@ -1244,7 +1281,7 @@ describe('tests', () => {
});
});

test('Can exist together legacy style conditions and modan style conditions', () => {
test('Can exist together legacy style conditions and modern style conditions', () => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'Stack');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,119 @@ describe('tests', () => {
UnhealthyThresholdCount: 27,
});
});

test('Load balancer duration cookie stickiness', () => {
// GIVEN
const app = new cdk.App();
const stack = new cdk.Stack(app, 'Stack');
const vpc = new ec2.Vpc(stack, 'VPC', {});

// WHEN
new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', {
stickinessCookieDuration: cdk.Duration.minutes(5),
vpc,
});

// THEN
expect(stack).toHaveResource('AWS::ElasticLoadBalancingV2::TargetGroup', {
TargetGroupAttributes: [
{
Key: 'stickiness.enabled',
Value: 'true',
},
{
Key: 'stickiness.type',
Value: 'lb_cookie',
},
{
Key: 'stickiness.lb_cookie.duration_seconds',
Value: '300',
},
],
});
});

test('Load balancer app cookie stickiness', () => {
// GIVEN
const app = new cdk.App();
const stack = new cdk.Stack(app, 'Stack');
const vpc = new ec2.Vpc(stack, 'VPC', {});

// WHEN
new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', {
stickinessCookieDuration: cdk.Duration.minutes(5),
stickinessCookieName: 'MyDeliciousCookie',
vpc,
});

// THEN
expect(stack).toHaveResource('AWS::ElasticLoadBalancingV2::TargetGroup', {
TargetGroupAttributes: [
{
Key: 'stickiness.enabled',
Value: 'true',
},
{
Key: 'stickiness.type',
Value: 'app_cookie',
},
{
Key: 'stickiness.app_cookie.cookie_name',
Value: 'MyDeliciousCookie',
},
{
Key: 'stickiness.app_cookie.duration_seconds',
Value: '300',
},
],
});
});

test('Bad stickiness cookie names example', () => {
// GIVEN
const app = new cdk.App();
const stack = new cdk.Stack(app, 'Stack');
const vpc = new ec2.Vpc(stack, 'VPC', {});

// THEN
expect(() => {
new elbv2.ApplicationTargetGroup(stack, 'TargetGroup1', {
stickinessCookieDuration: cdk.Duration.minutes(5),
stickinessCookieName: 'AWSALBCookieName',
vpc,
});
}).toThrow(/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./);
robertd marked this conversation as resolved.
Show resolved Hide resolved

expect(() => {
new elbv2.ApplicationTargetGroup(stack, 'TargetGroup2', {
stickinessCookieDuration: cdk.Duration.minutes(5),
stickinessCookieName: 'AWSALBstickinessCookieName',
vpc,
});
}).toThrow(/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./);

expect(() => {
new elbv2.ApplicationTargetGroup(stack, 'TargetGroup3', {
stickinessCookieDuration: cdk.Duration.minutes(5),
stickinessCookieName: 'AWSALBTGCookieName',
vpc,
});
}).toThrow(/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./);
});

test('Empty stickiness cookie name', () => {
// GIVEN
const app = new cdk.App();
const stack = new cdk.Stack(app, 'Stack');
const vpc = new ec2.Vpc(stack, 'VPC', {});

// THEN
expect(() => {
new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', {
stickinessCookieDuration: cdk.Duration.minutes(5),
stickinessCookieName: '',
vpc,
});
}).toThrow(/App cookie name cannot be an empty string./);
});
});
Loading