CDKv2: ECS with EC2 launch type stuck in AWS::ECS::Service CREATE_IN_PROGRESS

0

Why doesn't this work? It gets stuck in AWS::ECS::Service CREATE_IN_PROGRESS:

import * as cdk from 'aws-cdk-lib';
import { StackProps } from 'aws-cdk-lib';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as servicediscovery from 'aws-cdk-lib/aws-servicediscovery';
import * as ssm from 'aws-cdk-lib/aws-ssm';
import { Construct } from 'constructs';
import * as autoscaling from 'aws-cdk-lib/aws-autoscaling';

interface EcsDeployStackProps extends StackProps {
    dockerImageUriParam: string;
}

export class DeployStack extends cdk.Stack {
    constructor(scope: Construct, id: string, props: EcsDeployStackProps) {
        super(scope, id, props);

        const vpc = new ec2.Vpc(this, 'LosAngelesVPC', {
            cidr: '10.210.0.0/16',
            availabilityZones: ['us-west-2-lax-1a', 'us-west-2-lax-1b'],
            subnetConfiguration: [
                {
                    cidrMask: 24,
                    name: 'lax-public-subnet-1',
                    subnetType: ec2.SubnetType.PUBLIC,
                },
                {
                    cidrMask: 24,
                    name: 'lax-public-subnet-2',
                    subnetType: ec2.SubnetType.PUBLIC,
                },
                {
                    cidrMask: 24,
                    name: 'lax-private-subnet-1',
                    subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
                },
                {
                    cidrMask: 24,
                    name: 'lax-private-subnet-2',
                    subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
                }
            ],
        });

        // Create an ECS cluster
        const cluster = new ecs.Cluster(this, 'Cluster', {
            vpc: vpc,
        });

        // Create a launch template
        const userData = ec2.UserData.forLinux();
        userData.addCommands(
            "echo ECS_CLUSTER=" + cluster.clusterName + " >> /etc/ecs/ecs.config",
            "yum install -y aws-cfn-bootstrap",
        );
        const launchTemplate = new ec2.LaunchTemplate(this, 'LaunchTemplate', {
            instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
            machineImage: ecs.EcsOptimizedImage.amazonLinux2(),
            userData: userData,
            role: new iam.Role(this, 'InstanceRole', {
                assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
                managedPolicies: [
                    iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonEC2ContainerServiceforEC2Role'),
                ],
            }),
        });

        // Create an Auto Scaling group using the launch template
        const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'ASG', {
            vpc,
            launchTemplate: launchTemplate,
            minCapacity: 1,
            maxCapacity: 2,
        });

        const capacityProvider = new ecs.AsgCapacityProvider(this, 'AsgCapacityProvider', {
            autoScalingGroup,
        });
        cluster.addAsgCapacityProvider(capacityProvider);

        // Create a Cloud Map namespace for service discovery
        const namespace = new servicediscovery.PrivateDnsNamespace(this, 'Namespace', {
            name: 'search-namespace.local',
            vpc: cluster.vpc,
        });

        const dockerImageUriParameter = ssm.StringParameter.fromStringParameterName(this, 'DockerImageUriParam', props.dockerImageUriParam);

        const executionRole = new iam.Role(this, 'TaskExecutionRole', {
            assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
        });

        executionRole.addManagedPolicy(
            iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy')
        );

        // Create a task definition for your application
        const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDefinition', {
            executionRole: executionRole,
        });

        // Add your application container to the task definition
        const appContainer = taskDefinition.addContainer('AppContainer', {
            image: ecs.ContainerImage.fromRegistry(dockerImageUriParameter.stringValue),
            memoryLimitMiB: 512,
            cpu: 256,
            portMappings: [{ containerPort: 8080 }],
        });

        // Create an EC2 service
        const service = new ecs.Ec2Service(this, 'Service', {
            cluster,
            taskDefinition,
            desiredCount: 1,
            cloudMapOptions: {
                name: 'search-service',
                cloudMapNamespace: namespace,
                dnsRecordType: servicediscovery.DnsRecordType.SRV,
            },
        });

        const scaling = service.autoScaleTaskCount(
            { maxCapacity: 2, minCapacity: 1 });
        scaling.scaleOnCpuUtilization('CpuScaling', {
            targetUtilizationPercent: 80,
        });

        // Create a CloudFront distribution with the service as the origin
        // const distribution = new cloudfront.Distribution(this, 'Distribution', {
        //     defaultBehavior: {
        //         origin: new origins.HttpOrigin(`search-service.${namespace.namespaceName}`, {
        //             protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
        //         }),
        //         viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        //         allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
        //         cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
        //         compress: true,
        //     },
        //     httpVersion: cloudfront.HttpVersion.HTTP3,
        // });
    }
}
1 Answer
1
Accepted Answer

Hello.

Probably, but I suspect that the ECS task is not starting properly.
If EC2 is started in a private subnet (ec2.SubnetType.PRIVATE_ISOLATED) in a VPC subnet, there will be no access route to ECR, so I thought it would be necessary to configure a NAT Gateway and VPC endpoint.
https://repost.aws/knowledge-center/cloudformation-ecs-service-stabilize

So why not change the private subnet as shown below to use NAT Gateway?
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.SubnetType.html

ec2.SubnetType.PRIVATE_WITH_NAT
profile picture
EXPERT
answered a month ago
profile picture
EXPERT
reviewed a month ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions