I have set up a basic express app for deployment on AWS ECS Fargate, using a simple dockerfile:
FROM node:18.0-alpine
WORKDIR '/app'
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 5000
CMD ["node", "index.js"]
with the following terraform file:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
provider "aws" {
region = "eu-west-2"
access_key = "XXXXX"
secret_key = "XXXXX"
}
resource "aws_ecr_repository" "prod_ecr_repo" {
name = "prod-ecr-repo"
}
resource "aws_ecs_cluster" "prod_ecs_cluster" {
name = "prod-ecs-cluster"
}
resource "aws_ecs_task_definition" "prod_ecs_task" {
family = "prod_ecs_task"
container_definitions = <<DEFINITION
[
{
"name": "prod_ecs_task",
"image": "${aws_ecr_repository.prod_ecr_repo.repository_url}",
"essential": true,
"portMappings": [
{
"containerPort": 5000,
"hostPort": 5000
}
],
"memory": 512,
"cpu": 256
}
]
DEFINITION
requires_compatibilities = ["FARGATE"] # use Fargate as the launch type
network_mode = "awsvpc" # add the AWS VPN network mode as this is required for Fargate
memory = 512 # Specify the memory the container requires
cpu = 256 # Specify the CPU the container requires
execution_role_arn = "${aws_iam_role.ecsTaskExecutionRole.arn}"
}
resource "aws_iam_role" "ecsTaskExecutionRole" {
name = "ecsTaskExecutionRole"
assume_role_policy = "${data.aws_iam_policy_document.assume_role_policy.json}"
}
data "aws_iam_policy_document" "assume_role_policy" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
resource "aws_iam_role_policy_attachment" "ecsTaskExecutionRole_policy" {
role = "${aws_iam_role.ecsTaskExecutionRole.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
resource "aws_default_vpc" "default_vpc" {
}
# Provide references to your default subnets
resource "aws_default_subnet" "default_subnet_a" {
# Use your own region here but reference to subnet 1a
availability_zone = "eu-west-2a"
}
resource "aws_default_subnet" "default_subnet_b" {
# Use your own region here but reference to subnet 1b
availability_zone = "eu-west-2b"
}
resource "aws_alb" "application_load_balancer" {
name = "load-balancer-dev"
load_balancer_type = "application"
subnets = [ # Referencing the default subnets
"${aws_default_subnet.default_subnet_a.id}",
"${aws_default_subnet.default_subnet_b.id}"
]
# security group
security_groups = ["${aws_security_group.load_balancer_security_group.id}"]
}
resource "aws_security_group" "load_balancer_security_group" {
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # Allow traffic in from all sources
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_lb_target_group" "target_group" {
name = "target-group"
port = 80
protocol = "HTTP"
target_type = "ip"
vpc_id = "${aws_default_vpc.default_vpc.id}" # default VPC
}
resource "aws_lb_listener" "listener" {
load_balancer_arn = "${aws_alb.application_load_balancer.arn}" # load balancer
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = "${aws_lb_target_group.target_group.arn}" # target group
}
}
resource "aws_ecs_service" "app_service" {
name = "app-first-service" # Name the service
cluster = "${aws_ecs_cluster.prod_ecs_cluster.id}" # Reference the created Cluster
task_definition = "${aws_ecs_task_definition.prod_ecs_task.arn}" # Reference the task that the service will spin up
launch_type = "FARGATE"
desired_count = 3 # Set up the number of containers to 3
load_balancer {
target_group_arn = "${aws_lb_target_group.target_group.arn}" # Reference the target group
container_name = "${aws_ecs_task_definition.prod_ecs_task.family}"
container_port = 5000 # Specify the container port
}
network_configuration {
subnets = ["${aws_default_subnet.default_subnet_a.id}", "${aws_default_subnet.default_subnet_b.id}"]
assign_public_ip = true # Provide the containers with public IPs
security_groups = ["${aws_security_group.service_security_group.id}"] # Set up the security group
}
}
resource "aws_security_group" "service_security_group" {
ingress {
from_port = 0
to_port = 0
protocol = "-1"
# Only allowing traffic in from the load balancer security group
security_groups = ["${aws_security_group.load_balancer_security_group.id}"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
output "app_url" {
value = aws_alb.application_load_balancer.dns_name
}
When I visit the load balancer i get a 504 the first time and then consecutive 503's. If i look at the ECS console, I see a bunch of failed deployments, but no clear reason as to why. What would be a good way to start investigating this?
You need to change the aws_lb_target_group
resource's port to 5000
. That needs to match the port that the target is listening for traffic on.