'Deploy a FargateService to an ECS that's living within a different Stack (preoject)

1- I have a project core-infra that encapasses all the core infra related compoents (VPCs, Subnets, ECS Cluster...etc)

2- I have microservice projects with independant stacks each used for deployment

  • I want to deploy a FargateService from a microservice project stack A to the already existing ECS living within the core-infra stack

Affected area/feature

  • Pulumi Service
  • ECS
  • Deploy microservice
  • FargateService

Pulumi github issue link



Solution 1:[1]

Pulumi Stack References are the answer here: https://www.pulumi.com/docs/intro/concepts/stack/#stackreferences

Your core-infra stack would output the ECS cluster ID and then stack B consumes that output so it can, for example, deploy an ECS service to the given cluster (https://www.pulumi.com/registry/packages/aws/api-docs/ecs/service/).

Solution 2:[2]

I was able to deploy using aws classic. PS: The setup is way more complex than with the awsx, the doc and resources aren't exhaustive.

Now I have few issues:

  • The Loadbalancer isn't reachable and keeps loading forever
  • I don't have any logs in the CloudWatch LogGoup
  • Not sure how to use the LB Listner with the ECS service / Not sure about the port mapping

Here is the complete code for reference (people who're husteling) and I'd appreciate if you could suggest any improvments/answers.

// Capture the EnvVars
const appName = process.env.APP_NAME;
const namespace = process.env.NAMESPACE;
const environment = process.env.ENVIRONMENT;

// Load the Deployment Environment config.
const configMapLoader = new ConfigMapLoader(namespace, environment);

const env = pulumi.getStack();
const infra = new pulumi.StackReference(`org/core-datainfra/${env}`);

// Fetch ECS Fargate cluster ID.
const ecsClusterId = infra.getOutput('ecsClusterId');

// Fetch DeVpc ID.
const deVpcId = infra.getOutput('deVpcId');

// Fetch DeVpc subnets IDS.
const subnets = ['subnet-aaaaaaaaaa', 'subnet-bbbbbbbbb'];

// Fetch DeVpc Security Group ID.
const securityGroupId = infra.getOutput('deSecurityGroupId');

// Define the Networking for our service.
const serviceLb = new aws.lb.LoadBalancer(`${appName}-lb`, {
    internal: false,
    loadBalancerType: 'application',
    securityGroups: [securityGroupId],
    subnets,
    enableDeletionProtection: false,
    tags: {
        Environment: environment
    }
});
const serviceTargetGroup = new aws.lb.TargetGroup(`${appName}-t-g`, {
    port: configMapLoader.configMap.service.http.externalPort,
    protocol: configMapLoader.configMap.service.http.protocol,
    vpcId: deVpcId,
    targetType: 'ip'
});
const http = new aws.lb.Listener(`${appName}-listener`, {
    loadBalancerArn: serviceLb.arn,
    port: configMapLoader.configMap.service.http.externalPort,
    protocol: configMapLoader.configMap.service.http.protocol,
    defaultActions: [
        {
            type: 'forward',
            targetGroupArn: serviceTargetGroup.arn
        }
    ]
});

// Create AmazonECSTaskExecutionRolePolicy
const taskExecutionPolicy = new aws.iam.Policy(
    `${appName}-task-execution-policy`,
    {
        policy: JSON.stringify({
            Version: '2012-10-17',
            Statement: [
                {
                    Effect: 'Allow',
                    Action: [
                        'ecr:GetAuthorizationToken',
                        'ecr:BatchCheckLayerAvailability',
                        'ecr:GetDownloadUrlForLayer',
                        'ecr:BatchGetImage',
                        'logs:CreateLogStream',
                        'logs:PutLogEvents',
                        'ec2:AuthorizeSecurityGroupIngress',
                        'ec2:Describe*',
                        'elasticloadbalancing:DeregisterInstancesFromLoadBalancer',
                        'elasticloadbalancing:DeregisterTargets',
                        'elasticloadbalancing:Describe*',
                        'elasticloadbalancing:RegisterInstancesWithLoadBalancer',
                        'elasticloadbalancing:RegisterTargets'
                    ],
                    Resource: '*'
                }
            ]
        })
    }
);

//  IAM role that allows Amazon ECS to make calls to the load balancer
const taskExecutionRole = new aws.iam.Role(`${appName}-task-execution-role`, {
    assumeRolePolicy: JSON.stringify({
        Version: '2012-10-17',
        Statement: [
            {
                Effect: 'Allow',
                Principal: {
                    Service: ['ecs-tasks.amazonaws.com']
                },
                Action: 'sts:AssumeRole'
            },
            {
                Action: 'sts:AssumeRole',
                Principal: {
                    Service: 'ecs.amazonaws.com'
                },
                Effect: 'Allow',
                Sid: ''
            },
            {
                Action: 'sts:AssumeRole',
                Principal: {
                    Service: 'ec2.amazonaws.com'
                },
                Effect: 'Allow',
                Sid: ''
            }
        ]
    }),
    tags: {
        name: `${appName}-iam-role`
    }
});

new aws.iam.RolePolicyAttachment(`${appName}-role-policy`, {
    role: taskExecutionRole.name,
    policyArn: taskExecutionPolicy.arn
});

// New image to be pulled
const image = `${configMapLoader.configMap.service.image.repository}:${process.env.IMAGE_TAG}`;

// Set up Log Group
const awsLogGroup = new aws.cloudwatch.LogGroup(`${appName}-awslogs-group`, {
    name: `${appName}-awslogs-group`,
    tags: {
        Application: `${appName}`,
        Environment: 'production'
    }
});

const serviceTaskDefinition = new aws.ecs.TaskDefinition(
    `${appName}-task-definition`,
    {
        family: `${appName}-task-definition`,
        networkMode: 'awsvpc',
        executionRoleArn: taskExecutionRole.arn,
        requiresCompatibilities: ['FARGATE'],
        cpu: configMapLoader.configMap.service.resources.limits.cpu,
        memory: configMapLoader.configMap.service.resources.limits.memory,
        containerDefinitions: JSON.stringify([
            {
                name: `${appName}-fargate`,
                image,
                cpu: parseInt(
                    configMapLoader.configMap.service.resources.limits.cpu
                ),
                memory: parseInt(
                    configMapLoader.configMap.service.resources.limits.memory
                ),
                essential: true,
                portMappings: [
                    {
                        containerPort: 80,
                        hostPort: 80
                    }
                ],
                environment: configMapLoader.getConfigAsEnvironment(),
                logConfiguration: {
                    logDriver: 'awslogs',
                    options: {
                        'awslogs-group': `${appName}-awslogs-group`,
                        'awslogs-region': 'us-east-2',
                        'awslogs-stream-prefix': `${appName}`
                    }
                }
            }
        ])
    }
);

// Create a Fargate service task that can scale out.
const fargateService = new aws.ecs.Service(`${appName}-fargate`, {
    name: `${appName}-fargate`,
    cluster: ecsClusterId,
    taskDefinition: serviceTaskDefinition.arn,
    desiredCount: 5,
    loadBalancers: [
        {
            targetGroupArn: serviceTargetGroup.arn,
            containerName: `${appName}-fargate`,
            containerPort: configMapLoader.configMap.service.http.internalPort
        }
    ],
    networkConfiguration: {
        subnets
    }
});

// Export the Fargate Service Info.
export const fargateServiceName = fargateService.name;
export const fargateServiceUrl = serviceLb.dnsName;
export const fargateServiceId = fargateService.id;
export const fargateServiceImage = image;

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mitch G
Solution 2