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:
ApplicationLoadBalancer
(ALB)ApplicationTargetGroup
(ATG) aka Target GroupWe 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.
Any help populating that targets
property of the ApplicationTargetGroup
with IApplicationLoadBalancerTarget
objects is appreciated. Thanks!
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.
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:
vpcEndpointDnsEntries
and VPC-ID as outputs using CfnOutput.Image credits: