aws / aws/aws-cdk

(aws-elasticloadbalancingv2): Default HealthCheck Protocol Logic Flawed for NLB Target Groups with ALB Targets

Open
#18,422 12 comments 0 reactions 0 assignees View on GitHub
@aws-cdk/aws-elasticloadbalancingv2 bug effort/small p3
Dominant language
TypeScript
Stars
12.9k
Forks
4.6k
Avg merge
2d 3h
Merged PRs (30d)
83

Description

### What is the problem?

This is a bug present in the new NLB -> ALB functionality added to AWS and CloudFormation a few months ago and added to CDK shortly afterwards.

CDK incorrectly assumes the protocol for the healthCheck is HTTP even if the target group is using TCP on port 443, which puts the target group into an unhealthy state.

### Reproduction Steps

```ts
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from "@aws-cdk/aws-iam";
import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2';
import * as targets from '@aws-cdk/aws-elasticloadbalancingv2-targets';
import * as ecs from '@aws-cdk/aws-ecs';
import * as patterns from '@aws-cdk/aws-ecs-patterns';
import * as cm from '@aws-cdk/aws-certificatemanager';

export class NblToAlbStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const coreVpc = ec2.Vpc.fromLookup(this, 'CoreVPC', {isDefault: false, vpcName: 'core-vpc' });

const task = new ecs.FargateTaskDefinition(this, 'Task', { cpu: 256, memoryLimitMiB: 512 });
task.addContainer('nginx', {
image: ecs.ContainerImage.fromRegistry('public.ecr.aws/nginx/nginx:latest'),
portMappings: [{ containerPort: 80 }],
});

const certificate = cm.Certificate.fromCertificateArn(this, 'Certificate', 'MY-CERT-ARN');
const svc = new patterns.ApplicationLoadBalancedFargateService(this, 'Service', {
vpc: coreVpc,
taskDefinition: task,
publicLoadBalancer: false,
certificate,
protocol: elb.Protocol.HTTPS,
targetProtocol: elb.Protocol.HTTP,
});

const nlb = new elbv2.NetworkLoadBalancer(this, 'Nlb', {
vpc:coreVpc,
crossZoneEnabled: true,
internetFacing: true,
});

const listener = nlb.addListener('listener', { port: 443 });

listener.addTargets('Targets', {
targets: [new targets.AlbTarget(svc.loadBalancer, 443)],
port: 443,
protocol: elb.Protocol.TCP,
// Note the absence of the 'healthCheck' object here
});

new cdk.CfnOutput(this, 'NlbEndpoint', { value: `http://${nlb.loadBalancerDnsName}`})
}
}
```

### What did you expect to happen?

Creation of an NLB with a listener and a target group with an ALB target that correctly functions in all aspects, including not failing its health check.

### What actually happened?

In the AWS Console, you can see that the target group reports an unhealthy status and that the protocol is HTTP.

### CDK CLI Version

1.139.0

### Framework Version

_No response_

### Node.js Version

v14.17.3

### OS

Mac OS X 11.6.2

### Language

Typescript

### Language Version

_No response_

### Other information

Here's what the docs currently say should happen (but this is impossible for this target type, so the docs should be amended to mention the ALB target type scenario):
>/**
* (experimental) The protocol the load balancer uses when performing health checks on targets.
*
* The TCP protocol is supported for health checks only if the protocol of the target group is TCP, TLS, UDP, or TCP_UDP.
* The TLS, UDP, and TCP_UDP protocols are not supported for health checks.
*
* @default HTTP for ALBs, TCP for NLBs
* @experimental
*/
readonly protocol?: Protocol;

However, this doesn't make sense here. Apparently, AWS did not implement a TCP health check protocol option for target groups with ALB targets, which would be the obvious solution and what the CDK docs suggest should be happening. Instead, CDK needs to go down a list of logic rules to determine the default protocol it should be attempting for the health check. Here's the logic that I think should be used to determine health check protocol:

1. If an NLB target group's `healthCheck` object has been provided with a protocol when the NLB target group has an ALB target, just use that protocol.
2. If an NLB target group's `healthCheck` object has been provided without a protocol when the NLB target group has an ALB target and is using port 80, assume the health check's protocol is going to be HTTP.
3. If an NLB target group's `healthCheck` object has been provided without a protocol when the NLB target group has an ALB target and is using port 443, assume the health check's protocol is going to be HTTPS.
4. If an NLB target group's `healthCheck` object has been provided without a protocol when the NLB target group has an ALB target and is using neither port 80 nor 443, throw an error like "When using a non-standard port with an NLB target group that has an ALB target, you must explicitly declare a protocol of HTTP or HTTPS in your `healthCheck` object."
5. If an NLB target group's `healthCheck` object has not been provided when the NLB target group has an ALB target and is using port 80, assume the health check's protocol is going to be HTTP.
6. If an NLB target group's `healthCheck` object has not been provided when the NLB target group has an ALB target and is using port 443, assume the health check's protocol is going to be HTTPS.
7. If an NLB target group's `healthCheck` object has been provided without a protocol when the NLB target group has an ALB target and is using neither port 80 nor 443, throw an error like "When using a non-standard port with an NLB target group that has an ALB target, you must explicitly define a `healthCheck` object with a protocol of HTTP or HTTPS."

Complicated? Kinda, yeah. Won't be fun to explain in the docs, either, but I don't see any way around it. This is the behavior that users are going to expect to happen without doing loads of research on the specific quirks involved here.

Contributor guide

Open the contributing guide

Research direction

Start with the TypeScript reproduction using listener.addTargets, targets.AlbTarget, and the healthCheck options for an NLB target group. Trace how defaults are selected for ALB targets on ports 80 and 443, then verify the non-standard-port validation and explicit HTTP or HTTPS handling described in the issue. Done means the synthesized target group uses a valid health-check protocol instead of defaulting to HTTP for TCP port 443.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, typescript
Domain
cloud, devops
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.