AWS CDK ignores taskSubnets selection - flask

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

Related

AWS CDK: How to integrate API gateway with ApplicationLoadBalancedFargateService in private subnets?

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,
},
}
)
);

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 }]

Fargate Service Cannot Write to DynamoDB Table (CDK)

I'm working through setting up a new infrastructure with the AWS CDK and I'm trying to get a TypeScript app running in Fargate to be able to read/write from/to a DynamoDB table, but am hitting IAM issues.
I have both my Fargate service and my DynamoDB Table defined, and both are running as they should be in AWS, but whenever I attempt to write to the table from my app, I am getting an access denied error.
I've tried the solutions defined in this post, as well as the ones it links to, but nothing seems to be allowing my container to write to the table. I've tried everything from setting table.grantReadWriteData(fargateService.taskDefinition.taskRole) to the more complex solutions described in the linked articles of defining my own IAM policies and setting the effects and actions, but I always just get the same access denied error when attempting to do a putItem:
AccessDeniedException: User: {fargate-service-arn} is not authorized to perform: dynamodb:PutItem on resource: {dynamodb-table} because no identity-based policy allows the dynamodb:PutItem action
Am I missing something, or a crucial step to make this possible?
Any help is greatly appreciated.
Thanks!
Edit (2022-09-19):
Here is the boiled down code for how I'm defining my Vpc, Cluster, Container Image, FargateService, and Table.
export class FooCdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const vpc = new Vpc(this, 'FooVpc', {
maxAzs: 2,
natGateways: 1
});
const cluster = new Cluster(this, 'FooCluster', { vpc });
const containerImage = ContainerImage.fromAsset(
path.join(__dirname, '/../app'),
{
platform: Platform.LINUX_AMD64 // I'm on an M1 Mac and images weren't working appropriately without this
}
);
const fargateService = new ApplicationLoadBalancedFargateService(
this,
'FooFargateService',
{
assignPublicIp: true,
cluster,
memoryLimitMiB: 1024,
cpu: 512,
desiredCount: 1,
taskImageOptions: {
containerPort: PORT,
image: containerImage
}
}
);
fargateService.targetGroup.configureHealthCheck({ path: '/health' });
const serverTable = new Table(this, 'FooTable', {
billingMode: BillingMode.PAY_PER_REQUEST,
removalPolicy: cdk.RemovalPolicy.DESTROY,
partitionKey: { name: 'id', type: AttributeType.STRING },
pointInTimeRecovery: true
});
serverTable.grantReadWriteData(fargateService.taskDefinition.taskRole);
}
}
Apparently either order in which the resources are defined matters, or the inclusion of a property from the table in the Fargate service is what did the trick. I moved the table definition up above the Fargate service and included an environment variable holding the table name and it's working as intended now.
export class FooCdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const vpc = new Vpc(this, 'FooVpc', {
maxAzs: 2,
natGateways: 1
});
const cluster = new Cluster(this, 'FooCluster', { vpc });
const containerImage = ContainerImage.fromAsset(
path.join(__dirname, '/../app'),
{
platform: Platform.LINUX_AMD64 // I'm on an M1 Mac and images weren't working appropriately without this
}
);
const serverTable = new Table(this, 'FooTable', {
billingMode: BillingMode.PAY_PER_REQUEST,
removalPolicy: cdk.RemovalPolicy.DESTROY,
partitionKey: { name: 'id', type: AttributeType.STRING },
pointInTimeRecovery: true
});
const fargateService = new ApplicationLoadBalancedFargateService(
this,
'FooFargateService',
{
assignPublicIp: true,
cluster,
memoryLimitMiB: 1024,
cpu: 512,
desiredCount: 1,
taskImageOptions: {
containerPort: PORT,
image: containerImage,
environment: {
SERVER_TABLE_NAME: serverTable.tableName
}
}
}
);
fargateService.targetGroup.configureHealthCheck({ path: '/health' });
serverTable.grantReadWriteData(fargateService.taskDefinition.taskRole);
}
}
Hopefully this helps someone in the future who may come across the same issue.

Using secrets from AWS Secrets Manager in a CDK stack using ECS + Fargate

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
});

ec2.SubnetType deploying to 2AZs rather than 3

I'm having issues trying to deploy 3 subnets to a VPC using AWS CDK. Subnets are only deployed to 2x AZs instead of the default 3x
Goal:
To deploy an AWS VPC with 3x Isolated subnets using AWS CDK
Expect Results:
Using the costruct prop subnetType: ec2.SubnetType.ISOLATED within the VPC construct to create 3x isolated AZs
Actual Results:
Subnets are only deployed to 2AZs
Code:
import * as cdk from '#aws-cdk/core';
import ec2 = require('#aws-cdk/aws-ec2');
export class CdkWorkshopStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
new ec2.Vpc(this, 'VPC', {
cidr: '10.0.0.0/16',
maxAzs: 3,
subnetConfiguration: [
{
cidrMask: 28,
name: 'Private Subnet',
subnetType: ec2.SubnetType.ISOLATED,
}
]
});
}
}
Found the answer:
'In an environment-agnostic stack, any constructs that use availability zones will see two of them'
So I would have to explicitly define the environment of my stacks.
Documentation here - https://docs.aws.amazon.com/cdk/latest/guide/environments.html
Example Code:
new TestStack(app, 'TestStack', {
env: {
region: "eu-central-1",
account: "XXXXXXXX"
}
});