AWS Subnet is out of IP Addresses (using CDK) - amazon-web-services

I've been trying de deploy a RestApiGateway with 20 lambda functions since a while now, and I keep getting the error message Subnet is out of IP addresses every time I try to deploy. Any idea why this may be happening?
Here's how my Vpc stack looks like:
this.vpc = new ec2.Vpc(this, 'vpc', {
cidr: '10.0.0.0/21',
maxAzs: this.azs,
natGateways: this.azs,
subnetConfiguration: [
{
subnetType: ec2.SubnetType.PUBLIC,
name: `${APP_PREFIX}-public-subnet`,
cidrMask: 24,
},
{
subnetType: ec2.SubnetType.PRIVATE,
name: `${APP_PREFIX}-private-subnet`,
cidrMask: 28,
},
],
});
...and here's the gateway:
const interfaceVpcEndpoint = vpc.addInterfaceEndpoint(`${APP_PREFIX}-endpoint`, {
service: ec2.InterfaceVpcEndpointAwsService.APIGATEWAY,
subnets: { subnetType: ec2.SubnetType.PRIVATE },
});
const restApiLogGroup = new logs.LogGroup(stack, `${APP_PREFIX}-log-group`, {
logGroupName: `${APP_PREFIX}-log-group`,
});
return new gateway.RestApi(stack, `${APP_PREFIX}-rest-api`, {
restApiName: `${APP_PREFIX}-rest-api`,
endpointConfiguration: {
types: [gateway.EndpointType.PRIVATE],
vpcEndpoints: [interfaceVpcEndpoint],
},
retainDeployments: true,
deployOptions: {
stageName: 'v2',
loggingLevel: gateway.MethodLoggingLevel.INFO,
dataTraceEnabled: true,
accessLogDestination: new gateway.LogGroupLogDestination(restApiLogGroup),
accessLogFormat: gateway.AccessLogFormat.jsonWithStandardFields(),
},
policy: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.DENY,
principals: [new iam.AnyPrincipal()],
actions: ['execute-api:Invoke'],
resources: ['execute-api:/*/POST/*'],
conditions: {
StringNotEquals: {
'aws:sourceVpc': vpc.vpcId,
},
},
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
principals: [new iam.AnyPrincipal()],
actions: ['execute-api:Invoke'],
resources: ['execute-api:/*/POST/*'],
}),
],
}),
});
The idea is that the lambda functions should not be accessible from the internet, only from a Fargate service that I will deploy via another stack.

I think this is the problem:
cidrMask: 28
A /28 network allows 16 IP addresses, and AWS takes 3 of those addresses for itself.
I'm not familiar with the CDK classes to create a subnet, so can't say whether the overall approach that you're using is correct. I'm assuming that it's smart enough to break the VPC CIDR into subnet blocks based on what you provide for a mask.
I recommend giving at least a /24 allocation to each subnet; I prefer a /20. And a /16 to the VPC. Network configuration is free when you start, extremely expensive to correct later.

I ended up explicitly adding the default security group created by the VPC Construct to each Lambda and that worked.

Related

Export CDK Generated InternetGateway Resource [AWS Cloudformation]

So I have a basic VPC setup on CDK, after running cdk synth I see that an AWS::EC2::InternetGateway construct has been generated in my template under cdk.out. The problem is that I need to export this resource to another stack, but I can't reference this.vpc.internetGatewayId in my main .ts cdk file to export.
I know that this resource has been created, so I won't use CfnInternetGateway to create another, but at the same time this.vpc.internetGatewayId seems to have returned undefined even though I have a public subnet created and the resulting resource is in the template file.
Below is my VPC construct:
this.vpc = new ec2.Vpc(this, 'VPC', {
cidr: CONFIG.REGIONAL_CONFIG[region].VPC_CIDR,
maxAzs: CONFIG.REGIONAL_CONFIG[region].AVAILABILITY_ZONES,
subnetConfiguration: [
{
name: 'public',
subnetType: ec2.SubnetType.PUBLIC,
},
{
name: 'private',
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
},
{
name: 'isolated',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
],
});
Create a CloudFormation output on the exporting stack with with a CfnOutput:
// MyExportingStack.ts
// Cause the synth to fail if the ID is undefined. Alternatively, throw an error.
if (!vpc.internetGatewayId)
cdk.Annotations.of(this).addError('An Internet Gateway ID is required!');
// Create the CloudFormation Output
new cdk.CfnOutput(this, 'IgwId', { value: vpc.internetGatewayId ?? 'MISSING', exportName: 'my-igw-id',});

AWS CDK Getting Error when try to initialize a new VPC with private isolated subnet used in Fargate Cluster

AWS CDK Getting Error when try to initialize a new VPC with private isolated subnet used in Fargate Cluster. (#aws-cdk/ -- #1.174.0 - Version).
this.vpc = new ec2.Vpc(this, `horizonCloudVpc`, {
cidr: '10.0.0.0/16',
vpcName: `horizonCloudVpc-${envName}`,
enableDnsHostnames: true,
enableDnsSupport: true,
maxAzs: 2,
subnetConfiguration: [
{
name: 'public-subnet',
subnetType: ec2.SubnetType.PUBLIC,
cidrMask: 24,
},
{
name: 'isolated-subnet',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
cidrMask: 24,
},
]
});
const clusterAdmin = new Role(this, 'eksClusterMasterRole', {
roleName: `clusterMasterRole-${envName}`,
assumedBy: new AccountRootPrincipal(),
});
const cluster = new eks.FargateCluster(this, 'horizonCloudEks', {
version: eks.KubernetesVersion.V1_21,
vpc: vpc,
clusterName: `horizonCloudEks-${envName}`,
endpointAccess: eks.EndpointAccess.PUBLIC,
mastersRole: clusterAdmin,
});
Error from Deploy -
/home/runner/work/horizon/horizon/cdk/node_modules/#aws-cdk/aws-ec2/lib/vpc.ts:606
throw new Error(`There are no '${subnetType}' subnet groups in this VPC. Available types: ${availableTypes}`);
^
Error: There are no 'Private' subnet groups in this VPC. Available types: Isolated,Deprecated_Isolated,Public
I might think it requires a PRIVATE_WITH_NAT subnet also.
Thanks!
This is happening because EKS is trying to make the cluster use Private and Public subnets in the VPC, and there are no Private subnets in it.
From the eks.FargateCluster docs:
vpcSubnets?
Type: SubnetSelection[] (optional, default: All public and private subnets)
To change this default behavior, change the vpcSubnets constructor prop to the appropriate value. For example, to make the cluster only use Public subnets:
vpcSubnets: [{ subnetType: ec2.SubnetType.PUBLIC }]

ECS task unable to pull secrets or registry auth

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.

Why does CDK complain about missing public subnet groups?

This is my current CDK stack:
const vpc = new ec2.Vpc(this, "vpc-staging", {
maxAzs: 1,
enableDnsHostnames: true,
enableDnsSupport: true,
cidr: '10.10.0.0/16',
subnetConfiguration: []
});
const publicSubnet = new ec2.Subnet(this, 'public-subnet', {
cidrBlock: '10.10.10.0/24',
vpcId: vpc.vpcId,
mapPublicIpOnLaunch: true
})
To the above I am trying to add an ECS cluster like so:
const cluster = new ecs.Cluster(this, 'EcsCluster', { vpc });
cluster.addCapacity('DefaultAutoScalingGroup', {
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO)
})
When running cdk diff this is the error that I get:
(node:48942) ExperimentalWarning: The fs.promises API is experimental
/Users/me/src/wow/aws/node_modules/#aws-cdk/aws-ec2/lib/vpc.js:201
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:
What is it that that I am missing from my config?
mapPublicIpOnLaunch: true is not sufficient for a subnet to be considered Public.
You also need an Internet Gateway which is attached to your VPC. In addition, route tables should be setup to route internet traffic 0.0.0.0/0 to the gateway.
General information about VPC, public and private subnets is here.
Hope this helps.
The mapPublicIpOnLaunch parameter has a default set to true, so you could simply not set any subnet at all by simply removing the subnetConfiguration line from the vpc and deleting the subnet object to leave the default creation.
If you really want to set them, add subnetType: ec2.SubnetType.PUBLIC to your subnet.
Also, I think it's better if you keep the subnet config on the vpc construct to wire everything on the vpc creation:
const vpc = new ec2.Vpc(this, "vpc-staging", {
maxAzs: 1,
enableDnsHostnames: true,
enableDnsSupport: true,
cidr: '10.10.0.0/16',
subnetConfiguration: [
{
cidrMask: 24, // this is optional as it divides equally if not set
name: 'public-subnet',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'private-subnet',
subnetType: ec2.SubnetType.PRIVATE,
},
...
]
});
Also, I'm not 100% sure, but this code might fail because ECS need at least two availability zones to work.

Get existing VPC for use within a Pulumi stack

I'm trying to use Pulumi within a somewhat restricted AWS environment.
This sandbox requires that I use a specific VPC, and there is no default VPC.
I have tried the examples showing how to reference an existing VPC, but they all fail with some variant of "invoking aws:ec2/getVpc:getVpc: no matching VPC found"
#pulumi/awsx, using code referenced from: https://github.com/pulumi/pulumi-awsx/issues/522:
const vpc = awsx.ec2.Vpc.fromExistingIds('name', {
vpcId: 'id',
publicSubnetIds: ['a', 'b'],
privateSubnetIds: ['a', 'b']
})
#pulumi/aws, using code referenced from https://www.pulumi.com/docs/reference/pkg/aws/ec2/getvpc/:
const vpc = aws.ec2.Vpc.get('vpc-1', 'vpc-1')
Question: what is the correct and complete syntax for referencing an existing VPC within a Pulumi stack?
Note that I would rather not "adopt" this resource as it is shared and the user running the pulumi up command does not have permission to delete VPC resources.
There is a subtle difference between getVpc() that you linked to and Vpc.get() that you tried using. You should use the former:
const vpc = aws.ec2.getVpc({ id: yourVpcId });
This is what worked in the end:
const vpc = aws.ec2.Vpc.get('vpc-123', 'vpc-123')
I don't think I had saved my file correctly before pulumi up after making the above change.
Note that I also had to add subnets manually to my ALB to get this working, as below:
const vpc = aws.ec2.Vpc.get('vpc-123', 'vpc-123')
const clusterName = nameResource('graphQlServiceCluster')
const ecsCluster = new awsx.ecs.Cluster(clusterName, {
name: clusterName,
vpc
})
const PublicSubnet1a = 'subnet-123'
const PublicSubnet1b = 'subnet-123'
const alb = new awsx.lb.ApplicationLoadBalancer(nameResource('graphQlServiceElb'), {
name: nameResource('graphQlServiceElb'),
external: true,
vpc,
subnets: [
PublicSubnet1a,
PublicSubnet1b
]
})
const listener = alb.createListener(nameResource('graphqlServiceListener'), {
name: nameResource('graphqlServiceListener'),
port: 80,
external: true,
vpc
})
Pulumi has multiple Vpc types. You probably want to use the awsx VPC as it's higher level (and required to use other awsx infrastructure).
There's two ways to do this:
Creating a new VPC
const vpc = new awsx.ec2.Vpc(config.vpcName, {
cidrBlock: "10.0.0.0/16",
subnets: [
{
name: "public",
type: "public",
location: {
cidrBlock: "10.0.0.0/24",
availabilityZone: "us-east-2a",
},
},
{
name: "private-a",
type: "private",
location: {
cidrBlock: "10.0.1.0/24",
availabilityZone: "us-east-2a",
},
},
{
name: "private-b",
type: "private",
location: {
cidrBlock: "10.0.2.0/24",
availabilityZone: "us-east-2b",
},
},
],
});
Using an existing VPC
Borrowing from this GitHub thread with the Pulumi CTO produced a correct result:
const vpc = awsx.ec2.Vpc.fromExistingIds("mycompany", {
vpcId: "vpc-myvpcid",
});
// Create an ECS Fargate cluster.
const ecsCluster = new awsx.ecs.Cluster("mycompany-pulumi-cluster", {
vpc,
});