packages/aws-cdk-lib/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts (451 lines of code) (raw):

import { Construct } from 'constructs'; import { BaseNetworkListenerProps, NetworkListener } from './network-listener'; import * as cloudwatch from '../../../aws-cloudwatch'; import * as ec2 from '../../../aws-ec2'; import * as cxschema from '../../../cloud-assembly-schema'; import { Lazy, Resource, Token } from '../../../core'; import { ValidationError } from '../../../core/lib/errors'; import { addConstructMetadata, MethodMetadata } from '../../../core/lib/metadata-resource'; import * as cxapi from '../../../cx-api'; import { NetworkELBMetrics } from '../elasticloadbalancingv2-canned-metrics.generated'; import { BaseLoadBalancer, BaseLoadBalancerLookupOptions, BaseLoadBalancerProps, ILoadBalancerV2, SubnetMapping } from '../shared/base-load-balancer'; import { IpAddressType, Protocol } from '../shared/enums'; import { parseLoadBalancerFullName } from '../shared/util'; /** * Indicates how traffic is distributed among the load balancer Availability Zones. * * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/network/network-load-balancers.html#zonal-dns-affinity */ export enum ClientRoutingPolicy { /** * 100 percent zonal affinity */ AVAILABILITY_ZONE_AFFINITY = 'availability_zone_affinity', /** * 85 percent zonal affinity */ PARTIAL_AVAILABILITY_ZONE_AFFINITY = 'partial_availability_zone_affinity', /** * No zonal affinity */ ANY_AVAILABILITY_ZONE = 'any_availability_zone', } /** * Properties for a network load balancer */ export interface NetworkLoadBalancerProps extends BaseLoadBalancerProps { /** * Security groups to associate with this load balancer * * @default - No security groups associated with the load balancer. */ readonly securityGroups?: ec2.ISecurityGroup[]; /** * The type of IP addresses to use * * If you want to add a UDP or TCP_UDP listener to the load balancer, * you must choose IPv4. * * @default IpAddressType.IPV4 */ readonly ipAddressType?: IpAddressType; /** * The AZ affinity routing policy * * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/network/network-load-balancers.html#zonal-dns-affinity * * @default - AZ affinity is disabled. */ readonly clientRoutingPolicy?: ClientRoutingPolicy; /** * Indicates whether to evaluate inbound security group rules for traffic sent to a Network Load Balancer through AWS PrivateLink. * * @default true */ readonly enforceSecurityGroupInboundRulesOnPrivateLinkTraffic?: boolean; /** * Indicates whether zonal shift is enabled * * @see https://docs.aws.amazon.com/elasticloadbalancing/latest/network/zonal-shift.html * * @default false */ readonly zonalShift?: boolean; /** * Indicates whether to use an IPv6 prefix from each subnet for source NAT. * * The IP address type must be IpAddressType.DUALSTACK. * * @default undefined - NLB default behavior is false */ readonly enablePrefixForIpv6SourceNat?: boolean; /** * Subnet information for the load balancer. * * @default undefined - The VPC default strategy for subnets is used */ readonly subnetMappings?: SubnetMapping[]; } /** * Properties to reference an existing load balancer */ export interface NetworkLoadBalancerAttributes { /** * ARN of the load balancer */ readonly loadBalancerArn: string; /** * The canonical hosted zone ID of this load balancer * * @default - When not provided, LB cannot be used as Route53 Alias target. */ readonly loadBalancerCanonicalHostedZoneId?: string; /** * The DNS name of this load balancer * * @default - When not provided, LB cannot be used as Route53 Alias target. */ readonly loadBalancerDnsName?: string; /** * The VPC to associate with the load balancer. * * @default - When not provided, listeners cannot be created on imported load * balancers. */ readonly vpc?: ec2.IVpc; /** * Security groups to associate with this load balancer * * @default - No security groups associated with the load balancer. */ readonly loadBalancerSecurityGroups?: string[]; } /** * Options for looking up an NetworkLoadBalancer */ export interface NetworkLoadBalancerLookupOptions extends BaseLoadBalancerLookupOptions { } /** * The metrics for a network load balancer. */ class NetworkLoadBalancerMetrics implements INetworkLoadBalancerMetrics { private readonly loadBalancerFullName: string; private readonly scope: Construct; constructor(scope: Construct, loadBalancerFullName: string) { this.scope = scope; this.loadBalancerFullName = loadBalancerFullName; } public custom(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { return new cloudwatch.Metric({ namespace: 'AWS/NetworkELB', metricName, dimensionsMap: { LoadBalancer: this.loadBalancerFullName }, ...props, }).attachTo(this.scope); } public activeFlowCount(props?: cloudwatch.MetricOptions) { return this.cannedMetric(NetworkELBMetrics.activeFlowCountAverage, props); } public consumedLCUs(props?: cloudwatch.MetricOptions) { return this.cannedMetric(NetworkELBMetrics.consumedLcUsAverage, { statistic: 'Sum', ...props, }); } public newFlowCount(props?: cloudwatch.MetricOptions) { return this.cannedMetric(NetworkELBMetrics.newFlowCountSum, props); } public processedBytes(props?: cloudwatch.MetricOptions) { return this.cannedMetric(NetworkELBMetrics.processedBytesSum, props); } public tcpClientResetCount(props?: cloudwatch.MetricOptions) { return this.cannedMetric(NetworkELBMetrics.tcpClientResetCountSum, props); } public tcpElbResetCount(props?: cloudwatch.MetricOptions) { return this.cannedMetric(NetworkELBMetrics.tcpElbResetCountSum, props); } public tcpTargetResetCount(props?: cloudwatch.MetricOptions) { return this.cannedMetric(NetworkELBMetrics.tcpTargetResetCountSum, props); } private cannedMetric( fn: (dims: { LoadBalancer: string }) => cloudwatch.MetricProps, props?: cloudwatch.MetricOptions, ): cloudwatch.Metric { return new cloudwatch.Metric({ ...fn({ LoadBalancer: this.loadBalancerFullName }), ...props, }).attachTo(this.scope); } } /** * Define a new network load balancer * * @resource AWS::ElasticLoadBalancingV2::LoadBalancer */ export class NetworkLoadBalancer extends BaseLoadBalancer implements INetworkLoadBalancer { /** * Looks up the network load balancer. */ public static fromLookup(scope: Construct, id: string, options: NetworkLoadBalancerLookupOptions): INetworkLoadBalancer { const props = BaseLoadBalancer._queryContextProvider(scope, { userOptions: options, loadBalancerType: cxschema.LoadBalancerType.NETWORK, }); return new LookedUpNetworkLoadBalancer(scope, id, props); } public static fromNetworkLoadBalancerAttributes(scope: Construct, id: string, attrs: NetworkLoadBalancerAttributes): INetworkLoadBalancer { class Import extends Resource implements INetworkLoadBalancer { public readonly connections: ec2.Connections = new ec2.Connections({ securityGroups: attrs.loadBalancerSecurityGroups?.map( (securityGroupId, index) => ec2.SecurityGroup.fromSecurityGroupId(this, `SecurityGroup-${index}`, securityGroupId), ), }); public readonly loadBalancerArn = attrs.loadBalancerArn; public readonly vpc?: ec2.IVpc = attrs.vpc; public readonly metrics: INetworkLoadBalancerMetrics = new NetworkLoadBalancerMetrics(this, parseLoadBalancerFullName(attrs.loadBalancerArn)); public readonly securityGroups?: string[] = attrs.loadBalancerSecurityGroups; public addListener(lid: string, props: BaseNetworkListenerProps): NetworkListener { return new NetworkListener(this, lid, { loadBalancer: this, ...props, }); } public get loadBalancerCanonicalHostedZoneId(): string { if (attrs.loadBalancerCanonicalHostedZoneId) { return attrs.loadBalancerCanonicalHostedZoneId; } // eslint-disable-next-line max-len throw new ValidationError(`'loadBalancerCanonicalHostedZoneId' was not provided when constructing Network Load Balancer ${this.node.path} from attributes`, this); } public get loadBalancerDnsName(): string { if (attrs.loadBalancerDnsName) { return attrs.loadBalancerDnsName; } // eslint-disable-next-line max-len throw new ValidationError(`'loadBalancerDnsName' was not provided when constructing Network Load Balancer ${this.node.path} from attributes`, this); } } return new Import(scope, id, { environmentFromArn: attrs.loadBalancerArn }); } public readonly metrics: INetworkLoadBalancerMetrics; public readonly ipAddressType?: IpAddressType; public readonly connections: ec2.Connections; private readonly isSecurityGroupsPropertyDefined: boolean; private readonly _enforceSecurityGroupInboundRulesOnPrivateLinkTraffic?: boolean; private enablePrefixForIpv6SourceNat?: boolean; /** * After the implementation of `IConnectable` (see https://github.com/aws/aws-cdk/pull/28494), the default * value for `securityGroups` is set by the `ec2.Connections` constructor to an empty array. * To keep backward compatibility (`securityGroups` is `undefined` if the related property is not specified) * a getter has been added. */ public get securityGroups(): string[] | undefined { return this.isSecurityGroupsPropertyDefined || this.connections.securityGroups.length ? this.connections.securityGroups.map(sg => sg.securityGroupId) : undefined; } constructor(scope: Construct, id: string, props: NetworkLoadBalancerProps) { super(scope, id, props, { type: 'network', securityGroups: Lazy.list({ produce: () => this.securityGroups }), ipAddressType: props.ipAddressType, enforceSecurityGroupInboundRulesOnPrivateLinkTraffic: Lazy.string({ produce: () => this.enforceSecurityGroupInboundRulesOnPrivateLinkTraffic, }), enablePrefixForIpv6SourceNat: props.enablePrefixForIpv6SourceNat === true ? 'on': props.enablePrefixForIpv6SourceNat === false ? 'off' : undefined, subnetMappings: props.subnetMappings, }); // Enhanced CDK Analytics Telemetry addConstructMetadata(this, props); if (props.subnetMappings && props.subnetMappings.length > 0) { if (props.internetFacing && props.subnetMappings.some(sm => sm.privateIpv4Address !== undefined)) { throw new ValidationError('Cannot specify `privateIpv4Address` for a internet facing load balancer.', this); } if (props.internetFacing !== true && props.subnetMappings.some(sm => sm.allocationId !== undefined)) { throw new ValidationError('Cannot specify `allocationId` for a internal load balancer.', this); } if (props.enablePrefixForIpv6SourceNat !== true && props.subnetMappings.some(sm => sm.sourceNatIpv6Prefix !== undefined)) { throw new ValidationError('Cannot specify `sourceNatIpv6Prefix` for a load balancer that does not have `enablePrefixForIpv6SourceNat` enabled.', this); } } const minimumCapacityUnit = props.minimumCapacityUnit; if (minimumCapacityUnit && !Token.isUnresolved(minimumCapacityUnit)) { const capacityUnitPerAz = minimumCapacityUnit / props.vpc.availabilityZones.length; if (!Number.isInteger(minimumCapacityUnit) || capacityUnitPerAz < 2750 || capacityUnitPerAz > 45000) { throw new ValidationError(`'minimumCapacityUnit' must be a positive value between 2750 and 45000 per AZ for Network Load Balancer, got ${capacityUnitPerAz} LCU per AZ.`, this); } } this.enablePrefixForIpv6SourceNat = props.enablePrefixForIpv6SourceNat; this.metrics = new NetworkLoadBalancerMetrics(this, this.loadBalancerFullName); this.isSecurityGroupsPropertyDefined = !!props.securityGroups; this.connections = new ec2.Connections({ securityGroups: props.securityGroups }); this.ipAddressType = props.ipAddressType ?? IpAddressType.IPV4; if (props.clientRoutingPolicy) { this.setAttribute('dns_record.client_routing_policy', props.clientRoutingPolicy); } if (props.zonalShift !== undefined) { this.setAttribute('zonal_shift.config.enabled', props.zonalShift ? 'true' : 'false'); } this._enforceSecurityGroupInboundRulesOnPrivateLinkTraffic = props.enforceSecurityGroupInboundRulesOnPrivateLinkTraffic; } public get enforceSecurityGroupInboundRulesOnPrivateLinkTraffic(): string | undefined { if (this._enforceSecurityGroupInboundRulesOnPrivateLinkTraffic === undefined) return undefined; return this._enforceSecurityGroupInboundRulesOnPrivateLinkTraffic ? 'on' : 'off'; } /** * Add a listener to this load balancer * * @returns The newly created listener */ @MethodMetadata() public addListener(id: string, props: BaseNetworkListenerProps): NetworkListener { // UDP listener with dual stack NLB requires prefix IPv6 source NAT to be enabled if ( (props.protocol === Protocol.UDP || props.protocol === Protocol.TCP_UDP) && (this.ipAddressType === IpAddressType.DUAL_STACK || this.ipAddressType === IpAddressType.DUAL_STACK_WITHOUT_PUBLIC_IPV4) && this.enablePrefixForIpv6SourceNat !== true ) { throw new ValidationError('To add a listener with UDP protocol to a dual stack NLB, \'enablePrefixForIpv6SourceNat\' must be set to true.', this); } return new NetworkListener(this, id, { loadBalancer: this, ...props, }); } /** * Add a security group to this load balancer */ @MethodMetadata() public addSecurityGroup(securityGroup: ec2.ISecurityGroup) { this.connections.addSecurityGroup(securityGroup); } /** * Return the given named metric for this Network Load Balancer * * @default Average over 5 minutes * @deprecated Use ``NetworkLoadBalancer.metrics.custom`` instead */ @MethodMetadata() public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { return new cloudwatch.Metric({ namespace: 'AWS/NetworkELB', metricName, dimensions: { LoadBalancer: this.loadBalancerFullName }, ...props, }).attachTo(this); } /** * The total number of concurrent TCP flows (or connections) from clients to targets. * * This metric includes connections in the SYN_SENT and ESTABLISHED states. * TCP connections are not terminated at the load balancer, so a client * opening a TCP connection to a target counts as a single flow. * * @default Average over 5 minutes * @deprecated Use ``NetworkLoadBalancer.metrics.activeFlowCount`` instead */ @MethodMetadata() public metricActiveFlowCount(props?: cloudwatch.MetricOptions) { return this.metrics.activeFlowCount(props); } /** * The number of load balancer capacity units (LCU) used by your load balancer. * * @default Sum over 5 minutes * @deprecated Use ``NetworkLoadBalancer.metrics.activeFlowCount`` instead */ @MethodMetadata() public metricConsumedLCUs(props?: cloudwatch.MetricOptions) { return this.metrics.consumedLCUs(props); } /** * The number of targets that are considered healthy. * * @default Average over 5 minutes * @deprecated use ``NetworkTargetGroup.metricHealthyHostCount`` instead */ @MethodMetadata() public metricHealthyHostCount(props?: cloudwatch.MetricOptions) { return this.metric('HealthyHostCount', { statistic: 'Average', ...props, }); } /** * The number of targets that are considered unhealthy. * * @default Average over 5 minutes * @deprecated use ``NetworkTargetGroup.metricUnHealthyHostCount`` instead */ @MethodMetadata() public metricUnHealthyHostCount(props?: cloudwatch.MetricOptions) { return this.metric('UnHealthyHostCount', { statistic: 'Average', ...props, }); } /** * The total number of new TCP flows (or connections) established from clients to targets in the time period. * * @default Sum over 5 minutes * @deprecated Use ``NetworkLoadBalancer.metrics.newFlowCount`` instead */ @MethodMetadata() public metricNewFlowCount(props?: cloudwatch.MetricOptions) { return this.metrics.newFlowCount(props); } /** * The total number of bytes processed by the load balancer, including TCP/IP headers. * * @default Sum over 5 minutes * @deprecated Use ``NetworkLoadBalancer.metrics.processedBytes`` instead */ @MethodMetadata() public metricProcessedBytes(props?: cloudwatch.MetricOptions) { return this.metrics.processedBytes(props); } /** * The total number of reset (RST) packets sent from a client to a target. * * These resets are generated by the client and forwarded by the load balancer. * * @default Sum over 5 minutes * @deprecated Use ``NetworkLoadBalancer.metrics.tcpClientResetCount`` instead */ @MethodMetadata() public metricTcpClientResetCount(props?: cloudwatch.MetricOptions) { return this.metrics.tcpClientResetCount(props); } /** * The total number of reset (RST) packets generated by the load balancer. * * @default Sum over 5 minutes * @deprecated Use ``NetworkLoadBalancer.metrics.tcpElbResetCount`` instead */ @MethodMetadata() public metricTcpElbResetCount(props?: cloudwatch.MetricOptions) { return this.metrics.tcpElbResetCount(props); } /** * The total number of reset (RST) packets sent from a target to a client. * * These resets are generated by the target and forwarded by the load balancer. * * @default Sum over 5 minutes * @deprecated Use ``NetworkLoadBalancer.metrics.tcpTargetResetCount`` instead */ @MethodMetadata() public metricTcpTargetResetCount(props?: cloudwatch.MetricOptions) { return this.metrics.tcpTargetResetCount(props); } } /** * Contains all metrics for a Network Load Balancer. */ export interface INetworkLoadBalancerMetrics { /** * Return the given named metric for this Network Load Balancer * * @default Average over 5 minutes */ custom(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric; /** * The total number of concurrent TCP flows (or connections) from clients to targets. * * This metric includes connections in the SYN_SENT and ESTABLISHED states. * TCP connections are not terminated at the load balancer, so a client * opening a TCP connection to a target counts as a single flow. * * @default Average over 5 minutes */ activeFlowCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric; /** * The number of load balancer capacity units (LCU) used by your load balancer. * * @default Sum over 5 minutes */ consumedLCUs(props?: cloudwatch.MetricOptions): cloudwatch.Metric; /** * The total number of new TCP flows (or connections) established from clients to targets in the time period. * * @default Sum over 5 minutes */ newFlowCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric; /** * The total number of bytes processed by the load balancer, including TCP/IP headers. * * @default Sum over 5 minutes */ processedBytes(props?: cloudwatch.MetricOptions): cloudwatch.Metric; /** * The total number of reset (RST) packets sent from a client to a target. * * These resets are generated by the client and forwarded by the load balancer. * * @default Sum over 5 minutes */ tcpClientResetCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric; /** * The total number of reset (RST) packets generated by the load balancer. * * @default Sum over 5 minutes */ tcpElbResetCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric; /** * The total number of reset (RST) packets sent from a target to a client. * * These resets are generated by the target and forwarded by the load balancer. * * @default Sum over 5 minutes */ tcpTargetResetCount(props?: cloudwatch.MetricOptions): cloudwatch.Metric; } /** * A network load balancer */ export interface INetworkLoadBalancer extends ILoadBalancerV2, ec2.IVpcEndpointServiceLoadBalancer, ec2.IConnectable { /** * The VPC this load balancer has been created in (if available) */ readonly vpc?: ec2.IVpc; /** * All metrics available for this load balancer */ readonly metrics: INetworkLoadBalancerMetrics; /** * Security groups associated with this load balancer */ readonly securityGroups?: string[]; /** * The type of IP addresses to use * * @default IpAddressType.IPV4 */ readonly ipAddressType?: IpAddressType; /** * Indicates whether to evaluate inbound security group rules for traffic sent to a Network Load Balancer through AWS PrivateLink * * @default on */ readonly enforceSecurityGroupInboundRulesOnPrivateLinkTraffic?: string; /** * Add a listener to this load balancer * * @returns The newly created listener */ addListener(id: string, props: BaseNetworkListenerProps): NetworkListener; } class LookedUpNetworkLoadBalancer extends Resource implements INetworkLoadBalancer { public readonly loadBalancerCanonicalHostedZoneId: string; public readonly loadBalancerDnsName: string; public readonly loadBalancerArn: string; public readonly vpc?: ec2.IVpc; public readonly metrics: INetworkLoadBalancerMetrics; public readonly securityGroups?: string[]; public readonly ipAddressType?: IpAddressType; public readonly connections: ec2.Connections; constructor(scope: Construct, id: string, props: cxapi.LoadBalancerContextResponse) { super(scope, id, { environmentFromArn: props.loadBalancerArn }); // Enhanced CDK Analytics Telemetry addConstructMetadata(this, props); this.loadBalancerArn = props.loadBalancerArn; this.loadBalancerCanonicalHostedZoneId = props.loadBalancerCanonicalHostedZoneId; this.loadBalancerDnsName = props.loadBalancerDnsName; this.metrics = new NetworkLoadBalancerMetrics(this, parseLoadBalancerFullName(props.loadBalancerArn)); this.securityGroups = props.securityGroupIds; this.connections = new ec2.Connections({ securityGroups: props.securityGroupIds.map( (securityGroupId, index) => ec2.SecurityGroup.fromLookupById(this, `SecurityGroup-${index}`, securityGroupId), ), }); if (props.ipAddressType === cxapi.LoadBalancerIpAddressType.IPV4) { this.ipAddressType = IpAddressType.IPV4; } else if (props.ipAddressType === cxapi.LoadBalancerIpAddressType.DUAL_STACK) { this.ipAddressType = IpAddressType.DUAL_STACK; } this.vpc = ec2.Vpc.fromLookup(this, 'Vpc', { vpcId: props.vpcId, }); } @MethodMetadata() public addListener(lid: string, props: BaseNetworkListenerProps): NetworkListener { return new NetworkListener(this, lid, { loadBalancer: this, ...props, }); } }