amazon-web-servicesnetwork-programmingamazon-vpcaws-cdkvpc-endpoint

Using AWS CDK, How to connect an AWS Load balancer and an AWS Interface VPC Endpoint together


Background: We're using AWS Cloud Development Kit (CDK) 2.5.0.

Manually using the AWS Console and hard-coded IP addresses, Route 53 to an ALB (Application Load Balancer) to a private Interface VPC Endpoint to a private REST API-Gateway (and so on..) works. See image below.

Code: We're trying to code this manual solution via CDK, but are stuck on how to get and use the IP addresses or in some way hook up the load balancer to the Interface VPC Endpoint. (Endpoint has 3 IP addresses, one per availability zone in the region.)

The ALB needs a Target Group which targets the IP addresses of the Interface VPC Endpoint. (Using an "instance" approach instead of IP addresses, we tried using InstanceIdTarget with the endpoint's vpcEndpointId, but that failed. We got the error Instance ID 'vpce-WITHWHATEVERWASHERE' is not valid )

Using CDK, we created the following (among other things) using the aws_elasticloadbalancingv2 module:

We were hopeful about aws_elasticloadbalancingv2_targets similar to aws_route53_targets, but no luck. We know the targets property of the ApplicationTargetGroup takes an array of IApplicationLoadBalancerTarget objects, but that's it.

  :
import { aws_ec2 as ec2 } from 'aws-cdk-lib';
  :
import { aws_elasticloadbalancingv2 as loadbalancing } from 'aws-cdk-lib';

// endpointSG, loadBalancerSG, vpc, ... are defined up higher
        const endpoint = new ec2.InterfaceVpcEndpoint(this, `ABCEndpoint`, {
            service: {
                name: `com.amazonaws.us-east-1.execute-api`,
                port: 443
            },
            vpc,
            securityGroups: [endpointSG],
            privateDnsEnabled: false,
            subnets: { subnetGroupName: "Private" }
        });


        const loadBalancer = new loadbalancing.ApplicationLoadBalancer(this, 'abc-${config.LEVEL}-load-balancer', {
            vpc: vpc,
            vpcSubnets: { subnetGroupName: "Private" },
            internetFacing: false,
            securityGroup: loadBalancerSG
        });

        const listenerCertificate = loadbalancing.ListenerCertificate.fromArn(config.ARNS.CERTIFICATE)
        const listener = loadBalancer.addListener('listener', {
            port: 443,
            certificates: [ listenerCertificate ]
        });

        let applicationTargetGroup = new loadbalancing.ApplicationTargetGroup(this, 'abc-${config.LEVEL}-target-group', {
            port: 443,
            vpc: vpc,
            // targets: [ HELP ], - how to get the IApplicationLoadBalancerTarget objects?  
        })
        listener.addTargetGroups( 'abc-listener-forward-to-target-groups', { 
            targetGroups: [applicationTargetGroup]
        } );

As you can see above, we added a listener to the ALB. We added the Target Group to the listener.

Some of the resources we used:

In case visualizing the set up via an image helps, here's a close approximation of what we're going for. enter image description here

Any help populating that targets property of the ApplicationTargetGroup with IApplicationLoadBalancerTarget objects is appreciated. Thanks!


Solution

  • https://aws.amazon.com/blogs/networking-and-content-delivery/accessing-an-aws-api-gateway-via-static-ip-addresses-provided-by-aws-global-accelerator/

    This blog shows how to configure the architecture given in the question using AWS console (just disable the global accelerator option). The key takeaway is that the application load balancer uses target type IP and resolves the VPC endpoint domain name manually in step 2. The other two options, instance (target is an EC2 instances) and lambda (target is an AWS Lambda function) cannot be used.

    The ec2.InterfaceVpcEndpoint construct has no output which directly gives an IP address. The underlying CloudFormation resource also does not support it. Instead, you will have to use the vpcEndpointDnsEntries property of ec2.InterfaceVpcEndpoint and resolve the domain names to IP addresses in your code (the console configuration also required the same domain name resolution). You can use an IpTarget object in your ApplicationTargetGroup.

    At this point, you will run into one final roadblock due to how CDK works under the hood. cdk structure from documentation If you have all your resources defined in one CDK application, the value for each parameter (or a reference to the value using an underlying CloudFormation functions like Ref, GetAtt, etc.) needs to be available before the synthesize step, since that's when all templates are generated. AWS CDK uses tokens for this purpose, which during synthesis resolve to values such as {'Fn::GetAtt': ['EndpointResourceLogicalName', 'DnsEntries']. However since we need the actual value of the DNS entry to be able to resolve it, the token value won't be useful.

    One way to fix this issue is to have two completely independent CDK applications structured this way:

    Image credits: