I had this issue, and eventually sorted it out.
My solution below is to:
- Set up the ECS in private subnet
- Add AWS PrivateLink endpoints in VPC
Post my CDK code here for reference. I pasted some documentation links in the function comments for you to better understand its purpose.
This is the EcsStack:
export class EcsStack extends Stack {
constructor(scope: cdk.App, id: string, props: EcsStackProps) {
super(scope, id, props);
this.createOrderServiceCluster(props.vpc);
}
private createOrderServiceCluster(serviceVpc:ec2.IVpc) {
const ecsClusterName = "EcsClusterOfOrderService";
const OrderServiceCluster = new ecs.Cluster(this, ecsClusterName, {
vpc: serviceVpc,
clusterName: ecsClusterName
});
// Now ApplicationLoadBalancedFargateService just pick a randeom private subnet.
// https://github.com/aws/aws-cdk/issues/8621
new ecs_patterns.ApplicationLoadBalancedFargateService(this, "FargateOfOrderService", {
cluster: OrderServiceCluster, // Required
cpu: 512, // Default is 256
desiredCount: 1, // Default is 1
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry("12345.dkr.ecr.us-east-1.amazonaws.com/comics:user-service"),
taskRole: this.createEcsTaskRole(),
executionRole: this.createEcsExecutionRole(),
containerPort: 8080
},
memoryLimitMiB: 2048, // Default is 512
// creates a public-facing load balancer that we will be able to call
// from curl or our web browser. This load balancer will forward calls
// to our container on port 8080 running inside of our ECS service.
publicLoadBalancer: true // Default is false
});
}
/**
* This IAM role is the set of permissions provided to the ECS Service Team to execute ECS Tasks on your behalf.
* It is NOT the permissions your application will have while executing.
* https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html
* @private
*/
private createEcsExecutionRole() : iam.IRole {
const ecsExecutionRole = new iam.Role(this, 'EcsExecutionRole', {
//assumedBy: new iam.ServicePrincipal(ecsTasksServicePrincipal),
assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
roleName: "EcsExecutionRole",
});
ecsExecutionRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryReadOnly'));
ecsExecutionRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLogsFullAccess'));
return ecsExecutionRole;
}
/**
* Creates the IAM role (with all the required permissions) which will be used by the ECS tasks.
* https://docs.aws.amazon.com/AmazonECS/latest/developerguide/instance_IAM_role.html
* @private
*/
private createEcsTaskRole(): iam.IRole {
const ecsTaskRole = new iam.Role(this, 'OrderServiceEcsTaskRole', {
//assumedBy: new iam.ServicePrincipal(ecsTasksServicePrincipal),
assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
roleName: "OrderServiceEcsTaskRole",
});
ecsTaskRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryReadOnly'));
ecsTaskRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLogsFullAccess'));
ecsTaskRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonS3ReadOnlyAccess'));
return ecsTaskRole;
}
}
This is code snippet of the VpcStack:
export class VpcStack extends Stack {
readonly coreVpc : ec2.Vpc;
constructor(scope: cdk.App, id: string) {
super(scope, id);
this.coreVpc = new ec2.Vpc(this, "CoreVpc", {
cidr: '10.0.0.0/16',
natGateways: 1,
enableDnsHostnames: true,
enableDnsSupport: true,
maxAzs: 3,
subnetConfiguration: [
{
cidrMask: 28,
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE,
}
]
});
this.setupInterfaceVpcEndpoints();
}
/**
* Builds VPC endpoints to access AWS services without using NAT Gateway.
* @private
*/
private setupInterfaceVpcEndpoints(): void {
// Allow ECS to pull Docker images without using NAT Gateway
// https://docs.aws.amazon.com/AmazonECR/latest/userguide/vpc-endpoints.html
this.addInterfaceEndpoint("ECRDockerEndpoint", ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER);
this.addInterfaceEndpoint("ECREndpoint", ec2.InterfaceVpcEndpointAwsService.ECR);
this.addInterfaceEndpoint("SecretManagerEndpoint", ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER);
this.addInterfaceEndpoint("CloudWatchEndpoint", ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH);
this.addInterfaceEndpoint("CloudWatchLogsEndpoint", ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS);
this.addInterfaceEndpoint("CloudWatchEventsEndpoint", ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_EVENTS);
this.addInterfaceEndpoint("SSMEndpoint", ec2.InterfaceVpcEndpointAwsService.SSM);
}
private addInterfaceEndpoint(name: string, awsService: ec2.InterfaceVpcEndpointAwsService): void {
const endpoint: ec2.InterfaceVpcEndpoint = this.coreVpc.addInterfaceEndpoint(`${name}`, {
service: awsService
});
endpoint.connections.allowFrom(ec2.Peer.ipv4(this.coreVpc.vpcCidrBlock), endpoint.connections.defaultPort!);
}
}