I'm trying to deploy a fairly basic Nodejs CRUD API to AWS using AWS-CDK. The service runs in a docker container and I'm deploying it to an ECS Fargate cluster behind an ALB. I also have a domain in Route53 that I'm trying to use.
The problem I'm having is I can't seem to access the ALB through the domain. I can access the ALB directly using its default AWS DNS (XXXXX.us-west-2.elb.amazonaws.com/) over HTTP, but I get 504 timeouts when I attempt to access it through the domain.
I'm pretty new to AWS and CDK, so I'm sure I'm missing something obvious here. Any advice or recommended resources/examples would be much appreciated. Here's my CDK code:
import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as Cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as CloudfrontOrigins from "aws-cdk-lib/aws-cloudfront-origins";
import * as Route53 from "aws-cdk-lib/aws-route53";
import * as Route53Targets from "aws-cdk-lib/aws-route53-targets";
import * as ACM from "aws-cdk-lib/aws-certificatemanager";
import * as EC2 from "aws-cdk-lib/aws-ec2";
import * as ECS from "aws-cdk-lib/aws-ecs";
import * as EcsPatterns from "aws-cdk-lib/aws-ecs-patterns";
interface Props extends StackProps {
domainName: string;
dockerDir: string;
}
export class AppStack extends Stack {
constructor(scope: Construct, id: string, { domainName, dockerDir, ...rest }: Props) {
super(scope, id, rest);
const hostedZone = Route53.HostedZone.fromLookup(this, `${id}_Zone`, {
domainName,
});
const vpc = new EC2.Vpc(this, `${id}_Vpc`, { maxAzs: 2 });
const cluster = new ECS.Cluster(this, `${id}_Ec2Cluster`, { vpc });
cluster.addCapacity(`${id}_DefaultAutoScalingGroup`, {
instanceType: EC2.InstanceType.of(
EC2.InstanceClass.T3,
EC2.InstanceSize.MICRO
),
minCapacity: 1,
maxCapacity: 3,
});
const certificate = new ACM.DnsValidatedCertificate(
this,
`${id}_SiteCertificate`,
{
domainName,
hostedZone,
region: "us-east-1",
}
);
const fargateService = new EcsPatterns.ApplicationLoadBalancedFargateService(
this,
`${id}_FargateLoadBalancedService`,
{
cluster,
desiredCount: 1,
publicLoadBalancer: true,
taskImageOptions: {
image: ECS.ContainerImage.fromAsset(dockerDir),
containerPort: 8000,
environment: {
PORT: '8000',
},
},
}
);
const distribution = new Cloudfront.Distribution(
this,
`${id}_SiteDistribution`,
{
certificate,
domainNames: [domainName],
minimumProtocolVersion: Cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
defaultBehavior: {
origin: new CloudfrontOrigins.HttpOrigin(
fargateService.loadBalancer.loadBalancerDnsName
),
compress: false,
cachePolicy: Cloudfront.CachePolicy.CACHING_DISABLED,
allowedMethods: Cloudfront.AllowedMethods.ALLOW_ALL,
},
}
);
new Route53.ARecord(this, `${id}_SiteAliasRecord`, {
recordName: domainName,
target: Route53.RecordTarget.fromAlias(
new Route53Targets.CloudFrontTarget(distribution)
),
zone: hostedZone,
});
}
}
And this class gets created in my bin/infra.ts file:
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import * as path from "path";
import { AppStack } from "../lib/AppStack";
const appId = `MyApp`;
const app = new cdk.App();
new AppStack(app, `${appId}Stack`, {
dockerDir: path.resolve(__dirname, "..", "api"), // contains the Dockerfile
domainName: 'mydomain.com',
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
});
And here's the Dockerfile in case it's useful.
FROM node:16-alpine as builder
ENV NODE_ENV build
USER node
WORKDIR /home/node
COPY package*.json ./
RUN npm i
COPY --chown=node:node . .
RUN npm run build \
&& npm prune --production
# ---
FROM node:16-alpine
ENV PORT 8000
ENV NODE_ENV production
# Add curl for healthcheck
RUN apk --no-cache add curl
USER node
WORKDIR /home/node
COPY --from=builder --chown=node:node /home/node/package*.json ./
COPY --from=builder --chown=node:node /home/node/node_modules/ ./node_modules/
COPY --from=builder --chown=node:node /home/node/dist/ ./dist/
EXPOSE 8000
CMD ["node", "dist/main.js"]
HEALTHCHECK CMD curl -f http://localhost:8000/api/healthcheck || exit 1
Why am I getting 504 errors when I access my service through my domain? Or where can I look to get a better idea of what I'm missing?
CloudFront talks HTTPS (port 443) to its origins by default. An ALB (regardless of whether created explicitly or implicitly by the ApplicationLoadBalancedFargateService construct) listens on HTTP (port 80) by default unless explicitly set up for HTTPS.
As your ALB is not configured to listen on HTTPS, CloudFront attempts to talk HTTPS to an ALB that only listens on HTTP.
To fix that, set origin.protocolPolicy to OriginProtocolPolicy.HTTP_ONLY, which instructs CloudFront to talk HTTP to your ALB. Please note that the below code uses CDK v2 and also, I've used LoadBalancerV2Origin over HttpOrigin, although both should work the same.
const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', {
vpc,
internetFacing: true,
});
new cloudfront.Distribution(this, 'myDist', {
defaultBehavior: {
new origins.LoadBalancerV2Origin(loadBalancer, {
protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
})
},
});
Related
Problem
I need create a public API in AWS which will be integrated with multiple services: including multiple fargate services, EC2 services, and Lambda, etc. You can think it as twitter or facebook public API. Something like this: https://aws.amazon.com/blogs/compute/architecting-multiple-microservices-behind-a-single-domain-with-amazon-api-gateway/
Now I'm working on integrating one of its endpoint to an "ApplicationLoadBalancedFargateService".
I only see HTTPIntegration and LambdaIntegration there, not "AlbIntegration".
Therefore, I picked HTTPIntegration which requires VPCLLink.
Then I find that the "targets" attribute in ApiGateway.VpcLink only support INetworkLoadBalancer type. But my fargate service is created with EcsPatterns.ApplicationLoadBalancedFargateService.
So the error I see at line targets: [PandaServiceLoadBalancer] is :
Type 'IApplicationLoadBalancer' is not assignable to type 'INetworkLoadBalancer'.
Types of property 'addListener' are incompatible.
What change should I make here to make it work?
Should I change my fargate service to EcsPatterns.NetworkLoadBalancedFargateService to make it easier?
Details
I have a fargate service defined as this:
this.fargateService =
new EcsPatterns.ApplicationLoadBalancedFargateService(
this,
`${props.stageName}-${this.serviceName}-ID`,
{
cluster: PandaServiceFargetCluster, // Required
//loadBalancerName: "PandaServiceLoadBalancer",
serviceName: "PandaService",
desiredCount: 1, // Increase it for better scalability.
taskDefinition: this.createFargateTask(props),
publicLoadBalancer: true, // Default is false
healthCheckGracePeriod: CDK.Duration.minutes(1),
circuitBreaker: {
rollback: true,
},
}
);
Now in my public API gateway, I want to integrate it with the fargate service.
import * as CDK from "aws-cdk-lib";
import * as CertificateManager from "aws-cdk-lib/aws-certificatemanager";
import * as Route53 from "aws-cdk-lib/aws-route53";
import * as ApiGateway from "aws-cdk-lib/aws-apigateway";
import * as ELBv2 from "aws-cdk-lib/aws-elasticloadbalancingv2";
import { Construct } from "constructs";
import { StageInfo } from "../config/stage-config";
import * as EC2 from "aws-cdk-lib/aws-ec2";
...
...
...
this.vpcLink = new ApiGateway.VpcLink(this, `${this.constructIdPrefix}VpcLinkCreation`, {
vpcLinkName: "PandaServiceVpcLink"
targets: [PandaServiceLoadBalancer]
});
this.pandaApi = new ApiGateway.RestApi(
this,
`${this.constructIdPrefix}-pandaApi`,
{
description: "The centralized API for panda.com",
domainName: {
domainName: props.stageInfo.domainName,
certificate: domainCertificate,
//mappingKey: props.pipelineStageInfo.stageName
},
defaultCorsPreflightOptions: {
allowOrigins: ApiGateway.Cors.ALL_ORIGINS,
allowMethods: [...ApiGateway.Cors.DEFAULT_HEADERS],
},
}
);
const productsResource = this.pandaApi.root.addResource("products");
productsResource.addMethod(
"GET",
new ApiGateway.HttpIntegration(
`${props.stageInfo.PandaServiceLoadBalancerDns}/products`,
{
httpMethod: "GET",
options: {
connectionType: ApiGateway.ConnectionType.VPC_LINK,
vpcLink: this.vpcLink,
},
}
)
);
I have a CDK project that creates a CodePipeline which deploys an application on ECS. I had it all previously working, but the VPC was using a NAT gateway, which ended up being too expensive. So now I am trying to recreate the project without requiring a NAT gateway. I am almost there, but I have now run into issues when the ECS service is trying to start tasks. All tasks fail to start with the following error:
ResourceInitializationError: unable to pull secrets or registry auth: execution resource retrieval failed: unable to retrieve secret from asm: service call has been retried 5 time(s): failed to fetch secret
At this point I've kind of lost track of the different things I have tried, but I will post the relevant bits here as well as some of my attempts.
const repository = ECR.Repository.fromRepositoryAttributes(
this,
"ecr-repository",
{
repositoryArn: props.repository.arn,
repositoryName: props.repository.name,
}
);
// vpc
const vpc = new EC2.Vpc(this, this.resourceName(props, "vpc"), {
maxAzs: 2,
natGateways: 0,
enableDnsSupport: true,
});
const vpcSecurityGroup = new SecurityGroup(this, "vpc-security-group", {
vpc: vpc,
allowAllOutbound: true,
});
// tried this to allow the task to access secrets manager
const vpcEndpoint = new EC2.InterfaceVpcEndpoint(this, "secrets-manager-task-vpc-endpoint", {
vpc: vpc,
service: EC2.InterfaceVpcEndpointAwsService.SSM,
});
const secrets = SecretsManager.Secret.fromSecretCompleteArn(
this,
"secrets",
props.secrets.arn
);
const cluster = new ECS.Cluster(this, this.resourceName(props, "cluster"), {
vpc: vpc,
clusterName: `api-cluster`,
});
const ecsService = new EcsPatterns.ApplicationLoadBalancedFargateService(
this,
"ecs-service",
{
taskSubnets: {
subnetType: SubnetType.PUBLIC,
},
securityGroups: [vpcSecurityGroup],
serviceName: "api-service",
cluster: cluster,
cpu: 256,
desiredCount: props.scaling.desiredCount,
taskImageOptions: {
image: ECS.ContainerImage.fromEcrRepository(
repository,
this.ecrTagNameParameter.stringValue
),
secrets: getApplicationSecrets(secrets), // returns
logDriver: LogDriver.awsLogs({
streamPrefix: "api",
logGroup: new LogGroup(this, "ecs-task-log-group", {
logGroupName: `${props.environment}-api`,
}),
logRetention: RetentionDays.TWO_MONTHS,
}),
},
memoryLimitMiB: 512,
publicLoadBalancer: true,
domainZone: this.hostedZone,
certificate: this.certificate,
redirectHTTP: true,
}
);
const scalableTarget = ecsService.service.autoScaleTaskCount({
minCapacity: props.scaling.desiredCount,
maxCapacity: props.scaling.maxCount,
});
scalableTarget.scaleOnCpuUtilization("cpu-scaling", {
targetUtilizationPercent: props.scaling.cpuPercentage,
});
scalableTarget.scaleOnMemoryUtilization("memory-scaling", {
targetUtilizationPercent: props.scaling.memoryPercentage,
});
secrets.grantRead(ecsService.taskDefinition.taskRole);
repository.grantPull(ecsService.taskDefinition.taskRole);
I read somewhere that it probably has something to do with Fargate version 1.4.0 vs 1.3.0, but I'm not sure what I need to change to allow the tasks to access what they need to run.
You need to create an interface endpoints for Secrets Manager, ECR (two types of endpoints), CloudWatch, as well as a gateway endpoint for S3.
Refer to the documentation on the topic.
Here's an example in Python, it'd work the same in TS:
vpc.add_interface_endpoint(
"secretsmanager_endpoint",
service=ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER,
)
vpc.add_interface_endpoint(
"ecr_docker_endpoint",
service=ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER,
)
vpc.add_interface_endpoint(
"ecr_endpoint",
service=ec2.InterfaceVpcEndpointAwsService.ECR,
)
vpc.add_interface_endpoint(
"cloudwatch_logs_endpoint",
service=ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS,
)
vpc.add_gateway_endpoint(
"s3_endpoint",
service=ec2.GatewayVpcEndpointAwsService.S3
)
Keep in mind that interface endpoints cost money as well, and may not be cheaper than a NAT.
I have a flask app which I'm trying to deploy into a landing zone, using CDK (Typescript, v2.5.0) as a Fargate instance.
The landing zone an existing VPC which I need to use, with Isolated and Private subnets.
I've tried every combination I can think of to get the load balancer (tried both application- and network-balanced) to use the Isolated subnets, but nothing has worked.
The error I get from cdk synth is
deploy/node_modules/aws-cdk-lib/aws-ec2/lib/vpc.ts:401
throw new Error(`There are no '${subnetType}' subnet groups in this VPC. Available types: ${availableTypes}`);
^
*Error: There are no 'Public' subnet groups in this VPC. Available types: Isolated*
Here's my code:
import * as cdk from "#aws-cdk/core";
import { Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as ecs from "aws-cdk-lib/aws-ecs";
import * as ecsp from "aws-cdk-lib/aws-ecs-patterns";
export class DeployStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const defaultnonprodVPC = "existing-vpc";
const defaultVPC = ec2.Vpc.fromLookup(this,
"defaultVPC",
{
isDefault: false,
vpcId: defaultnonprodVPC,
tags: { "aws-cdk:subnet-type": "isolated" }
}
);
const knownIsolatedSubnets = defaultVPC.isolatedSubnets;
const monitoringSubnets = defaultVPC.selectSubnets(
{
subnetType: ec2.SubnetType.PRIVATE_ISOLATED
}
);
const networkBalancedFargateService = new ecsp.NetworkLoadBalancedFargateService(this,
"ConnectorMonitorService", {
memoryLimitMiB: 512,
desiredCount: 1,
cpu: 256,
taskImageOptions: {
image: ecs.ContainerImage.fromAsset("../src")
},
taskSubnets:
{
subnetType: ec2.SubnetType.PRIVATE_ISOLATED
},
vpc: defaultVPC
});
}
}
Changing the taskSubnets to any of
{ subnets: { knownIsolatedSubnets } }
or
subnetGroupName: "subnet-existing-subnet-name"
or
monitoringSubnets
makes no difference to cdk synth. Setting assignPublicIp: false doesn't change things either.
What am I doing wrong, or missing?
NetworkLoadBalancedFargateService has a property publicLoadBalancer which is true by default. This makes the Load Balancer internet-facing, which is not correct in your case. You need to set it to false so that private or isolated subnets work.
Documentation for NetworkLoadBalancedFargateService
I have defined a CDK app stack using TypeScript (sensitive information rendomized in the code below):
import * as cdk from "#aws-cdk/core";
import * as ec2 from "#aws-cdk/aws-ec2";
import * as ecs from "#aws-cdk/aws-ecs";
import * as ecr from "#aws-cdk/aws-ecr";
import * as ecr_assets from "#aws-cdk/aws-ecr-assets";
import * as ecs_patterns from "#aws-cdk/aws-ecs-patterns";
import * as sm from "#aws-cdk/aws-secretsmanager";
export class CdkAppStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Create a Docker image and upload it to the Amazon Elastic Container Registry (ECR)
const dockerImage = new ecr_assets.DockerImageAsset(this, "ApiDockerImage", {
directory: "/home/ec2-user/environment/node-test"
});
// Create a new VPC and NAT Gateway
const vpc = new ec2.Vpc(this, "ApiVpc", {
maxAzs: 3 // Default is all AZs in region
});
// Create a new Amazon Elastic Container Service (ECS) cluster
const cluster = new ecs.Cluster(this, "ApiCluster", {
vpc: vpc
});
// Create a load-balanced Fargate service and make it public
new ecs_patterns.ApplicationLoadBalancedFargateService(this, "ApiFargateService", {
cluster: cluster, // Required
cpu: 512, // Default is 256
desiredCount: 2, // Default is 1
taskImageOptions: {
image: ecs.ContainerImage.fromDockerImageAsset(dockerImage),
containerPort: 8080,
enableLogging: true,
secrets: sm.Secret.fromSecretCompleteArn(this, "ImportedSecret", "arn:aws:secretsmanager:ap-south-1:762589711820:secret:/api/production/FrOibp")
},
memoryLimitMiB: 2048, // Default is 512
publicLoadBalancer: true // Default is false
});
}
}
Deployment with cdk deploy is successful if I remove the secrets key from taskImageOptions but with the secrets there, I get this error when trying to deploy:
ec2-user:~/environment/cdk-app (master) $ cdk deploy
тип Unable to compile TypeScript:
lib/cdk-app-stack.ts:42:9 - error TS2322: Type 'ISecret' is not assignable to type '{ [key: string]: Secret; }'.
Index signature is missing in type 'ISecret'.
42 secrets: secret
~~~~~~~
Subprocess exited with error 1
I'm doing something wrong here in trying to use secrets from Secrets Manager. What is the right way to reference secrets in a ApplicationLoadBalancedFargateService?
There are two issues here:
secrets is of type index signature. you should therefore name your secret (this is the environment variable that will be exposed in your container)
an ecs.Secret is expected (you can create it from an sm.Secret)
here is a working version:
new ecs_patterns.ApplicationLoadBalancedFargateService(this, "ApiFargateService", {
cluster: cluster, // Required
cpu: 512, // Default is 256
desiredCount: 2, // Default is 1
taskImageOptions: {
image: ecs.ContainerImage.fromDockerImageAsset(dockerImage),
containerPort: 8080,
enableLogging: true,
secrets: {
"MY_SECRET": ecs.Secret.fromSecretsManager( sm.Secret.fromSecretCompleteArn(this, "ImportedSecret", "arn:aws:secretsmanager:ap-south-1:762589711820:secret:/api/production/FrOibp"))
}
},
memoryLimitMiB: 2048, // Default is 512
publicLoadBalancer: true // Default is false
});
For the purposes of this question, assume that I already have an example.org Hosted Zone in Route53 (my actual zone is, of course, different)
With the following CDK app:
export class MyExampleStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const backend = new Function(this, 'backendLambda', {
code: new AssetCode("lambda/"),
handler: "index.handler",
runtime: Runtime.PYTHON_3_8
});
apiDomainName = 'api.test.example.org'
const api = new LambdaRestApi(this, 'api', {
handler: backend,
proxy: true,
deploy: true,
domainName: {
domainName: apiDomainName,
certificate: new Certificate(this, 'apiCertificate', {
domainName: apiDomainName
})
}
});
}
}
, when I run cdk deploy, part of the output reads:
Outputs:
MyExampleStack.apiEndpoint0F54D2EA = https://<alphanumericId>.execute-api.us-east-1.amazonaws.com/prod/
, and, indeed, when I curl that url, I see the response I would expect from my Lambda code. I would expect curling api.test.example.org to give the same result - however, instead it gives curl: (6) Could not resolve host: api.test.example.org.
Based on this documentation, I tried:
export class MyExampleStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const rootDomain = 'example.org'
const zone = HostedZone.fromLookup(this, 'baseZone', {
domainName: rootDomain
});
const backend = new Function(...);
const api = new LambdaRestApi(...);
new ARecord(this, 'apiDNS', {
zone: zone,
recordName: 'api.test',
target: RecordTarget.fromAlias(new ApiGateway(api))
});
}
}
which did give a Route53 entry:
$ aws route53 list-hosted-zones
{
"HostedZones": [
{
"Id": "/hostedzone/ZO3B2N6W70PDD",
"Name": "example.org.",
"CallerReference": "598D71AB-4A98-EC5A-A170-D51CB243A2EA",
"Config": {
"PrivateZone": false
},
"ResourceRecordSetCount": 8
}
]
}
$ aws route53 list-resource-record-sets --hosted-zone-id /hostedzone/ZO3B2N6W70PDD --query 'ResourceRecordSets[?Name==`api.test.example.org.`]'
[
{
"Name": "api.test.example.org.",
"Type": "A",
"AliasTarget": {
"HostedZoneId": "Z1UJRXOUMOOFQ9",
"DNSName": "<alphanumericId2>.execute-api.us-east-1.amazonaws.com.",
"EvaluateTargetHealth": false
}
}
]
But this still doesn't work:
curl api.test.example.org still gives "Could not resolve host"
curl <alphanumericId2>.execute-api.us-east-1.amazonaws.com gives curl: (7) Failed to connect to <alphanumericId2>.execute-api.us-east-1.amazonaws.com port 80: Connection refused
curl https://<alphanumericId2>..execute-api.us-east-1.amazonaws.com gives {"message":"Forbidden"}
curl https://<alphanumericId>.[...] (i.e. the output from cdk deploy) still gives the expected response from the Lambda
How can I define a custom name in Route53 to route to my Lambda-backed APIGateway API?
Overall code LambdaRestApi with Route53 A Record, will create
Custom domain pointing to a particular stage prod i.e api.test.example.org domain to stage `prod'(example)
Route 53 A record for api.test.example.org pointing to Api Gateway hosted zone.
These are two combinations that will work
https://api.test.example.org will work pointing directly to stage prod.
CDK Output https://abcdef1234.execute-api.us-east-1.amazonaws.com/prod/ will work as stage is appended to it.
These are few combinations that will not work
Two other tests you did with http://
With no protocol, defaults to http, will not work, as we api gateway by default gives TLS 1.0 (ssl-https) and no http listener.
One other attempt you did with https:// without a stage name at the end, will return 403 forbidden, as stage name is missing.
Here is full CDK code.
import * as cdk from "#aws-cdk/core";
import * as apigw from "#aws-cdk/aws-apigateway";
import * as acm from "#aws-cdk/aws-certificatemanager";
import * as route53 from "#aws-cdk/aws-route53";
import * as route53Targets from "#aws-cdk/aws-route53-targets";
import * as lambda from "#aws-cdk/aws-lambda";
export class HelloCdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
this.buildLambdaApiGateway();
}
buildLambdaApiGateway() {
const rootDomain = "example.org";
const zone = route53.HostedZone.fromLookup(this, "baseZone", {
domainName: rootDomain,
});
const backend = new lambda.Function(this, "MyLayeredLambda", {
code: new lambda.InlineCode("foo"),
handler: "index.handler",
runtime: lambda.Runtime.NODEJS_10_X,
});
const restApi = new apigw.LambdaRestApi(this, "myapi", {
handler: backend,
domainName: {
domainName: `api-test.${rootDomain}`,
certificate: acm.Certificate.fromCertificateArn(
this,
"my-cert",
"arn:aws:acm:us-east-1:111112222333:certificate/abcd6805-1234-4159-ac38-761acdc700ef"
),
endpointType: apigw.EndpointType.REGIONAL,
},
});
new route53.ARecord(this, "apiDNS", {
zone: zone,
recordName: "api-test",
target: route53.RecordTarget.fromAlias(
new route53Targets.ApiGateway(restApi)
),
});
}
}